Enviar mensagens do bacula via ICQ

Para quem é da década de 80 deve se lembrar muito bem do ICQ, um famoso aplicativo de comunicação instantânea que foi o precursor do MSN Messenger. Pertence à companhia Mail.ru Group. Foi um dos primeiros programas de mensagem instantânea da internet, criado em 1996. A sigla “ICQ” é um acrónimo feito com base na pronúncia das letras em inglês (I Seek You), em português, “Eu procuro você”, porém é popularmente conhecido no Brasil como “i-cê-quê”. Fonte: (Wikipedia).

Existe uma classe PHP chamada WebIcqLite que é utilizada para enviar mensagens para o ICQ usando PHP.

Caso não possua um conta do ICQ ainda, baixe o aplicativo e instale em seu smartphone ou em seu computador, crie uma nova conta e anote o seu UIN (identificador único do ICQ)
https://icq.com/windows/pt

Criar um diretório conforme abaixo

mkdir /usr/src/webicqlite

Copiar o conteúdo da classe PHP abaixo para um arquivo chamado “WebIcqLite.class.php” e salvar em “/usr/src/webicqlite”

<?php
/*
* http://wip.asminog.com/projects/icq/WebIcqLite.class.phps
* WebIcqLite: ICQ messages sender. v3.2b
* (C) 2006 Sergey Akudovich, http://intrigue.ru/
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* See http://www.gnu.org/copyleft/lesser.html
*
*/

class WebIcqLite_TLV {
    var $type;
    var $size;
    var $error;
    
    var $types = array
    (
        'UIN'                 =>  1, // 0x01
        'DATA'                =>  2, // 0x02
        'CLIENT'            =>  3, // 0x03
        'ERROR_URL'            =>  4, // 0x04
        'RECONECT_HERE'        =>  5, // 0x05
        'COOKIE'            =>  6, // 0x06
        'SNAC_VERSION'        =>  7, // 0x07
        'ERROR_SUBCODE'        =>  8, // 0x08
        'DISCONECT_REASON'    =>  9, // 0x09
        'RECONECT_HOST'        => 10, // 0x0A
        'URL'                => 11, // 0x0B
        'DEBUG_DATA'        => 12, // 0x0C
        'SERVICE'            => 13, // 0x0D
        'CLIENT_COUNTRY'    => 14, // 0x0E
        'CLIENT_LNG'        => 15, // 0x0F
        'SCRIPT'            => 16, // 0x10
        'USER_EMAIL'        => 17, // 0x11
        'OLD_PASSWORD'        => 18, // 0x12
        'REG_STATUS'        => 19, // 0x13
        'DISTRIB_NUMBER'    => 20, // 0x14
        'PERSONAL_TEXT'        => 21, // 0x15
        'CLIENT_ID'            => 22, // 0x16
        'CLI_MAJOR_VER'     => 23, // 0x17
        'CLI_MINOR_VER'     => 24, // 0x18
        'CLI_LESSER_VER'     => 25, // 0x19
        'CLI_BUILD_NUMBER'    => 26, // 0x1A
//        'PASSWORD'            => 37
    );
    
    function setTLV($type, $value, $length = false)
    {
        switch ($length) 
        {
            case 1:
                $format = 'c';
                break;
            case 2:
                $format = 'n';
                break;
            case 4:
                $format = 'N';
                break;
            default:
                $format = 'a*';
                break;
        }
        if ($length === false) 
        {
            $length = strlen($value);
        }
        return pack('nn'.$format, $this->types[$type], $length, $value);
    }
    
    function getTLV($data)
    {
        $arr = unpack('n2', substr($data, 0, 4));
        $this->type = $arr[1];
        $this->size = $arr[2];
        return substr($data, 4, $this->size);
    }

    function getTLVFragment($data)
    {
        $frg = unpack('cid/cversion/nsize', substr($data, 0, 4));
        $frg['data'] = substr($data, 4, $frg['size']);
        return $frg;
    }
}

class WebIcqLite_SNAC extends WebIcqLite_TLV {
    
    var $request_id = 0;
    var $uin;
    
    function setSNAC0102()
    {
        $this->request_id++;
        $out = pack('nnnN', 1, 2, 0, $this->request_id);
        $out .= pack('n*', 1, 3, 272, 650);
        $out .= pack('n*', 2, 1, 272, 650);
        $out .= pack('n*', 3, 1, 272, 650);
        $out .= pack('n*', 21, 1, 272, 650);
        $out .= pack('n*', 4, 1, 272, 650);
        $out .= pack('n*', 6, 1, 272, 650);
        $out .= pack('n*', 9, 1, 272, 650);
        $out .= pack('n*', 10, 1, 272, 650);
        
        return $out;
    }
    
    function setSNAC0406($uin, $message)
    {
        $this->request_id++;
        $cookie = microtime();
        $out = pack('nnnNdnca*', 4, 6, 0, $this->request_id, $cookie, 2, strlen($uin), $uin);
        
        $capabilities = pack('H*', '094613494C7F11D18222444553540000'); // utf-8 support
        // '97B12751243C4334AD22D6ABF73F1492' rtf support
        
        $data = pack('nd', 0, $cookie).$capabilities;
        $data .= pack('nnn', 10, 2, 1);
        $data .= pack('nn', 15, 0);
        $data .= pack('nnvvddnVn', 10001, strlen($message)+62, 27, 8, 0, 0, 0, 3, $this->request_id);
        $data .= pack('nndnn', 14, $this->request_id, 0, 0, 0); //45
        $data .= pack('ncvnva*', 1, 0, 0, 1, (strlen($message)+1), $message);
        $data .= pack('H*', '0000000000FFFFFF00');
        $out .= $this->setTLV('RECONECT_HERE', $data);
        $out .= $this->setTLV('CLIENT', '');
        return $out;
    }
    
    function setSNAC0406offline($uin, $message)
    {
        $this->request_id++;
        $cookie = microtime();
        $out = pack('nnnNdnca*', 4, 6, 0, $this->request_id, $cookie, 1, strlen($uin), $uin);
        
        $data = pack('ccnc', 5, 1, 1, 1);
        $data .= pack('ccnnna*', 1, 1, strlen($message)+4, 3, 0, $message);
        $out .= $this->setTLV('DATA', $data);
        $out .= $this->setTLV('CLIENT', '');
        $out .= $this->setTLV('COOKIE', '');
        return $out;
    }
    
    function getSNAC0407($body)
    {
        if (strlen($body)) 
        {
            $msg = unpack('nfamily/nsubtype/nflags/Nrequestid/N2msgid/nchannel/cnamesize', $body);
            if ($msg['family'] == 4 && $msg['subtype'] == 7) 
            {
                $body = substr($body, 21);
                $from = substr($body, 0, $msg['namesize']);
                $channel = $msg['channel'];
                $body = substr($body, $msg['namesize']);
                $msg = unpack('nwarnlevel/nTLVnumber', $body);
                $body = substr($body, 4);
                for ($i = 0; $i <= $msg['TLVnumber']; $i++)
                {
                    $part = $this->getTLV($body);
                    $body = substr($body, 4 + $this->size);
                    if ($channel == 1 && $this->type == 2) 
                    {
                        while (strlen($part)) 
                        {
                            $frg = $this->getTLVFragment($part);
                            if ($frg['id'] == 1 && $frg['version'] == 1) 
                            {
                                return array('from' => $from, 'message' => substr($frg['data'], 4));
                            }
                            $part = substr($part, 4+$frg['size']);
                        }
                        return false;
                    }
                }
            }
        }
        return false;
    }
    function dump($str)
    {
        $f = fopen('dump', 'a');
        fwrite($f, $str);
        fclose($f);
    }
    
}

class WebIcqLite_FLAP extends WebIcqLite_SNAC{
    
    var $socet;
    var $command = 0x2A;
    var $channel;
    var $sequence;
    var $body;
    var $info = array();

    function WebIcqLite_FLAP() {
        $this->sequence = rand(1, 30000);
    }
    
    function getFLAP()
    {
        if($this->socet && !socket_last_error($this->socet))
        {
            $header = socket_read($this->socet, 6);
            if ($header) 
            {
                $header = unpack('c2channel/n2size', $header);
                $this->channel = $header['channel2'];
                $this->body = socket_read($this->socet, $header['size2']);
                return true;
            }
            else 
            {
                return false;
            }
        }
    }
    
    function parseCookieFLAP()
    {
        $this->getFLAP();
        $this->info = array();
        while($this->body != '')
        {
            $info = $this->getTLV($this->body);
            $key = array_search($this->type, $this->types);
            if($key)
            {
                $this->info[$key] = $info;
            }
            $this->body = substr($this->body, ($this->size+4));
        }
    }
    
    function parseAnswerFLAP()
    {
        $this->getFLAP();
        $array = unpack('n3int/Nint', $this->body);
        while ($array['int'] != $this->request_id) 
        {
            $this->getFLAP();
            $array = unpack('n3int/Nint', $this->body);
        }

        $this->error = 'Unknown serwer answer';
        if ($array['int1'] == 4) 
        {
            switch ($array['int2']) 
            {
                case 1:
                        $this->error = 'Error to sent message';
                        return false;
                    break;
                case 0x0c:
                        return true;
                    break;
            }
        }

        $this->error = 'Unknown serwer answer';
        return false;
    }
    
    function prepare()
    {
        $this->sequence++;
        $out = pack('ccnn', $this->command, $this->channel, $this->sequence, strlen($this->body)).$this->body;
        return $out;
    }
    
    function login($uin, $password)
    {
        $this->getFLAP();
        $this->uin = $uin;
        $this->body .= $this->setTLV('UIN',                 "$uin");
        $this->body .= $this->setTLV('DATA',                 $this->xorpass($password));
        $this->body .= $this->setTLV('CLIENT',                 'ICQBasic');
        $this->body .= $this->setTLV('CLIENT_ID',             266, 2);
        $this->body .= $this->setTLV('CLI_MAJOR_VER',         20, 2);
        $this->body .= $this->setTLV('CLI_MINOR_VER',         34, 2);
        $this->body .= $this->setTLV('CLI_LESSER_VER',         0, 2);
        $this->body .= $this->setTLV('CLI_BUILD_NUMBER',     2321, 2);
        $this->body .= $this->setTLV('DISTRIB_NUMBER',         1085, 4);
        $this->body .= $this->setTLV('CLIENT_LNG',             'en');
        $this->body .= $this->setTLV('CLIENT_COUNTRY',         'us');
        
        
        $this->channel = 1;
        $pack = $this->prepare();
        socket_write($this->socet, $pack, strlen($pack));
        $this->parseCookieFLAP();
        
        $this->body = 0x0000;
        $pack = $this->prepare();
        socket_write($this->socet, $pack, strlen($pack));
        $this->close();
        
        if(isset($this->info['RECONECT_HERE']))
        {
            $url = explode(':', $this->info['RECONECT_HERE']);
            if(!$this->open($url))
            {
                $this->error = isset($this->info['DISCONECT_REASON']) ? $this->info['DISCONECT_REASON'] : 'Unable to reconnect';
                return false;
            }
        }
        else
        {
            $this->error = isset($this->info['DISCONECT_REASON']) ? $this->info['DISCONECT_REASON'] : 'UIN blocked, please try again 20 min later.';
            return false;
        }

        $this->getFLAP();
        $this->body .= $this->setTLV('COOKIE', $this->info['COOKIE']);
        $pack = $this->prepare();
        if (!socket_write($this->socet, $pack, strlen($pack)))
        {
            $this->error = 'Can`t send cookie, server close connection';
            return false;
        }
        $this->getFLAP();
        $this->body = $this->setSNAC0102();
        $pack = $this->prepare();
        if (!socket_write($this->socet, $pack, strlen($pack)))
        {
            $this->error = 'Can`t send ready signal, server close connection';
            return false;
        }
        return true;
    }
    
    function write_message($uin, $message)
    {
        $this->body = $this->setSNAC0406($uin, $message);
        $pack = $this->prepare();
        if (!socket_write($this->socet, $pack, strlen($pack)))
        {
            $this->error = 'Can`t send message, server close connection';
            return false;
        }
        if (! $this->parseAnswerFLAP()) {
            // try to send offline message
            
            $this->body = $this->setSNAC0406offline($uin, $message);
            $pack = $this->prepare();
            if (!socket_write($this->socet, $pack, strlen($pack)))
            {
                $this->error = 'Can`t send offline message, server close connection';
                return false;
            }
            if (! $this->parseAnswerFLAP()) 
            {
                return false;
            }
            else
            {
                $this->error = 'Client is offline. Message sent to server.';
                return false;
            }
        }
        
        return true;
    }
    
    function read_message()
    {
        while($this->getFLAP())
        {
            $message = $this->getSNAC0407($this->body);
            if($message){
                return $message;
            }
        }
        return false;
    }

    function xorpass($pass)
    {
        $roast = array(0xF3, 0x26, 0x81, 0xC4, 0x39, 0x86, 0xDB, 0x92, 0x71, 0xA3, 0xB9, 0xE6, 0x53, 0x7A, 0x95, 0x7c);
        $roasting_pass = '';
        for ($i=0; $i<strlen($pass); $i++) 
        {
            $roasting_pass .= chr($roast[$i] ^ ord($pass{$i}));
        }
        return($roasting_pass);
    }
    
    function open($url = array('login.icq.com', 5190))
    {
        $this->socet = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        if ($this->socet < 0 || $this->socet === false) 
        {
            $this->error = "socket_create() failed: reason: " . socket_strerror($this->socet);
            return false;
        }
        $result = socket_connect($this->socet, gethostbyname($url[0]), $url[1]);
        if ($result < 0 || $result === false) 
        {
            $this->error = "socket_connect() failed.\nReason: ($result) " . socket_strerror(socket_last_error($socket));
            return false;
        }
        return true;
    }

    function close()
    {
        return socket_close($this->socet);
    }
}

class WebIcqLite extends WebIcqLite_FLAP {

    function WebIcqLite ()
    {
        $this->WebIcqLite_FLAP();
    }
    
    function is_connected()
    {
        if(!$this->socet || socket_last_error($this->socet))
        {
            $this->error = socket_strerror(socket_last_error($socket));
            return false;
        }
        return true;
    }
    
    function connect($uin, $pass)
    {
        if (!$this->open()) 
        {
            return false;
        }
        
        return $this->login($uin, $pass);
    }

    function disconnect()
    {
        return $this->close();
    }

    function get_message()
    {
        return $this->read_message();
    }
    
    function send_message($uin, $message)
    {
        return $this->write_message($uin, $message);
    }
}
?>

Criar o arquivo “/usr/src/webicqlite/send.php” com o conteúdo abaixo para testar o envio de mensagens. Deve-se substituir o UIN e o PASSWORD pelos dados do remetente, e também o UIN_RECIPIENT que é o número o ICQ do destinatário.

<?php
   include('/usr/src/webicqlite/WebIcqLite.class.php');

   define('UIN', 111111111);  
   define('PASSWORD', 'password');
   define('UIN_RECIPIENT', '123456789'); // ou 123456789@chat.agent


   $icq = new WebIcqLite();
   if($icq->connect(UIN, PASSWORD)){
       if(!$icq->send_message(UIN_RECIPIENT, 'mensagem teste icq')){
            echo $icq->error;
        }else{
            echo 'Message sent\n';
        }
        $icq->disconnect();
    }else{
        echo $icq->error;
    }
?>

Para testar o envio de mensagens digite no console o seguinte comando:

/usr/bin/php /usr/src/webicqlite/send.php

Para enviar mensagens para um grupo do ICQ, é preciso criar um novo grupo ou utilizar um já existente e adicionar no grupo o número que será utilizado para enviar as mensagens. É necessário que o grupo contenha no mínimo 3 membros para ser considerado um grupo.

Para descobrir o número do grupo, vamos criar um arquivo para poder ler a mensagens do ICQ, aonde conseguiremos obter o ID do grupo.
Criar o arquivo “/usr/src/webicqlite/read.php” com o conteúdo abaixo, lembrando de substituir o UIN, PASSWORD e UIN_RECIPIENT.

<?php
    ignore_user_abort(true);
    set_time_limit(0);

    require_once('/usr/src/webicqlite/WebIcqLite.class.php');

    define('UIN', '111111111');
    define('PASSWORD', 'password');
    define('UIN_RECIPIENT', '123456789');

    $message='Message test from ICQ!';
    $message_recipients=UIN_RECIPIENT;

    $icq = new WebIcqLite();
    if(!$icq->connect(UIN, PASSWORD)){
        die($icq->error);
    }
    else{
        echo "ICQ Connected!\n";
    }
    while ($message=$icq->get_message()){
        echo serialize($message) . "\n";
    }

?>

Execute o comando abaixo e mande uma mensagem no grupo:

/usr/bin/php /usr/src/webicqlite/read.php

O comando vai retornar um texto conforme abaixo:

a:2:{s:4:"from";s:19:"123456789@chat.agent";s:7:"message";s:14:"Mensagem Teste";}

O ID do  grupo é o número “123456789@chat.agent

Talvez seja necessário instalar a extensão do php-pdo e php-mysql:

apt-get install php5-pdo php5-mysql

Criar o script em /etc/bacula/scripts/_send_icq.php e dar permissão a+x com o contéudo abaixo.

<?php
  require_once('/usr/src/webicqlite/WebIcqLite.class.php');
  $JobID=$argv[1];

  function formatBytes($size, $precision = 2){
     $base = log($size, 1024);
     $suffixes = array('bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');   
     if($size==0){
        return "0 bytes";
     }
     else{
        return round(pow(1024, $base - floor($base)), $precision) . ' '. $suffixes[floor($base)];
     }
  }

  define('DB_DRIVER', 'mysql');
  define('DB_ADDRESS', 'localhost');
  define('DB_NAME', 'bacula');
  define('DB_USERNAME', 'bacula');
  define('DB_PASSWORD', 'bacula');
  $pdo_options = array(PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION);
  try {
     $pdo = new PDO(DB_DRIVER . ':host='.DB_ADDRESS.';dbname='.DB_NAME.';charset=utf8', DB_USERNAME, DB_PASSWORD, $pdo_options );
  }
  catch(PDOException $e){
     echo "Connection failed: " . $e->getMessage();
  }

  $sql_query="select Job.Name, Job.JobId,(select Client.Name from Client where Client.ClientId = Job.ClientId) as Client, Job.JobBytes, Job.JobFiles,
  case when Job.Level = 'F' then 'Full' when Job.Level = 'I' then 'Incremental' when Job.Level = 'D' then 'Differential' end as Level,
  (select Pool.Name from Pool where Pool.PoolId = Job.PoolId) as Pool,
  (select Storage.Name  from JobMedia left join Media on (Media.MediaId = JobMedia.MediaId) left join Storage on (Media.StorageId = Storage.StorageId)
  where JobMedia.JobId = Job.JobId limit 1 ) as Storage, date_format( Job.StartTime , '%d/%m/%Y %H:%i:%s' ) as StartTime, date_format( Job.EndTime , '%d/%m/%Y %H:%i:%s' ) as EndTime,
  sec_to_time(TIMESTAMPDIFF(SECOND,Job.StartTime,Job.EndTime)) as Duration, Job.JobStatus,
  (select Status.JobStatusLong from Status where Job.JobStatus = Status.JobStatus) as JobStatusLong
  from Job where Job.JobId=$JobID";

  $stmt  = $pdo->query($sql_query);
  $row = $stmt->fetch(PDO::FETCH_ASSOC);

  # Atribuição de Variáveis
  $JobName   = $row['Name'];
  $JobId     = $row['JobId'];
  $Client    = $row['Client'];
  $JobBytes  = formatBytes($row['JobBytes']);
  $JobFiles  = $row['JobFiles'];
  $Level     = $row['Level'];
  $Pool      = $row['Pool'];
  $Storage   = $row['Storage'];
  $StartTime = $row['StartTime'];
  $EndTime   = $row['EndTime'];
  $Duration  = $row['Duration'];
  $JobStatus = $row['JobStatus'];
  $Status    = $row['JobStatusLong'];

  if ("$JobStatus" == "T" ){
     $HEADER=">>>>> BACULA BAKUP <<<<<\n";  # OK
  }
  else{
     $HEADER=">>>>> BACULA BAKUP ERROR <<<<<\n";  # Error
  }

  # Formata a mensagem
  $MESSAGE="$HEADER\nJobName=$JobName\nJobid=$JobId\nClient=$Client\nJobBytes=$JobBytes\nJobFiles=$JobFiles\nLevel=$Level\nPool=$Pool\nStorage=$Storage\nStartTime=$StartTime\nEndTime=$EndTime\nDuration=$Duration\nJobStatus=$JobStatus\nStatus=$Status";

  define('UIN', 111111111); 
  define('PASSWORD', 'password');
  define('UIN_RECIPIENT', '123456789'); // ou chat 123456789@chat.agent
  $icq = new WebIcqLite();
  if($icq->connect(UIN, PASSWORD)){
      if(!$icq->send_message(UIN_RECIPIENT, $MESSAGE) ){
          echo $icq->error;
      }else{
          echo "Message sent\n";
      }
      $icq->disconnect();
  }
  else{
      echo $icq->error;
  }
?>

Configurando o Bacula
É possível configurar em apenas em alguns Jobs, mas como eu queria em todos, configurei no JobDefs. Deve-se incluir o RunScript, conforme abaixo, salvar e dar um reload no bconsole:

JobDefs {
  Name = "Backup_Padrao"
  Type = Backup
  Level = Incremental
  Client = bacula-fd
  FileSet = "FileSet_Bacula"
  Schedule = "WeeklyCycle"
  Messages = Standard
  Pool = "File"
  SpoolAttributes = yes
  Priority = 10
  Write Bootstrap = "/etc/bacula/working/%c.bsr"
  RunScript {
     Command = "/usr/bin/php /etc/bacula/scripts/_send_icq.php %i"
     RunsWhen = After
     RunsOnFailure = yes
     RunsOnClient = no
     RunsOnSuccess = yes # se quiser apenas nos Jobs com erro, altere para No.
  }
}

Exemplo de mensagem com sucesso e com erro:
icq_message

FacebooktwitterlinkedinFacebooktwitterlinkedinby feather