Шесть лет назад я рассказывал, как сделать пересылку SMS на email. Задача простая — есть SIM-карта, которую не хочется держать постоянно в телефоне, но на неё приходят SMS с сообщениями типа кодов авторизации банка. Я её решал, подключив USB-модем к серверу с Ubuntu 16.04 LTS.

Если бы у меня в телефоне было две SIM-карты, я бы не заморачивался. Наверное.

Выбор модема

Кроме приёма SMS в некоторых модемах можно определять приход голосовых вызовов. Ответить, конечно, не получится. Мне это не нужно, достаточно узнать, что был звонок и отбить его. Можно, например, отослать в ответ SMS с предложением написать email.

Для того, чтобы хоть ориентировочно прикинуть, какой модем поддерживает эту функцию, можно посмотреть asterisk-chan-dongle/wiki. Учтите, что информация там противоречивая, всё зависит от ревизии/оператора/фазы луны.

Например, для Huawei E303:

For devices sold in Brazil only: OK to receive voice calls, OK to place calls to T1/E1/J1 or other virtual channels, NOT working to place calls to other mobiles or analog landlines

Мне сильно пригодился документ “AT Commands, GSM Reference Guide”.

По факту мой Huawei E303 не поддерживал голосовых вызовов, Huawei E3531 тоже. А вот Huawei E169 (причём два разных) работает нормально.

Есть более продвинутый вариант для голоса — Asterix+SIP. Мне лично это неинтересно, мне хочется знать просто о факте звонка, говорить голосом я не собираюсь.

SMSTools

SMSTools прекрасно принимал голосовые вызовы несколько месяцев, а потом случилось непонятно что и он перестал их ловить. Мистика. Версия SMSTools не менялась (я потом перекомпилировал несколько разных версий, всё то же самое), обновлялись библиотеки и ядро, скорее всего это повлияло. Поэтому и начал искать замену.

Вот такой был конфиг

$ cat /etc/smsd.conf
devices = GSM1
outgoing = /var/spool/sms/outgoing
checked = /var/spool/sms/checked
incoming = /var/spool/sms/incoming
logfile = /var/log/smstools/smsd.log
infofile = /var/run/smstools/smsd.working
pidfile = /var/run/smstools/smsd.pid
outgoing = /var/spool/sms/outgoing
checked = /var/spool/sms/checked
failed = /var/spool/sms/failed
incoming = /var/spool/sms/incoming
sent = /var/spool/sms/sent
stats = /var/log/smstools/smsd_stats
loglevel = 15
delaytime = 5
receive_before_send = no
autosplit = 3
eventhandler = /etc/smsd/trsms.sh
phonecalls = /var/spool/sms/phonecalls

[GSM1]
device = /dev/ttyUSB0
incoming = yes
baudrate = 19200
rtscts = no
cs_convert = no
init = AT+CLCC
check_memory_method = 31
phonecalls = yes
voicecall_hangup_ath = yes
voicecall_clcc = yes
voicecall_ignore_modem_response = yes

Критичной командой была voicecall_ignore_modem_response, E169 выдавал ошибки в ответ на команды по голосу, а она всё решила.

Скрипт /etc/smsd/trsms.sh (в самой последней версии SMSTools из исходников сбивается кодировка):

#!/bin/bash
status="$1"
file="$2"

case "$1" in
  RECEIVED)
    FILE=`mktemp /tmp/smsd_XXXXXX`

    header=`head -12 $file | grep -e "^From: " -e "^Sent: " -e "^Received: "`
    from=`head -12 $file | grep -e "^From: " | awk '{print $2}'`

    if grep "Alphabet: UCS2" $file > /dev/null > /dev/null; then
      message=`tail -n +13 $file | iconv -f UCS-2BE -t UTF-8`
    else
      message=`tail -n +13 $file`
    fi
    echo "$message" | mail --subject="Incoming SMS from +$from" email@company.com
    echo -e "$header\n$message\n" >> /var/log/sms.log
    ;;
  CALL)
    header=`head -12 $file | grep -e "^From: " -e "^From_TOA: " -e "^IMSI" -e "^Call_type: " -e "^Received: "`
    from=`head -12 $file | grep -e "^From: " | awk '{print $2}'`
    imsi=`head -12 $file | grep -e "^IMSI: " | awk '{print $2}'`
    received=`head -12 $file | grep -e "^Received: " | awk '{print $2}'`
    message=$(cat $file)
    echo "$message" | mail --subject="Incoming CALL from +$from" email@company.com
    cat "$file" >> /var/log/calls.log
    echo >> /var/log/calls.log
esac

Gammu

Нашёл отличный вариант — Gammu SMSD. Штатно есть в Ubuntu, поддерживается сохранение сообщений в database backend’ах. Умеет ловить голосовые вызовы. Но вот с комбинированием SMS, состоящих из нескольких частей, у него не всё хорошо. Вернее сказать, что короткие SMS’ы, причём не все, а часть, не собираются (им такое, впрочем и не нужно), и нужно ставить два обработчика — частичных SMS и полных. И получать для некоторых SMS набор из частичных и полное сообщение, а для некоторых — только частичные. Запутанно формулирую.

sudo apt install gammu-smsd

Русский текст, понятно, без проблем работает. И конфигурация проста.

$ cat /etc/gammu-smsdrc

[gammu]
device = /dev/ttyUSB0
connection = at

[smsd]
service = files
logfile = syslog
debuglevel = 4

inboxpath = /var/spool/gammu/inbox/
outboxpath = /var/spool/gammu/outbox/
sentsmspath = /var/spool/gammu/sent/
errorsmspath = /var/spool/gammu/error/

RunOnReceive = /etc/smsd/RunOnReceive.sh

HangupCalls = 1
RunOnIncomingCall = /etc/smsd/RunOnIncomingCall.sh

SMS

Скрипт /etc/smsd/RunOnReceive.sh:

#!/usr/bin/env bash

log="/var/log/gammu-debug.log"

date >> "${log}"
echo "RunOnReceive" >> "${log}"
printenv >>"${log}"
echo "parameters: $@">> "${log}"

PROGRAM=/etc/smsd/process-sms.sh

numbers_file=$(mktemp)
for i in `seq $SMS_MESSAGES` ; do
  eval "echo \"\${SMS_${i}_NUMBER}\"" >> "${numbers_file}"
done

numbers=$(cat "${numbers_file}" | sort | uniq)
echo "Numbers:" >> "${log}"
cat "${numbers_file}" >> "${log}"
echo "result: ${numbers}" >> "${log}"
rm -f "${numbers_file}"

# Отсылка декодированных SMS
for i in `seq $DECODED_PARTS` ; do
  eval "$PROGRAM \"$numbers\" \"\${DECODED_${i}_TEXT}\""
done

# Отсылка частичных SMS
PROGRAM2=/etc/smsd/process-sms-parts.sh
for i in `seq $SMS_MESSAGES` ; do
  eval "$PROGRAM2 \"\${SMS_${i}_NUMBER}\" \"\${SMS_${i}_TEXT}\""
done

echo >> "${log}"

Много отладки, но я решаю мою домашнюю задачу, а не готовлю образцы к участию в конференции.

Скрипт /etc/smsd/process-sms.sh (нужен тул “jo”, есть в пакетах):

#!/usr/bin/env bash

number="${1}"
message="${2}"

log="/var/log/gammu-sms.log"
dt=$(date +%Y-%m-%d-%H%M%S)

date >> "${log}"
echo "Date: ${dt}" >> "${log}"
echo "Number: ${number}" >> "${log}"
echo "${message}" >> "${log}"
echo >> "${log}"

echo -e "Date: ${dt}\n\n${message}\n" | mail --subject="Incoming SMS from $number (gammu)" email@company.com

read -r -d '' slack_message <<EOF
\`\`\`
SMS from: $number

${message}
\`\`\`
EOF

payload=$(jo text="$slack_message" icon_emoji=":ghost:" channel="#sms" username="sms")
curl -qs -X POST --data-urlencode payload="$payload" https://hooks.slack.com/services/xxx/xxx/xxxxx

Обратите внимание на последние две строки — они отсылают текст в Slack. Если есть соединение с этим Slack. Я больше полагаюсь на email, он доставится в любом случае. Для надёжной доставки в Slack нужно строить воркеры/mq, это уже не для дома.

Скрипт /etc/smsd/process-sms-parts.sh:

#!/usr/bin/env bash

number="${1}"
message="${2}"

log="/var/log/gammu-sms-parts.log"
dt=$(date +%Y-%m-%d-%H%M%S)

date >> "${log}"
echo "Date: ${dt}" >> "${log}"
echo "Number: ${number}" >> "${log}"
echo "${message}" >> "${log}"
echo >> "${log}"

echo -e "Date: ${dt}\n\n${message}\n" | mail --subject="Incoming SMS from $number (parts)" email@company.com

read -r -d '' slack_message <<EOF
\`\`\`
SMS from: $number

${message}
\`\`\`
EOF

payload=$(jo text="$slack_message" icon_emoji=":ghost:" channel="#sms-parts" username="sms")
curl -qs -X POST --data-urlencode payload="$payload" https://hooks.slack.com/services/xxx/xxx/xxxxx

Голосовые вызовы

Скрипт /etc/smsd/RunOnIncomingCall.sh:

#!/usr/bin/env bash

log="/var/log/gammu-debug.log"

date >> "${log}"
echo "RunOnIncomingCall" >> "${log}"
printenv >>"${log}"
echo "parameters: $@">> "${log}"
echo >> "${log}"

/etc/smsd/process-call.sh "$@"

Скрипт /etc/smsd/process-call.sh:

#!/usr/bin/env bash

number="$@"

log="/var/log/gammu-calls.log"
dt=$(date +%Y-%m-%d-%H%M%S)

echo "Date: ${dt}" >> "${log}"
echo "Number: ${number}" >> "${log}"
echo >> "${log}"

echo -e "Date: ${dt}\n" | mail --subject="Incoming CALL from $number (gammu)" email@company.com

read -r -d '' slack_message <<EOF
\`\`\`
Call from: $number
\`\`\`
EOF

payload=$(jo text="$slack_message" icon_emoji=":ghost:" channel="#sms" username="calls")
curl -qs -X POST --data-urlencode payload="$payload" https://hooks.slack.com/services/xxx/xxx/xxxxx

Результаты

С email всё понятно. Вот Slack для декодированных сообщений (и вызовов):

и для частичных:

Вот тут как раз “особенность”, которую я запутанно описывал. Приватбанк (и ещё парочка) попали только в “частичные”, а Киевстар отослал длинное сообщение и попал и в частичные (два сообщения), и в декодированное (одно). По-нормальному нужно добавить логики и отслеживать такие ситуации, выдавая результат в один канал. От этого удлинится время доставки, а мне это критично (например, я хочу получить нотификацию от банка сразу же, не ожидая минут пять, пока не станет понятно, что больше частей не будет). Сделаю это, как будет время и желание.

Ещё хочется сделать две функции:

  1. Смотреть в CardDAV и определять, кто именно звонил/писал
  2. Присылать историю — когда и какие сообщения были с этого номера

Насчёт стабильности. У меня так работают два модема. Один — в нормальном сервере, второй — в Raspberry Pi 3. За месяцев пять был один раз, когда модем “залипал”. Слежение в Zabbix — и всё быстро можно починить.

Перезагрузки переживаются отлично. Можно настроить udev rules, чтобы создавались алиасы типа /dev/huawei:

$ cat /etc/udev/rules.d/98-modem.rules
SUBSYSTEM=="tty", ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="1001", SYMLINK+="huawei"

И не забывайте про пакеты usb-modeswitch + usb-modeswitch-data.