/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2011-03-08 19:09:03 UTC
  • Revision ID: teddy@fukt.bsnet.se-20110308190903-j499ebtb9bpk31ar
* INSTALL: Updated.

Show diffs side-by-side

added added

removed removed

Lines of Context:
36
36
 
37
37
import SocketServer as socketserver
38
38
import socket
39
 
import argparse
 
39
import optparse
40
40
import datetime
41
41
import errno
42
42
import gnutls.crypto
62
62
import functools
63
63
import cPickle as pickle
64
64
import multiprocessing
65
 
import types
66
65
 
67
66
import dbus
68
67
import dbus.service
83
82
        SO_BINDTODEVICE = None
84
83
 
85
84
 
86
 
version = "1.3.1"
 
85
version = "1.2.3"
87
86
 
88
87
#logger = logging.getLogger('mandos')
89
88
logger = logging.Logger('mandos')
152
151
        self.group = None       # our entry group
153
152
        self.server = None
154
153
        self.bus = bus
155
 
        self.entry_group_state_changed_match = None
156
154
    def rename(self):
157
155
        """Derived from the Avahi example code"""
158
156
        if self.rename_count >= self.max_renames:
170
168
        self.remove()
171
169
        try:
172
170
            self.add()
173
 
        except dbus.exceptions.DBusException as error:
 
171
        except dbus.exceptions.DBusException, error:
174
172
            logger.critical("DBusException: %s", error)
175
173
            self.cleanup()
176
174
            os._exit(1)
177
175
        self.rename_count += 1
178
176
    def remove(self):
179
177
        """Derived from the Avahi example code"""
180
 
        if self.entry_group_state_changed_match is not None:
181
 
            self.entry_group_state_changed_match.remove()
182
 
            self.entry_group_state_changed_match = None
183
178
        if self.group is not None:
184
179
            self.group.Reset()
185
180
    def add(self):
186
181
        """Derived from the Avahi example code"""
187
 
        self.remove()
188
182
        if self.group is None:
189
183
            self.group = dbus.Interface(
190
184
                self.bus.get_object(avahi.DBUS_NAME,
191
185
                                    self.server.EntryGroupNew()),
192
186
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
193
 
        self.entry_group_state_changed_match = (
194
 
            self.group.connect_to_signal(
195
 
                'StateChanged', self .entry_group_state_changed))
 
187
            self.group.connect_to_signal('StateChanged',
 
188
                                         self
 
189
                                         .entry_group_state_changed)
196
190
        logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
197
191
                     self.name, self.type)
198
192
        self.group.AddService(
221
215
    def cleanup(self):
222
216
        """Derived from the Avahi example code"""
223
217
        if self.group is not None:
224
 
            try:
225
 
                self.group.Free()
226
 
            except (dbus.exceptions.UnknownMethodException,
227
 
                    dbus.exceptions.DBusException) as e:
228
 
                pass
 
218
            self.group.Free()
229
219
            self.group = None
230
 
        self.remove()
231
 
    def server_state_changed(self, state, error=None):
 
220
    def server_state_changed(self, state):
232
221
        """Derived from the Avahi example code"""
233
222
        logger.debug("Avahi server state change: %i", state)
234
 
        bad_states = { avahi.SERVER_INVALID:
235
 
                           "Zeroconf server invalid",
236
 
                       avahi.SERVER_REGISTERING: None,
237
 
                       avahi.SERVER_COLLISION:
238
 
                           "Zeroconf server name collision",
239
 
                       avahi.SERVER_FAILURE:
240
 
                           "Zeroconf server failure" }
241
 
        if state in bad_states:
242
 
            if bad_states[state] is not None:
243
 
                if error is None:
244
 
                    logger.error(bad_states[state])
245
 
                else:
246
 
                    logger.error(bad_states[state] + ": %r", error)
247
 
            self.cleanup()
 
223
        if state == avahi.SERVER_COLLISION:
 
224
            logger.error("Zeroconf server name collision")
 
225
            self.remove()
248
226
        elif state == avahi.SERVER_RUNNING:
249
227
            self.add()
250
 
        else:
251
 
            if error is None:
252
 
                logger.debug("Unknown state: %r", state)
253
 
            else:
254
 
                logger.debug("Unknown state: %r: %r", state, error)
255
228
    def activate(self):
256
229
        """Derived from the Avahi example code"""
257
230
        if self.server is None:
258
231
            self.server = dbus.Interface(
259
232
                self.bus.get_object(avahi.DBUS_NAME,
260
 
                                    avahi.DBUS_PATH_SERVER,
261
 
                                    follow_name_owner_changes=True),
 
233
                                    avahi.DBUS_PATH_SERVER),
262
234
                avahi.DBUS_INTERFACE_SERVER)
263
235
        self.server.connect_to_signal("StateChanged",
264
236
                                 self.server_state_changed)
265
237
        self.server_state_changed(self.server.GetState())
266
238
 
267
239
 
268
 
def _timedelta_to_milliseconds(td):
269
 
    "Convert a datetime.timedelta() to milliseconds"
270
 
    return ((td.days * 24 * 60 * 60 * 1000)
271
 
            + (td.seconds * 1000)
272
 
            + (td.microseconds // 1000))
273
 
        
274
240
class Client(object):
275
241
    """A representation of a client host served by this server.
276
242
    
304
270
    secret:     bytestring; sent verbatim (over TLS) to client
305
271
    timeout:    datetime.timedelta(); How long from last_checked_ok
306
272
                                      until this client is disabled
307
 
    extended_timeout:   extra long timeout when password has been sent
308
273
    runtime_expansions: Allowed attributes for runtime expansion.
309
 
    expires:    datetime.datetime(); time (UTC) when a client will be
310
 
                disabled, or None
311
274
    """
312
275
    
313
276
    runtime_expansions = ("approval_delay", "approval_duration",
315
278
                          "host", "interval", "last_checked_ok",
316
279
                          "last_enabled", "name", "timeout")
317
280
    
 
281
    @staticmethod
 
282
    def _timedelta_to_milliseconds(td):
 
283
        "Convert a datetime.timedelta() to milliseconds"
 
284
        return ((td.days * 24 * 60 * 60 * 1000)
 
285
                + (td.seconds * 1000)
 
286
                + (td.microseconds // 1000))
 
287
    
318
288
    def timeout_milliseconds(self):
319
289
        "Return the 'timeout' attribute in milliseconds"
320
 
        return _timedelta_to_milliseconds(self.timeout)
321
 
    
322
 
    def extended_timeout_milliseconds(self):
323
 
        "Return the 'extended_timeout' attribute in milliseconds"
324
 
        return _timedelta_to_milliseconds(self.extended_timeout)    
 
290
        return self._timedelta_to_milliseconds(self.timeout)
325
291
    
326
292
    def interval_milliseconds(self):
327
293
        "Return the 'interval' attribute in milliseconds"
328
 
        return _timedelta_to_milliseconds(self.interval)
329
 
    
 
294
        return self._timedelta_to_milliseconds(self.interval)
 
295
 
330
296
    def approval_delay_milliseconds(self):
331
 
        return _timedelta_to_milliseconds(self.approval_delay)
 
297
        return self._timedelta_to_milliseconds(self.approval_delay)
332
298
    
333
299
    def __init__(self, name = None, disable_hook=None, config=None):
334
300
        """Note: the 'checker' key in 'config' sets the
361
327
        self.last_enabled = None
362
328
        self.last_checked_ok = None
363
329
        self.timeout = string_to_delta(config["timeout"])
364
 
        self.extended_timeout = string_to_delta(config["extended_timeout"])
365
330
        self.interval = string_to_delta(config["interval"])
366
331
        self.disable_hook = disable_hook
367
332
        self.checker = None
368
333
        self.checker_initiator_tag = None
369
334
        self.disable_initiator_tag = None
370
 
        self.expires = None
371
335
        self.checker_callback_tag = None
372
336
        self.checker_command = config["checker"]
373
337
        self.current_checker_command = None
393
357
            # Already enabled
394
358
            return
395
359
        self.send_changedstate()
 
360
        self.last_enabled = datetime.datetime.utcnow()
396
361
        # Schedule a new checker to be started an 'interval' from now,
397
362
        # and every interval from then on.
398
363
        self.checker_initiator_tag = (gobject.timeout_add
399
364
                                      (self.interval_milliseconds(),
400
365
                                       self.start_checker))
401
366
        # Schedule a disable() when 'timeout' has passed
402
 
        self.expires = datetime.datetime.utcnow() + self.timeout
403
367
        self.disable_initiator_tag = (gobject.timeout_add
404
368
                                   (self.timeout_milliseconds(),
405
369
                                    self.disable))
406
370
        self.enabled = True
407
 
        self.last_enabled = datetime.datetime.utcnow()
408
371
        # Also start a new checker *right now*.
409
372
        self.start_checker()
410
373
    
419
382
        if getattr(self, "disable_initiator_tag", False):
420
383
            gobject.source_remove(self.disable_initiator_tag)
421
384
            self.disable_initiator_tag = None
422
 
        self.expires = None
423
385
        if getattr(self, "checker_initiator_tag", False):
424
386
            gobject.source_remove(self.checker_initiator_tag)
425
387
            self.checker_initiator_tag = None
451
413
            logger.warning("Checker for %(name)s crashed?",
452
414
                           vars(self))
453
415
    
454
 
    def checked_ok(self, timeout=None):
 
416
    def checked_ok(self):
455
417
        """Bump up the timeout for this client.
456
418
        
457
419
        This should only be called when the client has been seen,
458
420
        alive and well.
459
421
        """
460
 
        if timeout is None:
461
 
            timeout = self.timeout
462
422
        self.last_checked_ok = datetime.datetime.utcnow()
463
423
        gobject.source_remove(self.disable_initiator_tag)
464
 
        self.expires = datetime.datetime.utcnow() + timeout
465
424
        self.disable_initiator_tag = (gobject.timeout_add
466
 
                                      (_timedelta_to_milliseconds(timeout),
 
425
                                      (self.timeout_milliseconds(),
467
426
                                       self.disable))
468
427
    
469
428
    def need_approval(self):
486
445
        # If a checker exists, make sure it is not a zombie
487
446
        try:
488
447
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
489
 
        except (AttributeError, OSError) as error:
 
448
        except (AttributeError, OSError), error:
490
449
            if (isinstance(error, OSError)
491
450
                and error.errno != errno.ECHILD):
492
451
                raise error
510
469
                                       'replace')))
511
470
                    for attr in
512
471
                    self.runtime_expansions)
513
 
                
 
472
 
514
473
                try:
515
474
                    command = self.checker_command % escaped_attrs
516
 
                except TypeError as error:
 
475
                except TypeError, error:
517
476
                    logger.error('Could not format string "%s":'
518
477
                                 ' %s', self.checker_command, error)
519
478
                    return True # Try again later
538
497
                if pid:
539
498
                    gobject.source_remove(self.checker_callback_tag)
540
499
                    self.checker_callback(pid, status, command)
541
 
            except OSError as error:
 
500
            except OSError, error:
542
501
                logger.error("Failed to start subprocess: %s",
543
502
                             error)
544
503
        # Re-run this periodically if run by gobject.timeout_add
557
516
            #time.sleep(0.5)
558
517
            #if self.checker.poll() is None:
559
518
            #    os.kill(self.checker.pid, signal.SIGKILL)
560
 
        except OSError as error:
 
519
        except OSError, error:
561
520
            if error.errno != errno.ESRCH: # No such process
562
521
                raise
563
522
        self.checker = None
564
523
 
565
 
 
566
524
def dbus_service_property(dbus_interface, signature="v",
567
525
                          access="readwrite", byte_arrays=False):
568
526
    """Decorators for marking methods of a DBusObjectWithProperties to
614
572
 
615
573
class DBusObjectWithProperties(dbus.service.Object):
616
574
    """A D-Bus object with properties.
617
 
    
 
575
 
618
576
    Classes inheriting from this can use the dbus_service_property
619
577
    decorator to expose methods as D-Bus properties.  It exposes the
620
578
    standard Get(), Set(), and GetAll() methods on the D-Bus.
627
585
    def _get_all_dbus_properties(self):
628
586
        """Returns a generator of (name, attribute) pairs
629
587
        """
630
 
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
631
 
                for cls in self.__class__.__mro__
632
 
                for name, prop in inspect.getmembers(cls, self._is_dbus_property))
 
588
        return ((prop._dbus_name, prop)
 
589
                for name, prop in
 
590
                inspect.getmembers(self, self._is_dbus_property))
633
591
    
634
592
    def _get_dbus_property(self, interface_name, property_name):
635
593
        """Returns a bound method if one exists which is a D-Bus
636
594
        property with the specified name and interface.
637
595
        """
638
 
        for cls in  self.__class__.__mro__:
639
 
            for name, value in inspect.getmembers(cls, self._is_dbus_property):
640
 
                if value._dbus_name == property_name and value._dbus_interface == interface_name:
641
 
                    return value.__get__(self)
642
 
        
 
596
        for name in (property_name,
 
597
                     property_name + "_dbus_property"):
 
598
            prop = getattr(self, name, None)
 
599
            if (prop is None
 
600
                or not self._is_dbus_property(prop)
 
601
                or prop._dbus_name != property_name
 
602
                or (interface_name and prop._dbus_interface
 
603
                    and interface_name != prop._dbus_interface)):
 
604
                continue
 
605
            return prop
643
606
        # No such property
644
607
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
645
608
                                   + interface_name + "."
646
609
                                   + property_name)
647
 
 
648
610
    
649
611
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
650
612
                         out_signature="v")
680
642
    def GetAll(self, interface_name):
681
643
        """Standard D-Bus property GetAll() method, see D-Bus
682
644
        standard.
683
 
        
 
645
 
684
646
        Note: Will not include properties with access="write".
685
647
        """
686
648
        all = {}
742
704
            xmlstring = document.toxml("utf-8")
743
705
            document.unlink()
744
706
        except (AttributeError, xml.dom.DOMException,
745
 
                xml.parsers.expat.ExpatError) as error:
 
707
                xml.parsers.expat.ExpatError), error:
746
708
            logger.error("Failed to override Introspection method",
747
709
                         error)
748
710
        return xmlstring
749
711
 
750
712
 
751
 
def datetime_to_dbus (dt, variant_level=0):
752
 
    """Convert a UTC datetime.datetime() to a D-Bus type."""
753
 
    if dt is None:
754
 
        return dbus.String("", variant_level = variant_level)
755
 
    return dbus.String(dt.isoformat(),
756
 
                       variant_level=variant_level)
757
 
 
758
 
class transitional_dbus_metaclass(DBusObjectWithProperties.__metaclass__):
759
 
    def __new__(mcs, name, bases, attr):
760
 
        for attrname, old_dbusobj in inspect.getmembers(bases[0]):
761
 
            new_interface = getattr(old_dbusobj, "_dbus_interface", "").replace("se.bsnet.fukt.", "se.recompile.")
762
 
            if (getattr(old_dbusobj, "_dbus_is_signal", False)
763
 
                and old_dbusobj._dbus_interface.startswith("se.bsnet.fukt.Mandos")):
764
 
                unwrappedfunc = dict(zip(old_dbusobj.func_code.co_freevars,
765
 
                                    old_dbusobj.__closure__))["func"].cell_contents
766
 
                newfunc = types.FunctionType(unwrappedfunc.func_code,
767
 
                                             unwrappedfunc.func_globals,
768
 
                                             unwrappedfunc.func_name,
769
 
                                             unwrappedfunc.func_defaults,
770
 
                                             unwrappedfunc.func_closure)
771
 
                new_dbusfunc = dbus.service.signal(
772
 
                    new_interface, old_dbusobj._dbus_signature)(newfunc)            
773
 
                attr["_transitional_" + attrname] = new_dbusfunc
774
 
 
775
 
                def fixscope(func1, func2):
776
 
                    def newcall(*args, **kwargs):
777
 
                        func1(*args, **kwargs)
778
 
                        func2(*args, **kwargs)
779
 
                    return newcall
780
 
 
781
 
                attr[attrname] = fixscope(old_dbusobj, new_dbusfunc)
782
 
            
783
 
            elif (getattr(old_dbusobj, "_dbus_is_method", False)
784
 
                and old_dbusobj._dbus_interface.startswith("se.bsnet.fukt.Mandos")):
785
 
                new_dbusfunc = (dbus.service.method
786
 
                                (new_interface,
787
 
                                 old_dbusobj._dbus_in_signature,
788
 
                                 old_dbusobj._dbus_out_signature)
789
 
                                (types.FunctionType
790
 
                                 (old_dbusobj.func_code,
791
 
                                  old_dbusobj.func_globals,
792
 
                                  old_dbusobj.func_name,
793
 
                                  old_dbusobj.func_defaults,
794
 
                                  old_dbusobj.func_closure)))
795
 
 
796
 
                attr[attrname] = new_dbusfunc
797
 
            elif (getattr(old_dbusobj, "_dbus_is_property", False)
798
 
                  and old_dbusobj._dbus_interface.startswith("se.bsnet.fukt.Mandos")):
799
 
                new_dbusfunc = (dbus_service_property
800
 
                                (new_interface,
801
 
                                 old_dbusobj._dbus_signature,
802
 
                                 old_dbusobj._dbus_access,
803
 
                                 old_dbusobj._dbus_get_args_options["byte_arrays"])
804
 
                                (types.FunctionType
805
 
                                 (old_dbusobj.func_code,
806
 
                                  old_dbusobj.func_globals,
807
 
                                  old_dbusobj.func_name,
808
 
                                  old_dbusobj.func_defaults,
809
 
                                  old_dbusobj.func_closure)))
810
 
 
811
 
                attr[attrname] = new_dbusfunc
812
 
        return type.__new__(mcs, name, bases, attr)
813
 
 
814
713
class ClientDBus(Client, DBusObjectWithProperties):
815
714
    """A Client class using D-Bus
816
715
    
838
737
        DBusObjectWithProperties.__init__(self, self.bus,
839
738
                                          self.dbus_object_path)
840
739
        
841
 
    def notifychangeproperty(transform_func,
842
 
                             dbus_name, type_func=lambda x: x,
843
 
                             variant_level=1):
844
 
        """ Modify a variable so that its a property that announce its
845
 
        changes to DBus.
846
 
        transform_fun: Function that takes a value and transform it to
847
 
                       DBus type.
848
 
        dbus_name: DBus name of the variable
849
 
        type_func: Function that transform the value before sending it
850
 
                   to DBus
851
 
        variant_level: DBus variant level. default: 1
852
 
        """
853
 
        real_value = [None,]
854
 
        def setter(self, value):
855
 
            old_value = real_value[0]
856
 
            real_value[0] = value
857
 
            if hasattr(self, "dbus_object_path"):
858
 
                if type_func(old_value) != type_func(real_value[0]):
859
 
                    dbus_value = transform_func(type_func(real_value[0]),
860
 
                                                variant_level)
861
 
                    self.PropertyChanged(dbus.String(dbus_name),
862
 
                                         dbus_value)
863
 
        
864
 
        return property(lambda self: real_value[0], setter)
865
 
    
866
 
    
867
 
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
868
 
    approvals_pending = notifychangeproperty(dbus.Boolean,
869
 
                                             "ApprovalPending",
870
 
                                             type_func = bool)
871
 
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
872
 
    last_enabled = notifychangeproperty(datetime_to_dbus,
873
 
                                        "LastEnabled")
874
 
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
875
 
                                   type_func = lambda checker: checker is not None)
876
 
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
877
 
                                           "LastCheckedOK")
878
 
    last_approval_request = notifychangeproperty(datetime_to_dbus,
879
 
                                                 "LastApprovalRequest")
880
 
    approved_by_default = notifychangeproperty(dbus.Boolean,
881
 
                                               "ApprovedByDefault")
882
 
    approval_delay = notifychangeproperty(dbus.UInt16, "ApprovalDelay",
883
 
                                          type_func = _timedelta_to_milliseconds)
884
 
    approval_duration = notifychangeproperty(dbus.UInt16, "ApprovalDuration",
885
 
                                             type_func = _timedelta_to_milliseconds)
886
 
    host = notifychangeproperty(dbus.String, "Host")
887
 
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
888
 
                                   type_func = _timedelta_to_milliseconds)
889
 
    extended_timeout = notifychangeproperty(dbus.UInt16, "ExtendedTimeout",
890
 
                                            type_func = _timedelta_to_milliseconds)
891
 
    interval = notifychangeproperty(dbus.UInt16, "Interval",
892
 
                                    type_func = _timedelta_to_milliseconds)
893
 
    checker_command = notifychangeproperty(dbus.String, "Checker")
894
 
    
895
 
    del notifychangeproperty
 
740
    def _get_approvals_pending(self):
 
741
        return self._approvals_pending
 
742
    def _set_approvals_pending(self, value):
 
743
        old_value = self._approvals_pending
 
744
        self._approvals_pending = value
 
745
        bval = bool(value)
 
746
        if (hasattr(self, "dbus_object_path")
 
747
            and bval is not bool(old_value)):
 
748
            dbus_bool = dbus.Boolean(bval, variant_level=1)
 
749
            self.PropertyChanged(dbus.String("ApprovalPending"),
 
750
                                 dbus_bool)
 
751
 
 
752
    approvals_pending = property(_get_approvals_pending,
 
753
                                 _set_approvals_pending)
 
754
    del _get_approvals_pending, _set_approvals_pending
 
755
    
 
756
    @staticmethod
 
757
    def _datetime_to_dbus(dt, variant_level=0):
 
758
        """Convert a UTC datetime.datetime() to a D-Bus type."""
 
759
        return dbus.String(dt.isoformat(),
 
760
                           variant_level=variant_level)
 
761
    
 
762
    def enable(self):
 
763
        oldstate = getattr(self, "enabled", False)
 
764
        r = Client.enable(self)
 
765
        if oldstate != self.enabled:
 
766
            # Emit D-Bus signals
 
767
            self.PropertyChanged(dbus.String("Enabled"),
 
768
                                 dbus.Boolean(True, variant_level=1))
 
769
            self.PropertyChanged(
 
770
                dbus.String("LastEnabled"),
 
771
                self._datetime_to_dbus(self.last_enabled,
 
772
                                       variant_level=1))
 
773
        return r
 
774
    
 
775
    def disable(self, quiet = False):
 
776
        oldstate = getattr(self, "enabled", False)
 
777
        r = Client.disable(self, quiet=quiet)
 
778
        if not quiet and oldstate != self.enabled:
 
779
            # Emit D-Bus signal
 
780
            self.PropertyChanged(dbus.String("Enabled"),
 
781
                                 dbus.Boolean(False, variant_level=1))
 
782
        return r
896
783
    
897
784
    def __del__(self, *args, **kwargs):
898
785
        try:
907
794
                         *args, **kwargs):
908
795
        self.checker_callback_tag = None
909
796
        self.checker = None
 
797
        # Emit D-Bus signal
 
798
        self.PropertyChanged(dbus.String("CheckerRunning"),
 
799
                             dbus.Boolean(False, variant_level=1))
910
800
        if os.WIFEXITED(condition):
911
801
            exitstatus = os.WEXITSTATUS(condition)
912
802
            # Emit D-Bus signal
922
812
        return Client.checker_callback(self, pid, condition, command,
923
813
                                       *args, **kwargs)
924
814
    
 
815
    def checked_ok(self, *args, **kwargs):
 
816
        r = Client.checked_ok(self, *args, **kwargs)
 
817
        # Emit D-Bus signal
 
818
        self.PropertyChanged(
 
819
            dbus.String("LastCheckedOK"),
 
820
            (self._datetime_to_dbus(self.last_checked_ok,
 
821
                                    variant_level=1)))
 
822
        return r
 
823
    
 
824
    def need_approval(self, *args, **kwargs):
 
825
        r = Client.need_approval(self, *args, **kwargs)
 
826
        # Emit D-Bus signal
 
827
        self.PropertyChanged(
 
828
            dbus.String("LastApprovalRequest"),
 
829
            (self._datetime_to_dbus(self.last_approval_request,
 
830
                                    variant_level=1)))
 
831
        return r
 
832
    
925
833
    def start_checker(self, *args, **kwargs):
926
834
        old_checker = self.checker
927
835
        if self.checker is not None:
934
842
            and old_checker_pid != self.checker.pid):
935
843
            # Emit D-Bus signal
936
844
            self.CheckerStarted(self.current_checker_command)
 
845
            self.PropertyChanged(
 
846
                dbus.String("CheckerRunning"),
 
847
                dbus.Boolean(True, variant_level=1))
937
848
        return r
938
849
    
 
850
    def stop_checker(self, *args, **kwargs):
 
851
        old_checker = getattr(self, "checker", None)
 
852
        r = Client.stop_checker(self, *args, **kwargs)
 
853
        if (old_checker is not None
 
854
            and getattr(self, "checker", None) is None):
 
855
            self.PropertyChanged(dbus.String("CheckerRunning"),
 
856
                                 dbus.Boolean(False, variant_level=1))
 
857
        return r
 
858
 
939
859
    def _reset_approved(self):
940
860
        self._approved = None
941
861
        return False
943
863
    def approve(self, value=True):
944
864
        self.send_changedstate()
945
865
        self._approved = value
946
 
        gobject.timeout_add(_timedelta_to_milliseconds
 
866
        gobject.timeout_add(self._timedelta_to_milliseconds
947
867
                            (self.approval_duration),
948
868
                            self._reset_approved)
949
869
    
950
870
    
951
871
    ## D-Bus methods, signals & properties
952
872
    _interface = "se.bsnet.fukt.Mandos.Client"
953
 
 
 
873
    
954
874
    ## Signals
955
875
    
956
876
    # CheckerCompleted - signal
1002
922
    # CheckedOK - method
1003
923
    @dbus.service.method(_interface)
1004
924
    def CheckedOK(self):
1005
 
        self.checked_ok()
 
925
        return self.checked_ok()
1006
926
    
1007
927
    # Enable - method
1008
928
    @dbus.service.method(_interface)
1041
961
        if value is None:       # get
1042
962
            return dbus.Boolean(self.approved_by_default)
1043
963
        self.approved_by_default = bool(value)
 
964
        # Emit D-Bus signal
 
965
        self.PropertyChanged(dbus.String("ApprovedByDefault"),
 
966
                             dbus.Boolean(value, variant_level=1))
1044
967
    
1045
968
    # ApprovalDelay - property
1046
969
    @dbus_service_property(_interface, signature="t",
1049
972
        if value is None:       # get
1050
973
            return dbus.UInt64(self.approval_delay_milliseconds())
1051
974
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
 
975
        # Emit D-Bus signal
 
976
        self.PropertyChanged(dbus.String("ApprovalDelay"),
 
977
                             dbus.UInt64(value, variant_level=1))
1052
978
    
1053
979
    # ApprovalDuration - property
1054
980
    @dbus_service_property(_interface, signature="t",
1055
981
                           access="readwrite")
1056
982
    def ApprovalDuration_dbus_property(self, value=None):
1057
983
        if value is None:       # get
1058
 
            return dbus.UInt64(_timedelta_to_milliseconds(
 
984
            return dbus.UInt64(self._timedelta_to_milliseconds(
1059
985
                    self.approval_duration))
1060
986
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
 
987
        # Emit D-Bus signal
 
988
        self.PropertyChanged(dbus.String("ApprovalDuration"),
 
989
                             dbus.UInt64(value, variant_level=1))
1061
990
    
1062
991
    # Name - property
1063
992
    @dbus_service_property(_interface, signature="s", access="read")
1076
1005
        if value is None:       # get
1077
1006
            return dbus.String(self.host)
1078
1007
        self.host = value
 
1008
        # Emit D-Bus signal
 
1009
        self.PropertyChanged(dbus.String("Host"),
 
1010
                             dbus.String(value, variant_level=1))
1079
1011
    
1080
1012
    # Created - property
1081
1013
    @dbus_service_property(_interface, signature="s", access="read")
1082
1014
    def Created_dbus_property(self):
1083
 
        return dbus.String(datetime_to_dbus(self.created))
 
1015
        return dbus.String(self._datetime_to_dbus(self.created))
1084
1016
    
1085
1017
    # LastEnabled - property
1086
1018
    @dbus_service_property(_interface, signature="s", access="read")
1087
1019
    def LastEnabled_dbus_property(self):
1088
 
        return datetime_to_dbus(self.last_enabled)
 
1020
        if self.last_enabled is None:
 
1021
            return dbus.String("")
 
1022
        return dbus.String(self._datetime_to_dbus(self.last_enabled))
1089
1023
    
1090
1024
    # Enabled - property
1091
1025
    @dbus_service_property(_interface, signature="b",
1105
1039
        if value is not None:
1106
1040
            self.checked_ok()
1107
1041
            return
1108
 
        return datetime_to_dbus(self.last_checked_ok)
1109
 
    
1110
 
    # Expires - property
1111
 
    @dbus_service_property(_interface, signature="s", access="read")
1112
 
    def Expires_dbus_property(self):
1113
 
        return datetime_to_dbus(self.expires)
 
1042
        if self.last_checked_ok is None:
 
1043
            return dbus.String("")
 
1044
        return dbus.String(self._datetime_to_dbus(self
 
1045
                                                  .last_checked_ok))
1114
1046
    
1115
1047
    # LastApprovalRequest - property
1116
1048
    @dbus_service_property(_interface, signature="s", access="read")
1117
1049
    def LastApprovalRequest_dbus_property(self):
1118
 
        return datetime_to_dbus(self.last_approval_request)
 
1050
        if self.last_approval_request is None:
 
1051
            return dbus.String("")
 
1052
        return dbus.String(self.
 
1053
                           _datetime_to_dbus(self
 
1054
                                             .last_approval_request))
1119
1055
    
1120
1056
    # Timeout - property
1121
1057
    @dbus_service_property(_interface, signature="t",
1124
1060
        if value is None:       # get
1125
1061
            return dbus.UInt64(self.timeout_milliseconds())
1126
1062
        self.timeout = datetime.timedelta(0, 0, 0, value)
 
1063
        # Emit D-Bus signal
 
1064
        self.PropertyChanged(dbus.String("Timeout"),
 
1065
                             dbus.UInt64(value, variant_level=1))
1127
1066
        if getattr(self, "disable_initiator_tag", None) is None:
1128
1067
            return
1129
1068
        # Reschedule timeout
1130
1069
        gobject.source_remove(self.disable_initiator_tag)
1131
1070
        self.disable_initiator_tag = None
1132
 
        self.expires = None
1133
1071
        time_to_die = (self.
1134
1072
                       _timedelta_to_milliseconds((self
1135
1073
                                                   .last_checked_ok
1140
1078
            # The timeout has passed
1141
1079
            self.disable()
1142
1080
        else:
1143
 
            self.expires = (datetime.datetime.utcnow()
1144
 
                            + datetime.timedelta(milliseconds = time_to_die))
1145
1081
            self.disable_initiator_tag = (gobject.timeout_add
1146
1082
                                          (time_to_die, self.disable))
1147
1083
    
1148
 
    # ExtendedTimeout - property
1149
 
    @dbus_service_property(_interface, signature="t",
1150
 
                           access="readwrite")
1151
 
    def ExtendedTimeout_dbus_property(self, value=None):
1152
 
        if value is None:       # get
1153
 
            return dbus.UInt64(self.extended_timeout_milliseconds())
1154
 
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1155
 
    
1156
1084
    # Interval - property
1157
1085
    @dbus_service_property(_interface, signature="t",
1158
1086
                           access="readwrite")
1160
1088
        if value is None:       # get
1161
1089
            return dbus.UInt64(self.interval_milliseconds())
1162
1090
        self.interval = datetime.timedelta(0, 0, 0, value)
 
1091
        # Emit D-Bus signal
 
1092
        self.PropertyChanged(dbus.String("Interval"),
 
1093
                             dbus.UInt64(value, variant_level=1))
1163
1094
        if getattr(self, "checker_initiator_tag", None) is None:
1164
1095
            return
1165
1096
        # Reschedule checker run
1167
1098
        self.checker_initiator_tag = (gobject.timeout_add
1168
1099
                                      (value, self.start_checker))
1169
1100
        self.start_checker()    # Start one now, too
1170
 
    
 
1101
 
1171
1102
    # Checker - property
1172
1103
    @dbus_service_property(_interface, signature="s",
1173
1104
                           access="readwrite")
1175
1106
        if value is None:       # get
1176
1107
            return dbus.String(self.checker_command)
1177
1108
        self.checker_command = value
 
1109
        # Emit D-Bus signal
 
1110
        self.PropertyChanged(dbus.String("Checker"),
 
1111
                             dbus.String(self.checker_command,
 
1112
                                         variant_level=1))
1178
1113
    
1179
1114
    # CheckerRunning - property
1180
1115
    @dbus_service_property(_interface, signature="b",
1207
1142
        self._pipe.send(('init', fpr, address))
1208
1143
        if not self._pipe.recv():
1209
1144
            raise KeyError()
1210
 
    
 
1145
 
1211
1146
    def __getattribute__(self, name):
1212
1147
        if(name == '_pipe'):
1213
1148
            return super(ProxyClient, self).__getattribute__(name)
1220
1155
                self._pipe.send(('funcall', name, args, kwargs))
1221
1156
                return self._pipe.recv()[1]
1222
1157
            return func
1223
 
    
 
1158
 
1224
1159
    def __setattr__(self, name, value):
1225
1160
        if(name == '_pipe'):
1226
1161
            return super(ProxyClient, self).__setattr__(name, value)
1227
1162
        self._pipe.send(('setattr', name, value))
1228
1163
 
1229
 
class ClientDBusTransitional(ClientDBus):
1230
 
    __metaclass__ = transitional_dbus_metaclass
1231
1164
 
1232
1165
class ClientHandler(socketserver.BaseRequestHandler, object):
1233
1166
    """A class to handle client connections.
1241
1174
                        unicode(self.client_address))
1242
1175
            logger.debug("Pipe FD: %d",
1243
1176
                         self.server.child_pipe.fileno())
1244
 
            
 
1177
 
1245
1178
            session = (gnutls.connection
1246
1179
                       .ClientSession(self.request,
1247
1180
                                      gnutls.connection
1248
1181
                                      .X509Credentials()))
1249
 
            
 
1182
 
1250
1183
            # Note: gnutls.connection.X509Credentials is really a
1251
1184
            # generic GnuTLS certificate credentials object so long as
1252
1185
            # no X.509 keys are added to it.  Therefore, we can use it
1253
1186
            # here despite using OpenPGP certificates.
1254
 
            
 
1187
 
1255
1188
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
1256
1189
            #                      "+AES-256-CBC", "+SHA1",
1257
1190
            #                      "+COMP-NULL", "+CTYPE-OPENPGP",
1263
1196
            (gnutls.library.functions
1264
1197
             .gnutls_priority_set_direct(session._c_object,
1265
1198
                                         priority, None))
1266
 
            
 
1199
 
1267
1200
            # Start communication using the Mandos protocol
1268
1201
            # Get protocol number
1269
1202
            line = self.request.makefile().readline()
1271
1204
            try:
1272
1205
                if int(line.strip().split()[0]) > 1:
1273
1206
                    raise RuntimeError
1274
 
            except (ValueError, IndexError, RuntimeError) as error:
 
1207
            except (ValueError, IndexError, RuntimeError), error:
1275
1208
                logger.error("Unknown protocol version: %s", error)
1276
1209
                return
1277
 
            
 
1210
 
1278
1211
            # Start GnuTLS connection
1279
1212
            try:
1280
1213
                session.handshake()
1281
 
            except gnutls.errors.GNUTLSError as error:
 
1214
            except gnutls.errors.GNUTLSError, error:
1282
1215
                logger.warning("Handshake failed: %s", error)
1283
1216
                # Do not run session.bye() here: the session is not
1284
1217
                # established.  Just abandon the request.
1285
1218
                return
1286
1219
            logger.debug("Handshake succeeded")
1287
 
            
 
1220
 
1288
1221
            approval_required = False
1289
1222
            try:
1290
1223
                try:
1291
1224
                    fpr = self.fingerprint(self.peer_certificate
1292
1225
                                           (session))
1293
 
                except (TypeError,
1294
 
                        gnutls.errors.GNUTLSError) as error:
 
1226
                except (TypeError, gnutls.errors.GNUTLSError), error:
1295
1227
                    logger.warning("Bad certificate: %s", error)
1296
1228
                    return
1297
1229
                logger.debug("Fingerprint: %s", fpr)
1298
 
                
 
1230
 
1299
1231
                try:
1300
1232
                    client = ProxyClient(child_pipe, fpr,
1301
1233
                                         self.client_address)
1309
1241
                
1310
1242
                while True:
1311
1243
                    if not client.enabled:
1312
 
                        logger.info("Client %s is disabled",
 
1244
                        logger.warning("Client %s is disabled",
1313
1245
                                       client.name)
1314
1246
                        if self.server.use_dbus:
1315
1247
                            # Emit D-Bus signal
1360
1292
                while sent_size < len(client.secret):
1361
1293
                    try:
1362
1294
                        sent = session.send(client.secret[sent_size:])
1363
 
                    except gnutls.errors.GNUTLSError as error:
 
1295
                    except (gnutls.errors.GNUTLSError), error:
1364
1296
                        logger.warning("gnutls send failed")
1365
1297
                        return
1366
1298
                    logger.debug("Sent: %d, remaining: %d",
1367
1299
                                 sent, len(client.secret)
1368
1300
                                 - (sent_size + sent))
1369
1301
                    sent_size += sent
1370
 
                
 
1302
 
1371
1303
                logger.info("Sending secret to %s", client.name)
1372
1304
                # bump the timeout as if seen
1373
 
                client.checked_ok(client.extended_timeout)
 
1305
                client.checked_ok()
1374
1306
                if self.server.use_dbus:
1375
1307
                    # Emit D-Bus signal
1376
1308
                    client.GotSecret()
1380
1312
                    client.approvals_pending -= 1
1381
1313
                try:
1382
1314
                    session.bye()
1383
 
                except gnutls.errors.GNUTLSError as error:
 
1315
                except (gnutls.errors.GNUTLSError), error:
1384
1316
                    logger.warning("GnuTLS bye failed")
1385
1317
    
1386
1318
    @staticmethod
1461
1393
        multiprocessing.Process(target = self.sub_process_main,
1462
1394
                                args = (request, address)).start()
1463
1395
 
1464
 
 
1465
1396
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1466
1397
    """ adds a pipe to the MixIn """
1467
1398
    def process_request(self, request, client_address):
1470
1401
        This function creates a new pipe in self.pipe
1471
1402
        """
1472
1403
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
1473
 
        
 
1404
 
1474
1405
        super(MultiprocessingMixInWithPipe,
1475
1406
              self).process_request(request, client_address)
1476
1407
        self.child_pipe.close()
1477
1408
        self.add_pipe(parent_pipe)
1478
 
    
 
1409
 
1479
1410
    def add_pipe(self, parent_pipe):
1480
1411
        """Dummy function; override as necessary"""
1481
1412
        raise NotImplementedError
1482
1413
 
1483
 
 
1484
1414
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1485
1415
                     socketserver.TCPServer, object):
1486
1416
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1512
1442
                                           SO_BINDTODEVICE,
1513
1443
                                           str(self.interface
1514
1444
                                               + '\0'))
1515
 
                except socket.error as error:
 
1445
                except socket.error, error:
1516
1446
                    if error[0] == errno.EPERM:
1517
1447
                        logger.error("No permission to"
1518
1448
                                     " bind to interface %s",
1612
1542
                    client = c
1613
1543
                    break
1614
1544
            else:
1615
 
                logger.info("Client not found for fingerprint: %s, ad"
1616
 
                            "dress: %s", fpr, address)
 
1545
                logger.warning("Client not found for fingerprint: %s, ad"
 
1546
                               "dress: %s", fpr, address)
1617
1547
                if self.use_dbus:
1618
1548
                    # Emit D-Bus signal
1619
1549
                    mandos_dbus_service.ClientNotFound(fpr, address[0])
1634
1564
            kwargs = request[3]
1635
1565
            
1636
1566
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1637
 
        
 
1567
 
1638
1568
        if command == 'getattr':
1639
1569
            attrname = request[1]
1640
1570
            if callable(client_object.__getattribute__(attrname)):
1646
1576
            attrname = request[1]
1647
1577
            value = request[2]
1648
1578
            setattr(client_object, attrname, value)
1649
 
        
 
1579
 
1650
1580
        return True
1651
1581
 
1652
1582
 
1683
1613
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1684
1614
            else:
1685
1615
                raise ValueError("Unknown suffix %r" % suffix)
1686
 
        except (ValueError, IndexError) as e:
 
1616
        except (ValueError, IndexError), e:
1687
1617
            raise ValueError(*(e.args))
1688
1618
        timevalue += delta
1689
1619
    return timevalue
1743
1673
    ##################################################################
1744
1674
    # Parsing of options, both command line and config file
1745
1675
    
1746
 
    parser = argparse.ArgumentParser()
1747
 
    parser.add_argument("-v", "--version", action="version",
1748
 
                        version = "%%(prog)s %s" % version,
1749
 
                        help="show version number and exit")
1750
 
    parser.add_argument("-i", "--interface", metavar="IF",
1751
 
                        help="Bind to interface IF")
1752
 
    parser.add_argument("-a", "--address",
1753
 
                        help="Address to listen for requests on")
1754
 
    parser.add_argument("-p", "--port", type=int,
1755
 
                        help="Port number to receive requests on")
1756
 
    parser.add_argument("--check", action="store_true",
1757
 
                        help="Run self-test")
1758
 
    parser.add_argument("--debug", action="store_true",
1759
 
                        help="Debug mode; run in foreground and log"
1760
 
                        " to terminal")
1761
 
    parser.add_argument("--debuglevel", metavar="LEVEL",
1762
 
                        help="Debug level for stdout output")
1763
 
    parser.add_argument("--priority", help="GnuTLS"
1764
 
                        " priority string (see GnuTLS documentation)")
1765
 
    parser.add_argument("--servicename",
1766
 
                        metavar="NAME", help="Zeroconf service name")
1767
 
    parser.add_argument("--configdir",
1768
 
                        default="/etc/mandos", metavar="DIR",
1769
 
                        help="Directory to search for configuration"
1770
 
                        " files")
1771
 
    parser.add_argument("--no-dbus", action="store_false",
1772
 
                        dest="use_dbus", help="Do not provide D-Bus"
1773
 
                        " system bus interface")
1774
 
    parser.add_argument("--no-ipv6", action="store_false",
1775
 
                        dest="use_ipv6", help="Do not use IPv6")
1776
 
    options = parser.parse_args()
 
1676
    parser = optparse.OptionParser(version = "%%prog %s" % version)
 
1677
    parser.add_option("-i", "--interface", type="string",
 
1678
                      metavar="IF", help="Bind to interface IF")
 
1679
    parser.add_option("-a", "--address", type="string",
 
1680
                      help="Address to listen for requests on")
 
1681
    parser.add_option("-p", "--port", type="int",
 
1682
                      help="Port number to receive requests on")
 
1683
    parser.add_option("--check", action="store_true",
 
1684
                      help="Run self-test")
 
1685
    parser.add_option("--debug", action="store_true",
 
1686
                      help="Debug mode; run in foreground and log to"
 
1687
                      " terminal")
 
1688
    parser.add_option("--debuglevel", type="string", metavar="LEVEL",
 
1689
                      help="Debug level for stdout output")
 
1690
    parser.add_option("--priority", type="string", help="GnuTLS"
 
1691
                      " priority string (see GnuTLS documentation)")
 
1692
    parser.add_option("--servicename", type="string",
 
1693
                      metavar="NAME", help="Zeroconf service name")
 
1694
    parser.add_option("--configdir", type="string",
 
1695
                      default="/etc/mandos", metavar="DIR",
 
1696
                      help="Directory to search for configuration"
 
1697
                      " files")
 
1698
    parser.add_option("--no-dbus", action="store_false",
 
1699
                      dest="use_dbus", help="Do not provide D-Bus"
 
1700
                      " system bus interface")
 
1701
    parser.add_option("--no-ipv6", action="store_false",
 
1702
                      dest="use_ipv6", help="Do not use IPv6")
 
1703
    options = parser.parse_args()[0]
1777
1704
    
1778
1705
    if options.check:
1779
1706
        import doctest
1831
1758
    debuglevel = server_settings["debuglevel"]
1832
1759
    use_dbus = server_settings["use_dbus"]
1833
1760
    use_ipv6 = server_settings["use_ipv6"]
1834
 
    
 
1761
 
1835
1762
    if server_settings["servicename"] != "Mandos":
1836
1763
        syslogger.setFormatter(logging.Formatter
1837
1764
                               ('Mandos (%s) [%%(process)d]:'
1839
1766
                                % server_settings["servicename"]))
1840
1767
    
1841
1768
    # Parse config file with clients
1842
 
    client_defaults = { "timeout": "5m",
1843
 
                        "extended_timeout": "15m",
1844
 
                        "interval": "2m",
 
1769
    client_defaults = { "timeout": "1h",
 
1770
                        "interval": "5m",
1845
1771
                        "checker": "fping -q -- %%(host)s",
1846
1772
                        "host": "",
1847
1773
                        "approval_delay": "0s",
1887
1813
    try:
1888
1814
        os.setgid(gid)
1889
1815
        os.setuid(uid)
1890
 
    except OSError as error:
 
1816
    except OSError, error:
1891
1817
        if error[0] != errno.EPERM:
1892
1818
            raise error
1893
1819
    
1898
1824
        level = getattr(logging, debuglevel.upper())
1899
1825
        syslogger.setLevel(level)
1900
1826
        console.setLevel(level)
1901
 
    
 
1827
 
1902
1828
    if debug:
1903
1829
        # Enable all possible GnuTLS debugging
1904
1830
        
1937
1863
        try:
1938
1864
            bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
1939
1865
                                            bus, do_not_queue=True)
1940
 
            bus_name2 = dbus.service.BusName("se.recompile.Mandos",
1941
 
                                            bus, do_not_queue=True)
1942
 
        except dbus.exceptions.NameExistsException as e:
 
1866
        except dbus.exceptions.NameExistsException, e:
1943
1867
            logger.error(unicode(e) + ", disabling D-Bus")
1944
1868
            use_dbus = False
1945
1869
            server_settings["use_dbus"] = False
1957
1881
    
1958
1882
    client_class = Client
1959
1883
    if use_dbus:
1960
 
        client_class = functools.partial(ClientDBusTransitional, bus = bus)        
 
1884
        client_class = functools.partial(ClientDBus, bus = bus)
1961
1885
    def client_config_items(config, section):
1962
1886
        special_settings = {
1963
1887
            "approved_by_default":
1993
1917
        del pidfilename
1994
1918
        
1995
1919
        signal.signal(signal.SIGINT, signal.SIG_IGN)
1996
 
    
 
1920
 
1997
1921
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1998
1922
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1999
1923
    
2050
1974
            
2051
1975
            del _interface
2052
1976
        
2053
 
        class MandosDBusServiceTransitional(MandosDBusService):
2054
 
            __metaclass__ = transitional_dbus_metaclass
2055
 
        mandos_dbus_service = MandosDBusServiceTransitional()
 
1977
        mandos_dbus_service = MandosDBusService()
2056
1978
    
2057
1979
    def cleanup():
2058
1980
        "Cleanup function; run on exit"
2097
2019
        # From the Avahi example code
2098
2020
        try:
2099
2021
            service.activate()
2100
 
        except dbus.exceptions.DBusException as error:
 
2022
        except dbus.exceptions.DBusException, error:
2101
2023
            logger.critical("DBusException: %s", error)
2102
2024
            cleanup()
2103
2025
            sys.exit(1)
2110
2032
        
2111
2033
        logger.debug("Starting main loop")
2112
2034
        main_loop.run()
2113
 
    except AvahiError as error:
 
2035
    except AvahiError, error:
2114
2036
        logger.critical("AvahiError: %s", error)
2115
2037
        cleanup()
2116
2038
        sys.exit(1)
2122
2044
    # Must run before the D-Bus bus name gets deregistered
2123
2045
    cleanup()
2124
2046
 
2125
 
 
2126
2047
if __name__ == '__main__':
2127
2048
    main()