/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: 2015-03-10 18:03:38 UTC
  • Revision ID: teddy@recompile.se-20150310180338-pcxw6r2qmw9k6br9
Add ":!RSA" to GnuTLS priority string, to disallow non-DHE kx.

If Mandos was somehow made to use a non-ephemeral Diffie-Hellman key
exchange algorithm in the TLS handshake, any saved network traffic
could then be decrypted later if the Mandos client key was obtained.
By default, Mandos uses ephemeral DH key exchanges which does not have
this problem, but a non-ephemeral key exchange algorithm was still
enabled by default.  The simplest solution is to simply turn that off,
which ensures that Mandos will always use ephemeral DH key exchanges.

There is a "PFS" priority string specifier, but we can't use it because:

1. Security-wise, it is a mix between "NORMAL" and "SECURE128" - it
   enables a lot more algorithms than "SECURE256".

2. It is only available since GnuTLS 3.2.4.

Thanks to Andreas Fischer <af@bantuX.org> for reporting this issue.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
 
1
#!/usr/bin/python2.7
2
2
# -*- mode: python; coding: utf-8 -*-
3
3
4
4
# Mandos server - give out binary blobs to connecting clients.
88
88
    except ImportError:
89
89
        SO_BINDTODEVICE = None
90
90
 
91
 
version = "1.6.4"
 
91
if sys.version_info.major == 2:
 
92
    str = unicode
 
93
 
 
94
version = "1.6.9"
92
95
stored_state_file = "clients.pickle"
93
96
 
94
97
logger = logging.getLogger()
104
107
        SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
105
108
        with contextlib.closing(socket.socket()) as s:
106
109
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
107
 
                                struct.pack(str("16s16x"),
108
 
                                            interface))
109
 
        interface_index = struct.unpack(str("I"),
110
 
                                        ifreq[16:20])[0]
 
110
                                struct.pack(b"16s16x", interface))
 
111
        interface_index = struct.unpack("I", ifreq[16:20])[0]
111
112
        return interface_index
112
113
 
113
114
 
114
115
def initlogger(debug, level=logging.WARNING):
115
116
    """init logger and add loglevel"""
116
117
    
 
118
    global syslogger
117
119
    syslogger = (logging.handlers.SysLogHandler
118
120
                 (facility =
119
121
                  logging.handlers.SysLogHandler.LOG_DAEMON,
120
 
                  address = str("/dev/log")))
 
122
                  address = "/dev/log"))
121
123
    syslogger.setFormatter(logging.Formatter
122
124
                           ('Mandos [%(process)d]: %(levelname)s:'
123
125
                            ' %(message)s'))
223
225
class AvahiError(Exception):
224
226
    def __init__(self, value, *args, **kwargs):
225
227
        self.value = value
226
 
        super(AvahiError, self).__init__(value, *args, **kwargs)
227
 
    def __unicode__(self):
228
 
        return unicode(repr(self.value))
 
228
        return super(AvahiError, self).__init__(value, *args,
 
229
                                                **kwargs)
229
230
 
230
231
class AvahiServiceError(AvahiError):
231
232
    pass
274
275
        self.bus = bus
275
276
        self.entry_group_state_changed_match = None
276
277
    
277
 
    def rename(self):
 
278
    def rename(self, remove=True):
278
279
        """Derived from the Avahi example code"""
279
280
        if self.rename_count >= self.max_renames:
280
281
            logger.critical("No suitable Zeroconf service name found"
281
282
                            " after %i retries, exiting.",
282
283
                            self.rename_count)
283
284
            raise AvahiServiceError("Too many renames")
284
 
        self.name = unicode(self.server
285
 
                            .GetAlternativeServiceName(self.name))
 
285
        self.name = str(self.server
 
286
                        .GetAlternativeServiceName(self.name))
 
287
        self.rename_count += 1
286
288
        logger.info("Changing Zeroconf service name to %r ...",
287
289
                    self.name)
288
 
        self.remove()
 
290
        if remove:
 
291
            self.remove()
289
292
        try:
290
293
            self.add()
291
294
        except dbus.exceptions.DBusException as error:
292
 
            logger.critical("D-Bus Exception", exc_info=error)
293
 
            self.cleanup()
294
 
            os._exit(1)
295
 
        self.rename_count += 1
 
295
            if (error.get_dbus_name()
 
296
                == "org.freedesktop.Avahi.CollisionError"):
 
297
                logger.info("Local Zeroconf service name collision.")
 
298
                return self.rename(remove=False)
 
299
            else:
 
300
                logger.critical("D-Bus Exception", exc_info=error)
 
301
                self.cleanup()
 
302
                os._exit(1)
296
303
    
297
304
    def remove(self):
298
305
        """Derived from the Avahi example code"""
336
343
            self.rename()
337
344
        elif state == avahi.ENTRY_GROUP_FAILURE:
338
345
            logger.critical("Avahi: Error in group state changed %s",
339
 
                            unicode(error))
340
 
            raise AvahiGroupError("State changed: {0!s}"
 
346
                            str(error))
 
347
            raise AvahiGroupError("State changed: {!s}"
341
348
                                  .format(error))
342
349
    
343
350
    def cleanup(self):
390
397
 
391
398
 
392
399
class AvahiServiceToSyslog(AvahiService):
393
 
    def rename(self):
 
400
    def rename(self, *args, **kwargs):
394
401
        """Add the new name to the syslog messages"""
395
 
        ret = AvahiService.rename(self)
 
402
        ret = AvahiService.rename(self, *args, **kwargs)
396
403
        syslogger.setFormatter(logging.Formatter
397
 
                               ('Mandos ({0}) [%(process)d]:'
 
404
                               ('Mandos ({}) [%(process)d]:'
398
405
                                ' %(levelname)s: %(message)s'
399
406
                                .format(self.name)))
400
407
        return ret
401
408
 
402
409
 
403
 
def timedelta_to_milliseconds(td):
404
 
    "Convert a datetime.timedelta() to milliseconds"
405
 
    return ((td.days * 24 * 60 * 60 * 1000)
406
 
            + (td.seconds * 1000)
407
 
            + (td.microseconds // 1000))
408
 
 
409
 
 
410
410
class Client(object):
411
411
    """A representation of a client host served by this server.
412
412
    
467
467
                        "enabled": "True",
468
468
                        }
469
469
    
470
 
    def timeout_milliseconds(self):
471
 
        "Return the 'timeout' attribute in milliseconds"
472
 
        return timedelta_to_milliseconds(self.timeout)
473
 
    
474
 
    def extended_timeout_milliseconds(self):
475
 
        "Return the 'extended_timeout' attribute in milliseconds"
476
 
        return timedelta_to_milliseconds(self.extended_timeout)
477
 
    
478
 
    def interval_milliseconds(self):
479
 
        "Return the 'interval' attribute in milliseconds"
480
 
        return timedelta_to_milliseconds(self.interval)
481
 
    
482
 
    def approval_delay_milliseconds(self):
483
 
        return timedelta_to_milliseconds(self.approval_delay)
484
 
    
485
470
    @staticmethod
486
471
    def config_parser(config):
487
472
        """Construct a new dict of client settings of this form:
502
487
            client["enabled"] = config.getboolean(client_name,
503
488
                                                  "enabled")
504
489
            
 
490
            # Uppercase and remove spaces from fingerprint for later
 
491
            # comparison purposes with return value from the
 
492
            # fingerprint() function
505
493
            client["fingerprint"] = (section["fingerprint"].upper()
506
494
                                     .replace(" ", ""))
507
495
            if "secret" in section:
512
500
                          "rb") as secfile:
513
501
                    client["secret"] = secfile.read()
514
502
            else:
515
 
                raise TypeError("No secret or secfile for section {0}"
 
503
                raise TypeError("No secret or secfile for section {}"
516
504
                                .format(section))
517
505
            client["timeout"] = string_to_delta(section["timeout"])
518
506
            client["extended_timeout"] = string_to_delta(
535
523
            server_settings = {}
536
524
        self.server_settings = server_settings
537
525
        # adding all client settings
538
 
        for setting, value in settings.iteritems():
 
526
        for setting, value in settings.items():
539
527
            setattr(self, setting, value)
540
528
        
541
529
        if self.enabled:
549
537
            self.expires = None
550
538
        
551
539
        logger.debug("Creating client %r", self.name)
552
 
        # Uppercase and remove spaces from fingerprint for later
553
 
        # comparison purposes with return value from the fingerprint()
554
 
        # function
555
540
        logger.debug("  Fingerprint: %s", self.fingerprint)
556
541
        self.created = settings.get("created",
557
542
                                    datetime.datetime.utcnow())
624
609
        if self.checker_initiator_tag is not None:
625
610
            gobject.source_remove(self.checker_initiator_tag)
626
611
        self.checker_initiator_tag = (gobject.timeout_add
627
 
                                      (self.interval_milliseconds(),
 
612
                                      (int(self.interval
 
613
                                           .total_seconds() * 1000),
628
614
                                       self.start_checker))
629
615
        # Schedule a disable() when 'timeout' has passed
630
616
        if self.disable_initiator_tag is not None:
631
617
            gobject.source_remove(self.disable_initiator_tag)
632
618
        self.disable_initiator_tag = (gobject.timeout_add
633
 
                                   (self.timeout_milliseconds(),
634
 
                                    self.disable))
 
619
                                      (int(self.timeout
 
620
                                           .total_seconds() * 1000),
 
621
                                       self.disable))
635
622
        # Also start a new checker *right now*.
636
623
        self.start_checker()
637
624
    
668
655
            self.disable_initiator_tag = None
669
656
        if getattr(self, "enabled", False):
670
657
            self.disable_initiator_tag = (gobject.timeout_add
671
 
                                          (timedelta_to_milliseconds
672
 
                                           (timeout), self.disable))
 
658
                                          (int(timeout.total_seconds()
 
659
                                               * 1000), self.disable))
673
660
            self.expires = datetime.datetime.utcnow() + timeout
674
661
    
675
662
    def need_approval(self):
706
693
        # Start a new checker if needed
707
694
        if self.checker is None:
708
695
            # Escape attributes for the shell
709
 
            escaped_attrs = dict(
710
 
                (attr, re.escape(unicode(getattr(self, attr))))
711
 
                for attr in
712
 
                self.runtime_expansions)
 
696
            escaped_attrs = { attr:
 
697
                                  re.escape(str(getattr(self, attr)))
 
698
                              for attr in self.runtime_expansions }
713
699
            try:
714
700
                command = self.checker_command % escaped_attrs
715
701
            except TypeError as error:
796
782
    # "Set" method, so we fail early here:
797
783
    if byte_arrays and signature != "ay":
798
784
        raise ValueError("Byte arrays not supported for non-'ay'"
799
 
                         " signature {0!r}".format(signature))
 
785
                         " signature {!r}".format(signature))
800
786
    def decorator(func):
801
787
        func._dbus_is_property = True
802
788
        func._dbus_interface = dbus_interface
833
819
    """Decorator to annotate D-Bus methods, signals or properties
834
820
    Usage:
835
821
    
 
822
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
 
823
                       "org.freedesktop.DBus.Property."
 
824
                       "EmitsChangedSignal": "false"})
836
825
    @dbus_service_property("org.example.Interface", signature="b",
837
826
                           access="r")
838
 
    @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
839
 
                        "org.freedesktop.DBus.Property."
840
 
                        "EmitsChangedSignal": "false"})
841
827
    def Property_dbus_property(self):
842
828
        return dbus.Boolean(False)
843
829
    """
850
836
class DBusPropertyException(dbus.exceptions.DBusException):
851
837
    """A base class for D-Bus property-related exceptions
852
838
    """
853
 
    def __unicode__(self):
854
 
        return unicode(str(self))
855
 
 
 
839
    pass
856
840
 
857
841
class DBusPropertyAccessException(DBusPropertyException):
858
842
    """A property's access permissions disallows an operation.
881
865
        If called like _is_dbus_thing("method") it returns a function
882
866
        suitable for use as predicate to inspect.getmembers().
883
867
        """
884
 
        return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
 
868
        return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
885
869
                                   False)
886
870
    
887
871
    def _get_all_dbus_things(self, thing):
937
921
            # signatures other than "ay".
938
922
            if prop._dbus_signature != "ay":
939
923
                raise ValueError("Byte arrays not supported for non-"
940
 
                                 "'ay' signature {0!r}"
 
924
                                 "'ay' signature {!r}"
941
925
                                 .format(prop._dbus_signature))
942
926
            value = dbus.ByteArray(b''.join(chr(byte)
943
927
                                            for byte in value))
968
952
                                           value.variant_level+1)
969
953
        return dbus.Dictionary(properties, signature="sv")
970
954
    
 
955
    @dbus.service.signal(dbus.PROPERTIES_IFACE, signature="sa{sv}as")
 
956
    def PropertiesChanged(self, interface_name, changed_properties,
 
957
                          invalidated_properties):
 
958
        """Standard D-Bus PropertiesChanged() signal, see D-Bus
 
959
        standard.
 
960
        """
 
961
        pass
 
962
    
971
963
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
972
964
                         out_signature="s",
973
965
                         path_keyword='object_path',
1008
1000
                                              (prop,
1009
1001
                                               "_dbus_annotations",
1010
1002
                                               {}))
1011
 
                        for name, value in annots.iteritems():
 
1003
                        for name, value in annots.items():
1012
1004
                            ann_tag = document.createElement(
1013
1005
                                "annotation")
1014
1006
                            ann_tag.setAttribute("name", name)
1017
1009
                # Add interface annotation tags
1018
1010
                for annotation, value in dict(
1019
1011
                    itertools.chain.from_iterable(
1020
 
                        annotations().iteritems()
 
1012
                        annotations().items()
1021
1013
                        for name, annotations in
1022
1014
                        self._get_all_dbus_things("interface")
1023
1015
                        if name == if_tag.getAttribute("name")
1024
 
                        )).iteritems():
 
1016
                        )).items():
1025
1017
                    ann_tag = document.createElement("annotation")
1026
1018
                    ann_tag.setAttribute("name", annotation)
1027
1019
                    ann_tag.setAttribute("value", value)
1083
1075
    """
1084
1076
    def wrapper(cls):
1085
1077
        for orig_interface_name, alt_interface_name in (
1086
 
            alt_interface_names.iteritems()):
 
1078
            alt_interface_names.items()):
1087
1079
            attr = {}
1088
1080
            interface_names = set()
1089
1081
            # Go though all attributes of the class
1206
1198
                                        attribute.func_closure)))
1207
1199
            if deprecate:
1208
1200
                # Deprecate all alternate interfaces
1209
 
                iname="_AlternateDBusNames_interface_annotation{0}"
 
1201
                iname="_AlternateDBusNames_interface_annotation{}"
1210
1202
                for interface_name in interface_names:
1211
1203
                    @dbus_interface_annotations(interface_name)
1212
1204
                    def func(self):
1221
1213
            if interface_names:
1222
1214
                # Replace the class with a new subclass of it with
1223
1215
                # methods, signals, etc. as created above.
1224
 
                cls = type(b"{0}Alternate".format(cls.__name__),
 
1216
                cls = type(b"{}Alternate".format(cls.__name__),
1225
1217
                           (cls,), attr)
1226
1218
        return cls
1227
1219
    return wrapper
1240
1232
    runtime_expansions = (Client.runtime_expansions
1241
1233
                          + ("dbus_object_path",))
1242
1234
    
 
1235
    _interface = "se.recompile.Mandos.Client"
 
1236
    
1243
1237
    # dbus.service.Object doesn't use super(), so we can't either.
1244
1238
    
1245
1239
    def __init__(self, bus = None, *args, **kwargs):
1247
1241
        Client.__init__(self, *args, **kwargs)
1248
1242
        # Only now, when this client is initialized, can it show up on
1249
1243
        # the D-Bus
1250
 
        client_object_name = unicode(self.name).translate(
 
1244
        client_object_name = str(self.name).translate(
1251
1245
            {ord("."): ord("_"),
1252
1246
             ord("-"): ord("_")})
1253
1247
        self.dbus_object_path = (dbus.ObjectPath
1257
1251
    
1258
1252
    def notifychangeproperty(transform_func,
1259
1253
                             dbus_name, type_func=lambda x: x,
1260
 
                             variant_level=1):
 
1254
                             variant_level=1, invalidate_only=False,
 
1255
                             _interface=_interface):
1261
1256
        """ Modify a variable so that it's a property which announces
1262
1257
        its changes to DBus.
1263
1258
        
1268
1263
                   to the D-Bus.  Default: no transform
1269
1264
        variant_level: D-Bus variant level.  Default: 1
1270
1265
        """
1271
 
        attrname = "_{0}".format(dbus_name)
 
1266
        attrname = "_{}".format(dbus_name)
1272
1267
        def setter(self, value):
1273
1268
            if hasattr(self, "dbus_object_path"):
1274
1269
                if (not hasattr(self, attrname) or
1275
1270
                    type_func(getattr(self, attrname, None))
1276
1271
                    != type_func(value)):
1277
 
                    dbus_value = transform_func(type_func(value),
1278
 
                                                variant_level
1279
 
                                                =variant_level)
1280
 
                    self.PropertyChanged(dbus.String(dbus_name),
1281
 
                                         dbus_value)
 
1272
                    if invalidate_only:
 
1273
                        self.PropertiesChanged(_interface,
 
1274
                                               dbus.Dictionary(),
 
1275
                                               dbus.Array
 
1276
                                               ((dbus_name,)))
 
1277
                    else:
 
1278
                        dbus_value = transform_func(type_func(value),
 
1279
                                                    variant_level
 
1280
                                                    =variant_level)
 
1281
                        self.PropertyChanged(dbus.String(dbus_name),
 
1282
                                             dbus_value)
 
1283
                        self.PropertiesChanged(_interface,
 
1284
                                               dbus.Dictionary({
 
1285
                                    dbus.String(dbus_name):
 
1286
                                        dbus_value }), dbus.Array())
1282
1287
            setattr(self, attrname, value)
1283
1288
        
1284
1289
        return property(lambda self: getattr(self, attrname), setter)
1304
1309
    approval_delay = notifychangeproperty(dbus.UInt64,
1305
1310
                                          "ApprovalDelay",
1306
1311
                                          type_func =
1307
 
                                          timedelta_to_milliseconds)
 
1312
                                          lambda td: td.total_seconds()
 
1313
                                          * 1000)
1308
1314
    approval_duration = notifychangeproperty(
1309
1315
        dbus.UInt64, "ApprovalDuration",
1310
 
        type_func = timedelta_to_milliseconds)
 
1316
        type_func = lambda td: td.total_seconds() * 1000)
1311
1317
    host = notifychangeproperty(dbus.String, "Host")
1312
1318
    timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1313
 
                                   type_func =
1314
 
                                   timedelta_to_milliseconds)
 
1319
                                   type_func = lambda td:
 
1320
                                       td.total_seconds() * 1000)
1315
1321
    extended_timeout = notifychangeproperty(
1316
1322
        dbus.UInt64, "ExtendedTimeout",
1317
 
        type_func = timedelta_to_milliseconds)
 
1323
        type_func = lambda td: td.total_seconds() * 1000)
1318
1324
    interval = notifychangeproperty(dbus.UInt64,
1319
1325
                                    "Interval",
1320
1326
                                    type_func =
1321
 
                                    timedelta_to_milliseconds)
 
1327
                                    lambda td: td.total_seconds()
 
1328
                                    * 1000)
1322
1329
    checker_command = notifychangeproperty(dbus.String, "Checker")
 
1330
    secret = notifychangeproperty(dbus.ByteArray, "Secret",
 
1331
                                  invalidate_only=True)
1323
1332
    
1324
1333
    del notifychangeproperty
1325
1334
    
1352
1361
                                       *args, **kwargs)
1353
1362
    
1354
1363
    def start_checker(self, *args, **kwargs):
1355
 
        old_checker = self.checker
1356
 
        if self.checker is not None:
1357
 
            old_checker_pid = self.checker.pid
1358
 
        else:
1359
 
            old_checker_pid = None
 
1364
        old_checker_pid = getattr(self.checker, "pid", None)
1360
1365
        r = Client.start_checker(self, *args, **kwargs)
1361
1366
        # Only if new checker process was started
1362
1367
        if (self.checker is not None
1371
1376
    
1372
1377
    def approve(self, value=True):
1373
1378
        self.approved = value
1374
 
        gobject.timeout_add(timedelta_to_milliseconds
1375
 
                            (self.approval_duration),
1376
 
                            self._reset_approved)
 
1379
        gobject.timeout_add(int(self.approval_duration.total_seconds()
 
1380
                                * 1000), self._reset_approved)
1377
1381
        self.send_changedstate()
1378
1382
    
1379
1383
    ## D-Bus methods, signals & properties
1380
 
    _interface = "se.recompile.Mandos.Client"
1381
1384
    
1382
1385
    ## Interfaces
1383
1386
    
1384
 
    @dbus_interface_annotations(_interface)
1385
 
    def _foo(self):
1386
 
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
1387
 
                     "false"}
1388
 
    
1389
1387
    ## Signals
1390
1388
    
1391
1389
    # CheckerCompleted - signal
1401
1399
        pass
1402
1400
    
1403
1401
    # PropertyChanged - signal
 
1402
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1404
1403
    @dbus.service.signal(_interface, signature="sv")
1405
1404
    def PropertyChanged(self, property, value):
1406
1405
        "D-Bus signal"
1482
1481
                           access="readwrite")
1483
1482
    def ApprovalDelay_dbus_property(self, value=None):
1484
1483
        if value is None:       # get
1485
 
            return dbus.UInt64(self.approval_delay_milliseconds())
 
1484
            return dbus.UInt64(self.approval_delay.total_seconds()
 
1485
                               * 1000)
1486
1486
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
1487
1487
    
1488
1488
    # ApprovalDuration - property
1490
1490
                           access="readwrite")
1491
1491
    def ApprovalDuration_dbus_property(self, value=None):
1492
1492
        if value is None:       # get
1493
 
            return dbus.UInt64(timedelta_to_milliseconds(
1494
 
                    self.approval_duration))
 
1493
            return dbus.UInt64(self.approval_duration.total_seconds()
 
1494
                               * 1000)
1495
1495
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1496
1496
    
1497
1497
    # Name - property
1510
1510
    def Host_dbus_property(self, value=None):
1511
1511
        if value is None:       # get
1512
1512
            return dbus.String(self.host)
1513
 
        self.host = unicode(value)
 
1513
        self.host = str(value)
1514
1514
    
1515
1515
    # Created - property
1516
1516
    @dbus_service_property(_interface, signature="s", access="read")
1563
1563
                           access="readwrite")
1564
1564
    def Timeout_dbus_property(self, value=None):
1565
1565
        if value is None:       # get
1566
 
            return dbus.UInt64(self.timeout_milliseconds())
 
1566
            return dbus.UInt64(self.timeout.total_seconds() * 1000)
1567
1567
        old_timeout = self.timeout
1568
1568
        self.timeout = datetime.timedelta(0, 0, 0, value)
1569
1569
        # Reschedule disabling
1580
1580
                gobject.source_remove(self.disable_initiator_tag)
1581
1581
                self.disable_initiator_tag = (
1582
1582
                    gobject.timeout_add(
1583
 
                        timedelta_to_milliseconds(self.expires - now),
1584
 
                        self.disable))
 
1583
                        int((self.expires - now).total_seconds()
 
1584
                            * 1000), self.disable))
1585
1585
    
1586
1586
    # ExtendedTimeout - property
1587
1587
    @dbus_service_property(_interface, signature="t",
1588
1588
                           access="readwrite")
1589
1589
    def ExtendedTimeout_dbus_property(self, value=None):
1590
1590
        if value is None:       # get
1591
 
            return dbus.UInt64(self.extended_timeout_milliseconds())
 
1591
            return dbus.UInt64(self.extended_timeout.total_seconds()
 
1592
                               * 1000)
1592
1593
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1593
1594
    
1594
1595
    # Interval - property
1596
1597
                           access="readwrite")
1597
1598
    def Interval_dbus_property(self, value=None):
1598
1599
        if value is None:       # get
1599
 
            return dbus.UInt64(self.interval_milliseconds())
 
1600
            return dbus.UInt64(self.interval.total_seconds() * 1000)
1600
1601
        self.interval = datetime.timedelta(0, 0, 0, value)
1601
1602
        if getattr(self, "checker_initiator_tag", None) is None:
1602
1603
            return
1613
1614
    def Checker_dbus_property(self, value=None):
1614
1615
        if value is None:       # get
1615
1616
            return dbus.String(self.checker_command)
1616
 
        self.checker_command = unicode(value)
 
1617
        self.checker_command = str(value)
1617
1618
    
1618
1619
    # CheckerRunning - property
1619
1620
    @dbus_service_property(_interface, signature="b",
1635
1636
    @dbus_service_property(_interface, signature="ay",
1636
1637
                           access="write", byte_arrays=True)
1637
1638
    def Secret_dbus_property(self, value):
1638
 
        self.secret = str(value)
 
1639
        self.secret = bytes(value)
1639
1640
    
1640
1641
    del _interface
1641
1642
 
1675
1676
    def handle(self):
1676
1677
        with contextlib.closing(self.server.child_pipe) as child_pipe:
1677
1678
            logger.info("TCP connection from: %s",
1678
 
                        unicode(self.client_address))
 
1679
                        str(self.client_address))
1679
1680
            logger.debug("Pipe FD: %d",
1680
1681
                         self.server.child_pipe.fileno())
1681
1682
            
1762
1763
                        if self.server.use_dbus:
1763
1764
                            # Emit D-Bus signal
1764
1765
                            client.NeedApproval(
1765
 
                                client.approval_delay_milliseconds(),
1766
 
                                client.approved_by_default)
 
1766
                                client.approval_delay.total_seconds()
 
1767
                                * 1000, client.approved_by_default)
1767
1768
                    else:
1768
1769
                        logger.warning("Client %s was not approved",
1769
1770
                                       client.name)
1775
1776
                    #wait until timeout or approved
1776
1777
                    time = datetime.datetime.now()
1777
1778
                    client.changedstate.acquire()
1778
 
                    client.changedstate.wait(
1779
 
                        float(timedelta_to_milliseconds(delay)
1780
 
                              / 1000))
 
1779
                    client.changedstate.wait(delay.total_seconds())
1781
1780
                    client.changedstate.release()
1782
1781
                    time2 = datetime.datetime.now()
1783
1782
                    if (time2 - time) >= delay:
1982
1981
                try:
1983
1982
                    self.socket.setsockopt(socket.SOL_SOCKET,
1984
1983
                                           SO_BINDTODEVICE,
1985
 
                                           str(self.interface + '\0'))
 
1984
                                           (self.interface + "\0")
 
1985
                                           .encode("utf-8"))
1986
1986
                except socket.error as error:
1987
1987
                    if error.errno == errno.EPERM:
1988
1988
                        logger.error("No permission to bind to"
2191
2191
    token_duration = Token(re.compile(r"P"), None,
2192
2192
                           frozenset((token_year, token_month,
2193
2193
                                      token_day, token_time,
2194
 
                                      token_week))),
 
2194
                                      token_week)))
2195
2195
    # Define starting values
2196
2196
    value = datetime.timedelta() # Value so far
2197
2197
    found_token = None
2198
 
    followers = frozenset(token_duration,) # Following valid tokens
 
2198
    followers = frozenset((token_duration,)) # Following valid tokens
2199
2199
    s = duration                # String left to parse
2200
2200
    # Loop until end token is found
2201
2201
    while found_token is not token_end:
2248
2248
    timevalue = datetime.timedelta(0)
2249
2249
    for s in interval.split():
2250
2250
        try:
2251
 
            suffix = unicode(s[-1])
 
2251
            suffix = s[-1]
2252
2252
            value = int(s[:-1])
2253
2253
            if suffix == "d":
2254
2254
                delta = datetime.timedelta(value)
2261
2261
            elif suffix == "w":
2262
2262
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2263
2263
            else:
2264
 
                raise ValueError("Unknown suffix {0!r}"
 
2264
                raise ValueError("Unknown suffix {!r}"
2265
2265
                                 .format(suffix))
2266
2266
        except IndexError as e:
2267
2267
            raise ValueError(*(e.args))
2284
2284
        # Close all standard open file descriptors
2285
2285
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2286
2286
        if not stat.S_ISCHR(os.fstat(null).st_mode):
2287
 
            raise OSError(errno.ENODEV,
2288
 
                          "{0} not a character device"
 
2287
            raise OSError(errno.ENODEV, "{} not a character device"
2289
2288
                          .format(os.devnull))
2290
2289
        os.dup2(null, sys.stdin.fileno())
2291
2290
        os.dup2(null, sys.stdout.fileno())
2301
2300
    
2302
2301
    parser = argparse.ArgumentParser()
2303
2302
    parser.add_argument("-v", "--version", action="version",
2304
 
                        version = "%(prog)s {0}".format(version),
 
2303
                        version = "%(prog)s {}".format(version),
2305
2304
                        help="show version number and exit")
2306
2305
    parser.add_argument("-i", "--interface", metavar="IF",
2307
2306
                        help="Bind to interface IF")
2340
2339
                        help="Directory to save/restore state in")
2341
2340
    parser.add_argument("--foreground", action="store_true",
2342
2341
                        help="Run in foreground", default=None)
 
2342
    parser.add_argument("--no-zeroconf", action="store_false",
 
2343
                        dest="zeroconf", help="Do not use Zeroconf",
 
2344
                        default=None)
2343
2345
    
2344
2346
    options = parser.parse_args()
2345
2347
    
2354
2356
                        "port": "",
2355
2357
                        "debug": "False",
2356
2358
                        "priority":
2357
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
 
2359
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
 
2360
                        ":+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
2358
2361
                        "servicename": "Mandos",
2359
2362
                        "use_dbus": "True",
2360
2363
                        "use_ipv6": "True",
2363
2366
                        "socket": "",
2364
2367
                        "statedir": "/var/lib/mandos",
2365
2368
                        "foreground": "False",
 
2369
                        "zeroconf": "True",
2366
2370
                        }
2367
2371
    
2368
2372
    # Parse config file for server-global settings
2395
2399
    for option in ("interface", "address", "port", "debug",
2396
2400
                   "priority", "servicename", "configdir",
2397
2401
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2398
 
                   "statedir", "socket", "foreground"):
 
2402
                   "statedir", "socket", "foreground", "zeroconf"):
2399
2403
        value = getattr(options, option)
2400
2404
        if value is not None:
2401
2405
            server_settings[option] = value
2402
2406
    del options
2403
2407
    # Force all strings to be unicode
2404
2408
    for option in server_settings.keys():
2405
 
        if type(server_settings[option]) is str:
2406
 
            server_settings[option] = unicode(server_settings[option])
 
2409
        if isinstance(server_settings[option], bytes):
 
2410
            server_settings[option] = (server_settings[option]
 
2411
                                       .decode("utf-8"))
2407
2412
    # Force all boolean options to be boolean
2408
2413
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
2409
 
                   "foreground"):
 
2414
                   "foreground", "zeroconf"):
2410
2415
        server_settings[option] = bool(server_settings[option])
2411
2416
    # Debug implies foreground
2412
2417
    if server_settings["debug"]:
2415
2420
    
2416
2421
    ##################################################################
2417
2422
    
 
2423
    if (not server_settings["zeroconf"] and
 
2424
        not (server_settings["port"]
 
2425
             or server_settings["socket"] != "")):
 
2426
            parser.error("Needs port or socket to work without"
 
2427
                         " Zeroconf")
 
2428
    
2418
2429
    # For convenience
2419
2430
    debug = server_settings["debug"]
2420
2431
    debuglevel = server_settings["debuglevel"]
2423
2434
    stored_state_path = os.path.join(server_settings["statedir"],
2424
2435
                                     stored_state_file)
2425
2436
    foreground = server_settings["foreground"]
 
2437
    zeroconf = server_settings["zeroconf"]
2426
2438
    
2427
2439
    if debug:
2428
2440
        initlogger(debug, logging.DEBUG)
2435
2447
    
2436
2448
    if server_settings["servicename"] != "Mandos":
2437
2449
        syslogger.setFormatter(logging.Formatter
2438
 
                               ('Mandos ({0}) [%(process)d]:'
 
2450
                               ('Mandos ({}) [%(process)d]:'
2439
2451
                                ' %(levelname)s: %(message)s'
2440
2452
                                .format(server_settings
2441
2453
                                        ["servicename"])))
2449
2461
    global mandos_dbus_service
2450
2462
    mandos_dbus_service = None
2451
2463
    
 
2464
    socketfd = None
 
2465
    if server_settings["socket"] != "":
 
2466
        socketfd = server_settings["socket"]
2452
2467
    tcp_server = MandosServer((server_settings["address"],
2453
2468
                               server_settings["port"]),
2454
2469
                              ClientHandler,
2458
2473
                              gnutls_priority=
2459
2474
                              server_settings["priority"],
2460
2475
                              use_dbus=use_dbus,
2461
 
                              socketfd=(server_settings["socket"]
2462
 
                                        or None))
 
2476
                              socketfd=socketfd)
2463
2477
    if not foreground:
2464
2478
        pidfilename = "/run/mandos.pid"
2465
2479
        if not os.path.isdir("/run/."):
2535
2549
            use_dbus = False
2536
2550
            server_settings["use_dbus"] = False
2537
2551
            tcp_server.use_dbus = False
2538
 
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2539
 
    service = AvahiServiceToSyslog(name =
2540
 
                                   server_settings["servicename"],
2541
 
                                   servicetype = "_mandos._tcp",
2542
 
                                   protocol = protocol, bus = bus)
2543
 
    if server_settings["interface"]:
2544
 
        service.interface = (if_nametoindex
2545
 
                             (str(server_settings["interface"])))
 
2552
    if zeroconf:
 
2553
        protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
 
2554
        service = AvahiServiceToSyslog(name =
 
2555
                                       server_settings["servicename"],
 
2556
                                       servicetype = "_mandos._tcp",
 
2557
                                       protocol = protocol, bus = bus)
 
2558
        if server_settings["interface"]:
 
2559
            service.interface = (if_nametoindex
 
2560
                                 (server_settings["interface"]
 
2561
                                  .encode("utf-8")))
2546
2562
    
2547
2563
    global multiprocessing_manager
2548
2564
    multiprocessing_manager = multiprocessing.Manager()
2572
2588
            os.remove(stored_state_path)
2573
2589
        except IOError as e:
2574
2590
            if e.errno == errno.ENOENT:
2575
 
                logger.warning("Could not load persistent state: {0}"
 
2591
                logger.warning("Could not load persistent state: {}"
2576
2592
                                .format(os.strerror(e.errno)))
2577
2593
            else:
2578
2594
                logger.critical("Could not load persistent state:",
2583
2599
                           "EOFError:", exc_info=e)
2584
2600
    
2585
2601
    with PGPEngine() as pgp:
2586
 
        for client_name, client in clients_data.iteritems():
 
2602
        for client_name, client in clients_data.items():
2587
2603
            # Skip removed clients
2588
2604
            if client_name not in client_settings:
2589
2605
                continue
2614
2630
                if datetime.datetime.utcnow() >= client["expires"]:
2615
2631
                    if not client["last_checked_ok"]:
2616
2632
                        logger.warning(
2617
 
                            "disabling client {0} - Client never "
 
2633
                            "disabling client {} - Client never "
2618
2634
                            "performed a successful checker"
2619
2635
                            .format(client_name))
2620
2636
                        client["enabled"] = False
2621
2637
                    elif client["last_checker_status"] != 0:
2622
2638
                        logger.warning(
2623
 
                            "disabling client {0} - Client "
2624
 
                            "last checker failed with error code {1}"
 
2639
                            "disabling client {} - Client last"
 
2640
                            " checker failed with error code {}"
2625
2641
                            .format(client_name,
2626
2642
                                    client["last_checker_status"]))
2627
2643
                        client["enabled"] = False
2630
2646
                                             .utcnow()
2631
2647
                                             + client["timeout"])
2632
2648
                        logger.debug("Last checker succeeded,"
2633
 
                                     " keeping {0} enabled"
 
2649
                                     " keeping {} enabled"
2634
2650
                                     .format(client_name))
2635
2651
            try:
2636
2652
                client["secret"] = (
2639
2655
                                ["secret"]))
2640
2656
            except PGPError:
2641
2657
                # If decryption fails, we use secret from new settings
2642
 
                logger.debug("Failed to decrypt {0} old secret"
 
2658
                logger.debug("Failed to decrypt {} old secret"
2643
2659
                             .format(client_name))
2644
2660
                client["secret"] = (
2645
2661
                    client_settings[client_name]["secret"])
2653
2669
        clients_data[client_name] = client_settings[client_name]
2654
2670
    
2655
2671
    # Create all client objects
2656
 
    for client_name, client in clients_data.iteritems():
 
2672
    for client_name, client in clients_data.items():
2657
2673
        tcp_server.clients[client_name] = client_class(
2658
2674
            name = client_name, settings = client,
2659
2675
            server_settings = server_settings)
2666
2682
            try:
2667
2683
                with pidfile:
2668
2684
                    pid = os.getpid()
2669
 
                    pidfile.write(str(pid) + "\n".encode("utf-8"))
 
2685
                    pidfile.write("{}\n".format(pid).encode("utf-8"))
2670
2686
            except IOError:
2671
2687
                logger.error("Could not write to file %r with PID %d",
2672
2688
                             pidfilename, pid)
2718
2734
            def GetAllClientsWithProperties(self):
2719
2735
                "D-Bus method"
2720
2736
                return dbus.Dictionary(
2721
 
                    ((c.dbus_object_path, c.GetAll(""))
2722
 
                     for c in tcp_server.clients.itervalues()),
 
2737
                    { c.dbus_object_path: c.GetAll("")
 
2738
                      for c in tcp_server.clients.itervalues() },
2723
2739
                    signature="oa{sv}")
2724
2740
            
2725
2741
            @dbus.service.method(_interface, in_signature="o")
2742
2758
    
2743
2759
    def cleanup():
2744
2760
        "Cleanup function; run on exit"
2745
 
        service.cleanup()
 
2761
        if zeroconf:
 
2762
            service.cleanup()
2746
2763
        
2747
2764
        multiprocessing.active_children()
2748
2765
        wnull.close()
2762
2779
                
2763
2780
                # A list of attributes that can not be pickled
2764
2781
                # + secret.
2765
 
                exclude = set(("bus", "changedstate", "secret",
2766
 
                               "checker", "server_settings"))
 
2782
                exclude = { "bus", "changedstate", "secret",
 
2783
                            "checker", "server_settings" }
2767
2784
                for name, typ in (inspect.getmembers
2768
2785
                                  (dbus.service.Object)):
2769
2786
                    exclude.add(name)
2792
2809
                except NameError:
2793
2810
                    pass
2794
2811
            if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2795
 
                logger.warning("Could not save persistent state: {0}"
 
2812
                logger.warning("Could not save persistent state: {}"
2796
2813
                               .format(os.strerror(e.errno)))
2797
2814
            else:
2798
2815
                logger.warning("Could not save persistent state:",
2827
2844
    tcp_server.server_activate()
2828
2845
    
2829
2846
    # Find out what port we got
2830
 
    service.port = tcp_server.socket.getsockname()[1]
 
2847
    if zeroconf:
 
2848
        service.port = tcp_server.socket.getsockname()[1]
2831
2849
    if use_ipv6:
2832
2850
        logger.info("Now listening on address %r, port %d,"
2833
2851
                    " flowinfo %d, scope_id %d",
2839
2857
    #service.interface = tcp_server.socket.getsockname()[3]
2840
2858
    
2841
2859
    try:
2842
 
        # From the Avahi example code
2843
 
        try:
2844
 
            service.activate()
2845
 
        except dbus.exceptions.DBusException as error:
2846
 
            logger.critical("D-Bus Exception", exc_info=error)
2847
 
            cleanup()
2848
 
            sys.exit(1)
2849
 
        # End of Avahi example code
 
2860
        if zeroconf:
 
2861
            # From the Avahi example code
 
2862
            try:
 
2863
                service.activate()
 
2864
            except dbus.exceptions.DBusException as error:
 
2865
                logger.critical("D-Bus Exception", exc_info=error)
 
2866
                cleanup()
 
2867
                sys.exit(1)
 
2868
            # End of Avahi example code
2850
2869
        
2851
2870
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2852
2871
                             lambda *args, **kwargs: