Есть бухгалтерия, которая работает с множеством коммерческих организаций. Банковские 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)
Mnemonik
31.03.2016 13:50-1Это точно приз за лучший технический дизайн системы.
А, я извиняюсь даже как-то неловко спрашивать, несколько китайских gsm модемов даже без серийников в одном линукс сервере у вас ДЛЯ НАДЕЖНОСТИ, если вдруг отвалится один сотовый оператор?Angel2S2
31.03.2016 15:04+2Судя по всему, к разным симкам привязаны разные банк-онлайны: один номер = один клиент.
Mnemonik
31.03.2016 15:24-2на мой дилетантский взгляд проще было бы разрубать все опираясь на номера подтверждений. один-два номера, для надежности лучше два на двух разных машинах от двух разных операторов, и ориентироваться на код подтверждения.
ведь каждый код подтверждения все равно привязан к конкретной операции, какой смысл в разных номерах для этого???Angel2S2
31.03.2016 17:05Например, два разных бухгалтера могут одновременно выполнить 2 разные операции для разных организаций, у которых одинаковый банк. Какое сообщение какому бухгалтеру слать? Ведь в разных банках подробности sms-информирования разные. Не у всех может быть указан полный набор данных, который можно распарсить и понять к какой организации относится та или иная sms. Да и номер отправителя будет, в данном случае, одинаковый.
А по одному коду подтверждения ничего не скажешь (не определишь).Mnemonik
31.03.2016 20:38я наверное чего-то не понимаю, но мне абсолютно непонятна схема которую вы рисуете. ведь не будет же банк высылать клиенту запрос на код подтверждения и просить его выслать на какой-то третий левый номер? Если бы банк просил выслать код подтверждения, он бы просил выслать его себе.
Тут я так понимаю бухгалтера свои операции обрабатывают и чтобы подтвердить операции своих клиентов ожидают от них подтверждения на свои номера. Когда есть вся информация об операции, код ожидаемый от клиента привязан к этой операции однозначно. и на какой номер он придет ну совершенно все равно. придет — операция подтверждена, не придет — не подтверждена.pash7ka
03.04.2016 17:40Насколько я понял, есть N бухгалтеров, обслуживающих М организаций. За каждой организацией закреплена SIM-карта, на которую банк шлёт SMS c кодами (например для подтверждения перевода). Все симки воткнуты в модемы где-то на сервере. Задача, которая тут решалась — передать SMS бухгалтеру, не отдавая ему SIM-карту.
EnigMan
03.04.2016 17:42Скорее для безопасности, чтобы не привязывать все банковские сервисы к одной sim-карте
Angel2S2
31.03.2016 14:13транслирую лог файл на пять последних смс через веб страницу
$output = shell_exec('tail -n 30 /tmp/sms.log');
Может все же на 30? ;)
За статью спасибо. Интересное решение.
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
Так он и короче и читается легче.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
denis_l_eryomin
31.03.2016 15:39+1Ребята, всем спасибо за комментарии, особенно за рекомендации по башу. Такие модемы достались мне в наследство от предыдущего админа. Куча ИП обслуживает один бухгалтер, для каждого ИП своя симка.
homecreate
01.04.2016 13:56+1Можно с помощью udev задать каждому модему своё имя в зависимости от порта USB и не придётся мучиться с генерацией конфига каждый раз. Единственное, нельзя будет менять местами модемы
denis_l_eryomin
01.04.2016 14:53К сожалению модемы имеют свойство подвисать. Переткнул и всё, нумерация сбилась.
homecreate
01.04.2016 16:25Но ведь номер порта не изменится при передёргивании модема? Или непременно надо перевставить в другой порт?
Hamer13
03.04.2016 17:38При обновлении ядра нумерация сбивается запросто. При переключении режима модема с помощью usb_modeswitch нумерация тоже может сбиться. У меня были как-то два модема, huawei и zte, так вот они друг друга не уважали так что при переключении режима одного (при втыкании) отваливался и заново обнаруживался второй.
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 просиходит один раз для каждой смс, что избавляет нас от возможных проблем со смешением текста разных сообщений при одновременном приёме с нескольких модемов. Да и операций обращения к файловой системе становится меньше.
EvilBlueBeaver
Для udev можно написать скрипт, который при втыкании модема выполнит AT-команду на получение его IMEI к примеру и создаст симлинк.