/mandos/release

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

« back to all changes in this revision

Viewing changes to mandos

merge
new approve/deny functionallity in mandos-monitor

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
 
 
86
#logger = logging.getLogger(u'mandos')
84
87
logger = logging.Logger(u'mandos')
85
88
syslogger = (logging.handlers.SysLogHandler
86
89
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
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
256
259
                     runtime with vars(self) as dict, so that for
257
260
                     instance %(name)s can be used in the command.
258
261
    current_checker_command: string; current running checker_command
 
262
    approved_delay: datetime.timedelta(); Time to wait for approval
 
263
    _approved:   bool(); 'None' if not yet approved/disapproved
 
264
    approved_duration: datetime.timedelta(); Duration of one approval
259
265
    """
260
266
    
261
267
    @staticmethod
272
278
    def interval_milliseconds(self):
273
279
        "Return the 'interval' attribute in milliseconds"
274
280
        return self._timedelta_to_milliseconds(self.interval)
 
281
 
 
282
    def approved_delay_milliseconds(self):
 
283
        return self._timedelta_to_milliseconds(self.approved_delay)
275
284
    
276
285
    def __init__(self, name = None, disable_hook=None, config=None):
277
286
        """Note: the 'checker' key in 'config' sets the
290
299
        if u"secret" in config:
291
300
            self.secret = config[u"secret"].decode(u"base64")
292
301
        elif u"secfile" in config:
293
 
            with closing(open(os.path.expanduser
294
 
                              (os.path.expandvars
295
 
                               (config[u"secfile"])))) as secfile:
 
302
            with open(os.path.expanduser(os.path.expandvars
 
303
                                         (config[u"secfile"])),
 
304
                      "rb") as secfile:
296
305
                self.secret = secfile.read()
297
306
        else:
 
307
            #XXX Need to allow secret on demand!
298
308
            raise TypeError(u"No secret or secfile for client %s"
299
309
                            % self.name)
300
310
        self.host = config.get(u"host", u"")
312
322
        self.checker_command = config[u"checker"]
313
323
        self.current_checker_command = None
314
324
        self.last_connect = None
 
325
        self._approved = None
 
326
        self.approved_by_default = config.get(u"approved_by_default",
 
327
                                              True)
 
328
        self.approvals_pending = 0
 
329
        self.approved_delay = string_to_delta(
 
330
            config[u"approved_delay"])
 
331
        self.approved_duration = string_to_delta(
 
332
            config[u"approved_duration"])
 
333
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
315
334
    
 
335
    def send_changedstate(self):
 
336
        self.changedstate.acquire()
 
337
        self.changedstate.notify_all()
 
338
        self.changedstate.release()
 
339
        
316
340
    def enable(self):
317
341
        """Start this client's checker and timeout hooks"""
318
342
        if getattr(self, u"enabled", False):
319
343
            # Already enabled
320
344
            return
 
345
        self.send_changedstate()
321
346
        self.last_enabled = datetime.datetime.utcnow()
322
347
        # Schedule a new checker to be started an 'interval' from now,
323
348
        # and every interval from then on.
324
349
        self.checker_initiator_tag = (gobject.timeout_add
325
350
                                      (self.interval_milliseconds(),
326
351
                                       self.start_checker))
327
 
        # Also start a new checker *right now*.
328
 
        self.start_checker()
329
352
        # Schedule a disable() when 'timeout' has passed
330
353
        self.disable_initiator_tag = (gobject.timeout_add
331
354
                                   (self.timeout_milliseconds(),
332
355
                                    self.disable))
333
356
        self.enabled = True
 
357
        # Also start a new checker *right now*.
 
358
        self.start_checker()
334
359
    
335
 
    def disable(self):
 
360
    def disable(self, quiet=True):
336
361
        """Disable this client."""
337
362
        if not getattr(self, "enabled", False):
338
363
            return False
339
 
        logger.info(u"Disabling client %s", self.name)
 
364
        if not quiet:
 
365
            self.send_changedstate()
 
366
        if not quiet:
 
367
            logger.info(u"Disabling client %s", self.name)
340
368
        if getattr(self, u"disable_initiator_tag", False):
341
369
            gobject.source_remove(self.disable_initiator_tag)
342
370
            self.disable_initiator_tag = None
394
422
        # client would inevitably timeout, since no checker would get
395
423
        # a chance to run to completion.  If we instead leave running
396
424
        # 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.
 
425
        # than 'timeout' for the client to be disabled, which is as it
 
426
        # should be.
399
427
        
400
428
        # If a checker exists, make sure it is not a zombie
401
429
        try:
466
494
        logger.debug(u"Stopping checker for %(name)s", vars(self))
467
495
        try:
468
496
            os.kill(self.checker.pid, signal.SIGTERM)
469
 
            #os.sleep(0.5)
 
497
            #time.sleep(0.5)
470
498
            #if self.checker.poll() is None:
471
499
            #    os.kill(self.checker.pid, signal.SIGKILL)
472
500
        except OSError, error:
473
501
            if error.errno != errno.ESRCH: # No such process
474
502
                raise
475
503
        self.checker = None
476
 
    
477
 
    def still_valid(self):
478
 
        """Has the timeout not yet passed for this client?"""
479
 
        if not getattr(self, u"enabled", False):
480
 
            return False
481
 
        now = datetime.datetime.utcnow()
482
 
        if self.last_checked_ok is None:
483
 
            return now < (self.created + self.timeout)
484
 
        else:
485
 
            return now < (self.last_checked_ok + self.timeout)
486
 
 
487
504
 
488
505
def dbus_service_property(dbus_interface, signature=u"v",
489
506
                          access=u"readwrite", byte_arrays=False):
497
514
    dbus.service.method, except there is only "signature", since the
498
515
    type from Get() and the type sent to Set() is the same.
499
516
    """
 
517
    # Encoding deeply encoded byte arrays is not supported yet by the
 
518
    # "Set" method, so we fail early here:
 
519
    if byte_arrays and signature != u"ay":
 
520
        raise ValueError(u"Byte arrays not supported for non-'ay'"
 
521
                         u" signature %r" % signature)
500
522
    def decorator(func):
501
523
        func._dbus_is_property = True
502
524
        func._dbus_interface = dbus_interface
588
610
        if prop._dbus_access == u"read":
589
611
            raise DBusPropertyAccessException(property_name)
590
612
        if prop._dbus_get_args_options[u"byte_arrays"]:
 
613
            # The byte_arrays option is not supported yet on
 
614
            # signatures other than "ay".
 
615
            if prop._dbus_signature != u"ay":
 
616
                raise ValueError
591
617
            value = dbus.ByteArray(''.join(unichr(byte)
592
618
                                           for byte in value))
593
619
        prop(value)
625
651
        """Standard D-Bus method, overloaded to insert property tags.
626
652
        """
627
653
        xmlstring = dbus.service.Object.Introspect(self, object_path,
628
 
                                           connection)
629
 
        document = xml.dom.minidom.parseString(xmlstring)
630
 
        del xmlstring
631
 
        def make_tag(document, name, prop):
632
 
            e = document.createElement(u"property")
633
 
            e.setAttribute(u"name", name)
634
 
            e.setAttribute(u"type", prop._dbus_signature)
635
 
            e.setAttribute(u"access", prop._dbus_access)
636
 
            return e
637
 
        for if_tag in document.getElementsByTagName(u"interface"):
638
 
            for tag in (make_tag(document, name, prop)
639
 
                        for name, prop
640
 
                        in self._get_all_dbus_properties()
641
 
                        if prop._dbus_interface
642
 
                        == if_tag.getAttribute(u"name")):
643
 
                if_tag.appendChild(tag)
644
 
        xmlstring = document.toxml(u"utf-8")
645
 
        document.unlink()
 
654
                                                   connection)
 
655
        try:
 
656
            document = xml.dom.minidom.parseString(xmlstring)
 
657
            def make_tag(document, name, prop):
 
658
                e = document.createElement(u"property")
 
659
                e.setAttribute(u"name", name)
 
660
                e.setAttribute(u"type", prop._dbus_signature)
 
661
                e.setAttribute(u"access", prop._dbus_access)
 
662
                return e
 
663
            for if_tag in document.getElementsByTagName(u"interface"):
 
664
                for tag in (make_tag(document, name, prop)
 
665
                            for name, prop
 
666
                            in self._get_all_dbus_properties()
 
667
                            if prop._dbus_interface
 
668
                            == if_tag.getAttribute(u"name")):
 
669
                    if_tag.appendChild(tag)
 
670
                # Add the names to the return values for the
 
671
                # "org.freedesktop.DBus.Properties" methods
 
672
                if (if_tag.getAttribute(u"name")
 
673
                    == u"org.freedesktop.DBus.Properties"):
 
674
                    for cn in if_tag.getElementsByTagName(u"method"):
 
675
                        if cn.getAttribute(u"name") == u"Get":
 
676
                            for arg in cn.getElementsByTagName(u"arg"):
 
677
                                if (arg.getAttribute(u"direction")
 
678
                                    == u"out"):
 
679
                                    arg.setAttribute(u"name", u"value")
 
680
                        elif cn.getAttribute(u"name") == u"GetAll":
 
681
                            for arg in cn.getElementsByTagName(u"arg"):
 
682
                                if (arg.getAttribute(u"direction")
 
683
                                    == u"out"):
 
684
                                    arg.setAttribute(u"name", u"props")
 
685
            xmlstring = document.toxml(u"utf-8")
 
686
            document.unlink()
 
687
        except (AttributeError, xml.dom.DOMException,
 
688
                xml.parsers.expat.ExpatError), error:
 
689
            logger.error(u"Failed to override Introspection method",
 
690
                         error)
646
691
        return xmlstring
647
692
 
648
693
 
656
701
    # dbus.service.Object doesn't use super(), so we can't either.
657
702
    
658
703
    def __init__(self, bus = None, *args, **kwargs):
 
704
        self._approvals_pending = 0
659
705
        self.bus = bus
660
706
        Client.__init__(self, *args, **kwargs)
661
707
        # Only now, when this client is initialized, can it show up on
665
711
                                  + self.name.replace(u".", u"_")))
666
712
        DBusObjectWithProperties.__init__(self, self.bus,
667
713
                                          self.dbus_object_path)
 
714
 
 
715
    #Could possible return a bool(self._approvals_pending),
 
716
    #but this could mess up approvals_pending += 1 XXX 
 
717
    def _get_approvals_pending(self):
 
718
        return self._approvals_pending
 
719
    def _set_approvals_pending(self, value):
 
720
        old_value = self._approvals_pending
 
721
        self._approvals_pending = value
 
722
        bval = bool(value)
 
723
        if (hasattr(self, "dbus_object_path")
 
724
            and bval is not bool(old_value)):
 
725
            dbus_bool = dbus.Boolean(bval, variant_level=1)
 
726
            self.PropertyChanged(dbus.String(u"approved_pending"),
 
727
                                 dbus_bool)
 
728
 
 
729
    approvals_pending = property(_get_approvals_pending,
 
730
                                 _set_approvals_pending)
 
731
    del _get_approvals_pending, _set_approvals_pending
668
732
    
669
733
    @staticmethod
670
734
    def _datetime_to_dbus(dt, variant_level=0):
685
749
                                       variant_level=1))
686
750
        return r
687
751
    
688
 
    def disable(self, signal = True):
 
752
    def disable(self, quiet = False):
689
753
        oldstate = getattr(self, u"enabled", False)
690
 
        r = Client.disable(self)
691
 
        if signal and oldstate != self.enabled:
 
754
        r = Client.disable(self, quiet=quiet)
 
755
        if not quiet and oldstate != self.enabled:
692
756
            # Emit D-Bus signal
693
757
            self.PropertyChanged(dbus.String(u"enabled"),
694
758
                                 dbus.Boolean(False, variant_level=1))
759
823
            self.PropertyChanged(dbus.String(u"checker_running"),
760
824
                                 dbus.Boolean(False, variant_level=1))
761
825
        return r
762
 
    
763
 
    ## D-Bus methods & signals
 
826
 
 
827
    def _reset_approved(self):
 
828
        self._approved = None
 
829
        return False
 
830
    
 
831
    def approve(self, value=True):
 
832
        self.send_changedstate()
 
833
        self._approved = value
 
834
        gobject.timeout_add(self._timedelta_to_milliseconds(self.approved_duration),
 
835
                            self._reset_approved)
 
836
    
 
837
    
 
838
    ## D-Bus methods, signals & properties
764
839
    _interface = u"se.bsnet.fukt.Mandos.Client"
765
840
    
766
 
    # CheckedOK - method
767
 
    @dbus.service.method(_interface)
768
 
    def CheckedOK(self):
769
 
        return self.checked_ok()
 
841
    ## Signals
770
842
    
771
843
    # CheckerCompleted - signal
772
844
    @dbus.service.signal(_interface, signature=u"nxs")
786
858
        "D-Bus signal"
787
859
        pass
788
860
    
789
 
    # ReceivedSecret - signal
 
861
    # GotSecret - signal
 
862
    # XXXTEDDY Is sent after succesfull transfer of secret from mandos-server to mandos-client
790
863
    @dbus.service.signal(_interface)
791
 
    def ReceivedSecret(self):
 
864
    def GotSecret(self):
792
865
        "D-Bus signal"
793
866
        pass
794
867
    
795
868
    # Rejected - signal
796
 
    @dbus.service.signal(_interface)
797
 
    def Rejected(self):
798
 
        "D-Bus signal"
799
 
        pass
 
869
    @dbus.service.signal(_interface, signature=u"s")
 
870
    def Rejected(self, reason):
 
871
        "D-Bus signal"
 
872
        pass
 
873
    
 
874
    # NeedApproval - signal
 
875
    @dbus.service.signal(_interface, signature=u"db")
 
876
    def NeedApproval(self, timeout, default):
 
877
        "D-Bus signal"
 
878
        pass
 
879
    
 
880
    ## Methods
 
881
 
 
882
    # Approve - method
 
883
    @dbus.service.method(_interface, in_signature=u"b")
 
884
    def Approve(self, value):
 
885
        self.approve(value)
 
886
 
 
887
    # CheckedOK - method
 
888
    @dbus.service.method(_interface)
 
889
    def CheckedOK(self):
 
890
        return self.checked_ok()
800
891
    
801
892
    # Enable - method
802
893
    @dbus.service.method(_interface)
821
912
    def StopChecker(self):
822
913
        self.stop_checker()
823
914
    
 
915
    ## Properties
 
916
    
 
917
    # approved_pending - property
 
918
    @dbus_service_property(_interface, signature=u"b", access=u"read")
 
919
    def approved_pending_dbus_property(self):
 
920
        return dbus.Boolean(bool(self.approvals_pending))
 
921
    
 
922
    # approved_by_default - property
 
923
    @dbus_service_property(_interface, signature=u"b",
 
924
                           access=u"readwrite")
 
925
    def approved_by_default_dbus_property(self):
 
926
        return dbus.Boolean(self.approved_by_default)
 
927
    
 
928
    # approved_delay - property
 
929
    @dbus_service_property(_interface, signature=u"t",
 
930
                           access=u"readwrite")
 
931
    def approved_delay_dbus_property(self):
 
932
        return dbus.UInt64(self.approved_delay_milliseconds())
 
933
    
 
934
    # approved_duration - property
 
935
    @dbus_service_property(_interface, signature=u"t",
 
936
                           access=u"readwrite")
 
937
    def approved_duration_dbus_property(self):
 
938
        return dbus.UInt64(self._timedelta_to_milliseconds(
 
939
                self.approved_duration))
 
940
    
824
941
    # name - property
825
942
    @dbus_service_property(_interface, signature=u"s", access=u"read")
826
943
    def name_dbus_property(self):
960
1077
    del _interface
961
1078
 
962
1079
 
 
1080
class ProxyClient(object):
 
1081
    def __init__(self, child_pipe, fpr, address):
 
1082
        self._pipe = child_pipe
 
1083
        self._pipe.send(('init', fpr, address))
 
1084
        if not self._pipe.recv():
 
1085
            raise KeyError()
 
1086
 
 
1087
    def __getattribute__(self, name):
 
1088
        if(name == '_pipe'):
 
1089
            return super(ProxyClient, self).__getattribute__(name)
 
1090
        self._pipe.send(('getattr', name))
 
1091
        data = self._pipe.recv()
 
1092
        if data[0] == 'data':
 
1093
            return data[1]
 
1094
        if data[0] == 'function':
 
1095
            def func(*args, **kwargs):
 
1096
                self._pipe.send(('funcall', name, args, kwargs))
 
1097
                return self._pipe.recv()[1]
 
1098
            return func
 
1099
 
 
1100
    def __setattr__(self, name, value):
 
1101
        if(name == '_pipe'):
 
1102
            return super(ProxyClient, self).__setattr__(name, value)
 
1103
        self._pipe.send(('setattr', name, value))
 
1104
 
 
1105
 
963
1106
class ClientHandler(socketserver.BaseRequestHandler, object):
964
1107
    """A class to handle client connections.
965
1108
    
967
1110
    Note: This will run in its own forked process."""
968
1111
    
969
1112
    def handle(self):
970
 
        logger.info(u"TCP connection from: %s",
971
 
                    unicode(self.client_address))
972
 
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
973
 
        # Open IPC pipe to parent process
974
 
        with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
 
1113
        with contextlib.closing(self.server.child_pipe) as child_pipe:
 
1114
            logger.info(u"TCP connection from: %s",
 
1115
                        unicode(self.client_address))
 
1116
            logger.debug(u"Pipe FD: %d",
 
1117
                         self.server.child_pipe.fileno())
 
1118
 
975
1119
            session = (gnutls.connection
976
1120
                       .ClientSession(self.request,
977
1121
                                      gnutls.connection
978
1122
                                      .X509Credentials()))
979
 
            
980
 
            line = self.request.makefile().readline()
981
 
            logger.debug(u"Protocol version: %r", line)
982
 
            try:
983
 
                if int(line.strip().split()[0]) > 1:
984
 
                    raise RuntimeError
985
 
            except (ValueError, IndexError, RuntimeError), error:
986
 
                logger.error(u"Unknown protocol version: %s", error)
987
 
                return
988
 
            
 
1123
 
989
1124
            # Note: gnutls.connection.X509Credentials is really a
990
1125
            # generic GnuTLS certificate credentials object so long as
991
1126
            # no X.509 keys are added to it.  Therefore, we can use it
992
1127
            # here despite using OpenPGP certificates.
993
 
            
 
1128
 
994
1129
            #priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
995
1130
            #                      u"+AES-256-CBC", u"+SHA1",
996
1131
            #                      u"+COMP-NULL", u"+CTYPE-OPENPGP",
1002
1137
            (gnutls.library.functions
1003
1138
             .gnutls_priority_set_direct(session._c_object,
1004
1139
                                         priority, None))
1005
 
            
 
1140
 
 
1141
            # Start communication using the Mandos protocol
 
1142
            # Get protocol number
 
1143
            line = self.request.makefile().readline()
 
1144
            logger.debug(u"Protocol version: %r", line)
 
1145
            try:
 
1146
                if int(line.strip().split()[0]) > 1:
 
1147
                    raise RuntimeError
 
1148
            except (ValueError, IndexError, RuntimeError), error:
 
1149
                logger.error(u"Unknown protocol version: %s", error)
 
1150
                return
 
1151
 
 
1152
            # Start GnuTLS connection
1006
1153
            try:
1007
1154
                session.handshake()
1008
1155
            except gnutls.errors.GNUTLSError, error:
1011
1158
                # established.  Just abandon the request.
1012
1159
                return
1013
1160
            logger.debug(u"Handshake succeeded")
 
1161
 
 
1162
            approval_required = False
1014
1163
            try:
1015
 
                fpr = self.fingerprint(self.peer_certificate(session))
1016
 
            except (TypeError, gnutls.errors.GNUTLSError), error:
1017
 
                logger.warning(u"Bad certificate: %s", error)
1018
 
                session.bye()
1019
 
                return
1020
 
            logger.debug(u"Fingerprint: %s", fpr)
 
1164
                try:
 
1165
                    fpr = self.fingerprint(self.peer_certificate
 
1166
                                           (session))
 
1167
                except (TypeError, gnutls.errors.GNUTLSError), error:
 
1168
                    logger.warning(u"Bad certificate: %s", error)
 
1169
                    return
 
1170
                logger.debug(u"Fingerprint: %s", fpr)
 
1171
 
 
1172
                try:
 
1173
                    client = ProxyClient(child_pipe, fpr,
 
1174
                                         self.client_address)
 
1175
                except KeyError:
 
1176
                    return
 
1177
                
 
1178
                if client.approved_delay:
 
1179
                    delay = client.approved_delay
 
1180
                    client.approvals_pending += 1
 
1181
                    approval_required = True
 
1182
                
 
1183
                while True:
 
1184
                    if not client.enabled:
 
1185
                        logger.warning(u"Client %s is disabled",
 
1186
                                       client.name)
 
1187
                        if self.server.use_dbus:
 
1188
                            # Emit D-Bus signal
 
1189
                            client.Rejected("Disabled")                    
 
1190
                        return
 
1191
                    
 
1192
                    if client._approved or not client.approved_delay:
 
1193
                        #We are approved or approval is disabled
 
1194
                        break
 
1195
                    elif client._approved is None:
 
1196
                        logger.info(u"Client %s need approval",
 
1197
                                    client.name)
 
1198
                        if self.server.use_dbus:
 
1199
                            # Emit D-Bus signal
 
1200
                            client.NeedApproval(
 
1201
                                client.approved_delay_milliseconds(),
 
1202
                                client.approved_by_default)
 
1203
                    else:
 
1204
                        logger.warning(u"Client %s was not approved",
 
1205
                                       client.name)
 
1206
                        if self.server.use_dbus:
 
1207
                            # Emit D-Bus signal
 
1208
                            client.Rejected("Disapproved")
 
1209
                        return
 
1210
                    
 
1211
                    #wait until timeout or approved
 
1212
                    #x = float(client._timedelta_to_milliseconds(delay))
 
1213
                    time = datetime.datetime.now()
 
1214
                    client.changedstate.acquire()
 
1215
                    client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
 
1216
                    client.changedstate.release()
 
1217
                    time2 = datetime.datetime.now()
 
1218
                    if (time2 - time) >= delay:
 
1219
                        if not client.approved_by_default:
 
1220
                            logger.warning("Client %s timed out while"
 
1221
                                           " waiting for approval",
 
1222
                                           client.name)
 
1223
                            if self.server.use_dbus:
 
1224
                                # Emit D-Bus signal
 
1225
                                client.Rejected("Time out")
 
1226
                            return
 
1227
                        else:
 
1228
                            break
 
1229
                    else:
 
1230
                        delay -= time2 - time
 
1231
                
 
1232
                sent_size = 0
 
1233
                while sent_size < len(client.secret):
 
1234
                    # XXX handle session exception
 
1235
                    sent = session.send(client.secret[sent_size:])
 
1236
                    logger.debug(u"Sent: %d, remaining: %d",
 
1237
                                 sent, len(client.secret)
 
1238
                                 - (sent_size + sent))
 
1239
                    sent_size += sent
 
1240
 
 
1241
                logger.info(u"Sending secret to %s", client.name)
 
1242
                # bump the timeout as if seen
 
1243
                client.checked_ok()
 
1244
                if self.server.use_dbus:
 
1245
                    # Emit D-Bus signal
 
1246
                    client.GotSecret()
1021
1247
            
1022
 
            for c in self.server.clients:
1023
 
                if c.fingerprint == fpr:
1024
 
                    client = c
1025
 
                    break
1026
 
            else:
1027
 
                ipc.write(u"NOTFOUND %s %s\n"
1028
 
                          % (fpr, unicode(self.client_address)))
1029
 
                session.bye()
1030
 
                return
1031
 
            # Have to check if client.still_valid(), since it is
1032
 
            # possible that the client timed out while establishing
1033
 
            # the GnuTLS session.
1034
 
            if not client.still_valid():
1035
 
                ipc.write(u"INVALID %s\n" % client.name)
1036
 
                session.bye()
1037
 
                return
1038
 
            ipc.write(u"SENDING %s\n" % client.name)
1039
 
            sent_size = 0
1040
 
            while sent_size < len(client.secret):
1041
 
                sent = session.send(client.secret[sent_size:])
1042
 
                logger.debug(u"Sent: %d, remaining: %d",
1043
 
                             sent, len(client.secret)
1044
 
                             - (sent_size + sent))
1045
 
                sent_size += sent
1046
 
            session.bye()
 
1248
            finally:
 
1249
                if approval_required:
 
1250
                    client.approvals_pending -= 1
 
1251
                session.bye()
1047
1252
    
1048
1253
    @staticmethod
1049
1254
    def peer_certificate(session):
1109
1314
        return hex_fpr
1110
1315
 
1111
1316
 
1112
 
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1113
 
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
 
1317
class MultiprocessingMixIn(object):
 
1318
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
 
1319
    def sub_process_main(self, request, address):
 
1320
        try:
 
1321
            self.finish_request(request, address)
 
1322
        except:
 
1323
            self.handle_error(request, address)
 
1324
        self.close_request(request)
 
1325
            
 
1326
    def process_request(self, request, address):
 
1327
        """Start a new process to process the request."""
 
1328
        multiprocessing.Process(target = self.sub_process_main,
 
1329
                                args = (request, address)).start()
 
1330
 
 
1331
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
 
1332
    """ adds a pipe to the MixIn """
1114
1333
    def process_request(self, request, client_address):
1115
1334
        """Overrides and wraps the original process_request().
1116
1335
        
1117
1336
        This function creates a new pipe in self.pipe
1118
1337
        """
1119
 
        self.pipe = os.pipe()
1120
 
        super(ForkingMixInWithPipe,
 
1338
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
 
1339
 
 
1340
        super(MultiprocessingMixInWithPipe,
1121
1341
              self).process_request(request, client_address)
1122
 
        os.close(self.pipe[1])  # close write end
1123
 
        self.add_pipe(self.pipe[0])
1124
 
    def add_pipe(self, pipe):
 
1342
        self.child_pipe.close()
 
1343
        self.add_pipe(parent_pipe)
 
1344
 
 
1345
    def add_pipe(self, parent_pipe):
1125
1346
        """Dummy function; override as necessary"""
1126
 
        os.close(pipe)
1127
 
 
1128
 
 
1129
 
class IPv6_TCPServer(ForkingMixInWithPipe,
 
1347
        pass
 
1348
 
 
1349
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1130
1350
                     socketserver.TCPServer, object):
1131
1351
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1132
1352
    
1217
1437
            return socketserver.TCPServer.server_activate(self)
1218
1438
    def enable(self):
1219
1439
        self.enabled = True
1220
 
    def add_pipe(self, pipe):
 
1440
    def add_pipe(self, parent_pipe):
1221
1441
        # Call "handle_ipc" for both data and EOF events
1222
 
        gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1223
 
                             self.handle_ipc)
1224
 
    def handle_ipc(self, source, condition, file_objects={}):
 
1442
        gobject.io_add_watch(parent_pipe.fileno(),
 
1443
                             gobject.IO_IN | gobject.IO_HUP,
 
1444
                             functools.partial(self.handle_ipc,
 
1445
                                               parent_pipe = parent_pipe))
 
1446
        
 
1447
    def handle_ipc(self, source, condition, parent_pipe=None,
 
1448
                   client_object=None):
1225
1449
        condition_names = {
1226
1450
            gobject.IO_IN: u"IN",   # There is data to read.
1227
1451
            gobject.IO_OUT: u"OUT", # Data can be written (without
1238
1462
                                       if cond & condition)
1239
1463
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1240
1464
                     conditions_string)
1241
 
        
1242
 
        # Turn the pipe file descriptor into a Python file object
1243
 
        if source not in file_objects:
1244
 
            file_objects[source] = os.fdopen(source, u"r", 1)
1245
 
        
1246
 
        # Read a line from the file object
1247
 
        cmdline = file_objects[source].readline()
1248
 
        if not cmdline:             # Empty line means end of file
1249
 
            # close the IPC pipe
1250
 
            file_objects[source].close()
1251
 
            del file_objects[source]
1252
 
            
1253
 
            # Stop calling this function
1254
 
            return False
1255
 
        
1256
 
        logger.debug(u"IPC command: %r", cmdline)
1257
 
        
1258
 
        # Parse and act on command
1259
 
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1260
 
        
1261
 
        if cmd == u"NOTFOUND":
1262
 
            logger.warning(u"Client not found for fingerprint: %s",
1263
 
                           args)
1264
 
            if self.use_dbus:
1265
 
                # Emit D-Bus signal
1266
 
                mandos_dbus_service.ClientNotFound(args)
1267
 
        elif cmd == u"INVALID":
1268
 
            for client in self.clients:
1269
 
                if client.name == args:
1270
 
                    logger.warning(u"Client %s is invalid", args)
1271
 
                    if self.use_dbus:
1272
 
                        # Emit D-Bus signal
1273
 
                        client.Rejected()
1274
 
                    break
1275
 
            else:
1276
 
                logger.error(u"Unknown client %s is invalid", args)
1277
 
        elif cmd == u"SENDING":
1278
 
            for client in self.clients:
1279
 
                if client.name == args:
1280
 
                    logger.info(u"Sending secret to %s", client.name)
1281
 
                    client.checked_ok()
1282
 
                    if self.use_dbus:
1283
 
                        # Emit D-Bus signal
1284
 
                        client.ReceivedSecret()
1285
 
                    break
1286
 
            else:
1287
 
                logger.error(u"Sending secret to unknown client %s",
1288
 
                             args)
1289
 
        else:
1290
 
            logger.error(u"Unknown IPC command: %r", cmdline)
1291
 
        
1292
 
        # Keep calling this function
 
1465
 
 
1466
        # XXXTEDDY error or the other end of multiprocessing.Pipe has closed
 
1467
        if condition & gobject.IO_HUP or condition & gobject.IO_ERR:
 
1468
            return False
 
1469
        
 
1470
        # Read a request from the child
 
1471
        request = parent_pipe.recv()
 
1472
        logger.debug(u"IPC request: %s", repr(request))
 
1473
        command = request[0]
 
1474
        
 
1475
        if command == 'init':
 
1476
            fpr = request[1]
 
1477
            address = request[2]
 
1478
            
 
1479
            for c in self.clients:
 
1480
                if c.fingerprint == fpr:
 
1481
                    client = c
 
1482
                    break
 
1483
            else:
 
1484
                logger.warning(u"Client not found for fingerprint: %s, ad"
 
1485
                               u"dress: %s", fpr, address)
 
1486
                if self.use_dbus:
 
1487
                    # Emit D-Bus signal
 
1488
                    mandos_dbus_service.ClientNotFound(fpr, address)
 
1489
                parent_pipe.send(False)
 
1490
                return False
 
1491
            
 
1492
            gobject.io_add_watch(parent_pipe.fileno(),
 
1493
                                 gobject.IO_IN | gobject.IO_HUP,
 
1494
                                 functools.partial(self.handle_ipc,
 
1495
                                                   parent_pipe = parent_pipe,
 
1496
                                                   client_object = client))
 
1497
            parent_pipe.send(True)
 
1498
            # remove the old hook in favor of the new above hook on same fileno
 
1499
            return False
 
1500
        if command == 'funcall':
 
1501
            funcname = request[1]
 
1502
            args = request[2]
 
1503
            kwargs = request[3]
 
1504
            
 
1505
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
 
1506
 
 
1507
        if command == 'getattr':
 
1508
            attrname = request[1]
 
1509
            if callable(client_object.__getattribute__(attrname)):
 
1510
                parent_pipe.send(('function',))
 
1511
            else:
 
1512
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
 
1513
        
 
1514
        if command == 'setattr':
 
1515
            attrname = request[1]
 
1516
            value = request[2]
 
1517
            setattr(client_object, attrname, value)
 
1518
 
1293
1519
        return True
1294
1520
 
1295
1521
 
1325
1551
            elif suffix == u"w":
1326
1552
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1327
1553
            else:
1328
 
                raise ValueError
1329
 
        except (ValueError, IndexError):
1330
 
            raise ValueError
 
1554
                raise ValueError(u"Unknown suffix %r" % suffix)
 
1555
        except (ValueError, IndexError), e:
 
1556
            raise ValueError(e.message)
1331
1557
        timevalue += delta
1332
1558
    return timevalue
1333
1559
 
1346
1572
        def if_nametoindex(interface):
1347
1573
            "Get an interface index the hard way, i.e. using fcntl()"
1348
1574
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1349
 
            with closing(socket.socket()) as s:
 
1575
            with contextlib.closing(socket.socket()) as s:
1350
1576
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1351
1577
                                    struct.pack(str(u"16s16x"),
1352
1578
                                                interface))
1372
1598
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1373
1599
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1374
1600
            raise OSError(errno.ENODEV,
1375
 
                          u"/dev/null not a character device")
 
1601
                          u"%s not a character device"
 
1602
                          % os.path.devnull)
1376
1603
        os.dup2(null, sys.stdin.fileno())
1377
1604
        os.dup2(null, sys.stdout.fileno())
1378
1605
        os.dup2(null, sys.stderr.fileno())
1482
1709
                        u"interval": u"5m",
1483
1710
                        u"checker": u"fping -q -- %%(host)s",
1484
1711
                        u"host": u"",
 
1712
                        u"approved_delay": u"0s",
 
1713
                        u"approved_duration": u"1s",
1485
1714
                        }
1486
1715
    client_config = configparser.SafeConfigParser(client_defaults)
1487
1716
    client_config.read(os.path.join(server_settings[u"configdir"],
1545
1774
    bus = dbus.SystemBus()
1546
1775
    # End of Avahi example code
1547
1776
    if use_dbus:
1548
 
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
 
1777
        try:
 
1778
            bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
 
1779
                                            bus, do_not_queue=True)
 
1780
        except dbus.exceptions.NameExistsException, e:
 
1781
            logger.error(unicode(e) + u", disabling D-Bus")
 
1782
            use_dbus = False
 
1783
            server_settings[u"use_dbus"] = False
 
1784
            tcp_server.use_dbus = False
1549
1785
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1550
1786
    service = AvahiService(name = server_settings[u"servicename"],
1551
1787
                           servicetype = u"_mandos._tcp",
1553
1789
    if server_settings["interface"]:
1554
1790
        service.interface = (if_nametoindex
1555
1791
                             (str(server_settings[u"interface"])))
 
1792
 
 
1793
    global multiprocessing_manager
 
1794
    multiprocessing_manager = multiprocessing.Manager()
1556
1795
    
1557
1796
    client_class = Client
1558
1797
    if use_dbus:
1559
1798
        client_class = functools.partial(ClientDBus, bus = bus)
 
1799
    def client_config_items(config, section):
 
1800
        special_settings = {
 
1801
            "approved_by_default":
 
1802
                lambda: config.getboolean(section,
 
1803
                                          "approved_by_default"),
 
1804
            }
 
1805
        for name, value in config.items(section):
 
1806
            try:
 
1807
                yield (name, special_settings[name]())
 
1808
            except KeyError:
 
1809
                yield (name, value)
 
1810
    
1560
1811
    tcp_server.clients.update(set(
1561
1812
            client_class(name = section,
1562
 
                         config= dict(client_config.items(section)))
 
1813
                         config= dict(client_config_items(
 
1814
                        client_config, section)))
1563
1815
            for section in client_config.sections()))
1564
1816
    if not tcp_server.clients:
1565
1817
        logger.warning(u"No clients defined")
1577
1829
        daemon()
1578
1830
    
1579
1831
    try:
1580
 
        with closing(pidfile):
 
1832
        with pidfile:
1581
1833
            pid = os.getpid()
1582
1834
            pidfile.write(str(pid) + "\n")
1583
1835
        del pidfile
1589
1841
        pass
1590
1842
    del pidfilename
1591
1843
    
1592
 
    def cleanup():
1593
 
        "Cleanup function; run on exit"
1594
 
        service.cleanup()
1595
 
        
1596
 
        while tcp_server.clients:
1597
 
            client = tcp_server.clients.pop()
1598
 
            client.disable_hook = None
1599
 
            client.disable()
1600
 
    
1601
 
    atexit.register(cleanup)
1602
 
    
1603
1844
    if not debug:
1604
1845
        signal.signal(signal.SIGINT, signal.SIG_IGN)
1605
1846
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1612
1853
                dbus.service.Object.__init__(self, bus, u"/")
1613
1854
            _interface = u"se.bsnet.fukt.Mandos"
1614
1855
            
1615
 
            @dbus.service.signal(_interface, signature=u"oa{sv}")
1616
 
            def ClientAdded(self, objpath, properties):
 
1856
            @dbus.service.signal(_interface, signature=u"o")
 
1857
            def ClientAdded(self, objpath):
1617
1858
                "D-Bus signal"
1618
1859
                pass
1619
1860
            
1620
 
            @dbus.service.signal(_interface, signature=u"s")
1621
 
            def ClientNotFound(self, fingerprint):
 
1861
            @dbus.service.signal(_interface, signature=u"ss")
 
1862
            def ClientNotFound(self, fingerprint, address):
1622
1863
                "D-Bus signal"
1623
1864
                pass
1624
1865
            
1650
1891
                        tcp_server.clients.remove(c)
1651
1892
                        c.remove_from_connection()
1652
1893
                        # Don't signal anything except ClientRemoved
1653
 
                        c.disable(signal=False)
 
1894
                        c.disable(quiet=True)
1654
1895
                        # Emit D-Bus signal
1655
1896
                        self.ClientRemoved(object_path, c.name)
1656
1897
                        return
1657
 
                raise KeyError
 
1898
                raise KeyError(object_path)
1658
1899
            
1659
1900
            del _interface
1660
1901
        
1661
1902
        mandos_dbus_service = MandosDBusService()
1662
1903
    
 
1904
    def cleanup():
 
1905
        "Cleanup function; run on exit"
 
1906
        service.cleanup()
 
1907
        
 
1908
        while tcp_server.clients:
 
1909
            client = tcp_server.clients.pop()
 
1910
            if use_dbus:
 
1911
                client.remove_from_connection()
 
1912
            client.disable_hook = None
 
1913
            # Don't signal anything except ClientRemoved
 
1914
            client.disable(quiet=True)
 
1915
            if use_dbus:
 
1916
                # Emit D-Bus signal
 
1917
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
 
1918
                                                  client.name)
 
1919
    
 
1920
    atexit.register(cleanup)
 
1921
    
1663
1922
    for client in tcp_server.clients:
1664
1923
        if use_dbus:
1665
1924
            # Emit D-Bus signal
1666
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
1667
 
                                            client.GetAll(u""))
 
1925
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
1668
1926
        client.enable()
1669
1927
    
1670
1928
    tcp_server.enable()
1688
1946
            service.activate()
1689
1947
        except dbus.exceptions.DBusException, error:
1690
1948
            logger.critical(u"DBusException: %s", error)
 
1949
            cleanup()
1691
1950
            sys.exit(1)
1692
1951
        # End of Avahi example code
1693
1952
        
1700
1959
        main_loop.run()
1701
1960
    except AvahiError, error:
1702
1961
        logger.critical(u"AvahiError: %s", error)
 
1962
        cleanup()
1703
1963
        sys.exit(1)
1704
1964
    except KeyboardInterrupt:
1705
1965
        if debug:
1706
1966
            print >> sys.stderr
1707
1967
        logger.debug(u"Server received KeyboardInterrupt")
1708
1968
    logger.debug(u"Server exiting")
 
1969
    # Must run before the D-Bus bus name gets deregistered
 
1970
    cleanup()
1709
1971
 
1710
1972
if __name__ == '__main__':
1711
1973
    main()