Обработка sms на куче одинаковых gsm модемов без насилия над udev

    Дано:
    Есть бухгалтерия, которая работает с множеством коммерческих организаций. Банковские 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>";
    ?>
    
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 18

      +3
      Для udev можно написать скрипт, который при втыкании модема выполнит AT-команду на получение его IMEI к примеру и создаст симлинк.
        –1
        Это точно приз за лучший технический дизайн системы.
        А, я извиняюсь даже как-то неловко спрашивать, несколько китайских gsm модемов даже без серийников в одном линукс сервере у вас ДЛЯ НАДЕЖНОСТИ, если вдруг отвалится один сотовый оператор?
          +2
          Судя по всему, к разным симкам привязаны разные банк-онлайны: один номер = один клиент.
            –2
            на мой дилетантский взгляд проще было бы разрубать все опираясь на номера подтверждений. один-два номера, для надежности лучше два на двух разных машинах от двух разных операторов, и ориентироваться на код подтверждения.
            ведь каждый код подтверждения все равно привязан к конкретной операции, какой смысл в разных номерах для этого???
              0
              Например, два разных бухгалтера могут одновременно выполнить 2 разные операции для разных организаций, у которых одинаковый банк. Какое сообщение какому бухгалтеру слать? Ведь в разных банках подробности sms-информирования разные. Не у всех может быть указан полный набор данных, который можно распарсить и понять к какой организации относится та или иная sms. Да и номер отправителя будет, в данном случае, одинаковый.
              А по одному коду подтверждения ничего не скажешь (не определишь).
                0
                я наверное чего-то не понимаю, но мне абсолютно непонятна схема которую вы рисуете. ведь не будет же банк высылать клиенту запрос на код подтверждения и просить его выслать на какой-то третий левый номер? Если бы банк просил выслать код подтверждения, он бы просил выслать его себе.
                Тут я так понимаю бухгалтера свои операции обрабатывают и чтобы подтвердить операции своих клиентов ожидают от них подтверждения на свои номера. Когда есть вся информация об операции, код ожидаемый от клиента привязан к этой операции однозначно. и на какой номер он придет ну совершенно все равно. придет — операция подтверждена, не придет — не подтверждена.
                  0
                  Насколько я понял, есть N бухгалтеров, обслуживающих М организаций. За каждой организацией закреплена SIM-карта, на которую банк шлёт SMS c кодами (например для подтверждения перевода). Все симки воткнуты в модемы где-то на сервере. Задача, которая тут решалась — передать SMS бухгалтеру, не отдавая ему SIM-карту.
            0
            Скорее для безопасности, чтобы не привязывать все банковские сервисы к одной sim-карте
            0
            транслирую лог файл на пять последних смс через веб страницу
            $output = shell_exec('tail -n 30 /tmp/sms.log');
            Может все же на 30? ;)

            За статью спасибо. Интересное решение.
              +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
                Заметил еще одно… Вот такое решение...
                if grep "Alphabet: UCS2" $file >/dev/null; then

                … из моей практики, не всегда отрабатывает верно. Поэтому я делаю так:
                grep "Alphabet: UCS2" $file >/dev/null
                if [$? -eq 0] ; then
                  +1
                  вместо >/dev/null есть ключ -q у grep
                  +1
                  Ребята, всем спасибо за комментарии, особенно за рекомендации по башу. Такие модемы достались мне в наследство от предыдущего админа. Куча ИП обслуживает один бухгалтер, для каждого ИП своя симка.
                    +1
                    Можно с помощью udev задать каждому модему своё имя в зависимости от порта USB и не придётся мучиться с генерацией конфига каждый раз. Единственное, нельзя будет менять местами модемы
                      0
                      К сожалению модемы имеют свойство подвисать. Переткнул и всё, нумерация сбилась.
                        0
                        Но ведь номер порта не изменится при передёргивании модема? Или непременно надо перевставить в другой порт?
                          0
                          При обновлении ядра нумерация сбивается запросто. При переключении режима модема с помощью usb_modeswitch нумерация тоже может сбиться. У меня были как-то два модема, huawei и zte, так вот они друг друга не уважали так что при переключении режима одного (при втыкании) отваливался и заново обнаруживался второй.
                      0
                      Следуя рекомендациям товарищей немного оптимизировал скрипт обработки сообщений.
                      #!/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 просиходит один раз для каждой смс, что избавляет нас от возможных проблем со смешением текста разных сообщений при одновременном приёме с нескольких модемов. Да и операций обращения к файловой системе становится меньше.

                      Only users with full accounts can post comments. Log in, please.