/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2015-03-10 18:03:38 UTC
  • Revision ID: teddy@recompile.se-20150310180338-pcxw6r2qmw9k6br9
Add ":!RSA" to GnuTLS priority string, to disallow non-DHE kx.

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

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

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

2. It is only available since GnuTLS 3.2.4.

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

Show diffs side-by-side

added added

removed removed

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