/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

Changed ForkingMixIn in favor of multiprocessing
Added approval functionallity

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
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(
224
231
        self.server_state_changed(self.server.GetState())
225
232
 
226
233
 
 
234
# XXX Need to add:
 
235
# approved_by_default (Config option for each client)
 
236
# approved_delay (config option for each client)
 
237
# approved_duration (config option for each client)
227
238
class Client(object):
228
239
    """A representation of a client host served by this server.
229
240
    
239
250
    enabled:    bool()
240
251
    last_checked_ok: datetime.datetime(); (UTC) or None
241
252
    timeout:    datetime.timedelta(); How long from last_checked_ok
242
 
                                      until this client is invalid
 
253
                                      until this client is disabled
243
254
    interval:   datetime.timedelta(); How often to start a new checker
244
255
    disable_hook:  If set, called by disable() as disable_hook(self)
245
256
    checker:    subprocess.Popen(); a running checker process used
246
257
                                    to see if the client lives.
247
258
                                    'None' if no process is running.
248
259
    checker_initiator_tag: a gobject event source tag, or None
249
 
    disable_initiator_tag:    - '' -
 
260
    disable_initiator_tag: - '' -
250
261
    checker_callback_tag:  - '' -
251
262
    checker_command: string; External command which is run to check if
252
263
                     client lives.  %() expansions are done at
253
264
                     runtime with vars(self) as dict, so that for
254
265
                     instance %(name)s can be used in the command.
255
266
    current_checker_command: string; current running checker_command
 
267
    approved_delay: datetime.timedelta(); Time to wait for approval
 
268
    _approved:   bool(); 'None' if not yet approved/disapproved
 
269
    approved_duration: datetime.timedelta(); Duration of one approval
256
270
    """
257
271
    
258
272
    @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))
 
273
    def _timedelta_to_milliseconds(td):
 
274
        "Convert a datetime.timedelta() to milliseconds"
 
275
        return ((td.days * 24 * 60 * 60 * 1000)
 
276
                + (td.seconds * 1000)
 
277
                + (td.microseconds // 1000))
264
278
    
265
279
    def timeout_milliseconds(self):
266
280
        "Return the 'timeout' attribute in milliseconds"
267
 
        return self._datetime_to_milliseconds(self.timeout)
 
281
        return self._timedelta_to_milliseconds(self.timeout)
268
282
    
269
283
    def interval_milliseconds(self):
270
284
        "Return the 'interval' attribute in milliseconds"
271
 
        return self._datetime_to_milliseconds(self.interval)
 
285
        return self._timedelta_to_milliseconds(self.interval)
 
286
 
 
287
    def approved_delay_milliseconds(self):
 
288
        return self._timedelta_to_milliseconds(self.approved_delay)
272
289
    
273
290
    def __init__(self, name = None, disable_hook=None, config=None):
274
291
        """Note: the 'checker' key in 'config' sets the
287
304
        if u"secret" in config:
288
305
            self.secret = config[u"secret"].decode(u"base64")
289
306
        elif u"secfile" in config:
290
 
            with closing(open(os.path.expanduser
291
 
                              (os.path.expandvars
292
 
                               (config[u"secfile"])))) as secfile:
 
307
            with open(os.path.expanduser(os.path.expandvars
 
308
                                         (config[u"secfile"])),
 
309
                      "rb") as secfile:
293
310
                self.secret = secfile.read()
294
311
        else:
 
312
            #XXX Need to allow secret on demand!
295
313
            raise TypeError(u"No secret or secfile for client %s"
296
314
                            % self.name)
297
315
        self.host = config.get(u"host", u"")
309
327
        self.checker_command = config[u"checker"]
310
328
        self.current_checker_command = None
311
329
        self.last_connect = None
312
 
    
 
330
        self._approved = None
 
331
        self.approved_by_default = config.get(u"approved_by_default",
 
332
                                              False)
 
333
        self.approved_delay = string_to_delta(
 
334
            config[u"approved_delay"])
 
335
        self.approved_duration = string_to_delta(
 
336
            config[u"approved_duration"])
 
337
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
 
338
 
 
339
    def send_changedstate(self):
 
340
        self.changedstate.acquire()
 
341
        self.changedstate.notify_all()
 
342
        self.changedstate.release()
 
343
        
313
344
    def enable(self):
314
345
        """Start this client's checker and timeout hooks"""
315
346
        if getattr(self, u"enabled", False):
316
347
            # Already enabled
317
348
            return
 
349
        self.send_changedstate()
318
350
        self.last_enabled = datetime.datetime.utcnow()
319
351
        # Schedule a new checker to be started an 'interval' from now,
320
352
        # and every interval from then on.
321
353
        self.checker_initiator_tag = (gobject.timeout_add
322
354
                                      (self.interval_milliseconds(),
323
355
                                       self.start_checker))
324
 
        # Also start a new checker *right now*.
325
 
        self.start_checker()
326
356
        # Schedule a disable() when 'timeout' has passed
327
357
        self.disable_initiator_tag = (gobject.timeout_add
328
358
                                   (self.timeout_milliseconds(),
329
359
                                    self.disable))
330
360
        self.enabled = True
 
361
        # Also start a new checker *right now*.
 
362
        self.start_checker()
331
363
    
332
 
    def disable(self):
 
364
    def disable(self, quiet=True):
333
365
        """Disable this client."""
334
366
        if not getattr(self, "enabled", False):
335
367
            return False
336
 
        logger.info(u"Disabling client %s", self.name)
 
368
        if not quiet:
 
369
            self.send_changedstate()
 
370
        if not quiet:
 
371
            logger.info(u"Disabling client %s", self.name)
337
372
        if getattr(self, u"disable_initiator_tag", False):
338
373
            gobject.source_remove(self.disable_initiator_tag)
339
374
            self.disable_initiator_tag = None
391
426
        # client would inevitably timeout, since no checker would get
392
427
        # a chance to run to completion.  If we instead leave running
393
428
        # 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.
 
429
        # than 'timeout' for the client to be disabled, which is as it
 
430
        # should be.
396
431
        
397
432
        # If a checker exists, make sure it is not a zombie
398
 
        if self.checker is not None:
 
433
        try:
399
434
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
435
        except (AttributeError, OSError), error:
 
436
            if (isinstance(error, OSError)
 
437
                and error.errno != errno.ECHILD):
 
438
                raise error
 
439
        else:
400
440
            if pid:
401
441
                logger.warning(u"Checker was a zombie")
402
442
                gobject.source_remove(self.checker_callback_tag)
458
498
        logger.debug(u"Stopping checker for %(name)s", vars(self))
459
499
        try:
460
500
            os.kill(self.checker.pid, signal.SIGTERM)
461
 
            #os.sleep(0.5)
 
501
            #time.sleep(0.5)
462
502
            #if self.checker.poll() is None:
463
503
            #    os.kill(self.checker.pid, signal.SIGKILL)
464
504
        except OSError, error:
465
505
            if error.errno != errno.ESRCH: # No such process
466
506
                raise
467
507
        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):
 
508
 
 
509
def dbus_service_property(dbus_interface, signature=u"v",
 
510
                          access=u"readwrite", byte_arrays=False):
 
511
    """Decorators for marking methods of a DBusObjectWithProperties to
 
512
    become properties on the D-Bus.
 
513
    
 
514
    The decorated method will be called with no arguments by "Get"
 
515
    and with one argument by "Set".
 
516
    
 
517
    The parameters, where they are supported, are the same as
 
518
    dbus.service.method, except there is only "signature", since the
 
519
    type from Get() and the type sent to Set() is the same.
 
520
    """
 
521
    # Encoding deeply encoded byte arrays is not supported yet by the
 
522
    # "Set" method, so we fail early here:
 
523
    if byte_arrays and signature != u"ay":
 
524
        raise ValueError(u"Byte arrays not supported for non-'ay'"
 
525
                         u" signature %r" % signature)
 
526
    def decorator(func):
 
527
        func._dbus_is_property = True
 
528
        func._dbus_interface = dbus_interface
 
529
        func._dbus_signature = signature
 
530
        func._dbus_access = access
 
531
        func._dbus_name = func.__name__
 
532
        if func._dbus_name.endswith(u"_dbus_property"):
 
533
            func._dbus_name = func._dbus_name[:-14]
 
534
        func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
 
535
        return func
 
536
    return decorator
 
537
 
 
538
 
 
539
class DBusPropertyException(dbus.exceptions.DBusException):
 
540
    """A base class for D-Bus property-related exceptions
 
541
    """
 
542
    def __unicode__(self):
 
543
        return unicode(str(self))
 
544
 
 
545
 
 
546
class DBusPropertyAccessException(DBusPropertyException):
 
547
    """A property's access permissions disallows an operation.
 
548
    """
 
549
    pass
 
550
 
 
551
 
 
552
class DBusPropertyNotFound(DBusPropertyException):
 
553
    """An attempt was made to access a non-existing property.
 
554
    """
 
555
    pass
 
556
 
 
557
 
 
558
class DBusObjectWithProperties(dbus.service.Object):
 
559
    """A D-Bus object with properties.
 
560
 
 
561
    Classes inheriting from this can use the dbus_service_property
 
562
    decorator to expose methods as D-Bus properties.  It exposes the
 
563
    standard Get(), Set(), and GetAll() methods on the D-Bus.
 
564
    """
 
565
    
 
566
    @staticmethod
 
567
    def _is_dbus_property(obj):
 
568
        return getattr(obj, u"_dbus_is_property", False)
 
569
    
 
570
    def _get_all_dbus_properties(self):
 
571
        """Returns a generator of (name, attribute) pairs
 
572
        """
 
573
        return ((prop._dbus_name, prop)
 
574
                for name, prop in
 
575
                inspect.getmembers(self, self._is_dbus_property))
 
576
    
 
577
    def _get_dbus_property(self, interface_name, property_name):
 
578
        """Returns a bound method if one exists which is a D-Bus
 
579
        property with the specified name and interface.
 
580
        """
 
581
        for name in (property_name,
 
582
                     property_name + u"_dbus_property"):
 
583
            prop = getattr(self, name, None)
 
584
            if (prop is None
 
585
                or not self._is_dbus_property(prop)
 
586
                or prop._dbus_name != property_name
 
587
                or (interface_name and prop._dbus_interface
 
588
                    and interface_name != prop._dbus_interface)):
 
589
                continue
 
590
            return prop
 
591
        # No such property
 
592
        raise DBusPropertyNotFound(self.dbus_object_path + u":"
 
593
                                   + interface_name + u"."
 
594
                                   + property_name)
 
595
    
 
596
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
 
597
                         out_signature=u"v")
 
598
    def Get(self, interface_name, property_name):
 
599
        """Standard D-Bus property Get() method, see D-Bus standard.
 
600
        """
 
601
        prop = self._get_dbus_property(interface_name, property_name)
 
602
        if prop._dbus_access == u"write":
 
603
            raise DBusPropertyAccessException(property_name)
 
604
        value = prop()
 
605
        if not hasattr(value, u"variant_level"):
 
606
            return value
 
607
        return type(value)(value, variant_level=value.variant_level+1)
 
608
    
 
609
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
 
610
    def Set(self, interface_name, property_name, value):
 
611
        """Standard D-Bus property Set() method, see D-Bus standard.
 
612
        """
 
613
        prop = self._get_dbus_property(interface_name, property_name)
 
614
        if prop._dbus_access == u"read":
 
615
            raise DBusPropertyAccessException(property_name)
 
616
        if prop._dbus_get_args_options[u"byte_arrays"]:
 
617
            # The byte_arrays option is not supported yet on
 
618
            # signatures other than "ay".
 
619
            if prop._dbus_signature != u"ay":
 
620
                raise ValueError
 
621
            value = dbus.ByteArray(''.join(unichr(byte)
 
622
                                           for byte in value))
 
623
        prop(value)
 
624
    
 
625
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
 
626
                         out_signature=u"a{sv}")
 
627
    def GetAll(self, interface_name):
 
628
        """Standard D-Bus property GetAll() method, see D-Bus
 
629
        standard.
 
630
 
 
631
        Note: Will not include properties with access="write".
 
632
        """
 
633
        all = {}
 
634
        for name, prop in self._get_all_dbus_properties():
 
635
            if (interface_name
 
636
                and interface_name != prop._dbus_interface):
 
637
                # Interface non-empty but did not match
 
638
                continue
 
639
            # Ignore write-only properties
 
640
            if prop._dbus_access == u"write":
 
641
                continue
 
642
            value = prop()
 
643
            if not hasattr(value, u"variant_level"):
 
644
                all[name] = value
 
645
                continue
 
646
            all[name] = type(value)(value, variant_level=
 
647
                                    value.variant_level+1)
 
648
        return dbus.Dictionary(all, signature=u"sv")
 
649
    
 
650
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
 
651
                         out_signature=u"s",
 
652
                         path_keyword='object_path',
 
653
                         connection_keyword='connection')
 
654
    def Introspect(self, object_path, connection):
 
655
        """Standard D-Bus method, overloaded to insert property tags.
 
656
        """
 
657
        xmlstring = dbus.service.Object.Introspect(self, object_path,
 
658
                                                   connection)
 
659
        try:
 
660
            document = xml.dom.minidom.parseString(xmlstring)
 
661
            def make_tag(document, name, prop):
 
662
                e = document.createElement(u"property")
 
663
                e.setAttribute(u"name", name)
 
664
                e.setAttribute(u"type", prop._dbus_signature)
 
665
                e.setAttribute(u"access", prop._dbus_access)
 
666
                return e
 
667
            for if_tag in document.getElementsByTagName(u"interface"):
 
668
                for tag in (make_tag(document, name, prop)
 
669
                            for name, prop
 
670
                            in self._get_all_dbus_properties()
 
671
                            if prop._dbus_interface
 
672
                            == if_tag.getAttribute(u"name")):
 
673
                    if_tag.appendChild(tag)
 
674
                # Add the names to the return values for the
 
675
                # "org.freedesktop.DBus.Properties" methods
 
676
                if (if_tag.getAttribute(u"name")
 
677
                    == u"org.freedesktop.DBus.Properties"):
 
678
                    for cn in if_tag.getElementsByTagName(u"method"):
 
679
                        if cn.getAttribute(u"name") == u"Get":
 
680
                            for arg in cn.getElementsByTagName(u"arg"):
 
681
                                if (arg.getAttribute(u"direction")
 
682
                                    == u"out"):
 
683
                                    arg.setAttribute(u"name", u"value")
 
684
                        elif cn.getAttribute(u"name") == u"GetAll":
 
685
                            for arg in cn.getElementsByTagName(u"arg"):
 
686
                                if (arg.getAttribute(u"direction")
 
687
                                    == u"out"):
 
688
                                    arg.setAttribute(u"name", u"props")
 
689
            xmlstring = document.toxml(u"utf-8")
 
690
            document.unlink()
 
691
        except (AttributeError, xml.dom.DOMException,
 
692
                xml.parsers.expat.ExpatError), error:
 
693
            logger.error(u"Failed to override Introspection method",
 
694
                         error)
 
695
        return xmlstring
 
696
 
 
697
 
 
698
class ClientDBus(Client, DBusObjectWithProperties):
481
699
    """A Client class using D-Bus
482
700
    
483
701
    Attributes:
494
712
        self.dbus_object_path = (dbus.ObjectPath
495
713
                                 (u"/clients/"
496
714
                                  + self.name.replace(u".", u"_")))
497
 
        dbus.service.Object.__init__(self, self.bus,
498
 
                                     self.dbus_object_path)
 
715
        DBusObjectWithProperties.__init__(self, self.bus,
 
716
                                          self.dbus_object_path)
499
717
    
500
718
    @staticmethod
501
719
    def _datetime_to_dbus(dt, variant_level=0):
516
734
                                       variant_level=1))
517
735
        return r
518
736
    
519
 
    def disable(self, signal = True):
 
737
    def disable(self, quiet = False):
520
738
        oldstate = getattr(self, u"enabled", False)
521
 
        r = Client.disable(self)
522
 
        if signal and oldstate != self.enabled:
 
739
        r = Client.disable(self, quiet=quiet)
 
740
        if not quiet and oldstate != self.enabled:
523
741
            # Emit D-Bus signal
524
742
            self.PropertyChanged(dbus.String(u"enabled"),
525
743
                                 dbus.Boolean(False, variant_level=1))
530
748
            self.remove_from_connection()
531
749
        except LookupError:
532
750
            pass
533
 
        if hasattr(dbus.service.Object, u"__del__"):
534
 
            dbus.service.Object.__del__(self, *args, **kwargs)
 
751
        if hasattr(DBusObjectWithProperties, u"__del__"):
 
752
            DBusObjectWithProperties.__del__(self, *args, **kwargs)
535
753
        Client.__del__(self, *args, **kwargs)
536
754
    
537
755
    def checker_callback(self, pid, condition, command,
590
808
            self.PropertyChanged(dbus.String(u"checker_running"),
591
809
                                 dbus.Boolean(False, variant_level=1))
592
810
        return r
593
 
    
594
 
    ## D-Bus methods & signals
 
811
 
 
812
    def _reset_approved(self):
 
813
        self._approved = None
 
814
        return False
 
815
    
 
816
    def approve(self, value=True):
 
817
        self._approved = value
 
818
        gobject.timeout_add(self._timedelta_to_milliseconds(self.approved_duration, self._reset_approved))
 
819
    
 
820
    ## D-Bus methods, signals & properties
595
821
    _interface = u"se.bsnet.fukt.Mandos.Client"
596
822
    
597
 
    # CheckedOK - method
598
 
    @dbus.service.method(_interface)
599
 
    def CheckedOK(self):
600
 
        return self.checked_ok()
 
823
    ## Signals
601
824
    
602
825
    # CheckerCompleted - signal
603
826
    @dbus.service.signal(_interface, signature=u"nxs")
611
834
        "D-Bus signal"
612
835
        pass
613
836
    
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
837
    # PropertyChanged - signal
663
838
    @dbus.service.signal(_interface, signature=u"sv")
664
839
    def PropertyChanged(self, property, value):
665
840
        "D-Bus signal"
666
841
        pass
667
842
    
668
 
    # ReceivedSecret - signal
 
843
    # GotSecret - signal
669
844
    @dbus.service.signal(_interface)
670
 
    def ReceivedSecret(self):
 
845
    def GotSecret(self):
671
846
        "D-Bus signal"
672
847
        pass
673
848
    
674
849
    # 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)))
 
850
    @dbus.service.signal(_interface, signature=u"s")
 
851
    def Rejected(self, reason):
 
852
        "D-Bus signal"
 
853
        pass
 
854
    
 
855
    # NeedApproval - signal
 
856
    @dbus.service.signal(_interface, signature=u"db")
 
857
    def NeedApproval(self, timeout, default):
 
858
        "D-Bus signal"
 
859
        pass
 
860
    
 
861
    ## Methods
 
862
 
 
863
    # Approve - method
 
864
    @dbus.service.method(_interface, in_signature=u"b")
 
865
    def Approve(self, value):
 
866
        self.approve(value)
 
867
 
 
868
    # CheckedOK - method
 
869
    @dbus.service.method(_interface)
 
870
    def CheckedOK(self):
 
871
        return self.checked_ok()
723
872
    
724
873
    # Enable - method
725
874
    @dbus.service.method(_interface)
744
893
    def StopChecker(self):
745
894
        self.stop_checker()
746
895
    
 
896
    ## Properties
 
897
    
 
898
    # xxx 3 new properties
 
899
    
 
900
    # name - property
 
901
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
902
    def name_dbus_property(self):
 
903
        return dbus.String(self.name)
 
904
    
 
905
    # fingerprint - property
 
906
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
907
    def fingerprint_dbus_property(self):
 
908
        return dbus.String(self.fingerprint)
 
909
    
 
910
    # host - property
 
911
    @dbus_service_property(_interface, signature=u"s",
 
912
                           access=u"readwrite")
 
913
    def host_dbus_property(self, value=None):
 
914
        if value is None:       # get
 
915
            return dbus.String(self.host)
 
916
        self.host = value
 
917
        # Emit D-Bus signal
 
918
        self.PropertyChanged(dbus.String(u"host"),
 
919
                             dbus.String(value, variant_level=1))
 
920
    
 
921
    # created - property
 
922
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
923
    def created_dbus_property(self):
 
924
        return dbus.String(self._datetime_to_dbus(self.created))
 
925
    
 
926
    # last_enabled - property
 
927
    @dbus_service_property(_interface, signature=u"s", access=u"read")
 
928
    def last_enabled_dbus_property(self):
 
929
        if self.last_enabled is None:
 
930
            return dbus.String(u"")
 
931
        return dbus.String(self._datetime_to_dbus(self.last_enabled))
 
932
    
 
933
    # enabled - property
 
934
    @dbus_service_property(_interface, signature=u"b",
 
935
                           access=u"readwrite")
 
936
    def enabled_dbus_property(self, value=None):
 
937
        if value is None:       # get
 
938
            return dbus.Boolean(self.enabled)
 
939
        if value:
 
940
            self.enable()
 
941
        else:
 
942
            self.disable()
 
943
    
 
944
    # last_checked_ok - property
 
945
    @dbus_service_property(_interface, signature=u"s",
 
946
                           access=u"readwrite")
 
947
    def last_checked_ok_dbus_property(self, value=None):
 
948
        if value is not None:
 
949
            self.checked_ok()
 
950
            return
 
951
        if self.last_checked_ok is None:
 
952
            return dbus.String(u"")
 
953
        return dbus.String(self._datetime_to_dbus(self
 
954
                                                  .last_checked_ok))
 
955
    
 
956
    # timeout - property
 
957
    @dbus_service_property(_interface, signature=u"t",
 
958
                           access=u"readwrite")
 
959
    def timeout_dbus_property(self, value=None):
 
960
        if value is None:       # get
 
961
            return dbus.UInt64(self.timeout_milliseconds())
 
962
        self.timeout = datetime.timedelta(0, 0, 0, value)
 
963
        # Emit D-Bus signal
 
964
        self.PropertyChanged(dbus.String(u"timeout"),
 
965
                             dbus.UInt64(value, variant_level=1))
 
966
        if getattr(self, u"disable_initiator_tag", None) is None:
 
967
            return
 
968
        # Reschedule timeout
 
969
        gobject.source_remove(self.disable_initiator_tag)
 
970
        self.disable_initiator_tag = None
 
971
        time_to_die = (self.
 
972
                       _timedelta_to_milliseconds((self
 
973
                                                   .last_checked_ok
 
974
                                                   + self.timeout)
 
975
                                                  - datetime.datetime
 
976
                                                  .utcnow()))
 
977
        if time_to_die <= 0:
 
978
            # The timeout has passed
 
979
            self.disable()
 
980
        else:
 
981
            self.disable_initiator_tag = (gobject.timeout_add
 
982
                                          (time_to_die, self.disable))
 
983
    
 
984
    # interval - property
 
985
    @dbus_service_property(_interface, signature=u"t",
 
986
                           access=u"readwrite")
 
987
    def interval_dbus_property(self, value=None):
 
988
        if value is None:       # get
 
989
            return dbus.UInt64(self.interval_milliseconds())
 
990
        self.interval = datetime.timedelta(0, 0, 0, value)
 
991
        # Emit D-Bus signal
 
992
        self.PropertyChanged(dbus.String(u"interval"),
 
993
                             dbus.UInt64(value, variant_level=1))
 
994
        if getattr(self, u"checker_initiator_tag", None) is None:
 
995
            return
 
996
        # Reschedule checker run
 
997
        gobject.source_remove(self.checker_initiator_tag)
 
998
        self.checker_initiator_tag = (gobject.timeout_add
 
999
                                      (value, self.start_checker))
 
1000
        self.start_checker()    # Start one now, too
 
1001
 
 
1002
    # checker - property
 
1003
    @dbus_service_property(_interface, signature=u"s",
 
1004
                           access=u"readwrite")
 
1005
    def checker_dbus_property(self, value=None):
 
1006
        if value is None:       # get
 
1007
            return dbus.String(self.checker_command)
 
1008
        self.checker_command = value
 
1009
        # Emit D-Bus signal
 
1010
        self.PropertyChanged(dbus.String(u"checker"),
 
1011
                             dbus.String(self.checker_command,
 
1012
                                         variant_level=1))
 
1013
    
 
1014
    # checker_running - property
 
1015
    @dbus_service_property(_interface, signature=u"b",
 
1016
                           access=u"readwrite")
 
1017
    def checker_running_dbus_property(self, value=None):
 
1018
        if value is None:       # get
 
1019
            return dbus.Boolean(self.checker is not None)
 
1020
        if value:
 
1021
            self.start_checker()
 
1022
        else:
 
1023
            self.stop_checker()
 
1024
    
 
1025
    # object_path - property
 
1026
    @dbus_service_property(_interface, signature=u"o", access=u"read")
 
1027
    def object_path_dbus_property(self):
 
1028
        return self.dbus_object_path # is already a dbus.ObjectPath
 
1029
    
 
1030
    # secret = property
 
1031
    @dbus_service_property(_interface, signature=u"ay",
 
1032
                           access=u"write", byte_arrays=True)
 
1033
    def secret_dbus_property(self, value):
 
1034
        self.secret = str(value)
 
1035
    
747
1036
    del _interface
748
1037
 
749
1038
 
 
1039
class ProxyClient(object):
 
1040
    def __init__(self, child_pipe, fpr, address):
 
1041
        self._pipe = child_pipe
 
1042
        self._pipe.send(('init', fpr, address))
 
1043
        if not self._pipe.recv():
 
1044
            raise KeyError()
 
1045
 
 
1046
    def __getattribute__(self, name):
 
1047
        if(name == '_pipe'):
 
1048
            return super(ProxyClient, self).__getattribute__(name)
 
1049
        self._pipe.send(('getattr', name))
 
1050
        data = self._pipe.recv()
 
1051
        if data[0] == 'data':
 
1052
            return data[1]
 
1053
        if data[0] == 'function':
 
1054
            def func(*args, **kwargs):
 
1055
                self._pipe.send(('funcall', name, args, kwargs))
 
1056
                return self._pipe.recv()[1]
 
1057
            return func
 
1058
 
 
1059
    def __setattr__(self, name, value):
 
1060
        if(name == '_pipe'):
 
1061
            return super(ProxyClient, self).__setattr__(name, value)
 
1062
        self._pipe.send(('setattr', name, value))
 
1063
 
 
1064
 
750
1065
class ClientHandler(socketserver.BaseRequestHandler, object):
751
1066
    """A class to handle client connections.
752
1067
    
754
1069
    Note: This will run in its own forked process."""
755
1070
    
756
1071
    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:
 
1072
        with contextlib.closing(self.server.child_pipe) as child_pipe:
 
1073
            logger.info(u"TCP connection from: %s",
 
1074
                        unicode(self.client_address))
 
1075
            logger.debug(u"Pipe FD: %d",
 
1076
                         self.server.child_pipe.fileno())
 
1077
 
762
1078
            session = (gnutls.connection
763
1079
                       .ClientSession(self.request,
764
1080
                                      gnutls.connection
765
1081
                                      .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
 
            
 
1082
 
776
1083
            # Note: gnutls.connection.X509Credentials is really a
777
1084
            # generic GnuTLS certificate credentials object so long as
778
1085
            # no X.509 keys are added to it.  Therefore, we can use it
779
1086
            # here despite using OpenPGP certificates.
780
 
            
 
1087
 
781
1088
            #priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
782
1089
            #                      u"+AES-256-CBC", u"+SHA1",
783
1090
            #                      u"+COMP-NULL", u"+CTYPE-OPENPGP",
789
1096
            (gnutls.library.functions
790
1097
             .gnutls_priority_set_direct(session._c_object,
791
1098
                                         priority, None))
792
 
            
 
1099
 
 
1100
            # Start communication using the Mandos protocol
 
1101
            # Get protocol number
 
1102
            line = self.request.makefile().readline()
 
1103
            logger.debug(u"Protocol version: %r", line)
 
1104
            try:
 
1105
                if int(line.strip().split()[0]) > 1:
 
1106
                    raise RuntimeError
 
1107
            except (ValueError, IndexError, RuntimeError), error:
 
1108
                logger.error(u"Unknown protocol version: %s", error)
 
1109
                return
 
1110
 
 
1111
            # Start GnuTLS connection
793
1112
            try:
794
1113
                session.handshake()
795
1114
            except gnutls.errors.GNUTLSError, error:
799
1118
                return
800
1119
            logger.debug(u"Handshake succeeded")
801
1120
            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)
808
 
            
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()
 
1121
                try:
 
1122
                    fpr = self.fingerprint(self.peer_certificate
 
1123
                                           (session))
 
1124
                except (TypeError, gnutls.errors.GNUTLSError), error:
 
1125
                    logger.warning(u"Bad certificate: %s", error)
 
1126
                    return
 
1127
                logger.debug(u"Fingerprint: %s", fpr)
 
1128
 
 
1129
                try:
 
1130
                    client = ProxyClient(child_pipe, fpr,
 
1131
                                         self.client_address)
 
1132
                except KeyError:
 
1133
                    return
 
1134
                
 
1135
                delay = client.approved_delay
 
1136
                while True:
 
1137
                    if not client.enabled:
 
1138
                        logger.warning(u"Client %s is disabled",
 
1139
                                       client.name)
 
1140
                        if self.server.use_dbus:
 
1141
                            # Emit D-Bus signal
 
1142
                            client.Rejected("Disabled")                    
 
1143
                        return
 
1144
                    if client._approved is None:
 
1145
                        logger.info(u"Client %s need approval",
 
1146
                                    client.name)
 
1147
                        if self.server.use_dbus:
 
1148
                            # Emit D-Bus signal
 
1149
                            client.NeedApproval(
 
1150
                                client.approved_delay_milliseconds(),
 
1151
                                client.approved_by_default)
 
1152
                    elif client._approved:
 
1153
                        #We have a password and are approved
 
1154
                        break
 
1155
                    else:
 
1156
                        logger.warning(u"Client %s was not approved",
 
1157
                                       client.name)
 
1158
                        if self.server.use_dbus:
 
1159
                            # Emit D-Bus signal                        
 
1160
                            client.Rejected("Disapproved")
 
1161
                        return
 
1162
                    
 
1163
                    #wait until timeout or approved
 
1164
                    #x = float(client._timedelta_to_milliseconds(delay))
 
1165
                    time = datetime.datetime.now()
 
1166
                    client.changedstate.acquire()
 
1167
                    client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
 
1168
                    client.changedstate.release()
 
1169
                    time2 = datetime.datetime.now()
 
1170
                    if (time2 - time) >= delay:
 
1171
                        if not client.approved_by_default:
 
1172
                            logger.warning("Client %s timed out while"
 
1173
                                           " waiting for approval",
 
1174
                                           client.name)
 
1175
                            if self.server.use_dbus:
 
1176
                                # Emit D-Bus signal
 
1177
                                client.Rejected("Time out")
 
1178
                            return
 
1179
                        else:
 
1180
                            break
 
1181
                    else:
 
1182
                        delay -= time2 - time
 
1183
                
 
1184
                sent_size = 0
 
1185
                while sent_size < len(client.secret):
 
1186
                    # XXX handle session exception
 
1187
                    sent = session.send(client.secret[sent_size:])
 
1188
                    logger.debug(u"Sent: %d, remaining: %d",
 
1189
                                 sent, len(client.secret)
 
1190
                                 - (sent_size + sent))
 
1191
                    sent_size += sent
 
1192
 
 
1193
                logger.info(u"Sending secret to %s", client.name)
 
1194
                # bump the timeout as if seen
 
1195
                client.checked_ok()
 
1196
                if self.server.use_dbus:
 
1197
                    # Emit D-Bus signal
 
1198
                    client.GotSecret()
 
1199
 
 
1200
            finally:
 
1201
                session.bye()
834
1202
    
835
1203
    @staticmethod
836
1204
    def peer_certificate(session):
896
1264
        return hex_fpr
897
1265
 
898
1266
 
899
 
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
900
 
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
 
1267
class MultiprocessingMixIn(object):
 
1268
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
 
1269
    def sub_process_main(self, request, address):
 
1270
        try:
 
1271
            self.finish_request(request, address)
 
1272
        except:
 
1273
            self.handle_error(request, address)
 
1274
        self.close_request(request)
 
1275
            
 
1276
    def process_request(self, request, address):
 
1277
        """Start a new process to process the request."""
 
1278
        multiprocessing.Process(target = self.sub_process_main,
 
1279
                                args = (request, address)).start()
 
1280
 
 
1281
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
 
1282
    """ adds a pipe to the MixIn """
901
1283
    def process_request(self, request, client_address):
902
1284
        """Overrides and wraps the original process_request().
903
1285
        
904
1286
        This function creates a new pipe in self.pipe
905
1287
        """
906
 
        self.pipe = os.pipe()
907
 
        super(ForkingMixInWithPipe,
 
1288
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
 
1289
 
 
1290
        super(MultiprocessingMixInWithPipe,
908
1291
              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):
 
1292
        self.add_pipe(parent_pipe)
 
1293
    def add_pipe(self, parent_pipe):
912
1294
        """Dummy function; override as necessary"""
913
 
        os.close(pipe)
914
 
 
915
 
 
916
 
class IPv6_TCPServer(ForkingMixInWithPipe,
 
1295
        pass
 
1296
 
 
1297
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
917
1298
                     socketserver.TCPServer, object):
918
1299
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
919
1300
    
983
1364
        clients:        set of Client objects
984
1365
        gnutls_priority GnuTLS priority string
985
1366
        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
1367
    
990
1368
    Assumes a gobject.MainLoop event loop.
991
1369
    """
1007
1385
            return socketserver.TCPServer.server_activate(self)
1008
1386
    def enable(self):
1009
1387
        self.enabled = True
1010
 
    def add_pipe(self, pipe):
 
1388
    def add_pipe(self, parent_pipe):
1011
1389
        # 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={}):
 
1390
        gobject.io_add_watch(parent_pipe.fileno(),
 
1391
                             gobject.IO_IN | gobject.IO_HUP,
 
1392
                             functools.partial(self.handle_ipc,
 
1393
                                               parent_pipe = parent_pipe))
 
1394
        
 
1395
    def handle_ipc(self, source, condition, parent_pipe=None,
 
1396
                   client_object=None):
1015
1397
        condition_names = {
1016
1398
            gobject.IO_IN: u"IN",   # There is data to read.
1017
1399
            gobject.IO_OUT: u"OUT", # Data can be written (without
1029
1411
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1030
1412
                     conditions_string)
1031
1413
        
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)
 
1414
        # Read a request from the child
 
1415
        request = parent_pipe.recv()
 
1416
        command = request[0]
1035
1417
        
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
 
1418
        if command == 'init':
 
1419
            fpr = request[1]
 
1420
            address = request[2]
 
1421
            
 
1422
            for c in self.clients:
 
1423
                if c.fingerprint == fpr:
 
1424
                    client = c
 
1425
                    break
 
1426
            else:
 
1427
                logger.warning(u"Client not found for fingerprint: %s, ad"
 
1428
                               u"dress: %s", fpr, address)
 
1429
                if self.use_dbus:
 
1430
                    # Emit D-Bus signal
 
1431
                    mandos_dbus_service.ClientNotFound(fpr, address)
 
1432
                parent_pipe.send(False)
 
1433
                return False
 
1434
            
 
1435
            gobject.io_add_watch(parent_pipe.fileno(),
 
1436
                                 gobject.IO_IN | gobject.IO_HUP,
 
1437
                                 functools.partial(self.handle_ipc,
 
1438
                                                   parent_pipe = parent_pipe,
 
1439
                                                   client_object = client))
 
1440
            parent_pipe.send(True)
 
1441
            # remove the old hook in favor of the new above hook on same fileno
1044
1442
            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
 
1443
        if command == 'funcall':
 
1444
            funcname = request[1]
 
1445
            args = request[2]
 
1446
            kwargs = request[3]
 
1447
            
 
1448
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
 
1449
 
 
1450
        if command == 'getattr':
 
1451
            attrname = request[1]
 
1452
            if callable(client_object.__getattribute__(attrname)):
 
1453
                parent_pipe.send(('function',))
 
1454
            else:
 
1455
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
 
1456
 
 
1457
        if command == 'setattr':
 
1458
            attrname = request[1]
 
1459
            value = request[2]
 
1460
            setattr(client_object, attrname, value)
 
1461
            
1083
1462
        return True
1084
1463
 
1085
1464
 
1115
1494
            elif suffix == u"w":
1116
1495
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1117
1496
            else:
1118
 
                raise ValueError
1119
 
        except (ValueError, IndexError):
1120
 
            raise ValueError
 
1497
                raise ValueError(u"Unknown suffix %r" % suffix)
 
1498
        except (ValueError, IndexError), e:
 
1499
            raise ValueError(e.message)
1121
1500
        timevalue += delta
1122
1501
    return timevalue
1123
1502
 
1136
1515
        def if_nametoindex(interface):
1137
1516
            "Get an interface index the hard way, i.e. using fcntl()"
1138
1517
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1139
 
            with closing(socket.socket()) as s:
 
1518
            with contextlib.closing(socket.socket()) as s:
1140
1519
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1141
1520
                                    struct.pack(str(u"16s16x"),
1142
1521
                                                interface))
1162
1541
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1163
1542
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1164
1543
            raise OSError(errno.ENODEV,
1165
 
                          u"/dev/null not a character device")
 
1544
                          u"%s not a character device"
 
1545
                          % os.path.devnull)
1166
1546
        os.dup2(null, sys.stdin.fileno())
1167
1547
        os.dup2(null, sys.stdout.fileno())
1168
1548
        os.dup2(null, sys.stderr.fileno())
1172
1552
 
1173
1553
def main():
1174
1554
    
1175
 
    ######################################################################
 
1555
    ##################################################################
1176
1556
    # Parsing of options, both command line and config file
1177
1557
    
1178
1558
    parser = optparse.OptionParser(version = "%%prog %s" % version)
1272
1652
                        u"interval": u"5m",
1273
1653
                        u"checker": u"fping -q -- %%(host)s",
1274
1654
                        u"host": u"",
 
1655
                        u"approved_delay": u"5m",
 
1656
                        u"approved_duration": u"1s",
1275
1657
                        }
1276
1658
    client_config = configparser.SafeConfigParser(client_defaults)
1277
1659
    client_config.read(os.path.join(server_settings[u"configdir"],
1335
1717
    bus = dbus.SystemBus()
1336
1718
    # End of Avahi example code
1337
1719
    if use_dbus:
1338
 
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
 
1720
        try:
 
1721
            bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
 
1722
                                            bus, do_not_queue=True)
 
1723
        except dbus.exceptions.NameExistsException, e:
 
1724
            logger.error(unicode(e) + u", disabling D-Bus")
 
1725
            use_dbus = False
 
1726
            server_settings[u"use_dbus"] = False
 
1727
            tcp_server.use_dbus = False
1339
1728
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1340
1729
    service = AvahiService(name = server_settings[u"servicename"],
1341
1730
                           servicetype = u"_mandos._tcp",
1347
1736
    client_class = Client
1348
1737
    if use_dbus:
1349
1738
        client_class = functools.partial(ClientDBus, bus = bus)
 
1739
    def client_config_items(config, section):
 
1740
        special_settings = {
 
1741
            "approve_by_default":
 
1742
                lambda: config.getboolean(section,
 
1743
                                          "approve_by_default"),
 
1744
            }
 
1745
        for name, value in config.items(section):
 
1746
            try:
 
1747
                yield special_settings[name]()
 
1748
            except KeyError:
 
1749
                yield (name, value)
 
1750
    
1350
1751
    tcp_server.clients.update(set(
1351
1752
            client_class(name = section,
1352
 
                         config= dict(client_config.items(section)))
 
1753
                         config= dict(client_config_items(
 
1754
                        client_config, section)))
1353
1755
            for section in client_config.sections()))
1354
1756
    if not tcp_server.clients:
1355
1757
        logger.warning(u"No clients defined")
1367
1769
        daemon()
1368
1770
    
1369
1771
    try:
1370
 
        with closing(pidfile):
 
1772
        with pidfile:
1371
1773
            pid = os.getpid()
1372
1774
            pidfile.write(str(pid) + "\n")
1373
1775
        del pidfile
1379
1781
        pass
1380
1782
    del pidfilename
1381
1783
    
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
1784
    if not debug:
1394
1785
        signal.signal(signal.SIGINT, signal.SIG_IGN)
1395
1786
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1402
1793
                dbus.service.Object.__init__(self, bus, u"/")
1403
1794
            _interface = u"se.bsnet.fukt.Mandos"
1404
1795
            
1405
 
            @dbus.service.signal(_interface, signature=u"oa{sv}")
1406
 
            def ClientAdded(self, objpath, properties):
 
1796
            @dbus.service.signal(_interface, signature=u"o")
 
1797
            def ClientAdded(self, objpath):
1407
1798
                "D-Bus signal"
1408
1799
                pass
1409
1800
            
1410
 
            @dbus.service.signal(_interface, signature=u"s")
1411
 
            def ClientNotFound(self, fingerprint):
 
1801
            @dbus.service.signal(_interface, signature=u"ss")
 
1802
            def ClientNotFound(self, fingerprint, address):
1412
1803
                "D-Bus signal"
1413
1804
                pass
1414
1805
            
1428
1819
            def GetAllClientsWithProperties(self):
1429
1820
                "D-Bus method"
1430
1821
                return dbus.Dictionary(
1431
 
                    ((c.dbus_object_path, c.GetAllProperties())
 
1822
                    ((c.dbus_object_path, c.GetAll(u""))
1432
1823
                     for c in tcp_server.clients),
1433
1824
                    signature=u"oa{sv}")
1434
1825
            
1440
1831
                        tcp_server.clients.remove(c)
1441
1832
                        c.remove_from_connection()
1442
1833
                        # Don't signal anything except ClientRemoved
1443
 
                        c.disable(signal=False)
 
1834
                        c.disable(quiet=True)
1444
1835
                        # Emit D-Bus signal
1445
1836
                        self.ClientRemoved(object_path, c.name)
1446
1837
                        return
1447
 
                raise KeyError
 
1838
                raise KeyError(object_path)
1448
1839
            
1449
1840
            del _interface
1450
1841
        
1451
1842
        mandos_dbus_service = MandosDBusService()
1452
1843
    
 
1844
    def cleanup():
 
1845
        "Cleanup function; run on exit"
 
1846
        service.cleanup()
 
1847
        
 
1848
        while tcp_server.clients:
 
1849
            client = tcp_server.clients.pop()
 
1850
            if use_dbus:
 
1851
                client.remove_from_connection()
 
1852
            client.disable_hook = None
 
1853
            # Don't signal anything except ClientRemoved
 
1854
            client.disable(quiet=True)
 
1855
            if use_dbus:
 
1856
                # Emit D-Bus signal
 
1857
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
 
1858
                                                  client.name)
 
1859
    
 
1860
    atexit.register(cleanup)
 
1861
    
1453
1862
    for client in tcp_server.clients:
1454
1863
        if use_dbus:
1455
1864
            # Emit D-Bus signal
1456
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
1457
 
                                            client.GetAllProperties())
 
1865
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
1458
1866
        client.enable()
1459
1867
    
1460
1868
    tcp_server.enable()
1478
1886
            service.activate()
1479
1887
        except dbus.exceptions.DBusException, error:
1480
1888
            logger.critical(u"DBusException: %s", error)
 
1889
            cleanup()
1481
1890
            sys.exit(1)
1482
1891
        # End of Avahi example code
1483
1892
        
1490
1899
        main_loop.run()
1491
1900
    except AvahiError, error:
1492
1901
        logger.critical(u"AvahiError: %s", error)
 
1902
        cleanup()
1493
1903
        sys.exit(1)
1494
1904
    except KeyboardInterrupt:
1495
1905
        if debug:
1496
1906
            print >> sys.stderr
1497
1907
        logger.debug(u"Server received KeyboardInterrupt")
1498
1908
    logger.debug(u"Server exiting")
 
1909
    # Must run before the D-Bus bus name gets deregistered
 
1910
    cleanup()
1499
1911
 
1500
1912
if __name__ == '__main__':
1501
1913
    main()