/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

* Makefile: Merge branch adding warning messages to "run-*" targets.

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
 
from contextlib import closing
 
58
import contextlib
59
59
import struct
60
60
import fcntl
61
61
import functools
 
62
import cPickle as pickle
 
63
import select
62
64
 
63
65
import dbus
64
66
import dbus.service
79
81
        SO_BINDTODEVICE = None
80
82
 
81
83
 
82
 
version = "1.0.12"
 
84
version = "1.0.14"
83
85
 
84
86
logger = logging.Logger(u'mandos')
85
87
syslogger = (logging.handlers.SysLogHandler
191
193
        self.group.Commit()
192
194
    def entry_group_state_changed(self, state, error):
193
195
        """Derived from the Avahi example code"""
194
 
        logger.debug(u"Avahi state change: %i", state)
 
196
        logger.debug(u"Avahi entry group state change: %i", state)
195
197
        
196
198
        if state == avahi.ENTRY_GROUP_ESTABLISHED:
197
199
            logger.debug(u"Zeroconf service established.")
210
212
            self.group = None
211
213
    def server_state_changed(self, state):
212
214
        """Derived from the Avahi example code"""
 
215
        logger.debug(u"Avahi server state change: %i", state)
213
216
        if state == avahi.SERVER_COLLISION:
214
217
            logger.error(u"Zeroconf server name collision")
215
218
            self.remove()
242
245
    enabled:    bool()
243
246
    last_checked_ok: datetime.datetime(); (UTC) or None
244
247
    timeout:    datetime.timedelta(); How long from last_checked_ok
245
 
                                      until this client is invalid
 
248
                                      until this client is disabled
246
249
    interval:   datetime.timedelta(); How often to start a new checker
247
250
    disable_hook:  If set, called by disable() as disable_hook(self)
248
251
    checker:    subprocess.Popen(); a running checker process used
290
293
        if u"secret" in config:
291
294
            self.secret = config[u"secret"].decode(u"base64")
292
295
        elif u"secfile" in config:
293
 
            with closing(open(os.path.expanduser
294
 
                              (os.path.expandvars
295
 
                               (config[u"secfile"])))) as secfile:
 
296
            with open(os.path.expanduser(os.path.expandvars
 
297
                                         (config[u"secfile"])),
 
298
                      "rb") as secfile:
296
299
                self.secret = secfile.read()
297
300
        else:
298
301
            raise TypeError(u"No secret or secfile for client %s"
324
327
        self.checker_initiator_tag = (gobject.timeout_add
325
328
                                      (self.interval_milliseconds(),
326
329
                                       self.start_checker))
327
 
        # Also start a new checker *right now*.
328
 
        self.start_checker()
329
330
        # Schedule a disable() when 'timeout' has passed
330
331
        self.disable_initiator_tag = (gobject.timeout_add
331
332
                                   (self.timeout_milliseconds(),
332
333
                                    self.disable))
333
334
        self.enabled = True
 
335
        # Also start a new checker *right now*.
 
336
        self.start_checker()
334
337
    
335
 
    def disable(self):
 
338
    def disable(self, quiet=True):
336
339
        """Disable this client."""
337
340
        if not getattr(self, "enabled", False):
338
341
            return False
339
 
        logger.info(u"Disabling client %s", self.name)
 
342
        if not quiet:
 
343
            logger.info(u"Disabling client %s", self.name)
340
344
        if getattr(self, u"disable_initiator_tag", False):
341
345
            gobject.source_remove(self.disable_initiator_tag)
342
346
            self.disable_initiator_tag = None
394
398
        # client would inevitably timeout, since no checker would get
395
399
        # a chance to run to completion.  If we instead leave running
396
400
        # checkers alone, the checker would have to take more time
397
 
        # than 'timeout' for the client to be declared invalid, which
398
 
        # is as it should be.
 
401
        # than 'timeout' for the client to be disabled, which is as it
 
402
        # should be.
399
403
        
400
404
        # If a checker exists, make sure it is not a zombie
401
 
        if self.checker is not None:
 
405
        try:
402
406
            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:
403
412
            if pid:
404
413
                logger.warning(u"Checker was a zombie")
405
414
                gobject.source_remove(self.checker_callback_tag)
461
470
        logger.debug(u"Stopping checker for %(name)s", vars(self))
462
471
        try:
463
472
            os.kill(self.checker.pid, signal.SIGTERM)
464
 
            #os.sleep(0.5)
 
473
            #time.sleep(0.5)
465
474
            #if self.checker.poll() is None:
466
475
            #    os.kill(self.checker.pid, signal.SIGKILL)
467
476
        except OSError, error:
468
477
            if error.errno != errno.ESRCH: # No such process
469
478
                raise
470
479
        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)
481
480
 
482
481
 
483
482
def dbus_service_property(dbus_interface, signature=u"v",
492
491
    dbus.service.method, except there is only "signature", since the
493
492
    type from Get() and the type sent to Set() is the same.
494
493
    """
 
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)
495
499
    def decorator(func):
496
500
        func._dbus_is_property = True
497
501
        func._dbus_interface = dbus_interface
583
587
        if prop._dbus_access == u"read":
584
588
            raise DBusPropertyAccessException(property_name)
585
589
        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
586
594
            value = dbus.ByteArray(''.join(unichr(byte)
587
595
                                           for byte in value))
588
596
        prop(value)
620
628
        """Standard D-Bus method, overloaded to insert property tags.
621
629
        """
622
630
        xmlstring = dbus.service.Object.Introspect(self, object_path,
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()
 
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)
641
668
        return xmlstring
642
669
 
643
670
 
680
707
                                       variant_level=1))
681
708
        return r
682
709
    
683
 
    def disable(self, signal = True):
 
710
    def disable(self, quiet = False):
684
711
        oldstate = getattr(self, u"enabled", False)
685
 
        r = Client.disable(self)
686
 
        if signal and oldstate != self.enabled:
 
712
        r = Client.disable(self, quiet=quiet)
 
713
        if not quiet and oldstate != self.enabled:
687
714
            # Emit D-Bus signal
688
715
            self.PropertyChanged(dbus.String(u"enabled"),
689
716
                                 dbus.Boolean(False, variant_level=1))
755
782
                                 dbus.Boolean(False, variant_level=1))
756
783
        return r
757
784
    
758
 
    ## D-Bus methods & signals
 
785
    ## D-Bus methods, signals & properties
759
786
    _interface = u"se.bsnet.fukt.Mandos.Client"
760
787
    
 
788
    ## Signals
 
789
    
761
790
    # CheckerCompleted - signal
762
791
    @dbus.service.signal(_interface, signature=u"nxs")
763
792
    def CheckerCompleted(self, exitcode, waitstatus, command):
776
805
        "D-Bus signal"
777
806
        pass
778
807
    
779
 
    # ReceivedSecret - signal
 
808
    # GotSecret - signal
780
809
    @dbus.service.signal(_interface)
781
 
    def ReceivedSecret(self):
 
810
    def GotSecret(self):
782
811
        "D-Bus signal"
783
812
        pass
784
813
    
788
817
        "D-Bus signal"
789
818
        pass
790
819
    
 
820
    ## Methods
 
821
    
 
822
    # CheckedOK - method
 
823
    @dbus.service.method(_interface)
 
824
    def CheckedOK(self):
 
825
        return self.checked_ok()
 
826
    
 
827
    # Enable - method
 
828
    @dbus.service.method(_interface)
 
829
    def Enable(self):
 
830
        "D-Bus method"
 
831
        self.enable()
 
832
    
 
833
    # StartChecker - method
 
834
    @dbus.service.method(_interface)
 
835
    def StartChecker(self):
 
836
        "D-Bus method"
 
837
        self.start_checker()
 
838
    
 
839
    # Disable - method
 
840
    @dbus.service.method(_interface)
 
841
    def Disable(self):
 
842
        "D-Bus method"
 
843
        self.disable()
 
844
    
 
845
    # StopChecker - method
 
846
    @dbus.service.method(_interface)
 
847
    def StopChecker(self):
 
848
        self.stop_checker()
 
849
    
 
850
    ## Properties
 
851
    
791
852
    # name - property
792
853
    @dbus_service_property(_interface, signature=u"s", access=u"read")
793
854
    def name_dbus_property(self):
833
894
            self.disable()
834
895
    
835
896
    # last_checked_ok - property
836
 
    @dbus_service_property(_interface, signature=u"s", access=u"read")
837
 
    def last_checked_ok_dbus_property(self):
 
897
    @dbus_service_property(_interface, signature=u"s",
 
898
                           access=u"readwrite")
 
899
    def last_checked_ok_dbus_property(self, value=None):
 
900
        if value is not None:
 
901
            self.checked_ok()
 
902
            return
838
903
        if self.last_checked_ok is None:
839
904
            return dbus.String(u"")
840
905
        return dbus.String(self._datetime_to_dbus(self
914
979
    def object_path_dbus_property(self):
915
980
        return self.dbus_object_path # is already a dbus.ObjectPath
916
981
    
917
 
    # secret = property xxx
 
982
    # secret = property
918
983
    @dbus_service_property(_interface, signature=u"ay",
919
984
                           access=u"write", byte_arrays=True)
920
985
    def secret_dbus_property(self, value):
932
997
    def handle(self):
933
998
        logger.info(u"TCP connection from: %s",
934
999
                    unicode(self.client_address))
935
 
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
 
1000
        logger.debug(u"IPC Pipe FD: %d",
 
1001
                     self.server.child_pipe[1].fileno())
936
1002
        # Open IPC pipe to parent process
937
 
        with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
 
1003
        with contextlib.nested(self.server.child_pipe[1],
 
1004
                               self.server.parent_pipe[0]
 
1005
                               ) as (ipc, ipc_return):
938
1006
            session = (gnutls.connection
939
1007
                       .ClientSession(self.request,
940
1008
                                      gnutls.connection
941
1009
                                      .X509Credentials()))
942
1010
            
943
 
            line = self.request.makefile().readline()
944
 
            logger.debug(u"Protocol version: %r", line)
945
 
            try:
946
 
                if int(line.strip().split()[0]) > 1:
947
 
                    raise RuntimeError
948
 
            except (ValueError, IndexError, RuntimeError), error:
949
 
                logger.error(u"Unknown protocol version: %s", error)
950
 
                return
951
 
            
952
1011
            # Note: gnutls.connection.X509Credentials is really a
953
1012
            # generic GnuTLS certificate credentials object so long as
954
1013
            # no X.509 keys are added to it.  Therefore, we can use it
966
1025
             .gnutls_priority_set_direct(session._c_object,
967
1026
                                         priority, None))
968
1027
            
 
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
969
1040
            try:
970
1041
                session.handshake()
971
1042
            except gnutls.errors.GNUTLSError, error:
975
1046
                return
976
1047
            logger.debug(u"Handshake succeeded")
977
1048
            try:
978
 
                fpr = self.fingerprint(self.peer_certificate(session))
979
 
            except (TypeError, gnutls.errors.GNUTLSError), error:
980
 
                logger.warning(u"Bad certificate: %s", error)
981
 
                session.bye()
982
 
                return
983
 
            logger.debug(u"Fingerprint: %s", fpr)
984
 
            
985
 
            for c in self.server.clients:
986
 
                if c.fingerprint == fpr:
987
 
                    client = c
988
 
                    break
989
 
            else:
990
 
                ipc.write(u"NOTFOUND %s %s\n"
991
 
                          % (fpr, unicode(self.client_address)))
992
 
                session.bye()
993
 
                return
994
 
            # Have to check if client.still_valid(), since it is
995
 
            # possible that the client timed out while establishing
996
 
            # the GnuTLS session.
997
 
            if not client.still_valid():
998
 
                ipc.write(u"INVALID %s\n" % client.name)
999
 
                session.bye()
1000
 
                return
1001
 
            ipc.write(u"SENDING %s\n" % client.name)
1002
 
            sent_size = 0
1003
 
            while sent_size < len(client.secret):
1004
 
                sent = session.send(client.secret[sent_size:])
1005
 
                logger.debug(u"Sent: %d, remaining: %d",
1006
 
                             sent, len(client.secret)
1007
 
                             - (sent_size + sent))
1008
 
                sent_size += sent
1009
 
            session.bye()
 
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
1099
    
1011
1100
    @staticmethod
1012
1101
    def peer_certificate(session):
1072
1161
        return hex_fpr
1073
1162
 
1074
1163
 
1075
 
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1076
 
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
 
1164
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
 
1165
    """Like socketserver.ForkingMixIn, but also pass a pipe pair."""
1077
1166
    def process_request(self, request, client_address):
1078
1167
        """Overrides and wraps the original process_request().
1079
1168
        
1080
1169
        This function creates a new pipe in self.pipe
1081
1170
        """
1082
 
        self.pipe = os.pipe()
1083
 
        super(ForkingMixInWithPipe,
 
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,
1084
1176
              self).process_request(request, client_address)
1085
 
        os.close(self.pipe[1])  # close write end
1086
 
        self.add_pipe(self.pipe[0])
1087
 
    def add_pipe(self, pipe):
 
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):
1088
1182
        """Dummy function; override as necessary"""
1089
 
        os.close(pipe)
1090
 
 
1091
 
 
1092
 
class IPv6_TCPServer(ForkingMixInWithPipe,
 
1183
        child_pipe_fd.close()
 
1184
        parent_pipe_fd.close()
 
1185
 
 
1186
 
 
1187
class IPv6_TCPServer(ForkingMixInWithPipes,
1093
1188
                     socketserver.TCPServer, object):
1094
1189
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1095
1190
    
1180
1275
            return socketserver.TCPServer.server_activate(self)
1181
1276
    def enable(self):
1182
1277
        self.enabled = True
1183
 
    def add_pipe(self, pipe):
 
1278
    def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1184
1279
        # Call "handle_ipc" for both data and EOF events
1185
 
        gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1186
 
                             self.handle_ipc)
1187
 
    def handle_ipc(self, source, condition, file_objects={}):
 
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):
1188
1286
        condition_names = {
1189
1287
            gobject.IO_IN: u"IN",   # There is data to read.
1190
1288
            gobject.IO_OUT: u"OUT", # Data can be written (without
1202
1300
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1203
1301
                     conditions_string)
1204
1302
        
1205
 
        # Turn the pipe file descriptor into a Python file object
1206
 
        if source not in file_objects:
1207
 
            file_objects[source] = os.fdopen(source, u"r", 1)
1208
 
        
1209
1303
        # Read a line from the file object
1210
 
        cmdline = file_objects[source].readline()
 
1304
        cmdline = sender.readline()
1211
1305
        if not cmdline:             # Empty line means end of file
1212
 
            # close the IPC pipe
1213
 
            file_objects[source].close()
1214
 
            del file_objects[source]
 
1306
            # close the IPC pipes
 
1307
            sender.close()
 
1308
            reply.close()
1215
1309
            
1216
1310
            # Stop calling this function
1217
1311
            return False
1222
1316
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1223
1317
        
1224
1318
        if cmd == u"NOTFOUND":
1225
 
            logger.warning(u"Client not found for fingerprint: %s",
1226
 
                           args)
 
1319
            fpr, address = args.split(None, 1)
 
1320
            logger.warning(u"Client not found for fingerprint: %s, ad"
 
1321
                           u"dress: %s", fpr, address)
1227
1322
            if self.use_dbus:
1228
1323
                # Emit D-Bus signal
1229
 
                mandos_dbus_service.ClientNotFound(args)
1230
 
        elif cmd == u"INVALID":
 
1324
                mandos_dbus_service.ClientNotFound(fpr, address)
 
1325
        elif cmd == u"DISABLED":
1231
1326
            for client in self.clients:
1232
1327
                if client.name == args:
1233
 
                    logger.warning(u"Client %s is invalid", args)
 
1328
                    logger.warning(u"Client %s is disabled", args)
1234
1329
                    if self.use_dbus:
1235
1330
                        # Emit D-Bus signal
1236
1331
                        client.Rejected()
1237
1332
                    break
1238
1333
            else:
1239
 
                logger.error(u"Unknown client %s is invalid", args)
 
1334
                logger.error(u"Unknown client %s is disabled", args)
1240
1335
        elif cmd == u"SENDING":
1241
1336
            for client in self.clients:
1242
1337
                if client.name == args:
1244
1339
                    client.checked_ok()
1245
1340
                    if self.use_dbus:
1246
1341
                        # Emit D-Bus signal
1247
 
                        client.ReceivedSecret()
 
1342
                        client.GotSecret()
1248
1343
                    break
1249
1344
            else:
1250
1345
                logger.error(u"Sending secret to unknown client %s",
1251
1346
                             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)
1252
1360
        else:
1253
1361
            logger.error(u"Unknown IPC command: %r", cmdline)
1254
1362
        
1288
1396
            elif suffix == u"w":
1289
1397
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1290
1398
            else:
1291
 
                raise ValueError
1292
 
        except (ValueError, IndexError):
1293
 
            raise ValueError
 
1399
                raise ValueError(u"Unknown suffix %r" % suffix)
 
1400
        except (ValueError, IndexError), e:
 
1401
            raise ValueError(e.message)
1294
1402
        timevalue += delta
1295
1403
    return timevalue
1296
1404
 
1309
1417
        def if_nametoindex(interface):
1310
1418
            "Get an interface index the hard way, i.e. using fcntl()"
1311
1419
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1312
 
            with closing(socket.socket()) as s:
 
1420
            with contextlib.closing(socket.socket()) as s:
1313
1421
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1314
1422
                                    struct.pack(str(u"16s16x"),
1315
1423
                                                interface))
1335
1443
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1336
1444
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1337
1445
            raise OSError(errno.ENODEV,
1338
 
                          u"/dev/null not a character device")
 
1446
                          u"%s not a character device"
 
1447
                          % os.path.devnull)
1339
1448
        os.dup2(null, sys.stdin.fileno())
1340
1449
        os.dup2(null, sys.stdout.fileno())
1341
1450
        os.dup2(null, sys.stderr.fileno())
1456
1565
    tcp_server = MandosServer((server_settings[u"address"],
1457
1566
                               server_settings[u"port"]),
1458
1567
                              ClientHandler,
1459
 
                              interface=server_settings[u"interface"],
 
1568
                              interface=(server_settings[u"interface"]
 
1569
                                         or None),
1460
1570
                              use_ipv6=use_ipv6,
1461
1571
                              gnutls_priority=
1462
1572
                              server_settings[u"priority"],
1508
1618
    bus = dbus.SystemBus()
1509
1619
    # End of Avahi example code
1510
1620
    if use_dbus:
1511
 
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
 
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
1512
1629
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1513
1630
    service = AvahiService(name = server_settings[u"servicename"],
1514
1631
                           servicetype = u"_mandos._tcp",
1540
1657
        daemon()
1541
1658
    
1542
1659
    try:
1543
 
        with closing(pidfile):
 
1660
        with pidfile:
1544
1661
            pid = os.getpid()
1545
1662
            pidfile.write(str(pid) + "\n")
1546
1663
        del pidfile
1552
1669
        pass
1553
1670
    del pidfilename
1554
1671
    
1555
 
    def cleanup():
1556
 
        "Cleanup function; run on exit"
1557
 
        service.cleanup()
1558
 
        
1559
 
        while tcp_server.clients:
1560
 
            client = tcp_server.clients.pop()
1561
 
            client.disable_hook = None
1562
 
            client.disable()
1563
 
    
1564
 
    atexit.register(cleanup)
1565
 
    
1566
1672
    if not debug:
1567
1673
        signal.signal(signal.SIGINT, signal.SIG_IGN)
1568
1674
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1575
1681
                dbus.service.Object.__init__(self, bus, u"/")
1576
1682
            _interface = u"se.bsnet.fukt.Mandos"
1577
1683
            
1578
 
            @dbus.service.signal(_interface, signature=u"oa{sv}")
1579
 
            def ClientAdded(self, objpath, properties):
 
1684
            @dbus.service.signal(_interface, signature=u"o")
 
1685
            def ClientAdded(self, objpath):
1580
1686
                "D-Bus signal"
1581
1687
                pass
1582
1688
            
1583
 
            @dbus.service.signal(_interface, signature=u"s")
1584
 
            def ClientNotFound(self, fingerprint):
 
1689
            @dbus.service.signal(_interface, signature=u"ss")
 
1690
            def ClientNotFound(self, fingerprint, address):
1585
1691
                "D-Bus signal"
1586
1692
                pass
1587
1693
            
1613
1719
                        tcp_server.clients.remove(c)
1614
1720
                        c.remove_from_connection()
1615
1721
                        # Don't signal anything except ClientRemoved
1616
 
                        c.disable(signal=False)
 
1722
                        c.disable(quiet=True)
1617
1723
                        # Emit D-Bus signal
1618
1724
                        self.ClientRemoved(object_path, c.name)
1619
1725
                        return
1620
 
                raise KeyError
 
1726
                raise KeyError(object_path)
1621
1727
            
1622
1728
            del _interface
1623
1729
        
1624
1730
        mandos_dbus_service = MandosDBusService()
1625
1731
    
 
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
    
1626
1750
    for client in tcp_server.clients:
1627
1751
        if use_dbus:
1628
1752
            # Emit D-Bus signal
1629
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
1630
 
                                            client.GetAll(u""))
 
1753
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
1631
1754
        client.enable()
1632
1755
    
1633
1756
    tcp_server.enable()
1651
1774
            service.activate()
1652
1775
        except dbus.exceptions.DBusException, error:
1653
1776
            logger.critical(u"DBusException: %s", error)
 
1777
            cleanup()
1654
1778
            sys.exit(1)
1655
1779
        # End of Avahi example code
1656
1780
        
1663
1787
        main_loop.run()
1664
1788
    except AvahiError, error:
1665
1789
        logger.critical(u"AvahiError: %s", error)
 
1790
        cleanup()
1666
1791
        sys.exit(1)
1667
1792
    except KeyboardInterrupt:
1668
1793
        if debug:
1669
1794
            print >> sys.stderr
1670
1795
        logger.debug(u"Server received KeyboardInterrupt")
1671
1796
    logger.debug(u"Server exiting")
 
1797
    # Must run before the D-Bus bus name gets deregistered
 
1798
    cleanup()
1672
1799
 
1673
1800
if __name__ == '__main__':
1674
1801
    main()