/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-2012 Teddy Hogeborn
15
 
# Copyright © 2008-2012 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.0"
 
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'))
172
176
    def password_encode(self, password):
173
177
        # Passphrase can not be empty and can not contain newlines or
174
178
        # NUL bytes.  So we prefix it and hex encode it.
175
 
        return b"mandos" + binascii.hexlify(password)
 
179
        encoded = b"mandos" + binascii.hexlify(password)
 
180
        if len(encoded) > 2048:
 
181
            # GnuPG can't handle long passwords, so encode differently
 
182
            encoded = (b"mandos" + password.replace(b"\\", b"\\\\")
 
183
                       .replace(b"\n", b"\\n")
 
184
                       .replace(b"\0", b"\\x00"))
 
185
        return encoded
176
186
    
177
187
    def encrypt(self, data, password):
178
188
        passphrase = self.password_encode(password)
215
225
class AvahiError(Exception):
216
226
    def __init__(self, value, *args, **kwargs):
217
227
        self.value = value
218
 
        super(AvahiError, self).__init__(value, *args, **kwargs)
219
 
    def __unicode__(self):
220
 
        return unicode(repr(self.value))
 
228
        return super(AvahiError, self).__init__(value, *args,
 
229
                                                **kwargs)
221
230
 
222
231
class AvahiServiceError(AvahiError):
223
232
    pass
266
275
        self.bus = bus
267
276
        self.entry_group_state_changed_match = None
268
277
    
269
 
    def rename(self):
 
278
    def rename(self, remove=True):
270
279
        """Derived from the Avahi example code"""
271
280
        if self.rename_count >= self.max_renames:
272
281
            logger.critical("No suitable Zeroconf service name found"
273
282
                            " after %i retries, exiting.",
274
283
                            self.rename_count)
275
284
            raise AvahiServiceError("Too many renames")
276
 
        self.name = unicode(self.server
277
 
                            .GetAlternativeServiceName(self.name))
 
285
        self.name = str(self.server
 
286
                        .GetAlternativeServiceName(self.name))
 
287
        self.rename_count += 1
278
288
        logger.info("Changing Zeroconf service name to %r ...",
279
289
                    self.name)
280
 
        self.remove()
 
290
        if remove:
 
291
            self.remove()
281
292
        try:
282
293
            self.add()
283
294
        except dbus.exceptions.DBusException as error:
284
 
            logger.critical("D-Bus Exception", exc_info=error)
285
 
            self.cleanup()
286
 
            os._exit(1)
287
 
        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)
288
303
    
289
304
    def remove(self):
290
305
        """Derived from the Avahi example code"""
328
343
            self.rename()
329
344
        elif state == avahi.ENTRY_GROUP_FAILURE:
330
345
            logger.critical("Avahi: Error in group state changed %s",
331
 
                            unicode(error))
332
 
            raise AvahiGroupError("State changed: {0!s}"
 
346
                            str(error))
 
347
            raise AvahiGroupError("State changed: {!s}"
333
348
                                  .format(error))
334
349
    
335
350
    def cleanup(self):
382
397
 
383
398
 
384
399
class AvahiServiceToSyslog(AvahiService):
385
 
    def rename(self):
 
400
    def rename(self, *args, **kwargs):
386
401
        """Add the new name to the syslog messages"""
387
 
        ret = AvahiService.rename(self)
 
402
        ret = AvahiService.rename(self, *args, **kwargs)
388
403
        syslogger.setFormatter(logging.Formatter
389
 
                               ('Mandos ({0}) [%(process)d]:'
 
404
                               ('Mandos ({}) [%(process)d]:'
390
405
                                ' %(levelname)s: %(message)s'
391
406
                                .format(self.name)))
392
407
        return ret
393
408
 
394
409
 
395
 
def timedelta_to_milliseconds(td):
396
 
    "Convert a datetime.timedelta() to milliseconds"
397
 
    return ((td.days * 24 * 60 * 60 * 1000)
398
 
            + (td.seconds * 1000)
399
 
            + (td.microseconds // 1000))
400
 
 
401
 
 
402
410
class Client(object):
403
411
    """A representation of a client host served by this server.
404
412
    
459
467
                        "enabled": "True",
460
468
                        }
461
469
    
462
 
    def timeout_milliseconds(self):
463
 
        "Return the 'timeout' attribute in milliseconds"
464
 
        return timedelta_to_milliseconds(self.timeout)
465
 
    
466
 
    def extended_timeout_milliseconds(self):
467
 
        "Return the 'extended_timeout' attribute in milliseconds"
468
 
        return timedelta_to_milliseconds(self.extended_timeout)
469
 
    
470
 
    def interval_milliseconds(self):
471
 
        "Return the 'interval' attribute in milliseconds"
472
 
        return timedelta_to_milliseconds(self.interval)
473
 
    
474
 
    def approval_delay_milliseconds(self):
475
 
        return timedelta_to_milliseconds(self.approval_delay)
476
 
    
477
470
    @staticmethod
478
471
    def config_parser(config):
479
472
        """Construct a new dict of client settings of this form:
494
487
            client["enabled"] = config.getboolean(client_name,
495
488
                                                  "enabled")
496
489
            
 
490
            # Uppercase and remove spaces from fingerprint for later
 
491
            # comparison purposes with return value from the
 
492
            # fingerprint() function
497
493
            client["fingerprint"] = (section["fingerprint"].upper()
498
494
                                     .replace(" ", ""))
499
495
            if "secret" in section:
504
500
                          "rb") as secfile:
505
501
                    client["secret"] = secfile.read()
506
502
            else:
507
 
                raise TypeError("No secret or secfile for section {0}"
 
503
                raise TypeError("No secret or secfile for section {}"
508
504
                                .format(section))
509
505
            client["timeout"] = string_to_delta(section["timeout"])
510
506
            client["extended_timeout"] = string_to_delta(
527
523
            server_settings = {}
528
524
        self.server_settings = server_settings
529
525
        # adding all client settings
530
 
        for setting, value in settings.iteritems():
 
526
        for setting, value in settings.items():
531
527
            setattr(self, setting, value)
532
528
        
533
529
        if self.enabled:
541
537
            self.expires = None
542
538
        
543
539
        logger.debug("Creating client %r", self.name)
544
 
        # Uppercase and remove spaces from fingerprint for later
545
 
        # comparison purposes with return value from the fingerprint()
546
 
        # function
547
540
        logger.debug("  Fingerprint: %s", self.fingerprint)
548
541
        self.created = settings.get("created",
549
542
                                    datetime.datetime.utcnow())
616
609
        if self.checker_initiator_tag is not None:
617
610
            gobject.source_remove(self.checker_initiator_tag)
618
611
        self.checker_initiator_tag = (gobject.timeout_add
619
 
                                      (self.interval_milliseconds(),
 
612
                                      (int(self.interval
 
613
                                           .total_seconds() * 1000),
620
614
                                       self.start_checker))
621
615
        # Schedule a disable() when 'timeout' has passed
622
616
        if self.disable_initiator_tag is not None:
623
617
            gobject.source_remove(self.disable_initiator_tag)
624
618
        self.disable_initiator_tag = (gobject.timeout_add
625
 
                                   (self.timeout_milliseconds(),
626
 
                                    self.disable))
 
619
                                      (int(self.timeout
 
620
                                           .total_seconds() * 1000),
 
621
                                       self.disable))
627
622
        # Also start a new checker *right now*.
628
623
        self.start_checker()
629
624
    
660
655
            self.disable_initiator_tag = None
661
656
        if getattr(self, "enabled", False):
662
657
            self.disable_initiator_tag = (gobject.timeout_add
663
 
                                          (timedelta_to_milliseconds
664
 
                                           (timeout), self.disable))
 
658
                                          (int(timeout.total_seconds()
 
659
                                               * 1000), self.disable))
665
660
            self.expires = datetime.datetime.utcnow() + timeout
666
661
    
667
662
    def need_approval(self):
684
679
        # If a checker exists, make sure it is not a zombie
685
680
        try:
686
681
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
687
 
        except (AttributeError, OSError) as error:
688
 
            if (isinstance(error, OSError)
689
 
                and error.errno != errno.ECHILD):
690
 
                raise error
 
682
        except AttributeError:
 
683
            pass
 
684
        except OSError as error:
 
685
            if error.errno != errno.ECHILD:
 
686
                raise
691
687
        else:
692
688
            if pid:
693
689
                logger.warning("Checker was a zombie")
697
693
        # Start a new checker if needed
698
694
        if self.checker is None:
699
695
            # Escape attributes for the shell
700
 
            escaped_attrs = dict(
701
 
                (attr, re.escape(unicode(getattr(self, attr))))
702
 
                for attr in
703
 
                self.runtime_expansions)
 
696
            escaped_attrs = { attr:
 
697
                                  re.escape(str(getattr(self, attr)))
 
698
                              for attr in self.runtime_expansions }
704
699
            try:
705
700
                command = self.checker_command % escaped_attrs
706
701
            except TypeError as error:
787
782
    # "Set" method, so we fail early here:
788
783
    if byte_arrays and signature != "ay":
789
784
        raise ValueError("Byte arrays not supported for non-'ay'"
790
 
                         " signature {0!r}".format(signature))
 
785
                         " signature {!r}".format(signature))
791
786
    def decorator(func):
792
787
        func._dbus_is_property = True
793
788
        func._dbus_interface = dbus_interface
824
819
    """Decorator to annotate D-Bus methods, signals or properties
825
820
    Usage:
826
821
    
 
822
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true",
 
823
                       "org.freedesktop.DBus.Property."
 
824
                       "EmitsChangedSignal": "false"})
827
825
    @dbus_service_property("org.example.Interface", signature="b",
828
826
                           access="r")
829
 
    @dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
830
 
                        "org.freedesktop.DBus.Property."
831
 
                        "EmitsChangedSignal": "false"})
832
827
    def Property_dbus_property(self):
833
828
        return dbus.Boolean(False)
834
829
    """
841
836
class DBusPropertyException(dbus.exceptions.DBusException):
842
837
    """A base class for D-Bus property-related exceptions
843
838
    """
844
 
    def __unicode__(self):
845
 
        return unicode(str(self))
846
 
 
 
839
    pass
847
840
 
848
841
class DBusPropertyAccessException(DBusPropertyException):
849
842
    """A property's access permissions disallows an operation.
872
865
        If called like _is_dbus_thing("method") it returns a function
873
866
        suitable for use as predicate to inspect.getmembers().
874
867
        """
875
 
        return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
 
868
        return lambda obj: getattr(obj, "_dbus_is_{}".format(thing),
876
869
                                   False)
877
870
    
878
871
    def _get_all_dbus_things(self, thing):
927
920
            # The byte_arrays option is not supported yet on
928
921
            # signatures other than "ay".
929
922
            if prop._dbus_signature != "ay":
930
 
                raise ValueError
 
923
                raise ValueError("Byte arrays not supported for non-"
 
924
                                 "'ay' signature {!r}"
 
925
                                 .format(prop._dbus_signature))
931
926
            value = dbus.ByteArray(b''.join(chr(byte)
932
927
                                            for byte in value))
933
928
        prop(value)
957
952
                                           value.variant_level+1)
958
953
        return dbus.Dictionary(properties, signature="sv")
959
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
    
960
963
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
961
964
                         out_signature="s",
962
965
                         path_keyword='object_path',
997
1000
                                              (prop,
998
1001
                                               "_dbus_annotations",
999
1002
                                               {}))
1000
 
                        for name, value in annots.iteritems():
 
1003
                        for name, value in annots.items():
1001
1004
                            ann_tag = document.createElement(
1002
1005
                                "annotation")
1003
1006
                            ann_tag.setAttribute("name", name)
1006
1009
                # Add interface annotation tags
1007
1010
                for annotation, value in dict(
1008
1011
                    itertools.chain.from_iterable(
1009
 
                        annotations().iteritems()
 
1012
                        annotations().items()
1010
1013
                        for name, annotations in
1011
1014
                        self._get_all_dbus_things("interface")
1012
1015
                        if name == if_tag.getAttribute("name")
1013
 
                        )).iteritems():
 
1016
                        )).items():
1014
1017
                    ann_tag = document.createElement("annotation")
1015
1018
                    ann_tag.setAttribute("name", annotation)
1016
1019
                    ann_tag.setAttribute("value", value)
1072
1075
    """
1073
1076
    def wrapper(cls):
1074
1077
        for orig_interface_name, alt_interface_name in (
1075
 
            alt_interface_names.iteritems()):
 
1078
            alt_interface_names.items()):
1076
1079
            attr = {}
1077
1080
            interface_names = set()
1078
1081
            # Go though all attributes of the class
1195
1198
                                        attribute.func_closure)))
1196
1199
            if deprecate:
1197
1200
                # Deprecate all alternate interfaces
1198
 
                iname="_AlternateDBusNames_interface_annotation{0}"
 
1201
                iname="_AlternateDBusNames_interface_annotation{}"
1199
1202
                for interface_name in interface_names:
1200
1203
                    @dbus_interface_annotations(interface_name)
1201
1204
                    def func(self):
1210
1213
            if interface_names:
1211
1214
                # Replace the class with a new subclass of it with
1212
1215
                # methods, signals, etc. as created above.
1213
 
                cls = type(b"{0}Alternate".format(cls.__name__),
 
1216
                cls = type(b"{}Alternate".format(cls.__name__),
1214
1217
                           (cls,), attr)
1215
1218
        return cls
1216
1219
    return wrapper
1229
1232
    runtime_expansions = (Client.runtime_expansions
1230
1233
                          + ("dbus_object_path",))
1231
1234
    
 
1235
    _interface = "se.recompile.Mandos.Client"
 
1236
    
1232
1237
    # dbus.service.Object doesn't use super(), so we can't either.
1233
1238
    
1234
1239
    def __init__(self, bus = None, *args, **kwargs):
1236
1241
        Client.__init__(self, *args, **kwargs)
1237
1242
        # Only now, when this client is initialized, can it show up on
1238
1243
        # the D-Bus
1239
 
        client_object_name = unicode(self.name).translate(
 
1244
        client_object_name = str(self.name).translate(
1240
1245
            {ord("."): ord("_"),
1241
1246
             ord("-"): ord("_")})
1242
1247
        self.dbus_object_path = (dbus.ObjectPath
1246
1251
    
1247
1252
    def notifychangeproperty(transform_func,
1248
1253
                             dbus_name, type_func=lambda x: x,
1249
 
                             variant_level=1):
 
1254
                             variant_level=1, invalidate_only=False,
 
1255
                             _interface=_interface):
1250
1256
        """ Modify a variable so that it's a property which announces
1251
1257
        its changes to DBus.
1252
1258
        
1257
1263
                   to the D-Bus.  Default: no transform
1258
1264
        variant_level: D-Bus variant level.  Default: 1
1259
1265
        """
1260
 
        attrname = "_{0}".format(dbus_name)
 
1266
        attrname = "_{}".format(dbus_name)
1261
1267
        def setter(self, value):
1262
1268
            if hasattr(self, "dbus_object_path"):
1263
1269
                if (not hasattr(self, attrname) or
1264
1270
                    type_func(getattr(self, attrname, None))
1265
1271
                    != type_func(value)):
1266
 
                    dbus_value = transform_func(type_func(value),
1267
 
                                                variant_level
1268
 
                                                =variant_level)
1269
 
                    self.PropertyChanged(dbus.String(dbus_name),
1270
 
                                         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())
1271
1287
            setattr(self, attrname, value)
1272
1288
        
1273
1289
        return property(lambda self: getattr(self, attrname), setter)
1293
1309
    approval_delay = notifychangeproperty(dbus.UInt64,
1294
1310
                                          "ApprovalDelay",
1295
1311
                                          type_func =
1296
 
                                          timedelta_to_milliseconds)
 
1312
                                          lambda td: td.total_seconds()
 
1313
                                          * 1000)
1297
1314
    approval_duration = notifychangeproperty(
1298
1315
        dbus.UInt64, "ApprovalDuration",
1299
 
        type_func = timedelta_to_milliseconds)
 
1316
        type_func = lambda td: td.total_seconds() * 1000)
1300
1317
    host = notifychangeproperty(dbus.String, "Host")
1301
1318
    timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1302
 
                                   type_func =
1303
 
                                   timedelta_to_milliseconds)
 
1319
                                   type_func = lambda td:
 
1320
                                       td.total_seconds() * 1000)
1304
1321
    extended_timeout = notifychangeproperty(
1305
1322
        dbus.UInt64, "ExtendedTimeout",
1306
 
        type_func = timedelta_to_milliseconds)
 
1323
        type_func = lambda td: td.total_seconds() * 1000)
1307
1324
    interval = notifychangeproperty(dbus.UInt64,
1308
1325
                                    "Interval",
1309
1326
                                    type_func =
1310
 
                                    timedelta_to_milliseconds)
 
1327
                                    lambda td: td.total_seconds()
 
1328
                                    * 1000)
1311
1329
    checker_command = notifychangeproperty(dbus.String, "Checker")
 
1330
    secret = notifychangeproperty(dbus.ByteArray, "Secret",
 
1331
                                  invalidate_only=True)
1312
1332
    
1313
1333
    del notifychangeproperty
1314
1334
    
1341
1361
                                       *args, **kwargs)
1342
1362
    
1343
1363
    def start_checker(self, *args, **kwargs):
1344
 
        old_checker = self.checker
1345
 
        if self.checker is not None:
1346
 
            old_checker_pid = self.checker.pid
1347
 
        else:
1348
 
            old_checker_pid = None
 
1364
        old_checker_pid = getattr(self.checker, "pid", None)
1349
1365
        r = Client.start_checker(self, *args, **kwargs)
1350
1366
        # Only if new checker process was started
1351
1367
        if (self.checker is not None
1360
1376
    
1361
1377
    def approve(self, value=True):
1362
1378
        self.approved = value
1363
 
        gobject.timeout_add(timedelta_to_milliseconds
1364
 
                            (self.approval_duration),
1365
 
                            self._reset_approved)
 
1379
        gobject.timeout_add(int(self.approval_duration.total_seconds()
 
1380
                                * 1000), self._reset_approved)
1366
1381
        self.send_changedstate()
1367
1382
    
1368
1383
    ## D-Bus methods, signals & properties
1369
 
    _interface = "se.recompile.Mandos.Client"
1370
1384
    
1371
1385
    ## Interfaces
1372
1386
    
1373
 
    @dbus_interface_annotations(_interface)
1374
 
    def _foo(self):
1375
 
        return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
1376
 
                     "false"}
1377
 
    
1378
1387
    ## Signals
1379
1388
    
1380
1389
    # CheckerCompleted - signal
1390
1399
        pass
1391
1400
    
1392
1401
    # PropertyChanged - signal
 
1402
    @dbus_annotations({"org.freedesktop.DBus.Deprecated": "true"})
1393
1403
    @dbus.service.signal(_interface, signature="sv")
1394
1404
    def PropertyChanged(self, property, value):
1395
1405
        "D-Bus signal"
1471
1481
                           access="readwrite")
1472
1482
    def ApprovalDelay_dbus_property(self, value=None):
1473
1483
        if value is None:       # get
1474
 
            return dbus.UInt64(self.approval_delay_milliseconds())
 
1484
            return dbus.UInt64(self.approval_delay.total_seconds()
 
1485
                               * 1000)
1475
1486
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
1476
1487
    
1477
1488
    # ApprovalDuration - property
1479
1490
                           access="readwrite")
1480
1491
    def ApprovalDuration_dbus_property(self, value=None):
1481
1492
        if value is None:       # get
1482
 
            return dbus.UInt64(timedelta_to_milliseconds(
1483
 
                    self.approval_duration))
 
1493
            return dbus.UInt64(self.approval_duration.total_seconds()
 
1494
                               * 1000)
1484
1495
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
1485
1496
    
1486
1497
    # Name - property
1499
1510
    def Host_dbus_property(self, value=None):
1500
1511
        if value is None:       # get
1501
1512
            return dbus.String(self.host)
1502
 
        self.host = unicode(value)
 
1513
        self.host = str(value)
1503
1514
    
1504
1515
    # Created - property
1505
1516
    @dbus_service_property(_interface, signature="s", access="read")
1552
1563
                           access="readwrite")
1553
1564
    def Timeout_dbus_property(self, value=None):
1554
1565
        if value is None:       # get
1555
 
            return dbus.UInt64(self.timeout_milliseconds())
 
1566
            return dbus.UInt64(self.timeout.total_seconds() * 1000)
1556
1567
        old_timeout = self.timeout
1557
1568
        self.timeout = datetime.timedelta(0, 0, 0, value)
1558
1569
        # Reschedule disabling
1569
1580
                gobject.source_remove(self.disable_initiator_tag)
1570
1581
                self.disable_initiator_tag = (
1571
1582
                    gobject.timeout_add(
1572
 
                        timedelta_to_milliseconds(self.expires - now),
1573
 
                        self.disable))
 
1583
                        int((self.expires - now).total_seconds()
 
1584
                            * 1000), self.disable))
1574
1585
    
1575
1586
    # ExtendedTimeout - property
1576
1587
    @dbus_service_property(_interface, signature="t",
1577
1588
                           access="readwrite")
1578
1589
    def ExtendedTimeout_dbus_property(self, value=None):
1579
1590
        if value is None:       # get
1580
 
            return dbus.UInt64(self.extended_timeout_milliseconds())
 
1591
            return dbus.UInt64(self.extended_timeout.total_seconds()
 
1592
                               * 1000)
1581
1593
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1582
1594
    
1583
1595
    # Interval - property
1585
1597
                           access="readwrite")
1586
1598
    def Interval_dbus_property(self, value=None):
1587
1599
        if value is None:       # get
1588
 
            return dbus.UInt64(self.interval_milliseconds())
 
1600
            return dbus.UInt64(self.interval.total_seconds() * 1000)
1589
1601
        self.interval = datetime.timedelta(0, 0, 0, value)
1590
1602
        if getattr(self, "checker_initiator_tag", None) is None:
1591
1603
            return
1602
1614
    def Checker_dbus_property(self, value=None):
1603
1615
        if value is None:       # get
1604
1616
            return dbus.String(self.checker_command)
1605
 
        self.checker_command = unicode(value)
 
1617
        self.checker_command = str(value)
1606
1618
    
1607
1619
    # CheckerRunning - property
1608
1620
    @dbus_service_property(_interface, signature="b",
1624
1636
    @dbus_service_property(_interface, signature="ay",
1625
1637
                           access="write", byte_arrays=True)
1626
1638
    def Secret_dbus_property(self, value):
1627
 
        self.secret = str(value)
 
1639
        self.secret = bytes(value)
1628
1640
    
1629
1641
    del _interface
1630
1642
 
1664
1676
    def handle(self):
1665
1677
        with contextlib.closing(self.server.child_pipe) as child_pipe:
1666
1678
            logger.info("TCP connection from: %s",
1667
 
                        unicode(self.client_address))
 
1679
                        str(self.client_address))
1668
1680
            logger.debug("Pipe FD: %d",
1669
1681
                         self.server.child_pipe.fileno())
1670
1682
            
1696
1708
            logger.debug("Protocol version: %r", line)
1697
1709
            try:
1698
1710
                if int(line.strip().split()[0]) > 1:
1699
 
                    raise RuntimeError
 
1711
                    raise RuntimeError(line)
1700
1712
            except (ValueError, IndexError, RuntimeError) as error:
1701
1713
                logger.error("Unknown protocol version: %s", error)
1702
1714
                return
1751
1763
                        if self.server.use_dbus:
1752
1764
                            # Emit D-Bus signal
1753
1765
                            client.NeedApproval(
1754
 
                                client.approval_delay_milliseconds(),
1755
 
                                client.approved_by_default)
 
1766
                                client.approval_delay.total_seconds()
 
1767
                                * 1000, client.approved_by_default)
1756
1768
                    else:
1757
1769
                        logger.warning("Client %s was not approved",
1758
1770
                                       client.name)
1764
1776
                    #wait until timeout or approved
1765
1777
                    time = datetime.datetime.now()
1766
1778
                    client.changedstate.acquire()
1767
 
                    client.changedstate.wait(
1768
 
                        float(timedelta_to_milliseconds(delay)
1769
 
                              / 1000))
 
1779
                    client.changedstate.wait(delay.total_seconds())
1770
1780
                    client.changedstate.release()
1771
1781
                    time2 = datetime.datetime.now()
1772
1782
                    if (time2 - time) >= delay:
1909
1919
    
1910
1920
    def add_pipe(self, parent_pipe, proc):
1911
1921
        """Dummy function; override as necessary"""
1912
 
        raise NotImplementedError
 
1922
        raise NotImplementedError()
1913
1923
 
1914
1924
 
1915
1925
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1971
1981
                try:
1972
1982
                    self.socket.setsockopt(socket.SOL_SOCKET,
1973
1983
                                           SO_BINDTODEVICE,
1974
 
                                           str(self.interface + '\0'))
 
1984
                                           (self.interface + "\0")
 
1985
                                           .encode("utf-8"))
1975
1986
                except socket.error as error:
1976
1987
                    if error.errno == errno.EPERM:
1977
1988
                        logger.error("No permission to bind to"
2180
2191
    token_duration = Token(re.compile(r"P"), None,
2181
2192
                           frozenset((token_year, token_month,
2182
2193
                                      token_day, token_time,
2183
 
                                      token_week))),
 
2194
                                      token_week)))
2184
2195
    # Define starting values
2185
2196
    value = datetime.timedelta() # Value so far
2186
2197
    found_token = None
2187
 
    followers = frozenset(token_duration,) # Following valid tokens
 
2198
    followers = frozenset((token_duration,)) # Following valid tokens
2188
2199
    s = duration                # String left to parse
2189
2200
    # Loop until end token is found
2190
2201
    while found_token is not token_end:
2237
2248
    timevalue = datetime.timedelta(0)
2238
2249
    for s in interval.split():
2239
2250
        try:
2240
 
            suffix = unicode(s[-1])
 
2251
            suffix = s[-1]
2241
2252
            value = int(s[:-1])
2242
2253
            if suffix == "d":
2243
2254
                delta = datetime.timedelta(value)
2250
2261
            elif suffix == "w":
2251
2262
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
2252
2263
            else:
2253
 
                raise ValueError("Unknown suffix {0!r}"
 
2264
                raise ValueError("Unknown suffix {!r}"
2254
2265
                                 .format(suffix))
2255
 
        except (ValueError, IndexError) as e:
 
2266
        except IndexError as e:
2256
2267
            raise ValueError(*(e.args))
2257
2268
        timevalue += delta
2258
2269
    return timevalue
2273
2284
        # Close all standard open file descriptors
2274
2285
        null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2275
2286
        if not stat.S_ISCHR(os.fstat(null).st_mode):
2276
 
            raise OSError(errno.ENODEV,
2277
 
                          "{0} not a character device"
 
2287
            raise OSError(errno.ENODEV, "{} not a character device"
2278
2288
                          .format(os.devnull))
2279
2289
        os.dup2(null, sys.stdin.fileno())
2280
2290
        os.dup2(null, sys.stdout.fileno())
2290
2300
    
2291
2301
    parser = argparse.ArgumentParser()
2292
2302
    parser.add_argument("-v", "--version", action="version",
2293
 
                        version = "%(prog)s {0}".format(version),
 
2303
                        version = "%(prog)s {}".format(version),
2294
2304
                        help="show version number and exit")
2295
2305
    parser.add_argument("-i", "--interface", metavar="IF",
2296
2306
                        help="Bind to interface IF")
2329
2339
                        help="Directory to save/restore state in")
2330
2340
    parser.add_argument("--foreground", action="store_true",
2331
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)
2332
2345
    
2333
2346
    options = parser.parse_args()
2334
2347
    
2335
2348
    if options.check:
2336
2349
        import doctest
2337
 
        doctest.testmod()
2338
 
        sys.exit()
 
2350
        fail_count, test_count = doctest.testmod()
 
2351
        sys.exit(os.EX_OK if fail_count == 0 else 1)
2339
2352
    
2340
2353
    # Default values for config file for server-global settings
2341
2354
    server_defaults = { "interface": "",
2343
2356
                        "port": "",
2344
2357
                        "debug": "False",
2345
2358
                        "priority":
2346
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:+SIGN-RSA-SHA224",
 
2359
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA"
 
2360
                        ":+SIGN-RSA-SHA224:+SIGN-RSA-RMD160",
2347
2361
                        "servicename": "Mandos",
2348
2362
                        "use_dbus": "True",
2349
2363
                        "use_ipv6": "True",
2352
2366
                        "socket": "",
2353
2367
                        "statedir": "/var/lib/mandos",
2354
2368
                        "foreground": "False",
 
2369
                        "zeroconf": "True",
2355
2370
                        }
2356
2371
    
2357
2372
    # Parse config file for server-global settings
2384
2399
    for option in ("interface", "address", "port", "debug",
2385
2400
                   "priority", "servicename", "configdir",
2386
2401
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2387
 
                   "statedir", "socket", "foreground"):
 
2402
                   "statedir", "socket", "foreground", "zeroconf"):
2388
2403
        value = getattr(options, option)
2389
2404
        if value is not None:
2390
2405
            server_settings[option] = value
2391
2406
    del options
2392
2407
    # Force all strings to be unicode
2393
2408
    for option in server_settings.keys():
2394
 
        if type(server_settings[option]) is str:
2395
 
            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"))
2396
2412
    # Force all boolean options to be boolean
2397
2413
    for option in ("debug", "use_dbus", "use_ipv6", "restore",
2398
 
                   "foreground"):
 
2414
                   "foreground", "zeroconf"):
2399
2415
        server_settings[option] = bool(server_settings[option])
2400
2416
    # Debug implies foreground
2401
2417
    if server_settings["debug"]:
2404
2420
    
2405
2421
    ##################################################################
2406
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
    
2407
2429
    # For convenience
2408
2430
    debug = server_settings["debug"]
2409
2431
    debuglevel = server_settings["debuglevel"]
2412
2434
    stored_state_path = os.path.join(server_settings["statedir"],
2413
2435
                                     stored_state_file)
2414
2436
    foreground = server_settings["foreground"]
 
2437
    zeroconf = server_settings["zeroconf"]
2415
2438
    
2416
2439
    if debug:
2417
2440
        initlogger(debug, logging.DEBUG)
2424
2447
    
2425
2448
    if server_settings["servicename"] != "Mandos":
2426
2449
        syslogger.setFormatter(logging.Formatter
2427
 
                               ('Mandos ({0}) [%(process)d]:'
 
2450
                               ('Mandos ({}) [%(process)d]:'
2428
2451
                                ' %(levelname)s: %(message)s'
2429
2452
                                .format(server_settings
2430
2453
                                        ["servicename"])))
2438
2461
    global mandos_dbus_service
2439
2462
    mandos_dbus_service = None
2440
2463
    
 
2464
    socketfd = None
 
2465
    if server_settings["socket"] != "":
 
2466
        socketfd = server_settings["socket"]
2441
2467
    tcp_server = MandosServer((server_settings["address"],
2442
2468
                               server_settings["port"]),
2443
2469
                              ClientHandler,
2447
2473
                              gnutls_priority=
2448
2474
                              server_settings["priority"],
2449
2475
                              use_dbus=use_dbus,
2450
 
                              socketfd=(server_settings["socket"]
2451
 
                                        or None))
 
2476
                              socketfd=socketfd)
2452
2477
    if not foreground:
2453
 
        pidfilename = "/var/run/mandos.pid"
 
2478
        pidfilename = "/run/mandos.pid"
 
2479
        if not os.path.isdir("/run/."):
 
2480
            pidfilename = "/var/run/mandos.pid"
2454
2481
        pidfile = None
2455
2482
        try:
2456
2483
            pidfile = open(pidfilename, "w")
2473
2500
        os.setuid(uid)
2474
2501
    except OSError as error:
2475
2502
        if error.errno != errno.EPERM:
2476
 
            raise error
 
2503
            raise
2477
2504
    
2478
2505
    if debug:
2479
2506
        # Enable all possible GnuTLS debugging
2522
2549
            use_dbus = False
2523
2550
            server_settings["use_dbus"] = False
2524
2551
            tcp_server.use_dbus = False
2525
 
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2526
 
    service = AvahiServiceToSyslog(name =
2527
 
                                   server_settings["servicename"],
2528
 
                                   servicetype = "_mandos._tcp",
2529
 
                                   protocol = protocol, bus = bus)
2530
 
    if server_settings["interface"]:
2531
 
        service.interface = (if_nametoindex
2532
 
                             (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")))
2533
2562
    
2534
2563
    global multiprocessing_manager
2535
2564
    multiprocessing_manager = multiprocessing.Manager()
2559
2588
            os.remove(stored_state_path)
2560
2589
        except IOError as e:
2561
2590
            if e.errno == errno.ENOENT:
2562
 
                logger.warning("Could not load persistent state: {0}"
 
2591
                logger.warning("Could not load persistent state: {}"
2563
2592
                                .format(os.strerror(e.errno)))
2564
2593
            else:
2565
2594
                logger.critical("Could not load persistent state:",
2570
2599
                           "EOFError:", exc_info=e)
2571
2600
    
2572
2601
    with PGPEngine() as pgp:
2573
 
        for client_name, client in clients_data.iteritems():
 
2602
        for client_name, client in clients_data.items():
2574
2603
            # Skip removed clients
2575
2604
            if client_name not in client_settings:
2576
2605
                continue
2601
2630
                if datetime.datetime.utcnow() >= client["expires"]:
2602
2631
                    if not client["last_checked_ok"]:
2603
2632
                        logger.warning(
2604
 
                            "disabling client {0} - Client never "
 
2633
                            "disabling client {} - Client never "
2605
2634
                            "performed a successful checker"
2606
2635
                            .format(client_name))
2607
2636
                        client["enabled"] = False
2608
2637
                    elif client["last_checker_status"] != 0:
2609
2638
                        logger.warning(
2610
 
                            "disabling client {0} - Client "
2611
 
                            "last checker failed with error code {1}"
 
2639
                            "disabling client {} - Client last"
 
2640
                            " checker failed with error code {}"
2612
2641
                            .format(client_name,
2613
2642
                                    client["last_checker_status"]))
2614
2643
                        client["enabled"] = False
2617
2646
                                             .utcnow()
2618
2647
                                             + client["timeout"])
2619
2648
                        logger.debug("Last checker succeeded,"
2620
 
                                     " keeping {0} enabled"
 
2649
                                     " keeping {} enabled"
2621
2650
                                     .format(client_name))
2622
2651
            try:
2623
2652
                client["secret"] = (
2626
2655
                                ["secret"]))
2627
2656
            except PGPError:
2628
2657
                # If decryption fails, we use secret from new settings
2629
 
                logger.debug("Failed to decrypt {0} old secret"
 
2658
                logger.debug("Failed to decrypt {} old secret"
2630
2659
                             .format(client_name))
2631
2660
                client["secret"] = (
2632
2661
                    client_settings[client_name]["secret"])
2640
2669
        clients_data[client_name] = client_settings[client_name]
2641
2670
    
2642
2671
    # Create all client objects
2643
 
    for client_name, client in clients_data.iteritems():
 
2672
    for client_name, client in clients_data.items():
2644
2673
        tcp_server.clients[client_name] = client_class(
2645
2674
            name = client_name, settings = client,
2646
2675
            server_settings = server_settings)
2653
2682
            try:
2654
2683
                with pidfile:
2655
2684
                    pid = os.getpid()
2656
 
                    pidfile.write(str(pid) + "\n".encode("utf-8"))
 
2685
                    pidfile.write("{}\n".format(pid).encode("utf-8"))
2657
2686
            except IOError:
2658
2687
                logger.error("Could not write to file %r with PID %d",
2659
2688
                             pidfilename, pid)
2705
2734
            def GetAllClientsWithProperties(self):
2706
2735
                "D-Bus method"
2707
2736
                return dbus.Dictionary(
2708
 
                    ((c.dbus_object_path, c.GetAll(""))
2709
 
                     for c in tcp_server.clients.itervalues()),
 
2737
                    { c.dbus_object_path: c.GetAll("")
 
2738
                      for c in tcp_server.clients.itervalues() },
2710
2739
                    signature="oa{sv}")
2711
2740
            
2712
2741
            @dbus.service.method(_interface, in_signature="o")
2729
2758
    
2730
2759
    def cleanup():
2731
2760
        "Cleanup function; run on exit"
2732
 
        service.cleanup()
 
2761
        if zeroconf:
 
2762
            service.cleanup()
2733
2763
        
2734
2764
        multiprocessing.active_children()
2735
2765
        wnull.close()
2749
2779
                
2750
2780
                # A list of attributes that can not be pickled
2751
2781
                # + secret.
2752
 
                exclude = set(("bus", "changedstate", "secret",
2753
 
                               "checker", "server_settings"))
 
2782
                exclude = { "bus", "changedstate", "secret",
 
2783
                            "checker", "server_settings" }
2754
2784
                for name, typ in (inspect.getmembers
2755
2785
                                  (dbus.service.Object)):
2756
2786
                    exclude.add(name)
2779
2809
                except NameError:
2780
2810
                    pass
2781
2811
            if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2782
 
                logger.warning("Could not save persistent state: {0}"
 
2812
                logger.warning("Could not save persistent state: {}"
2783
2813
                               .format(os.strerror(e.errno)))
2784
2814
            else:
2785
2815
                logger.warning("Could not save persistent state:",
2786
2816
                               exc_info=e)
2787
 
                raise e
 
2817
                raise
2788
2818
        
2789
2819
        # Delete all clients, and settings from config
2790
2820
        while tcp_server.clients:
2814
2844
    tcp_server.server_activate()
2815
2845
    
2816
2846
    # Find out what port we got
2817
 
    service.port = tcp_server.socket.getsockname()[1]
 
2847
    if zeroconf:
 
2848
        service.port = tcp_server.socket.getsockname()[1]
2818
2849
    if use_ipv6:
2819
2850
        logger.info("Now listening on address %r, port %d,"
2820
2851
                    " flowinfo %d, scope_id %d",
2826
2857
    #service.interface = tcp_server.socket.getsockname()[3]
2827
2858
    
2828
2859
    try:
2829
 
        # From the Avahi example code
2830
 
        try:
2831
 
            service.activate()
2832
 
        except dbus.exceptions.DBusException as error:
2833
 
            logger.critical("D-Bus Exception", exc_info=error)
2834
 
            cleanup()
2835
 
            sys.exit(1)
2836
 
        # 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
2837
2869
        
2838
2870
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2839
2871
                             lambda *args, **kwargs: