/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.
11
11
# "AvahiService" class, and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008-2013 Teddy Hogeborn
15
 
# Copyright © 2008-2013 Björn Påhlsson
 
14
# Copyright © 2008-2014 Teddy Hogeborn
 
15
# Copyright © 2008-2014 Björn Påhlsson
16
16
17
17
# This program is free software: you can redistribute it and/or modify
18
18
# it under the terms of the GNU General Public License as published by
88
88
    except ImportError:
89
89
        SO_BINDTODEVICE = None
90
90
 
91
 
version = "1.6.2"
 
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()
95
 
syslogger = (logging.handlers.SysLogHandler
96
 
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
97
 
              address = str("/dev/log")))
 
98
syslogger = None
98
99
 
99
100
try:
100
101
    if_nametoindex = (ctypes.cdll.LoadLibrary
106
107
        SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
107
108
        with contextlib.closing(socket.socket()) as s:
108
109
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
109
 
                                struct.pack(str("16s16x"),
110
 
                                            interface))
111
 
        interface_index = struct.unpack(str("I"),
112
 
                                        ifreq[16:20])[0]
 
110
                                struct.pack(b"16s16x", interface))
 
111
        interface_index = struct.unpack("I", ifreq[16:20])[0]
113
112
        return interface_index
114
113
 
115
114
 
116
115
def initlogger(debug, level=logging.WARNING):
117
116
    """init logger and add loglevel"""
118
117
    
 
118
    global syslogger
 
119
    syslogger = (logging.handlers.SysLogHandler
 
120
                 (facility =
 
121
                  logging.handlers.SysLogHandler.LOG_DAEMON,
 
122
                  address = "/dev/log"))
119
123
    syslogger.setFormatter(logging.Formatter
120
124
                           ('Mandos [%(process)d]: %(levelname)s:'
121
125
                            ' %(message)s'))
221
225
class AvahiError(Exception):
222
226
    def __init__(self, value, *args, **kwargs):
223
227
        self.value = value
224
 
        super(AvahiError, self).__init__(value, *args, **kwargs)
225
 
    def __unicode__(self):
226
 
        return unicode(repr(self.value))
 
228
        return super(AvahiError, self).__init__(value, *args,
 
229
                                                **kwargs)
227
230
 
228
231
class AvahiServiceError(AvahiError):
229
232
    pass
272
275
        self.bus = bus
273
276
        self.entry_group_state_changed_match = None
274
277
    
275
 
    def rename(self):
 
278
    def rename(self, remove=True):
276
279
        """Derived from the Avahi example code"""
277
280
        if self.rename_count >= self.max_renames:
278
281
            logger.critical("No suitable Zeroconf service name found"
279
282
                            " after %i retries, exiting.",
280
283
                            self.rename_count)
281
284
            raise AvahiServiceError("Too many renames")
282
 
        self.name = unicode(self.server
283
 
                            .GetAlternativeServiceName(self.name))
 
285
        self.name = str(self.server
 
286
                        .GetAlternativeServiceName(self.name))
 
287
        self.rename_count += 1
284
288
        logger.info("Changing Zeroconf service name to %r ...",
285
289
                    self.name)
286
 
        self.remove()
 
290
        if remove:
 
291
            self.remove()
287
292
        try:
288
293
            self.add()
289
294
        except dbus.exceptions.DBusException as error:
290
 
            logger.critical("D-Bus Exception", exc_info=error)
291
 
            self.cleanup()
292
 
            os._exit(1)
293
 
        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)
294
303
    
295
304
    def remove(self):
296
305
        """Derived from the Avahi example code"""
334
343
            self.rename()
335
344
        elif state == avahi.ENTRY_GROUP_FAILURE:
336
345
            logger.critical("Avahi: Error in group state changed %s",
337
 
                            unicode(error))
338
 
            raise AvahiGroupError("State changed: {0!s}"
 
346
                            str(error))
 
347
            raise AvahiGroupError("State changed: {!s}"
339
348
                                  .format(error))
340
349
    
341
350
    def cleanup(self):
388
397
 
389
398
 
390
399
class AvahiServiceToSyslog(AvahiService):
391
 
    def rename(self):
 
400
    def rename(self, *args, **kwargs):
392
401
        """Add the new name to the syslog messages"""
393
 
        ret = AvahiService.rename(self)
 
402
        ret = AvahiService.rename(self, *args, **kwargs)
394
403
        syslogger.setFormatter(logging.Formatter
395
 
                               ('Mandos ({0}) [%(process)d]:'
 
404
                               ('Mandos ({}) [%(process)d]:'
396
405
                                ' %(levelname)s: %(message)s'
397
406
                                .format(self.name)))
398
407
        return ret
399
408
 
400
409
 
401
 
def timedelta_to_milliseconds(td):
402
 
    "Convert a datetime.timedelta() to milliseconds"
403
 
    return ((td.days * 24 * 60 * 60 * 1000)
404
 
            + (td.seconds * 1000)
405
 
            + (td.microseconds // 1000))
406
 
 
407
 
 
408
410
class Client(object):
409
411
    """A representation of a client host served by this server.
410
412
    
465
467
                        "enabled": "True",
466
468
                        }
467
469
    
468
 
    def timeout_milliseconds(self):
469
 
        "Return the 'timeout' attribute in milliseconds"
470
 
        return timedelta_to_milliseconds(self.timeout)
471
 
    
472
 
    def extended_timeout_milliseconds(self):
473
 
        "Return the 'extended_timeout' attribute in milliseconds"
474
 
        return timedelta_to_milliseconds(self.extended_timeout)
475
 
    
476
 
    def interval_milliseconds(self):
477
 
        "Return the 'interval' attribute in milliseconds"
478
 
        return timedelta_to_milliseconds(self.interval)
479
 
    
480
 
    def approval_delay_milliseconds(self):
481
 
        return timedelta_to_milliseconds(self.approval_delay)
482
 
    
483
470
    @staticmethod
484
471
    def config_parser(config):
485
472
        """Construct a new dict of client settings of this form:
500
487
            client["enabled"] = config.getboolean(client_name,
501
488
                                                  "enabled")
502
489
            
 
490
            # Uppercase and remove spaces from fingerprint for later
 
491
            # comparison purposes with return value from the
 
492
            # fingerprint() function
503
493
            client["fingerprint"] = (section["fingerprint"].upper()
504
494
                                     .replace(" ", ""))
505
495
            if "secret" in section:
510
500
                          "rb") as secfile:
511
501
                    client["secret"] = secfile.read()
512
502
            else:
513
 
                raise TypeError("No secret or secfile for section {0}"
 
503
                raise TypeError("No secret or secfile for section {}"
514
504
                                .format(section))
515
505
            client["timeout"] = string_to_delta(section["timeout"])
516
506
            client["extended_timeout"] = string_to_delta(
533
523
            server_settings = {}
534
524
        self.server_settings = server_settings
535
525
        # adding all client settings
536
 
        for setting, value in settings.iteritems():
 
526
        for setting, value in settings.items():
537
527
            setattr(self, setting, value)
538
528
        
539
529
        if self.enabled:
547
537
            self.expires = None
548
538
        
549
539
        logger.debug("Creating client %r", self.name)
550
 
        # Uppercase and remove spaces from fingerprint for later
551
 
        # comparison purposes with return value from the fingerprint()
552
 
        # function
553
540
        logger.debug("  Fingerprint: %s", self.fingerprint)
554
541
        self.created = settings.get("created",
555
542
                                    datetime.datetime.utcnow())
622
609
        if self.checker_initiator_tag is not None:
623
610
            gobject.source_remove(self.checker_initiator_tag)
624
611
        self.checker_initiator_tag = (gobject.timeout_add
625
 
                                      (self.interval_milliseconds(),
 
612
                                      (int(self.interval
 
613
                                           .total_seconds() * 1000),
626
614
                                       self.start_checker))
627
615
        # Schedule a disable() when 'timeout' has passed
628
616
        if self.disable_initiator_tag is not None:
629
617
            gobject.source_remove(self.disable_initiator_tag)
630
618
        self.disable_initiator_tag = (gobject.timeout_add
631
 
                                   (self.timeout_milliseconds(),
632
 
                                    self.disable))
 
619
                                      (int(self.timeout
 
620
                                           .total_seconds() * 1000),
 
621
                                       self.disable))
633
622
        # Also start a new checker *right now*.
634
623
        self.start_checker()
635
624
    
666
655
            self.disable_initiator_tag = None
667
656
        if getattr(self, "enabled", False):
668
657
            self.disable_initiator_tag = (gobject.timeout_add
669
 
                                          (timedelta_to_milliseconds
670
 
                                           (timeout), self.disable))
 
658
                                          (int(timeout.total_seconds()
 
659
                                               * 1000), self.disable))
671
660
            self.expires = datetime.datetime.utcnow() + timeout
672
661
    
673
662
    def need_approval(self):
690
679
        # If a checker exists, make sure it is not a zombie
691
680
        try:
692
681
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
693
 
        except (AttributeError, OSError) as error:
694
 
            if (isinstance(error, OSError)
695
 
                and error.errno != errno.ECHILD):
696
 
                raise error
 
682
        except AttributeError:
 
683
            pass
 
684
        except OSError as error:
 
685
            if error.errno != errno.ECHILD:
 
686
                raise
697
687
        else:
698
688
            if pid:
699
689
                logger.warning("Checker was a zombie")
703
693
        # Start a new checker if needed
704
694
        if self.checker is None:
705
695
            # Escape attributes for the shell
706
 
            escaped_attrs = dict(
707
 
                (attr, re.escape(unicode(getattr(self, attr))))
708
 
                for attr in
709
 
                self.runtime_expansions)
 
696
            escaped_attrs = { attr:
 
697
                                  re.escape(str(getattr(self, attr)))
 
698
                              for attr in self.runtime_expansions }
710
699
            try:
711
700
                command = self.checker_command % escaped_attrs
712
701
            except TypeError as error:
793
782
    # "Set" method, so we fail early here:
794
783
    if byte_arrays and signature != "ay":
795
784
        raise ValueError("Byte arrays not supported for non-'ay'"
796
 
                         " signature {0!r}".format(signature))
 
785
                         " signature {!r}".format(signature))
797
786
    def decorator(func):
798
787
        func._dbus_is_property = True
799
788
        func._dbus_interface = dbus_interface
830
819
    """Decorator to annotate D-Bus methods, signals or properties
831
820
    Usage:
832
821
    
 
822
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
 
823
                       "org.freedesktop.DBus.Property."
 
824
                       "EmitsChangedSignal": "false"})
833
825
    @dbus_service_property("org.example.Interface", signature="b",
834
826
                           access="r")
835
 
    @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
836
 
                        "org.freedesktop.DBus.Property."
837
 
                        "EmitsChangedSignal": "false"})
838
827
    def Property_dbus_property(self):
839
828
        return dbus.Boolean(False)
840
829
    """
847
836
class DBusPropertyException(dbus.exceptions.DBusException):
848
837
    """A base class for D-Bus property-related exceptions
849
838
    """
850
 
    def __unicode__(self):
851
 
        return unicode(str(self))
852
 
 
 
839
    pass
853
840
 
854
841
class DBusPropertyAccessException(DBusPropertyException):
855
842
    """A property's access permissions disallows an operation.
878
865
        If called like _is_dbus_thing("method") it returns a function
879
866
        suitable for use as predicate to inspect.getmembers().
880
867
        """
881
 
        return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
 
868
        return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
882
869
                                   False)
883
870
    
884
871
    def _get_all_dbus_things(self, thing):
933
920
            # The byte_arrays option is not supported yet on
934
921
            # signatures other than "ay".
935
922
            if prop._dbus_signature != "ay":
936
 
                raise ValueError
 
923
                raise ValueError("Byte arrays not supported for non-"
 
924
                                 "'ay' signature {!r}"
 
925
                                 .format(prop._dbus_signature))
937
926
            value = dbus.ByteArray(b''.join(chr(byte)
938
927
                                            for byte in value))
939
928
        prop(value)
963
952
                                           value.variant_level+1)
964
953
        return dbus.Dictionary(properties, signature="sv")
965
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
    
966
963
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
967
964
                         out_signature="s",
968
965
                         path_keyword='object_path',
1003
1000
                                              (prop,
1004
1001
                                               "_dbus_annotations",
1005
1002
                                               {}))
1006
 
                        for name, value in annots.iteritems():
 
1003
                        for name, value in annots.items():
1007
1004
                            ann_tag = document.createElement(
1008
1005
                                "annotation")
1009
1006
                            ann_tag.setAttribute("name", name)
1012
1009
                # Add interface annotation tags
1013
1010
                for annotation, value in dict(
1014
1011
                    itertools.chain.from_iterable(
1015
 
                        annotations().iteritems()
 
1012
                        annotations().items()
1016
1013
                        for name, annotations in
1017
1014
                        self._get_all_dbus_things("interface")
1018
1015
                        if name == if_tag.getAttribute("name")
1019
 
                        )).iteritems():
 
1016
                        )).items():
1020
1017
                    ann_tag = document.createElement("annotation")
1021
1018
                    ann_tag.setAttribute("name", annotation)
1022
1019
                    ann_tag.setAttribute("value", value)
1078
1075
    """
1079
1076
    def wrapper(cls):
1080
1077
        for orig_interface_name, alt_interface_name in (
1081
 
            alt_interface_names.iteritems()):
 
1078
            alt_interface_names.items()):
1082
1079
            attr = {}
1083
1080
            interface_names = set()
1084
1081
            # Go though all attributes of the class
1201
1198
                                        attribute.func_closure)))
1202
1199
            if deprecate:
1203
1200
                # Deprecate all alternate interfaces
1204
 
                iname="_AlternateDBusNames_interface_annotation{0}"
 
1201
                iname="_AlternateDBusNames_interface_annotation{}"
1205
1202
                for interface_name in interface_names:
1206
1203
                    @dbus_interface_annotations(interface_name)
1207
1204
                    def func(self):
1216
1213
            if interface_names:
1217
1214
                # Replace the class with a new subclass of it with
1218
1215
                # methods, signals, etc. as created above.
1219
 
                cls = type(b"{0}Alternate".format(cls.__name__),
 
1216
                cls = type(b"{}Alternate".format(cls.__name__),
1220
1217
                           (cls,), attr)
1221
1218
        return cls
1222
1219
    return wrapper
1235
1232
    runtime_expansions = (Client.runtime_expansions
1236
1233
                          + ("dbus_object_path",))
1237
1234
    
 
1235
    _interface = "se.recompile.Mandos.Client"
 
1236
    
1238
1237
    # dbus.service.Object doesn't use super(), so we can't either.
1239
1238
    
1240
1239
    def __init__(self, bus = None, *args, **kwargs):
1242
1241
        Client.__init__(self, *args, **kwargs)
1243
1242
        # Only now, when this client is initialized, can it show up on
1244
1243
        # the D-Bus
1245
 
        client_object_name = unicode(self.name).translate(
 
1244
        client_object_name = str(self.name).translate(
1246
1245
            {ord("."): ord("_"),
1247
1246
             ord("-"): ord("_")})
1248
1247
        self.dbus_object_path = (dbus.ObjectPath
1252
1251
    
1253
1252
    def notifychangeproperty(transform_func,
1254
1253
                             dbus_name, type_func=lambda x: x,
1255
 
                             variant_level=1):
 
1254
                             variant_level=1, invalidate_only=False,
 
1255
                             _interface=_interface):
1256
1256
        """ Modify a variable so that it's a property which announces
1257
1257
        its changes to DBus.
1258
1258
        
1263
1263
                   to the D-Bus.  Default: no transform
1264
1264
        variant_level: D-Bus variant level.  Default: 1
1265
1265
        """
1266
 
        attrname = "_{0}".format(dbus_name)
 
1266
        attrname = "_{}".format(dbus_name)
1267
1267
        def setter(self, value):
1268
1268
            if hasattr(self, "dbus_object_path"):
1269
1269
                if (not hasattr(self, attrname) or
1270
1270
                    type_func(getattr(self, attrname, None))
1271
1271
                    != type_func(value)):
1272
 
                    dbus_value = transform_func(type_func(value),
1273
 
                                                variant_level
1274
 
                                                =variant_level)
1275
 
                    self.PropertyChanged(dbus.String(dbus_name),
1276
 
                                         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())
1277
1287
            setattr(self, attrname, value)
1278
1288
        
1279
1289
        return property(lambda self: getattr(self, attrname), setter)
1299
1309
    approval_delay = notifychangeproperty(dbus.UInt64,
1300
1310
                                          "ApprovalDelay",
1301
1311
                                          type_func =
1302
 
                                          timedelta_to_milliseconds)
 
1312
                                          lambda td: td.total_seconds()
 
1313
                                          * 1000)
1303
1314
    approval_duration = notifychangeproperty(
1304
1315
        dbus.UInt64, "ApprovalDuration",
1305
 
        type_func = timedelta_to_milliseconds)
 
1316
        type_func = lambda td: td.total_seconds() * 1000)
1306
1317
    host = notifychangeproperty(dbus.String, "Host")
1307
1318
    timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1308
 
                                   type_func =
1309
 
                                   timedelta_to_milliseconds)
 
1319
                                   type_func = lambda td:
 
1320
                                       td.total_seconds() * 1000)
1310
1321
    extended_timeout = notifychangeproperty(
1311
1322
        dbus.UInt64, "ExtendedTimeout",
1312
 
        type_func = timedelta_to_milliseconds)
 
1323
        type_func = lambda td: td.total_seconds() * 1000)
1313
1324
    interval = notifychangeproperty(dbus.UInt64,
1314
1325
                                    "Interval",
1315
1326
                                    type_func =
1316
 
                                    timedelta_to_milliseconds)
 
1327
                                    lambda td: td.total_seconds()
 
1328
                                    * 1000)
1317
1329
    checker_command = notifychangeproperty(dbus.String, "Checker")
 
1330
    secret = notifychangeproperty(dbus.ByteArray, "Secret",
 
1331
                                  invalidate_only=True)
1318
1332
    
1319
1333
    del notifychangeproperty
1320
1334
    
1347
1361
                                       *args, **kwargs)
1348
1362
    
1349
1363
    def start_checker(self, *args, **kwargs):
1350
 
        old_checker = self.checker
1351
 
        if self.checker is not None:
1352
 
            old_checker_pid = self.checker.pid
1353
 
        else:
1354
 
            old_checker_pid = None
 
1364
        old_checker_pid = getattr(self.checker, "pid", None)
1355
1365
        r = Client.start_checker(self, *args, **kwargs)
1356
1366
        # Only if new checker process was started
1357
1367
        if (self.checker is not None
1366
1376
    
1367
1377
    def approve(self, value=True):
1368
1378
        self.approved = value
1369
 
        gobject.timeout_add(timedelta_to_milliseconds
1370
 
                            (self.approval_duration),
1371
 
                            self._reset_approved)
 
1379
        gobject.timeout_add(int(self.approval_duration.total_seconds()
 
1380
                                * 1000), self._reset_approved)
1372
1381
        self.send_changedstate()
1373
1382
    
1374
1383
    ## D-Bus methods, signals & properties
1375
 
    _interface = "se.recompile.Mandos.Client"
1376
1384
    
1377
1385
    ## Interfaces
1378
1386
    
1379
 
    @dbus_interface_annotations(_interface)
1380
 
    def _foo(self):
1381
 
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
1382
 
                     "false"}
1383
 
    
1384
1387
    ## Signals
1385
1388
    
1386
1389
    # CheckerCompleted - signal
1396
1399
        pass
1397
1400
    
1398
1401
    # PropertyChanged - signal
 
1402
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1399
1403
    @dbus.service.signal(_interface, signature="sv")
1400
1404
    def PropertyChanged(self, property, value):
1401
1405
        "D-Bus signal"
1477
1481
                           access="readwrite")
1478
1482
    def ApprovalDelay_dbus_property(self, value=None):
1479
1483
        if value is None:       # get
1480
 
            return dbus.UInt64(self.approval_delay_milliseconds())
 
1484
            return dbus.UInt64(self.approval_delay.total_seconds()
 
1485
                               * 1000)
1481
1486
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
1482
1487
    
1483
1488
    # ApprovalDuration - property
1485
1490
                           access="readwrite")
1486
1491
    def ApprovalDuration_dbus_property(self, value=None):
1487
1492
        if value is None:       # get
1488
 
            return dbus.UInt64(timedelta_to_milliseconds(
1489
 
                    self.approval_duration))
 
1493
            return dbus.UInt64(self.approval_duration.total_seconds()
 
1494
                               * 1000)
1490
1495
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1491
1496
    
1492
1497
    # Name - property
1505
1510
    def Host_dbus_property(self, value=None):
1506
1511
        if value is None:       # get
1507
1512
            return dbus.String(self.host)
1508
 
        self.host = unicode(value)
 
1513
        self.host = str(value)
1509
1514
    
1510
1515
    # Created - property
1511
1516
    @dbus_service_property(_interface, signature="s", access="read")
1558
1563
                           access="readwrite")
1559
1564
    def Timeout_dbus_property(self, value=None):
1560
1565
        if value is None:       # get
1561
 
            return dbus.UInt64(self.timeout_milliseconds())
 
1566
            return dbus.UInt64(self.timeout.total_seconds() * 1000)
1562
1567
        old_timeout = self.timeout
1563
1568
        self.timeout = datetime.timedelta(0, 0, 0, value)
1564
1569
        # Reschedule disabling
1575
1580
                gobject.source_remove(self.disable_initiator_tag)
1576
1581
                self.disable_initiator_tag = (
1577
1582
                    gobject.timeout_add(
1578
 
                        timedelta_to_milliseconds(self.expires - now),
1579
 
                        self.disable))
 
1583
                        int((self.expires - now).total_seconds()
 
1584
                            * 1000), self.disable))
1580
1585
    
1581
1586
    # ExtendedTimeout - property
1582
1587
    @dbus_service_property(_interface, signature="t",
1583
1588
                           access="readwrite")
1584
1589
    def ExtendedTimeout_dbus_property(self, value=None):
1585
1590
        if value is None:       # get
1586
 
            return dbus.UInt64(self.extended_timeout_milliseconds())
 
1591
            return dbus.UInt64(self.extended_timeout.total_seconds()
 
1592
                               * 1000)
1587
1593
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1588
1594
    
1589
1595
    # Interval - property
1591
1597
                           access="readwrite")
1592
1598
    def Interval_dbus_property(self, value=None):
1593
1599
        if value is None:       # get
1594
 
            return dbus.UInt64(self.interval_milliseconds())
 
1600
            return dbus.UInt64(self.interval.total_seconds() * 1000)
1595
1601
        self.interval = datetime.timedelta(0, 0, 0, value)
1596
1602
        if getattr(self, "checker_initiator_tag", None) is None:
1597
1603
            return
1608
1614
    def Checker_dbus_property(self, value=None):
1609
1615
        if value is None:       # get
1610
1616
            return dbus.String(self.checker_command)
1611
 
        self.checker_command = unicode(value)
 
1617
        self.checker_command = str(value)
1612
1618
    
1613
1619
    # CheckerRunning - property
1614
1620
    @dbus_service_property(_interface, signature="b",
1630
1636
    @dbus_service_property(_interface, signature="ay",
1631
1637
                           access="write", byte_arrays=True)
1632
1638
    def Secret_dbus_property(self, value):
1633
 
        self.secret = str(value)
 
1639
        self.secret = bytes(value)
1634
1640
    
1635
1641
    del _interface
1636
1642
 
1670
1676
    def handle(self):
1671
1677
        with contextlib.closing(self.server.child_pipe) as child_pipe:
1672
1678
            logger.info("TCP connection from: %s",
1673
 
                        unicode(self.client_address))
 
1679
                        str(self.client_address))
1674
1680
            logger.debug("Pipe FD: %d",
1675
1681
                         self.server.child_pipe.fileno())
1676
1682
            
1702
1708
            logger.debug("Protocol version: %r", line)
1703
1709
            try:
1704
1710
                if int(line.strip().split()[0]) > 1:
1705
 
                    raise RuntimeError
 
1711
                    raise RuntimeError(line)
1706
1712
            except (ValueError, IndexError, RuntimeError) as error:
1707
1713
                logger.error("Unknown protocol version: %s", error)
1708
1714
                return
1757
1763
                        if self.server.use_dbus:
1758
1764
                            # Emit D-Bus signal
1759
1765
                            client.NeedApproval(
1760
 
                                client.approval_delay_milliseconds(),
1761
 
                                client.approved_by_default)
 
1766
                                client.approval_delay.total_seconds()
 
1767
                                * 1000, client.approved_by_default)
1762
1768
                    else:
1763
1769
                        logger.warning("Client %s was not approved",
1764
1770
                                       client.name)
1770
1776
                    #wait until timeout or approved
1771
1777
                    time = datetime.datetime.now()
1772
1778
                    client.changedstate.acquire()
1773
 
                    client.changedstate.wait(
1774
 
                        float(timedelta_to_milliseconds(delay)
1775
 
                              / 1000))
 
1779
                    client.changedstate.wait(delay.total_seconds())
1776
1780
                    client.changedstate.release()
1777
1781
                    time2 = datetime.datetime.now()
1778
1782
                    if (time2 - time) >= delay:
1915
1919
    
1916
1920
    def add_pipe(self, parent_pipe, proc):
1917
1921
        """Dummy function; override as necessary"""
1918
 
        raise NotImplementedError
 
1922
        raise NotImplementedError()
1919
1923
 
1920
1924
 
1921
1925
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1977
1981
                try:
1978
1982
                    self.socket.setsockopt(socket.SOL_SOCKET,
1979
1983
                                           SO_BINDTODEVICE,
1980
 
                                           str(self.interface + '\0'))
 
1984
                                           (self.interface + "\0")
 
1985
                                           .encode("utf-8"))
1981
1986
                except socket.error as error:
1982
1987
                    if error.errno == errno.EPERM:
1983
1988
                        logger.error("No permission to bind to"
2186
2191
    token_duration = Token(re.compile(r"P"), None,
2187
2192
                           frozenset((token_year, token_month,
2188
2193
                                      token_day, token_time,
2189
 
                                      token_week))),
 
2194
                                      token_week)))
2190
2195
    # Define starting values
2191
2196
    value = datetime.timedelta() # Value so far
2192
2197
    found_token = None
2193
 
    followers = frozenset(token_duration,) # Following valid tokens
 
2198
    followers = frozenset((token_duration,)) # Following valid tokens
2194
2199
    s = duration                # String left to parse
2195
2200
    # Loop until end token is found
2196
2201
    while found_token is not token_end:
2243
2248
    timevalue = datetime.timedelta(0)
2244
2249
    for s in interval.split():
2245
2250
        try:
2246
 
            suffix = unicode(s[-1])
 
2251
            suffix = s[-1]
2247
2252
            value = int(s[:-1])
2248
2253
            if suffix == "d":
2249
2254
                delta = datetime.timedelta(value)
2256
2261
            elif suffix == "w":
2257
2262
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2258
2263
            else:
2259
 
                raise ValueError("Unknown suffix {0!r}"
 
2264
                raise ValueError("Unknown suffix {!r}"
2260
2265
                                 .format(suffix))
2261
 
        except (ValueError, IndexError) as e:
 
2266
        except IndexError as e:
2262
2267
            raise ValueError(*(e.args))
2263
2268
        timevalue += delta
2264
2269
    return timevalue
2279
2284
        # Close all standard open file descriptors
2280
2285
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2281
2286
        if not stat.S_ISCHR(os.fstat(null).st_mode):
2282
 
            raise OSError(errno.ENODEV,
2283
 
                          "{0} not a character device"
 
2287
            raise OSError(errno.ENODEV, "{} not a character device"
2284
2288
                          .format(os.devnull))
2285
2289
        os.dup2(null, sys.stdin.fileno())
2286
2290
        os.dup2(null, sys.stdout.fileno())
2296
2300
    
2297
2301
    parser = argparse.ArgumentParser()
2298
2302
    parser.add_argument("-v", "--version", action="version",
2299
 
                        version = "%(prog)s {0}".format(version),
 
2303
                        version = "%(prog)s {}".format(version),
2300
2304
                        help="show version number and exit")
2301
2305
    parser.add_argument("-i", "--interface", metavar="IF",
2302
2306
                        help="Bind to interface IF")
2335
2339
                        help="Directory to save/restore state in")
2336
2340
    parser.add_argument("--foreground", action="store_true",
2337
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)
2338
2345
    
2339
2346
    options = parser.parse_args()
2340
2347
    
2341
2348
    if options.check:
2342
2349
        import doctest
2343
 
        doctest.testmod()
2344
 
        sys.exit()
 
2350
        fail_count, test_count = doctest.testmod()
 
2351
        sys.exit(os.EX_OK if fail_count == 0 else 1)
2345
2352
    
2346
2353
    # Default values for config file for server-global settings
2347
2354
    server_defaults = { "interface": "",
2349
2356
                        "port": "",
2350
2357
                        "debug": "False",
2351
2358
                        "priority":
2352
 
                        "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",
2353
2361
                        "servicename": "Mandos",
2354
2362
                        "use_dbus": "True",
2355
2363
                        "use_ipv6": "True",
2358
2366
                        "socket": "",
2359
2367
                        "statedir": "/var/lib/mandos",
2360
2368
                        "foreground": "False",
 
2369
                        "zeroconf": "True",
2361
2370
                        }
2362
2371
    
2363
2372
    # Parse config file for server-global settings
2390
2399
    for option in ("interface", "address", "port", "debug",
2391
2400
                   "priority", "servicename", "configdir",
2392
2401
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2393
 
                   "statedir", "socket", "foreground"):
 
2402
                   "statedir", "socket", "foreground", "zeroconf"):
2394
2403
        value = getattr(options, option)
2395
2404
        if value is not None:
2396
2405
            server_settings[option] = value
2397
2406
    del options
2398
2407
    # Force all strings to be unicode
2399
2408
    for option in server_settings.keys():
2400
 
        if type(server_settings[option]) is str:
2401
 
            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"))
2402
2412
    # Force all boolean options to be boolean
2403
2413
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
2404
 
                   "foreground"):
 
2414
                   "foreground", "zeroconf"):
2405
2415
        server_settings[option] = bool(server_settings[option])
2406
2416
    # Debug implies foreground
2407
2417
    if server_settings["debug"]:
2410
2420
    
2411
2421
    ##################################################################
2412
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
    
2413
2429
    # For convenience
2414
2430
    debug = server_settings["debug"]
2415
2431
    debuglevel = server_settings["debuglevel"]
2418
2434
    stored_state_path = os.path.join(server_settings["statedir"],
2419
2435
                                     stored_state_file)
2420
2436
    foreground = server_settings["foreground"]
 
2437
    zeroconf = server_settings["zeroconf"]
2421
2438
    
2422
2439
    if debug:
2423
2440
        initlogger(debug, logging.DEBUG)
2430
2447
    
2431
2448
    if server_settings["servicename"] != "Mandos":
2432
2449
        syslogger.setFormatter(logging.Formatter
2433
 
                               ('Mandos ({0}) [%(process)d]:'
 
2450
                               ('Mandos ({}) [%(process)d]:'
2434
2451
                                ' %(levelname)s: %(message)s'
2435
2452
                                .format(server_settings
2436
2453
                                        ["servicename"])))
2444
2461
    global mandos_dbus_service
2445
2462
    mandos_dbus_service = None
2446
2463
    
 
2464
    socketfd = None
 
2465
    if server_settings["socket"] != "":
 
2466
        socketfd = server_settings["socket"]
2447
2467
    tcp_server = MandosServer((server_settings["address"],
2448
2468
                               server_settings["port"]),
2449
2469
                              ClientHandler,
2453
2473
                              gnutls_priority=
2454
2474
                              server_settings["priority"],
2455
2475
                              use_dbus=use_dbus,
2456
 
                              socketfd=(server_settings["socket"]
2457
 
                                        or None))
 
2476
                              socketfd=socketfd)
2458
2477
    if not foreground:
2459
2478
        pidfilename = "/run/mandos.pid"
 
2479
        if not os.path.isdir("/run/."):
 
2480
            pidfilename = "/var/run/mandos.pid"
2460
2481
        pidfile = None
2461
2482
        try:
2462
2483
            pidfile = open(pidfilename, "w")
2479
2500
        os.setuid(uid)
2480
2501
    except OSError as error:
2481
2502
        if error.errno != errno.EPERM:
2482
 
            raise error
 
2503
            raise
2483
2504
    
2484
2505
    if debug:
2485
2506
        # Enable all possible GnuTLS debugging
2528
2549
            use_dbus = False
2529
2550
            server_settings["use_dbus"] = False
2530
2551
            tcp_server.use_dbus = False
2531
 
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2532
 
    service = AvahiServiceToSyslog(name =
2533
 
                                   server_settings["servicename"],
2534
 
                                   servicetype = "_mandos._tcp",
2535
 
                                   protocol = protocol, bus = bus)
2536
 
    if server_settings["interface"]:
2537
 
        service.interface = (if_nametoindex
2538
 
                             (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")))
2539
2562
    
2540
2563
    global multiprocessing_manager
2541
2564
    multiprocessing_manager = multiprocessing.Manager()
2565
2588
            os.remove(stored_state_path)
2566
2589
        except IOError as e:
2567
2590
            if e.errno == errno.ENOENT:
2568
 
                logger.warning("Could not load persistent state: {0}"
 
2591
                logger.warning("Could not load persistent state: {}"
2569
2592
                                .format(os.strerror(e.errno)))
2570
2593
            else:
2571
2594
                logger.critical("Could not load persistent state:",
2576
2599
                           "EOFError:", exc_info=e)
2577
2600
    
2578
2601
    with PGPEngine() as pgp:
2579
 
        for client_name, client in clients_data.iteritems():
 
2602
        for client_name, client in clients_data.items():
2580
2603
            # Skip removed clients
2581
2604
            if client_name not in client_settings:
2582
2605
                continue
2607
2630
                if datetime.datetime.utcnow() >= client["expires"]:
2608
2631
                    if not client["last_checked_ok"]:
2609
2632
                        logger.warning(
2610
 
                            "disabling client {0} - Client never "
 
2633
                            "disabling client {} - Client never "
2611
2634
                            "performed a successful checker"
2612
2635
                            .format(client_name))
2613
2636
                        client["enabled"] = False
2614
2637
                    elif client["last_checker_status"] != 0:
2615
2638
                        logger.warning(
2616
 
                            "disabling client {0} - Client "
2617
 
                            "last checker failed with error code {1}"
 
2639
                            "disabling client {} - Client last"
 
2640
                            " checker failed with error code {}"
2618
2641
                            .format(client_name,
2619
2642
                                    client["last_checker_status"]))
2620
2643
                        client["enabled"] = False
2623
2646
                                             .utcnow()
2624
2647
                                             + client["timeout"])
2625
2648
                        logger.debug("Last checker succeeded,"
2626
 
                                     " keeping {0} enabled"
 
2649
                                     " keeping {} enabled"
2627
2650
                                     .format(client_name))
2628
2651
            try:
2629
2652
                client["secret"] = (
2632
2655
                                ["secret"]))
2633
2656
            except PGPError:
2634
2657
                # If decryption fails, we use secret from new settings
2635
 
                logger.debug("Failed to decrypt {0} old secret"
 
2658
                logger.debug("Failed to decrypt {} old secret"
2636
2659
                             .format(client_name))
2637
2660
                client["secret"] = (
2638
2661
                    client_settings[client_name]["secret"])
2646
2669
        clients_data[client_name] = client_settings[client_name]
2647
2670
    
2648
2671
    # Create all client objects
2649
 
    for client_name, client in clients_data.iteritems():
 
2672
    for client_name, client in clients_data.items():
2650
2673
        tcp_server.clients[client_name] = client_class(
2651
2674
            name = client_name, settings = client,
2652
2675
            server_settings = server_settings)
2659
2682
            try:
2660
2683
                with pidfile:
2661
2684
                    pid = os.getpid()
2662
 
                    pidfile.write(str(pid) + "\n".encode("utf-8"))
 
2685
                    pidfile.write("{}\n".format(pid).encode("utf-8"))
2663
2686
            except IOError:
2664
2687
                logger.error("Could not write to file %r with PID %d",
2665
2688
                             pidfilename, pid)
2711
2734
            def GetAllClientsWithProperties(self):
2712
2735
                "D-Bus method"
2713
2736
                return dbus.Dictionary(
2714
 
                    ((c.dbus_object_path, c.GetAll(""))
2715
 
                     for c in tcp_server.clients.itervalues()),
 
2737
                    { c.dbus_object_path: c.GetAll("")
 
2738
                      for c in tcp_server.clients.itervalues() },
2716
2739
                    signature="oa{sv}")
2717
2740
            
2718
2741
            @dbus.service.method(_interface, in_signature="o")
2735
2758
    
2736
2759
    def cleanup():
2737
2760
        "Cleanup function; run on exit"
2738
 
        service.cleanup()
 
2761
        if zeroconf:
 
2762
            service.cleanup()
2739
2763
        
2740
2764
        multiprocessing.active_children()
2741
2765
        wnull.close()
2755
2779
                
2756
2780
                # A list of attributes that can not be pickled
2757
2781
                # + secret.
2758
 
                exclude = set(("bus", "changedstate", "secret",
2759
 
                               "checker", "server_settings"))
 
2782
                exclude = { "bus", "changedstate", "secret",
 
2783
                            "checker", "server_settings" }
2760
2784
                for name, typ in (inspect.getmembers
2761
2785
                                  (dbus.service.Object)):
2762
2786
                    exclude.add(name)
2785
2809
                except NameError:
2786
2810
                    pass
2787
2811
            if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2788
 
                logger.warning("Could not save persistent state: {0}"
 
2812
                logger.warning("Could not save persistent state: {}"
2789
2813
                               .format(os.strerror(e.errno)))
2790
2814
            else:
2791
2815
                logger.warning("Could not save persistent state:",
2792
2816
                               exc_info=e)
2793
 
                raise e
 
2817
                raise
2794
2818
        
2795
2819
        # Delete all clients, and settings from config
2796
2820
        while tcp_server.clients:
2820
2844
    tcp_server.server_activate()
2821
2845
    
2822
2846
    # Find out what port we got
2823
 
    service.port = tcp_server.socket.getsockname()[1]
 
2847
    if zeroconf:
 
2848
        service.port = tcp_server.socket.getsockname()[1]
2824
2849
    if use_ipv6:
2825
2850
        logger.info("Now listening on address %r, port %d,"
2826
2851
                    " flowinfo %d, scope_id %d",
2832
2857
    #service.interface = tcp_server.socket.getsockname()[3]
2833
2858
    
2834
2859
    try:
2835
 
        # From the Avahi example code
2836
 
        try:
2837
 
            service.activate()
2838
 
        except dbus.exceptions.DBusException as error:
2839
 
            logger.critical("D-Bus Exception", exc_info=error)
2840
 
            cleanup()
2841
 
            sys.exit(1)
2842
 
        # 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
2843
2869
        
2844
2870
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2845
2871
                             lambda *args, **kwargs: