/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: teddy at bsnet
  • Date: 2010-08-23 19:23:15 UTC
  • mto: (24.1.154 mandos)
  • mto: This revision was merged to the branch mainline in revision 421.
  • Revision ID: teddy@fukt.bsnet.se-20100823192315-pefgye0l6cavcejs
* debian/control (mandos/Depends): Added "python-urwid".
* mandos (Client.approved_by_default): Changed default to "True".
  (Client.approved_delay): Changed default to "0s".
  (ClientDBus.GotSecret, ClientDBus.Rejected,
  ClientDBus.NeedApproval): Emit "PropertyChanged" signal for the
                            "approved_pending" property.

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