/mandos/release

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

« back to all changes in this revision

Viewing changes to mandos

* mandos-ctl: Made work again after D-Bus API changes.
  (datetime_to_milliseconds): Renamed to "timedelta_to_milliseconds".
                              All callers changed.
  (milliseconds_to_string): Use clearer mapping string format.
  (string_to_delta): Add some comments.

Show diffs side-by-side

added added

removed removed

Lines of Context:
55
55
import logging
56
56
import logging.handlers
57
57
import pwd
58
 
import contextlib
 
58
from contextlib import closing
59
59
import struct
60
60
import fcntl
61
61
import functools
62
 
import cPickle as pickle
63
 
import select
64
62
 
65
63
import dbus
66
64
import dbus.service
81
79
        SO_BINDTODEVICE = None
82
80
 
83
81
 
84
 
version = "1.0.14"
 
82
version = "1.0.12"
85
83
 
86
84
logger = logging.Logger(u'mandos')
87
85
syslogger = (logging.handlers.SysLogHandler
193
191
        self.group.Commit()
194
192
    def entry_group_state_changed(self, state, error):
195
193
        """Derived from the Avahi example code"""
196
 
        logger.debug(u"Avahi entry group state change: %i", state)
 
194
        logger.debug(u"Avahi state change: %i", state)
197
195
        
198
196
        if state == avahi.ENTRY_GROUP_ESTABLISHED:
199
197
            logger.debug(u"Zeroconf service established.")
212
210
            self.group = None
213
211
    def server_state_changed(self, state):
214
212
        """Derived from the Avahi example code"""
215
 
        logger.debug(u"Avahi server state change: %i", state)
216
213
        if state == avahi.SERVER_COLLISION:
217
214
            logger.error(u"Zeroconf server name collision")
218
215
            self.remove()
245
242
    enabled:    bool()
246
243
    last_checked_ok: datetime.datetime(); (UTC) or None
247
244
    timeout:    datetime.timedelta(); How long from last_checked_ok
248
 
                                      until this client is disabled
 
245
                                      until this client is invalid
249
246
    interval:   datetime.timedelta(); How often to start a new checker
250
247
    disable_hook:  If set, called by disable() as disable_hook(self)
251
248
    checker:    subprocess.Popen(); a running checker process used
293
290
        if u"secret" in config:
294
291
            self.secret = config[u"secret"].decode(u"base64")
295
292
        elif u"secfile" in config:
296
 
            with open(os.path.expanduser(os.path.expandvars
297
 
                                         (config[u"secfile"])),
298
 
                      "rb") as secfile:
 
293
            with closing(open(os.path.expanduser
 
294
                              (os.path.expandvars
 
295
                               (config[u"secfile"])))) as secfile:
299
296
                self.secret = secfile.read()
300
297
        else:
301
298
            raise TypeError(u"No secret or secfile for client %s"
327
324
        self.checker_initiator_tag = (gobject.timeout_add
328
325
                                      (self.interval_milliseconds(),
329
326
                                       self.start_checker))
 
327
        # Also start a new checker *right now*.
 
328
        self.start_checker()
330
329
        # Schedule a disable() when 'timeout' has passed
331
330
        self.disable_initiator_tag = (gobject.timeout_add
332
331
                                   (self.timeout_milliseconds(),
333
332
                                    self.disable))
334
333
        self.enabled = True
335
 
        # Also start a new checker *right now*.
336
 
        self.start_checker()
337
334
    
338
 
    def disable(self, quiet=True):
 
335
    def disable(self):
339
336
        """Disable this client."""
340
337
        if not getattr(self, "enabled", False):
341
338
            return False
342
 
        if not quiet:
343
 
            logger.info(u"Disabling client %s", self.name)
 
339
        logger.info(u"Disabling client %s", self.name)
344
340
        if getattr(self, u"disable_initiator_tag", False):
345
341
            gobject.source_remove(self.disable_initiator_tag)
346
342
            self.disable_initiator_tag = None
398
394
        # client would inevitably timeout, since no checker would get
399
395
        # a chance to run to completion.  If we instead leave running
400
396
        # checkers alone, the checker would have to take more time
401
 
        # than 'timeout' for the client to be disabled, which is as it
402
 
        # should be.
 
397
        # than 'timeout' for the client to be declared invalid, which
 
398
        # is as it should be.
403
399
        
404
400
        # If a checker exists, make sure it is not a zombie
405
 
        try:
 
401
        if self.checker is not None:
406
402
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
407
 
        except (AttributeError, OSError), error:
408
 
            if (isinstance(error, OSError)
409
 
                and error.errno != errno.ECHILD):
410
 
                raise error
411
 
        else:
412
403
            if pid:
413
404
                logger.warning(u"Checker was a zombie")
414
405
                gobject.source_remove(self.checker_callback_tag)
470
461
        logger.debug(u"Stopping checker for %(name)s", vars(self))
471
462
        try:
472
463
            os.kill(self.checker.pid, signal.SIGTERM)
473
 
            #time.sleep(0.5)
 
464
            #os.sleep(0.5)
474
465
            #if self.checker.poll() is None:
475
466
            #    os.kill(self.checker.pid, signal.SIGKILL)
476
467
        except OSError, error:
477
468
            if error.errno != errno.ESRCH: # No such process
478
469
                raise
479
470
        self.checker = None
 
471
    
 
472
    def still_valid(self):
 
473
        """Has the timeout not yet passed for this client?"""
 
474
        if not getattr(self, u"enabled", False):
 
475
            return False
 
476
        now = datetime.datetime.utcnow()
 
477
        if self.last_checked_ok is None:
 
478
            return now < (self.created + self.timeout)
 
479
        else:
 
480
            return now < (self.last_checked_ok + self.timeout)
480
481
 
481
482
 
482
483
def dbus_service_property(dbus_interface, signature=u"v",
491
492
    dbus.service.method, except there is only "signature", since the
492
493
    type from Get() and the type sent to Set() is the same.
493
494
    """
494
 
    # Encoding deeply encoded byte arrays is not supported yet by the
495
 
    # "Set" method, so we fail early here:
496
 
    if byte_arrays and signature != u"ay":
497
 
        raise ValueError(u"Byte arrays not supported for non-'ay'"
498
 
                         u" signature %r" % signature)
499
495
    def decorator(func):
500
496
        func._dbus_is_property = True
501
497
        func._dbus_interface = dbus_interface
587
583
        if prop._dbus_access == u"read":
588
584
            raise DBusPropertyAccessException(property_name)
589
585
        if prop._dbus_get_args_options[u"byte_arrays"]:
590
 
            # The byte_arrays option is not supported yet on
591
 
            # signatures other than "ay".
592
 
            if prop._dbus_signature != u"ay":
593
 
                raise ValueError
594
586
            value = dbus.ByteArray(''.join(unichr(byte)
595
587
                                           for byte in value))
596
588
        prop(value)
628
620
        """Standard D-Bus method, overloaded to insert property tags.
629
621
        """
630
622
        xmlstring = dbus.service.Object.Introspect(self, object_path,
631
 
                                                   connection)
632
 
        try:
633
 
            document = xml.dom.minidom.parseString(xmlstring)
634
 
            def make_tag(document, name, prop):
635
 
                e = document.createElement(u"property")
636
 
                e.setAttribute(u"name", name)
637
 
                e.setAttribute(u"type", prop._dbus_signature)
638
 
                e.setAttribute(u"access", prop._dbus_access)
639
 
                return e
640
 
            for if_tag in document.getElementsByTagName(u"interface"):
641
 
                for tag in (make_tag(document, name, prop)
642
 
                            for name, prop
643
 
                            in self._get_all_dbus_properties()
644
 
                            if prop._dbus_interface
645
 
                            == if_tag.getAttribute(u"name")):
646
 
                    if_tag.appendChild(tag)
647
 
                # Add the names to the return values for the
648
 
                # "org.freedesktop.DBus.Properties" methods
649
 
                if (if_tag.getAttribute(u"name")
650
 
                    == u"org.freedesktop.DBus.Properties"):
651
 
                    for cn in if_tag.getElementsByTagName(u"method"):
652
 
                        if cn.getAttribute(u"name") == u"Get":
653
 
                            for arg in cn.getElementsByTagName(u"arg"):
654
 
                                if (arg.getAttribute(u"direction")
655
 
                                    == u"out"):
656
 
                                    arg.setAttribute(u"name", u"value")
657
 
                        elif cn.getAttribute(u"name") == u"GetAll":
658
 
                            for arg in cn.getElementsByTagName(u"arg"):
659
 
                                if (arg.getAttribute(u"direction")
660
 
                                    == u"out"):
661
 
                                    arg.setAttribute(u"name", u"props")
662
 
            xmlstring = document.toxml(u"utf-8")
663
 
            document.unlink()
664
 
        except (AttributeError, xml.dom.DOMException,
665
 
                xml.parsers.expat.ExpatError), error:
666
 
            logger.error(u"Failed to override Introspection method",
667
 
                         error)
 
623
                                           connection)
 
624
        document = xml.dom.minidom.parseString(xmlstring)
 
625
        del xmlstring
 
626
        def make_tag(document, name, prop):
 
627
            e = document.createElement(u"property")
 
628
            e.setAttribute(u"name", name)
 
629
            e.setAttribute(u"type", prop._dbus_signature)
 
630
            e.setAttribute(u"access", prop._dbus_access)
 
631
            return e
 
632
        for if_tag in document.getElementsByTagName(u"interface"):
 
633
            for tag in (make_tag(document, name, prop)
 
634
                        for name, prop
 
635
                        in self._get_all_dbus_properties()
 
636
                        if prop._dbus_interface
 
637
                        == if_tag.getAttribute(u"name")):
 
638
                if_tag.appendChild(tag)
 
639
        xmlstring = document.toxml(u"utf-8")
 
640
        document.unlink()
668
641
        return xmlstring
669
642
 
670
643
 
707
680
                                       variant_level=1))
708
681
        return r
709
682
    
710
 
    def disable(self, quiet = False):
 
683
    def disable(self, signal = True):
711
684
        oldstate = getattr(self, u"enabled", False)
712
 
        r = Client.disable(self, quiet=quiet)
713
 
        if not quiet and oldstate != self.enabled:
 
685
        r = Client.disable(self)
 
686
        if signal and oldstate != self.enabled:
714
687
            # Emit D-Bus signal
715
688
            self.PropertyChanged(dbus.String(u"enabled"),
716
689
                                 dbus.Boolean(False, variant_level=1))
782
755
                                 dbus.Boolean(False, variant_level=1))
783
756
        return r
784
757
    
785
 
    ## D-Bus methods, signals & properties
 
758
    ## D-Bus methods & signals
786
759
    _interface = u"se.bsnet.fukt.Mandos.Client"
787
760
    
788
 
    ## Signals
 
761
    # CheckedOK - method
 
762
    @dbus.service.method(_interface)
 
763
    def CheckedOK(self):
 
764
        return self.checked_ok()
789
765
    
790
766
    # CheckerCompleted - signal
791
767
    @dbus.service.signal(_interface, signature=u"nxs")
805
781
        "D-Bus signal"
806
782
        pass
807
783
    
808
 
    # GotSecret - signal
 
784
    # ReceivedSecret - signal
809
785
    @dbus.service.signal(_interface)
810
 
    def GotSecret(self):
 
786
    def ReceivedSecret(self):
811
787
        "D-Bus signal"
812
788
        pass
813
789
    
817
793
        "D-Bus signal"
818
794
        pass
819
795
    
820
 
    ## Methods
821
 
    
822
 
    # CheckedOK - method
823
 
    @dbus.service.method(_interface)
824
 
    def CheckedOK(self):
825
 
        return self.checked_ok()
826
 
    
827
796
    # Enable - method
828
797
    @dbus.service.method(_interface)
829
798
    def Enable(self):
847
816
    def StopChecker(self):
848
817
        self.stop_checker()
849
818
    
850
 
    ## Properties
851
 
    
852
819
    # name - property
853
820
    @dbus_service_property(_interface, signature=u"s", access=u"read")
854
821
    def name_dbus_property(self):
997
964
    def handle(self):
998
965
        logger.info(u"TCP connection from: %s",
999
966
                    unicode(self.client_address))
1000
 
        logger.debug(u"IPC Pipe FD: %d",
1001
 
                     self.server.child_pipe[1].fileno())
 
967
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
1002
968
        # Open IPC pipe to parent process
1003
 
        with contextlib.nested(self.server.child_pipe[1],
1004
 
                               self.server.parent_pipe[0]
1005
 
                               ) as (ipc, ipc_return):
 
969
        with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
1006
970
            session = (gnutls.connection
1007
971
                       .ClientSession(self.request,
1008
972
                                      gnutls.connection
1009
973
                                      .X509Credentials()))
1010
974
            
 
975
            line = self.request.makefile().readline()
 
976
            logger.debug(u"Protocol version: %r", line)
 
977
            try:
 
978
                if int(line.strip().split()[0]) > 1:
 
979
                    raise RuntimeError
 
980
            except (ValueError, IndexError, RuntimeError), error:
 
981
                logger.error(u"Unknown protocol version: %s", error)
 
982
                return
 
983
            
1011
984
            # Note: gnutls.connection.X509Credentials is really a
1012
985
            # generic GnuTLS certificate credentials object so long as
1013
986
            # no X.509 keys are added to it.  Therefore, we can use it
1025
998
             .gnutls_priority_set_direct(session._c_object,
1026
999
                                         priority, None))
1027
1000
            
1028
 
            # Start communication using the Mandos protocol
1029
 
            # Get protocol number
1030
 
            line = self.request.makefile().readline()
1031
 
            logger.debug(u"Protocol version: %r", line)
1032
 
            try:
1033
 
                if int(line.strip().split()[0]) > 1:
1034
 
                    raise RuntimeError
1035
 
            except (ValueError, IndexError, RuntimeError), error:
1036
 
                logger.error(u"Unknown protocol version: %s", error)
1037
 
                return
1038
 
            
1039
 
            # Start GnuTLS connection
1040
1001
            try:
1041
1002
                session.handshake()
1042
1003
            except gnutls.errors.GNUTLSError, error:
1046
1007
                return
1047
1008
            logger.debug(u"Handshake succeeded")
1048
1009
            try:
1049
 
                try:
1050
 
                    fpr = self.fingerprint(self.peer_certificate
1051
 
                                           (session))
1052
 
                except (TypeError, gnutls.errors.GNUTLSError), error:
1053
 
                    logger.warning(u"Bad certificate: %s", error)
1054
 
                    return
1055
 
                logger.debug(u"Fingerprint: %s", fpr)
1056
 
 
1057
 
                for c in self.server.clients:
1058
 
                    if c.fingerprint == fpr:
1059
 
                        client = c
1060
 
                        break
1061
 
                else:
1062
 
                    ipc.write(u"NOTFOUND %s %s\n"
1063
 
                              % (fpr, unicode(self.client_address)))
1064
 
                    return
1065
 
                
1066
 
                class ClientProxy(object):
1067
 
                    """Client proxy object.  Not for calling methods."""
1068
 
                    def __init__(self, client):
1069
 
                        self.client = client
1070
 
                    def __getattr__(self, name):
1071
 
                        if name.startswith("ipc_"):
1072
 
                            def tempfunc():
1073
 
                                ipc.write("%s %s\n" % (name[4:].upper(),
1074
 
                                                       self.client.name))
1075
 
                            return tempfunc
1076
 
                        if not hasattr(self.client, name):
1077
 
                            raise AttributeError
1078
 
                        ipc.write(u"GETATTR %s %s\n"
1079
 
                                  % (name, self.client.fingerprint))
1080
 
                        return pickle.load(ipc_return)
1081
 
                clientproxy = ClientProxy(client)
1082
 
                # Have to check if client.enabled, since it is
1083
 
                # possible that the client was disabled since the
1084
 
                # GnuTLS session was established.
1085
 
                if not clientproxy.enabled:
1086
 
                    clientproxy.ipc_disabled()
1087
 
                    return
1088
 
                
1089
 
                clientproxy.ipc_sending()
1090
 
                sent_size = 0
1091
 
                while sent_size < len(client.secret):
1092
 
                    sent = session.send(client.secret[sent_size:])
1093
 
                    logger.debug(u"Sent: %d, remaining: %d",
1094
 
                                 sent, len(client.secret)
1095
 
                                 - (sent_size + sent))
1096
 
                    sent_size += sent
1097
 
            finally:
1098
 
                session.bye()
 
1010
                fpr = self.fingerprint(self.peer_certificate(session))
 
1011
            except (TypeError, gnutls.errors.GNUTLSError), error:
 
1012
                logger.warning(u"Bad certificate: %s", error)
 
1013
                session.bye()
 
1014
                return
 
1015
            logger.debug(u"Fingerprint: %s", fpr)
 
1016
            
 
1017
            for c in self.server.clients:
 
1018
                if c.fingerprint == fpr:
 
1019
                    client = c
 
1020
                    break
 
1021
            else:
 
1022
                ipc.write(u"NOTFOUND %s %s\n"
 
1023
                          % (fpr, unicode(self.client_address)))
 
1024
                session.bye()
 
1025
                return
 
1026
            # Have to check if client.still_valid(), since it is
 
1027
            # possible that the client timed out while establishing
 
1028
            # the GnuTLS session.
 
1029
            if not client.still_valid():
 
1030
                ipc.write(u"INVALID %s\n" % client.name)
 
1031
                session.bye()
 
1032
                return
 
1033
            ipc.write(u"SENDING %s\n" % client.name)
 
1034
            sent_size = 0
 
1035
            while sent_size < len(client.secret):
 
1036
                sent = session.send(client.secret[sent_size:])
 
1037
                logger.debug(u"Sent: %d, remaining: %d",
 
1038
                             sent, len(client.secret)
 
1039
                             - (sent_size + sent))
 
1040
                sent_size += sent
 
1041
            session.bye()
1099
1042
    
1100
1043
    @staticmethod
1101
1044
    def peer_certificate(session):
1161
1104
        return hex_fpr
1162
1105
 
1163
1106
 
1164
 
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
1165
 
    """Like socketserver.ForkingMixIn, but also pass a pipe pair."""
 
1107
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
 
1108
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
1166
1109
    def process_request(self, request, client_address):
1167
1110
        """Overrides and wraps the original process_request().
1168
1111
        
1169
1112
        This function creates a new pipe in self.pipe
1170
1113
        """
1171
 
        # Child writes to child_pipe
1172
 
        self.child_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1173
 
        # Parent writes to parent_pipe
1174
 
        self.parent_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1175
 
        super(ForkingMixInWithPipes,
 
1114
        self.pipe = os.pipe()
 
1115
        super(ForkingMixInWithPipe,
1176
1116
              self).process_request(request, client_address)
1177
 
        # Close unused ends for parent
1178
 
        self.parent_pipe[0].close() # close read end
1179
 
        self.child_pipe[1].close()  # close write end
1180
 
        self.add_pipe_fds(self.child_pipe[0], self.parent_pipe[1])
1181
 
    def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
 
1117
        os.close(self.pipe[1])  # close write end
 
1118
        self.add_pipe(self.pipe[0])
 
1119
    def add_pipe(self, pipe):
1182
1120
        """Dummy function; override as necessary"""
1183
 
        child_pipe_fd.close()
1184
 
        parent_pipe_fd.close()
1185
 
 
1186
 
 
1187
 
class IPv6_TCPServer(ForkingMixInWithPipes,
 
1121
        os.close(pipe)
 
1122
 
 
1123
 
 
1124
class IPv6_TCPServer(ForkingMixInWithPipe,
1188
1125
                     socketserver.TCPServer, object):
1189
1126
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1190
1127
    
1275
1212
            return socketserver.TCPServer.server_activate(self)
1276
1213
    def enable(self):
1277
1214
        self.enabled = True
1278
 
    def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
 
1215
    def add_pipe(self, pipe):
1279
1216
        # Call "handle_ipc" for both data and EOF events
1280
 
        gobject.io_add_watch(child_pipe_fd.fileno(),
1281
 
                             gobject.IO_IN | gobject.IO_HUP,
1282
 
                             functools.partial(self.handle_ipc,
1283
 
                                               reply = parent_pipe_fd,
1284
 
                                               sender= child_pipe_fd))
1285
 
    def handle_ipc(self, source, condition, reply=None, sender=None):
 
1217
        gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
 
1218
                             self.handle_ipc)
 
1219
    def handle_ipc(self, source, condition, file_objects={}):
1286
1220
        condition_names = {
1287
1221
            gobject.IO_IN: u"IN",   # There is data to read.
1288
1222
            gobject.IO_OUT: u"OUT", # Data can be written (without
1300
1234
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1301
1235
                     conditions_string)
1302
1236
        
 
1237
        # Turn the pipe file descriptor into a Python file object
 
1238
        if source not in file_objects:
 
1239
            file_objects[source] = os.fdopen(source, u"r", 1)
 
1240
        
1303
1241
        # Read a line from the file object
1304
 
        cmdline = sender.readline()
 
1242
        cmdline = file_objects[source].readline()
1305
1243
        if not cmdline:             # Empty line means end of file
1306
 
            # close the IPC pipes
1307
 
            sender.close()
1308
 
            reply.close()
 
1244
            # close the IPC pipe
 
1245
            file_objects[source].close()
 
1246
            del file_objects[source]
1309
1247
            
1310
1248
            # Stop calling this function
1311
1249
            return False
1316
1254
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1317
1255
        
1318
1256
        if cmd == u"NOTFOUND":
1319
 
            fpr, address = args.split(None, 1)
1320
 
            logger.warning(u"Client not found for fingerprint: %s, ad"
1321
 
                           u"dress: %s", fpr, address)
 
1257
            logger.warning(u"Client not found for fingerprint: %s",
 
1258
                           args)
1322
1259
            if self.use_dbus:
1323
1260
                # Emit D-Bus signal
1324
 
                mandos_dbus_service.ClientNotFound(fpr, address)
1325
 
        elif cmd == u"DISABLED":
 
1261
                mandos_dbus_service.ClientNotFound(args)
 
1262
        elif cmd == u"INVALID":
1326
1263
            for client in self.clients:
1327
1264
                if client.name == args:
1328
 
                    logger.warning(u"Client %s is disabled", args)
 
1265
                    logger.warning(u"Client %s is invalid", args)
1329
1266
                    if self.use_dbus:
1330
1267
                        # Emit D-Bus signal
1331
1268
                        client.Rejected()
1332
1269
                    break
1333
1270
            else:
1334
 
                logger.error(u"Unknown client %s is disabled", args)
 
1271
                logger.error(u"Unknown client %s is invalid", args)
1335
1272
        elif cmd == u"SENDING":
1336
1273
            for client in self.clients:
1337
1274
                if client.name == args:
1339
1276
                    client.checked_ok()
1340
1277
                    if self.use_dbus:
1341
1278
                        # Emit D-Bus signal
1342
 
                        client.GotSecret()
 
1279
                        client.ReceivedSecret()
1343
1280
                    break
1344
1281
            else:
1345
1282
                logger.error(u"Sending secret to unknown client %s",
1346
1283
                             args)
1347
 
        elif cmd == u"GETATTR":
1348
 
            attr_name, fpr = args.split(None, 1)
1349
 
            for client in self.clients:
1350
 
                if client.fingerprint == fpr:
1351
 
                    attr_value = getattr(client, attr_name, None)
1352
 
                    logger.debug("IPC reply: %r", attr_value)
1353
 
                    pickle.dump(attr_value, reply)
1354
 
                    break
1355
 
            else:
1356
 
                logger.error(u"Client %s on address %s requesting "
1357
 
                             u"attribute %s not found", fpr, address,
1358
 
                             attr_name)
1359
 
                pickle.dump(None, reply)
1360
1284
        else:
1361
1285
            logger.error(u"Unknown IPC command: %r", cmdline)
1362
1286
        
1396
1320
            elif suffix == u"w":
1397
1321
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1398
1322
            else:
1399
 
                raise ValueError(u"Unknown suffix %r" % suffix)
1400
 
        except (ValueError, IndexError), e:
1401
 
            raise ValueError(e.message)
 
1323
                raise ValueError
 
1324
        except (ValueError, IndexError):
 
1325
            raise ValueError
1402
1326
        timevalue += delta
1403
1327
    return timevalue
1404
1328
 
1417
1341
        def if_nametoindex(interface):
1418
1342
            "Get an interface index the hard way, i.e. using fcntl()"
1419
1343
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1420
 
            with contextlib.closing(socket.socket()) as s:
 
1344
            with closing(socket.socket()) as s:
1421
1345
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1422
1346
                                    struct.pack(str(u"16s16x"),
1423
1347
                                                interface))
1443
1367
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1444
1368
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1445
1369
            raise OSError(errno.ENODEV,
1446
 
                          u"%s not a character device"
1447
 
                          % os.path.devnull)
 
1370
                          u"/dev/null not a character device")
1448
1371
        os.dup2(null, sys.stdin.fileno())
1449
1372
        os.dup2(null, sys.stdout.fileno())
1450
1373
        os.dup2(null, sys.stderr.fileno())
1565
1488
    tcp_server = MandosServer((server_settings[u"address"],
1566
1489
                               server_settings[u"port"]),
1567
1490
                              ClientHandler,
1568
 
                              interface=(server_settings[u"interface"]
1569
 
                                         or None),
 
1491
                              interface=server_settings[u"interface"],
1570
1492
                              use_ipv6=use_ipv6,
1571
1493
                              gnutls_priority=
1572
1494
                              server_settings[u"priority"],
1618
1540
    bus = dbus.SystemBus()
1619
1541
    # End of Avahi example code
1620
1542
    if use_dbus:
1621
 
        try:
1622
 
            bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1623
 
                                            bus, do_not_queue=True)
1624
 
        except dbus.exceptions.NameExistsException, e:
1625
 
            logger.error(unicode(e) + u", disabling D-Bus")
1626
 
            use_dbus = False
1627
 
            server_settings[u"use_dbus"] = False
1628
 
            tcp_server.use_dbus = False
 
1543
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1629
1544
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1630
1545
    service = AvahiService(name = server_settings[u"servicename"],
1631
1546
                           servicetype = u"_mandos._tcp",
1657
1572
        daemon()
1658
1573
    
1659
1574
    try:
1660
 
        with pidfile:
 
1575
        with closing(pidfile):
1661
1576
            pid = os.getpid()
1662
1577
            pidfile.write(str(pid) + "\n")
1663
1578
        del pidfile
1669
1584
        pass
1670
1585
    del pidfilename
1671
1586
    
 
1587
    def cleanup():
 
1588
        "Cleanup function; run on exit"
 
1589
        service.cleanup()
 
1590
        
 
1591
        while tcp_server.clients:
 
1592
            client = tcp_server.clients.pop()
 
1593
            client.disable_hook = None
 
1594
            client.disable()
 
1595
    
 
1596
    atexit.register(cleanup)
 
1597
    
1672
1598
    if not debug:
1673
1599
        signal.signal(signal.SIGINT, signal.SIG_IGN)
1674
1600
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1681
1607
                dbus.service.Object.__init__(self, bus, u"/")
1682
1608
            _interface = u"se.bsnet.fukt.Mandos"
1683
1609
            
1684
 
            @dbus.service.signal(_interface, signature=u"o")
1685
 
            def ClientAdded(self, objpath):
 
1610
            @dbus.service.signal(_interface, signature=u"oa{sv}")
 
1611
            def ClientAdded(self, objpath, properties):
1686
1612
                "D-Bus signal"
1687
1613
                pass
1688
1614
            
1689
 
            @dbus.service.signal(_interface, signature=u"ss")
1690
 
            def ClientNotFound(self, fingerprint, address):
 
1615
            @dbus.service.signal(_interface, signature=u"s")
 
1616
            def ClientNotFound(self, fingerprint):
1691
1617
                "D-Bus signal"
1692
1618
                pass
1693
1619
            
1719
1645
                        tcp_server.clients.remove(c)
1720
1646
                        c.remove_from_connection()
1721
1647
                        # Don't signal anything except ClientRemoved
1722
 
                        c.disable(quiet=True)
 
1648
                        c.disable(signal=False)
1723
1649
                        # Emit D-Bus signal
1724
1650
                        self.ClientRemoved(object_path, c.name)
1725
1651
                        return
1726
 
                raise KeyError(object_path)
 
1652
                raise KeyError
1727
1653
            
1728
1654
            del _interface
1729
1655
        
1730
1656
        mandos_dbus_service = MandosDBusService()
1731
1657
    
1732
 
    def cleanup():
1733
 
        "Cleanup function; run on exit"
1734
 
        service.cleanup()
1735
 
        
1736
 
        while tcp_server.clients:
1737
 
            client = tcp_server.clients.pop()
1738
 
            if use_dbus:
1739
 
                client.remove_from_connection()
1740
 
            client.disable_hook = None
1741
 
            # Don't signal anything except ClientRemoved
1742
 
            client.disable(quiet=True)
1743
 
            if use_dbus:
1744
 
                # Emit D-Bus signal
1745
 
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
1746
 
                                                  client.name)
1747
 
    
1748
 
    atexit.register(cleanup)
1749
 
    
1750
1658
    for client in tcp_server.clients:
1751
1659
        if use_dbus:
1752
1660
            # Emit D-Bus signal
1753
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
 
1661
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
 
1662
                                            client.GetAll(u""))
1754
1663
        client.enable()
1755
1664
    
1756
1665
    tcp_server.enable()
1774
1683
            service.activate()
1775
1684
        except dbus.exceptions.DBusException, error:
1776
1685
            logger.critical(u"DBusException: %s", error)
1777
 
            cleanup()
1778
1686
            sys.exit(1)
1779
1687
        # End of Avahi example code
1780
1688
        
1787
1695
        main_loop.run()
1788
1696
    except AvahiError, error:
1789
1697
        logger.critical(u"AvahiError: %s", error)
1790
 
        cleanup()
1791
1698
        sys.exit(1)
1792
1699
    except KeyboardInterrupt:
1793
1700
        if debug:
1794
1701
            print >> sys.stderr
1795
1702
        logger.debug(u"Server received KeyboardInterrupt")
1796
1703
    logger.debug(u"Server exiting")
1797
 
    # Must run before the D-Bus bus name gets deregistered
1798
 
    cleanup()
1799
1704
 
1800
1705
if __name__ == '__main__':
1801
1706
    main()