/mandos/release

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Björn Påhlsson
  • Date: 2010-09-01 18:03:03 UTC
  • mto: (237.7.1 mandos)
  • mto: This revision was merged to the branch mainline in revision 270.
  • Revision ID: belorn@fukt.bsnet.se-20100901180303-u47edb73fczu56ob
bug fixes that prevent problems when runing server as root

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