/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: Björn Påhlsson
  • Date: 2010-09-01 18:03:03 UTC
  • mto: (237.4.3 mandos-release)
  • mto: This revision was merged to the branch mainline in revision 421.
  • Revision ID: belorn@fukt.bsnet.se-20100901180303-u47edb73fczu56ob
bug fixes that prevent problems when runing server as root

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
242
244
    enabled:    bool()
243
245
    last_checked_ok: datetime.datetime(); (UTC) or None
244
246
    timeout:    datetime.timedelta(); How long from last_checked_ok
245
 
                                      until this client is invalid
 
247
                                      until this client is disabled
246
248
    interval:   datetime.timedelta(); How often to start a new checker
247
249
    disable_hook:  If set, called by disable() as disable_hook(self)
248
250
    checker:    subprocess.Popen(); a running checker process used
256
258
                     runtime with vars(self) as dict, so that for
257
259
                     instance %(name)s can be used in the command.
258
260
    current_checker_command: string; current running checker_command
 
261
    approved_delay: datetime.timedelta(); Time to wait for approval
 
262
    _approved:   bool(); 'None' if not yet approved/disapproved
 
263
    approved_duration: datetime.timedelta(); Duration of one approval
259
264
    """
260
265
    
261
266
    @staticmethod
272
277
    def interval_milliseconds(self):
273
278
        "Return the 'interval' attribute in milliseconds"
274
279
        return self._timedelta_to_milliseconds(self.interval)
 
280
 
 
281
    def approved_delay_milliseconds(self):
 
282
        return self._timedelta_to_milliseconds(self.approved_delay)
275
283
    
276
284
    def __init__(self, name = None, disable_hook=None, config=None):
277
285
        """Note: the 'checker' key in 'config' sets the
290
298
        if u"secret" in config:
291
299
            self.secret = config[u"secret"].decode(u"base64")
292
300
        elif u"secfile" in config:
293
 
            with closing(open(os.path.expanduser
294
 
                              (os.path.expandvars
295
 
                               (config[u"secfile"])))) as secfile:
 
301
            with open(os.path.expanduser(os.path.expandvars
 
302
                                         (config[u"secfile"])),
 
303
                      "rb") as secfile:
296
304
                self.secret = secfile.read()
297
305
        else:
 
306
            #XXX Need to allow secret on demand!
298
307
            raise TypeError(u"No secret or secfile for client %s"
299
308
                            % self.name)
300
309
        self.host = config.get(u"host", u"")
312
321
        self.checker_command = config[u"checker"]
313
322
        self.current_checker_command = None
314
323
        self.last_connect = None
315
 
    
 
324
        self.approvals_pending = 0
 
325
        self._approved = None
 
326
        self.approved_by_default = config.get(u"approved_by_default",
 
327
                                              False)
 
328
        self.approved_delay = string_to_delta(
 
329
            config[u"approved_delay"])
 
330
        self.approved_duration = string_to_delta(
 
331
            config[u"approved_duration"])
 
332
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
 
333
 
 
334
    def send_changedstate(self):
 
335
        self.changedstate.acquire()
 
336
        self.changedstate.notify_all()
 
337
        self.changedstate.release()
 
338
        
316
339
    def enable(self):
317
340
        """Start this client's checker and timeout hooks"""
318
341
        if getattr(self, u"enabled", False):
319
342
            # Already enabled
320
343
            return
 
344
        self.send_changedstate()
321
345
        self.last_enabled = datetime.datetime.utcnow()
322
346
        # Schedule a new checker to be started an 'interval' from now,
323
347
        # and every interval from then on.
324
348
        self.checker_initiator_tag = (gobject.timeout_add
325
349
                                      (self.interval_milliseconds(),
326
350
                                       self.start_checker))
327
 
        # Also start a new checker *right now*.
328
 
        self.start_checker()
329
351
        # Schedule a disable() when 'timeout' has passed
330
352
        self.disable_initiator_tag = (gobject.timeout_add
331
353
                                   (self.timeout_milliseconds(),
332
354
                                    self.disable))
333
355
        self.enabled = True
 
356
        # Also start a new checker *right now*.
 
357
        self.start_checker()
334
358
    
335
 
    def disable(self):
 
359
    def disable(self, quiet=True):
336
360
        """Disable this client."""
337
361
        if not getattr(self, "enabled", False):
338
362
            return False
339
 
        logger.info(u"Disabling client %s", self.name)
 
363
        if not quiet:
 
364
            self.send_changedstate()
 
365
        if not quiet:
 
366
            logger.info(u"Disabling client %s", self.name)
340
367
        if getattr(self, u"disable_initiator_tag", False):
341
368
            gobject.source_remove(self.disable_initiator_tag)
342
369
            self.disable_initiator_tag = None
394
421
        # client would inevitably timeout, since no checker would get
395
422
        # a chance to run to completion.  If we instead leave running
396
423
        # 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.
 
424
        # than 'timeout' for the client to be disabled, which is as it
 
425
        # should be.
399
426
        
400
427
        # If a checker exists, make sure it is not a zombie
401
 
        if self.checker is not None:
 
428
        try:
402
429
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
430
        except (AttributeError, OSError), error:
 
431
            if (isinstance(error, OSError)
 
432
                and error.errno != errno.ECHILD):
 
433
                raise error
 
434
        else:
403
435
            if pid:
404
436
                logger.warning(u"Checker was a zombie")
405
437
                gobject.source_remove(self.checker_callback_tag)
461
493
        logger.debug(u"Stopping checker for %(name)s", vars(self))
462
494
        try:
463
495
            os.kill(self.checker.pid, signal.SIGTERM)
464
 
            #os.sleep(0.5)
 
496
            #time.sleep(0.5)
465
497
            #if self.checker.poll() is None:
466
498
            #    os.kill(self.checker.pid, signal.SIGKILL)
467
499
        except OSError, error:
468
500
            if error.errno != errno.ESRCH: # No such process
469
501
                raise
470
502
        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
 
 
482
503
 
483
504
def dbus_service_property(dbus_interface, signature=u"v",
484
505
                          access=u"readwrite", byte_arrays=False):
492
513
    dbus.service.method, except there is only "signature", since the
493
514
    type from Get() and the type sent to Set() is the same.
494
515
    """
 
516
    # Encoding deeply encoded byte arrays is not supported yet by the
 
517
    # "Set" method, so we fail early here:
 
518
    if byte_arrays and signature != u"ay":
 
519
        raise ValueError(u"Byte arrays not supported for non-'ay'"
 
520
                         u" signature %r" % signature)
495
521
    def decorator(func):
496
522
        func._dbus_is_property = True
497
523
        func._dbus_interface = dbus_interface
583
609
        if prop._dbus_access == u"read":
584
610
            raise DBusPropertyAccessException(property_name)
585
611
        if prop._dbus_get_args_options[u"byte_arrays"]:
 
612
            # The byte_arrays option is not supported yet on
 
613
            # signatures other than "ay".
 
614
            if prop._dbus_signature != u"ay":
 
615
                raise ValueError
586
616
            value = dbus.ByteArray(''.join(unichr(byte)
587
617
                                           for byte in value))
588
618
        prop(value)
620
650
        """Standard D-Bus method, overloaded to insert property tags.
621
651
        """
622
652
        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()
 
653
                                                   connection)
 
654
        try:
 
655
            document = xml.dom.minidom.parseString(xmlstring)
 
656
            def make_tag(document, name, prop):
 
657
                e = document.createElement(u"property")
 
658
                e.setAttribute(u"name", name)
 
659
                e.setAttribute(u"type", prop._dbus_signature)
 
660
                e.setAttribute(u"access", prop._dbus_access)
 
661
                return e
 
662
            for if_tag in document.getElementsByTagName(u"interface"):
 
663
                for tag in (make_tag(document, name, prop)
 
664
                            for name, prop
 
665
                            in self._get_all_dbus_properties()
 
666
                            if prop._dbus_interface
 
667
                            == if_tag.getAttribute(u"name")):
 
668
                    if_tag.appendChild(tag)
 
669
                # Add the names to the return values for the
 
670
                # "org.freedesktop.DBus.Properties" methods
 
671
                if (if_tag.getAttribute(u"name")
 
672
                    == u"org.freedesktop.DBus.Properties"):
 
673
                    for cn in if_tag.getElementsByTagName(u"method"):
 
674
                        if cn.getAttribute(u"name") == u"Get":
 
675
                            for arg in cn.getElementsByTagName(u"arg"):
 
676
                                if (arg.getAttribute(u"direction")
 
677
                                    == u"out"):
 
678
                                    arg.setAttribute(u"name", u"value")
 
679
                        elif cn.getAttribute(u"name") == u"GetAll":
 
680
                            for arg in cn.getElementsByTagName(u"arg"):
 
681
                                if (arg.getAttribute(u"direction")
 
682
                                    == u"out"):
 
683
                                    arg.setAttribute(u"name", u"props")
 
684
            xmlstring = document.toxml(u"utf-8")
 
685
            document.unlink()
 
686
        except (AttributeError, xml.dom.DOMException,
 
687
                xml.parsers.expat.ExpatError), error:
 
688
            logger.error(u"Failed to override Introspection method",
 
689
                         error)
641
690
        return xmlstring
642
691
 
643
692
 
680
729
                                       variant_level=1))
681
730
        return r
682
731
    
683
 
    def disable(self, signal = True):
 
732
    def disable(self, quiet = False):
684
733
        oldstate = getattr(self, u"enabled", False)
685
 
        r = Client.disable(self)
686
 
        if signal and oldstate != self.enabled:
 
734
        r = Client.disable(self, quiet=quiet)
 
735
        if not quiet and oldstate != self.enabled:
687
736
            # Emit D-Bus signal
688
737
            self.PropertyChanged(dbus.String(u"enabled"),
689
738
                                 dbus.Boolean(False, variant_level=1))
754
803
            self.PropertyChanged(dbus.String(u"checker_running"),
755
804
                                 dbus.Boolean(False, variant_level=1))
756
805
        return r
757
 
    
758
 
    ## D-Bus methods & signals
 
806
 
 
807
    def _reset_approved(self):
 
808
        self._approved = None
 
809
        return False
 
810
    
 
811
    def approve(self, value=True):
 
812
        self._approved = value
 
813
        gobject.timeout_add(self._timedelta_to_milliseconds(self.approved_duration, self._reset_approved))
 
814
 
 
815
    def approved_pending(self):
 
816
        return self.approvals_pending > 0
 
817
 
 
818
    
 
819
    ## D-Bus methods, signals & properties
759
820
    _interface = u"se.bsnet.fukt.Mandos.Client"
760
821
    
761
 
    # CheckedOK - method
762
 
    @dbus.service.method(_interface)
763
 
    def CheckedOK(self):
764
 
        return self.checked_ok()
 
822
    ## Signals
765
823
    
766
824
    # CheckerCompleted - signal
767
825
    @dbus.service.signal(_interface, signature=u"nxs")
781
839
        "D-Bus signal"
782
840
        pass
783
841
    
784
 
    # ReceivedSecret - signal
 
842
    # GotSecret - signal
785
843
    @dbus.service.signal(_interface)
786
 
    def ReceivedSecret(self):
 
844
    def GotSecret(self):
787
845
        "D-Bus signal"
788
846
        pass
789
847
    
790
848
    # Rejected - signal
791
 
    @dbus.service.signal(_interface)
792
 
    def Rejected(self):
793
 
        "D-Bus signal"
794
 
        pass
 
849
    @dbus.service.signal(_interface, signature=u"s")
 
850
    def Rejected(self, reason):
 
851
        "D-Bus signal"
 
852
        pass
 
853
    
 
854
    # NeedApproval - signal
 
855
    @dbus.service.signal(_interface, signature=u"db")
 
856
    def NeedApproval(self, timeout, default):
 
857
        "D-Bus signal"
 
858
        pass
 
859
    
 
860
    ## Methods
 
861
 
 
862
    # Approve - method
 
863
    @dbus.service.method(_interface, in_signature=u"b")
 
864
    def Approve(self, value):
 
865
        self.approve(value)
 
866
 
 
867
    # CheckedOK - method
 
868
    @dbus.service.method(_interface)
 
869
    def CheckedOK(self):
 
870
        return self.checked_ok()
795
871
    
796
872
    # Enable - method
797
873
    @dbus.service.method(_interface)
816
892
    def StopChecker(self):
817
893
        self.stop_checker()
818
894
    
 
895
    ## Properties
 
896
    
 
897
    # approved_pending - property
 
898
    @dbus_service_property(_interface, signature=u"b", access=u"read")
 
899
    def approved_pending_dbus_property(self):
 
900
        return dbus.Boolean(self.approved_pending())
 
901
    
 
902
    # approved_by_default - property
 
903
    @dbus_service_property(_interface, signature=u"b",
 
904
                           access=u"readwrite")
 
905
    def approved_by_default_dbus_property(self):
 
906
        return dbus.Boolean(self.approved_by_default)
 
907
    
 
908
    # approved_delay - property
 
909
    @dbus_service_property(_interface, signature=u"t",
 
910
                           access=u"readwrite")
 
911
    def approved_delay_dbus_property(self):
 
912
        return dbus.UInt64(self.approved_delay_milliseconds())
 
913
    
 
914
    # approved_duration - property
 
915
    @dbus_service_property(_interface, signature=u"t",
 
916
                           access=u"readwrite")
 
917
    def approved_duration_dbus_property(self):
 
918
        return dbus.UInt64(self._timedelta_to_milliseconds(
 
919
                self.approved_duration))
 
920
    
819
921
    # name - property
820
922
    @dbus_service_property(_interface, signature=u"s", access=u"read")
821
923
    def name_dbus_property(self):
955
1057
    del _interface
956
1058
 
957
1059
 
 
1060
class ProxyClient(object):
 
1061
    def __init__(self, child_pipe, fpr, address):
 
1062
        self._pipe = child_pipe
 
1063
        self._pipe.send(('init', fpr, address))
 
1064
        if not self._pipe.recv():
 
1065
            raise KeyError()
 
1066
 
 
1067
    def __getattribute__(self, name):
 
1068
        if(name == '_pipe'):
 
1069
            return super(ProxyClient, self).__getattribute__(name)
 
1070
        self._pipe.send(('getattr', name))
 
1071
        data = self._pipe.recv()
 
1072
        if data[0] == 'data':
 
1073
            return data[1]
 
1074
        if data[0] == 'function':
 
1075
            def func(*args, **kwargs):
 
1076
                self._pipe.send(('funcall', name, args, kwargs))
 
1077
                return self._pipe.recv()[1]
 
1078
            return func
 
1079
 
 
1080
    def __setattr__(self, name, value):
 
1081
        if(name == '_pipe'):
 
1082
            return super(ProxyClient, self).__setattr__(name, value)
 
1083
        self._pipe.send(('setattr', name, value))
 
1084
 
 
1085
 
958
1086
class ClientHandler(socketserver.BaseRequestHandler, object):
959
1087
    """A class to handle client connections.
960
1088
    
962
1090
    Note: This will run in its own forked process."""
963
1091
    
964
1092
    def handle(self):
965
 
        logger.info(u"TCP connection from: %s",
966
 
                    unicode(self.client_address))
967
 
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
968
 
        # Open IPC pipe to parent process
969
 
        with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
 
1093
        with contextlib.closing(self.server.child_pipe) as child_pipe:
 
1094
            logger.info(u"TCP connection from: %s",
 
1095
                        unicode(self.client_address))
 
1096
            logger.debug(u"Pipe FD: %d",
 
1097
                         self.server.child_pipe.fileno())
 
1098
 
970
1099
            session = (gnutls.connection
971
1100
                       .ClientSession(self.request,
972
1101
                                      gnutls.connection
973
1102
                                      .X509Credentials()))
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
 
            
 
1103
 
984
1104
            # Note: gnutls.connection.X509Credentials is really a
985
1105
            # generic GnuTLS certificate credentials object so long as
986
1106
            # no X.509 keys are added to it.  Therefore, we can use it
987
1107
            # here despite using OpenPGP certificates.
988
 
            
 
1108
 
989
1109
            #priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
990
1110
            #                      u"+AES-256-CBC", u"+SHA1",
991
1111
            #                      u"+COMP-NULL", u"+CTYPE-OPENPGP",
997
1117
            (gnutls.library.functions
998
1118
             .gnutls_priority_set_direct(session._c_object,
999
1119
                                         priority, None))
1000
 
            
 
1120
 
 
1121
            # Start communication using the Mandos protocol
 
1122
            # Get protocol number
 
1123
            line = self.request.makefile().readline()
 
1124
            logger.debug(u"Protocol version: %r", line)
 
1125
            try:
 
1126
                if int(line.strip().split()[0]) > 1:
 
1127
                    raise RuntimeError
 
1128
            except (ValueError, IndexError, RuntimeError), error:
 
1129
                logger.error(u"Unknown protocol version: %s", error)
 
1130
                return
 
1131
 
 
1132
            # Start GnuTLS connection
1001
1133
            try:
1002
1134
                session.handshake()
1003
1135
            except gnutls.errors.GNUTLSError, error:
1006
1138
                # established.  Just abandon the request.
1007
1139
                return
1008
1140
            logger.debug(u"Handshake succeeded")
 
1141
 
 
1142
            approval_required = False
1009
1143
            try:
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)
 
1144
                try:
 
1145
                    fpr = self.fingerprint(self.peer_certificate
 
1146
                                           (session))
 
1147
                except (TypeError, gnutls.errors.GNUTLSError), error:
 
1148
                    logger.warning(u"Bad certificate: %s", error)
 
1149
                    return
 
1150
                logger.debug(u"Fingerprint: %s", fpr)
 
1151
 
 
1152
                try:
 
1153
                    client = ProxyClient(child_pipe, fpr,
 
1154
                                         self.client_address)
 
1155
                except KeyError:
 
1156
                    return
 
1157
                
 
1158
                if client.approved_delay:
 
1159
                    delay = client.approved_delay
 
1160
                    client.approvals_pending += 1
 
1161
                    approval_required = True
 
1162
                
 
1163
                while True:
 
1164
                    if not client.enabled:
 
1165
                        logger.warning(u"Client %s is disabled",
 
1166
                                       client.name)
 
1167
                        if self.server.use_dbus:
 
1168
                            # Emit D-Bus signal
 
1169
                            client.Rejected("Disabled")                    
 
1170
                        return
 
1171
                    
 
1172
                    if client._approved or not client.approved_delay:
 
1173
                        #We are approved or approval is disabled
 
1174
                        break
 
1175
                    elif client._approved is None:
 
1176
                        logger.info(u"Client %s need approval",
 
1177
                                    client.name)
 
1178
                        if self.server.use_dbus:
 
1179
                            # Emit D-Bus signal
 
1180
                            client.NeedApproval(
 
1181
                                client.approved_delay_milliseconds(),
 
1182
                                client.approved_by_default)
 
1183
                    else:
 
1184
                        logger.warning(u"Client %s was not approved",
 
1185
                                       client.name)
 
1186
                        if self.server.use_dbus:
 
1187
                            # Emit D-Bus signal
 
1188
                            client.Rejected("Disapproved")
 
1189
                        return
 
1190
                    
 
1191
                    #wait until timeout or approved
 
1192
                    #x = float(client._timedelta_to_milliseconds(delay))
 
1193
                    time = datetime.datetime.now()
 
1194
                    client.changedstate.acquire()
 
1195
                    client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
 
1196
                    client.changedstate.release()
 
1197
                    time2 = datetime.datetime.now()
 
1198
                    if (time2 - time) >= delay:
 
1199
                        if not client.approved_by_default:
 
1200
                            logger.warning("Client %s timed out while"
 
1201
                                           " waiting for approval",
 
1202
                                           client.name)
 
1203
                            if self.server.use_dbus:
 
1204
                                # Emit D-Bus signal
 
1205
                                client.Rejected("Time out")
 
1206
                            return
 
1207
                        else:
 
1208
                            break
 
1209
                    else:
 
1210
                        delay -= time2 - time
 
1211
                
 
1212
                sent_size = 0
 
1213
                while sent_size < len(client.secret):
 
1214
                    # XXX handle session exception
 
1215
                    sent = session.send(client.secret[sent_size:])
 
1216
                    logger.debug(u"Sent: %d, remaining: %d",
 
1217
                                 sent, len(client.secret)
 
1218
                                 - (sent_size + sent))
 
1219
                    sent_size += sent
 
1220
 
 
1221
                logger.info(u"Sending secret to %s", client.name)
 
1222
                # bump the timeout as if seen
 
1223
                client.checked_ok()
 
1224
                if self.server.use_dbus:
 
1225
                    # Emit D-Bus signal
 
1226
                    client.GotSecret()
1016
1227
            
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()
 
1228
            finally:
 
1229
                if approval_required:
 
1230
                    client.approvals_pending -= 1
 
1231
                session.bye()
1042
1232
    
1043
1233
    @staticmethod
1044
1234
    def peer_certificate(session):
1104
1294
        return hex_fpr
1105
1295
 
1106
1296
 
1107
 
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1108
 
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
 
1297
class MultiprocessingMixIn(object):
 
1298
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
 
1299
    def sub_process_main(self, request, address):
 
1300
        try:
 
1301
            self.finish_request(request, address)
 
1302
        except:
 
1303
            self.handle_error(request, address)
 
1304
        self.close_request(request)
 
1305
            
 
1306
    def process_request(self, request, address):
 
1307
        """Start a new process to process the request."""
 
1308
        multiprocessing.Process(target = self.sub_process_main,
 
1309
                                args = (request, address)).start()
 
1310
 
 
1311
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
 
1312
    """ adds a pipe to the MixIn """
1109
1313
    def process_request(self, request, client_address):
1110
1314
        """Overrides and wraps the original process_request().
1111
1315
        
1112
1316
        This function creates a new pipe in self.pipe
1113
1317
        """
1114
 
        self.pipe = os.pipe()
1115
 
        super(ForkingMixInWithPipe,
 
1318
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
 
1319
 
 
1320
        super(MultiprocessingMixInWithPipe,
1116
1321
              self).process_request(request, client_address)
1117
 
        os.close(self.pipe[1])  # close write end
1118
 
        self.add_pipe(self.pipe[0])
1119
 
    def add_pipe(self, pipe):
 
1322
        self.child_pipe.close()
 
1323
        self.add_pipe(parent_pipe)
 
1324
        
 
1325
    def add_pipe(self, parent_pipe):
1120
1326
        """Dummy function; override as necessary"""
1121
 
        os.close(pipe)
1122
 
 
1123
 
 
1124
 
class IPv6_TCPServer(ForkingMixInWithPipe,
 
1327
        pass
 
1328
 
 
1329
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1125
1330
                     socketserver.TCPServer, object):
1126
1331
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1127
1332
    
1212
1417
            return socketserver.TCPServer.server_activate(self)
1213
1418
    def enable(self):
1214
1419
        self.enabled = True
1215
 
    def add_pipe(self, pipe):
 
1420
    def add_pipe(self, parent_pipe):
1216
1421
        # Call "handle_ipc" for both data and EOF events
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={}):
 
1422
        gobject.io_add_watch(parent_pipe.fileno(),
 
1423
                             gobject.IO_IN | gobject.IO_HUP,
 
1424
                             functools.partial(self.handle_ipc,
 
1425
                                               parent_pipe = parent_pipe))
 
1426
        
 
1427
    def handle_ipc(self, source, condition, parent_pipe=None,
 
1428
                   client_object=None):
1220
1429
        condition_names = {
1221
1430
            gobject.IO_IN: u"IN",   # There is data to read.
1222
1431
            gobject.IO_OUT: u"OUT", # Data can be written (without
1233
1442
                                       if cond & condition)
1234
1443
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1235
1444
                     conditions_string)
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
 
        
1241
 
        # Read a line from the file object
1242
 
        cmdline = file_objects[source].readline()
1243
 
        if not cmdline:             # Empty line means end of file
1244
 
            # close the IPC pipe
1245
 
            file_objects[source].close()
1246
 
            del file_objects[source]
1247
 
            
1248
 
            # Stop calling this function
1249
 
            return False
1250
 
        
1251
 
        logger.debug(u"IPC command: %r", cmdline)
1252
 
        
1253
 
        # Parse and act on command
1254
 
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1255
 
        
1256
 
        if cmd == u"NOTFOUND":
1257
 
            logger.warning(u"Client not found for fingerprint: %s",
1258
 
                           args)
1259
 
            if self.use_dbus:
1260
 
                # Emit D-Bus signal
1261
 
                mandos_dbus_service.ClientNotFound(args)
1262
 
        elif cmd == u"INVALID":
1263
 
            for client in self.clients:
1264
 
                if client.name == args:
1265
 
                    logger.warning(u"Client %s is invalid", args)
1266
 
                    if self.use_dbus:
1267
 
                        # Emit D-Bus signal
1268
 
                        client.Rejected()
1269
 
                    break
1270
 
            else:
1271
 
                logger.error(u"Unknown client %s is invalid", args)
1272
 
        elif cmd == u"SENDING":
1273
 
            for client in self.clients:
1274
 
                if client.name == args:
1275
 
                    logger.info(u"Sending secret to %s", client.name)
1276
 
                    client.checked_ok()
1277
 
                    if self.use_dbus:
1278
 
                        # Emit D-Bus signal
1279
 
                        client.ReceivedSecret()
1280
 
                    break
1281
 
            else:
1282
 
                logger.error(u"Sending secret to unknown client %s",
1283
 
                             args)
1284
 
        else:
1285
 
            logger.error(u"Unknown IPC command: %r", cmdline)
1286
 
        
1287
 
        # Keep calling this function
 
1445
 
 
1446
        # error or the other end of multiprocessing.Pipe has closed
 
1447
        if condition & gobject.IO_HUP or condition & gobject.IO_ERR:
 
1448
            return False
 
1449
        
 
1450
        # Read a request from the child
 
1451
        request = parent_pipe.recv()
 
1452
        logger.debug(u"IPC request: %s", repr(request))
 
1453
        command = request[0]
 
1454
        
 
1455
        if command == 'init':
 
1456
            fpr = request[1]
 
1457
            address = request[2]
 
1458
            
 
1459
            for c in self.clients:
 
1460
                if c.fingerprint == fpr:
 
1461
                    client = c
 
1462
                    break
 
1463
            else:
 
1464
                logger.warning(u"Client not found for fingerprint: %s, ad"
 
1465
                               u"dress: %s", fpr, address)
 
1466
                if self.use_dbus:
 
1467
                    # Emit D-Bus signal
 
1468
                    mandos_dbus_service.ClientNotFound(fpr, address)
 
1469
                parent_pipe.send(False)
 
1470
                return False
 
1471
            
 
1472
            gobject.io_add_watch(parent_pipe.fileno(),
 
1473
                                 gobject.IO_IN | gobject.IO_HUP,
 
1474
                                 functools.partial(self.handle_ipc,
 
1475
                                                   parent_pipe = parent_pipe,
 
1476
                                                   client_object = client))
 
1477
            parent_pipe.send(True)
 
1478
            # remove the old hook in favor of the new above hook on same fileno
 
1479
            return False
 
1480
        if command == 'funcall':
 
1481
            funcname = request[1]
 
1482
            args = request[2]
 
1483
            kwargs = request[3]
 
1484
            
 
1485
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
 
1486
 
 
1487
        if command == 'getattr':
 
1488
            attrname = request[1]
 
1489
            if callable(client_object.__getattribute__(attrname)):
 
1490
                parent_pipe.send(('function',))
 
1491
            else:
 
1492
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
 
1493
 
 
1494
        if command == 'setattr':
 
1495
            attrname = request[1]
 
1496
            value = request[2]
 
1497
            setattr(client_object, attrname, value)
 
1498
            
1288
1499
        return True
1289
1500
 
1290
1501
 
1320
1531
            elif suffix == u"w":
1321
1532
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1322
1533
            else:
1323
 
                raise ValueError
1324
 
        except (ValueError, IndexError):
1325
 
            raise ValueError
 
1534
                raise ValueError(u"Unknown suffix %r" % suffix)
 
1535
        except (ValueError, IndexError), e:
 
1536
            raise ValueError(e.message)
1326
1537
        timevalue += delta
1327
1538
    return timevalue
1328
1539
 
1341
1552
        def if_nametoindex(interface):
1342
1553
            "Get an interface index the hard way, i.e. using fcntl()"
1343
1554
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1344
 
            with closing(socket.socket()) as s:
 
1555
            with contextlib.closing(socket.socket()) as s:
1345
1556
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1346
1557
                                    struct.pack(str(u"16s16x"),
1347
1558
                                                interface))
1367
1578
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1368
1579
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1369
1580
            raise OSError(errno.ENODEV,
1370
 
                          u"/dev/null not a character device")
 
1581
                          u"%s not a character device"
 
1582
                          % os.path.devnull)
1371
1583
        os.dup2(null, sys.stdin.fileno())
1372
1584
        os.dup2(null, sys.stdout.fileno())
1373
1585
        os.dup2(null, sys.stderr.fileno())
1477
1689
                        u"interval": u"5m",
1478
1690
                        u"checker": u"fping -q -- %%(host)s",
1479
1691
                        u"host": u"",
 
1692
                        u"approved_delay": u"5m",
 
1693
                        u"approved_duration": u"1s",
1480
1694
                        }
1481
1695
    client_config = configparser.SafeConfigParser(client_defaults)
1482
1696
    client_config.read(os.path.join(server_settings[u"configdir"],
1540
1754
    bus = dbus.SystemBus()
1541
1755
    # End of Avahi example code
1542
1756
    if use_dbus:
1543
 
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
 
1757
        try:
 
1758
            bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
 
1759
                                            bus, do_not_queue=True)
 
1760
        except dbus.exceptions.NameExistsException, e:
 
1761
            logger.error(unicode(e) + u", disabling D-Bus")
 
1762
            use_dbus = False
 
1763
            server_settings[u"use_dbus"] = False
 
1764
            tcp_server.use_dbus = False
1544
1765
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1545
1766
    service = AvahiService(name = server_settings[u"servicename"],
1546
1767
                           servicetype = u"_mandos._tcp",
1548
1769
    if server_settings["interface"]:
1549
1770
        service.interface = (if_nametoindex
1550
1771
                             (str(server_settings[u"interface"])))
 
1772
 
 
1773
    global multiprocessing_manager
 
1774
    multiprocessing_manager = multiprocessing.Manager()
1551
1775
    
1552
1776
    client_class = Client
1553
1777
    if use_dbus:
1554
1778
        client_class = functools.partial(ClientDBus, bus = bus)
 
1779
    def client_config_items(config, section):
 
1780
        special_settings = {
 
1781
            "approved_by_default":
 
1782
                lambda: config.getboolean(section,
 
1783
                                          "approved_by_default"),
 
1784
            }
 
1785
        for name, value in config.items(section):
 
1786
            try:
 
1787
                yield (name, special_settings[name]())
 
1788
            except KeyError:
 
1789
                yield (name, value)
 
1790
    
1555
1791
    tcp_server.clients.update(set(
1556
1792
            client_class(name = section,
1557
 
                         config= dict(client_config.items(section)))
 
1793
                         config= dict(client_config_items(
 
1794
                        client_config, section)))
1558
1795
            for section in client_config.sections()))
1559
1796
    if not tcp_server.clients:
1560
1797
        logger.warning(u"No clients defined")
1572
1809
        daemon()
1573
1810
    
1574
1811
    try:
1575
 
        with closing(pidfile):
 
1812
        with pidfile:
1576
1813
            pid = os.getpid()
1577
1814
            pidfile.write(str(pid) + "\n")
1578
1815
        del pidfile
1584
1821
        pass
1585
1822
    del pidfilename
1586
1823
    
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
 
    
1598
1824
    if not debug:
1599
1825
        signal.signal(signal.SIGINT, signal.SIG_IGN)
1600
1826
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1607
1833
                dbus.service.Object.__init__(self, bus, u"/")
1608
1834
            _interface = u"se.bsnet.fukt.Mandos"
1609
1835
            
1610
 
            @dbus.service.signal(_interface, signature=u"oa{sv}")
1611
 
            def ClientAdded(self, objpath, properties):
 
1836
            @dbus.service.signal(_interface, signature=u"o")
 
1837
            def ClientAdded(self, objpath):
1612
1838
                "D-Bus signal"
1613
1839
                pass
1614
1840
            
1615
 
            @dbus.service.signal(_interface, signature=u"s")
1616
 
            def ClientNotFound(self, fingerprint):
 
1841
            @dbus.service.signal(_interface, signature=u"ss")
 
1842
            def ClientNotFound(self, fingerprint, address):
1617
1843
                "D-Bus signal"
1618
1844
                pass
1619
1845
            
1645
1871
                        tcp_server.clients.remove(c)
1646
1872
                        c.remove_from_connection()
1647
1873
                        # Don't signal anything except ClientRemoved
1648
 
                        c.disable(signal=False)
 
1874
                        c.disable(quiet=True)
1649
1875
                        # Emit D-Bus signal
1650
1876
                        self.ClientRemoved(object_path, c.name)
1651
1877
                        return
1652
 
                raise KeyError
 
1878
                raise KeyError(object_path)
1653
1879
            
1654
1880
            del _interface
1655
1881
        
1656
1882
        mandos_dbus_service = MandosDBusService()
1657
1883
    
 
1884
    def cleanup():
 
1885
        "Cleanup function; run on exit"
 
1886
        service.cleanup()
 
1887
        
 
1888
        while tcp_server.clients:
 
1889
            client = tcp_server.clients.pop()
 
1890
            if use_dbus:
 
1891
                client.remove_from_connection()
 
1892
            client.disable_hook = None
 
1893
            # Don't signal anything except ClientRemoved
 
1894
            client.disable(quiet=True)
 
1895
            if use_dbus:
 
1896
                # Emit D-Bus signal
 
1897
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
 
1898
                                                  client.name)
 
1899
    
 
1900
    atexit.register(cleanup)
 
1901
    
1658
1902
    for client in tcp_server.clients:
1659
1903
        if use_dbus:
1660
1904
            # Emit D-Bus signal
1661
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
1662
 
                                            client.GetAll(u""))
 
1905
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
1663
1906
        client.enable()
1664
1907
    
1665
1908
    tcp_server.enable()
1683
1926
            service.activate()
1684
1927
        except dbus.exceptions.DBusException, error:
1685
1928
            logger.critical(u"DBusException: %s", error)
 
1929
            cleanup()
1686
1930
            sys.exit(1)
1687
1931
        # End of Avahi example code
1688
1932
        
1695
1939
        main_loop.run()
1696
1940
    except AvahiError, error:
1697
1941
        logger.critical(u"AvahiError: %s", error)
 
1942
        cleanup()
1698
1943
        sys.exit(1)
1699
1944
    except KeyboardInterrupt:
1700
1945
        if debug:
1701
1946
            print >> sys.stderr
1702
1947
        logger.debug(u"Server received KeyboardInterrupt")
1703
1948
    logger.debug(u"Server exiting")
 
1949
    # Must run before the D-Bus bus name gets deregistered
 
1950
    cleanup()
1704
1951
 
1705
1952
if __name__ == '__main__':
1706
1953
    main()