Pentagrid's SMSgate
is a Python-based interface for sending and especially receiving SMS using multiple GSM modems and SIM cards.
SMSgate
is a multi-modem interface based on the Python module
python-gsmmodem-new, which is
used for receiving and sending SMS. The main use case for this SMS gateway
is receiving SMS and to forward the SMS or to allow XML-RPC clients to
fetch SMS via an API. The second use case is to send SMS alarms for
service monitoring purposes.
Our blog post on the occasion of releasing the source code gives more background regarding this project.
The SMS gateway implements a few features which are:
- Support for a GSM modem pool attached via USB (or serial interfaces)
- Receive SMS and forward it via SMTP to a e-mail recipient
- XML-RPC API to fetch received SMS, to send SMS, to check the SMS delivery status, and to send USSD codes
- Support for API token to control API access
- Support for Icinga/Nagios monitoring
- Support for Munin monitoring
- Support for receiving flash SMS and sending flash SMS, useful for sending monitoring alerts.
- An XML-RPC API client to interact with the service
- Experimental SECCOMP support (may break things)
The SMS gateway misses these features:
- Persistence of SMS: Each SMS is kept in memory.
- User management: This is solved via API keys.
The Python module python-gsmmodem-new
does not have a list of supported
devices, but this software should be usable with most modems. It has been tested with
- ZTE MF 190 (Surfstick)
- Quectel M35 modules (modem pool)
- SIM7600E modules (modem pool)
If you want to operate the SMS gateway in a KVM guest system, you need to pass USB devices to the guest. A problem is that if you have multiple devices with the same USB product and vendor ID, you need to specify the device number, which changes every time you powercycle the modem(s). Alternativly, you can pass an USB hub or use this script this script, which has received a bug fix available.
Installation of the script:
1. Identify the main device path. Run:
udevadm monitor --property --udev --subsystem-match=usb/usb_device
Be aware of modem pools bring their own USB hubs. 2. Download and install the code:
wget https://raw.githubusercontent.com/nitram2342/usb-libvirt-hotplug/master/usb-libvirt-hotplug.sh sudo chown root.root usb-libvirt-hotplug.sh sudo mv usb-libvirt-hotplug.sh /usr/local/bin/ sudo chmod 755 /usr/local/bin/usb-libvirt-hotplug.sh
3. Create /etc/udev/rules.d/90-usb-libvirt-hotplug.rules
with the following content (adjust the DEVPATH and MYDOMAIN):
SUBSYSTEM=="usb",DEVPATH=="/devices/pci0000:00/0000:00:14.0/usb1/1-11/1-11.1",OWNER="qemu",RUN+="/usr/local/bin/usb-libvirt-hotplug.sh MYDOMAIN" SUBSYSTEM=="usb",DEVPATH=="/devices/pci0000:00/0000:00:14.0/usb1/1-11/1-11.2",OWNER="qemu",RUN+="/usr/local/bin/usb-libvirt-hotplug.sh MYDOMAIN"
4. Check and maybe fix ownership:
ls -l /etc/udev/rules.d/90-usb-libvirt-hotplug.rules chown root.root /etc/udev/rules.d/90-usb-libvirt-hotplug.rules
5. Reload Udev rules:
sudo udevadm control --reload-rules
Create a user that will later run the software:
sudo useradd -d /var/smsgate -m -s /usr/sbin/nologin smsgate
Add user smsgate
to group dialout
.
sudo usermod -a -G dialout smsgate
If you prefer to install as much Python packages via your OS package manager as possible, run:
sudo apt install python3-openssl python3-twisted python3-service-identity python3-venv python3-bcrypt python3 -m venv venv source venv/bin/activate pip3 install python-gsmmodem-new
Otherwise if you prefer your Python modules to have in a virtual environment, run:
sudo apt install python3-venv rustc librust-openssl-dev python3 -m venv venv source venv/bin/activate pip3 install -r requirements.txt
- Checkout code:
git clone https://github.com/pentagridsec/smsgate
- Move code to installation directory:
sudo mv smsgate /opt cd /opt/smsgate
- Create a directory to store runtime data
mkdir /var/smsgate
- Fix permissions
chown -R root.smsgate /opt/smsgate /var/cache/smsgate chmod 640 /opt/smsgate/*.conf chmod 644 /opt/smsgate/cert.pem chmod 770 /var/cache/smsgate
- Install service:
cp smsgate.service /etc/systemd/system/ sudo chown root.root /etc/systemd/system/smsgate.service
- Start service:
sudo systemctl daemon-reload sudo systemctl enable smsgate sudo systemctl start smsgate sudo systemctl status smsgate
The sim-cards.conf
configuration files contains the settings for the SIM
cards and corresponding modems. Each modem has an own SIM card and a
corresponding SIM card configuration entry. An example for a single modem
respectively SIM card is shown below.
[00] enabled = True port=/dev/ttyUSB1 phone_number = +491762xxxxxxx provider = Myprovider pin = 2342 ussd_account_balance = *101# #ussd_account_balance_regexp = Ihr Guthaben beträgt: ([\d+\,]+) currency = EUR account_balance_warning = 10.00 account_balance_critical = 5.00 prefixes = +49176 +49 costs_per_sms = 0.09 health_check_interval = 600 imei = 35972xxxxxxxxxx encoding = UCS2 #email_address = [email protected]
The configuration is in the INI format. The modem is identified via a string.
Here it is 00
written in brackets. It refers to the modem slot 00
,
but could be any other identifier as well. The enabled
setting allows
the operator to disable a modem slot. If disabled, the modem is not initialized.
The port
setting defines the serial interface, where the modem is attached.
If the exact port is not known, the file path may use wildcard such as /dev/ttyACM*
.
The SMS gateway will then probe for the device. It does so by looking for the imei
,
which is the International Mobile Equipment Identity and which identifies the modem.
The phone_number
defines the phone number assigned to the SIM card. It is
used as identifier, for example for incoming SMS, but also to identify modems,
for example, when a user sends a USSD code or an SMS via the XMLRPC API. Then
it is possisble and for USSD codes necessary to specify a sender. The
phone_number
enables the gateway to find the right modem for sending the
SMS or the USSD code.
The provider
is an
information about the operator, the SIM card is associated with. It is not
necessarily the same network operator the modem connects to. The information is not used
but it may be helpful to find SIM cards in the config file. The pin
setting is
the SIM card PIN that unlocks secret keys on the SIM card to allow an
authentication towards the GSM network. If there is no SIM, leave it blank.
The ussd_account_balance
is an USSD code to retrieve the account balance
associated with the SIM card. This is required for pre-paid accounts, which
require vouchers to be loaded to an account. When the USSD code is sent, the
network often returns a human-readable string. The ussd_account_balance_regexp
is a regular expression, which is checked against the string returned by the
ussd_account_balance
operation in order to extract the account balance
value in a currency referred via currency
. The default regular expression for
ussd_account_balance_regexp
is (\d+[\,\.]\d\d)
, which should match
in most cases. If the account balance is
below a certain threshold, a warning respectively a fault is triggered
depending on the underrun of account_balance_warning
or
account_balance_critical
. If there is no ussd_account_balance
setting, the balance is not checked. If account_balance_warning
and account_balance_critical
are set to
zero, neither a warning nor a critical is triggered, which effectively
disables the function.
The prefixes
configuration value specifies which phone networks a modem
respectively a SIM card is responsible for. The setting's value is a list of
phone number prefixes in E.123 international format, which is used to feed
the SMS router. The standard router is a simple implementation with a
preference for low costs. Additionally, the list is also an allowed list.
If a prefix is not on the list, there is no route to the network. The
costs_per_sms
is the costs to send an SMS to a destination network. There
is not conversion between currencies. There is also only a fixed price per
SIM card. If the standard router does not fit, the model must be re-implemented.
The health_check_interval
specified in seconds is used for the internal
monitoring. After this interval has expired, the server performs a modem, network
and account balance self check and updates internal flags.
The imei
information is necessary to identify the modem, because USB
devices may be renumbered. To get the IMEI from your device, open a serial
connection
picocom --echo -b 115200 /dev/ttyACM4 AT+CGSN 86053XXXXXXXXXX
The smsgate.conf
contains configurations for the SMS Gateway and its components.
An example configuration is shown below. The example is split into multiple sections
as described below.
[mail] #enabled = True server = mymailserver.example.org port = 465 user = [email protected] password = secretpass recipient = [email protected] health_check_interval = 600
A first section defines the SMTPS E-mail account for the SMTP delivery of received SMS. At the moment,
it is required to use SMTPS. The 'STARTTLS' mechanism is not supported. The recipient
defines the destination E-mail address that receives incoming SMS. If E-mail forwarding
is not desired, the option can be disabled via the enabled
setting by setting its
value to False
.
[server] host = localhost port = 7000 certificate = cert.pem key = key.pem
The next section defines the XMLRPC server interface that runs on host:port
. The server
supports TLS version >= 1.2. certificate
and key
refer to an X.509 certificate. When you
want to operate the gateway in the local network, binding the server to 0.0.0.0
is recommended.
otherwise it won't be accessible.
If you do not have an own certificate authority, generating and using a self-signed certificate is okay, when you bind the client to also use this self-signed certificate as trust anchor. You can create a private key and certificate by running:
./tools/make_cert.sh
This script creates a certificate with the CN set to localhost
. You may want to adjust this. Otherwise
clients trusting the self-signed certificate may fail at the hostname verification.
If you do not use a self-signed certificate, but a certificate deployed to your server, the path can be entered there, for example:
certificate = /etc/ssl/certs/myhostname.crt key = /etc/ssl/private/myhostname.key
You need to ensure the server can read the private key. If you use a Linux, your
certificates/keys may belong to the group ssl-cert
sudo usermod -a -G ssl-cert smsgate
In the next configuration section, the API access is configured.
[api] # Allow users to send SMS via the XMLRPC API. # Warning: User may send SMS to expensive service lines. enable_send_sms = True # Allow users to send USSD codes via the XMLRPC API. # Warning: User may alter mobile billing plans and booking # options, which may lead to costs. enable_send_ussd = True # API token token_send_sms = $2b$10$Vr3t8gYlc9.OFQspGP7Ez.fR9TLXnBVdZKZKgg77Vuspg16MOel4G token_send_ussd = $2b$10$Vr3t8gYlc9.OFQspGP7Ez.fR9TLXnBVdZKZKgg77Vuspg16MOel4G token_get_health_state = $2b$10$yPqkNIyAZuzxLebb/oROiuoFxv2h9AlORWnMO312G8N9.oem0Xnpi token_get_stats = $2b$10$yPqkNIyAZuzxLebb/oROiuoFxv2h9AlORWnMO312G8N9.oem0Xnpi token_00_get_sms = $2b$10$MIeCuGE9mZ0DiLv0RHZbweFtMHgEf/Wr20aNniYTvvullbGs9Rc7e token_01_get_sms = $2b$10$MIeCuGE9mZ0DiLv0RHZbweFtMHgEf/Wr20aNniYTvvullbGs9Rc7e token_02_get_sms = $2b$10$MIeCuGE9mZ0DiLv0RHZbweFtMHgEf/Wr20aNniYTvvullbGs9Rc7e
The setting enable_send_sms
enables or disables access to the SMS
sending API. If sending SMS is not desired, this functionality can be disabled here. A
similar option is enable_send_ussd
, which gives control on enabling or disabling
sending USSD codes via the API.
When the XMLRPC API is used, the user must provide an access token. In the configuation
file, it is a bcrypt-hashed token. You can create API token with the tools/generate_api_token.py
script like this:
./tools/generate_api_token.py Time : 0.053636 s Hashed API Token : $2b$10$MIeCuGE9mZ0DiLv0RHZbweFtMHgEf/Wr20aNniYTvvullbGs9Rc7e API Token : tymhoA1khwtcDIe3RD0DUoDiwH81BJ
Add its hash output to the configuration file and use the clear-text token on the client side. You can add multiple hashed API token per config entry. They must be separated with a space.
token_send_sms
is the API token required to send SMS and to fetch the SMS delivery state.
token_send_ussd
is quite the same for USSD codes, but without status fetching. There, the
API call returns the USSD response directly. The token_get_health_state
API token is
intended for Icinga checks and the token_get_stats
for a Munin plugin. In the
configuration file, there are several token_*_get_sms
API token for retrieving SMS content
via individual modems. It allows you to assign modems to individuals for testing or to assign
a modem to a project group.
Warning: Please ensure that files containing API token have proper file permissions.
They likely won't with a standard umask
value.
[modempool] # Perform an internal health check after this time intervall in seconds. # The health check includes an account balance check. If the interval is # to tight, the balance check may fail. health_check_interval = 300 # At a regular interval, each enabled modem sends an SMS to "itself". # This is part of the health check and generates a financial event # that may convince the operator to not shut down the subscription. # Possible values are: monthly, weekly, daily sms_self_test_interval = monthly # A file that stores previously found associations between serial ports # and IMEIs. These associations are used as hints on server (re)start to # speed-up the process. The file must be writable by the server user. serial_ports_hint_file = /var/cache/smsgate/serial_ports.hints
The modempool
section contains settings to control the interval for
health checks. In addition, it is possible to trigger SMS sending at regular
intervals via the sms_self_test_interval
setting. The serial_ports_hint_file
setting controls where the service stores associations between IMEIs and
serial ports.
[logging] # Log level: debug, info, warning, error, critical # Warning: Enabling DEBUG causes the SIM card pin and SMS to be logged. level = INFO
Furthermore, it is possible to define a log level via setting level
on
which the SMS Gateway produces logs.
Last but not least, there is _experimental_ support for SECCOMP to restrict, which system-calls are allowed to run. For non-allowed syscalls, the kernel denies the operation. SECCOMP is disabled by default here, but it is possible to enable this.
[seccomp] # Experimental SECCOMP support. Enabling this may require startup debugging. enabled = False
- Install plugin:
cd /etc/icinga2/conf.d/ ln -s /opt/smsgate/icinga/check_smsgate.py . ln -s /opt/smsgate/icinga/service-smsgate.conf .
- Ensure that Icinga is able to read the configuration files. Otherwise, the check will be silently ignored (but maybe logged somewhere).
- Restart icinga:
systemctl restart icinga2 systemctl status icinga2.service
- To install ths Munin plugin, go to the Munin nodes plugin directory and add a link.
cd /etc/munin/plugins ln -s /opt/smsgate/munin/munin_smsgate.py smsgate
- Edit
/etc/munin/plugin-conf.d/munin-node
, add and adjust the following lines:
[smsgate] env.smsgate_cafile /opt/smsgate/conf/cert.pem env.smsgate_host localhost env.smsgate_port 7000 env.smsgate_api_token MY-API-KEY
A SBOM file in Cyclone-DX format has been added as cyclonedx-sbom.xml
.
SMSgate
is developed by Martin Schobert <[email protected]> and
published under a BSD licence with a non-military clause. Please read
LICENSE.txt
for further details.