1
0

asterisk-opensim.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. #!/usr/bin/python
  2. # -*- encoding: utf-8 -*-
  3. from __future__ import with_statement
  4. import base64
  5. import ConfigParser
  6. import optparse
  7. import MySQLdb
  8. import re
  9. import SimpleXMLRPCServer
  10. import socket
  11. import sys
  12. import uuid
  13. class AsteriskOpenSimServerException(Exception):
  14. pass
  15. class AsteriskOpenSimServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
  16. '''Subclassed SimpleXMLRPCServer to be able to muck around with the socket options.
  17. '''
  18. def __init__(self, config):
  19. baseURL = config.get('xmlrpc', 'baseurl')
  20. match = reURLParser.match(baseURL)
  21. if not match:
  22. raise AsteriskOpenSimServerException('baseURL "%s" is not a well-formed URL' % (baseURL))
  23. host = 'localhost'
  24. port = 80
  25. path = None
  26. if match.group('host'):
  27. host = match.group('host')
  28. port = int(match.group('port'))
  29. else:
  30. host = match.group('hostonly')
  31. self.__host = host
  32. self.__port = port
  33. SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self,(host, port))
  34. def host(self):
  35. return self.__host
  36. def port(self):
  37. return self.__port
  38. def server_bind(self):
  39. self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  40. SimpleXMLRPCServer.SimpleXMLRPCServer.server_bind(self)
  41. class AsteriskFrontend(object):
  42. '''AsteriskFrontend serves as an XmlRpc function dispatcher.
  43. '''
  44. def __init__(self, config, db):
  45. '''Constructor to take note of the AsteriskDB object.
  46. '''
  47. self.__db = db
  48. try:
  49. self.__debug = config.getboolean('dispatcher', 'debug')
  50. except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
  51. self.__debug = False
  52. def account_update(self, request):
  53. '''update (or create) the SIP account data in the Asterisk RealTime DB.
  54. OpenSim's AsteriskVoiceModule will call this method each
  55. time it receives a ProvisionVoiceAccount request.
  56. '''
  57. print '[asterisk-opensim] account_update: new request'
  58. for p in ['admin_password', 'username', 'password']:
  59. if p not in request:
  60. print '[asterisk-opensim] account_update: failed: missing password'
  61. return { 'success': 'false', 'error': 'missing parameter "%s"' % (p)}
  62. # turn base64 binary UUID into proper UUID
  63. user = request['username'].partition('@')[0]
  64. user = user.lstrip('x').replace('-','+').replace('_','/')
  65. user = uuid.UUID(bytes = base64.standard_b64decode(user))
  66. if self.__debug: print '[asterisk-opensim]: account_update: user %s' % user
  67. error = self.__db.AccountUpdate(user = user, password = request['password'])
  68. if error:
  69. print '[asterisk-opensim]: DB.AccountUpdate failed'
  70. return { 'success': 'false', 'error': error}
  71. print '[asterisk-opensim] account_update: done'
  72. return { 'success': 'true'}
  73. def region_update(self, request):
  74. '''update (or create) a VoIP conference call for a region.
  75. OpenSim's AsteriskVoiceModule will call this method each time it
  76. receives a ParcelVoiceInfo request.
  77. '''
  78. print '[asterisk-opensim] region_update: new request'
  79. for p in ['admin_password', 'region']:
  80. if p not in request:
  81. print '[asterisk-opensim] region_update: failed: missing password'
  82. return { 'success': 'false', 'error': 'missing parameter "%s"' % (p)}
  83. region = request['region'].partition('@')[0]
  84. if self.__debug: print '[asterisk-opensim]: region_update: region %s' % user
  85. error = self.__db.RegionUpdate(region = region)
  86. if error:
  87. print '[asterisk-opensim]: DB.RegionUpdate failed'
  88. return { 'success': 'false', 'error': error}
  89. print '[asterisk-opensim] region_update: done'
  90. return { 'success': 'true' }
  91. class AsteriskDBException(Exception):
  92. pass
  93. class AsteriskDB(object):
  94. '''AsteriskDB maintains the connection to Asterisk's MySQL database.
  95. '''
  96. def __init__(self, config):
  97. # configure from config object
  98. self.__server = config.get('mysql', 'server')
  99. self.__database = config.get('mysql', 'database')
  100. self.__user = config.get('mysql', 'user')
  101. self.__password = config.get('mysql', 'password')
  102. try:
  103. self.__debug = config.getboolean('mysql', 'debug')
  104. self.__debug_t = config.getboolean('mysql-templates', 'debug')
  105. except ConfigParser.NoOptionError:
  106. self.__debug = False
  107. self.__tablesTemplate = self.__loadTemplate(config, 'tables')
  108. self.__userTemplate = self.__loadTemplate(config, 'user')
  109. self.__regionTemplate = self.__loadTemplate(config, 'region')
  110. self.__mysql = MySQLdb.connect(host = self.__server, db = self.__database,
  111. user = self.__user, passwd = self.__password)
  112. if self.__assertDBExists():
  113. raise AsteriskDBException('could not initialize DB')
  114. def __loadTemplate(self, config, templateName):
  115. template = config.get('mysql-templates', templateName)
  116. t = ''
  117. with open(template, 'r') as templateFile:
  118. for line in templateFile:
  119. line = line.rstrip('\n')
  120. t += line
  121. return t.split(';')
  122. def __assertDBExists(self):
  123. '''Assert that DB tables exist.
  124. '''
  125. try:
  126. cursor = self.__mysql.cursor()
  127. for sql in self.__tablesTemplate[:]:
  128. if not sql: continue
  129. sql = sql % { 'database': self.__database }
  130. if self.__debug: print 'AsteriskDB.__assertDBExists: %s' % sql
  131. cursor.execute(sql)
  132. cursor.fetchall()
  133. cursor.close()
  134. except MySQLdb.Error, e:
  135. if self.__debug: print 'AsteriskDB.__assertDBExists: Error %d: %s' % (e.args[0], e.args[1])
  136. return e.args[1]
  137. return None
  138. def AccountUpdate(self, user, password):
  139. print 'AsteriskDB.AccountUpdate: user %s' % (user)
  140. try:
  141. cursor = self.__mysql.cursor()
  142. for sql in self.__userTemplate[:]:
  143. if not sql: continue
  144. sql = sql % { 'database': self.__database, 'username': user, 'password': password }
  145. if self.__debug_t: print 'AsteriskDB.AccountUpdate: sql: %s' % sql
  146. cursor.execute(sql)
  147. cursor.fetchall()
  148. cursor.close()
  149. except MySQLdb.Error, e:
  150. if self.__debug: print 'AsteriskDB.RegionUpdate: Error %d: %s' % (e.args[0], e.args[1])
  151. return e.args[1]
  152. return None
  153. def RegionUpdate(self, region):
  154. print 'AsteriskDB.RegionUpdate: region %s' % (region)
  155. try:
  156. cursor = self.__mysql.cursor()
  157. for sql in self.__regionTemplate[:]:
  158. if not sql: continue
  159. sql = sql % { 'database': self.__database, 'regionname': region }
  160. if self.__debug_t: print 'AsteriskDB.RegionUpdate: sql: %s' % sql
  161. cursor.execute(sql)
  162. res = cursor.fetchall()
  163. except MySQLdb.Error, e:
  164. if self.__debug: print 'AsteriskDB.RegionUpdate: Error %d: %s' % (e.args[0], e.args[1])
  165. return e.args[1]
  166. return None
  167. reURLParser = re.compile(r'^http://((?P<host>[^/]+):(?P<port>\d+)|(?P<hostonly>[^/]+))/', re.IGNORECASE)
  168. # main
  169. if __name__ == '__main__':
  170. parser = optparse.OptionParser()
  171. parser.add_option('-c', '--config', dest = 'config', help = 'config file', metavar = 'CONFIG')
  172. (options, args) = parser.parse_args()
  173. if not options.config:
  174. parser.error('missing option config')
  175. sys.exit(1)
  176. config = ConfigParser.ConfigParser()
  177. config.readfp(open(options.config))
  178. server = AsteriskOpenSimServer(config)
  179. server.register_introspection_functions()
  180. server.register_instance(AsteriskFrontend(config, AsteriskDB(config)))
  181. # get cracking
  182. print '[asterisk-opensim] server ready on %s:%d' % (server.host(), server.port())
  183. server.serve_forever()
  184. sys.exit(0)