Дано:
Есть бухгалтерия, которая работает с множеством коммерческих организаций. Банковские web-клиенты шлют коды подтверждения для той или иной банковской операции в виде sms. Одинаковые GSM модемы воткнуты в USB хаб Linux сервера. На сервере установлен пакет smstools3 для приёма и обработки sms и пакет usb_modeswitch для правильного определения модемов через udev.

Задача:
Организовать приём sms и их подачу бухгалтерам. Помечать приходящие смс наименованием организации.

Проблемы:
Модемы китайские, без индивидуальных серийных номеров и нет возможности их различить с помощью правил udev. При перезагрузке сервера или перестановке того или иного модема происходит переименование этих устройств.

Решение.

1. Создаём генератор (/usr/local/bin/smsdconfgen) кофигурационного файла (/etc/smsd.conf) для демона smsd:

#!/bin/sh
# Считаем кол-во доступных системе USB терминалов и создаём строку с их названиями.
num=`ls /dev/ttyUSB* | awk -F tty '{print $2}' | awk -F USB '{print $2}' | awk 'BEGIN { ORS = " " } { print }' | sed 's/.$//'`
devlist=`ls /dev/ttyUSB* | awk -F tty '{print $2}' | awk 'BEGIN { ORS = "," } { print }' | sed 's/.$//'`
# Подпрограмма с настройками для каждого устройства
selection () {
echo "["USB$i"]" >>/etc/smsd.conf
echo 'init = AT+CPMS="ME","ME","ME"' >>/etc/smsd.conf
echo "device = /dev/ttyUSB"$i >>/etc/smsd.conf
echo "baudrate = 115200" >>/etc/smsd.conf
echo "incoming = yes" >>/etc/smsd.conf
echo "memory_start = 1" >>/etc/smsd.conf
echo "eventhandler = /usr/local/bin/sms2mail" >>/etc/smsd.conf
echo >>/etc/smsd.conf
}
# Обнуляем конфигурационный файл /etc/smsd.conf
echo >/etc/smsd.conf
# Заносим в конфигурационный файл основные параметры sms демона
echo "devices = "$devlist >/etc/smsd.conf
echo "outgoing = /var/spool/sms/outgoing" >>/etc/smsd.conf
echo "checked = /var/spool/sms/checked" >>/etc/smsd.conf
echo "incoming = /var/spool/sms/incoming" >>/etc/smsd.conf
echo "receive_before_send = no" >>/etc/smsd.conf
echo "incoming_utf8 = yes" >>/etc/smsd.conf
echo >>/etc/smsd.conf
# Заносим настройки устройств в конфигурационный файл
for i in $num;
do selection;
done

Этот скрипт пропишет все имеющиеся в системе USB терминалы в конфиг smsd. Для своего удобства вы можете внести изменения в init скрипт smsd (обычно он находится в /etc/init.d) и прописать запуск генератора конфига перед стартом самого smsd. Это избавит вас от ручного запуска перед рестартом демона.

2. Создаём скрипт обработчик входящих смс. Он будет сканировать каждую входящую смс и определять принадлежность смс конкретной симке через код IMSI.

#!/bin/bash
#IMSI наших симок.
#Иванов - 250014712255725
#Петров - 250014712342902
#Сидоров - 250014712553982
#Яшин - 250014710661053

#$1 и $2 - это переменные самого smsd для каждой смс
status="$1"
file="$2"
#Вычленяем код IMSI из файла с смс
imsi=`head -12 $file | grep -e "IMSI: " | awk -F" " '{print $2}'`
#Проверяем чья смс  
case "$1" in
  RECEIVED)
    if [ $imsi = 250014712255725 ]; then
       name="Иванов"
    fi
    if [ $imsi = 250014712342902 ]; then
       name="Петров"
    fi
    if [ $imsi = 250014712553982 ]; then
       name="Сидоров"
    fi
    if [ $imsi = 250014710661053 ]; then
       name="Яшин"
    fi
    head -12 $file | grep -e "^From: " -e "^Sent: " -e "^Received: " >> /tmp/sms.log
#Если смс приходят в кодировке UCS, перекодируем в UTF-8
    if grep "Alphabet: UCS2" $file >/dev/null; then
       echo "$name" >> /tmp/sms.log
       tail -n +13 $file | iconv -f UCS-2BE -t UTF-8 >> /tmp/sms.log
       tail -n +13 $file | iconv -f UCS-2BE -t UTF-8 | mutt -x -s "$name" x@mail.com
    else
       echo "$name" >> /tmp/sms.log
       tail -n +13 $file >> /tmp/sms.log
       tail -n +13 $file | mutt -x -s "$name" x@mail.com
    fi
    echo >> /tmp/sms.log
    echo >> /tmp/sms.log
    ;;
esac

Из скрипта видно, что каждая смс подписывается и отсылается на определённый почтовый ящик. Кроме этого она попадает и в лог файл /tmp/sms.log. Что делать с смсками решать вам, моим бухгалтерам, кроме отправки на почту, я транслирую лог файл на пять последних смс через веб страницу. Для этого достаточно поднять веб сервер и закинуть в корень сайта файл index.php вроде этого:

 <title>SMS-ки</title>
<meta http-equiv="refresh" content="5;url=index.php">
<meta charset="UTF-8">
 <?php
$output = shell_exec('tail -n 30 /tmp/sms.log');
echo "<pre>$output</pre>";
?>

Комментарии (18)


  1. EvilBlueBeaver
    31.03.2016 13:13
    +3

    Для udev можно написать скрипт, который при втыкании модема выполнит AT-команду на получение его IMEI к примеру и создаст симлинк.


  1. Mnemonik
    31.03.2016 13:50
    -1

    Это точно приз за лучший технический дизайн системы.
    А, я извиняюсь даже как-то неловко спрашивать, несколько китайских gsm модемов даже без серийников в одном линукс сервере у вас ДЛЯ НАДЕЖНОСТИ, если вдруг отвалится один сотовый оператор?


    1. Angel2S2
      31.03.2016 15:04
      +2

      Судя по всему, к разным симкам привязаны разные банк-онлайны: один номер = один клиент.


      1. Mnemonik
        31.03.2016 15:24
        -2

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


        1. Angel2S2
          31.03.2016 17:05

          Например, два разных бухгалтера могут одновременно выполнить 2 разные операции для разных организаций, у которых одинаковый банк. Какое сообщение какому бухгалтеру слать? Ведь в разных банках подробности sms-информирования разные. Не у всех может быть указан полный набор данных, который можно распарсить и понять к какой организации относится та или иная sms. Да и номер отправителя будет, в данном случае, одинаковый.
          А по одному коду подтверждения ничего не скажешь (не определишь).


          1. Mnemonik
            31.03.2016 20:38

            я наверное чего-то не понимаю, но мне абсолютно непонятна схема которую вы рисуете. ведь не будет же банк высылать клиенту запрос на код подтверждения и просить его выслать на какой-то третий левый номер? Если бы банк просил выслать код подтверждения, он бы просил выслать его себе.
            Тут я так понимаю бухгалтера свои операции обрабатывают и чтобы подтвердить операции своих клиентов ожидают от них подтверждения на свои номера. Когда есть вся информация об операции, код ожидаемый от клиента привязан к этой операции однозначно. и на какой номер он придет ну совершенно все равно. придет — операция подтверждена, не придет — не подтверждена.


            1. pash7ka
              03.04.2016 17:40

              Насколько я понял, есть N бухгалтеров, обслуживающих М организаций. За каждой организацией закреплена SIM-карта, на которую банк шлёт SMS c кодами (например для подтверждения перевода). Все симки воткнуты в модемы где-то на сервере. Задача, которая тут решалась — передать SMS бухгалтеру, не отдавая ему SIM-карту.


    1. EnigMan
      03.04.2016 17:42

      Скорее для безопасности, чтобы не привязывать все банковские сервисы к одной sim-карте


  1. Angel2S2
    31.03.2016 14:13

    транслирую лог файл на пять последних смс через веб страницу
    $output = shell_exec('tail -n 30 /tmp/sms.log');
    Может все же на 30? ;)

    За статью спасибо. Интересное решение.


  1. Angel2S2
    31.03.2016 14:19
    +1

    Предложения по улучшению кода
    Первый скрипт. Нет нужны дважды вызывать ls, в некоторых случаях может получиться так, что они вернут разные результаты (например, перед вызовом первого было 4 модема, а перед вызовом второго, один из них отвалился). Я бы этот код...
    num=`ls /dev/ttyUSB* | awk -F tty '{print $2}' | awk -F USB '{print $2}' | awk 'BEGIN { ORS = " " } { print }' | sed 's/.$//'`
    devlist=`ls /dev/ttyUSB* | awk -F tty '{print $2}' | awk 'BEGIN { ORS = "," } { print }' | sed 's/.$//'`

    … заменил на
    devlist=`ls /dev/ttyUSB* | awk -F tty '{print $2}' | awk 'BEGIN { ORS = "," } { print }' | sed 's/.$//'`
    num=`echo "$devlist" | sed -e 's/USB//g;s/,/ /g'`

    Второй скрипт. Зачем использовать case там, где место для if и наоборот? Т.е. вот этот код...
    case "$1" in
      RECEIVED)
        if [ $imsi = 250014712255725 ]; then
           name="Иванов"
        fi
        if [ $imsi = 250014712342902 ]; then
           name="Петров"
        fi
        if [ $imsi = 250014712553982 ]; then
           name="Сидоров"
        fi
        if [ $imsi = 250014710661053 ]; then
           name="Яшин"
        fi
        # ТУТ ТОЖЕ БЫЛ КОД, НО УБРАЛ ЕГО, ДЛЯ КРАТКОСТИ
        ;;
    esac

    … на мой взгляд, лучше сделать так:
    if [ "$1" = "RECEIVED" ] ; then
        case "$imsi" in
            250014712255725) name="Иванов" ;;
            250014712342902) name="Петров" ;;
            250014712553982) name="Сидоров" ;;
            250014710661053) name="Яшин" ;;
        esac
        # ТУТ ТОЖЕ БЫЛ КОД, НО УБРАЛ ЕГО, ДЛЯ КРАТКОСТИ
    fi

    Так он и короче и читается легче.


  1. Angel2S2
    31.03.2016 14:27
    +1

    Заметил еще одно… Вот такое решение...

    if grep "Alphabet: UCS2" $file >/dev/null; then

    … из моей практики, не всегда отрабатывает верно. Поэтому я делаю так:
    grep "Alphabet: UCS2" $file >/dev/null
    if [$? -eq 0] ; then


    1. BasilioCat
      31.03.2016 16:03
      +1

      вместо >/dev/null есть ключ -q у grep


  1. denis_l_eryomin
    31.03.2016 15:39
    +1

    Ребята, всем спасибо за комментарии, особенно за рекомендации по башу. Такие модемы достались мне в наследство от предыдущего админа. Куча ИП обслуживает один бухгалтер, для каждого ИП своя симка.


  1. homecreate
    01.04.2016 13:56
    +1

    Можно с помощью udev задать каждому модему своё имя в зависимости от порта USB и не придётся мучиться с генерацией конфига каждый раз. Единственное, нельзя будет менять местами модемы


    1. denis_l_eryomin
      01.04.2016 14:53

      К сожалению модемы имеют свойство подвисать. Переткнул и всё, нумерация сбилась.


      1. homecreate
        01.04.2016 16:25

        Но ведь номер порта не изменится при передёргивании модема? Или непременно надо перевставить в другой порт?


        1. Hamer13
          03.04.2016 17:38

          При обновлении ядра нумерация сбивается запросто. При переключении режима модема с помощью usb_modeswitch нумерация тоже может сбиться. У меня были как-то два модема, huawei и zte, так вот они друг друга не уважали так что при переключении режима одного (при втыкании) отваливался и заново обнаруживался второй.


  1. denis_l_eryomin
    06.04.2016 04:15

    Следуя рекомендациям товарищей немного оптимизировал скрипт обработки сообщений.

    #!/bin/sh
    status="$1"
    file="$2"
    imsi=`head -12 $file | grep -e "IMSI: " | awk -F" " '{print $2}'`
    from=`head -n 12 $file | grep -e "From:"`
    sent=`head -n 12 $file | grep -e "Sent:"`
    rec=`head -n 12 $file | grep -e "Received:"`
    message=`tail -n +13 $file | iconv -f UCS-2BE -t UTF-8`
    utf_mes=`tail -n +13 $file`
     
    if [ "$1" = "RECEIVED" ] ; then
        case "$imsi" in
            250014712255725) name="Иванов" ;;
            250014712342902) name="Петров" ;;
            250014712553982) name="Сидоров" ;;
            250014710661053) name="Яшин" ;;
        esac
        if grep "Alphabet: UCS2" $file >/dev/null; then
           echo "\n"$from"\n"$sent"\n"$rec"\n"$name"\n"$message >>/tmp/sms.log
    #Раскомментируйте следующую строку для пересылки sms на почту
    #      echo "\n"$from"\n"$sent"\n"$rec"\n"$name"\n"$message | mutt -x -s "$name" x@mail.ru
        else
           echo "\n"$from"\n"$sent"\n"$rec"\n"$name"\n"$utf_mes >>/tmp/sms.log
    #Раскомментируйте следующую строку для пересылки sms на почту
    #      echo "\n"$from"\n"$sent"\n"$rec"\n"$name"\n"$utf_mes | mutt -x -s "$name" x@mail.ru
        fi
        ;;
    fi
    

    В данном случае запись в файл /tmp/sms.log просиходит один раз для каждой смс, что избавляет нас от возможных проблем со смешением текста разных сообщений при одновременном приёме с нескольких модемов. Да и операций обращения к файловой системе становится меньше.