Monday, 16 December 2013

Use an old Mobile Phone as a GSM Gateway in Asterisk

SOURCE: http://www.stocksy.co.uk/articles/Networks/use_an_old_mobile_phone_as_a_gsm_gateway_in_asterisk/

Like most people I carry a mobile phone, but mine is for emergencies only. Just a handful of people know the the number and that's how I like to keep it. Because I use Asterisk, I'm always reachable through my land line number which I route around between various destinations or voicemail depending on what suits me. I'm spoilt by this, so having my mobile phone ring unexpectedly at an inconvenient time is a bit intrusive.
But, increasingly, almost everyone I interact with wants my mobile number - employers, customers, banks, garages, insurance companies - if it doesn't start '07', they're not happy.
As usual I've decided to try a technical solution to a social problem. I began by using a 'personal use' 070 number which is designed for precisely the kind of single-number-reach setup I use. This 070 number was presented at my SIP provider, who would then route the calls to my Asterisk server across the internet. In the end, this proved to be unsatisfactory because many providers block the 070 range with the justification that it has been abused for premium-rate scams. For example, the number couldn't be dialled from T-Mobile or Orange. Shame.
Undeterred, I tried another approach. I now have an old spare mobile phone which never leaves the house and is permanently connected through bluetooth to Asterisk. This is a real mobile with a real mobile number. I simply feed any incoming calls to this mobile into a macro which handles the call in the same way as calls to my landline. If I want to take calls on my (real) mobile, I can. If I'm not available to take the call, the caller is passed to my Asterisk voicemail box. SMS text messages arrive as emails, my replies to which are sent by SMS. Having all my incoming calls and voicemail messages in one place is very convenient and it prevents me from missing calls when I am in the house and probably would not hear a mobile phone ringing.
Asterisk has included support for bluetooth connections to mobile phones and headsets for some time now. This is accomplished through chan_mobile. Not all phones are supported, so it's worth taking a look at voip-info.org's page which lists the confirmed compatible dongles and phones. I am getting good results with a D-Link DBT-120 dongle and a Nokia E72, 6306i, 6021 handsets, however only the 6021 works with SMS. It is worth noting that each bluetooth dongle can support only one mobile device - this is an annoying limitation of chan_mobile, but it's not as though USB dongles are very expensive.

How it's Done

chan_mobile is an addon, so it needs to be enabled before Asterisk is compiled. On Debian, it's pretty simple, just add a few packages:
# apt-get install bluez-utils bluez-hcidump libbluetooth-dev
then, go to your Asterisk source directory and use make menuselect to enable chan_mobile. It's in Add-ons -> chan_mobile:
# cd /usr/src/asterisk-1.8.11.0
# ./configure && make menuselect
Whilst it compiled and installed OK, I had to make a modification to the chan_mobile source before it would recognise my phone:
# vi /usr/src/asterisk-1.8.11.0/addons/chan_mobile.c

Find this:
 addr.rc_channel = (uint8_t) 1;

Replace with:
 addr.rc_channel = (uint8_t) 0;
Build Asterisk and (re)install:
# make && make install
In order to use a bluetooth-connected phone as a GSM gateway, it's necessary to pair the phone with the Asterisk server. In Debian, this can be accomplished painlessly through the CLI. First, make your phone discoverable and then scan for it:
# hcitool scan
Scanning ...
 EC:1B:6B:64:C2:88 Trollphone
Make a note of the MAC address. In order to pair, a helper is required to handle the PIN. Run the helper in the background and begin the pairing process:
# bluetooth-agent 7472 &
# rfcomm connect hci0 EC:1B:6B:64:C2:88
Once the pairing has succeeded, make sure your phone is configured to automatically accept connections for this paring in future. You can verify that the paring is working at any time by running:
# hcitool con
Connections:
 < ACL EC:1B:6B:64:C2:88 handle 41 state 1 lm MASTER AUTH ENCRYPT
Now, Asterisk needs to be configured to use the paired phone. We need to know which rfcomm channel offers the voice service. The easiest way is to use chan_mobile:
# rasterisk
*CLI> module load chan_mobile.so
Don't worry about any errors loading the module, it'll do for now:
*CLI> mobile search 
EC:1B:6B:64:C2:88 Trollphone                     Yes    Phone   2
In this case it is rfcomm channel 2. In addition, we need to know the MAC address of the bluetooth dongle installed in the Asterisk server. Exit the Asterisk CLI and use hcitool:
# hcitool dev
Devices:
 hci0 00:81:C5:33:25:A4
At last we have all the information needed. Edit or create the chan_mobile configuration file:
# vi /etc/asterisk/chan_mobile.conf

[Adapter]
address = 00:81:C5:33:25:A4
id = pabx

[Trollphone]
address = EC:1B:6B:64:C2:88
port = 2
context = from-trollphone
adapter = pabx
You will need something in the dialplan to handle this, at minimum something like:
# vi /etc/asterisk/extensions.conf

[from-trollphone]
exten => s,1,Dial(SIP/100)

[my-phones]
exten => *12,1,Dial(MOBILE/Trollphone/150)
When the mobile rings, you should get a call on SIP extension 100. Dialling *12 will cause the phone to dial 150, which in my case gives me Orange customer services. I'm sure you get the idea.

What about SMS?

Trickier, but there is a solution. None of the phones I had spare were supported by chan_mobile's SMS capabilities. According to the chan_mobile wiki page, only three phones are known to support SMS: the Nokia models E51, 6021 and 6230i. Of the three, the 6021 seems to be the most widely available - I was able to get three of them from eBay for just a few pounds.
Once the phone is paired in the normal way, it will send any incoming SMS messages to Asterisk over the bluetooth connection. Asterisk looks for an 'sms' extension in the context you specified in chan_mobile.conf. I suggest something like this in your dialplan:
[from-trollphone]
exten => sms,1,Verbose(Incoming SMS from ${SMSSRC} ${SMSTXT})
exten => sms,n,System(echo "To: stocksy@stocksy.co.uk" > /tmp/smsmail)
exten => sms,n,System(echo "Subject: SMS from ${SMSSRC}" >> /tmp/smsmail)
exten => sms,n,System(echo "${SMSTXT}" >> /tmp/smsmail)
exten => sms,n,System(sendmail -t -f ${SMSSRC}@sms.stocksy.co.uk < /tmp/smsmail)
exten => sms,n,Hangup()
At first, incoming messages were all arriving with a blank ${SMSSRC}, the easy solution was to apply a patch and re-compile:
# cd /usr/src/asterisk-1.8*
# wget --no-check-certificate https://issues.asterisk.org/jira/secure/attachment/42026/sms-sender-fix.diff
# patch -p0 < sms-sender-fix.diff
# ./configure && make && make install
Now, incoming messages are delivered to me as emails claiming to be from +MOBILENUMBER@sms.stocksy.co.uk. Obviously, this requires the Asterisk system to have a working MTA, the setup of which I won't cover here. If you don't have an MTA at present, take a look at postfix.
Outgoing SMS messages are more work because it's necessary to parse the contents of the email message, the format of which will be a little less predicatable than an SMS. I elected to use python to do this because it already has a library to do this.
#!/usr/bin/env python
# (:? YOUR SCRIPT IS BAD AND YOU SHOULD FEEL BAD! (:?
# I'M NOT A DEVELOPER AND THIS IS PROBABLY VERY, VERY BAD, but it does work.
# email2sms.py James Stocks
# based upon emailspeak.py by sysadminman - http://sysadminman.net
# v0.0  2012-04-28


# Import libs we need
import sys, time, email, email.Message, email.Errors, email.Utils, smtplib, os, socket, random, re
from datetime import date
from email.Iterators import typed_subpart_iterator
from time import sleep

# Asterisk Manager connection details
HOST = '127.0.0.1'
PORT = 5038
# Asterisk Manager username and password
USER = 'your-ast-man-user'
SECRET = 'dysmsdvsa'

# Generate a random number as a string. We'll use this for file names later on
callnum = str(random.randint(1, 100000000))

# Taken from here, with thanks -
# http://ginstrom.com/scribbles/2007/11/19/parsing-multilingual-
# email-with-python/
def get_charset(message, default="ascii"):
    """Get the message charset"""

    if message.get_content_charset():
        return message.get_content_charset()

    if message.get_charset():
        return message.get_charset()

    return default

# Taken from here, with thanks -
# http://ginstrom.com/scribbles/2007/11/19/parsing-multilingual-
# email-with-python/
def get_body(message):
    """Get the body of the email message"""

    if message.is_multipart():
        #get the plain text version only
        text_parts = [part
                      for part in typed_subpart_iterator(message,
                                                         'text',
                                                         'plain')]
        body = []
        for part in text_parts:
            charset = get_charset(part, get_charset(message))
            body.append(unicode(part.get_payload(decode=True),
                                charset,
                                "replace"))

        return u"\n".join(body).strip()

    else: # if it is not multipart, the payload will be a string
          # representing the message body
        body = unicode(message.get_payload(decode=True),
                       get_charset(message),
                       "replace")
        return body.strip()

# Read the e-mail message that has been piped to us by Postfix
raw_msg = sys.stdin.read()
emailmsg = email.message_from_string(raw_msg)

# Extract database Fields from mail
msgfrom = emailmsg['From']
msgto =  emailmsg['To']
msgsubj = emailmsg['Subject']
msgbody = get_body(emailmsg)

# Find the part of the 'To' field that is the phone number
phonenum = re.match( r'\+?([0-9]+)', msgto, re.M)

# Whose mobile is this?
mobile = sys.argv[1]

# Write a log file in /tmp with a record of the e-mails
currtime = date.today().strftime("%B %d, %Y")
logfile = open('/tmp/email2sms.log', 'a')
logfile.write(currtime + "\n")
logfile.write("Call Number: " + callnum + "\n")
logfile.write("From: " + msgfrom + "\n")
logfile.write("To: " + msgto + "\n")
logfile.write("Subject: " + msgsubj + "\n")
logfile.write("Body: " + msgbody + "\n\n")
logfile.close()

# Send the call details to the Asterisk manager interface
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
sleep(1)
s.send('Action: login\r\n')
s.send('Username: ' + USER + '\r\n')
s.send('Secret: ' + SECRET + '\r\n\r\n')
sleep(1)
s.send('Action: originate\r\n')
# Dummy channel - I don't actually want any phones to ring
s.send('Channel: LOCAL/1@sms-dummy\r\n')
s.send('Context: mobiles\r\n')
s.send('Exten: ' + mobile + '\r\n') 
s.send('WaitTime: 30\r\n')
# This is a bogus value, but the field is required
s.send('CallerId: 5555\r\n')
# Do not wait for response from dummy channel
s.send('Async: true\r\n')
s.send('Priority: 1\r\n')
# The variables ${SMSTO} and ${SMSBODY} are used in the dialplan
s.send('Variable: SMSTO=' + phonenum.group(1) + ',SMSBODY=\"' + msgbody + '\"\r\n\r\n')
sleep(1)
s.send('Action: Logoff\r\n\r\n')
#Omitting this causes "ast_careful_fwrite: fwrite() returned error: Broken pipe"
sleep(3)
s.close()
Copy the above script to /usr/sbin/email2sms.py and make executable:

# chmod +x /usr/sbin/email2sms.py
The script uses the Asterisk Manager Interface, so it will need an AMI user. Append this to manager.conf:
# vi /etc/asterisk/manager.conf

[your-ast-man-user]
secret=dysmsdvsa
read=call,user,originate
write=call,user,originate
and also make sure it is enabled in the general section:
# vi /etc/asterisk/manager.conf

[general]
enabled = yes
webenabled = yes
port = 5038
You'll note that I'm using the context 'mobiles'. You'll need to make sure that the extensions you'll be using exist in this context in extensions.conf:
# vi /etc/asterisk/extensions.conf

exten => stocksy,1,MobileSendSMS(JS6021,${SMSTO},${SMSBODY})
exten => karen,1,MobileSendSMS(trollphone,${SMSTO},${SMSBODY})
Secondly, there is a dummy extension which the 'call' needs to connect to. A NoOp isn't quite sufficient, I could only get it to work if the extension answered and then did something, in this case answer and wait 10 seconds:
# vi /etc/asterisk/extensions.conf

[sms-dummy]
exten => 1,1,Answer()
exten => 1,n,Wait(10)
exten => 1,n,Hangup
Reload Asterisk to pick up the changes.
So, calling email2sms.py with the argument 'stocksy' uses the JS6021 mobile, and calling it with 'karen' uses the trollphone mobile.
You need to make sure that email for the domain you have chosen - in my case sms.stocksy.co.uk - is routed to the Asterisk box. This will normally be accomplished by creating an MX record or creating a transport for the domain on your mail server. Again, I'm not going to cover that part here, but I will cover how to pipe the incoming messages into the python script.
Assuming that you are using postfix, you'll need a new transport for each mobile you want to use. In my case:
# vi /etc/postfix/master.cf

sms-stocksy unix -      n       n       -       -       pipe
  flags=FR user=stocksy argv=/usr/sbin/email2sms.py stocksy

sms-karen unix  -       n       n       -       -       pipe
  flags=FR user=stocksy argv=/usr/sbin/email2sms.py karen
postfix needs to know that it must use these transports for SMS domains:
# vi /etc/postfix/transport ; postmap /etc/postfix/transport

sms.stocksy.co.uk sms-stocksy
sms.herdomain.co.uk sms-karen
If postfix doesn't already have a transport_maps setting, create one. Obviously this could break any existing postfix setup you might have, but if so I'm expecting you to know what you're doing:
# postconf -e transport_maps=hash:/etc/postfix/transport
Restart postfix and that should be all that's necessary.
# /etc/init.d/postfix restart
You need to satisfy yourself that you are not allowing the entire world to relay through your SMS gateway! Understand and make use of postfix's security features! Don't wait until you've racked up a collosal SMS bill! Loud noises!
If things aren't quite working, start by checking your mail log:
# tail -f /var/log/mail.log
You can do a packet trace to see what's happening on the Asterisk Manager Interface:
# tcpdump -A -i lo port 5038
Try talking to the AMI directly:
$ nc localhost 5038

Action: login
Username: your-ast-man-user
Secret: dysmsdvsa

Action: originate
Channel: LOCAL/1@sms-dummy
Context: mobiles 
Exten: stocksy
WaitTime: 30
CallerId: 5555
Async: true
Priority: 1
Variable: SMSTO=5555555555,SMSBODY="foo"

Action: Logoff
Watch out for whitespace in the AMI - exten 'stocksy' != 'stocksy '.
Good luck.

No comments: