/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: 2012-06-01 21:48:12 UTC
  • Revision ID: teddy@recompile.se-20120601214812-g7685v5oeiuhi2qw
* debian/copyright (Copyright): Join the two lines to one line.
* debian/mandos-client.README.Debian: Refer to new location of example
  network hooks.
* debian/mandos-client.docs (network-hooks.d): Removed.
* debian/mandos-client.examples: New; contains "network-hooks.d".
* debian/rules (binary-common): Added "dh_installexamples".
  (binary-common/dh_fixperms): Changed to exclude new location of
  "network-hooks.d".
* init.d-mandos (status): Support new "status" action.

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
from __future__ import (division, absolute_import, print_function,
35
35
                        unicode_literals)
36
36
 
 
37
from future_builtins import *
 
38
 
37
39
import SocketServer as socketserver
38
40
import socket
39
41
import argparse
86
88
    except ImportError:
87
89
        SO_BINDTODEVICE = None
88
90
 
89
 
version = "1.5.3"
 
91
version = "1.5.5"
90
92
stored_state_file = "clients.pickle"
91
93
 
92
94
logger = logging.getLogger()
149
151
    def __enter__(self):
150
152
        return self
151
153
    
152
 
    def __exit__ (self, exc_type, exc_value, traceback):
 
154
    def __exit__(self, exc_type, exc_value, traceback):
153
155
        self._cleanup()
154
156
        return False
155
157
    
209
211
        return decrypted_plaintext
210
212
 
211
213
 
212
 
 
213
214
class AvahiError(Exception):
214
215
    def __init__(self, value, *args, **kwargs):
215
216
        self.value = value
244
245
    server: D-Bus Server
245
246
    bus: dbus.SystemBus()
246
247
    """
 
248
    
247
249
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
248
250
                 servicetype = None, port = None, TXT = None,
249
251
                 domain = "", host = "", max_renames = 32768,
262
264
        self.server = None
263
265
        self.bus = bus
264
266
        self.entry_group_state_changed_match = None
 
267
    
265
268
    def rename(self):
266
269
        """Derived from the Avahi example code"""
267
270
        if self.rename_count >= self.max_renames:
277
280
        try:
278
281
            self.add()
279
282
        except dbus.exceptions.DBusException as error:
280
 
            logger.critical("DBusException: %s", error)
 
283
            logger.critical("D-Bus Exception", exc_info=error)
281
284
            self.cleanup()
282
285
            os._exit(1)
283
286
        self.rename_count += 1
 
287
    
284
288
    def remove(self):
285
289
        """Derived from the Avahi example code"""
286
290
        if self.entry_group_state_changed_match is not None:
288
292
            self.entry_group_state_changed_match = None
289
293
        if self.group is not None:
290
294
            self.group.Reset()
 
295
    
291
296
    def add(self):
292
297
        """Derived from the Avahi example code"""
293
298
        self.remove()
310
315
            dbus.UInt16(self.port),
311
316
            avahi.string_array_to_txt_array(self.TXT))
312
317
        self.group.Commit()
 
318
    
313
319
    def entry_group_state_changed(self, state, error):
314
320
        """Derived from the Avahi example code"""
315
321
        logger.debug("Avahi entry group state change: %i", state)
322
328
        elif state == avahi.ENTRY_GROUP_FAILURE:
323
329
            logger.critical("Avahi: Error in group state changed %s",
324
330
                            unicode(error))
325
 
            raise AvahiGroupError("State changed: %s"
326
 
                                  % unicode(error))
 
331
            raise AvahiGroupError("State changed: {0!s}"
 
332
                                  .format(error))
 
333
    
327
334
    def cleanup(self):
328
335
        """Derived from the Avahi example code"""
329
336
        if self.group is not None:
334
341
                pass
335
342
            self.group = None
336
343
        self.remove()
 
344
    
337
345
    def server_state_changed(self, state, error=None):
338
346
        """Derived from the Avahi example code"""
339
347
        logger.debug("Avahi server state change: %i", state)
358
366
                logger.debug("Unknown state: %r", state)
359
367
            else:
360
368
                logger.debug("Unknown state: %r: %r", state, error)
 
369
    
361
370
    def activate(self):
362
371
        """Derived from the Avahi example code"""
363
372
        if self.server is None:
370
379
                                 self.server_state_changed)
371
380
        self.server_state_changed(self.server.GetState())
372
381
 
 
382
 
373
383
class AvahiServiceToSyslog(AvahiService):
374
384
    def rename(self):
375
385
        """Add the new name to the syslog messages"""
376
386
        ret = AvahiService.rename(self)
377
387
        syslogger.setFormatter(logging.Formatter
378
 
                               ('Mandos (%s) [%%(process)d]:'
379
 
                                ' %%(levelname)s: %%(message)s'
380
 
                                % self.name))
 
388
                               ('Mandos ({0}) [%(process)d]:'
 
389
                                ' %(levelname)s: %(message)s'
 
390
                                .format(self.name)))
381
391
        return ret
382
392
 
 
393
 
383
394
def timedelta_to_milliseconds(td):
384
395
    "Convert a datetime.timedelta() to milliseconds"
385
396
    return ((td.days * 24 * 60 * 60 * 1000)
386
397
            + (td.seconds * 1000)
387
398
            + (td.microseconds // 1000))
388
 
        
 
399
 
 
400
 
389
401
class Client(object):
390
402
    """A representation of a client host served by this server.
391
403
    
430
442
    """
431
443
    
432
444
    runtime_expansions = ("approval_delay", "approval_duration",
433
 
                          "created", "enabled", "fingerprint",
434
 
                          "host", "interval", "last_checked_ok",
 
445
                          "created", "enabled", "expires",
 
446
                          "fingerprint", "host", "interval",
 
447
                          "last_approval_request", "last_checked_ok",
435
448
                          "last_enabled", "name", "timeout")
436
449
    client_defaults = { "timeout": "5m",
437
450
                        "extended_timeout": "15m",
458
471
    
459
472
    def approval_delay_milliseconds(self):
460
473
        return timedelta_to_milliseconds(self.approval_delay)
461
 
 
 
474
    
462
475
    @staticmethod
463
476
    def config_parser(config):
464
477
        """Construct a new dict of client settings of this form:
489
502
                          "rb") as secfile:
490
503
                    client["secret"] = secfile.read()
491
504
            else:
492
 
                raise TypeError("No secret or secfile for section %s"
493
 
                                % section)
 
505
                raise TypeError("No secret or secfile for section {0}"
 
506
                                .format(section))
494
507
            client["timeout"] = string_to_delta(section["timeout"])
495
508
            client["extended_timeout"] = string_to_delta(
496
509
                section["extended_timeout"])
505
518
            client["last_checker_status"] = -2
506
519
        
507
520
        return settings
508
 
        
509
 
        
 
521
    
510
522
    def __init__(self, settings, name = None):
511
 
        """Note: the 'checker' key in 'config' sets the
512
 
        'checker_command' attribute and *not* the 'checker'
513
 
        attribute."""
514
523
        self.name = name
515
524
        # adding all client settings
516
525
        for setting, value in settings.iteritems():
525
534
        else:
526
535
            self.last_enabled = None
527
536
            self.expires = None
528
 
       
 
537
        
529
538
        logger.debug("Creating client %r", self.name)
530
539
        # Uppercase and remove spaces from fingerprint for later
531
540
        # comparison purposes with return value from the fingerprint()
533
542
        logger.debug("  Fingerprint: %s", self.fingerprint)
534
543
        self.created = settings.get("created",
535
544
                                    datetime.datetime.utcnow())
536
 
 
 
545
        
537
546
        # attributes specific for this server instance
538
547
        self.checker = None
539
548
        self.checker_initiator_tag = None
567
576
        if getattr(self, "enabled", False):
568
577
            # Already enabled
569
578
            return
570
 
        self.send_changedstate()
571
579
        self.expires = datetime.datetime.utcnow() + self.timeout
572
580
        self.enabled = True
573
581
        self.last_enabled = datetime.datetime.utcnow()
574
582
        self.init_checker()
 
583
        self.send_changedstate()
575
584
    
576
585
    def disable(self, quiet=True):
577
586
        """Disable this client."""
578
587
        if not getattr(self, "enabled", False):
579
588
            return False
580
589
        if not quiet:
581
 
            self.send_changedstate()
582
 
        if not quiet:
583
590
            logger.info("Disabling client %s", self.name)
584
 
        if getattr(self, "disable_initiator_tag", False):
 
591
        if getattr(self, "disable_initiator_tag", None) is not None:
585
592
            gobject.source_remove(self.disable_initiator_tag)
586
593
            self.disable_initiator_tag = None
587
594
        self.expires = None
588
 
        if getattr(self, "checker_initiator_tag", False):
 
595
        if getattr(self, "checker_initiator_tag", None) is not None:
589
596
            gobject.source_remove(self.checker_initiator_tag)
590
597
            self.checker_initiator_tag = None
591
598
        self.stop_checker()
592
599
        self.enabled = False
 
600
        if not quiet:
 
601
            self.send_changedstate()
593
602
        # Do not run this again if called by a gobject.timeout_add
594
603
        return False
595
604
    
599
608
    def init_checker(self):
600
609
        # Schedule a new checker to be started an 'interval' from now,
601
610
        # and every interval from then on.
 
611
        if self.checker_initiator_tag is not None:
 
612
            gobject.source_remove(self.checker_initiator_tag)
602
613
        self.checker_initiator_tag = (gobject.timeout_add
603
614
                                      (self.interval_milliseconds(),
604
615
                                       self.start_checker))
605
616
        # Schedule a disable() when 'timeout' has passed
 
617
        if self.disable_initiator_tag is not None:
 
618
            gobject.source_remove(self.disable_initiator_tag)
606
619
        self.disable_initiator_tag = (gobject.timeout_add
607
620
                                   (self.timeout_milliseconds(),
608
621
                                    self.disable))
639
652
            timeout = self.timeout
640
653
        if self.disable_initiator_tag is not None:
641
654
            gobject.source_remove(self.disable_initiator_tag)
 
655
            self.disable_initiator_tag = None
642
656
        if getattr(self, "enabled", False):
643
657
            self.disable_initiator_tag = (gobject.timeout_add
644
658
                                          (timedelta_to_milliseconds
654
668
        If a checker already exists, leave it running and do
655
669
        nothing."""
656
670
        # The reason for not killing a running checker is that if we
657
 
        # did that, then if a checker (for some reason) started
658
 
        # running slowly and taking more than 'interval' time, the
659
 
        # client would inevitably timeout, since no checker would get
660
 
        # a chance to run to completion.  If we instead leave running
 
671
        # did that, and if a checker (for some reason) started running
 
672
        # slowly and taking more than 'interval' time, then the client
 
673
        # would inevitably timeout, since no checker would get a
 
674
        # chance to run to completion.  If we instead leave running
661
675
        # checkers alone, the checker would have to take more time
662
676
        # than 'timeout' for the client to be disabled, which is as it
663
677
        # should be.
677
691
                                      self.current_checker_command)
678
692
        # Start a new checker if needed
679
693
        if self.checker is None:
 
694
            # Escape attributes for the shell
 
695
            escaped_attrs = dict(
 
696
                (attr, re.escape(unicode(getattr(self, attr))))
 
697
                for attr in
 
698
                self.runtime_expansions)
680
699
            try:
681
 
                # In case checker_command has exactly one % operator
682
 
                command = self.checker_command % self.host
683
 
            except TypeError:
684
 
                # Escape attributes for the shell
685
 
                escaped_attrs = dict(
686
 
                    (attr,
687
 
                     re.escape(unicode(str(getattr(self, attr, "")),
688
 
                                       errors=
689
 
                                       'replace')))
690
 
                    for attr in
691
 
                    self.runtime_expansions)
692
 
                
693
 
                try:
694
 
                    command = self.checker_command % escaped_attrs
695
 
                except TypeError as error:
696
 
                    logger.error('Could not format string "%s":'
697
 
                                 ' %s', self.checker_command, error)
698
 
                    return True # Try again later
 
700
                command = self.checker_command % escaped_attrs
 
701
            except TypeError as error:
 
702
                logger.error('Could not format string "%s"',
 
703
                             self.checker_command, exc_info=error)
 
704
                return True # Try again later
699
705
            self.current_checker_command = command
700
706
            try:
701
707
                logger.info("Starting checker %r for %s",
707
713
                self.checker = subprocess.Popen(command,
708
714
                                                close_fds=True,
709
715
                                                shell=True, cwd="/")
710
 
                self.checker_callback_tag = (gobject.child_watch_add
711
 
                                             (self.checker.pid,
712
 
                                              self.checker_callback,
713
 
                                              data=command))
714
 
                # The checker may have completed before the gobject
715
 
                # watch was added.  Check for this.
716
 
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
717
 
                if pid:
718
 
                    gobject.source_remove(self.checker_callback_tag)
719
 
                    self.checker_callback(pid, status, command)
720
716
            except OSError as error:
721
 
                logger.error("Failed to start subprocess: %s",
722
 
                             error)
 
717
                logger.error("Failed to start subprocess",
 
718
                             exc_info=error)
 
719
            self.checker_callback_tag = (gobject.child_watch_add
 
720
                                         (self.checker.pid,
 
721
                                          self.checker_callback,
 
722
                                          data=command))
 
723
            # The checker may have completed before the gobject
 
724
            # watch was added.  Check for this.
 
725
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
726
            if pid:
 
727
                gobject.source_remove(self.checker_callback_tag)
 
728
                self.checker_callback(pid, status, command)
723
729
        # Re-run this periodically if run by gobject.timeout_add
724
730
        return True
725
731
    
758
764
    # "Set" method, so we fail early here:
759
765
    if byte_arrays and signature != "ay":
760
766
        raise ValueError("Byte arrays not supported for non-'ay'"
761
 
                         " signature %r" % signature)
 
767
                         " signature {0!r}".format(signature))
762
768
    def decorator(func):
763
769
        func._dbus_is_property = True
764
770
        func._dbus_interface = dbus_interface
773
779
 
774
780
 
775
781
def dbus_interface_annotations(dbus_interface):
776
 
    """Decorator for marking functions returning interface annotations.
 
782
    """Decorator for marking functions returning interface annotations
777
783
    
778
784
    Usage:
779
785
    
976
982
                            tag.appendChild(ann_tag)
977
983
                # Add interface annotation tags
978
984
                for annotation, value in dict(
979
 
                    itertools.chain(
980
 
                        *(annotations().iteritems()
981
 
                          for name, annotations in
982
 
                          self._get_all_dbus_things("interface")
983
 
                          if name == if_tag.getAttribute("name")
984
 
                          ))).iteritems():
 
985
                    itertools.chain.from_iterable(
 
986
                        annotations().iteritems()
 
987
                        for name, annotations in
 
988
                        self._get_all_dbus_things("interface")
 
989
                        if name == if_tag.getAttribute("name")
 
990
                        )).iteritems():
985
991
                    ann_tag = document.createElement("annotation")
986
992
                    ann_tag.setAttribute("name", annotation)
987
993
                    ann_tag.setAttribute("value", value)
1006
1012
        except (AttributeError, xml.dom.DOMException,
1007
1013
                xml.parsers.expat.ExpatError) as error:
1008
1014
            logger.error("Failed to override Introspection method",
1009
 
                         error)
 
1015
                         exc_info=error)
1010
1016
        return xmlstring
1011
1017
 
1012
1018
 
1013
 
def datetime_to_dbus (dt, variant_level=0):
 
1019
def datetime_to_dbus(dt, variant_level=0):
1014
1020
    """Convert a UTC datetime.datetime() to a D-Bus type."""
1015
1021
    if dt is None:
1016
1022
        return dbus.String("", variant_level = variant_level)
1018
1024
                       variant_level=variant_level)
1019
1025
 
1020
1026
 
1021
 
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
1022
 
                                  .__metaclass__):
1023
 
    """Applied to an empty subclass of a D-Bus object, this metaclass
1024
 
    will add additional D-Bus attributes matching a certain pattern.
 
1027
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
 
1028
    """A class decorator; applied to a subclass of
 
1029
    dbus.service.Object, it will add alternate D-Bus attributes with
 
1030
    interface names according to the "alt_interface_names" mapping.
 
1031
    Usage:
 
1032
    
 
1033
    @alternate_dbus_interfaces({"org.example.Interface":
 
1034
                                    "net.example.AlternateInterface"})
 
1035
    class SampleDBusObject(dbus.service.Object):
 
1036
        @dbus.service.method("org.example.Interface")
 
1037
        def SampleDBusMethod():
 
1038
            pass
 
1039
    
 
1040
    The above "SampleDBusMethod" on "SampleDBusObject" will be
 
1041
    reachable via two interfaces: "org.example.Interface" and
 
1042
    "net.example.AlternateInterface", the latter of which will have
 
1043
    its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
 
1044
    "true", unless "deprecate" is passed with a False value.
 
1045
    
 
1046
    This works for methods and signals, and also for D-Bus properties
 
1047
    (from DBusObjectWithProperties) and interfaces (from the
 
1048
    dbus_interface_annotations decorator).
1025
1049
    """
1026
 
    def __new__(mcs, name, bases, attr):
1027
 
        # Go through all the base classes which could have D-Bus
1028
 
        # methods, signals, or properties in them
1029
 
        old_interface_names = []
1030
 
        for base in (b for b in bases
1031
 
                     if issubclass(b, dbus.service.Object)):
1032
 
            # Go though all attributes of the base class
1033
 
            for attrname, attribute in inspect.getmembers(base):
 
1050
    def wrapper(cls):
 
1051
        for orig_interface_name, alt_interface_name in (
 
1052
            alt_interface_names.iteritems()):
 
1053
            attr = {}
 
1054
            interface_names = set()
 
1055
            # Go though all attributes of the class
 
1056
            for attrname, attribute in inspect.getmembers(cls):
1034
1057
                # Ignore non-D-Bus attributes, and D-Bus attributes
1035
1058
                # with the wrong interface name
1036
1059
                if (not hasattr(attribute, "_dbus_interface")
1037
1060
                    or not attribute._dbus_interface
1038
 
                    .startswith("se.recompile.Mandos")):
 
1061
                    .startswith(orig_interface_name)):
1039
1062
                    continue
1040
1063
                # Create an alternate D-Bus interface name based on
1041
1064
                # the current name
1042
1065
                alt_interface = (attribute._dbus_interface
1043
 
                                 .replace("se.recompile.Mandos",
1044
 
                                          "se.bsnet.fukt.Mandos"))
1045
 
                if alt_interface != attribute._dbus_interface:
1046
 
                    old_interface_names.append(alt_interface)
 
1066
                                 .replace(orig_interface_name,
 
1067
                                          alt_interface_name))
 
1068
                interface_names.add(alt_interface)
1047
1069
                # Is this a D-Bus signal?
1048
1070
                if getattr(attribute, "_dbus_is_signal", False):
1049
1071
                    # Extract the original non-method function by
1071
1093
                    except AttributeError:
1072
1094
                        pass
1073
1095
                    # Define a creator of a function to call both the
1074
 
                    # old and new functions, so both the old and new
1075
 
                    # signals gets sent when the function is called
 
1096
                    # original and alternate functions, so both the
 
1097
                    # original and alternate signals gets sent when
 
1098
                    # the function is called
1076
1099
                    def fixscope(func1, func2):
1077
1100
                        """This function is a scope container to pass
1078
1101
                        func1 and func2 to the "call_both" function
1085
1108
                        return call_both
1086
1109
                    # Create the "call_both" function and add it to
1087
1110
                    # the class
1088
 
                    attr[attrname] = fixscope(attribute,
1089
 
                                              new_function)
 
1111
                    attr[attrname] = fixscope(attribute, new_function)
1090
1112
                # Is this a D-Bus method?
1091
1113
                elif getattr(attribute, "_dbus_is_method", False):
1092
1114
                    # Create a new, but exactly alike, function
1148
1170
                                        attribute.func_name,
1149
1171
                                        attribute.func_defaults,
1150
1172
                                        attribute.func_closure)))
1151
 
        # Deprecate all old interfaces
1152
 
        basename="_AlternateDBusNamesMetaclass_interface_annotation{0}"
1153
 
        for old_interface_name in old_interface_names:
1154
 
            @dbus_interface_annotations(old_interface_name)
1155
 
            def func(self):
1156
 
                return { "org.freedesktop.DBus.Deprecated": "true" }
1157
 
            # Find an unused name
1158
 
            for aname in (basename.format(i) for i in
1159
 
                          itertools.count()):
1160
 
                if aname not in attr:
1161
 
                    attr[aname] = func
1162
 
                    break
1163
 
        return type.__new__(mcs, name, bases, attr)
1164
 
 
1165
 
 
 
1173
            if deprecate:
 
1174
                # Deprecate all alternate interfaces
 
1175
                iname="_AlternateDBusNames_interface_annotation{0}"
 
1176
                for interface_name in interface_names:
 
1177
                    @dbus_interface_annotations(interface_name)
 
1178
                    def func(self):
 
1179
                        return { "org.freedesktop.DBus.Deprecated":
 
1180
                                     "true" }
 
1181
                    # Find an unused name
 
1182
                    for aname in (iname.format(i)
 
1183
                                  for i in itertools.count()):
 
1184
                        if aname not in attr:
 
1185
                            attr[aname] = func
 
1186
                            break
 
1187
            if interface_names:
 
1188
                # Replace the class with a new subclass of it with
 
1189
                # methods, signals, etc. as created above.
 
1190
                cls = type(b"{0}Alternate".format(cls.__name__),
 
1191
                           (cls,), attr)
 
1192
        return cls
 
1193
    return wrapper
 
1194
 
 
1195
 
 
1196
@alternate_dbus_interfaces({"se.recompile.Mandos":
 
1197
                                "se.bsnet.fukt.Mandos"})
1166
1198
class ClientDBus(Client, DBusObjectWithProperties):
1167
1199
    """A Client class using D-Bus
1168
1200
    
1188
1220
                                 ("/clients/" + client_object_name))
1189
1221
        DBusObjectWithProperties.__init__(self, self.bus,
1190
1222
                                          self.dbus_object_path)
1191
 
        
 
1223
    
1192
1224
    def notifychangeproperty(transform_func,
1193
1225
                             dbus_name, type_func=lambda x: x,
1194
1226
                             variant_level=1):
1217
1249
        
1218
1250
        return property(lambda self: getattr(self, attrname), setter)
1219
1251
    
1220
 
    
1221
1252
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
1222
1253
    approvals_pending = notifychangeproperty(dbus.Boolean,
1223
1254
                                             "ApprovalPending",
1305
1336
        return False
1306
1337
    
1307
1338
    def approve(self, value=True):
1308
 
        self.send_changedstate()
1309
1339
        self.approved = value
1310
1340
        gobject.timeout_add(timedelta_to_milliseconds
1311
1341
                            (self.approval_duration),
1312
1342
                            self._reset_approved)
1313
 
    
 
1343
        self.send_changedstate()
1314
1344
    
1315
1345
    ## D-Bus methods, signals & properties
1316
1346
    _interface = "se.recompile.Mandos.Client"
1500
1530
    def Timeout_dbus_property(self, value=None):
1501
1531
        if value is None:       # get
1502
1532
            return dbus.UInt64(self.timeout_milliseconds())
 
1533
        old_timeout = self.timeout
1503
1534
        self.timeout = datetime.timedelta(0, 0, 0, value)
1504
 
        # Reschedule timeout
 
1535
        # Reschedule disabling
1505
1536
        if self.enabled:
1506
1537
            now = datetime.datetime.utcnow()
1507
 
            time_to_die = timedelta_to_milliseconds(
1508
 
                (self.last_checked_ok + self.timeout) - now)
1509
 
            if time_to_die <= 0:
 
1538
            self.expires += self.timeout - old_timeout
 
1539
            if self.expires <= now:
1510
1540
                # The timeout has passed
1511
1541
                self.disable()
1512
1542
            else:
1513
 
                self.expires = (now +
1514
 
                                datetime.timedelta(milliseconds =
1515
 
                                                   time_to_die))
1516
1543
                if (getattr(self, "disable_initiator_tag", None)
1517
1544
                    is None):
1518
1545
                    return
1519
1546
                gobject.source_remove(self.disable_initiator_tag)
1520
 
                self.disable_initiator_tag = (gobject.timeout_add
1521
 
                                              (time_to_die,
1522
 
                                               self.disable))
 
1547
                self.disable_initiator_tag = (
 
1548
                    gobject.timeout_add(
 
1549
                        timedelta_to_milliseconds(self.expires - now),
 
1550
                        self.disable))
1523
1551
    
1524
1552
    # ExtendedTimeout - property
1525
1553
    @dbus_service_property(_interface, signature="t",
1604
1632
        self._pipe.send(('setattr', name, value))
1605
1633
 
1606
1634
 
1607
 
class ClientDBusTransitional(ClientDBus):
1608
 
    __metaclass__ = AlternateDBusNamesMetaclass
1609
 
 
1610
 
 
1611
1635
class ClientHandler(socketserver.BaseRequestHandler, object):
1612
1636
    """A class to handle client connections.
1613
1637
    
1717
1741
                    #wait until timeout or approved
1718
1742
                    time = datetime.datetime.now()
1719
1743
                    client.changedstate.acquire()
1720
 
                    (client.changedstate.wait
1721
 
                     (float(client.timedelta_to_milliseconds(delay)
1722
 
                            / 1000)))
 
1744
                    client.changedstate.wait(
 
1745
                        float(timedelta_to_milliseconds(delay)
 
1746
                              / 1000))
1723
1747
                    client.changedstate.release()
1724
1748
                    time2 = datetime.datetime.now()
1725
1749
                    if (time2 - time) >= delay:
1741
1765
                    try:
1742
1766
                        sent = session.send(client.secret[sent_size:])
1743
1767
                    except gnutls.errors.GNUTLSError as error:
1744
 
                        logger.warning("gnutls send failed")
 
1768
                        logger.warning("gnutls send failed",
 
1769
                                       exc_info=error)
1745
1770
                        return
1746
1771
                    logger.debug("Sent: %d, remaining: %d",
1747
1772
                                 sent, len(client.secret)
1761
1786
                try:
1762
1787
                    session.bye()
1763
1788
                except gnutls.errors.GNUTLSError as error:
1764
 
                    logger.warning("GnuTLS bye failed")
 
1789
                    logger.warning("GnuTLS bye failed",
 
1790
                                   exc_info=error)
1765
1791
    
1766
1792
    @staticmethod
1767
1793
    def peer_certificate(session):
1839
1865
    def process_request(self, request, address):
1840
1866
        """Start a new process to process the request."""
1841
1867
        proc = multiprocessing.Process(target = self.sub_process_main,
1842
 
                                       args = (request,
1843
 
                                               address))
 
1868
                                       args = (request, address))
1844
1869
        proc.start()
1845
1870
        return proc
1846
1871
 
1874
1899
        use_ipv6:       Boolean; to use IPv6 or not
1875
1900
    """
1876
1901
    def __init__(self, server_address, RequestHandlerClass,
1877
 
                 interface=None, use_ipv6=True):
 
1902
                 interface=None, use_ipv6=True, socketfd=None):
 
1903
        """If socketfd is set, use that file descriptor instead of
 
1904
        creating a new one with socket.socket().
 
1905
        """
1878
1906
        self.interface = interface
1879
1907
        if use_ipv6:
1880
1908
            self.address_family = socket.AF_INET6
 
1909
        if socketfd is not None:
 
1910
            # Save the file descriptor
 
1911
            self.socketfd = socketfd
 
1912
            # Save the original socket.socket() function
 
1913
            self.socket_socket = socket.socket
 
1914
            # To implement --socket, we monkey patch socket.socket.
 
1915
            # 
 
1916
            # (When socketserver.TCPServer is a new-style class, we
 
1917
            # could make self.socket into a property instead of monkey
 
1918
            # patching socket.socket.)
 
1919
            # 
 
1920
            # Create a one-time-only replacement for socket.socket()
 
1921
            @functools.wraps(socket.socket)
 
1922
            def socket_wrapper(*args, **kwargs):
 
1923
                # Restore original function so subsequent calls are
 
1924
                # not affected.
 
1925
                socket.socket = self.socket_socket
 
1926
                del self.socket_socket
 
1927
                # This time only, return a new socket object from the
 
1928
                # saved file descriptor.
 
1929
                return socket.fromfd(self.socketfd, *args, **kwargs)
 
1930
            # Replace socket.socket() function with wrapper
 
1931
            socket.socket = socket_wrapper
 
1932
        # The socketserver.TCPServer.__init__ will call
 
1933
        # socket.socket(), which might be our replacement,
 
1934
        # socket_wrapper(), if socketfd was set.
1881
1935
        socketserver.TCPServer.__init__(self, server_address,
1882
1936
                                        RequestHandlerClass)
 
1937
    
1883
1938
    def server_bind(self):
1884
1939
        """This overrides the normal server_bind() function
1885
1940
        to bind to an interface if one was specified, and also NOT to
1896
1951
                                           str(self.interface
1897
1952
                                               + '\0'))
1898
1953
                except socket.error as error:
1899
 
                    if error[0] == errno.EPERM:
 
1954
                    if error.errno == errno.EPERM:
1900
1955
                        logger.error("No permission to"
1901
1956
                                     " bind to interface %s",
1902
1957
                                     self.interface)
1903
 
                    elif error[0] == errno.ENOPROTOOPT:
 
1958
                    elif error.errno == errno.ENOPROTOOPT:
1904
1959
                        logger.error("SO_BINDTODEVICE not available;"
1905
1960
                                     " cannot bind to interface %s",
1906
1961
                                     self.interface)
 
1962
                    elif error.errno == errno.ENODEV:
 
1963
                        logger.error("Interface %s does not"
 
1964
                                     " exist, cannot bind",
 
1965
                                     self.interface)
1907
1966
                    else:
1908
1967
                        raise
1909
1968
        # Only bind(2) the socket if we really need to.
1939
1998
    """
1940
1999
    def __init__(self, server_address, RequestHandlerClass,
1941
2000
                 interface=None, use_ipv6=True, clients=None,
1942
 
                 gnutls_priority=None, use_dbus=True):
 
2001
                 gnutls_priority=None, use_dbus=True, socketfd=None):
1943
2002
        self.enabled = False
1944
2003
        self.clients = clients
1945
2004
        if self.clients is None:
1949
2008
        IPv6_TCPServer.__init__(self, server_address,
1950
2009
                                RequestHandlerClass,
1951
2010
                                interface = interface,
1952
 
                                use_ipv6 = use_ipv6)
 
2011
                                use_ipv6 = use_ipv6,
 
2012
                                socketfd = socketfd)
1953
2013
    def server_activate(self):
1954
2014
        if self.enabled:
1955
2015
            return socketserver.TCPServer.server_activate(self)
1968
2028
    
1969
2029
    def handle_ipc(self, source, condition, parent_pipe=None,
1970
2030
                   proc = None, client_object=None):
1971
 
        condition_names = {
1972
 
            gobject.IO_IN: "IN",   # There is data to read.
1973
 
            gobject.IO_OUT: "OUT", # Data can be written (without
1974
 
                                    # blocking).
1975
 
            gobject.IO_PRI: "PRI", # There is urgent data to read.
1976
 
            gobject.IO_ERR: "ERR", # Error condition.
1977
 
            gobject.IO_HUP: "HUP"  # Hung up (the connection has been
1978
 
                                    # broken, usually for pipes and
1979
 
                                    # sockets).
1980
 
            }
1981
 
        conditions_string = ' | '.join(name
1982
 
                                       for cond, name in
1983
 
                                       condition_names.iteritems()
1984
 
                                       if cond & condition)
1985
2031
        # error, or the other end of multiprocessing.Pipe has closed
1986
 
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
 
2032
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
1987
2033
            # Wait for other process to exit
1988
2034
            proc.join()
1989
2035
            return False
2079
2125
            elif suffix == "w":
2080
2126
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2081
2127
            else:
2082
 
                raise ValueError("Unknown suffix %r" % suffix)
 
2128
                raise ValueError("Unknown suffix {0!r}"
 
2129
                                 .format(suffix))
2083
2130
        except (ValueError, IndexError) as e:
2084
2131
            raise ValueError(*(e.args))
2085
2132
        timevalue += delta
2102
2149
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2103
2150
        if not stat.S_ISCHR(os.fstat(null).st_mode):
2104
2151
            raise OSError(errno.ENODEV,
2105
 
                          "%s not a character device"
2106
 
                          % os.devnull)
 
2152
                          "{0} not a character device"
 
2153
                          .format(os.devnull))
2107
2154
        os.dup2(null, sys.stdin.fileno())
2108
2155
        os.dup2(null, sys.stdout.fileno())
2109
2156
        os.dup2(null, sys.stderr.fileno())
2118
2165
    
2119
2166
    parser = argparse.ArgumentParser()
2120
2167
    parser.add_argument("-v", "--version", action="version",
2121
 
                        version = "%%(prog)s %s" % version,
 
2168
                        version = "%(prog)s {0}".format(version),
2122
2169
                        help="show version number and exit")
2123
2170
    parser.add_argument("-i", "--interface", metavar="IF",
2124
2171
                        help="Bind to interface IF")
2149
2196
    parser.add_argument("--no-restore", action="store_false",
2150
2197
                        dest="restore", help="Do not restore stored"
2151
2198
                        " state")
 
2199
    parser.add_argument("--socket", type=int,
 
2200
                        help="Specify a file descriptor to a network"
 
2201
                        " socket to use instead of creating one")
2152
2202
    parser.add_argument("--statedir", metavar="DIR",
2153
2203
                        help="Directory to save/restore state in")
2154
2204
    
2171
2221
                        "use_ipv6": "True",
2172
2222
                        "debuglevel": "",
2173
2223
                        "restore": "True",
 
2224
                        "socket": "",
2174
2225
                        "statedir": "/var/lib/mandos"
2175
2226
                        }
2176
2227
    
2188
2239
    if server_settings["port"]:
2189
2240
        server_settings["port"] = server_config.getint("DEFAULT",
2190
2241
                                                       "port")
 
2242
    if server_settings["socket"]:
 
2243
        server_settings["socket"] = server_config.getint("DEFAULT",
 
2244
                                                         "socket")
 
2245
        # Later, stdin will, and stdout and stderr might, be dup'ed
 
2246
        # over with an opened os.devnull.  But we don't want this to
 
2247
        # happen with a supplied network socket.
 
2248
        if 0 <= server_settings["socket"] <= 2:
 
2249
            server_settings["socket"] = os.dup(server_settings
 
2250
                                               ["socket"])
2191
2251
    del server_config
2192
2252
    
2193
2253
    # Override the settings from the config file with command line
2195
2255
    for option in ("interface", "address", "port", "debug",
2196
2256
                   "priority", "servicename", "configdir",
2197
2257
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2198
 
                   "statedir"):
 
2258
                   "statedir", "socket"):
2199
2259
        value = getattr(options, option)
2200
2260
        if value is not None:
2201
2261
            server_settings[option] = value
2227
2287
    
2228
2288
    if server_settings["servicename"] != "Mandos":
2229
2289
        syslogger.setFormatter(logging.Formatter
2230
 
                               ('Mandos (%s) [%%(process)d]:'
2231
 
                                ' %%(levelname)s: %%(message)s'
2232
 
                                % server_settings["servicename"]))
 
2290
                               ('Mandos ({0}) [%(process)d]:'
 
2291
                                ' %(levelname)s: %(message)s'
 
2292
                                .format(server_settings
 
2293
                                        ["servicename"])))
2233
2294
    
2234
2295
    # Parse config file with clients
2235
2296
    client_config = configparser.SafeConfigParser(Client
2248
2309
                              use_ipv6=use_ipv6,
2249
2310
                              gnutls_priority=
2250
2311
                              server_settings["priority"],
2251
 
                              use_dbus=use_dbus)
 
2312
                              use_dbus=use_dbus,
 
2313
                              socketfd=(server_settings["socket"]
 
2314
                                        or None))
2252
2315
    if not debug:
2253
2316
        pidfilename = "/var/run/mandos.pid"
2254
2317
        try:
2255
2318
            pidfile = open(pidfilename, "w")
2256
 
        except IOError:
2257
 
            logger.error("Could not open file %r", pidfilename)
 
2319
        except IOError as e:
 
2320
            logger.error("Could not open file %r", pidfilename,
 
2321
                         exc_info=e)
2258
2322
    
2259
 
    try:
2260
 
        uid = pwd.getpwnam("_mandos").pw_uid
2261
 
        gid = pwd.getpwnam("_mandos").pw_gid
2262
 
    except KeyError:
 
2323
    for name in ("_mandos", "mandos", "nobody"):
2263
2324
        try:
2264
 
            uid = pwd.getpwnam("mandos").pw_uid
2265
 
            gid = pwd.getpwnam("mandos").pw_gid
 
2325
            uid = pwd.getpwnam(name).pw_uid
 
2326
            gid = pwd.getpwnam(name).pw_gid
 
2327
            break
2266
2328
        except KeyError:
2267
 
            try:
2268
 
                uid = pwd.getpwnam("nobody").pw_uid
2269
 
                gid = pwd.getpwnam("nobody").pw_gid
2270
 
            except KeyError:
2271
 
                uid = 65534
2272
 
                gid = 65534
 
2329
            continue
 
2330
    else:
 
2331
        uid = 65534
 
2332
        gid = 65534
2273
2333
    try:
2274
2334
        os.setgid(gid)
2275
2335
        os.setuid(uid)
2276
2336
    except OSError as error:
2277
 
        if error[0] != errno.EPERM:
 
2337
        if error.errno != errno.EPERM:
2278
2338
            raise error
2279
2339
    
2280
2340
    if debug:
2302
2362
        # Close all input and output, do double fork, etc.
2303
2363
        daemon()
2304
2364
    
 
2365
    # multiprocessing will use threads, so before we use gobject we
 
2366
    # need to inform gobject that threads will be used.
2305
2367
    gobject.threads_init()
2306
2368
    
2307
2369
    global main_loop
2318
2380
                            ("se.bsnet.fukt.Mandos", bus,
2319
2381
                             do_not_queue=True))
2320
2382
        except dbus.exceptions.NameExistsException as e:
2321
 
            logger.error(unicode(e) + ", disabling D-Bus")
 
2383
            logger.error("Disabling D-Bus:", exc_info=e)
2322
2384
            use_dbus = False
2323
2385
            server_settings["use_dbus"] = False
2324
2386
            tcp_server.use_dbus = False
2336
2398
    
2337
2399
    client_class = Client
2338
2400
    if use_dbus:
2339
 
        client_class = functools.partial(ClientDBusTransitional,
2340
 
                                         bus = bus)
 
2401
        client_class = functools.partial(ClientDBus, bus = bus)
2341
2402
    
2342
2403
    client_settings = Client.config_parser(client_config)
2343
2404
    old_client_settings = {}
2351
2412
                                                     (stored_state))
2352
2413
            os.remove(stored_state_path)
2353
2414
        except IOError as e:
2354
 
            logger.warning("Could not load persistent state: {0}"
2355
 
                           .format(e))
2356
 
            if e.errno != errno.ENOENT:
 
2415
            if e.errno == errno.ENOENT:
 
2416
                logger.warning("Could not load persistent state: {0}"
 
2417
                                .format(os.strerror(e.errno)))
 
2418
            else:
 
2419
                logger.critical("Could not load persistent state:",
 
2420
                                exc_info=e)
2357
2421
                raise
2358
2422
        except EOFError as e:
2359
2423
            logger.warning("Could not load persistent state: "
2360
 
                           "EOFError: {0}".format(e))
 
2424
                           "EOFError:", exc_info=e)
2361
2425
    
2362
2426
    with PGPEngine() as pgp:
2363
2427
        for client_name, client in clients_data.iteritems():
2416
2480
                             .format(client_name))
2417
2481
                client["secret"] = (
2418
2482
                    client_settings[client_name]["secret"])
2419
 
 
2420
2483
    
2421
2484
    # Add/remove clients based on new changes made to config
2422
2485
    for client_name in (set(old_client_settings)
2425
2488
    for client_name in (set(client_settings)
2426
2489
                        - set(old_client_settings)):
2427
2490
        clients_data[client_name] = client_settings[client_name]
2428
 
 
 
2491
    
2429
2492
    # Create all client objects
2430
2493
    for client_name, client in clients_data.iteritems():
2431
2494
        tcp_server.clients[client_name] = client_class(
2433
2496
    
2434
2497
    if not tcp_server.clients:
2435
2498
        logger.warning("No clients defined")
2436
 
        
 
2499
    
2437
2500
    if not debug:
2438
2501
        try:
2439
2502
            with pidfile:
2447
2510
            # "pidfile" was never created
2448
2511
            pass
2449
2512
        del pidfilename
2450
 
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2451
2513
    
2452
2514
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2453
2515
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2454
2516
    
2455
2517
    if use_dbus:
 
2518
        @alternate_dbus_interfaces({"se.recompile.Mandos":
 
2519
                                        "se.bsnet.fukt.Mandos"})
2456
2520
        class MandosDBusService(DBusObjectWithProperties):
2457
2521
            """A D-Bus proxy object"""
2458
2522
            def __init__(self):
2512
2576
            
2513
2577
            del _interface
2514
2578
        
2515
 
        class MandosDBusServiceTransitional(MandosDBusService):
2516
 
            __metaclass__ = AlternateDBusNamesMetaclass
2517
 
        mandos_dbus_service = MandosDBusServiceTransitional()
 
2579
        mandos_dbus_service = MandosDBusService()
2518
2580
    
2519
2581
    def cleanup():
2520
2582
        "Cleanup function; run on exit"
2553
2615
                del client_settings[client.name]["secret"]
2554
2616
        
2555
2617
        try:
2556
 
            tempfd, tempname = tempfile.mkstemp(suffix=".pickle",
2557
 
                                                prefix="clients-",
2558
 
                                                dir=os.path.dirname
2559
 
                                                (stored_state_path))
2560
 
            with os.fdopen(tempfd, "wb") as stored_state:
 
2618
            with (tempfile.NamedTemporaryFile
 
2619
                  (mode='wb', suffix=".pickle", prefix='clients-',
 
2620
                   dir=os.path.dirname(stored_state_path),
 
2621
                   delete=False)) as stored_state:
2561
2622
                pickle.dump((clients, client_settings), stored_state)
 
2623
                tempname=stored_state.name
2562
2624
            os.rename(tempname, stored_state_path)
2563
2625
        except (IOError, OSError) as e:
2564
 
            logger.warning("Could not save persistent state: {0}"
2565
 
                           .format(e))
2566
2626
            if not debug:
2567
2627
                try:
2568
2628
                    os.remove(tempname)
2569
2629
                except NameError:
2570
2630
                    pass
2571
 
            if e.errno not in set((errno.ENOENT, errno.EACCES,
2572
 
                                   errno.EEXIST)):
 
2631
            if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
 
2632
                logger.warning("Could not save persistent state: {0}"
 
2633
                               .format(os.strerror(e.errno)))
 
2634
            else:
 
2635
                logger.warning("Could not save persistent state:",
 
2636
                               exc_info=e)
2573
2637
                raise e
2574
2638
        
2575
2639
        # Delete all clients, and settings from config
2603
2667
    service.port = tcp_server.socket.getsockname()[1]
2604
2668
    if use_ipv6:
2605
2669
        logger.info("Now listening on address %r, port %d,"
2606
 
                    " flowinfo %d, scope_id %d"
2607
 
                    % tcp_server.socket.getsockname())
 
2670
                    " flowinfo %d, scope_id %d",
 
2671
                    *tcp_server.socket.getsockname())
2608
2672
    else:                       # IPv4
2609
 
        logger.info("Now listening on address %r, port %d"
2610
 
                    % tcp_server.socket.getsockname())
 
2673
        logger.info("Now listening on address %r, port %d",
 
2674
                    *tcp_server.socket.getsockname())
2611
2675
    
2612
2676
    #service.interface = tcp_server.socket.getsockname()[3]
2613
2677
    
2616
2680
        try:
2617
2681
            service.activate()
2618
2682
        except dbus.exceptions.DBusException as error:
2619
 
            logger.critical("DBusException: %s", error)
 
2683
            logger.critical("D-Bus Exception", exc_info=error)
2620
2684
            cleanup()
2621
2685
            sys.exit(1)
2622
2686
        # End of Avahi example code
2629
2693
        logger.debug("Starting main loop")
2630
2694
        main_loop.run()
2631
2695
    except AvahiError as error:
2632
 
        logger.critical("AvahiError: %s", error)
 
2696
        logger.critical("Avahi Error", exc_info=error)
2633
2697
        cleanup()
2634
2698
        sys.exit(1)
2635
2699
    except KeyboardInterrupt: