/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2011-03-08 11:08:35 UTC
  • Revision ID: teddy@fukt.bsnet.se-20110308110835-3sh7b314tf7q3o1i
* plugins.d/password-prompt.c (conflict_detection): Check for both
                                                    "plymouth" and
                                                    "plymouthd".

Show diffs side-by-side

added added

removed removed

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