/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 at bsnet
  • Date: 2010-08-23 19:23:15 UTC
  • mto: (24.1.154 mandos)
  • mto: This revision was merged to the branch mainline in revision 421.
  • Revision ID: teddy@fukt.bsnet.se-20100823192315-pefgye0l6cavcejs
* debian/control (mandos/Depends): Added "python-urwid".
* mandos (Client.approved_by_default): Changed default to "True".
  (Client.approved_delay): Changed default to "0s".
  (ClientDBus.GotSecret, ClientDBus.Rejected,
  ClientDBus.NeedApproval): Emit "PropertyChanged" signal for the
                            "approved_pending" property.

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 multiprocessing
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
96
98
                                       u' %(message)s'))
97
99
logger.addHandler(console)
98
100
 
 
101
multiprocessing_manager = multiprocessing.Manager()
 
102
 
99
103
class AvahiError(Exception):
100
104
    def __init__(self, value, *args, **kwargs):
101
105
        self.value = value
242
246
    enabled:    bool()
243
247
    last_checked_ok: datetime.datetime(); (UTC) or None
244
248
    timeout:    datetime.timedelta(); How long from last_checked_ok
245
 
                                      until this client is invalid
 
249
                                      until this client is disabled
246
250
    interval:   datetime.timedelta(); How often to start a new checker
247
251
    disable_hook:  If set, called by disable() as disable_hook(self)
248
252
    checker:    subprocess.Popen(); a running checker process used
256
260
                     runtime with vars(self) as dict, so that for
257
261
                     instance %(name)s can be used in the command.
258
262
    current_checker_command: string; current running checker_command
 
263
    approved_delay: datetime.timedelta(); Time to wait for approval
 
264
    _approved:   bool(); 'None' if not yet approved/disapproved
 
265
    approved_duration: datetime.timedelta(); Duration of one approval
259
266
    """
260
267
    
261
268
    @staticmethod
272
279
    def interval_milliseconds(self):
273
280
        "Return the 'interval' attribute in milliseconds"
274
281
        return self._timedelta_to_milliseconds(self.interval)
 
282
 
 
283
    def approved_delay_milliseconds(self):
 
284
        return self._timedelta_to_milliseconds(self.approved_delay)
275
285
    
276
286
    def __init__(self, name = None, disable_hook=None, config=None):
277
287
        """Note: the 'checker' key in 'config' sets the
290
300
        if u"secret" in config:
291
301
            self.secret = config[u"secret"].decode(u"base64")
292
302
        elif u"secfile" in config:
293
 
            with closing(open(os.path.expanduser
294
 
                              (os.path.expandvars
295
 
                               (config[u"secfile"])),
296
 
                              "rb")) as secfile:
 
303
            with open(os.path.expanduser(os.path.expandvars
 
304
                                         (config[u"secfile"])),
 
305
                      "rb") as secfile:
297
306
                self.secret = secfile.read()
298
307
        else:
 
308
            #XXX Need to allow secret on demand!
299
309
            raise TypeError(u"No secret or secfile for client %s"
300
310
                            % self.name)
301
311
        self.host = config.get(u"host", u"")
313
323
        self.checker_command = config[u"checker"]
314
324
        self.current_checker_command = None
315
325
        self.last_connect = None
316
 
    
 
326
        self.approvals_pending = 0
 
327
        self._approved = None
 
328
        self.approved_by_default = config.get(u"approved_by_default",
 
329
                                              True)
 
330
        self.approved_delay = string_to_delta(
 
331
            config[u"approved_delay"])
 
332
        self.approved_duration = string_to_delta(
 
333
            config[u"approved_duration"])
 
334
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
 
335
 
 
336
    def send_changedstate(self):
 
337
        self.changedstate.acquire()
 
338
        self.changedstate.notify_all()
 
339
        self.changedstate.release()
 
340
        
317
341
    def enable(self):
318
342
        """Start this client's checker and timeout hooks"""
319
343
        if getattr(self, u"enabled", False):
320
344
            # Already enabled
321
345
            return
 
346
        self.send_changedstate()
322
347
        self.last_enabled = datetime.datetime.utcnow()
323
348
        # Schedule a new checker to be started an 'interval' from now,
324
349
        # and every interval from then on.
325
350
        self.checker_initiator_tag = (gobject.timeout_add
326
351
                                      (self.interval_milliseconds(),
327
352
                                       self.start_checker))
328
 
        # Also start a new checker *right now*.
329
 
        self.start_checker()
330
353
        # Schedule a disable() when 'timeout' has passed
331
354
        self.disable_initiator_tag = (gobject.timeout_add
332
355
                                   (self.timeout_milliseconds(),
333
356
                                    self.disable))
334
357
        self.enabled = True
 
358
        # Also start a new checker *right now*.
 
359
        self.start_checker()
335
360
    
336
 
    def disable(self):
 
361
    def disable(self, quiet=True):
337
362
        """Disable this client."""
338
363
        if not getattr(self, "enabled", False):
339
364
            return False
340
 
        logger.info(u"Disabling client %s", self.name)
 
365
        if not quiet:
 
366
            self.send_changedstate()
 
367
        if not quiet:
 
368
            logger.info(u"Disabling client %s", self.name)
341
369
        if getattr(self, u"disable_initiator_tag", False):
342
370
            gobject.source_remove(self.disable_initiator_tag)
343
371
            self.disable_initiator_tag = None
395
423
        # client would inevitably timeout, since no checker would get
396
424
        # a chance to run to completion.  If we instead leave running
397
425
        # checkers alone, the checker would have to take more time
398
 
        # than 'timeout' for the client to be declared invalid, which
399
 
        # is as it should be.
 
426
        # than 'timeout' for the client to be disabled, which is as it
 
427
        # should be.
400
428
        
401
429
        # If a checker exists, make sure it is not a zombie
402
430
        try:
467
495
        logger.debug(u"Stopping checker for %(name)s", vars(self))
468
496
        try:
469
497
            os.kill(self.checker.pid, signal.SIGTERM)
470
 
            #os.sleep(0.5)
 
498
            #time.sleep(0.5)
471
499
            #if self.checker.poll() is None:
472
500
            #    os.kill(self.checker.pid, signal.SIGKILL)
473
501
        except OSError, error:
474
502
            if error.errno != errno.ESRCH: # No such process
475
503
                raise
476
504
        self.checker = None
477
 
    
478
 
    def still_valid(self):
479
 
        """Has the timeout not yet passed for this client?"""
480
 
        if not getattr(self, u"enabled", False):
481
 
            return False
482
 
        now = datetime.datetime.utcnow()
483
 
        if self.last_checked_ok is None:
484
 
            return now < (self.created + self.timeout)
485
 
        else:
486
 
            return now < (self.last_checked_ok + self.timeout)
487
 
 
488
505
 
489
506
def dbus_service_property(dbus_interface, signature=u"v",
490
507
                          access=u"readwrite", byte_arrays=False):
498
515
    dbus.service.method, except there is only "signature", since the
499
516
    type from Get() and the type sent to Set() is the same.
500
517
    """
 
518
    # Encoding deeply encoded byte arrays is not supported yet by the
 
519
    # "Set" method, so we fail early here:
 
520
    if byte_arrays and signature != u"ay":
 
521
        raise ValueError(u"Byte arrays not supported for non-'ay'"
 
522
                         u" signature %r" % signature)
501
523
    def decorator(func):
502
524
        func._dbus_is_property = True
503
525
        func._dbus_interface = dbus_interface
589
611
        if prop._dbus_access == u"read":
590
612
            raise DBusPropertyAccessException(property_name)
591
613
        if prop._dbus_get_args_options[u"byte_arrays"]:
 
614
            # The byte_arrays option is not supported yet on
 
615
            # signatures other than "ay".
 
616
            if prop._dbus_signature != u"ay":
 
617
                raise ValueError
592
618
            value = dbus.ByteArray(''.join(unichr(byte)
593
619
                                           for byte in value))
594
620
        prop(value)
626
652
        """Standard D-Bus method, overloaded to insert property tags.
627
653
        """
628
654
        xmlstring = dbus.service.Object.Introspect(self, object_path,
629
 
                                           connection)
630
 
        document = xml.dom.minidom.parseString(xmlstring)
631
 
        del xmlstring
632
 
        def make_tag(document, name, prop):
633
 
            e = document.createElement(u"property")
634
 
            e.setAttribute(u"name", name)
635
 
            e.setAttribute(u"type", prop._dbus_signature)
636
 
            e.setAttribute(u"access", prop._dbus_access)
637
 
            return e
638
 
        for if_tag in document.getElementsByTagName(u"interface"):
639
 
            for tag in (make_tag(document, name, prop)
640
 
                        for name, prop
641
 
                        in self._get_all_dbus_properties()
642
 
                        if prop._dbus_interface
643
 
                        == if_tag.getAttribute(u"name")):
644
 
                if_tag.appendChild(tag)
645
 
        xmlstring = document.toxml(u"utf-8")
646
 
        document.unlink()
 
655
                                                   connection)
 
656
        try:
 
657
            document = xml.dom.minidom.parseString(xmlstring)
 
658
            def make_tag(document, name, prop):
 
659
                e = document.createElement(u"property")
 
660
                e.setAttribute(u"name", name)
 
661
                e.setAttribute(u"type", prop._dbus_signature)
 
662
                e.setAttribute(u"access", prop._dbus_access)
 
663
                return e
 
664
            for if_tag in document.getElementsByTagName(u"interface"):
 
665
                for tag in (make_tag(document, name, prop)
 
666
                            for name, prop
 
667
                            in self._get_all_dbus_properties()
 
668
                            if prop._dbus_interface
 
669
                            == if_tag.getAttribute(u"name")):
 
670
                    if_tag.appendChild(tag)
 
671
                # Add the names to the return values for the
 
672
                # "org.freedesktop.DBus.Properties" methods
 
673
                if (if_tag.getAttribute(u"name")
 
674
                    == u"org.freedesktop.DBus.Properties"):
 
675
                    for cn in if_tag.getElementsByTagName(u"method"):
 
676
                        if cn.getAttribute(u"name") == u"Get":
 
677
                            for arg in cn.getElementsByTagName(u"arg"):
 
678
                                if (arg.getAttribute(u"direction")
 
679
                                    == u"out"):
 
680
                                    arg.setAttribute(u"name", u"value")
 
681
                        elif cn.getAttribute(u"name") == u"GetAll":
 
682
                            for arg in cn.getElementsByTagName(u"arg"):
 
683
                                if (arg.getAttribute(u"direction")
 
684
                                    == u"out"):
 
685
                                    arg.setAttribute(u"name", u"props")
 
686
            xmlstring = document.toxml(u"utf-8")
 
687
            document.unlink()
 
688
        except (AttributeError, xml.dom.DOMException,
 
689
                xml.parsers.expat.ExpatError), error:
 
690
            logger.error(u"Failed to override Introspection method",
 
691
                         error)
647
692
        return xmlstring
648
693
 
649
694
 
686
731
                                       variant_level=1))
687
732
        return r
688
733
    
689
 
    def disable(self, signal = True):
 
734
    def disable(self, quiet = False):
690
735
        oldstate = getattr(self, u"enabled", False)
691
 
        r = Client.disable(self)
692
 
        if signal and oldstate != self.enabled:
 
736
        r = Client.disable(self, quiet=quiet)
 
737
        if not quiet and oldstate != self.enabled:
693
738
            # Emit D-Bus signal
694
739
            self.PropertyChanged(dbus.String(u"enabled"),
695
740
                                 dbus.Boolean(False, variant_level=1))
760
805
            self.PropertyChanged(dbus.String(u"checker_running"),
761
806
                                 dbus.Boolean(False, variant_level=1))
762
807
        return r
763
 
    
764
 
    ## D-Bus methods & signals
 
808
 
 
809
    def _reset_approved(self):
 
810
        self._approved = None
 
811
        return False
 
812
    
 
813
    def approve(self, value=True):
 
814
        self._approved = value
 
815
        gobject.timeout_add(self._timedelta_to_milliseconds(self.approved_duration, self._reset_approved))
 
816
 
 
817
    def approved_pending(self):
 
818
        return self.approvals_pending > 0
 
819
    
 
820
    
 
821
    ## D-Bus methods, signals & properties
765
822
    _interface = u"se.bsnet.fukt.Mandos.Client"
766
823
    
767
 
    # CheckedOK - method
768
 
    @dbus.service.method(_interface)
769
 
    def CheckedOK(self):
770
 
        return self.checked_ok()
 
824
    ## Signals
771
825
    
772
826
    # CheckerCompleted - signal
773
827
    @dbus.service.signal(_interface, signature=u"nxs")
787
841
        "D-Bus signal"
788
842
        pass
789
843
    
790
 
    # ReceivedSecret - signal
 
844
    # GotSecret - signal
791
845
    @dbus.service.signal(_interface)
792
 
    def ReceivedSecret(self):
 
846
    def GotSecret(self):
793
847
        "D-Bus signal"
794
 
        pass
 
848
        if self.approved_pending():
 
849
            self.PropertyChanged(dbus.String(u"checker_running"),
 
850
                                 dbus.Boolean(False, variant_level=1))
795
851
    
796
852
    # Rejected - signal
797
 
    @dbus.service.signal(_interface)
798
 
    def Rejected(self):
799
 
        "D-Bus signal"
800
 
        pass
 
853
    @dbus.service.signal(_interface, signature=u"s")
 
854
    def Rejected(self, reason):
 
855
        "D-Bus signal"
 
856
        if self.approved_pending():
 
857
            self.PropertyChanged(dbus.String(u"checker_running"),
 
858
                                 dbus.Boolean(False, variant_level=1))
 
859
    
 
860
    # NeedApproval - signal
 
861
    @dbus.service.signal(_interface, signature=u"db")
 
862
    def NeedApproval(self, timeout, default):
 
863
        "D-Bus signal"
 
864
        if not self.approved_pending():
 
865
            self.PropertyChanged(dbus.String(u"approved_pending"),
 
866
                                 dbus.Boolean(True, variant_level=1))
 
867
    
 
868
    ## Methods
 
869
 
 
870
    # Approve - method
 
871
    @dbus.service.method(_interface, in_signature=u"b")
 
872
    def Approve(self, value):
 
873
        self.approve(value)
 
874
 
 
875
    # CheckedOK - method
 
876
    @dbus.service.method(_interface)
 
877
    def CheckedOK(self):
 
878
        return self.checked_ok()
801
879
    
802
880
    # Enable - method
803
881
    @dbus.service.method(_interface)
822
900
    def StopChecker(self):
823
901
        self.stop_checker()
824
902
    
 
903
    ## Properties
 
904
    
 
905
    # approved_pending - property
 
906
    @dbus_service_property(_interface, signature=u"b", access=u"read")
 
907
    def approved_pending_dbus_property(self):
 
908
        return dbus.Boolean(self.approved_pending())
 
909
    
 
910
    # approved_by_default - property
 
911
    @dbus_service_property(_interface, signature=u"b",
 
912
                           access=u"readwrite")
 
913
    def approved_by_default_dbus_property(self):
 
914
        return dbus.Boolean(self.approved_by_default)
 
915
    
 
916
    # approved_delay - property
 
917
    @dbus_service_property(_interface, signature=u"t",
 
918
                           access=u"readwrite")
 
919
    def approved_delay_dbus_property(self):
 
920
        return dbus.UInt64(self.approved_delay_milliseconds())
 
921
    
 
922
    # approved_duration - property
 
923
    @dbus_service_property(_interface, signature=u"t",
 
924
                           access=u"readwrite")
 
925
    def approved_duration_dbus_property(self):
 
926
        return dbus.UInt64(self._timedelta_to_milliseconds(
 
927
                self.approved_duration))
 
928
    
825
929
    # name - property
826
930
    @dbus_service_property(_interface, signature=u"s", access=u"read")
827
931
    def name_dbus_property(self):
961
1065
    del _interface
962
1066
 
963
1067
 
 
1068
class ProxyClient(object):
 
1069
    def __init__(self, child_pipe, fpr, address):
 
1070
        self._pipe = child_pipe
 
1071
        self._pipe.send(('init', fpr, address))
 
1072
        if not self._pipe.recv():
 
1073
            raise KeyError()
 
1074
 
 
1075
    def __getattribute__(self, name):
 
1076
        if(name == '_pipe'):
 
1077
            return super(ProxyClient, self).__getattribute__(name)
 
1078
        self._pipe.send(('getattr', name))
 
1079
        data = self._pipe.recv()
 
1080
        if data[0] == 'data':
 
1081
            return data[1]
 
1082
        if data[0] == 'function':
 
1083
            def func(*args, **kwargs):
 
1084
                self._pipe.send(('funcall', name, args, kwargs))
 
1085
                return self._pipe.recv()[1]
 
1086
            return func
 
1087
 
 
1088
    def __setattr__(self, name, value):
 
1089
        if(name == '_pipe'):
 
1090
            return super(ProxyClient, self).__setattr__(name, value)
 
1091
        self._pipe.send(('setattr', name, value))
 
1092
 
 
1093
 
964
1094
class ClientHandler(socketserver.BaseRequestHandler, object):
965
1095
    """A class to handle client connections.
966
1096
    
968
1098
    Note: This will run in its own forked process."""
969
1099
    
970
1100
    def handle(self):
971
 
        logger.info(u"TCP connection from: %s",
972
 
                    unicode(self.client_address))
973
 
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
974
 
        # Open IPC pipe to parent process
975
 
        with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
 
1101
        with contextlib.closing(self.server.child_pipe) as child_pipe:
 
1102
            logger.info(u"TCP connection from: %s",
 
1103
                        unicode(self.client_address))
 
1104
            logger.debug(u"Pipe FD: %d",
 
1105
                         self.server.child_pipe.fileno())
 
1106
 
976
1107
            session = (gnutls.connection
977
1108
                       .ClientSession(self.request,
978
1109
                                      gnutls.connection
979
1110
                                      .X509Credentials()))
980
 
            
981
 
            line = self.request.makefile().readline()
982
 
            logger.debug(u"Protocol version: %r", line)
983
 
            try:
984
 
                if int(line.strip().split()[0]) > 1:
985
 
                    raise RuntimeError
986
 
            except (ValueError, IndexError, RuntimeError), error:
987
 
                logger.error(u"Unknown protocol version: %s", error)
988
 
                return
989
 
            
 
1111
 
990
1112
            # Note: gnutls.connection.X509Credentials is really a
991
1113
            # generic GnuTLS certificate credentials object so long as
992
1114
            # no X.509 keys are added to it.  Therefore, we can use it
993
1115
            # here despite using OpenPGP certificates.
994
 
            
 
1116
 
995
1117
            #priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
996
1118
            #                      u"+AES-256-CBC", u"+SHA1",
997
1119
            #                      u"+COMP-NULL", u"+CTYPE-OPENPGP",
1003
1125
            (gnutls.library.functions
1004
1126
             .gnutls_priority_set_direct(session._c_object,
1005
1127
                                         priority, None))
1006
 
            
 
1128
 
 
1129
            # Start communication using the Mandos protocol
 
1130
            # Get protocol number
 
1131
            line = self.request.makefile().readline()
 
1132
            logger.debug(u"Protocol version: %r", line)
 
1133
            try:
 
1134
                if int(line.strip().split()[0]) > 1:
 
1135
                    raise RuntimeError
 
1136
            except (ValueError, IndexError, RuntimeError), error:
 
1137
                logger.error(u"Unknown protocol version: %s", error)
 
1138
                return
 
1139
 
 
1140
            # Start GnuTLS connection
1007
1141
            try:
1008
1142
                session.handshake()
1009
1143
            except gnutls.errors.GNUTLSError, error:
1012
1146
                # established.  Just abandon the request.
1013
1147
                return
1014
1148
            logger.debug(u"Handshake succeeded")
 
1149
 
 
1150
            approval_required = False
1015
1151
            try:
1016
 
                fpr = self.fingerprint(self.peer_certificate(session))
1017
 
            except (TypeError, gnutls.errors.GNUTLSError), error:
1018
 
                logger.warning(u"Bad certificate: %s", error)
1019
 
                session.bye()
1020
 
                return
1021
 
            logger.debug(u"Fingerprint: %s", fpr)
 
1152
                try:
 
1153
                    fpr = self.fingerprint(self.peer_certificate
 
1154
                                           (session))
 
1155
                except (TypeError, gnutls.errors.GNUTLSError), error:
 
1156
                    logger.warning(u"Bad certificate: %s", error)
 
1157
                    return
 
1158
                logger.debug(u"Fingerprint: %s", fpr)
 
1159
 
 
1160
                try:
 
1161
                    client = ProxyClient(child_pipe, fpr,
 
1162
                                         self.client_address)
 
1163
                except KeyError:
 
1164
                    return
 
1165
                
 
1166
                if client.approved_delay:
 
1167
                    delay = client.approved_delay
 
1168
                    client.approvals_pending += 1
 
1169
                    approval_required = True
 
1170
                
 
1171
                while True:
 
1172
                    if not client.enabled:
 
1173
                        logger.warning(u"Client %s is disabled",
 
1174
                                       client.name)
 
1175
                        if self.server.use_dbus:
 
1176
                            # Emit D-Bus signal
 
1177
                            client.Rejected("Disabled")                    
 
1178
                        return
 
1179
                    
 
1180
                    if client._approved or not client.approved_delay:
 
1181
                        #We are approved or approval is disabled
 
1182
                        break
 
1183
                    elif client._approved is None:
 
1184
                        logger.info(u"Client %s need approval",
 
1185
                                    client.name)
 
1186
                        if self.server.use_dbus:
 
1187
                            # Emit D-Bus signal
 
1188
                            client.NeedApproval(
 
1189
                                client.approved_delay_milliseconds(),
 
1190
                                client.approved_by_default)
 
1191
                    else:
 
1192
                        logger.warning(u"Client %s was not approved",
 
1193
                                       client.name)
 
1194
                        if self.server.use_dbus:
 
1195
                            # Emit D-Bus signal
 
1196
                            client.Rejected("Disapproved")
 
1197
                        return
 
1198
                    
 
1199
                    #wait until timeout or approved
 
1200
                    #x = float(client._timedelta_to_milliseconds(delay))
 
1201
                    time = datetime.datetime.now()
 
1202
                    client.changedstate.acquire()
 
1203
                    client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
 
1204
                    client.changedstate.release()
 
1205
                    time2 = datetime.datetime.now()
 
1206
                    if (time2 - time) >= delay:
 
1207
                        if not client.approved_by_default:
 
1208
                            logger.warning("Client %s timed out while"
 
1209
                                           " waiting for approval",
 
1210
                                           client.name)
 
1211
                            if self.server.use_dbus:
 
1212
                                # Emit D-Bus signal
 
1213
                                client.Rejected("Time out")
 
1214
                            return
 
1215
                        else:
 
1216
                            break
 
1217
                    else:
 
1218
                        delay -= time2 - time
 
1219
                
 
1220
                sent_size = 0
 
1221
                while sent_size < len(client.secret):
 
1222
                    # XXX handle session exception
 
1223
                    sent = session.send(client.secret[sent_size:])
 
1224
                    logger.debug(u"Sent: %d, remaining: %d",
 
1225
                                 sent, len(client.secret)
 
1226
                                 - (sent_size + sent))
 
1227
                    sent_size += sent
 
1228
 
 
1229
                logger.info(u"Sending secret to %s", client.name)
 
1230
                # bump the timeout as if seen
 
1231
                client.checked_ok()
 
1232
                if self.server.use_dbus:
 
1233
                    # Emit D-Bus signal
 
1234
                    client.GotSecret()
1022
1235
            
1023
 
            for c in self.server.clients:
1024
 
                if c.fingerprint == fpr:
1025
 
                    client = c
1026
 
                    break
1027
 
            else:
1028
 
                ipc.write(u"NOTFOUND %s %s\n"
1029
 
                          % (fpr, unicode(self.client_address)))
1030
 
                session.bye()
1031
 
                return
1032
 
            # Have to check if client.still_valid(), since it is
1033
 
            # possible that the client timed out while establishing
1034
 
            # the GnuTLS session.
1035
 
            if not client.still_valid():
1036
 
                ipc.write(u"INVALID %s\n" % client.name)
1037
 
                session.bye()
1038
 
                return
1039
 
            ipc.write(u"SENDING %s\n" % client.name)
1040
 
            sent_size = 0
1041
 
            while sent_size < len(client.secret):
1042
 
                sent = session.send(client.secret[sent_size:])
1043
 
                logger.debug(u"Sent: %d, remaining: %d",
1044
 
                             sent, len(client.secret)
1045
 
                             - (sent_size + sent))
1046
 
                sent_size += sent
1047
 
            session.bye()
 
1236
            finally:
 
1237
                if approval_required:
 
1238
                    client.approvals_pending -= 1
 
1239
                session.bye()
1048
1240
    
1049
1241
    @staticmethod
1050
1242
    def peer_certificate(session):
1110
1302
        return hex_fpr
1111
1303
 
1112
1304
 
1113
 
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1114
 
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
 
1305
class MultiprocessingMixIn(object):
 
1306
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
 
1307
    def sub_process_main(self, request, address):
 
1308
        try:
 
1309
            self.finish_request(request, address)
 
1310
        except:
 
1311
            self.handle_error(request, address)
 
1312
        self.close_request(request)
 
1313
            
 
1314
    def process_request(self, request, address):
 
1315
        """Start a new process to process the request."""
 
1316
        multiprocessing.Process(target = self.sub_process_main,
 
1317
                                args = (request, address)).start()
 
1318
 
 
1319
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
 
1320
    """ adds a pipe to the MixIn """
1115
1321
    def process_request(self, request, client_address):
1116
1322
        """Overrides and wraps the original process_request().
1117
1323
        
1118
1324
        This function creates a new pipe in self.pipe
1119
1325
        """
1120
 
        self.pipe = os.pipe()
1121
 
        super(ForkingMixInWithPipe,
 
1326
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
 
1327
 
 
1328
        super(MultiprocessingMixInWithPipe,
1122
1329
              self).process_request(request, client_address)
1123
 
        os.close(self.pipe[1])  # close write end
1124
 
        self.add_pipe(self.pipe[0])
1125
 
    def add_pipe(self, pipe):
 
1330
        self.add_pipe(parent_pipe)
 
1331
    def add_pipe(self, parent_pipe):
1126
1332
        """Dummy function; override as necessary"""
1127
 
        os.close(pipe)
1128
 
 
1129
 
 
1130
 
class IPv6_TCPServer(ForkingMixInWithPipe,
 
1333
        pass
 
1334
 
 
1335
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1131
1336
                     socketserver.TCPServer, object):
1132
1337
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1133
1338
    
1218
1423
            return socketserver.TCPServer.server_activate(self)
1219
1424
    def enable(self):
1220
1425
        self.enabled = True
1221
 
    def add_pipe(self, pipe):
 
1426
    def add_pipe(self, parent_pipe):
1222
1427
        # Call "handle_ipc" for both data and EOF events
1223
 
        gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1224
 
                             self.handle_ipc)
1225
 
    def handle_ipc(self, source, condition, file_objects={}):
 
1428
        gobject.io_add_watch(parent_pipe.fileno(),
 
1429
                             gobject.IO_IN | gobject.IO_HUP,
 
1430
                             functools.partial(self.handle_ipc,
 
1431
                                               parent_pipe = parent_pipe))
 
1432
        
 
1433
    def handle_ipc(self, source, condition, parent_pipe=None,
 
1434
                   client_object=None):
1226
1435
        condition_names = {
1227
1436
            gobject.IO_IN: u"IN",   # There is data to read.
1228
1437
            gobject.IO_OUT: u"OUT", # Data can be written (without
1240
1449
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1241
1450
                     conditions_string)
1242
1451
        
1243
 
        # Turn the pipe file descriptor into a Python file object
1244
 
        if source not in file_objects:
1245
 
            file_objects[source] = os.fdopen(source, u"r", 1)
 
1452
        # Read a request from the child
 
1453
        request = parent_pipe.recv()
 
1454
        command = request[0]
1246
1455
        
1247
 
        # Read a line from the file object
1248
 
        cmdline = file_objects[source].readline()
1249
 
        if not cmdline:             # Empty line means end of file
1250
 
            # close the IPC pipe
1251
 
            file_objects[source].close()
1252
 
            del file_objects[source]
1253
 
            
1254
 
            # Stop calling this function
 
1456
        if command == 'init':
 
1457
            fpr = request[1]
 
1458
            address = request[2]
 
1459
            
 
1460
            for c in self.clients:
 
1461
                if c.fingerprint == fpr:
 
1462
                    client = c
 
1463
                    break
 
1464
            else:
 
1465
                logger.warning(u"Client not found for fingerprint: %s, ad"
 
1466
                               u"dress: %s", fpr, address)
 
1467
                if self.use_dbus:
 
1468
                    # Emit D-Bus signal
 
1469
                    mandos_dbus_service.ClientNotFound(fpr, address)
 
1470
                parent_pipe.send(False)
 
1471
                return False
 
1472
            
 
1473
            gobject.io_add_watch(parent_pipe.fileno(),
 
1474
                                 gobject.IO_IN | gobject.IO_HUP,
 
1475
                                 functools.partial(self.handle_ipc,
 
1476
                                                   parent_pipe = parent_pipe,
 
1477
                                                   client_object = client))
 
1478
            parent_pipe.send(True)
 
1479
            # remove the old hook in favor of the new above hook on same fileno
1255
1480
            return False
1256
 
        
1257
 
        logger.debug(u"IPC command: %r", cmdline)
1258
 
        
1259
 
        # Parse and act on command
1260
 
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1261
 
        
1262
 
        if cmd == u"NOTFOUND":
1263
 
            logger.warning(u"Client not found for fingerprint: %s",
1264
 
                           args)
1265
 
            if self.use_dbus:
1266
 
                # Emit D-Bus signal
1267
 
                mandos_dbus_service.ClientNotFound(args)
1268
 
        elif cmd == u"INVALID":
1269
 
            for client in self.clients:
1270
 
                if client.name == args:
1271
 
                    logger.warning(u"Client %s is invalid", args)
1272
 
                    if self.use_dbus:
1273
 
                        # Emit D-Bus signal
1274
 
                        client.Rejected()
1275
 
                    break
1276
 
            else:
1277
 
                logger.error(u"Unknown client %s is invalid", args)
1278
 
        elif cmd == u"SENDING":
1279
 
            for client in self.clients:
1280
 
                if client.name == args:
1281
 
                    logger.info(u"Sending secret to %s", client.name)
1282
 
                    client.checked_ok()
1283
 
                    if self.use_dbus:
1284
 
                        # Emit D-Bus signal
1285
 
                        client.ReceivedSecret()
1286
 
                    break
1287
 
            else:
1288
 
                logger.error(u"Sending secret to unknown client %s",
1289
 
                             args)
1290
 
        else:
1291
 
            logger.error(u"Unknown IPC command: %r", cmdline)
1292
 
        
1293
 
        # Keep calling this function
 
1481
        if command == 'funcall':
 
1482
            funcname = request[1]
 
1483
            args = request[2]
 
1484
            kwargs = request[3]
 
1485
            
 
1486
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
 
1487
 
 
1488
        if command == 'getattr':
 
1489
            attrname = request[1]
 
1490
            if callable(client_object.__getattribute__(attrname)):
 
1491
                parent_pipe.send(('function',))
 
1492
            else:
 
1493
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
 
1494
 
 
1495
        if command == 'setattr':
 
1496
            attrname = request[1]
 
1497
            value = request[2]
 
1498
            setattr(client_object, attrname, value)
 
1499
            
1294
1500
        return True
1295
1501
 
1296
1502
 
1326
1532
            elif suffix == u"w":
1327
1533
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1328
1534
            else:
1329
 
                raise ValueError
1330
 
        except (ValueError, IndexError):
1331
 
            raise ValueError
 
1535
                raise ValueError(u"Unknown suffix %r" % suffix)
 
1536
        except (ValueError, IndexError), e:
 
1537
            raise ValueError(e.message)
1332
1538
        timevalue += delta
1333
1539
    return timevalue
1334
1540
 
1347
1553
        def if_nametoindex(interface):
1348
1554
            "Get an interface index the hard way, i.e. using fcntl()"
1349
1555
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1350
 
            with closing(socket.socket()) as s:
 
1556
            with contextlib.closing(socket.socket()) as s:
1351
1557
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1352
1558
                                    struct.pack(str(u"16s16x"),
1353
1559
                                                interface))
1373
1579
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1374
1580
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1375
1581
            raise OSError(errno.ENODEV,
1376
 
                          u"/dev/null not a character device")
 
1582
                          u"%s not a character device"
 
1583
                          % os.path.devnull)
1377
1584
        os.dup2(null, sys.stdin.fileno())
1378
1585
        os.dup2(null, sys.stdout.fileno())
1379
1586
        os.dup2(null, sys.stderr.fileno())
1483
1690
                        u"interval": u"5m",
1484
1691
                        u"checker": u"fping -q -- %%(host)s",
1485
1692
                        u"host": u"",
 
1693
                        u"approved_delay": u"0s",
 
1694
                        u"approved_duration": u"1s",
1486
1695
                        }
1487
1696
    client_config = configparser.SafeConfigParser(client_defaults)
1488
1697
    client_config.read(os.path.join(server_settings[u"configdir"],
1546
1755
    bus = dbus.SystemBus()
1547
1756
    # End of Avahi example code
1548
1757
    if use_dbus:
1549
 
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
 
1758
        try:
 
1759
            bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
 
1760
                                            bus, do_not_queue=True)
 
1761
        except dbus.exceptions.NameExistsException, e:
 
1762
            logger.error(unicode(e) + u", disabling D-Bus")
 
1763
            use_dbus = False
 
1764
            server_settings[u"use_dbus"] = False
 
1765
            tcp_server.use_dbus = False
1550
1766
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1551
1767
    service = AvahiService(name = server_settings[u"servicename"],
1552
1768
                           servicetype = u"_mandos._tcp",
1558
1774
    client_class = Client
1559
1775
    if use_dbus:
1560
1776
        client_class = functools.partial(ClientDBus, bus = bus)
 
1777
    def client_config_items(config, section):
 
1778
        special_settings = {
 
1779
            "approved_by_default":
 
1780
                lambda: config.getboolean(section,
 
1781
                                          "approved_by_default"),
 
1782
            }
 
1783
        for name, value in config.items(section):
 
1784
            try:
 
1785
                yield (name, special_settings[name]())
 
1786
            except KeyError:
 
1787
                yield (name, value)
 
1788
    
1561
1789
    tcp_server.clients.update(set(
1562
1790
            client_class(name = section,
1563
 
                         config= dict(client_config.items(section)))
 
1791
                         config= dict(client_config_items(
 
1792
                        client_config, section)))
1564
1793
            for section in client_config.sections()))
1565
1794
    if not tcp_server.clients:
1566
1795
        logger.warning(u"No clients defined")
1578
1807
        daemon()
1579
1808
    
1580
1809
    try:
1581
 
        with closing(pidfile):
 
1810
        with pidfile:
1582
1811
            pid = os.getpid()
1583
1812
            pidfile.write(str(pid) + "\n")
1584
1813
        del pidfile
1590
1819
        pass
1591
1820
    del pidfilename
1592
1821
    
1593
 
    def cleanup():
1594
 
        "Cleanup function; run on exit"
1595
 
        service.cleanup()
1596
 
        
1597
 
        while tcp_server.clients:
1598
 
            client = tcp_server.clients.pop()
1599
 
            client.disable_hook = None
1600
 
            client.disable()
1601
 
    
1602
 
    atexit.register(cleanup)
1603
 
    
1604
1822
    if not debug:
1605
1823
        signal.signal(signal.SIGINT, signal.SIG_IGN)
1606
1824
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1613
1831
                dbus.service.Object.__init__(self, bus, u"/")
1614
1832
            _interface = u"se.bsnet.fukt.Mandos"
1615
1833
            
1616
 
            @dbus.service.signal(_interface, signature=u"oa{sv}")
1617
 
            def ClientAdded(self, objpath, properties):
 
1834
            @dbus.service.signal(_interface, signature=u"o")
 
1835
            def ClientAdded(self, objpath):
1618
1836
                "D-Bus signal"
1619
1837
                pass
1620
1838
            
1621
 
            @dbus.service.signal(_interface, signature=u"s")
1622
 
            def ClientNotFound(self, fingerprint):
 
1839
            @dbus.service.signal(_interface, signature=u"ss")
 
1840
            def ClientNotFound(self, fingerprint, address):
1623
1841
                "D-Bus signal"
1624
1842
                pass
1625
1843
            
1651
1869
                        tcp_server.clients.remove(c)
1652
1870
                        c.remove_from_connection()
1653
1871
                        # Don't signal anything except ClientRemoved
1654
 
                        c.disable(signal=False)
 
1872
                        c.disable(quiet=True)
1655
1873
                        # Emit D-Bus signal
1656
1874
                        self.ClientRemoved(object_path, c.name)
1657
1875
                        return
1658
 
                raise KeyError
 
1876
                raise KeyError(object_path)
1659
1877
            
1660
1878
            del _interface
1661
1879
        
1662
1880
        mandos_dbus_service = MandosDBusService()
1663
1881
    
 
1882
    def cleanup():
 
1883
        "Cleanup function; run on exit"
 
1884
        service.cleanup()
 
1885
        
 
1886
        while tcp_server.clients:
 
1887
            client = tcp_server.clients.pop()
 
1888
            if use_dbus:
 
1889
                client.remove_from_connection()
 
1890
            client.disable_hook = None
 
1891
            # Don't signal anything except ClientRemoved
 
1892
            client.disable(quiet=True)
 
1893
            if use_dbus:
 
1894
                # Emit D-Bus signal
 
1895
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
 
1896
                                                  client.name)
 
1897
    
 
1898
    atexit.register(cleanup)
 
1899
    
1664
1900
    for client in tcp_server.clients:
1665
1901
        if use_dbus:
1666
1902
            # Emit D-Bus signal
1667
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
1668
 
                                            client.GetAll(u""))
 
1903
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
1669
1904
        client.enable()
1670
1905
    
1671
1906
    tcp_server.enable()
1689
1924
            service.activate()
1690
1925
        except dbus.exceptions.DBusException, error:
1691
1926
            logger.critical(u"DBusException: %s", error)
 
1927
            cleanup()
1692
1928
            sys.exit(1)
1693
1929
        # End of Avahi example code
1694
1930
        
1701
1937
        main_loop.run()
1702
1938
    except AvahiError, error:
1703
1939
        logger.critical(u"AvahiError: %s", error)
 
1940
        cleanup()
1704
1941
        sys.exit(1)
1705
1942
    except KeyboardInterrupt:
1706
1943
        if debug:
1707
1944
            print >> sys.stderr
1708
1945
        logger.debug(u"Server received KeyboardInterrupt")
1709
1946
    logger.debug(u"Server exiting")
 
1947
    # Must run before the D-Bus bus name gets deregistered
 
1948
    cleanup()
1710
1949
 
1711
1950
if __name__ == '__main__':
1712
1951
    main()