406bc868ee8117022df20051e167bdba7965f136
[contrib/cloudeebus.git] / cloudeebus / cloudeebus.py
1 #!/usr/bin/env python
2
3 # Cloudeebus
4 #
5 # Copyright 2012 Intel Corporation.
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
18 #
19 # Luc Yriarte <luc.yriarte@intel.com>
20 # Christophe Guiraud <christophe.guiraud@intel.com>
21 # Frederic Paut <frederic.paut@intel.com>
22 #
23
24
25 import argparse, dbus, json, sys
26
27 from twisted.internet import glib2reactor
28 # Configure the twisted mainloop to be run inside the glib mainloop.
29 # This must be done before importing the other twisted modules
30 glib2reactor.install()
31 from twisted.internet import reactor, defer
32
33 from autobahn.websocket import listenWS
34 from autobahn.wamp import exportRpc, WampServerFactory, WampCraServerProtocol
35
36 from dbus.mainloop.glib import DBusGMainLoop
37
38 import gobject
39 import re
40 import dbus.service
41 gobject.threads_init()
42
43 from dbus import glib
44 glib.init_threads()
45
46 # enable debug log
47 from twisted.python import log
48
49 # XML parser module
50 from xml.etree.ElementTree import XMLParser
51
52 ###############################################################################
53
54 VERSION = "0.6.0"
55 OPENDOOR = False
56 CREDENTIALS = {}
57 WHITELIST = []
58 SERVICELIST = []
59 NETMASK =  []
60
61 ###############################################################################
62 def ipV4ToHex(mask):
63     ## Convert an ip or an IP mask (such as ip/24 or ip/255.255.255.0) in hex value (32bits)
64     maskHex = 0
65     byte = 0
66     if mask.rfind(".") == -1:
67         if (int(mask) < 32):
68             maskHex = (2**(int(mask))-1)
69             maskHex = maskHex << (32-int(mask))
70         else:
71             raise Exception("Illegal mask (larger than 32 bits) " + mask)
72     else:
73         maskField = mask.split(".")
74         # Check if mask has four fields (byte)
75         if len(maskField) != 4:
76             raise Exception("Illegal ip address / mask (should be 4 bytes) " + mask)
77         for maskQuartet in maskField:
78             byte = int(maskQuartet)
79             # Check if each field is really a byte
80             if byte > 255:
81                 raise Exception("Illegal ip address / mask (digit larger than a byte) " + mask)              
82             maskHex += byte
83             maskHex = maskHex << 8
84         maskHex = maskHex >> 8
85     return maskHex
86
87 ###############################################################################
88 class DbusCache:
89     '''
90     Global cache of DBus connexions and signal handlers
91     '''
92     def __init__(self):
93         self.dbusConnexions = {}
94         self.signalHandlers = {}
95
96
97     def reset(self):
98         '''
99         Disconnect signal handlers before resetting cache.
100         '''
101         self.dbusConnexions = {}
102         # disconnect signal handlers
103         for key in self.signalHandlers:
104             self.signalHandlers[key].disconnect()
105         self.signalHandlers = {}
106
107
108     def dbusConnexion(self, busName):
109         if not self.dbusConnexions.has_key(busName):
110             if busName == "session":
111                 self.dbusConnexions[busName] = dbus.SessionBus()
112             elif busName == "system":
113                 self.dbusConnexions[busName] = dbus.SystemBus()
114             else:
115                 raise Exception("Error: invalid bus: %s" % busName)
116         return self.dbusConnexions[busName]
117
118
119
120 ###############################################################################
121 class DbusSignalHandler:
122     '''
123     signal hash id as busName#senderName#objectName#interfaceName#signalName
124     '''
125     def __init__(self, busName, senderName, objectName, interfaceName, signalName):
126         self.id = "#".join([busName, senderName, objectName, interfaceName, signalName])
127         # connect handler to signal
128         self.bus = cache.dbusConnexion(busName)
129         self.bus.add_signal_receiver(self.handleSignal, signalName, interfaceName, senderName, objectName)
130         
131     
132     def disconnect(self):
133         names = self.id.split("#")
134         self.bus.remove_signal_receiver(self.handleSignal, names[4], names[3], names[1], names[2])
135
136
137     def handleSignal(self, *args):
138         '''
139         publish dbus args under topic hash id
140         '''
141         factory.dispatch(self.id, json.dumps(args))
142
143
144
145 ###############################################################################
146 class DbusCallHandler:
147     '''
148     deferred reply to return dbus results
149     '''
150     def __init__(self, method, args):
151         self.pending = False
152         self.request = defer.Deferred()
153         self.method = method
154         self.args = args
155
156
157     def callMethod(self):
158         '''
159         dbus method async call
160         '''
161         self.pending = True
162         self.method(*self.args, reply_handler=self.dbusSuccess, error_handler=self.dbusError)
163         return self.request
164
165
166     def dbusSuccess(self, *result):
167         '''
168         return JSON string result array
169         '''
170         self.request.callback(json.dumps(result))
171         self.pending = False
172
173
174     def dbusError(self, error):
175         '''
176         return dbus error message
177         '''
178         self.request.errback(Exception(error.get_dbus_message()))
179         self.pending = False
180
181
182
183 ################################################################################       
184 class ExecCode:
185     '''
186     Execute DynDBusClass generated code
187     '''
188     def __init__(self, globalCtx, localCtx) :
189         self.exec_string = ""
190         self.exec_code = None
191         self.exec_code_valid = 1
192         self.indent_level = 0
193         self.indent_increment = 1
194         self.line = 0
195         self.localCtx = localCtx
196         self.globalCtx = globalCtx
197         
198
199     def append_stmt(self, stmt) :
200         self.exec_code_valid = 0
201         self.line += 1
202         for x in range(0,self.indent_level):
203             self.exec_string = self.exec_string + ' '            
204         self.exec_string = self.exec_string + stmt + '\n'
205
206     def indent(self) :
207         self.indent_level = self.indent_level + self.indent_increment
208
209     def dedent(self) :
210         self.indent_level = self.indent_level - self.indent_increment
211     
212     # compile : Compile exec_string into exec_code using the builtin
213     # compile function. Skip if already in sync.
214     def compile(self) :
215         if not self.exec_code_valid :
216             self.exec_code = compile(self.exec_string, "<string>", "exec")
217         self.exec_code_valid = True
218
219     def execute(self) :
220         if not self.exec_code_valid :
221             self.compile()
222         exec(self.exec_code, self.globalCtx, self.localCtx)
223
224
225
226 ################################################################################       
227 class XmlCbParser: # The target object of the parser
228     maxDepth = 0
229     depth = 0
230     def __init__(self, dynDBusClass):
231         self.dynDBusClass = dynDBusClass
232         
233     def start(self, tag, attrib):   # Called for each opening tag.
234         if (tag == 'node'):
235             return
236         # Set interface name
237         if (tag == 'interface'):
238             self.dynDBusClass.set_interface(attrib['name'])
239             return
240         # Set method name
241         if (tag == 'method'):
242             self.current = tag
243             self.dynDBusClass.def_method(attrib['name'])
244             return
245         if (tag == 'signal'):
246             self.current = tag
247             self.dynDBusClass.def_signal(attrib['name'])
248             return
249
250         # Set signature (in/out & name) for method
251         if (tag == 'arg'):
252             if (self.current == 'method'):
253                 if (attrib.has_key('direction') == False):
254                     attrib['direction'] = "in"
255                 self.dynDBusClass.add_signature(attrib['name'],
256                                                 attrib['direction'],
257                                                 attrib['type'])
258                 return
259             if (self.current == 'signal'):
260                 if (attrib.has_key('name') == False):
261                     attrib['name'] = 'value'
262                 self.dynDBusClass.add_signature(attrib['name'], 'in',
263                                                 attrib['type'])
264                 return
265     def end(self, tag):             # Called for each closing tag.
266         if (tag == 'method'):
267             self.dynDBusClass.add_dbus_method()
268             self.dynDBusClass.add_body_method()
269             self.dynDBusClass.end_method()
270         if (tag == 'signal'):
271             self.dynDBusClass.add_dbus_signal()
272             self.dynDBusClass.add_body_signal()
273             self.dynDBusClass.end_method()
274            
275     def data(self, data):
276         pass            # We do not need to do anything with data.
277     def close(self):    # Called when all data has been parsed.
278         return self.maxDepth
279
280
281        
282 ###############################################################################
283 def createClassName(objectPath):
284     return re.sub('/', '_', objectPath[1:])
285
286 ################################################################################       
287 class DynDBusClass():
288     def __init__(self, className, globalCtx, localCtx):
289         self.xmlCB = XmlCbParser(self)
290         self.signature = {}
291         self.class_code = ExecCode(globalCtx, localCtx)  
292         self.class_code.indent_increment = 4
293         self.class_code.append_stmt("import dbus")
294         self.class_code.append_stmt("\n")
295         self.class_code.append_stmt("class " + className + "(dbus.service.Object):")
296         self.class_code.indent()
297         
298         ## Overload of __init__ method 
299         self.def_method("__init__")
300         self.add_method("bus, callback=None, objPath='/sample', srvName='org.cloudeebus'")
301         self.add_stmt("self.bus = bus")
302         self.add_stmt("self.objPath = objPath")
303         self.add_stmt("self.srvName = srvName")        
304         self.add_stmt("self.callback = callback")        
305         self.add_stmt("dbus.service.Object.__init__(self, conn=bus, bus_name=srvName)")
306         self.end_method()
307                
308         ## Create 'add_to_connection' method 
309         self.def_method("add_to_connection")
310         self.add_method("connection=None, path=None")
311         self.add_stmt("dbus.service.Object.add_to_connection(self, connection=self.bus, path=self.objPath)")
312         self.end_method()
313                
314         ## Create 'remove_from_connection' method 
315         self.def_method("remove_from_connection")
316         self.add_method("connection=None, path=None")
317         self.add_stmt("dbus.service.Object.remove_from_connection(self, connection=None, path=self.objPath)")
318         self.end_method()
319                
320     def createDBusServiceFromXML(self, xml):
321         self.parser = XMLParser(target=self.xmlCB)
322         self.parser.feed(xml)
323         self.parser.close()
324     
325     def set_interface(self, ifName):
326         self.ifName = ifName
327         
328     def def_method(self, methodName):
329         self.methodToAdd = methodName
330         self.signalToAdd = None
331         self.args_str = str()
332         self.signature = {}
333         self.signature['name'] = str()
334         self.signature['in'] = str()                
335         self.signature['out'] = str()                        
336
337     def def_signal(self, signalName):
338         self.methodToAdd = None
339         self.signalToAdd = signalName
340         self.args_str = str()
341         self.signature = {}
342         self.signature['name'] = str()
343         self.signature['in'] = str()                
344         self.signature['out'] = str()                        
345
346     def add_signature(self, name, direction, signature):
347         if (direction == 'in'):
348             self.signature['in'] += signature
349             if (self.signature['name'] != str()):
350                 self.signature['name'] += ", "
351             self.signature['name'] += name
352         if (direction == 'out'):
353             self.signature['out'] = signature                        
354         
355     def add_method(self, args = None, async_success_cb = None, async_err_cb = None):
356         async_cb_str = str()
357         if (self.methodToAdd != None):
358             name = self.methodToAdd
359         else:
360             name = self.signalToAdd
361         if (args != None):
362             self.args_str = args
363         if (async_success_cb != None):
364             async_cb_str = async_success_cb
365         if (async_err_cb != None):
366             if (async_cb_str != str()):
367                 async_cb_str += ", "
368             async_cb_str += async_err_cb
369                         
370         parameters = self.args_str
371         if (async_cb_str != str()):
372             if (parameters != str()):
373                 parameters += ", "
374             parameters +=async_cb_str       
375         
376         if (parameters != str()):
377             self.class_code.append_stmt("def " + name + "(self, %s):" % parameters)               
378         else:
379             self.class_code.append_stmt("def " + name + "(self):")
380         self.class_code.indent()
381         
382     def end_method(self):
383         self.class_code.append_stmt("\n")
384         self.class_code.dedent()
385         
386     def add_dbus_method(self):
387         decorator = '@dbus.service.method("' + self.ifName + '"'
388         if (self.signature.has_key('in') and self.signature['in'] != str()):
389                 decorator += ", in_signature='" + self.signature['in'] + "'"
390         if (self.signature.has_key('out') and self.signature['out'] != str()):
391                 decorator += ", out_signature='" + self.signature['out'] + "'"
392         decorator += ", async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')"            
393         decorator += ")"
394         self.class_code.append_stmt(decorator)
395         if (self.signature.has_key('name') and self.signature['name'] != str()):
396             self.add_method(self.signature['name'], async_success_cb='dbus_async_cb', async_err_cb='dbus_async_err_cb')
397         else:
398             self.add_method(async_success_cb='dbus_async_cb', async_err_cb='dbus_async_err_cb')
399
400     def add_dbus_signal(self):
401         decorator = '@dbus.service.signal("' + self.ifName + '"'
402         if (self.signature.has_key('in') and self.signature['in'] != str()):
403                 decorator += ", signature='" + self.signature['in'] + "'"
404         decorator += ")"            
405         self.class_code.append_stmt(decorator)
406         if (self.signature.has_key('name') and self.signature['name'] != str()):
407             self.add_method(self.signature['name'])
408         else:
409             self.add_method()
410
411     def add_body_method(self):
412         if (self.methodToAdd != None):
413             if (self.args_str != str()):
414                 self.class_code.append_stmt("self.callback(self.srvName,'" + self.methodToAdd + "', self.objPath, '"  + self.ifName + "', " + "dbus_async_cb, dbus_async_err_cb, %s)" % self.args_str)
415             else:        
416                 self.class_code.append_stmt("self.callback(self.srvName,'" + self.methodToAdd + "', self.objPath, '"  + self.ifName + "', " + "dbus_async_cb, dbus_async_err_cb)")
417
418     def add_body_signal(self):
419         self.class_code.append_stmt("return") ## TODO: Remove and fix with code ad hoc
420         self.class_code.append_stmt("\n")
421
422     def add_stmt(self, stmt) :
423         self.class_code.append_stmt(stmt)
424         
425     def declare(self) :
426         self.class_code.execute()
427
428
429
430 ###############################################################################
431 class CloudeebusService:
432     '''
433     support for sending DBus messages and registering for DBus signals
434     '''
435     def __init__(self, permissions):
436         self.permissions = {};
437         self.permissions['permissions'] = permissions['permissions']
438         self.permissions['authextra'] = permissions['authextra']
439         self.permissions['services'] = permissions['services']
440         self.proxyObjects = {}
441         self.proxyMethods = {}
442         self.pendingCalls = []
443         self.dynDBusClasses = {} # DBus class source code generated dynamically (a list because one by classname)
444         self.services = {}  # DBus service created
445         self.serviceAgents = {} # Instantiated DBus class previously generated dynamically, for now, one by classname
446         self.servicePendingCalls = {} # JS methods called (and waiting for a Success/error response), containing 'methodId', (successCB, errorCB)
447         self.localCtx = locals()
448         self.globalCtx = globals()
449
450         self.patternDbus        = re.compile('^dbus\.(\w+)')
451         self.patternDbusBoolean = re.compile('^dbus.Boolean\((\w+)\)$')
452         self.patternDbusByte    = re.compile('^dbus.Byte\((\d+)\)$')
453         self.patternDbusInt16   = re.compile('^dbus.Int16\((\d+)\)$')
454         self.patternDbusInt32   = re.compile('^dbus.Int32\((\d+)\)$')
455         self.patternDbusInt64   = re.compile('^dbus.Int64\((\d+)\)$')
456         self.patternDbusUInt16  = re.compile('^dbus.UInt16\((\d+)\)$')
457         self.patternDbusUInt32  = re.compile('^dbus.UInt32\((\d+)\)$')
458         self.patternDbusUInt64  = re.compile('^dbus.UInt64\((\d+)\)$')
459         self.patternDbusDouble  = re.compile('^dbus.Double\((\d+\.\d+)\)$')
460
461     def proxyObject(self, busName, serviceName, objectName):
462         '''
463         object hash id as busName#serviceName#objectName
464         '''
465         id = "#".join([busName, serviceName, objectName])
466         if not self.proxyObjects.has_key(id):
467             if not OPENDOOR:
468                 # check permissions, array.index throws exception
469                 self.permissions['permissions'].index(serviceName)
470             bus = cache.dbusConnexion(busName)
471             self.proxyObjects[id] = bus.get_object(serviceName, objectName)
472         return self.proxyObjects[id]
473
474
475     def proxyMethod(self, busName, serviceName, objectName, interfaceName, methodName):
476         '''
477         method hash id as busName#serviceName#objectName#interfaceName#methodName
478         '''
479         id = "#".join([busName, serviceName, objectName, interfaceName, methodName])
480         if not self.proxyMethods.has_key(id):
481             obj = self.proxyObject(busName, serviceName, objectName)
482             self.proxyMethods[id] = obj.get_dbus_method(methodName, interfaceName)
483         return self.proxyMethods[id]
484
485     def decodeArgs( self, args ):
486         if isinstance( args, list ):
487             newArgs = []
488             for arg in args:
489                 newArgs.append( self.decodeArgs( arg ))
490             return newArgs
491         elif isinstance( args, dict ):
492             newDict = {}
493             for key, value in args.iteritems():
494                 key = self.decodeArgs( key )
495                 newValue = self.decodeArgs( value )
496                 newDict[key] = newValue
497             return newDict
498         elif isinstance( args, basestring ):
499             newArg = self.decodeDbusString( args )
500             return newArg
501         else:
502             return args
503
504     def decodeDbusString( self, dbusString ):
505         matchDbus = self.patternDbus.match( dbusString )
506
507         if not matchDbus:
508             return dbusString
509
510
511         result = {
512            "Boolean" : lambda x : dbus.Boolean(   self.patternDbusBoolean.match( x ).group( 1 ).lower() in ("yes", "true", "t", "1")),
513            "Byte"    : lambda x : dbus.Byte( int( self.patternDbusByte.match(    x ).group( 1 ))),
514            "Int16"   : lambda x : dbus.Int16(     self.patternDbusInt16.match(   x ).group( 1 )),
515            "Int32"   : lambda x : dbus.Int32(     self.patternDbusInt32.match(   x ).group( 1 )),
516            "Int64"   : lambda x : dbus.Int64(     self.patternDbusInt64.match(    x ).group( 1 )),
517            "UInt16"  : lambda x : dbus.UInt16(    self.patternDbusUInt16.match(  x ).group( 1 )),
518            "UInt32"  : lambda x : dbus.UInt32(    self.patternDbusUInt32.match(  x ).group( 1 )),
519            "UInt64"  : lambda x : dbus.UInt64(    self.patternDbusUInt64.match(   x ).group( 1 )),
520            "Double"  : lambda x : dbus.Double(    self.patternDbusDouble.match(  x ).group( 1 ))
521         }[matchDbus.group(1)](dbusString)
522
523         return result
524
525     @exportRpc
526     def dbusRegister(self, list):
527         '''
528         arguments: bus, sender, object, interface, signal
529         '''
530         if len(list) < 5:
531             raise Exception("Error: expected arguments: bus, sender, object, interface, signal)")
532         
533         if not OPENDOOR:
534             # check permissions, array.index throws exception
535             self.permissions['permissions'].index(list[1])
536         
537         # check if a handler exists
538         sigId = "#".join(list)
539         if cache.signalHandlers.has_key(sigId):
540             return sigId
541         
542         # create a handler that will publish the signal
543         dbusSignalHandler = DbusSignalHandler(*list)
544         cache.signalHandlers[sigId] = dbusSignalHandler
545         
546         return dbusSignalHandler.id
547
548
549     @exportRpc
550     def dbusSend(self, list):
551         '''
552         arguments: bus, destination, object, interface, message, [args]
553         '''
554         # clear pending calls
555         for call in self.pendingCalls:
556             if not call.pending:
557                 self.pendingCalls.remove(call)
558         
559         if len(list) < 5:
560             raise Exception("Error: expected arguments: bus, destination, object, interface, message, [args])")
561         
562         # parse JSON arg list
563         args = []
564         if len(list) == 6:
565             jsonArgs = json.loads(list[5])
566             print "JSON Arguments:", jsonArgs
567             if jsonArgs:
568                 args = self.decodeArgs( jsonArgs )
569                 print "Decoded Arguments: ", args
570         
571         # get dbus proxy method
572         method = self.proxyMethod(*list[0:5])
573         
574         # use a deferred call handler to manage dbus results
575         dbusCallHandler = DbusCallHandler(method, args)
576         self.pendingCalls.append(dbusCallHandler)
577         return dbusCallHandler.callMethod()
578
579
580     @exportRpc
581     def emitSignal(self, list):
582         '''
583         arguments: agentObjectPath, signalName, args (to emit)
584         '''
585         objectPath = list[0]
586         className = re.sub('/', '_', objectPath[1:])
587         signalName = list[1]
588         args = []
589         jsonArgs = json.loads(list[2])
590         print "JSON Arguments:", jsonArgs
591         if jsonArgs:
592             args = self.decodeArgs( jsonArgs )
593             print "Decoded Arguments: ", args
594
595         if (self.serviceAgents.has_key(className) == True):            
596             exe_str = "self.serviceAgents['"+ className +"']."+ signalName + "("
597             if len(args) > 0:
598                 exe_str += json.dumps(args[0])
599                 for idx in args[1:]:
600                     exe_str += "," + json.dumps(idx)
601             exe_str += ")"               
602             eval(exe_str, self.globalCtx, self.localCtx)
603         else:
604             raise Exception("No object path " + objectPath)
605
606     @exportRpc
607     def returnMethod(self, list):
608         '''
609         arguments: methodId, callIndex, success (=true, error otherwise), result (to return)
610         '''
611         methodId = list[0]
612         callIndex = list[1]
613         success = list[2]
614         result = list[3]
615         if (self.servicePendingCalls.has_key(methodId)):
616             cb = self.servicePendingCalls[methodId]['calls'][callIndex]
617             if cb is None:
618                 raise Exception("No pending call " + str(callIndex) + " for methodID " + methodId)
619             if (success):                
620                 successCB = cb["successCB"]
621                 if (result != None):
622                     successCB(result)
623                 else:
624                     successCB()                    
625             else:     
626                 errorCB = cb["errorCB"]        
627                 if (result != None):
628                     errorCB(result)
629                 else:
630                     errorCB()
631             self.servicePendingCalls[methodId]['calls'][callIndex] = None
632             self.servicePendingCalls[methodId]['count'] = self.servicePendingCalls[methodId]['count'] - 1
633             if self.servicePendingCalls[methodId]['count'] == 0:
634                 del self.servicePendingCalls[methodId]
635         else:
636             raise Exception("No methodID " + methodId)
637
638     def srvCB(self, srvName, name, objPath, ifName, async_succes_cb, async_error_cb, *args):
639         methodId = srvName + "#" + objPath + "#" + ifName + "#" + name
640         cb = { 'successCB': async_succes_cb, 
641                'errorCB': async_error_cb}
642         if methodId not in self.servicePendingCalls:
643             self.servicePendingCalls[methodId] = {'count': 0, 'calls': []}
644             
645         try:
646             pendingCallStr = json.dumps({'callIndex': len(self.servicePendingCalls[methodId]['calls']), 'args': args})
647         except Exception, e:                
648             args = eval( str(args).replace("dbus.Byte", "dbus.Int16") )
649             pendingCallStr = json.dumps({'callIndex': len(self.servicePendingCalls[methodId]['calls']), 'args': args})
650                
651         self.servicePendingCalls[methodId]['calls'].append(cb)
652         self.servicePendingCalls[methodId]['count'] = self.servicePendingCalls[methodId]['count'] + 1
653         factory.dispatch(methodId, pendingCallStr)
654                     
655     @exportRpc
656     def serviceAdd(self, list):
657         '''
658         arguments: busName, srvName
659         '''
660         busName = list[0]
661         self.bus =  cache.dbusConnexion( busName )
662         srvName = list[1]
663         if not OPENDOOR and (SERVICELIST == [] or SERVICELIST != [] and self.permissions['services'] == None):
664             SERVICELIST.index(srvName)
665             
666         if (self.services.has_key(srvName) == False):
667             self.services[srvName] = dbus.service.BusName(name = srvName, bus = self.bus)
668         return srvName
669
670     @exportRpc
671     def serviceRelease(self, list):
672         '''
673         arguments: busName, srvName
674         '''
675         srvName = list[0]
676         if (self.services.has_key(srvName) == True):
677             self.services.pop(srvName)
678             return srvName
679         else:
680             raise Exception(srvName + " does not exist")
681                    
682     @exportRpc
683     def serviceAddAgent(self, list):
684         '''
685         arguments: objectPath, xmlTemplate
686         '''
687         srvName = list[0]
688         agentObjectPath = list[1]
689         xmlTemplate = list[2]
690         className = createClassName(agentObjectPath)
691         if (self.dynDBusClasses.has_key(className) == False):
692             self.dynDBusClasses[className] = DynDBusClass(className, self.globalCtx, self.localCtx)
693             self.dynDBusClasses[className].createDBusServiceFromXML(xmlTemplate)
694             self.dynDBusClasses[className].declare()
695
696         ## Class already exist, instanciate it if not already instanciated
697         if (self.serviceAgents.has_key(className) == False):
698             self.serviceAgents[className] = eval(className + "(self.bus, callback=self.srvCB, objPath='" + agentObjectPath + "', srvName='" + srvName + "')", self.globalCtx, self.localCtx)
699             
700         self.serviceAgents[className].add_to_connection()
701         return (agentObjectPath)
702                     
703     @exportRpc
704     def serviceDelAgent(self, list):
705         '''
706         arguments: objectPath, xmlTemplate
707         '''
708         agentObjectPath = list[0]
709         className = createClassName(agentObjectPath)
710         
711         if (self.serviceAgents.has_key(className)):
712             self.serviceAgents[className].remove_from_connection()
713             self.serviceAgents.pop(className)
714         else:
715             raise Exception(agentObjectPath + " doesn't exist!")
716         
717         return (agentObjectPath)
718                     
719     @exportRpc
720     def getVersion(self):
721         '''
722         return current version string
723         '''
724         return VERSION
725
726
727
728 ###############################################################################
729 class CloudeebusServerProtocol(WampCraServerProtocol):
730     '''
731     connexion and session authentication management
732     '''
733     
734     def onSessionOpen(self):
735         # CRA authentication options
736         self.clientAuthTimeout = 0
737         self.clientAuthAllowAnonymous = OPENDOOR
738         # CRA authentication init
739         WampCraServerProtocol.onSessionOpen(self)
740     
741     
742     def getAuthPermissions(self, key, extra):
743          return {'permissions': extra.get("permissions", None),
744                  'authextra': extra.get("authextra", None),
745                  'services': extra.get("services", None)}   
746     
747     def getAuthSecret(self, key):
748         secret = CREDENTIALS.get(key, None)
749         if secret is None:
750             return None
751         # secret must be of str type to be hashed
752         return str(secret)
753     
754
755     def onAuthenticated(self, key, permissions):
756         if not OPENDOOR:
757             # check net filter
758             if NETMASK != []:
759                 ipAllowed = False
760                 for netfilter in NETMASK:
761                     ipHex=ipV4ToHex(self.peer.host)
762                     ipAllowed = (ipHex & netfilter['mask']) == netfilter['ipAllowed'] & netfilter['mask']
763                     if ipAllowed:
764                         break
765                 if not ipAllowed:
766                     raise Exception("host " + self.peer.host + " is not allowed!")
767             # check authentication key
768             if key is None:
769                 raise Exception("Authentication failed")
770             # check permissions, array.index throws exception
771             if (permissions['permissions'] != None):
772                 for req in permissions['permissions']:
773                     WHITELIST.index(req);
774             # check allowed service creation, array.index throws exception
775             if (permissions['services'] != None):
776                 for req in permissions['services']:
777                     SERVICELIST.index(req);
778         # create cloudeebus service instance
779         self.cloudeebusService = CloudeebusService(permissions)
780         # register it for RPC
781         self.registerForRpc(self.cloudeebusService)
782         # register for Publish / Subscribe
783         self.registerForPubSub("", True)
784     
785     
786     def connectionLost(self, reason):
787         WampCraServerProtocol.connectionLost(self, reason)
788         if factory.getConnectionCount() == 0:
789             cache.reset()
790
791
792
793 ###############################################################################
794
795 if __name__ == '__main__':
796     
797     cache = DbusCache()
798
799     parser = argparse.ArgumentParser(description='Javascript DBus bridge.')
800     parser.add_argument('-v', '--version', action='store_true', 
801         help='print version and exit')
802     parser.add_argument('-d', '--debug', action='store_true', 
803         help='log debug info on standard output')
804     parser.add_argument('-o', '--opendoor', action='store_true',
805         help='allow anonymous access to all services')
806     parser.add_argument('-p', '--port', default='9000',
807         help='port number')
808     parser.add_argument('-c', '--credentials',
809         help='path to credentials file')
810     parser.add_argument('-w', '--whitelist',
811         help='path to whitelist file (DBus services to use)')
812     parser.add_argument('-s', '--servicelist',
813         help='path to servicelist file (DBus services to export)')
814     parser.add_argument('-n', '--netmask',
815         help='netmask,IP filter (comma separated.) eg. : -n 127.0.0.1,192.168.2.0/24,10.12.16.0/255.255.255.0')
816     
817     args = parser.parse_args(sys.argv[1:])
818
819     if args.version:
820         print("Cloudeebus version " + VERSION)
821         exit(0)
822     
823     if args.debug:
824         log.startLogging(sys.stdout)
825     
826     OPENDOOR = args.opendoor
827     
828     if args.credentials:
829         jfile = open(args.credentials)
830         CREDENTIALS = json.load(jfile)
831         jfile.close()
832     
833     if args.whitelist:
834         jfile = open(args.whitelist)
835         WHITELIST = json.load(jfile)
836         jfile.close()
837         
838     if args.servicelist:
839         jfile = open(args.servicelist)
840         SERVICELIST = json.load(jfile)
841         jfile.close()
842         
843     if args.netmask:
844         iplist = args.netmask.split(",")
845         for ip in iplist:
846             if ip.rfind("/") != -1:
847                 ip=ip.split("/")
848                 ipAllowed = ip[0]
849                 mask = ip[1]
850             else:
851                 ipAllowed = ip
852                 mask = "255.255.255.255" 
853             NETMASK.append( {'ipAllowed': ipV4ToHex(ipAllowed), 'mask' : ipV4ToHex(mask)} )
854
855     uri = "ws://localhost:" + args.port
856     
857     factory = WampServerFactory(uri, debugWamp = args.debug)
858     factory.protocol = CloudeebusServerProtocol
859     factory.setProtocolOptions(allowHixie76 = True)
860     
861     listenWS(factory)
862     
863     DBusGMainLoop(set_as_default=True)
864     
865     reactor.run()