пятница, 30 октября 2015 г.

Организация SMS шлюза (smstools3)

Введение

Мне на работе понадобилось иметь возможность рассылать смс для нужд нашей системы мониторинга, а также для уведомлений от других систем для внутреннего использования.
Есть 2 пути: можно использовать внешний сервис рассылки смс, коих огромное количество, а можно поднять свой собственный автономный шлюз.
В первом случае обычно это какой-то оплачиваемый  сервис, часто веб, доступный через интернет и имеющий свой протокол или определенный формат обмена данными. В данном случае есть зависимость от работоспособности интернета, что само собой не подходит для нашей системы мониторинга.
Во втором случае нам нужны сим-карта, любой gsm-модем и софт для работы с модемом. Зависимость тут только от железа и прямоты рук. Такой вариант я и буду реализовывать.
Замечу, что мне нужно только отправлять и только смс. Прием смс или звонки как таковые мне не интересны. Всё буду делать на FreeBSD, но разница в Linux совсем непринципиальна. Вопрос покупки симки и выбора тарифа я тоже не рассматриваю.

Софт и железо

Существует некоторое количество софта, предоставляющего возможность работать с модемом. Например, gammu, gnokii и smstools3. Я попробовал каждый из них и больше всего мне понравился smstools3.
Смысл его работы сводится к тому, что есть постоянно запущенный демон smsd, работающий с подключенным физически модемом через файл устройства. Демон периодически проверяет некоторую директорию для отправки смс на предмет определённым образом офтоматированных текстовых файлов. Если файл обнаруживается, то он берется в обработку, парсится, результаты посылаются как команды модему и файл удаляется. Всё просто.
При этом доступны промежуточные шаги, в рамках которых демон может произвести какие-либо операции по преобразованию текстового файла, логированию или еще каких-нибудь действий, которые админ только захочет/напишет. В общем более подробно можно почитать в документации на сайте.
В качестве железа можно взять любой gsm-модем, обычный USB'шный 3G-модем или даже мобильный телефон (главное чтобы он системе рассказывал правильно). Конечно, перед покупкой 3G-модема нужно поискать в интернете по нему инфу. Часто они залочены на конкретного оператора и имеют несколько режимов работы (как модем, как флэшка или cdrom и т. д.). Я пока не встречал таких 3G-модемов, которые было бы невозможно разлочить и перевести в режим "только модем". Я не рассматриваю эти вопросы тут.

Подключение модема.

В моем распоряжении есть промышленный gsm-модем. Разница между промышленным gsm-модемом и 3G-свистком будет лишь в имени файла в каталоге /dev при подключении к машине. Предпогалается, что симка уже вставлена в модем.
После подключения к машине моего модема появился файл устройства /dev/cuaU0.
Через него я могу работать с модемом, посылать ему вручную команды с помощью штатной фряшной утилиты cu(8).
# cu -l /dev/cuaU0
Далее можно вводить at-команды и модем будет отвечать. Например, просто ввести AT и нажать Enter. Вводимые мной команды могут не выводиться на экран в некоторых случаях. Однако, ответы от модема должны выводиться всегда.
Выйти из cu(8) можно странной последовательностью комбинаций клавиш shift+~ и затем crtl+d или просто точка (.).
На Linux'е нет cu(8). Можно использовать любой софт для работы с последовательным портом. Народ на Linux'е обычно любит minicom, который, кстати, также доступен в портах FreeBSD.

Настройка smstools

Первым делом, конечно, надо smstools поставить. Во Фре этот софт известен как comms/smstools3.
Главная рабочая часть smstools - это демон smsd. В его конфиге указываются все необхолимые параметры, коих очень много. Но в моём случае хватит совсем чуть-чуть, я взял конфиг по умолчанию и немного его подправил:
$ cat /usr/local/etc/smsd.conf
# Example smsd.conf. Read the manual for a description
devices = GSM1
# let's use syslog
#logfile = /var/log/smsd/smsd.log
loglevel = 5
checkhandler = /usr/local/bin/smsd_convert.sh 
[GSM1]
#device = /dev/cuaU0.1
#pin = 1111
device = /dev/cuaU0
incoming = no
В глобальной части конфига указывается устройство, с которым мы будем работать. Их может быть несколько, они заданы далее в отдельных разделах. Затем я указал, что хочу для логов использовать syslog с severity 5 (notice). Потом идёт важный параметр checkhandler с указанием скрипта, который запускается для обработки каждого сообщения (файла). Далее описывается раздел устройства GSM1. Указываю где устройство находится и говорю, что мне не интересны входящие смс. Пин-код у меня на симке выключен.
Всё, можно запускать smsd.
По умолчанию все рабочие каталоги (для файлов входящих и исходящих сообщений) располагаются в /var/spool/sms/. Поскольку мне нужно только отсылать смс, интерес представляет только каталог /var/spool/sms/outgoing. Сюда по идее и надо складывать специальным образом отформатированные текстовые файлы для отправки.
Формат таких файлов прост, всё как везде: заголовки, пустая строка, тело. В простейшем случае такой текстовый файл будет выглядеть так:
To: +79XXXXXXXXX

Hello! How are you?
Файл можно назвать любыми именем и поместить в /var/spool/sms/outgoing. Smsd найдет его там, обработает, отошлёт смс и удалит.
Кстати, в базовой поставке smstools есть скрипт sendsms, которым можно воспользоваться для отсылки смс. Я потом напишу свой вариант подобной утилиты, но всё по порядку.

Non-ACSII текст в смс

Для того, чтобы отправлять текст в языке, отличном от английского нужно smsd сказать о том, что текст не английский. Для этого в отправляемый файл нужно добавить еще один заголовок
Alphabet: UCS
и закодировать само тело сообщения в кодировке UCS-2.
Для того, чтобы облегчить жизнь можно реализовать распознавание Non-ASCII текста и дальнейшую вставку заголовка и перекодировку тела на стороне smsd.
Для этого и нужна то самая опция checkhandler в конфиге smsd. Эта опция указывает на скрипт, который первым делом будет запускаться для каждого текстового файла найденного в каталоге outgoing. Т. е. мы будем просто создавать файлик с содержимым
To: +79XXXXXXXXX

Привет! Как дела?
а smsd уже сам выполнит кое-какие преобразования над этим файлом с помощью скрипта из checkhandler.
Мой скрипт довольно прост. За основу я взял скрипт sms2unicode, который входит в базовую поставку smstools.

$ cat /usr/local/bin/smsd_convert.sh
#!/bin/sh
PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
if [ $# -lt 1 ]; then
        echo "Give me a file!"
        exit 10
fi
if [ ! -f "$1" ]; then
        echo "$1: No such file or it's not a regular file!"
        exit 20
fi
if ! which iconv > /dev/null 2>&1; then
        echo "There is no iconv(1) here! Please, install it."
        exit 40
fi
HEADER=`sed -e '/^$/ q' $1`
BODY=`sed -e '1,/^$/ d' $1`
ALPHABET=""
# Is body in english?
if ! echo -n "$BODY" | iconv -t ISO-8859-15 > /dev/null 2>&1; then
        ALPHABET="Alphabet: UCS"
fi
FILE=`mktemp /tmp/smsd_XXXXXX`
echo "$HEADER" >> $FILE
# If body isn't in english tell it to smsd
[ -n "$ALPHABET" ] && echo "$ALPHABET" >> $FILE
echo "" >> $FILE
# Convert body if it's needed.
if [ -n "$ALPHABET" ]; then
        echo -n "$BODY" | iconv -f UTF-8 -t UCS-2 >> $FILE
else
        echo -n "$BODY" >> $FILE
fi
mv $FILE $1
Тут я предполагаю, что изначально Non-ASCII текст записан в кодировке UTF-8. В принципе тут в комментариях всё написано.
Конечно, надо еще дать права на выполнение для этого скрипта:
# chmod 755 /usr/local/bin/smsd_convert.sh
Итак, у нас есть демон, который может отправлять смски на любом языке. Файлы с смс сообщениями мы можем генерировать вручную или с помощью sendsms. Однако, всё это работает локально на данной машине. Нужно предоставить возможность отправлять смски и с других машин в сети.

Отправка смс удаленно

Чтобы отправлять смс с удаленных машин нам по сути каким-то образом нужно положить текстовый файл в определенную директорию на машину, выполняющую функцию смс-шлюза. Вариантов как это сделать могут быть десятки. Например, можно смонтировать этот каталог по NFS всем машинам. Или написать простой CGI-скрипт, который бы парсил POST, например, и клал правильно сформированный файл в нужную директорию. Мне приглянулся другой вариант. Я решил предоставить к директории outgoing доступ по FTP.

Настройка смс-шлюза

Доступ у меня будет для анонимов, сеть-то внутреняя, хотя можно запилить и аутентификацию.
Для этого я добавил на машине-смс-шлюзе учетку для анонима. Взял её из портов, т. к. нынче эта учетка выпилена из базовой системы:
grep ftp /usr/ports/UIDs
ftp:*:14:14::0:0:Anonymous FTP:/var/ftp:/nonexistent
Единственное, я поменял домашний каталог с /var/ftp на /var/spool/sms/outgoing.
После добавления учетки для анонима я добавил учетку ftp в группу dialers, т. к. /var/spool/sms/outgoing по умолчанию принадлежит группе dialers. Затем добавил для этой директории права на запись для группы dialers.
Осталось только запустить ftp сервер. Для этого я добавил в /etc/rc.conf
ftpd_enable="YES"
ftpd_flags="-8 -A -M -O -l -l"
Ключи можно почитать в man. Если в краце, то доступ разрешен только анонимам и только на запись.

Отсылка смс удаленно

На удаленных машинах мне просто нужно написать скрипт, который бы подключался по ftp к смс-шлюзу и заливал туда правильно сформатированный текстовый файл.
Для работы этого скрипта я использовать утилиту ncftpput(1), которая входит в состав пакета ncftp. Во FreeBSD он известен как ftp/ncftp3. Штатный фряшный ftp(1) я не стал использовать, т. к. я не смог заставить его сделать put и отдать нормальный exit code.
$ cat /usr/local/bin/smsviagate.sh
#!/bin/sh
export PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
if ! which ncftpput > /dev/null 2>&1; then
        echo "Can't find ncftpput(1)! Please, install it."
        exit 1
fi
SMSGATE="smsgate.example.org"
DEST=$1
TEXT=$2
if [ -z "$DEST" ]; then
        printf "Destination(s): "
        read DEST
        if [ -z "$DEST" ]; then
                echo "No destination, stopping."
                exit 1
        fi
fi
if [ -z "$TEXT" ]; then
        printf "Text: "
        read TEXT
        if [ -z "$TEXT" ]; then
                echo "No text, stopping."
                exit 1
        fi
fi
if [ $# -gt 2 ]; then
        n=$#
        while [ $n -gt 1 ]; do
                destinations="$destinations $1"
                shift
                n=`expr $n - 1`
        done
        TEXT=$1
else
        destinations=$DEST
fi
for destination in $destinations; do
        TMPFILE=`mktemp /tmp/smsd_XXXXXX`
        echo "To: $destination" >> $TMPFILE
        echo "" >> $TMPFILE
        echo -n "$TEXT" >> $TMPFILE
        ncftpput -r 1 $SMSGATE / $TMPFILE > /dev/null 2>&1
        EXIT="$?"
        if [ $EXIT -eq 0 ]; then
                echo "SMS to $destination has been queued."
        else
                echo "SMS to $destination has not been queued, ncftpput exit code = $EXIT."
        fi
        rm $TMPFILE
done
За основу был взят sendsms из состава smstools. Нужно обратить внимание, что адрес смс-шлюза в скрипте захардкожен в переменной $SMSGATE.
Не забудем про права на выполнение:
# chmod 755 /usr/local/bin/smsviagate.sh
Запускаем скрипт
$ smsviagate.sh '+79xxxxxxxxxx' 'Превед!'
Или без параметров. Тогда скрипт спросит обо всем интерактивно.

Альтернативный вариант удаленной отсылки смс

Спустя какое-то время я сделал возмодность отсылки смс с удаленных машин через email. Это понадобилось для виндовых машин, которые могли только слать письма.
Я создал отдельную mx запись smsgate.example.org, которая указывала на сервер смс-шлюз.
В настройках sendmail в local-host-names добавил
$ cat /etc/mail/local-host-names
smsgate.example.org
Затем в файле aliases добавил перенаправление почты на скрипт:
$ grep sms /etc/mail/aliases
sms: "| /usr/local/bin/email2sms.sh"
Затем стандартные для перенастройки sendmail команды:
# cd /etc/mail && make && make install && make restart && newaliases
Конечно, sendmail должен принимать почту от удаленных машин:
$ grep sendmail /etc/rc.conf
sendmail_enable="YES"
Содержимое скрипта email2sms.sh выглядит так:

$ cat /usr/local/bin/email2sms.sh
#!/bin/sh
export PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
DEBUG="false"
SENDSMS="/usr/local/bin/smsviagate.sh"
EMAIL=`mktemp /tmp/email_XXXXXX`
LOG="/tmp/email2sms.debug"
cat >$EMAIL
FROM=`fgrep "From:" $EMAIL | cut -d : -f2- | sed -e 's/^ //g'`
DST=`fgrep "Subject:" $EMAIL | cut -d : -f2- | sed -e 's/,/ /g' -e 's/\./ /g' -e 's/;/ /g' -e 's/:/ /g' -e 's/"/ /g' -e "s/'/ /g"`
TEXT=`sed -e '1,/^$/ d' $EMAIL`
$SENDSMS $DST "$TEXT"
EXIT="$?"
$DEBUG && echo "`date`" >> $LOG
$DEBUG && echo "$SENDSMS exit code is $EXIT" >> $LOG
$DEBUG && echo "from is $FROM" >> $LOG
$DEBUG && echo "dst is $DST" >> $LOG
$DEBUG && echo "text is $TEXT" >> $LOG
$DEBUG && echo "---------------" >> $LOG
rm $EMAIL
Темой письма должен быть телефонный номер вроде +79XXXXXXXXXX.
Тело письма, соотвественно, должно быть в UTF-8.
Опять, нужно не забыть сделать скрипт выполняемым:
# chmod 755 /usr/local/bin/email2sms.sh

P. S.

Спустя какое-то время, я обнаружил, что при переустановке smstools права на /var/spool/sms/outgoing слетают на стандартные. Это не хорошо.
Чтобы предотвратить это я создал другой каталог, где повторил содержимое стандартного /var/spool/sms. Например:
# cd /var/spool && mkdir -p smsgate/outgoing smsgate/incoming smsgate/checked
# chown -R uucp:dialer /var/spool/smsgate
Затем задал нужные права (см. выше) для нового каталога outgoing:
# chmod g+w smsgate/outgoing
В заключении я поменял имена этих дректорий в 2-х местах:
- переопределил их его через параметры outgoing, checked и incoming в глобальной части конфига smsd.conf;
- изменил домашний каталог на смс-шлюзе для учетки ftp на /var/spool/smsgate/outgoing.

P. S. S. Как проверить счет:
У моего оператора счет проверяется по вызову *100#, поэтому:
# cu -l /dev/cuaU0
Connected
AT+CUSD=1,*100#,15
OK
Немного подождать и модем вернет что-то вроде такого:
+CUSD: 2,"003300360035002E003900300440002E00200416043C04380020002A003500320033002A003600230020043800200441043F0440043E044104380020042204100420041E002C002004470442043E0020044204350431044F002004360434043504420020002800330440002F04340029",72

Потом идем сюда http://smstools3.kekekasvi.com/topic.php?id=288
И копируем этот набор сиволов в USSD Entry/Display
Потом выбираем UCS2 и нажимаем convert.

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

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