SER - добавляем авторизацию и работу с базой данных MySQL


Что мы делаем в этом конфигурационном файле ser.cfg:

  1. Добавляем авторизацию IP с использованием данных, хранящихся в базе данных Mysql.
  2. Контактную информацию о зарегистрированных клиентах также сохраняем в MySQL.

Теперь, когда Вы протестировали простейшую конфигурацию SIP сервера, мы можем добавить дополнительные функциональные возможности. В этом разделе речь пойдет об авторизации.

В нормальных обстоятельствах, мы должны ограничить использование SIP сервера в пределах тех SIP телефонов (т.е., SIP клиентов), которые нам нужны. Авторизация позволяет нам обслуживать только те SIP телефоны, которым мы назначили пароль и позволили использовать наш SIP сервис.

Для поддержки авторизации, мы нуждаемся в хранилище информации, данные в которой не будут потерянны, когда мы останавливаем или перегружаем SIP сервер. Самое распространенное хранилище - это база данных, а самая популярная из них - MySQL, поскольку она содержится почти во всех дистрибутивах Линукс. Существует поддержка и других баз данных, например, PostgreSQL, но в этом документе мы сосредоточим свое внимание только на MySQL.

Для добавления поддержки MySQL, Вы должны вернуться к исходным кодам сервера и изменить несколько параметров. В главе посвященной поддержке базы данных MySQL описывается, как это сделать, после этого, Вам понадобиться повторно инсталлировать исполняемые файлы сервера. После обновления сервера SER, Вы должны внести изменения в файл ser.cfg, как это показано ниже.


Листинг файла ser.cfg, с поддержкой MySQL.


Приведенный ниже, файл конфигурации SIP прокси сервера, основан на простейшем файле конфигурации SIP маршрутизатора, рассмотренного в разделе: Простейшая конфигурация ser.cfg.

debug=3
fork=no
log_stderror=yes

listen=192.0.2.13   # ЗДЕСЬ ДОЛЖЕН БЫТЬ ВАШ IP АДРЕС
port=5060
children=4

dns=no
rev_dns=no
fifo="/tmp/ser_fifo"

fifo_db_url="mysql://ser:heslo@localhost/ser"
# Директива  Fifo_db_url включена для подавления предупреждающих сообщений при запуске сервера,
# которые появились бы, при добавлении  поддержки MySQL. Мы не используем явно значение 
# параметра fifo_db_url указанного в файле ser.cfg, однако, другие дополнительные инструменты такие,
# как утилита serctl использует это значение, для добавления пользователей в базу данных

loadmodule "/usr/local/lib/ser/modules/mysql.so"
# Включение поддержки базы данных MySQL производиться простым включением модуля mysql.so
# в секции loadmodule. Стоит отметить очень важную вещь - это то, что модуль mysql.so должен быть
# загружен до загрузки всех остальных модулей. Причина заключается в том, что модуль mysql.so
# не содержит зависимостей от других модулей, однако, другие модули, например, uri_db,
# содержат зависимости от модуля mysql.so.

loadmodule "/usr/local/lib/ser/modules/sl.so"
loadmodule "/usr/local/lib/ser/modules/tm.so"
loadmodule "/usr/local/lib/ser/modules/rr.so"
loadmodule "/usr/local/lib/ser/modules/maxfwd.so"
loadmodule "/usr/local/lib/ser/modules/usrloc.so"
loadmodule "/usr/local/lib/ser/modules/registrar.so"
loadmodule "/usr/local/lib/ser/modules/auth.so"
# Модуль auth.so не используется непосредственно в ser.cfg, однако он необходим для включения поддержки
# функциональности, связанной с авторизацией. Основная функциональность авторизации пользователей
# в данном файле ser.cfg обеспечивается модулями auth.so и auth_db.so.

loadmodule "/usr/local/lib/ser/modules/auth_db.so"
# Модуль Auth_db.so - непосредственно используется нами в данной конфигурации.
# Он взаимодействует с модулем auth.so для выполнения своих функций.

loadmodule "/usr/local/lib/ser/modules/uri_db.so"
# Модуль Uri_db.so предоставляет нам некоторые функции для авторизации, которые мы будем использовать
# в этом файле ser.cfg, а именно - check_to ().

modparam("auth_db|uri_db|usrloc", "db_url", "mysql://ser:heslo@localhost/ser")
# Модуль Auth_db использует параметр db_url для того, чтобы указать серверу SER, как подключиться
# к базе данных MySQL, которая используется для авторизации пользователей. Как можно заметить,
# мы одновременно включили имена модулей auth_db, uri_db, и usrloc в одну директиву modparam,
# используя символ вертикальной черты. Это сделано для уменьшения числа директив,
# однако это вполне согласуется с синтаксисом, используемым для сервера SER

modparam("auth_db", "calculate_ha1", 1)
# Параметр модуля Auth_db - calculate_ha1 указывает серверу SER,  нужно ли использовать зашифрованные пароли
# в таблице пользователей базы данных MySQL. Для рабочих систем Вам, скорее всего потребуется 
# установить этот параметр в ноль, но в нашем примере мы будем использовать незашифрованные пароли,
# следовательно, установим значение этого параметра в единицу. 

modparam("auth_db", "password_column", "password")
# В модуле Auth_db, по умолчанию, поле для хранения паролей имеет имя - ha1, однако в нашем случае, 
# это поле в MySQL базе имеет имя 'password',. Поэтому мы должны сообщить серверу SER, что имя этого 
# столбца отличается от значения по умолчанию. 

modparam("usrloc", "db_mode", 2)
# Параметр db_mode  модуля usrloc мы должны изменить с нулевого значения, которое мы использовали в первой
# простейшей конфигурации, на значение 2, в данном примере, для указания серверу SER, что он должен
# использовать MySQL для хранения контактной информации пользователей и данных авторизации.

modparam("rr", "enable_full_lr", 1)

route {

  # -----------------------------------------------------------------
  # Sanity Check Section
  # Секция проверки параметров.
  # -----------------------------------------------------------------
  if (!mf_process_maxfwd_header("10")) {
    sl_send_reply("483", "Too Many Hops");
    break;
  };

  if (msg:len > max_len) {
    sl_send_reply("513", "Message Overflow");
    break;
  };

  # -----------------------------------------------------------------
  # Record Route Section
  # Секция записи маршрутов.
  # -----------------------------------------------------------------
  if (method!="REGISTER") {
    record_route();
  };

  # -----------------------------------------------------------------
  # Loose Route Section
  # Секция свободной маршрутизации.
  # -----------------------------------------------------------------
  if (loose_route()) {
    route(1);
    break;
  };

  # -----------------------------------------------------------------
  # Call Type Processing Section
  # Секция, обрабатывающая различные типы вызовов.
  # -----------------------------------------------------------------
  if (uri!=myself) {
    route(1);
    break;
  };

  if (method=="ACK") {
    route(1);
    break;
  } if (method=="INVITE") { 
   #Теперь для обработки сообщений INVITE мы определим отдельный блок маршрутизации - route[3].
   # Он будет отвечать за процесс установления соединения. 

    route(3);
    # Передаем управление блоку маршрутизации route[3] для всех INVITE сообщений,
    # которые не были обработаны блоком свободной маршрутизации. INVITE сообщения,
    # которые подходят под это сравнение - это только оригинальные сообщения INIVITE,
    # а не повторно переданные. После того, как сообщение INVITE будет обработано,
    # мы заканчиваем обработку сообщения директивой break.

    break;
  } else  if (method=="REGISTER") {
    route(2);
    break;
  };

  lookup("aliases");
  if (uri!=myself) {
    route(1);
    break;
  };

  if (!lookup("location")) {
    sl_send_reply("404", "User Not Found");
    break;
  };  

  route(1);
}

# -----------------------------------------------------------------
# Default Message Handler
# Обработчик SIP сообщений, по умолчанию.
# -----------------------------------------------------------------
route[1] {

  if (!t_relay()) {
    sl_reply_error();
  };
}

# -----------------------------------------------------------------
# REGISTER Message Handler
# Обработчик SIP сообщений REGISTER.
# ----------------------------------------------------------------
route[2] {
  sl_send_reply("100", "Trying");
  # При получении SIP сообщения REGISTER, мы немедленно отвечаем сообщением "100 Trying",
  # SIP клиенту, пославшему данное сообщение, для предотвращения повторных отправок сообщений REGISTER.
  # Вследствии того, что SER базируется на транспортном протоколе UDP, то нет никакой гарантии доставки 
  # SIP сообщения адресату и, следовательно, если отправитель не получит довольно быстрого ответа 
  # на свое сообщение, то он будет пытаться отправить его повторно. 
  #
  # Отвечая ему сообщением "100 Trying", мы говорим SIP клиенту, что мы обрабатываем его запрос.
  
  if (!www_authorize("","subscriber")) {
   # Функция www_authorize () используется для проверки данных авторизации, предоставленных пользователем,
   # и данных, которые имеются в таблице пользователей MySQL. Если представленные данные правильные,
   # то функция вернет TRUE, иначе - FALSE. 
   #
   # Если в полученном SIP сообщении не представлены данные для авторизации, то эта функция вернет FALSE. 
   #
   # Первый параметр определяет значение для realm (имя домена), для проверки подлинности данных пользователя.
   # Так как у нас не используется realm, то мы будем использовать пустую строку для этого параметра.
   # Вы можете представить realm, так же, как и realm (имя домена), которое используется WEB сервером. 
   #
   # Второе значение указывает серверу SER, какое имя MySQL таблицы использовать для поиска учетной 
   # записи пользователя. В данном случае - это таблица с именем 'subscriber'.

    www_challenge("","0");
    # Тут мы отправляем SIP клиенту сообщение "401 Unauthorized", заставляя его повторно послать 
    # запрос на регистрацию, в который должна быть включена дайджест авторизация. 
    #
    # Функция www_challenge() имеет два аргумента. Первый - это realm, значение которого будет помещено 
    # в заголовочное поле WWW-Authorize сообщения, которое SER отправит SIP клиенту.
    # Если Вы укажите значение для этого аргумента, то realm будет использоваться SIP клиентом,
    # при выборе соответствующих данных для авторитизации.
    #
    # Второй аргумент управляет включением параметра qop в запрос для дайджест аворизации.
    # Рекомендуется сохранять значение этого параметра равного 1. См: RFC2617 для полного описания 
    # дайджест авторизации. Стоит отметить, что некоторые IP телефоны не поддерживают qop авторизацию.
    # Вы можете пробовать установить значение этого параметра в 0, если у Вас существуют проблемы 
    # с неправильным паролем, в том случае, когда Вы точно знаете, что он правильный. 

    break;
    # Т.к. мы уже отправили SIP клиенту сообщение с кодом ошибки 401 предыдущей командой,
    # нам больше не нужно обрабатывать это сообщение. Поэтому мы используем команду break,
    # для возврата в основной блок маршрутизации.
  };

  if (!check_to()) {
    # При работе в качестве SIP прокси сервера, Вы должны быть уверены, что правильные учетные 
    # записи пользователей, которые были успешно зарегистрированы на сервере, не могли бы использоваться 
    # неавторизированными пользователями. По этой причине в конфигурацию сервера SER включена 
    # функция check_to().
    #
    # Мы вызываем функцию check_to() до проверки сообщения REGISTER. Это заставляет сервер SER проверить, 
    # представленное в сообщении, заголовочное поле To: на предмет соответствия его с данными указанными 
    # для дайджест авторизации. Если оно не соответствуют, тогда мы должны отклонить это сообщение REGISTER 
    # и возвратить сообщение об ошибке.

    sl_send_reply("401", "Unauthorized");
    # Если функция check_to() возвратит FALSE, тогда мы отвечаем SIP клиенту сообщением "401 Unauthorized".
    # Потом выполняем команду break для возврата в основной блок маршрутизации. 

    break;
  };

  consume_credentials();
  # Мы не хотим рисковать, и предотвращаем отправку данных дайджест авторизации вышестоящему 
  # или нижестоящему серверу, путем удаления всех заголовочных полей сообщения: WWW-Authorize 
  # или Proxy-Authorize до того, как мы передадим это сообщение далее.


  if (!save("location")) {
  # Если обработка сообщения дошла до этой точки файла ser.cfg, то это означает, что данные SIP пользователя
  # были успешно проверены по данным из таблицы пользователей MySQL, теперь мы используем функцию 
  # save(location), для добавления данных о местонахождении SIP клиента в базу данных MySQL. 
  # Т.к. мы сохраняем эту контактную информацию в MySQL, мы можем безболезненно перезапустить 
  # сервер SER, не потеряв информацию о местонахождении зарегистрированных SIP клиентов. 

    sl_reply_error();
  };
}


# -----------------------------------------------------------------
# INVITE Message Handler
# Обработчик SIP сообщений INVITE.
# -----------------------------------------------------------------
route[3] {
 # Маршрутизационный блок Route[3], который тут рассматривается, предназначен для обработки
 # сообщений INVITE.

  if (!proxy_authorize("","subscriber")) {
  # Мы используем функцию proxy_authorize(), т.к. наш сервер не является публичным прокси сервером
  # для SIP коммуникаций.
  # Proxy_authorize() требует, чтобы запрос INVITE содержал в себе информацию с дайджест авторизацией. 
  # Если она присутствует, то функция попытается найти клиента в таблице подписчиков,  для проверки 
  # правильности представленных данных авторизации. 
  #
  # Как и функция www_authorize(), proxy_authorize () также имеет два параметра.
  # Первое - это realm, второе - имя таблицы MySQL,
  # по которой будут сверяться данные клиентов. В нашем случае это - MySQL таблица с именем "subscriber". 

    proxy_challenge("","0");
    # Если достоверность данных пользователя не подтверждается сервером SER, тогда он отвечает клиенту SIP
    # сообщением "401 Unauthorized". Функция Proxy_challenge()  имеет два параметра, назначение которых 
    # идентично параметрам функции www_challenge (), а именно, realm и спецификатор qop.

    break;
    # Теперь, когда мы ответили сообщением с кодом ошибки 401, мы вызываем команду break 
    # для прерывания работы и возврата в основной блок маршрутизации. 

  } else if (!check_from()) {
   # Тут мы используем функцию check_from() для проверки того, что полученные данные дайджест авторизации
   # не были подделаны в сообщении INVITE. Эта функция проверяет соответствие имени пользователя с данными 
   # дайджест авторизации, дабы удостовериться в их подлинности. 

    sl_send_reply("403", "Use From=ID");
    # Если функция check_from() возвратит FALSE, тогда мы отвечаем SIP клиенту сообщением "401 Unauthorized" 
    # и возвращаем управление в основной блок маршрутизации. 

    break;
  };

  consume_credentials();
  # Мы не хотим рисковать, и предотвращаем отправку данных дайджест авторизации вышестоящему 
  # или нижестоящему серверу, путем удаления всех заголовочных полей сообщения: WWW-Authorize или 
  # Proxy-Authorize до того, как мы передадим это сообщение далее. 

  lookup("aliases");
  # Здесь мы производим поиск псевдонимов, которые могут быть связанны с набранным номером. 
  # Если набранный номер является псевдонимом и его доменная часть не принадлежит нашей местной сети, 
  # то только в этом случае передаем это сообщение далее. 

  if (uri!=myself) {
    route(1);
    break;
  };

  if (!lookup("location")) {
   # Теперь, когда были проделаны все преобразования над URI запроса, мы можем попытаться найти 
   # правильную контактную информацию в таблице местоположений базы данных  MySQL. 
   # Если запись AOR ("address of record" адрес клиента) невозможно найти, тогда мы отвечаем 
   # сообщением с кодом ошибки 404.
    sl_send_reply("404", "User Not Found");
    break;
  };

  route(1);
  # И, наконец, если обработка сообщения дошла до этой точки, то это означает, что вызывающий 
  # абонент был успешно авторизирован и мы можем безопасно передать далее это INVITE сообщение. 
}


Использование ser.cfg с включенным в нем режима авторизации на прокси сервере.


Прежде, чем Вы сможете использовать эту новую конфигурацию SIP маршрутизатора, Вы должны сконфигурировать учетные записи SIP пользователей, используя скрипт serctl. Как и в предыдущей простейшей конфигурации, продолжим, что у нас есть пользователи с номерами 1000 и 1001. В Простейшей конфигурации мы регистрировали любого пользователя, отправившего нам SIP сообщение REGISTER, без всякой проверки.

Теперь нам требуется их авторизировать. Итак, откроем терминальное окно и выполним следующие две команды:

  1. serctl add 1000 password1
  2. serctl add 1001 password2

Вы получите запрос на ввод пароля. Это должен быть тот пароль, под которым Ваш пользователь, от которого сервер SER обращается к базе данных MySQL. Обратитесь к руководству по базе данных MySQL для получения инструкций, как создать пользовательский аккаунт для доступа к базе данных MySQL.

Как только будут созданы учетные записи для Ваших пользователей, Вам понадобиться обновить настройку Ваших SIP телефонов, изменив пароли на те, что Вы использовали в команде serctl. Теперь, после перезагрузки Ваших SIP телефонов, Вы должны иметь возможность совершать с них телефонные вызовы.




Использованные материалы: http://siprouter.onsip.org/doc/gettingstarted/ch07.html