[sip/asterisk] Ménage dans le code

This commit is contained in:
Valentin Samir 2015-09-30 11:16:38 +02:00
parent ef0e8bb22e
commit 588eba0515

View file

@ -1,19 +1,14 @@
# -*- coding: utf-8 - # -*- coding: utf-8 -
# liste d'event http://www.voip-info.org/wiki/view/asterisk+manager+events # liste d'event http://www.voip-info.org/wiki/view/asterisk+manager+events
# liste d'action http://www.voip-info.org/wiki/view/Asterisk+manager+API # liste d'action http://www.voip-info.org/wiki/view/Asterisk+manager+API
import os
import sys import sys
import time import time
import errno
import shutil
import syslog import syslog
import socket import socket
import base64 import base64
import syslog
import psycopg2 import psycopg2
import threading import threading
import psycopg2.extras import psycopg2.extras
from datetime import datetime
if '/usr/scripts' not in sys.path: if '/usr/scripts' not in sys.path:
sys.path.append('/usr/scripts') sys.path.append('/usr/scripts')
@ -22,40 +17,51 @@ import lc_ldap.objets
import gestion.secrets_new as secrets import gestion.secrets_new as secrets
class NullRecv(EnvironmentError): class NullRecv(EnvironmentError):
pass pass
class AsteriskError(ValueError): class AsteriskError(ValueError):
def __init__(self, message, action, params): def __init__(self, message, action, params):
self.message=message self.message = message
self.action=action self.action = action
self.params=params self.params = params
def __str__(self): def __str__(self):
return '%s, Action:%s, params:%s' % (self.message, self.action, self.params) return '%s, Action:%s, params:%s' % (self.message, self.action, self.params)
class Profile(object): class Profile(object):
def __init__(self, sql_params=None, database=None): def __init__(self, sql_params=None, database=None):
self.sql_params = sql_params self.sql_params = sql_params
self.database =database self.database = database
def update_pin(self, num, pin): def update_pin(self, num, pin):
conn = psycopg2.connect(self.sql_params) conn = psycopg2.connect(self.sql_params)
cur = conn.cursor() cur = conn.cursor()
cur.execute("UPDATE %s SET voicemail_password=%%s WHERE num=%%s" % self.database, (pin, num)) cur.execute(
"UPDATE %s SET voicemail_password=%%s WHERE num=%%s" % self.database,
(pin, num)
)
conn.commit() conn.commit()
cur.close() cur.close()
conn.close() conn.close()
def right_to_nums(self, right): def right_to_nums(self, right):
conn=lc_ldap.shortcuts.lc_ldap_readonly() conn = lc_ldap.shortcuts.lc_ldap_readonly()
ret=conn.search(u"(&(droits=%s)(!(chbre=EXT)))" % right) ret = conn.search(u"(&(droits=%s)(!(chbre=EXT)))" % right)
return [ "1%04d" % adh['aid'][0].value for adh in ret] return ["1%04d" % adh['aid'][0].value for adh in ret]
def alias_to_num(self, alias): def alias_to_num(self, alias):
try: try:
conn=lc_ldap.shortcuts.lc_ldap_readonly() conn = lc_ldap.shortcuts.lc_ldap_readonly()
ret=conn.search(u"(|(uid=%(alias)s)(mailAlias=%(alias)s@crans.org)(canonicalAlias=%(alias)s@crans.org))" % {'alias' : alias}) ret = conn.search(
(
u"(|(uid=%(alias)s)(mailAlias=%(alias)s@crans.org)"
+ "(canonicalAlias=%(alias)s@crans.org))"
) % {'alias': alias}
)
if len(ret) == 1: if len(ret) == 1:
return "1%04d" % ret[0]['aid'][0].value return "1%04d" % ret[0]['aid'][0].value
else: else:
@ -73,22 +79,30 @@ class Profile(object):
conn.close() conn.close()
if caller_id == 'full_name' or caller_id == 'both': if caller_id == 'full_name' or caller_id == 'both':
conn=lc_ldap.shortcuts.lc_ldap_readonly() conn = lc_ldap.shortcuts.lc_ldap_readonly()
aid=int(num[1:]) aid = int(num[1:])
adh=conn.search(u'aid=%s' % aid)[0] adh = conn.search(u'aid=%s' % aid)[0]
return '%s %s' % (adh['prenom'][0],adh['nom'][0]) return '%s %s' % (adh['prenom'][0], adh['nom'][0])
else: else:
return num return num
except: except:
return num return num
class Sms(object): class Sms(object):
def __init__(self, sql_params=None, database=None): def __init__(self, sql_params=None, database=None):
self.sql_params = sql_params self.sql_params = sql_params
self.database = database self.database = database
def sms_daemon(self, server,port,user,password, timeout=360): def sms_daemon(self, server, port, user, password, timeout=360):
manager = Manager(user, password, server=server, event=True, auto_connect=False, timeout=timeout) manager = Manager(
user,
password,
server=server,
event=True,
auto_connect=False,
timeout=timeout
)
manager.register_events_callback('PeerStatus', self._send_sms) manager.register_events_callback('PeerStatus', self._send_sms)
while True: while True:
manager.connect() manager.connect()
@ -99,29 +113,40 @@ class Sms(object):
pass pass
def sms_delay(self, src, dst, body, user, body_type='str'): def sms_delay(self, src, dst, body, user, body_type='str'):
if not body_type in ["str", "base64"]: if body_type not in ["str", "base64"]:
raise EnvironmentError("body_type sould be 'str' ou 'base64' not %r" % body_type) raise EnvironmentError("body_type sould be 'str' ou 'base64' not %r" % body_type)
conn = psycopg2.connect(self.sql_params) conn = psycopg2.connect(self.sql_params)
cur = conn.cursor() cur = conn.cursor()
cur.execute('INSERT INTO %s (date, "from", "to", body, "user") VALUES (NOW(), %%s, %%s, %%s, %%s)' % self.database, (src, dst, base64.b64encode(body).strip() if body_type=='str' else body, user)) cur.execute(
'INSERT INTO %s (date, "from", "to", body, "user") VALUES (NOW(), %%s, %%s, %%s, %%s)' %
self.database,
(src, dst, base64.b64encode(body).strip() if body_type == 'str' else body, user)
)
conn.commit() conn.commit()
cur.close() cur.close()
conn.close() conn.close()
def _send_sms(self, manager, params): def _send_sms(self, manager, params):
if params['PeerStatus'] in ['Reachable','Registered']: if params['PeerStatus'] in ['Reachable', 'Registered']:
num = params['Peer'].split('/')[1] num = params['Peer'].split('/')[1]
conn = psycopg2.connect(self.sql_params) conn = psycopg2.connect(self.sql_params)
cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
cur.execute('SELECT * FROM %s WHERE "user"=%%s ORDER BY "date" ASC' % self.database, (num,)) cur.execute(
'SELECT * FROM %s WHERE "user"=%%s ORDER BY "date" ASC' % self.database,
(num,)
)
for sms in cur.fetchall(): for sms in cur.fetchall():
try: try:
manager.messageSend(sms['from'], sms['to'], sms['body'], body_type='base64') manager.messageSend(sms['from'], sms['to'], sms['body'], body_type='base64')
syslog.syslog("Message from %s successfully delivered to %s" % (sms['from'], sms['to'])) syslog.syslog(
"Message from %s successfully delivered to %s" % (sms['from'], sms['to'])
)
cur.execute('DELETE FROM %s WHERE id=%%s' % self.database, (sms['id'],)) cur.execute('DELETE FROM %s WHERE id=%%s' % self.database, (sms['id'],))
conn.commit() conn.commit()
except AsteriskError as error: except AsteriskError:
syslog.syslog("Message from %s to %s : %s" % (sms['from'], sms['to'], params['Message'])) syslog.syslog(
"Message from %s to %s : %s" % (sms['from'], sms['to'], params['Message'])
)
cur.close() cur.close()
conn.close() conn.close()
@ -129,7 +154,7 @@ class Sms(object):
if isinstance(src, lc_ldap.objets.proprio): if isinstance(src, lc_ldap.objets.proprio):
# rajouter @crans.org ne semble pas marcher, pourquoi ? # rajouter @crans.org ne semble pas marcher, pourquoi ?
num="1%04d" % src['aid'][0].value num = "1%04d" % src['aid'][0].value
profile_manager = Profile(self.sql_params, "voip_profile") profile_manager = Profile(self.sql_params, "voip_profile")
callerid = profile_manager.num_to_callerid(num) callerid = profile_manager.num_to_callerid(num)
caller = '"%s" <sip:%s@crans.org>' % (callerid, num) caller = '"%s" <sip:%s@crans.org>' % (callerid, num)
@ -149,7 +174,13 @@ class Sms(object):
ast_manager.messageSend(caller, to, msg) ast_manager.messageSend(caller, to, msg)
except AsteriskError as error: except AsteriskError as error:
if error.message == "Message failed to send.": if error.message == "Message failed to send.":
self.sms_delay(error.params['from'], error.params['to'], error.params['base64body'], error.params['to'].split(':',1)[1], body_type='base64') self.sms_delay(
error.params['from'],
error.params['to'],
error.params['base64body'],
error.params['to'].split(':', 1)[1],
body_type='base64'
)
else: else:
raise raise
@ -157,13 +188,16 @@ class Sms(object):
class History(object): class History(object):
def __init__(self, sql_params, database, quota_limit): def __init__(self, sql_params, database, quota_limit):
self.sql_params = sql_params self.sql_params = sql_params
self.database =database self.database = database
self.quota_limit = quota_limit self.quota_limit = quota_limit
def add(self, id, src, dst): def add(self, id, src, dst):
conn = psycopg2.connect(self.sql_params) conn = psycopg2.connect(self.sql_params)
cur = conn.cursor() cur = conn.cursor()
cur.execute("INSERT INTO %s (uniq_id,src,dst) VALUES (%%s, %%s, %%s)" % self.database, (id, src, dst)) cur.execute(
"INSERT INTO %s (uniq_id,src,dst) VALUES (%%s, %%s, %%s)" % self.database,
(id, src, dst)
)
conn.commit() conn.commit()
cur.close() cur.close()
conn.close() conn.close()
@ -179,7 +213,9 @@ class History(object):
def update(self, id, duration): def update(self, id, duration):
conn = psycopg2.connect(self.sql_params) conn = psycopg2.connect(self.sql_params)
cur = conn.cursor() cur = conn.cursor()
cur.execute("UPDATE %s SET duration=%%s WHERE uniq_id=%%s" % self.database, (int(duration), id)) cur.execute(
"UPDATE %s SET duration=%%s WHERE uniq_id=%%s" % self.database, (int(duration), id)
)
conn.commit() conn.commit()
cur.close() cur.close()
conn.close() conn.close()
@ -189,10 +225,21 @@ class History(object):
try: try:
conn = psycopg2.connect(self.sql_params) conn = psycopg2.connect(self.sql_params)
cur = conn.cursor() cur = conn.cursor()
cur.execute("SELECT count(DISTINCT dst) FROM %s WHERE date>='%s' AND dst LIKE '+%%'" % (self.database, time.strftime('%Y-%m-01'))) cur.execute(
"SELECT count(DISTINCT dst) FROM %s WHERE date>='%s' AND dst LIKE '+%%'" % (
self.database,
time.strftime('%Y-%m-01')
)
)
outgoing_call_num = cur.fetchall()[0][0] outgoing_call_num = cur.fetchall()[0][0]
if outgoing_call_num >= self.quota_limit: if outgoing_call_num >= self.quota_limit:
cur.execute("SELECT count(dst)>0 FROM %s WHERE date>'%s' AND dst=%%s" % (self.database, time.strftime('%Y-%m-01')), (number,)) cur.execute(
"SELECT count(dst)>0 FROM %s WHERE date>'%s' AND dst=%%s" % (
self.database,
time.strftime('%Y-%m-01')
),
(number,)
)
allowed = cur.fetchall()[0][0] allowed = cur.fetchall()[0][0]
else: else:
allowed = True allowed = True
@ -202,18 +249,23 @@ class History(object):
pass pass
sys.stdout.write('ALLOWED' if allowed else 'DENY') sys.stdout.write('ALLOWED' if allowed else 'DENY')
class Manager(object): class Manager(object):
def __init__(self, username, password, timeout=10, server='idefisk.adm.crans.org', port=5038, debug=False, event=False, auto_connect=True, agi=None, wait_fullybooted=True): def __init__(
self, username, password, timeout=10, server='idefisk.adm.crans.org',
port=5038, debug=False, event=False, auto_connect=True, agi=None,
wait_fullybooted=True
):
self.timeout = timeout self.timeout = timeout
self.server = server self.server = server
self.port = port self.port = port
self.socket = None self.socket = None
self.debug = debug self.debug = debug
self.event = event self.event = event
self._pending_action=[] self._pending_action = []
self._response={} self._response = {}
self._event=[] self._event = []
self._event_callback = {} self._event_callback = {}
self._toread = "" self._toread = ""
self._agi = agi self._agi = agi
@ -246,17 +298,20 @@ class Manager(object):
if self.debug: if self.debug:
print msg + "\n" print msg + "\n"
self._parse_msg(msg) self._parse_msg(msg)
self._toread=self._toread[-1] self._toread = self._toread[-1]
def _send(self, str): def _send(self, str):
if self.debug: if self.debug:
print str print str
self.socket.send ('%s\r\n' % (str)) self.socket.send('%s\r\n' % (str))
def _parse_msg(self, msg): def _parse_msg(self, msg):
msg = msg.strip().split('\r\n') msg = msg.strip().split('\r\n')
type, value = msg[0].split(': ', 1) type, value = msg[0].split(': ', 1)
params = dict([line.split(': ', 1) if len(line.split(': ', 1))==2 else (line.split(': ', 1)[0], '') for line in msg[1:]]) params = dict([
line.split(': ', 1) if len(line.split(': ', 1)) == 2 else (line.split(': ', 1)[0], '')
for line in msg[1:]
])
handler = getattr(self, "_do_"+type, None) handler = getattr(self, "_do_"+type, None)
handler(value, params) handler(value, params)
@ -268,23 +323,22 @@ class Manager(object):
def _do_Event(self, type, params): def _do_Event(self, type, params):
self._event.append((type, params)) self._event.append((type, params))
def _gen_actionID(self): def _gen_actionID(self):
id=time.time() id = time.time()
while id in self._pending_action: while id in self._pending_action:
id=id+1 id = id+1
return str(id) return str(id)
def _action(self, name, params): def _action(self, name, params):
self._send ('ACTION: %s' % name.upper()) self._send('ACTION: %s' % name.upper())
for (key, value) in params.items(): for (key, value) in params.items():
self._send ('%s: %s' % (key.upper(), value)) self._send('%s: %s' % (key.upper(), value))
id=self._gen_actionID() id = self._gen_actionID()
self._send ('ActionID: %s' % id) self._send('ActionID: %s' % id)
self._pending_action.append(id) self._pending_action.append(id)
self._send ('') self._send('')
while not id in self._response.keys(): while id not in self._response.keys():
self._recv() self._recv()
response = self._response[id] response = self._response[id]
@ -298,13 +352,13 @@ class Manager(object):
return self._action(name, params) return self._action(name, params)
def connect(self): def connect(self):
sock = socket.socket ( socket.AF_INET, socket.SOCK_STREAM ) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(self.timeout) sock.settimeout(self.timeout)
sock.connect ( ( self.server, self.port ) ) sock.connect((self.server, self.port))
self.socket=sock self.socket = sock
msg = self.socket.recv(1024).strip().split('\r\n', 1) msg = self.socket.recv(1024).strip().split('\r\n', 1)
self.version = msg[0] self.version = msg[0]
if len(msg)>1: if len(msg) > 1:
self._toread += msg[1] self._toread += msg[1]
self.login() self.login()
if self.wait_fullybooted: if self.wait_fullybooted:
@ -317,19 +371,22 @@ class Manager(object):
def process_events(self): def process_events(self):
if not self._event: if not self._event:
try: self._recv() try:
except socket.timeout: pass self._recv()
except socket.timeout:
pass
for event in self._event: for event in self._event:
(type, params) = event (type, params) = event
for func in self._event_callback.get(type, []): for func in self._event_callback.get(type, []):
func(self, params) func(self, params)
self._event=[] self._event = []
def login(self, username=None, secret=None): def login(self, username=None, secret=None):
"""Login Manager""" """Login Manager"""
if username is None: username = self.username if username is None:
if secret is None: secret = self.password username = self.username
if secret is None:
secret = self.password
return self.action('login', username=username, secret=secret) return self.action('login', username=username, secret=secret)
def logoff(self): def logoff(self):
@ -340,8 +397,8 @@ class Manager(object):
def events(self, param): def events(self, param):
"""Control Event Flow """Control Event Flow
params should be a boolean or a list among system,call,log,verbose,command,agent,user to select params should be a boolean or a list among system,call,log,verbose,command,agent,user
which flags events should have to be sent.""" to select which flags events should have to be sent."""
if isinstance(param, list): if isinstance(param, list):
eventmask = ','.join(param) eventmask = ','.join(param)
elif isinstance(param, bool): elif isinstance(param, bool):
@ -357,34 +414,32 @@ class Manager(object):
def reload(self, module): def reload(self, module):
"""Synopsis: Send a reload event """Synopsis: Send a reload event
Privilege: system,config,all""" Privilege: system,config,all"""
privilege = ['system', 'config', 'all']
return self.action('reload', module=module) return self.action('reload', module=module)
def messageSend(self, src, dst, body, body_type="str"): def messageSend(self, src, dst, body, body_type="str"):
if not body_type in ["str", "base64"]: if body_type not in ["str", "base64"]:
raise EnvironmentError("body_type sould be 'str' ou 'base64' not %r" % body_type) raise EnvironmentError("body_type sould be 'str' ou 'base64' not %r" % body_type)
if body_type == "str": if body_type == "str":
body = base64.b64encode(body).strip() body = base64.b64encode(body).strip()
return self._action('messageSend', {'to':dst, 'from':src, 'base64body':body}) return self._action('messageSend', {'to': dst, 'from': src, 'base64body': body})
class AGI(object): class AGI(object):
"""voir http://www.voip-info.org/wiki/view/Asterisk+AGI""" """voir http://www.voip-info.org/wiki/view/Asterisk+AGI"""
def __init__(self, read=sys.stdin, write=sys.stdout, manager=None, **params): def __init__(self, read=sys.stdin, write=sys.stdout, manager=None, **params):
self.debug = True self.debug = True
self.read=read self.read = read
self.write=write self.write = write
self.params=params self.params = params
self._manager=manager self._manager = manager
self._lock=False self._lock = False
self._locklock = threading.Lock() self._locklock = threading.Lock()
self._read_params() self._read_params()
def manager(self, *args, **kwargs): def manager(self, *args, **kwargs):
if not self._manager: if not self._manager:
self._manager=Manager(*args, agi=self, **kwargs) self._manager = Manager(*args, agi=self, **kwargs)
return self._manager return self._manager
def __getitem__(self, key): def __getitem__(self, key):
@ -404,39 +459,42 @@ class AGI(object):
return result return result
def _read_params(self): def _read_params(self):
line=self.read.readline() line = self.read.readline()
while line.strip(): while line.strip():
syslog.syslog(line) syslog.syslog(line)
if line.startswith('agi_'): if line.startswith('agi_'):
(key, data) = line[4:].split(':',1) (key, data) = line[4:].split(':', 1)
self.params[key.strip()]=data.strip() self.params[key.strip()] = data.strip()
line = self.read.readline() line = self.read.readline()
def command(self, name, *params): def command(self, name, *params):
with self._locklock: with self._locklock:
if self._lock: if self._lock:
raise AsteriskError("Cannot lauch AGI command %s, an other command is processing" % name, name, params) raise AsteriskError(
"Cannot lauch AGI command %s, an other command is processing" % name,
name,
params
)
else: else:
self._lock = True self._lock = True
cmd=' '.join([name] + ["%s" % p for p in params]) cmd = ' '.join([name] + ["%s" % p for p in params])
if self.debug: if self.debug:
syslog.syslog("%s\n" % cmd) syslog.syslog("%s\n" % cmd)
self.write.write("%s\n" % cmd) self.write.write("%s\n" % cmd)
self.write.flush() self.write.flush()
line=self.read.readline() line = self.read.readline()
if self.debug: if self.debug:
syslog.syslog(line) syslog.syslog(line)
lines=[line] lines = [line]
code=int(line[0:3]) code = int(line[0:3])
type=line[3] type = line[3]
if type == '-': if type == '-':
while not "%s End of " % code not in line: while not "%s End of " % code not in line:
line=self.read.readline() line = self.read.readline()
if self.debug: if self.debug:
syslog.syslog("%s\n" % cmd) syslog.syslog("%s\n" % cmd)
lines.append(line) lines.append(line)
self._lock = False
self._lock=False
if code != 200: if code != 200:
raise AsteriskError((code, '\n'.join(lines)), name, params) raise AsteriskError((code, '\n'.join(lines)), name, params)
@ -444,27 +502,33 @@ class AGI(object):
(result, data) = lines[0][4:].split(' ', 1) (result, data) = lines[0][4:].split(' ', 1)
except ValueError: except ValueError:
result = lines[0][4:] result = lines[0][4:]
data="" data = ""
result=result.split('=',1)[1].strip() result = result.split('=', 1)[1].strip()
return (int(result), data) return (int(result), data)
def hangup(self): def hangup(self):
self.command("hangup") self.command("hangup")
def set_callerid(self, callerid): def set_callerid(self, callerid):
self.command("set_callerid", callerid) self.command("set_callerid", callerid)
def noop(self, str): def noop(self, str):
self.command("noop", str) self.command("noop", str)
def launch_app(self, app, *params): def launch_app(self, app, *params):
self.command("exec", app, *params) self.command("exec", app, *params)
def dial(self, to): def dial(self, to):
self.launch_app("dial", to) self.launch_app("dial", to)
def answer(self): def answer(self):
self.launch_app("Answer") self.launch_app("Answer")
def goto(self, arg): def goto(self, arg):
self.launch_app("goto", arg) self.launch_app("goto", arg)
### TODO # TODO
class FastAGI(object): class FastAGI(object):
def __init__(self, bind, port, *args,**kwargs): def __init__(self, bind, port, *args, **kwargs):
pass pass