/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

* mandos: Added ClientDBus.approve_pending property.  Exposed
          approved_by_default, approved_delay, approved_duration as
          D-Bus properties.

Show diffs side-by-side

added added

removed removed

Lines of Context:
55
55
import logging
56
56
import logging.handlers
57
57
import pwd
58
 
from contextlib import closing
 
58
import contextlib
59
59
import struct
60
60
import fcntl
61
61
import functools
 
62
import cPickle as pickle
 
63
import multiprocessing
62
64
 
63
65
import dbus
64
66
import dbus.service
67
69
from dbus.mainloop.glib import DBusGMainLoop
68
70
import ctypes
69
71
import ctypes.util
 
72
import xml.dom.minidom
 
73
import inspect
70
74
 
71
75
try:
72
76
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
77
81
        SO_BINDTODEVICE = None
78
82
 
79
83
 
80
 
version = "1.0.12"
 
84
version = "1.0.14"
81
85
 
82
86
logger = logging.Logger(u'mandos')
83
87
syslogger = (logging.handlers.SysLogHandler
94
98
                                       u' %(message)s'))
95
99
logger.addHandler(console)
96
100
 
 
101
multiprocessing_manager = multiprocessing.Manager()
 
102
 
97
103
class AvahiError(Exception):
98
104
    def __init__(self, value, *args, **kwargs):
99
105
        self.value = value
174
180
                                    self.server.EntryGroupNew()),
175
181
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
176
182
            self.group.connect_to_signal('StateChanged',
177
 
                                         self.entry_group_state_changed)
 
183
                                         self
 
184
                                         .entry_group_state_changed)
178
185
        logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
179
186
                     self.name, self.type)
180
187
        self.group.AddService(
239
246
    enabled:    bool()
240
247
    last_checked_ok: datetime.datetime(); (UTC) or None
241
248
    timeout:    datetime.timedelta(); How long from last_checked_ok
242
 
                                      until this client is invalid
 
249
                                      until this client is disabled
243
250
    interval:   datetime.timedelta(); How often to start a new checker
244
251
    disable_hook:  If set, called by disable() as disable_hook(self)
245
252
    checker:    subprocess.Popen(); a running checker process used
246
253
                                    to see if the client lives.
247
254
                                    'None' if no process is running.
248
255
    checker_initiator_tag: a gobject event source tag, or None
249
 
    disable_initiator_tag:    - '' -
 
256
    disable_initiator_tag: - '' -
250
257
    checker_callback_tag:  - '' -
251
258
    checker_command: string; External command which is run to check if
252
259
                     client lives.  %() expansions are done at
253
260
                     runtime with vars(self) as dict, so that for
254
261
                     instance %(name)s can be used in the command.
255
262
    current_checker_command: string; current running checker_command
 
263
    approved_delay: datetime.timedelta(); Time to wait for approval
 
264
    _approved:   bool(); 'None' if not yet approved/disapproved
 
265
    approved_duration: datetime.timedelta(); Duration of one approval
256
266
    """
257
267
    
258
268
    @staticmethod
259
 
    def _datetime_to_milliseconds(dt):
260
 
        "Convert a datetime.datetime() to milliseconds"
261
 
        return ((dt.days * 24 * 60 * 60 * 1000)
262
 
                + (dt.seconds * 1000)
263
 
                + (dt.microseconds // 1000))
 
269
    def _timedelta_to_milliseconds(td):
 
270
        "Convert a datetime.timedelta() to milliseconds"
 
271
        return ((td.days * 24 * 60 * 60 * 1000)
 
272
                + (td.seconds * 1000)
 
273
                + (td.microseconds // 1000))
264
274
    
265
275
    def timeout_milliseconds(self):
266
276
        "Return the 'timeout' attribute in milliseconds"
267
 
        return self._datetime_to_milliseconds(self.timeout)
 
277
        return self._timedelta_to_milliseconds(self.timeout)
268
278
    
269
279
    def interval_milliseconds(self):
270
280
        "Return the 'interval' attribute in milliseconds"
271
 
        return self._datetime_to_milliseconds(self.interval)
 
281
        return self._timedelta_to_milliseconds(self.interval)
 
282
 
 
283
    def approved_delay_milliseconds(self):
 
284
        return self._timedelta_to_milliseconds(self.approved_delay)
272
285
    
273
286
    def __init__(self, name = None, disable_hook=None, config=None):
274
287
        """Note: the 'checker' key in 'config' sets the
287
300
        if u"secret" in config:
288
301
            self.secret = config[u"secret"].decode(u"base64")
289
302
        elif u"secfile" in config:
290
 
            with closing(open(os.path.expanduser
291
 
                              (os.path.expandvars
292
 
                               (config[u"secfile"])))) as secfile:
 
303
            with open(os.path.expanduser(os.path.expandvars
 
304
                                         (config[u"secfile"])),
 
305
                      "rb") as secfile:
293
306
                self.secret = secfile.read()
294
307
        else:
 
308
            #XXX Need to allow secret on demand!
295
309
            raise TypeError(u"No secret or secfile for client %s"
296
310
                            % self.name)
297
311
        self.host = config.get(u"host", u"")
309
323
        self.checker_command = config[u"checker"]
310
324
        self.current_checker_command = None
311
325
        self.last_connect = None
312
 
    
 
326
        self.approvals_pending = 0
 
327
        self._approved = None
 
328
        self.approved_by_default = config.get(u"approved_by_default",
 
329
                                              False)
 
330
        self.approved_delay = string_to_delta(
 
331
            config[u"approved_delay"])
 
332
        self.approved_duration = string_to_delta(
 
333
            config[u"approved_duration"])
 
334
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
 
335
 
 
336
    def send_changedstate(self):
 
337
        self.changedstate.acquire()
 
338
        self.changedstate.notify_all()
 
339
        self.changedstate.release()
 
340
        
313
341
    def enable(self):
314
342
        """Start this client's checker and timeout hooks"""
315
343
        if getattr(self, u"enabled", False):
316
344
            # Already enabled
317
345
            return
 
346
        self.send_changedstate()
318
347
        self.last_enabled = datetime.datetime.utcnow()
319
348
        # Schedule a new checker to be started an 'interval' from now,
320
349
        # and every interval from then on.
321
350
        self.checker_initiator_tag = (gobject.timeout_add
322
351
                                      (self.interval_milliseconds(),
323
352
                                       self.start_checker))
324
 
        # Also start a new checker *right now*.
325
 
        self.start_checker()
326
353
        # Schedule a disable() when 'timeout' has passed
327
354
        self.disable_initiator_tag = (gobject.timeout_add
328
355
                                   (self.timeout_milliseconds(),
329
356
                                    self.disable))
330
357
        self.enabled = True
 
358
        # Also start a new checker *right now*.
 
359
        self.start_checker()
331
360
    
332
 
    def disable(self):
 
361
    def disable(self, quiet=True):
333
362
        """Disable this client."""
334
363
        if not getattr(self, "enabled", False):
335
364
            return False
336
 
        logger.info(u"Disabling client %s", self.name)
 
365
        if not quiet:
 
366
            self.send_changedstate()
 
367
        if not quiet:
 
368
            logger.info(u"Disabling client %s", self.name)
337
369
        if getattr(self, u"disable_initiator_tag", False):
338
370
            gobject.source_remove(self.disable_initiator_tag)
339
371
            self.disable_initiator_tag = None
391
423
        # client would inevitably timeout, since no checker would get
392
424
        # a chance to run to completion.  If we instead leave running
393
425
        # checkers alone, the checker would have to take more time
394
 
        # than 'timeout' for the client to be declared invalid, which
395
 
        # is as it should be.
 
426
        # than 'timeout' for the client to be disabled, which is as it
 
427
        # should be.
396
428
        
397
429
        # If a checker exists, make sure it is not a zombie
398
 
        if self.checker is not None:
 
430
        try:
399
431
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
432
        except (AttributeError, OSError), error:
 
433
            if (isinstance(error, OSError)
 
434
                and error.errno != errno.ECHILD):
 
435
                raise error
 
436
        else:
400
437
            if pid:
401
438
                logger.warning(u"Checker was a zombie")
402
439
                gobject.source_remove(self.checker_callback_tag)
458
495
        logger.debug(u"Stopping checker for %(name)s", vars(self))
459
496
        try:
460
497
            os.kill(self.checker.pid, signal.SIGTERM)
461
 
            #os.sleep(0.5)
 
498
            #time.sleep(0.5)
462
499
            #if self.checker.poll() is None:
463
500
            #    os.kill(self.checker.pid, signal.SIGKILL)
464
501
        except OSError, error:
465
502
            if error.errno != errno.ESRCH: # No such process
466
503
                raise
467
504
        self.checker = None
468
 
    
469
 
    def still_valid(self):
470
 
        """Has the timeout not yet passed for this client?"""
471
 
        if not getattr(self, u"enabled", False):
472
 
            return False
473
 
        now = datetime.datetime.utcnow()
474
 
        if self.last_checked_ok is None:
475
 
            return now < (self.created + self.timeout)
476
 
        else:
477
 
            return now < (self.last_checked_ok + self.timeout)
478
 
 
479
 
 
480
 
class ClientDBus(Client, dbus.service.Object):
 
505
 
 
506
def dbus_service_property(dbus_interface, signature=u"v",
 
507
                          access=u"readwrite", byte_arrays=False):
 
508
    """Decorators for marking methods of a DBusObjectWithProperties to
 
509
    become properties on the D-Bus.
 
510
    
 
511
    The decorated method will be called with no arguments by "Get"
 
512
    and with one argument by "Set".
 
513
    
 
514
    The parameters, where they are supported, are the same as
 
515
    dbus.service.method, except there is only "signature", since the
 
516
    type from Get() and the type sent to Set() is the same.
 
517
    """
 
518
    # Encoding deeply encoded byte arrays is not supported yet by the
 
519
    # "Set" method, so we fail early here:
 
520
    if byte_arrays and signature != u"ay":
 
521
        raise ValueError(u"Byte arrays not supported for non-'ay'"
 
522
                         u" signature %r" % signature)
 
523
    def decorator(func):
 
524
        func._dbus_is_property = True
 
525
        func._dbus_interface = dbus_interface
 
526
        func._dbus_signature = signature
 
527
        func._dbus_access = access
 
528
        func._dbus_name = func.__name__
 
529
        if func._dbus_name.endswith(u"_dbus_property"):
 
530
            func._dbus_name = func._dbus_name[:-14]
 
531
        func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
 
532
        return func
 
533
    return decorator
 
534
 
 
535
 
 
536
class DBusPropertyException(dbus.exceptions.DBusException):
 
537
    """A base class for D-Bus property-related exceptions
 
538
    """
 
539
    def __unicode__(self):
 
540
        return unicode(str(self))
 
541
 
 
542
 
 
543
class DBusPropertyAccessException(DBusPropertyException):
 
544
    """A property's access permissions disallows an operation.
 
545
    """
 
546
    pass
 
547
 
 
548
 
 
549
class DBusPropertyNotFound(DBusPropertyException):
 
550
    """An attempt was made to access a non-existing property.
 
551
    """
 
552
    pass
 
553
 
 
554
 
 
555
class DBusObjectWithProperties(dbus.service.Object):
 
556
    """A D-Bus object with properties.
 
557
 
 
558
    Classes inheriting from this can use the dbus_service_property
 
559
    decorator to expose methods as D-Bus properties.  It exposes the
 
560
    standard Get(), Set(), and GetAll() methods on the D-Bus.
 
561
    """
 
562
    
 
563
    @staticmethod
 
564
    def _is_dbus_property(obj):
 
565
        return getattr(obj, u"_dbus_is_property", False)
 
566
    
 
567
    def _get_all_dbus_properties(self):
 
568
        """Returns a generator of (name, attribute) pairs
 
569
        """
 
570
        return ((prop._dbus_name, prop)
 
571
                for name, prop in
 
572
                inspect.getmembers(self, self._is_dbus_property))
 
573
    
 
574
    def _get_dbus_property(self, interface_name, property_name):
 
575
        """Returns a bound method if one exists which is a D-Bus
 
576
        property with the specified name and interface.
 
577
        """
 
578
        for name in (property_name,
 
579
                     property_name + u"_dbus_property"):
 
580
            prop = getattr(self, name, None)
 
581
            if (prop is None
 
582
                or not self._is_dbus_property(prop)
 
583
                or prop._dbus_name != property_name
 
584
                or (interface_name and prop._dbus_interface
 
585
                    and interface_name != prop._dbus_interface)):
 
586
                continue
 
587
            return prop
 
588
        # No such property
 
589
        raise DBusPropertyNotFound(self.dbus_object_path + u":"
 
590
                                   + interface_name + u"."
 
591
                                   + property_name)
 
592
    
 
593
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
 
594
                         out_signature=u"v")
 
595
    def Get(self, interface_name, property_name):
 
596
        """Standard D-Bus property Get() method, see D-Bus standard.
 
597
        """
 
598
        prop = self._get_dbus_property(interface_name, property_name)
 
599
        if prop._dbus_access == u"write":
 
600
            raise DBusPropertyAccessException(property_name)
 
601
        value = prop()
 
602
        if not hasattr(value, u"variant_level"):
 
603
            return value
 
604
        return type(value)(value, variant_level=value.variant_level+1)
 
605
    
 
606
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
 
607
    def Set(self, interface_name, property_name, value):
 
608
        """Standard D-Bus property Set() method, see D-Bus standard.
 
609
        """
 
610
        prop = self._get_dbus_property(interface_name, property_name)
 
611
        if prop._dbus_access == u"read":
 
612
            raise DBusPropertyAccessException(property_name)
 
613
        if prop._dbus_get_args_options[u"byte_arrays"]:
 
614
            # The byte_arrays option is not supported yet on
 
615
            # signatures other than "ay".
 
616
            if prop._dbus_signature != u"ay":
 
617
                raise ValueError
 
618
            value = dbus.ByteArray(''.join(unichr(byte)
 
619
                                           for byte in value))
 
620
        prop(value)
 
621
    
 
622
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
 
623
                         out_signature=u"a{sv}")
 
624
    def GetAll(self, interface_name):
 
625
        """Standard D-Bus property GetAll() method, see D-Bus
 
626
        standard.
 
627
 
 
628
        Note: Will not include properties with access="write".
 
629
        """
 
630
        all = {}
 
631
        for name, prop in self._get_all_dbus_properties():
 
632
            if (interface_name
 
633
                and interface_name != prop._dbus_interface):
 
634
                # Interface non-empty but did not match
 
635
                continue
 
636
            # Ignore write-only properties
 
637
            if prop._dbus_access == u"write":
 
638
                continue
 
639
            value = prop()
 
640
            if not hasattr(value, u"variant_level"):
 
641
                all[name] = value
 
642
                continue
 
643
            all[name] = type(value)(value, variant_level=
 
644
                                    value.variant_level+1)
 
645
        return dbus.Dictionary(all, signature=u"sv")
 
646
    
 
647
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
 
648
                         out_signature=u"s",
 
649
                         path_keyword='object_path',
 
650
                         connection_keyword='connection')
 
651
    def Introspect(self, object_path, connection):
 
652
        """Standard D-Bus method, overloaded to insert property tags.
 
653
        """
 
654
        xmlstring = dbus.service.Object.Introspect(self, object_path,
 
655
                                                   connection)
 
656
        try:
 
657
            document = xml.dom.minidom.parseString(xmlstring)
 
658
            def make_tag(document, name, prop):
 
659
                e = document.createElement(u"property")
 
660
                e.setAttribute(u"name", name)
 
661
                e.setAttribute(u"type", prop._dbus_signature)
 
662
                e.setAttribute(u"access", prop._dbus_access)
 
663
                return e
 
664
            for if_tag in document.getElementsByTagName(u"interface"):
 
665
                for tag in (make_tag(document, name, prop)
 
666
                            for name, prop
 
667
                            in self._get_all_dbus_properties()
 
668
                            if prop._dbus_interface
 
669
                            == if_tag.getAttribute(u"name")):
 
670
                    if_tag.appendChild(tag)
 
671
                # Add the names to the return values for the
 
672
                # "org.freedesktop.DBus.Properties" methods
 
673
                if (if_tag.getAttribute(u"name")
 
674
                    == u"org.freedesktop.DBus.Properties"):
 
675
                    for cn in if_tag.getElementsByTagName(u"method"):
 
676
                        if cn.getAttribute(u"name") == u"Get":
 
677
                            for arg in cn.getElementsByTagName(u"arg"):
 
678
                                if (arg.getAttribute(u"direction")
 
679
                                    == u"out"):
 
680
                                    arg.setAttribute(u"name", u"value")
 
681
                        elif cn.getAttribute(u"name") == u"GetAll":
 
682
                            for arg in cn.getElementsByTagName(u"arg"):
 
683
                                if (arg.getAttribute(u"direction")
 
684
                                    == u"out"):
 
685
                                    arg.setAttribute(u"name", u"props")
 
686
            xmlstring = document.toxml(u"utf-8")
 
687
            document.unlink()
 
688
        except (AttributeError, xml.dom.DOMException,
 
689
                xml.parsers.expat.ExpatError), error:
 
690
            logger.error(u"Failed to override Introspection method",
 
691
                         error)
 
692
        return xmlstring
 
693
 
 
694
 
 
695
class ClientDBus(Client, DBusObjectWithProperties):
481
696
    """A Client class using D-Bus
482
697
    
483
698
    Attributes:
494
709
        self.dbus_object_path = (dbus.ObjectPath
495
710
                                 (u"/clients/"
496
711
                                  + self.name.replace(u".", u"_")))
497
 
        dbus.service.Object.__init__(self, self.bus,
498
 
                                     self.dbus_object_path)
 
712
        DBusObjectWithProperties.__init__(self, self.bus,
 
713
                                          self.dbus_object_path)
499
714
    
500
715
    @staticmethod
501
716
    def _datetime_to_dbus(dt, variant_level=0):
516
731
                                       variant_level=1))
517
732
        return r
518
733
    
519
 
    def disable(self, signal = True):
 
734
    def disable(self, quiet = False):
520
735
        oldstate = getattr(self, u"enabled", False)
521
 
        r = Client.disable(self)
522
 
        if signal and oldstate != self.enabled:
 
736
        r = Client.disable(self, quiet=quiet)
 
737
        if not quiet and oldstate != self.enabled:
523
738
            # Emit D-Bus signal
524
739
            self.PropertyChanged(dbus.String(u"enabled"),
525
740
                                 dbus.Boolean(False, variant_level=1))
530
745
            self.remove_from_connection()
531
746
        except LookupError:
532
747
            pass
533
 
        if hasattr(dbus.service.Object, u"__del__"):
534
 
            dbus.service.Object.__del__(self, *args, **kwargs)
 
748
        if hasattr(DBusObjectWithProperties, u"__del__"):
 
749
            DBusObjectWithProperties.__del__(self, *args, **kwargs)
535
750
        Client.__del__(self, *args, **kwargs)
536
751
    
537
752
    def checker_callback(self, pid, condition, command,
590
805
            self.PropertyChanged(dbus.String(u"checker_running"),
591
806
                                 dbus.Boolean(False, variant_level=1))
592
807
        return r
593
 
    
594
 
    ## D-Bus methods & signals
 
808
 
 
809
    def _reset_approved(self):
 
810
        self._approved = None
 
811
        return False
 
812
    
 
813
    def approve(self, value=True):
 
814
        self._approved = value
 
815
        gobject.timeout_add(self._timedelta_to_milliseconds(self.approved_duration, self._reset_approved))
 
816
 
 
817
    def approved_pending(self):
 
818
        return self.approvals_pending > 0
 
819
 
 
820
    
 
821
    ## D-Bus methods, signals & properties
595
822
    _interface = u"se.bsnet.fukt.Mandos.Client"
596
823
    
597
 
    # CheckedOK - method
598
 
    @dbus.service.method(_interface)
599
 
    def CheckedOK(self):
600
 
        return self.checked_ok()
 
824
    ## Signals
601
825
    
602
826
    # CheckerCompleted - signal
603
827
    @dbus.service.signal(_interface, signature=u"nxs")
611
835
        "D-Bus signal"
612
836
        pass
613
837
    
614
 
    # GetAllProperties - method
615
 
    @dbus.service.method(_interface, out_signature=u"a{sv}")
616
 
    def GetAllProperties(self):
617
 
        "D-Bus method"
618
 
        return dbus.Dictionary({
619
 
                dbus.String(u"name"):
620
 
                    dbus.String(self.name, variant_level=1),
621
 
                dbus.String(u"fingerprint"):
622
 
                    dbus.String(self.fingerprint, variant_level=1),
623
 
                dbus.String(u"host"):
624
 
                    dbus.String(self.host, variant_level=1),
625
 
                dbus.String(u"created"):
626
 
                    self._datetime_to_dbus(self.created,
627
 
                                           variant_level=1),
628
 
                dbus.String(u"last_enabled"):
629
 
                    (self._datetime_to_dbus(self.last_enabled,
630
 
                                            variant_level=1)
631
 
                     if self.last_enabled is not None
632
 
                     else dbus.Boolean(False, variant_level=1)),
633
 
                dbus.String(u"enabled"):
634
 
                    dbus.Boolean(self.enabled, variant_level=1),
635
 
                dbus.String(u"last_checked_ok"):
636
 
                    (self._datetime_to_dbus(self.last_checked_ok,
637
 
                                            variant_level=1)
638
 
                     if self.last_checked_ok is not None
639
 
                     else dbus.Boolean (False, variant_level=1)),
640
 
                dbus.String(u"timeout"):
641
 
                    dbus.UInt64(self.timeout_milliseconds(),
642
 
                                variant_level=1),
643
 
                dbus.String(u"interval"):
644
 
                    dbus.UInt64(self.interval_milliseconds(),
645
 
                                variant_level=1),
646
 
                dbus.String(u"checker"):
647
 
                    dbus.String(self.checker_command,
648
 
                                variant_level=1),
649
 
                dbus.String(u"checker_running"):
650
 
                    dbus.Boolean(self.checker is not None,
651
 
                                 variant_level=1),
652
 
                dbus.String(u"object_path"):
653
 
                    dbus.ObjectPath(self.dbus_object_path,
654
 
                                    variant_level=1)
655
 
                }, signature=u"sv")
656
 
    
657
 
    # IsStillValid - method
658
 
    @dbus.service.method(_interface, out_signature=u"b")
659
 
    def IsStillValid(self):
660
 
        return self.still_valid()
661
 
    
662
838
    # PropertyChanged - signal
663
839
    @dbus.service.signal(_interface, signature=u"sv")
664
840
    def PropertyChanged(self, property, value):
665
841
        "D-Bus signal"
666
842
        pass
667
843
    
668
 
    # ReceivedSecret - signal
 
844
    # GotSecret - signal
669
845
    @dbus.service.signal(_interface)
670
 
    def ReceivedSecret(self):
 
846
    def GotSecret(self):
671
847
        "D-Bus signal"
672
848
        pass
673
849
    
674
850
    # Rejected - signal
675
 
    @dbus.service.signal(_interface)
676
 
    def Rejected(self):
677
 
        "D-Bus signal"
678
 
        pass
679
 
    
680
 
    # SetChecker - method
681
 
    @dbus.service.method(_interface, in_signature=u"s")
682
 
    def SetChecker(self, checker):
683
 
        "D-Bus setter method"
684
 
        self.checker_command = checker
685
 
        # Emit D-Bus signal
686
 
        self.PropertyChanged(dbus.String(u"checker"),
687
 
                             dbus.String(self.checker_command,
688
 
                                         variant_level=1))
689
 
    
690
 
    # SetHost - method
691
 
    @dbus.service.method(_interface, in_signature=u"s")
692
 
    def SetHost(self, host):
693
 
        "D-Bus setter method"
694
 
        self.host = host
695
 
        # Emit D-Bus signal
696
 
        self.PropertyChanged(dbus.String(u"host"),
697
 
                             dbus.String(self.host, variant_level=1))
698
 
    
699
 
    # SetInterval - method
700
 
    @dbus.service.method(_interface, in_signature=u"t")
701
 
    def SetInterval(self, milliseconds):
702
 
        self.interval = datetime.timedelta(0, 0, 0, milliseconds)
703
 
        # Emit D-Bus signal
704
 
        self.PropertyChanged(dbus.String(u"interval"),
705
 
                             (dbus.UInt64(self.interval_milliseconds(),
706
 
                                          variant_level=1)))
707
 
    
708
 
    # SetSecret - method
709
 
    @dbus.service.method(_interface, in_signature=u"ay",
710
 
                         byte_arrays=True)
711
 
    def SetSecret(self, secret):
712
 
        "D-Bus setter method"
713
 
        self.secret = str(secret)
714
 
    
715
 
    # SetTimeout - method
716
 
    @dbus.service.method(_interface, in_signature=u"t")
717
 
    def SetTimeout(self, milliseconds):
718
 
        self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
719
 
        # Emit D-Bus signal
720
 
        self.PropertyChanged(dbus.String(u"timeout"),
721
 
                             (dbus.UInt64(self.timeout_milliseconds(),
722
 
                                          variant_level=1)))
 
851
    @dbus.service.signal(_interface, signature=u"s")
 
852
    def Rejected(self, reason):
 
853
        "D-Bus signal"
 
854
        pass
 
855
    
 
856
    # NeedApproval - signal
 
857
    @dbus.service.signal(_interface, signature=u"db")
 
858
    def NeedApproval(self, timeout, default):
 
859
        "D-Bus signal"
 
860
        pass
 
861
    
 
862
    ## Methods
 
863
 
 
864
    # Approve - method
 
865
    @dbus.service.method(_interface, in_signature=u"b")
 
866
    def Approve(self, value):
 
867
        self.approve(value)
 
868
 
 
869
    # CheckedOK - method
 
870
    @dbus.service.method(_interface)
 
871
    def CheckedOK(self):
 
872
        return self.checked_ok()
723
873
    
724
874
    # Enable - method
725
875
    @dbus.service.method(_interface)
744
894
    def StopChecker(self):
745
895
        self.stop_checker()
746
896
    
 
897
    ## Properties
 
898
    
 
899
    # approved_pending - property
 
900
    @dbus_service_property(_interface, signature=u"b", access=u"read")
 
901
    def approved_pending_dbus_property(self):
 
902
        return dbus.Boolean(self.approved_pending())
 
903
    
 
904
    # approved_by_default - property
 
905
    @dbus_service_property(_interface, signature=u"b",
 
906
                           access=u"readwrite")
 
907
    def approved_by_default_dbus_property(self):
 
908
        return dbus.Boolean(self.approved_by_default)
 
909
    
 
910
    # approved_delay - property
 
911
    @dbus_service_property(_interface, signature=u"t",
 
912
                           access=u"readwrite")
 
913
    def approved_delay_dbus_property(self):
 
914
        return dbus.UInt64(self.approved_delay_milliseconds())
 
915
    
 
916
    # approved_duration - property
 
917
    @dbus_service_property(_interface, signature=u"t",
 
918
                           access=u"readwrite")
 
919
    def approved_duration_dbus_property(self):
 
920
        return dbus.UInt64(self._timedelta_to_milliseconds(
 
921
                self.approved_duration))
 
922
    
 
923
    # name - property
 
924
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
925
    def name_dbus_property(self):
 
926
        return dbus.String(self.name)
 
927
    
 
928
    # fingerprint - property
 
929
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
930
    def fingerprint_dbus_property(self):
 
931
        return dbus.String(self.fingerprint)
 
932
    
 
933
    # host - property
 
934
    @dbus_service_property(_interface, signature=u"s",
 
935
                           access=u"readwrite")
 
936
    def host_dbus_property(self, value=None):
 
937
        if value is None:       # get
 
938
            return dbus.String(self.host)
 
939
        self.host = value
 
940
        # Emit D-Bus signal
 
941
        self.PropertyChanged(dbus.String(u"host"),
 
942
                             dbus.String(value, variant_level=1))
 
943
    
 
944
    # created - property
 
945
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
946
    def created_dbus_property(self):
 
947
        return dbus.String(self._datetime_to_dbus(self.created))
 
948
    
 
949
    # last_enabled - property
 
950
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
951
    def last_enabled_dbus_property(self):
 
952
        if self.last_enabled is None:
 
953
            return dbus.String(u"")
 
954
        return dbus.String(self._datetime_to_dbus(self.last_enabled))
 
955
    
 
956
    # enabled - property
 
957
    @dbus_service_property(_interface, signature=u"b",
 
958
                           access=u"readwrite")
 
959
    def enabled_dbus_property(self, value=None):
 
960
        if value is None:       # get
 
961
            return dbus.Boolean(self.enabled)
 
962
        if value:
 
963
            self.enable()
 
964
        else:
 
965
            self.disable()
 
966
    
 
967
    # last_checked_ok - property
 
968
    @dbus_service_property(_interface, signature=u"s",
 
969
                           access=u"readwrite")
 
970
    def last_checked_ok_dbus_property(self, value=None):
 
971
        if value is not None:
 
972
            self.checked_ok()
 
973
            return
 
974
        if self.last_checked_ok is None:
 
975
            return dbus.String(u"")
 
976
        return dbus.String(self._datetime_to_dbus(self
 
977
                                                  .last_checked_ok))
 
978
    
 
979
    # timeout - property
 
980
    @dbus_service_property(_interface, signature=u"t",
 
981
                           access=u"readwrite")
 
982
    def timeout_dbus_property(self, value=None):
 
983
        if value is None:       # get
 
984
            return dbus.UInt64(self.timeout_milliseconds())
 
985
        self.timeout = datetime.timedelta(0, 0, 0, value)
 
986
        # Emit D-Bus signal
 
987
        self.PropertyChanged(dbus.String(u"timeout"),
 
988
                             dbus.UInt64(value, variant_level=1))
 
989
        if getattr(self, u"disable_initiator_tag", None) is None:
 
990
            return
 
991
        # Reschedule timeout
 
992
        gobject.source_remove(self.disable_initiator_tag)
 
993
        self.disable_initiator_tag = None
 
994
        time_to_die = (self.
 
995
                       _timedelta_to_milliseconds((self
 
996
                                                   .last_checked_ok
 
997
                                                   + self.timeout)
 
998
                                                  - datetime.datetime
 
999
                                                  .utcnow()))
 
1000
        if time_to_die <= 0:
 
1001
            # The timeout has passed
 
1002
            self.disable()
 
1003
        else:
 
1004
            self.disable_initiator_tag = (gobject.timeout_add
 
1005
                                          (time_to_die, self.disable))
 
1006
    
 
1007
    # interval - property
 
1008
    @dbus_service_property(_interface, signature=u"t",
 
1009
                           access=u"readwrite")
 
1010
    def interval_dbus_property(self, value=None):
 
1011
        if value is None:       # get
 
1012
            return dbus.UInt64(self.interval_milliseconds())
 
1013
        self.interval = datetime.timedelta(0, 0, 0, value)
 
1014
        # Emit D-Bus signal
 
1015
        self.PropertyChanged(dbus.String(u"interval"),
 
1016
                             dbus.UInt64(value, variant_level=1))
 
1017
        if getattr(self, u"checker_initiator_tag", None) is None:
 
1018
            return
 
1019
        # Reschedule checker run
 
1020
        gobject.source_remove(self.checker_initiator_tag)
 
1021
        self.checker_initiator_tag = (gobject.timeout_add
 
1022
                                      (value, self.start_checker))
 
1023
        self.start_checker()    # Start one now, too
 
1024
 
 
1025
    # checker - property
 
1026
    @dbus_service_property(_interface, signature=u"s",
 
1027
                           access=u"readwrite")
 
1028
    def checker_dbus_property(self, value=None):
 
1029
        if value is None:       # get
 
1030
            return dbus.String(self.checker_command)
 
1031
        self.checker_command = value
 
1032
        # Emit D-Bus signal
 
1033
        self.PropertyChanged(dbus.String(u"checker"),
 
1034
                             dbus.String(self.checker_command,
 
1035
                                         variant_level=1))
 
1036
    
 
1037
    # checker_running - property
 
1038
    @dbus_service_property(_interface, signature=u"b",
 
1039
                           access=u"readwrite")
 
1040
    def checker_running_dbus_property(self, value=None):
 
1041
        if value is None:       # get
 
1042
            return dbus.Boolean(self.checker is not None)
 
1043
        if value:
 
1044
            self.start_checker()
 
1045
        else:
 
1046
            self.stop_checker()
 
1047
    
 
1048
    # object_path - property
 
1049
    @dbus_service_property(_interface, signature=u"o", access=u"read")
 
1050
    def object_path_dbus_property(self):
 
1051
        return self.dbus_object_path # is already a dbus.ObjectPath
 
1052
    
 
1053
    # secret = property
 
1054
    @dbus_service_property(_interface, signature=u"ay",
 
1055
                           access=u"write", byte_arrays=True)
 
1056
    def secret_dbus_property(self, value):
 
1057
        self.secret = str(value)
 
1058
    
747
1059
    del _interface
748
1060
 
749
1061
 
 
1062
class ProxyClient(object):
 
1063
    def __init__(self, child_pipe, fpr, address):
 
1064
        self._pipe = child_pipe
 
1065
        self._pipe.send(('init', fpr, address))
 
1066
        if not self._pipe.recv():
 
1067
            raise KeyError()
 
1068
 
 
1069
    def __getattribute__(self, name):
 
1070
        if(name == '_pipe'):
 
1071
            return super(ProxyClient, self).__getattribute__(name)
 
1072
        self._pipe.send(('getattr', name))
 
1073
        data = self._pipe.recv()
 
1074
        if data[0] == 'data':
 
1075
            return data[1]
 
1076
        if data[0] == 'function':
 
1077
            def func(*args, **kwargs):
 
1078
                self._pipe.send(('funcall', name, args, kwargs))
 
1079
                return self._pipe.recv()[1]
 
1080
            return func
 
1081
 
 
1082
    def __setattr__(self, name, value):
 
1083
        if(name == '_pipe'):
 
1084
            return super(ProxyClient, self).__setattr__(name, value)
 
1085
        self._pipe.send(('setattr', name, value))
 
1086
 
 
1087
 
750
1088
class ClientHandler(socketserver.BaseRequestHandler, object):
751
1089
    """A class to handle client connections.
752
1090
    
754
1092
    Note: This will run in its own forked process."""
755
1093
    
756
1094
    def handle(self):
757
 
        logger.info(u"TCP connection from: %s",
758
 
                    unicode(self.client_address))
759
 
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
760
 
        # Open IPC pipe to parent process
761
 
        with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
 
1095
        with contextlib.closing(self.server.child_pipe) as child_pipe:
 
1096
            logger.info(u"TCP connection from: %s",
 
1097
                        unicode(self.client_address))
 
1098
            logger.debug(u"Pipe FD: %d",
 
1099
                         self.server.child_pipe.fileno())
 
1100
 
762
1101
            session = (gnutls.connection
763
1102
                       .ClientSession(self.request,
764
1103
                                      gnutls.connection
765
1104
                                      .X509Credentials()))
766
 
            
767
 
            line = self.request.makefile().readline()
768
 
            logger.debug(u"Protocol version: %r", line)
769
 
            try:
770
 
                if int(line.strip().split()[0]) > 1:
771
 
                    raise RuntimeError
772
 
            except (ValueError, IndexError, RuntimeError), error:
773
 
                logger.error(u"Unknown protocol version: %s", error)
774
 
                return
775
 
            
 
1105
 
776
1106
            # Note: gnutls.connection.X509Credentials is really a
777
1107
            # generic GnuTLS certificate credentials object so long as
778
1108
            # no X.509 keys are added to it.  Therefore, we can use it
779
1109
            # here despite using OpenPGP certificates.
780
 
            
 
1110
 
781
1111
            #priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
782
1112
            #                      u"+AES-256-CBC", u"+SHA1",
783
1113
            #                      u"+COMP-NULL", u"+CTYPE-OPENPGP",
789
1119
            (gnutls.library.functions
790
1120
             .gnutls_priority_set_direct(session._c_object,
791
1121
                                         priority, None))
792
 
            
 
1122
 
 
1123
            # Start communication using the Mandos protocol
 
1124
            # Get protocol number
 
1125
            line = self.request.makefile().readline()
 
1126
            logger.debug(u"Protocol version: %r", line)
 
1127
            try:
 
1128
                if int(line.strip().split()[0]) > 1:
 
1129
                    raise RuntimeError
 
1130
            except (ValueError, IndexError, RuntimeError), error:
 
1131
                logger.error(u"Unknown protocol version: %s", error)
 
1132
                return
 
1133
 
 
1134
            # Start GnuTLS connection
793
1135
            try:
794
1136
                session.handshake()
795
1137
            except gnutls.errors.GNUTLSError, error:
798
1140
                # established.  Just abandon the request.
799
1141
                return
800
1142
            logger.debug(u"Handshake succeeded")
 
1143
 
 
1144
            approval_required = False
801
1145
            try:
802
 
                fpr = self.fingerprint(self.peer_certificate(session))
803
 
            except (TypeError, gnutls.errors.GNUTLSError), error:
804
 
                logger.warning(u"Bad certificate: %s", error)
805
 
                session.bye()
806
 
                return
807
 
            logger.debug(u"Fingerprint: %s", fpr)
 
1146
                try:
 
1147
                    fpr = self.fingerprint(self.peer_certificate
 
1148
                                           (session))
 
1149
                except (TypeError, gnutls.errors.GNUTLSError), error:
 
1150
                    logger.warning(u"Bad certificate: %s", error)
 
1151
                    return
 
1152
                logger.debug(u"Fingerprint: %s", fpr)
 
1153
 
 
1154
                try:
 
1155
                    client = ProxyClient(child_pipe, fpr,
 
1156
                                         self.client_address)
 
1157
                except KeyError:
 
1158
                    return
 
1159
                
 
1160
                if client.approved_delay:
 
1161
                    delay = client.approved_delay
 
1162
                    client.approvals_pending += 1
 
1163
                    approval_required = True
 
1164
                
 
1165
                while True:
 
1166
                    if not client.enabled:
 
1167
                        logger.warning(u"Client %s is disabled",
 
1168
                                       client.name)
 
1169
                        if self.server.use_dbus:
 
1170
                            # Emit D-Bus signal
 
1171
                            client.Rejected("Disabled")                    
 
1172
                        return
 
1173
                    
 
1174
                    if client._approved or not client.approved_delay:
 
1175
                        #We are approved or approval is disabled
 
1176
                        break
 
1177
                    elif client._approved is None:
 
1178
                        logger.info(u"Client %s need approval",
 
1179
                                    client.name)
 
1180
                        if self.server.use_dbus:
 
1181
                            # Emit D-Bus signal
 
1182
                            client.NeedApproval(
 
1183
                                client.approved_delay_milliseconds(),
 
1184
                                client.approved_by_default)
 
1185
                    else:
 
1186
                        logger.warning(u"Client %s was not approved",
 
1187
                                       client.name)
 
1188
                        if self.server.use_dbus:
 
1189
                            # Emit D-Bus signal
 
1190
                            client.Rejected("Disapproved")
 
1191
                        return
 
1192
                    
 
1193
                    #wait until timeout or approved
 
1194
                    #x = float(client._timedelta_to_milliseconds(delay))
 
1195
                    time = datetime.datetime.now()
 
1196
                    client.changedstate.acquire()
 
1197
                    client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
 
1198
                    client.changedstate.release()
 
1199
                    time2 = datetime.datetime.now()
 
1200
                    if (time2 - time) >= delay:
 
1201
                        if not client.approved_by_default:
 
1202
                            logger.warning("Client %s timed out while"
 
1203
                                           " waiting for approval",
 
1204
                                           client.name)
 
1205
                            if self.server.use_dbus:
 
1206
                                # Emit D-Bus signal
 
1207
                                client.Rejected("Time out")
 
1208
                            return
 
1209
                        else:
 
1210
                            break
 
1211
                    else:
 
1212
                        delay -= time2 - time
 
1213
                
 
1214
                sent_size = 0
 
1215
                while sent_size < len(client.secret):
 
1216
                    # XXX handle session exception
 
1217
                    sent = session.send(client.secret[sent_size:])
 
1218
                    logger.debug(u"Sent: %d, remaining: %d",
 
1219
                                 sent, len(client.secret)
 
1220
                                 - (sent_size + sent))
 
1221
                    sent_size += sent
 
1222
 
 
1223
                logger.info(u"Sending secret to %s", client.name)
 
1224
                # bump the timeout as if seen
 
1225
                client.checked_ok()
 
1226
                if self.server.use_dbus:
 
1227
                    # Emit D-Bus signal
 
1228
                    client.GotSecret()
808
1229
            
809
 
            for c in self.server.clients:
810
 
                if c.fingerprint == fpr:
811
 
                    client = c
812
 
                    break
813
 
            else:
814
 
                ipc.write(u"NOTFOUND %s %s\n"
815
 
                          % (fpr, unicode(self.client_address)))
816
 
                session.bye()
817
 
                return
818
 
            # Have to check if client.still_valid(), since it is
819
 
            # possible that the client timed out while establishing
820
 
            # the GnuTLS session.
821
 
            if not client.still_valid():
822
 
                ipc.write(u"INVALID %s\n" % client.name)
823
 
                session.bye()
824
 
                return
825
 
            ipc.write(u"SENDING %s\n" % client.name)
826
 
            sent_size = 0
827
 
            while sent_size < len(client.secret):
828
 
                sent = session.send(client.secret[sent_size:])
829
 
                logger.debug(u"Sent: %d, remaining: %d",
830
 
                             sent, len(client.secret)
831
 
                             - (sent_size + sent))
832
 
                sent_size += sent
833
 
            session.bye()
 
1230
            finally:
 
1231
                if approval_required:
 
1232
                    client.approvals_pending -= 1
 
1233
                session.bye()
834
1234
    
835
1235
    @staticmethod
836
1236
    def peer_certificate(session):
896
1296
        return hex_fpr
897
1297
 
898
1298
 
899
 
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
900
 
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
 
1299
class MultiprocessingMixIn(object):
 
1300
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
 
1301
    def sub_process_main(self, request, address):
 
1302
        try:
 
1303
            self.finish_request(request, address)
 
1304
        except:
 
1305
            self.handle_error(request, address)
 
1306
        self.close_request(request)
 
1307
            
 
1308
    def process_request(self, request, address):
 
1309
        """Start a new process to process the request."""
 
1310
        multiprocessing.Process(target = self.sub_process_main,
 
1311
                                args = (request, address)).start()
 
1312
 
 
1313
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
 
1314
    """ adds a pipe to the MixIn """
901
1315
    def process_request(self, request, client_address):
902
1316
        """Overrides and wraps the original process_request().
903
1317
        
904
1318
        This function creates a new pipe in self.pipe
905
1319
        """
906
 
        self.pipe = os.pipe()
907
 
        super(ForkingMixInWithPipe,
 
1320
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
 
1321
 
 
1322
        super(MultiprocessingMixInWithPipe,
908
1323
              self).process_request(request, client_address)
909
 
        os.close(self.pipe[1])  # close write end
910
 
        self.add_pipe(self.pipe[0])
911
 
    def add_pipe(self, pipe):
 
1324
        self.add_pipe(parent_pipe)
 
1325
    def add_pipe(self, parent_pipe):
912
1326
        """Dummy function; override as necessary"""
913
 
        os.close(pipe)
914
 
 
915
 
 
916
 
class IPv6_TCPServer(ForkingMixInWithPipe,
 
1327
        pass
 
1328
 
 
1329
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
917
1330
                     socketserver.TCPServer, object):
918
1331
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
919
1332
    
983
1396
        clients:        set of Client objects
984
1397
        gnutls_priority GnuTLS priority string
985
1398
        use_dbus:       Boolean; to emit D-Bus signals or not
986
 
        clients:        set of Client objects
987
 
        gnutls_priority GnuTLS priority string
988
 
        use_dbus:       Boolean; to emit D-Bus signals or not
989
1399
    
990
1400
    Assumes a gobject.MainLoop event loop.
991
1401
    """
1007
1417
            return socketserver.TCPServer.server_activate(self)
1008
1418
    def enable(self):
1009
1419
        self.enabled = True
1010
 
    def add_pipe(self, pipe):
 
1420
    def add_pipe(self, parent_pipe):
1011
1421
        # Call "handle_ipc" for both data and EOF events
1012
 
        gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1013
 
                             self.handle_ipc)
1014
 
    def handle_ipc(self, source, condition, file_objects={}):
 
1422
        gobject.io_add_watch(parent_pipe.fileno(),
 
1423
                             gobject.IO_IN | gobject.IO_HUP,
 
1424
                             functools.partial(self.handle_ipc,
 
1425
                                               parent_pipe = parent_pipe))
 
1426
        
 
1427
    def handle_ipc(self, source, condition, parent_pipe=None,
 
1428
                   client_object=None):
1015
1429
        condition_names = {
1016
1430
            gobject.IO_IN: u"IN",   # There is data to read.
1017
1431
            gobject.IO_OUT: u"OUT", # Data can be written (without
1029
1443
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1030
1444
                     conditions_string)
1031
1445
        
1032
 
        # Turn the pipe file descriptor into a Python file object
1033
 
        if source not in file_objects:
1034
 
            file_objects[source] = os.fdopen(source, u"r", 1)
 
1446
        # Read a request from the child
 
1447
        request = parent_pipe.recv()
 
1448
        command = request[0]
1035
1449
        
1036
 
        # Read a line from the file object
1037
 
        cmdline = file_objects[source].readline()
1038
 
        if not cmdline:             # Empty line means end of file
1039
 
            # close the IPC pipe
1040
 
            file_objects[source].close()
1041
 
            del file_objects[source]
1042
 
            
1043
 
            # Stop calling this function
 
1450
        if command == 'init':
 
1451
            fpr = request[1]
 
1452
            address = request[2]
 
1453
            
 
1454
            for c in self.clients:
 
1455
                if c.fingerprint == fpr:
 
1456
                    client = c
 
1457
                    break
 
1458
            else:
 
1459
                logger.warning(u"Client not found for fingerprint: %s, ad"
 
1460
                               u"dress: %s", fpr, address)
 
1461
                if self.use_dbus:
 
1462
                    # Emit D-Bus signal
 
1463
                    mandos_dbus_service.ClientNotFound(fpr, address)
 
1464
                parent_pipe.send(False)
 
1465
                return False
 
1466
            
 
1467
            gobject.io_add_watch(parent_pipe.fileno(),
 
1468
                                 gobject.IO_IN | gobject.IO_HUP,
 
1469
                                 functools.partial(self.handle_ipc,
 
1470
                                                   parent_pipe = parent_pipe,
 
1471
                                                   client_object = client))
 
1472
            parent_pipe.send(True)
 
1473
            # remove the old hook in favor of the new above hook on same fileno
1044
1474
            return False
1045
 
        
1046
 
        logger.debug(u"IPC command: %r", cmdline)
1047
 
        
1048
 
        # Parse and act on command
1049
 
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1050
 
        
1051
 
        if cmd == u"NOTFOUND":
1052
 
            logger.warning(u"Client not found for fingerprint: %s",
1053
 
                           args)
1054
 
            if self.use_dbus:
1055
 
                # Emit D-Bus signal
1056
 
                mandos_dbus_service.ClientNotFound(args)
1057
 
        elif cmd == u"INVALID":
1058
 
            for client in self.clients:
1059
 
                if client.name == args:
1060
 
                    logger.warning(u"Client %s is invalid", args)
1061
 
                    if self.use_dbus:
1062
 
                        # Emit D-Bus signal
1063
 
                        client.Rejected()
1064
 
                    break
1065
 
            else:
1066
 
                logger.error(u"Unknown client %s is invalid", args)
1067
 
        elif cmd == u"SENDING":
1068
 
            for client in self.clients:
1069
 
                if client.name == args:
1070
 
                    logger.info(u"Sending secret to %s", client.name)
1071
 
                    client.checked_ok()
1072
 
                    if self.use_dbus:
1073
 
                        # Emit D-Bus signal
1074
 
                        client.ReceivedSecret()
1075
 
                    break
1076
 
            else:
1077
 
                logger.error(u"Sending secret to unknown client %s",
1078
 
                             args)
1079
 
        else:
1080
 
            logger.error(u"Unknown IPC command: %r", cmdline)
1081
 
        
1082
 
        # Keep calling this function
 
1475
        if command == 'funcall':
 
1476
            funcname = request[1]
 
1477
            args = request[2]
 
1478
            kwargs = request[3]
 
1479
            
 
1480
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
 
1481
 
 
1482
        if command == 'getattr':
 
1483
            attrname = request[1]
 
1484
            if callable(client_object.__getattribute__(attrname)):
 
1485
                parent_pipe.send(('function',))
 
1486
            else:
 
1487
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
 
1488
 
 
1489
        if command == 'setattr':
 
1490
            attrname = request[1]
 
1491
            value = request[2]
 
1492
            setattr(client_object, attrname, value)
 
1493
            
1083
1494
        return True
1084
1495
 
1085
1496
 
1115
1526
            elif suffix == u"w":
1116
1527
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1117
1528
            else:
1118
 
                raise ValueError
1119
 
        except (ValueError, IndexError):
1120
 
            raise ValueError
 
1529
                raise ValueError(u"Unknown suffix %r" % suffix)
 
1530
        except (ValueError, IndexError), e:
 
1531
            raise ValueError(e.message)
1121
1532
        timevalue += delta
1122
1533
    return timevalue
1123
1534
 
1136
1547
        def if_nametoindex(interface):
1137
1548
            "Get an interface index the hard way, i.e. using fcntl()"
1138
1549
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1139
 
            with closing(socket.socket()) as s:
 
1550
            with contextlib.closing(socket.socket()) as s:
1140
1551
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1141
1552
                                    struct.pack(str(u"16s16x"),
1142
1553
                                                interface))
1162
1573
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1163
1574
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1164
1575
            raise OSError(errno.ENODEV,
1165
 
                          u"/dev/null not a character device")
 
1576
                          u"%s not a character device"
 
1577
                          % os.path.devnull)
1166
1578
        os.dup2(null, sys.stdin.fileno())
1167
1579
        os.dup2(null, sys.stdout.fileno())
1168
1580
        os.dup2(null, sys.stderr.fileno())
1172
1584
 
1173
1585
def main():
1174
1586
    
1175
 
    ######################################################################
 
1587
    ##################################################################
1176
1588
    # Parsing of options, both command line and config file
1177
1589
    
1178
1590
    parser = optparse.OptionParser(version = "%%prog %s" % version)
1272
1684
                        u"interval": u"5m",
1273
1685
                        u"checker": u"fping -q -- %%(host)s",
1274
1686
                        u"host": u"",
 
1687
                        u"approved_delay": u"5m",
 
1688
                        u"approved_duration": u"1s",
1275
1689
                        }
1276
1690
    client_config = configparser.SafeConfigParser(client_defaults)
1277
1691
    client_config.read(os.path.join(server_settings[u"configdir"],
1335
1749
    bus = dbus.SystemBus()
1336
1750
    # End of Avahi example code
1337
1751
    if use_dbus:
1338
 
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
 
1752
        try:
 
1753
            bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
 
1754
                                            bus, do_not_queue=True)
 
1755
        except dbus.exceptions.NameExistsException, e:
 
1756
            logger.error(unicode(e) + u", disabling D-Bus")
 
1757
            use_dbus = False
 
1758
            server_settings[u"use_dbus"] = False
 
1759
            tcp_server.use_dbus = False
1339
1760
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1340
1761
    service = AvahiService(name = server_settings[u"servicename"],
1341
1762
                           servicetype = u"_mandos._tcp",
1347
1768
    client_class = Client
1348
1769
    if use_dbus:
1349
1770
        client_class = functools.partial(ClientDBus, bus = bus)
 
1771
    def client_config_items(config, section):
 
1772
        special_settings = {
 
1773
            "approved_by_default":
 
1774
                lambda: config.getboolean(section,
 
1775
                                          "approved_by_default"),
 
1776
            }
 
1777
        for name, value in config.items(section):
 
1778
            try:
 
1779
                yield (name, special_settings[name]())
 
1780
            except KeyError:
 
1781
                yield (name, value)
 
1782
    
1350
1783
    tcp_server.clients.update(set(
1351
1784
            client_class(name = section,
1352
 
                         config= dict(client_config.items(section)))
 
1785
                         config= dict(client_config_items(
 
1786
                        client_config, section)))
1353
1787
            for section in client_config.sections()))
1354
1788
    if not tcp_server.clients:
1355
1789
        logger.warning(u"No clients defined")
1367
1801
        daemon()
1368
1802
    
1369
1803
    try:
1370
 
        with closing(pidfile):
 
1804
        with pidfile:
1371
1805
            pid = os.getpid()
1372
1806
            pidfile.write(str(pid) + "\n")
1373
1807
        del pidfile
1379
1813
        pass
1380
1814
    del pidfilename
1381
1815
    
1382
 
    def cleanup():
1383
 
        "Cleanup function; run on exit"
1384
 
        service.cleanup()
1385
 
        
1386
 
        while tcp_server.clients:
1387
 
            client = tcp_server.clients.pop()
1388
 
            client.disable_hook = None
1389
 
            client.disable()
1390
 
    
1391
 
    atexit.register(cleanup)
1392
 
    
1393
1816
    if not debug:
1394
1817
        signal.signal(signal.SIGINT, signal.SIG_IGN)
1395
1818
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1402
1825
                dbus.service.Object.__init__(self, bus, u"/")
1403
1826
            _interface = u"se.bsnet.fukt.Mandos"
1404
1827
            
1405
 
            @dbus.service.signal(_interface, signature=u"oa{sv}")
1406
 
            def ClientAdded(self, objpath, properties):
 
1828
            @dbus.service.signal(_interface, signature=u"o")
 
1829
            def ClientAdded(self, objpath):
1407
1830
                "D-Bus signal"
1408
1831
                pass
1409
1832
            
1410
 
            @dbus.service.signal(_interface, signature=u"s")
1411
 
            def ClientNotFound(self, fingerprint):
 
1833
            @dbus.service.signal(_interface, signature=u"ss")
 
1834
            def ClientNotFound(self, fingerprint, address):
1412
1835
                "D-Bus signal"
1413
1836
                pass
1414
1837
            
1428
1851
            def GetAllClientsWithProperties(self):
1429
1852
                "D-Bus method"
1430
1853
                return dbus.Dictionary(
1431
 
                    ((c.dbus_object_path, c.GetAllProperties())
 
1854
                    ((c.dbus_object_path, c.GetAll(u""))
1432
1855
                     for c in tcp_server.clients),
1433
1856
                    signature=u"oa{sv}")
1434
1857
            
1440
1863
                        tcp_server.clients.remove(c)
1441
1864
                        c.remove_from_connection()
1442
1865
                        # Don't signal anything except ClientRemoved
1443
 
                        c.disable(signal=False)
 
1866
                        c.disable(quiet=True)
1444
1867
                        # Emit D-Bus signal
1445
1868
                        self.ClientRemoved(object_path, c.name)
1446
1869
                        return
1447
 
                raise KeyError
 
1870
                raise KeyError(object_path)
1448
1871
            
1449
1872
            del _interface
1450
1873
        
1451
1874
        mandos_dbus_service = MandosDBusService()
1452
1875
    
 
1876
    def cleanup():
 
1877
        "Cleanup function; run on exit"
 
1878
        service.cleanup()
 
1879
        
 
1880
        while tcp_server.clients:
 
1881
            client = tcp_server.clients.pop()
 
1882
            if use_dbus:
 
1883
                client.remove_from_connection()
 
1884
            client.disable_hook = None
 
1885
            # Don't signal anything except ClientRemoved
 
1886
            client.disable(quiet=True)
 
1887
            if use_dbus:
 
1888
                # Emit D-Bus signal
 
1889
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
 
1890
                                                  client.name)
 
1891
    
 
1892
    atexit.register(cleanup)
 
1893
    
1453
1894
    for client in tcp_server.clients:
1454
1895
        if use_dbus:
1455
1896
            # Emit D-Bus signal
1456
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
1457
 
                                            client.GetAllProperties())
 
1897
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
1458
1898
        client.enable()
1459
1899
    
1460
1900
    tcp_server.enable()
1478
1918
            service.activate()
1479
1919
        except dbus.exceptions.DBusException, error:
1480
1920
            logger.critical(u"DBusException: %s", error)
 
1921
            cleanup()
1481
1922
            sys.exit(1)
1482
1923
        # End of Avahi example code
1483
1924
        
1490
1931
        main_loop.run()
1491
1932
    except AvahiError, error:
1492
1933
        logger.critical(u"AvahiError: %s", error)
 
1934
        cleanup()
1493
1935
        sys.exit(1)
1494
1936
    except KeyboardInterrupt:
1495
1937
        if debug:
1496
1938
            print >> sys.stderr
1497
1939
        logger.debug(u"Server received KeyboardInterrupt")
1498
1940
    logger.debug(u"Server exiting")
 
1941
    # Must run before the D-Bus bus name gets deregistered
 
1942
    cleanup()
1499
1943
 
1500
1944
if __name__ == '__main__':
1501
1945
    main()