first commit

This commit is contained in:
Wonko the Sane 2021-07-10 14:17:24 +01:00
commit 876d8d17dc
3 changed files with 844 additions and 0 deletions

612
sipproxy Executable file
View file

@ -0,0 +1,612 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#import sys first so that if we bale in the following try we can still exit clean.
import sys
try:
# twisted imports
from twisted.internet.protocol import DatagramProtocol
from twisted.internet import reactor
# system imports
import codecs
import os
import re
import signal
import socket
import syslog
import time
except ImportError:
print("Some modules missing: requires Twisted among others.\n")
sys.exit(1)
##################################################################
os.chdir(os.path.dirname(__file__))
BaseDir = os.getcwd()
CRLF = "\r\n"
confdir = "conf"
conffile = "config.conf"
config = "/" + confdir + "/" + conffile
phonebookfile = BaseDir + "/" + confdir + "/addressbook.conf"
ITSP = "127.0.0.1"
##################################################################
def ReadConfig():
print("Read config")
try:
with codecs.open(BaseDir + config, 'r', encoding='utf-8') as configfile:
for line in configfile:
preconf(line)
except:
print("Error reading " + BaseDir + config + "\nAborting")
def CreateConfig():
print("Not found.\n")
print("Checking that " + BaseDir + "/" + confdir + " exists")
if (os.path.isdir(BaseDir + "/" + confdir) == False):
print("No. Attempting to create " + BaseDir + "/" + confdir)
try:
os.mkdir(BaseDir + "/" + confdir)
print("Success\n")
except OSError as e:
print("Can\'t create " + BaseDir + "/" + confdir)
print(e)
sys.exit(1)
except IOError as e:
print("Can\'t create " + BaseDir + "/" + confdir)
print(e)
sys.exit(1)
else:
print("Yes.")
print("Attempting to write configuration file " + BaseDir + config)
try:
fh = codecs.open(BaseDir + config, 'w', encoding='utf-8')
fh.write('\n')
except IOError as e:
print("Error: can\'t write file - " + BaseDir + config)
print(e)
sys.exit(1)
except OSError as e:
print("Error: can\'t write file - " + BaseDir + config)
print(e)
sys.exit(1)
else:
fh.close()
with codecs.open(BaseDir + config, 'w', encoding='utf-8') as configfile:
configfile.write("# This software was written as a shim for a Grandstream Budget One VOIP telephone\r")
configfile.write(" operating on an ipv4 lan, to add a phone book and get around the UDP NAT problem.\r")
configfile.write(" consequently it does not require a stun server or other black magic to operate.\r")
configfile.write(" It will probably work with other VOIP telephones\n")
configfile.write("# Prerequisites.\r")
configfile.write(" A VOIP telephone\r")
configfile.write(" A computer on which to run, that has either direct access to the Internet, or has\r")
configfile.write(" the following 3 UDP ports portforwarded from something that does.\r")
configfile.write(" 5060, 11110, and 11111\n")
configfile.write("# The important settings in your VOIP telephone are :- \n")
configfile.write(" IP address:\r")
configfile.write(" SIP Server:\r")
configfile.write(" Outbound Proxy:\r")
configfile.write(" local RTP port:\r")
configfile.write("# These must match up with the settings below or it just won\'t work \n")
configfile.write("# In this configuration file, there are no defaults,\r")
configfile.write("# lines beginning with white space are comments\r")
configfile.write(" Anything after a # is a comment\n")
configfile.write(" The configuration lines must begin in column 1\r")
configfile.write(" so each of the following lines needs a value.\n\n")
configfile.write("LocalPhoneIP = 192.168.0.42 # ip address of the VOIP telephone\n")
configfile.write("LocalPhonePort = 5004 # local RTP port: in the telephone's setup menu\n")
configfile.write("ThisBoxIP = 192.168.0.69 # the Internet connected box I am running on\r")
configfile.write(" this is also the Outbound Proxy: in your telephone's setup.\n")
configfile.write("PublicIP = 127.0.0.1 # Where the sip provider will send your incoming calls\n")
configfile.write("ITSP = sip.sipdiscount.com # (Internet Telephone Service Provider)\r")
configfile.write(" this the same as SIP Server: in your telephone\'s setup.\n")
configfile.write("UserID = bob_smith # SIP User ID: in your telephone\'s setup. This is used to \r")
configfile.write(" prevent random idiots making your phone ring whilst unsuccessfully\r")
configfile.write(" trying to steal your bandwidth/services.\n")
configfile.write("logfile = /var/log/phone.log # needs root to write to /var/log so use a file you have actual\r")
configfile.write(" permission to write to.\n")
configfile.write(" \n")
configfile.write(" \n")
print("Config file " + BaseDir + config + " successfully written.")
print("Additionally, " + BaseDir + config + " will require editing before you can continue.")
def preconf(line):
global LocalPhoneIP
global LocalPhonePort
global ThisBoxIP
global PublicIP
global ITSP
global UserID
global logfile
if(line[:12] == u"LocalPhoneIP"):
LocalPhoneIP = line.split("=")[1].strip()
LocalPhoneIP = LocalPhoneIP.split("#")[0].strip()
print("LocalPhoneIP = " + LocalPhoneIP)
if(line[:14] == u"LocalPhonePort"):
LocalPhonePort = line.split("=")[1].strip()
LocalPhonePort = LocalPhonePort.split("#")[0].strip()
print("LocalPhonePort = " + LocalPhonePort)
if(line[:9] == u"ThisBoxIP"):
ThisBoxIP = line.split("=")[1].strip()
ThisBoxIP = ThisBoxIP.split("#")[0].strip()
print("ThisBoxIP = " + ThisBoxIP)
if(line[:8] == u"PublicIP"):
PublicIP = line.split("=")[1].strip()
PublicIP = PublicIP.split("#")[0].strip()
print("PublicIP = " + PublicIP)
if(line[:4] == u"ITSP"):
ITSP = line.split("=")[1].strip()
ITSP = ITSP.split("#")[0].strip()
print("ITSP = " + ITSP)
if(line[:6] == u"UserID"):
UserID = line.split("=")[1].strip()
UserID = UserID.split("#")[0].strip()
print("UserID = " + UserID)
if(line[:7] == u"logfile"):
logfile = line.split("=")[1].strip()
logfile = logfile.split("#")[0].strip()
print("logfile = " + logfile)
def isip(addr):
try:
socket.inet_aton(addr)
return True
except socket.error:
return False
##################################################################
#variable
BookList = []
Destination = "127.0.0.1"
Remote_Audio_IP = "127.0.0.1"
Remote_Audio_Port = "61110"
Local_Audio_IP = "127.0.0.1"
Local_Audio_Port = "61110"
register_regex = 'CSeq: (.+?) REGISTER'
inviteSD_regex = 'INVITE sip:(.+?)@sipdiscount.com SIP/2'
register_pattern=re.compile(register_regex, flags=re.IGNORECASE)
inviteSD_pattern=re.compile(inviteSD_regex, flags=re.IGNORECASE)
##################################################################
class Sip(DatagramProtocol):
def datagramReceived(self, data, addr):
global Destination
try:
data = data.decode()
except UnicodeDecodeError as e:
print("random shite detected from " + addr[0])
print(e)
return
Time = time.strftime("%Y-%m-%d %H:%M:%S")
reg = re.findall(register_pattern,data)
if reg:
Destination = ITSP # Registration always with ITSP
print(Time, "From:", addr[0])
print(data[:12])
else:
try:
syslog.syslog("From " + addr[0])
syslog.syslog(data)
print(Time, "From:", addr[0])
print(data)
except TypeError:
syslog.syslog("TypeError")
print(Time, "TypeError:")
tdata = Proxy(data, addr)
if (addr[0] == LocalPhoneIP):
reg = re.findall(register_pattern,tdata)
if reg:
Destination = ITSP # Registration always with ITSP
self.transport.write(tdata.encode(), (Destination, 5060))
print(tdata[:12])
else:
syslog.syslog("To " + Destination)
syslog.syslog(tdata)
print(Time, "To:", Destination)
self.transport.write(tdata.encode(), (Destination, 5060))
print(tdata)
else:
if (addr[0] != LocalPhoneIP):
Logit(Time + " From ip: " + addr[0] + " " + data)
if (UserID in data):
Destination = addr[0]
reg = re.findall(register_pattern,tdata)
if reg:
self.transport.write(tdata.encode(), (LocalPhoneIP, 5060))
print(tdata[:12])
else:
syslog.syslog("To " + LocalPhoneIP)
syslog.syslog(tdata)
print(Time, "To:", LocalPhoneIP)
self.transport.write(tdata.encode(), (LocalPhoneIP, 5060))
print(tdata)
else:
Logit(Time + " From ip: " + addr[0] + " " + tdata)
##################################################################
def Proxy(data, addr):
if (addr[0] == LocalPhoneIP):
tdata = FromLocal(data, addr)
else:
tdata = FromRemote(data, addr)
if ("m=audio" in tdata):
return(tdata.strip() + CRLF)
else:
return(tdata.strip() + CRLF + CRLF)
##################################################################
def FromRemote(data, addr):
global Remote_Audio_IP # spurious, probably unneeded
data = data.replace(PublicIP, LocalPhoneIP)
data = data.replace(addr[0], ThisBoxIP)
data = Remote_SDP_Edit(data)
return data
##################################################################
def FromLocal(data, addr):
global Destination
data = FoneBook(data)
# Too many bugs, needs thinking about.
# reg = re.findall(register_pattern,data)
# if reg:
# Destination = ITSP
# print("Destination is " + Destination)
# data = data.replace(addr[0], PublicIP)
# data = Local_SDP_Edit(data)
# return data
#
# sd = re.findall(inviteSD_pattern,data)
# if sd:
# Destination = ITSP
# print("Destination is " + Destination)
# else:
# if ("INVITE sip:" in data):
# begin = data.find("@")
# begin = begin + 1
# nigeb = data.find(" ", begin)
# Destination = data[begin:nigeb]
# Destination = Destination.split(':')[0]
# print("Destination is " + Destination)
#
# if isip(Destination) == False:
# Destination = socket.gethostbyname(Destination)
#
# print("Destination is " + Destination)
data = data.replace(addr[0], PublicIP)
data = Local_SDP_Edit(data)
# print(data)
return data
##################################################################
class OutRTP(DatagramProtocol):
def datagramReceived(self, data, addr):
global Remote_Audio_IP
global Remote_Audio_Port
if (addr[0] == LocalPhoneIP):
dst = ((Remote_Audio_IP, int(Remote_Audio_Port)))
else:
dst = ((LocalPhoneIP, int(LocalPhonePort)))
self.transport.write(data, dst)
##################################################################
class OutRTCP(DatagramProtocol):
def datagramReceived(self, data, addr):
global Remote_Audio_IP
global Remote_Audio_Port
if (addr[0] == LocalPhoneIP):
dst = ((Remote_Audio_IP, int(Remote_Audio_Port) + 1))
else:
dst = ((LocalPhoneIP, (int(LocalPhonePort) + 1)))
self.transport.write(data, dst)
##################################################################
def FoneBook(data):
for line in BookList:
if (line.split("=")[0] in data):
data = data.replace(line.split("=")[0],line.split("=")[1])
return data
##################################################################
def Local_SDP_Edit(data): #fiddle with data from localphone
global Local_Audio_IP
global Local_Audio_Port
if ("m=audio" in data):
PortA = data.split("m=audio ")[1]
Local_Audio_Port = PortA.split()[0]
IPA = data.split("c=IN IP4 ")[1]
Local_Audio_IP = IPA.split()[0]
data = data.replace("m=audio " + Local_Audio_Port, "m=audio 11110")
data = data.replace("IN IP4 " + Local_Audio_IP, "IN IP4 " + PublicIP)
First = data.split("Content-Length: ")[0] + "Content-Length: "
Second = data.split(CRLF + CRLF)[1]
ilength = len(Second)
return First + str(ilength) + CRLF + CRLF + Second
else:
return data
##################################################################
def Remote_SDP_Edit(data): #fiddle with data from remotephone
global Remote_Audio_IP
global Remote_Audio_Port
if ("m=audio" in data):
PortA = data.split("m=audio ")[1]
Remote_Audio_Port = PortA.split()[0]
IPA = data.split("c=IN IP4 ")[1]
Remote_Audio_IP= IPA.split()[0]
data = data.replace("m=audio " + Remote_Audio_Port, "m=audio 11110")
data = data.replace("IN IP4 " + Remote_Audio_IP, "IN IP4 " + ThisBoxIP)
First = data.split("Content-Length: ")[0] + "Content-Length: "
Second = data.split(CRLF + CRLF)[1]
ilength = len(Second)
return First + str(ilength) + CRLF + CRLF + Second
else:
return data
##################################################################
def signal_handler(sig, frame):
print
print("signal", sig)
Time = time.strftime("%Y-%m-%d %H:%M:%S")
print(Time, "Shutting down")
reactor.stop()
print('reactor stopped')
syslog.syslog("SIGINT - Shutting Down!")
os._exit(0)
syslog.syslog("Not Visible in syslog.")
quit()
##################################################################
def MkFoneBook():
Blurb001 = "# This fonebook is crude, the error checking is minimal, so if you screw it up, then it wont work.\n"
Blurb002 = "# However, if you delete the screwed up copy, the basic file will be recreated by SipProxy.\n\n"
Blurb003 = "# Lines beginning with # are ignored, as are lines consisting solely of whitespace and blank lines.\n#\n"
Blurb004 = "#Replace=With\n"
Blurb005 = "#01* Geographic area codes.\n"
Blurb006 = "INVITE sip:01=INVITE sip:00441\n"
Blurb007 = "ACK sip:01=ACK sip:00441\n"
Blurb008 = "BYE sip:01=BYE sip:00441\n"
Blurb009 = "CANCEL sip:01=CANCEL sip:00441\n\n"
Blurb010 = "#02* Geographic area codes (introduced in 2000).\n"
Blurb011 = "INVITE sip:02=INVITE sip:00442\n"
Blurb012 = "ACK sip:02=ACK sip:00442\n"
Blurb013 = "BYE sip:02=BYE sip:00442\n"
Blurb014 = "CANCEL sip:02=CANCEL sip:00442\n\n"
Blurb015 = "#03* Nationwide non-geographic code, charged to caller at geographic area code rates (introduced 2007).\n"
Blurb016 = "INVITE sip:03=INVITE sip:00443\n"
Blurb017 = "ACK sip:03=ACK sip:00443\n"
Blurb018 = "BYE sip:03=BYE sip:00443\n"
Blurb019 = "CANCEL sip:03=CANCEL sip:00443\n\n"
Blurb020 = "#05* Corporate numbering and VoIP services\n"
Blurb021 = "INVITE sip:05=INVITE sip:00445\n"
Blurb022 = "ACK sip:05=ACK sip:00445\n"
Blurb023 = "BYE sip:05=BYE sip:00445\n"
Blurb024 = "CANCEL sip:05=CANCEL sip:00445\n\n"
Blurb025 = "#07* Mostly for mobile phones\n"
Blurb026 = "INVITE sip:07=INVITE sip:00447\n"
Blurb027 = "ACK sip:07=ACK sip:00447\n"
Blurb028 = "BYE sip:07=BYE sip:00447\n"
Blurb029 = "CANCEL sip:07=CANCEL sip:00447\n\n"
Blurb030 = "#08* Freephone (toll free) on 080, and Special Services (formerly known as local and national rate) on 084 and 087.\n"
Blurb031 = "INVITE sip:08=INVITE sip:00448\n"
Blurb032 = "ACK sip:08=ACK sip:00448\n"
Blurb033 = "BYE sip:08=BYE sip:00448\n"
Blurb034 = "CANCEL sip:08=CANCEL sip:00448\n\n"
Blurb035 = "#The following numbers are for Scunthorpe (01724) local numbers.\n"
Blurb036 = "INVITE sip:2=INVITE sip:004417242\n"
Blurb037 = "ACK sip:2=ACK sip:004417242\n"
Blurb038 = "BYE sip:2=BYE sip:004417242\n"
Blurb039 = "CANCEL sip:2=CANCEL sip:004417242\n\n"
Blurb040 = "INVITE sip:3=INVITE sip:004417243\n"
Blurb041 = "ACK sip:3=ACK sip:004417243\n"
Blurb042 = "BYE sip:3=BYE sip:004417243\n"
Blurb043 = "CANCEL sip:3=CANCEL sip:004417243\n\n"
Blurb044 = "INVITE sip:7=INVITE sip:004417247\n"
Blurb045 = "ACK sip:7=ACK sip:004417247\n"
Blurb046 = "BYE sip:7=BYE sip:004417247\n"
Blurb047 = "CANCEL sip:7=CANCEL sip:004417247\n\n"
Blurb048 = "INVITE sip:8=INVITE sip:004417248\n"
Blurb049 = "ACK sip:8=ACK sip:004417248\n"
Blurb050 = "BYE sip:8=BYE sip:004417248\n"
Blurb051 = "CANCEL sip:8=CANCEL sip:004417248\n\n"
Blurb052 = "# Here can be placed upto 100, 3 digit, speed dial shortcuts, from 100 to 199.\n"
Blurb053 = "# Each entry is a pair of lines, firstly the #speed dial number and description.\n"
Blurb054 = "# Secondly the 'Replace=With' for the preceeding line.\n\n"
Blurb0541 = "# As far as the telephone is concerned, all traffic goes to sipdiscount.com\n"
Blurb0542 = "# so if we want it go elsewhere we need to check the destination after the phonebook\n"
Blurb0543 = "# has fiddled with it and set the destination accordingly.\n\n"
Blurb055 = "#100 Example speed dial 1\n"
Blurb056 = "INVITE sip:100@sip.sipdiscount.com=INVITE sip:00441724280280@sip.sipdiscount.com\n"
Blurb057 = "ACK sip:100@sip.sipdiscount.com=ACK sip:00441724280280@sip.sipdiscount.com\n"
Blurb058 = "BYE sip:100@sip.sipdiscount.com=BYE sip:00441724280280@sip.sipdiscount.com\n"
Blurb059 = "CANCEL sip:100@sip.sipdiscount.com=CANCEL sip:00441724280280@sip.sipdiscount.com\n\n"
Blurb060 = "#101 Example speed dial 2\n"
Blurb061 = "INVITE sip:101@sip.sipdiscount.com=INVITE sip:00441724855555@sip.sipdiscount.com\n"
Blurb062 = "ACK sip:101@sip.sipdiscount.com=ACK sip:00441724855555@sip.sipdiscount.com\n"
Blurb063 = "BYE sip:101@sip.sipdiscount.com=BYE sip:00441724855555@sip.sipdiscount.com\n"
Blurb064 = "CANCEL sip:101@sip.sipdiscount.com=CANCEL sip:00441724855555@sip.sipdiscount.com\n"
try:
fh = codecs.open(phonebookfile, 'w', encoding='utf-8')
fh.write(Blurb001)
fh.write(Blurb002)
fh.write(Blurb003)
fh.write(Blurb004)
fh.write(Blurb005)
fh.write(Blurb006)
fh.write(Blurb007)
fh.write(Blurb008)
fh.write(Blurb009)
fh.write(Blurb010)
fh.write(Blurb011)
fh.write(Blurb012)
fh.write(Blurb013)
fh.write(Blurb014)
fh.write(Blurb015)
fh.write(Blurb016)
fh.write(Blurb017)
fh.write(Blurb018)
fh.write(Blurb019)
fh.write(Blurb020)
fh.write(Blurb021)
fh.write(Blurb022)
fh.write(Blurb023)
fh.write(Blurb024)
fh.write(Blurb025)
fh.write(Blurb026)
fh.write(Blurb027)
fh.write(Blurb028)
fh.write(Blurb029)
fh.write(Blurb030)
fh.write(Blurb031)
fh.write(Blurb032)
fh.write(Blurb033)
fh.write(Blurb034)
fh.write(Blurb035)
fh.write(Blurb036)
fh.write(Blurb037)
fh.write(Blurb038)
fh.write(Blurb039)
fh.write(Blurb040)
fh.write(Blurb041)
fh.write(Blurb042)
fh.write(Blurb043)
fh.write(Blurb044)
fh.write(Blurb045)
fh.write(Blurb046)
fh.write(Blurb047)
fh.write(Blurb048)
fh.write(Blurb049)
fh.write(Blurb050)
fh.write(Blurb051)
fh.write(Blurb052)
fh.write(Blurb053)
fh.write(Blurb054)
fh.write(Blurb0541)
fh.write(Blurb0542)
fh.write(Blurb0543)
fh.write(Blurb055)
fh.write(Blurb056)
fh.write(Blurb057)
fh.write(Blurb058)
fh.write(Blurb059)
fh.write(Blurb060)
fh.write(Blurb061)
fh.write(Blurb062)
fh.write(Blurb063)
fh.write(Blurb064)
except IOError:
pass
else:
print("Phone book written")
print("The phone book will need to be edited to create your speed dial")
print("shortcuts and to map your local exchange. This is not absolutely")
print("necessary immediately, and sipproxy will still function regardless.")
fh.close()
##################################################################
def Logit(Blurb):
try:
fh = codecs.open(logfile, 'a', encoding='utf-8')
Blurb = Blurb.replace("\n","\\n")
Blurb = Blurb.replace("\r","\\r")
fh.write(Blurb)
fh.write("\n")
except IOError:
pass
else:
fh.close()
##################################################################
def ReadFoneBook():
global BookList
try:
with codecs.open(phonebookfile, 'r', encoding='utf-8') as config:
for line in config:
if line[:1] != "#" and line.strip() != "":
BookList.append(line.strip())
except IOError:
pass
##################################################################
print("\n\n\n\n\n\n")
print(" ____ ____")
print(" / ___) _ ____ | _ \\")
print("( (___ (_)| _ \| |_) )_ __ ___ __ ____ __")
print(" \__ \| || |_) | __/| '__/ _ \\\ \/ /\ \/ /")
print(" ___) | || __/| | | | ( (_) )> < \ / ")
print("(_____/|_||_| |_| |_| \___//_/\_\ /_/ ")
print("\n\n\n\n\n\n")
print("The all new and improved sipproxy running under Python " + (sys.version))
print("Initialising " + __file__)
print("Looking for " + BaseDir + config)
if os.path.isfile(BaseDir + config):
result = ReadConfig()
else:
result = CreateConfig()
if os.path.isfile(phonebookfile):
print("Phone book already exists, not overwriting")
sys.exit(0)
else:
result = MkFoneBook()
sys.exit(0)
if isip(ITSP) == False:
Destination = socket.gethostbyname(ITSP)
ITSP = Destination
print("ITSP = " + Destination)
else:
Destination = ITSP
if os.path.isfile(phonebookfile):
result = ReadFoneBook()
else:
result = MkFoneBook()
signal.signal(signal.SIGINT, signal_handler)
Time = time.strftime("%Y-%m-%d %H:%M:%S")
print(Time, "Everything started up")
Logit(Time + "Started up OK")
syslog.syslog("Everything started up")
reactor.listenUDP(5060, Sip())
reactor.listenUDP(11110, OutRTP())
reactor.listenUDP(11111, OutRTCP())
reactor.run()