суббота, 22 февраля 2014 г.

Корпоративный чат, на основе ejabberd в ubuntu server

Поставили на работе задачу - корпоративный чат организовать, с блекждеком. Покопавшись среди различных jabber серверов, я остановил свой выбор на ejabberd.


1. Устанавливаем jabber сервер

sudo apt-get install ejabberd

2. Начальные настройки

все настройки хранятся в конфигурационном файле:
sudo vi /etc/ejabberd/ejabberd.cfg
укажем, какой из пользователей является администратором
%% Admin user
{acl, admin, {user, "ADMIN_LOGIN", "SERVER_HOST"}}.
перечислим виртуальных хосты (доменные имена, обслуживаемые сервером)
%% Hostname
{hosts, ["localhost","SERVER_HOST"]}.
для того, чтобы пользователи могли регистрироваться через клиентскую программу, необходимо указать права для регистрации:
{access, register, [{allow, all}]}.
но нам этого не нужно, поэтому оставляем:
{access, register, [{deny, all}]}.
Перезапустим jabber сервер
sudo /etc/init.d/ejabberd restart
Добавим пользователя для администрирования (с логином, который мы указали в параметре acl, admin)
sudo ejabberdctl register ADMIN_LOGIN servername.com ADMIN_PASSWORD
теперь, используя этот логин и пароль, мы можем зайти на веб интерфейс сервера для его администрирования:
http://SERVER_NAME:5280/admin и, например, добавить пользователей к необходимому виртуальному хосту

3. Общий список контактов

Для сабжа (в джаббере он называется ростер-ом) необходимо включить модуль mod_shared_roster(раскомментировать в конфигурационном файле)
{mod_shared_roster,[]},
после этого в веб интерфейсе администрирования сервера можно создавать группы пользователей и указывать им, какие группы пользователей автоматически добавляются к ним в список контактов. Например, выбираем в веб интерфейсе необходимый выиртуальный хост, затем "Группы общих контактов" и создадим 3 группы: AllUsers, Company и Admins. Перечислим пользователей для этих групп и укажем, какие группы пользователей добавлять им в ростер:
для AllUsers укажем, что в группа содержит всех зарегистрированных пользователей на сервере
для Admins перечислим аккаунты которым автоматом добавим в ростер пользователей из группы AllUsers
для Company - перечислим пользователей, которым автоматом будем добавлять в ростер пользователей из этой же группы.
в результате для указаненых групп пользователей в ростере клиента будут отображаться указанные группы:

4. Внешняя авторизация пользователей

Так как у нас уже есть список пользователей, которых мы заводили при настройке email серера, то будем использовать этот же список для авторизации на нашем jabber сервере. На сранице http://www.ejabberd.im/extauth есть примеры скриптов для внешней авторизации. Мы возьмем оттуда скрипт на php использующтй mysql для проверки авторизации (http://www.ejabberd.im/check_mysql_php). В нем нам необходимо изменить 2 метода: checkpass() и checkuser() класса JabberAuth, которые отвечают за проверку валидности логина и пароля.
checkuser()
function checkuser()
 {
  /*
   * Put here your code to check user
   * $this->jabber_user
   * $this->jabber_pass
   * $this->jabber_server
   */
  $query="SELECT count( * ) AS `cnt` FROM `users` u WHERE `email` = '".$this->jabber_user."@".$this->jabber_server."'";
  $this->logg($query);
  $result = mysql_query($query, $this->mysock);
  if($result){
   $tmp=mysql_fetch_assoc($result);
   if($tmp['cnt']>0){
    return true;
   }
  }
  return false;

 }
checkpass()
function checkpass()
 {
  /*
   * Put here your code to check password
   * $this->jabber_user
   * $this->jabber_pass
   * $this->jabber_server
   */
  $query="SELECT count( * ) AS `cnt` FROM `users` u WHERE `email` = '".$this->jabber_user."@".$this->jabber_server."' AND ENCRYPT( '".$this->jabber_pass."', u.password ) = u.password";
  $this->logg($query);
  $result = mysql_query($query, $this->mysock);
  if($result){
   $tmp=mysql_fetch_assoc($result);
   if($tmp['cnt']>0){
    return true;
   }
  }
  return false;
 }

а также добавить в метод command() после строк:
case "isuser": // this is the "isuser" command, used to check for user existance
      $this->jabber_user = $data[1];
добавить строку
$this->jabber_server = $data[2];
и после строк:
case "auth": // check login, password
      $this->jabber_user = $data[1];
вставить строку:
$this->jabber_server = $data[2];
полный исходный код скрипта /etc/ejabberd/check_mysql.php со всеми изменениями:
#!/usr/bin/php
<?php

/*
Copyright (c) <2005> LISSY Alexandre, "lissyx" <alexandrelissy@free.fr>

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software andassociated documentation files (the "Software"), to deal in the
Software without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished to do so,
subject to thefollowing conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

error_reporting(0);
$auth = new JabberAuth();
$auth->dbhost = "localhost";
$auth->dbuser = "";
$auth->dbpass = "";
$auth->dbbase = "mail";
$auth->play(); // We simply start process !

class JabberAuth {
 var $dbhost; /* MySQL server */
 var $dbuser; /* MySQL user */
 var $dbpass; /* MySQL password */
 var $dbbase; /* MySQL database where users are stored */

 var $debug  = false;           /* Debug mode */
 var $debugfile  = "/var/log/pipe-debug.log";  /* Debug output */
 var $logging  = false;           /* Do we log requests ? */
 var $logfile  = "/var/log/pipe-log.log" ;   /* Log file ... */
 /*
  * For both debug and logging, ejabberd have to be able to write.
  */
 
 var $jabber_user;   /* This is the jabber user passed to the script. filled by $this->command() */
 var $jabber_pass;   /* This is the jabber user password passed to the script. filled by $this->command() */
 var $jabber_server; /* This is the jabber server passed to the script. filled by $this->command(). Useful for VirtualHosts */
 var $jid;           /* Simply the JID, if you need it, you have to fill. */
 var $data;          /* This is what SM component send to us. */
 
 var $dateformat = "M d H:i:s"; /* Check date() for string format. */
 var $command; /* This is the command sent ... */
 var $mysock;  /* MySQL connection ressource */
 var $stdin;   /* stdin file pointer */
 var $stdout;  /* stdout file pointer */

 function JabberAuth()
 {
  @define_syslog_variables();
  @openlog("pipe-auth", LOG_NDELAY, LOG_SYSLOG);
  
  if($this->debug) {
   @error_reporting(E_ALL);
   @ini_set("log_errors", "1");
   @ini_set("error_log", $this->debugfile);
  }
  $this->logg("Starting pipe-auth ..."); // We notice that it's starting ...
  $this->openstd();
 }

 function stop()
 {
  $this->logg("Shutting down ..."); // Sorry, have to go ...
  closelog();
  $this->closestd(); // Simply close files
  exit(0); // and exit cleanly
 }
 
 function openstd()
 {
  $this->stdout = @fopen("php://stdout", "w"); // We open STDOUT so we can read
  $this->stdin  = @fopen("php://stdin", "r"); // and STDIN so we can talk !
 }
 
 function readstdin()
 {
  $l      = @fgets($this->stdin, 3); // We take the length of string
  $length = @unpack("n", $l); // ejabberd give us something to play with ...
  $len    = $length["1"]; // and we now know how long to read.
  if($len > 0) { // if not, we'll fill logfile ... and disk full is just funny once
   $this->logg("Reading $len bytes ... "); // We notice ...
   $data   = @fgets($this->stdin, $len+1);
   // $data = iconv("UTF-8", "ISO-8859-15", $data); // To be tested, not sure if still needed.
   $this->data = $data; // We set what we got.
   $this->logg("IN: ".$data);
  }
 }
 
 function closestd()
 {
  @fclose($this->stdin); // We close everything ...
  @fclose($this->stdout);
 }
 
 function out($message)
 {
  @fwrite($this->stdout, $message); // We reply ...
  $dump = @unpack("nn", $message);
  $dump = $dump["n"];
  $this->logg("OUT: ". $dump);
 }
 
 function myalive()
 {
  if(!is_resource($this->mysock) || !@mysql_ping($this->mysock)) { // check if we have a MySQL connection and if it's valid.
   $this->mysql(); // We try to reconnect if MySQL gone away ...
   return @mysql_ping($this->mysock); // we simply try again, to be sure ...
  } else {
   return true; // so good !
  }
 }
 
 function play()
 {
  do {
   $this->readstdin(); // get data
   $length = strlen($this->data); // compute data length
   if($length > 0 ) { // for debug mainly ...
    $this->logg("GO: ".$this->data);
    $this->logg("data length is : ".$length);
   }
   $ret = $this->command(); // play with data !
   $this->logg("RE: " . $ret); // this is what WE send.
   $this->out($ret); // send what we reply.
   $this->data = NULL; // more clean. ...
  } while (true);
 }
 
 function command()
 {
  $data = $this->splitcomm(); // This is an array, where each node is part of what SM sent to us :
  // 0 => the command,
  // and the others are arguments .. e.g. : user, server, password ...
  
  if($this->myalive()) { // Check we can play with MySQL
   if(strlen($data[0]) > 0 ) {
    $this->logg("Command was : ".$data[0]);
   }
   switch($data[0]) {
    case "isuser": // this is the "isuser" command, used to check for user existance
      $this->jabber_user = $data[1];
      $this->jabber_server = $data[2];
      $parms = $data[1];  // only for logging purpose
      $return = $this->checkuser();
     break;
     
    case "auth": // check login, password
      $this->jabber_user = $data[1];
      $this->jabber_server = $data[2];
      $this->jabber_pass = $data[3];
      $parms = $data[1].":".$data[2].":".md5($data[3]); // only for logging purpose
      $return = $this->checkpass();
     break;
     
    case "setpass":
      $return = false; // We do not want jabber to be able to change password
     break;
     
    default:
      $this->stop(); // if it's not something known, we have to leave.
      // never had a problem with this using ejabberd, but might lead to problem ?
     break;
   }
   
   $return = ($return) ? 1 : 0;
   
   if(strlen($data[0]) > 0 && strlen($parms) > 0) {
    $this->logg("Command : ".$data[0].":".$parms." ==> ".$return." ");
   }
   return @pack("nn", 2, $return);
  } else {
   // $this->prevenir(); // Maybe useful to tell somewhere there's a problem ...
   return @pack("nn", 2, 0); // it's so bad.
  }
 }
 
 function checkpass()
 {
  /*
   * Put here your code to check password
   * $this->jabber_user
   * $this->jabber_pass
   * $this->jabber_server
   */
  $query="SELECT count( * ) AS `cnt` FROM `users` u WHERE `email` = '".$this->jabber_user."@".$this->jabber_server."' AND ENCRYPT( '".$this->jabber_pass."', u.password ) = u.password";
  $this->logg($query);
  $result = mysql_query($query, $this->mysock);
  if($result){
   $tmp=mysql_fetch_assoc($result);
   if($tmp['cnt']>0){
    return true;
   }
  }
  return false;
 }
 
 function checkuser()
 {
  /*
   * Put here your code to check user
   * $this->jabber_user
   * $this->jabber_pass
   * $this->jabber_server
   */
  $query="SELECT count( * ) AS `cnt` FROM `users` u WHERE `email` = '".$this->jabber_user."@".$this->jabber_server."'";
  $this->logg($query);
  $result = mysql_query($query, $this->mysock);
  if($result){
   $tmp=mysql_fetch_assoc($result);
   if($tmp['cnt']>0){
    return true;
   }
  }
  return false;

 }
 
 function splitcomm() // simply split command and arugments into an array.
 {
  return explode(":", $this->data);
 }
 
 function mysql() // "MySQL abstraction", this opens a permanent MySQL connection, and fill the ressource
 {
  $this->mysock = @mysql_pconnect($this->dbhost, $this->dbuser, $this->dbpass);
  @mysql_select_db($this->dbbase, $this->mysock);
  $this->logg("MySQL :: ". (is_resource($this->mysock) ? "Connect." : "Diconnect."));
 }
 
 function logg($message) // pretty simple, using syslog.
 // some says it doesn't work ? perhaps, but AFAIR, it was working.
 {
  if($this->logging) {
   @syslog(LOG_INFO, $message);
  }
 }
}
изменим владельца скрипта:
chown ejabberd:ejabberd ./check_mysql.php
и сделаем его исполняемым:
chmod +x check_mysql.php
укажем в конфигурационном файле /etc/ejabberd/ejabberd.cfg, что проверку пользователей необходимо выполнять через наш внешний скрипт:
{auth_method, external}.
{extauth_program, "/etc/ejabberd/check_mysql.php"}.
теперь пользователи, добавленные в mysql таблицу users (которая используется у нас для виртуальных почтовых ящиков) также смогут авторизироваться на jabber сервере. Их не нужно дополнительно добавлять через веб интерфейс.

5. ICQ транспорт

в файле /etc/ejabberd/ejabberd.cfg раскомментируем строки:
{5555, ejabberd_service, [
                            {ip, {127, 0, 0, 1}},
                            {access, all},
                            {shaper_rule, fast},
                            {hosts, ["icq.SERVER_HOST", "sms.SERVER_HOST"],
                                       [{password, "secret"}]}
                            ]},
устанавливаем зависимости. python библиотека Twisted
wget -c https://pypi.python.org/packages/source/T/Twisted/Twisted-13.2.0.tar.bz2
tar -xvjf Twisted-13.2.0.tar.bz2
cd Twisted-13.2.0/
sudo python setup.py install
библиотека Python Image Library
sudo pip install pil

скачиваем PyICQ и распаковываем в директорию /etc/ejaaberd/:
wget -c https://pyicqt.googlecode.com/files/pyicqt-0.8.1.5.tar.gz
tar zvxf pyicqt-0.8.1.5.tar.gz
mv pyicqt-0.8.1.5 /etc/ejabberd/pyicqt
установим права:
sudo chown -R ejabberd:ejabberd /etc/ejabberd/pyicqt
редактируем файл конфигурации PyICQ:
cd /etc/ejabberd/pyicqt
cp config_example.xml config.xml
vi ./config.xml
меняем параметры:
<jid>icq.HOSTNAME</jid>
<mainServerJID>jabber.HOSTNAME</mainServerJID>
<website>http://jabber.HOSTNAME/</website>
<port>5555</port>
<secret>secret123</secret>
запускаем pyicq
su -c 'python /etc/ejabberd/pyicqt/PyICQt.py > /etc/ejabberd/pyicqt/log 2>&1 &' - ejabberd
в /etc/rc.local добавляем перед exit 0
su -c 'python /etc/ejabberd/pyicqt/PyICQt.py > /etc/ejabberd/pyicqt/log 2>&1 &' - ejabberd
В jabber клиенте при просмотре сервисов сервера наблюдаем ICQ Transport, регистрируемся в нем указав свой ICQ UIN и пароль и подтверждаем авторизацию сервиса.


В ростере автоматом добавятся контакты схраненные на ICQ сервере и запросит для них авторизацию.

Комментариев нет:

Отправить комментарий