/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 Hogeborn
  • Date: 2010-09-12 03:00:40 UTC
  • Revision ID: teddy@fukt.bsnet.se-20100912030040-b0uopyennste9fdh
Documentation changes:

* DBUS-API: New file documenting the server D-Bus interface.

* clients.conf: Add examples of new approval settings.

* debian/mandos.docs: Added "DBUS-API".

* mandos-clients.conf.xml (OPTIONS): Added "approved_by_default",
                                     "approval_delay", and
                                     "approval_duration".
* mandos.xml (D-BUS INTERFACE): Refer to the "DBUS-API" file.
  (BUGS): Remove mention of lack of a remote query interface.

Show diffs side-by-side

added added

removed removed

Lines of Context:
60
60
import fcntl
61
61
import functools
62
62
import cPickle as pickle
63
 
import select
 
63
import multiprocessing
64
64
 
65
65
import dbus
66
66
import dbus.service
83
83
 
84
84
version = "1.0.14"
85
85
 
 
86
#logger = logging.getLogger(u'mandos')
86
87
logger = logging.Logger(u'mandos')
87
88
syslogger = (logging.handlers.SysLogHandler
88
89
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
156
157
                            u" after %i retries, exiting.",
157
158
                            self.rename_count)
158
159
            raise AvahiServiceError(u"Too many renames")
159
 
        self.name = self.server.GetAlternativeServiceName(self.name)
 
160
        self.name = unicode(self.server.GetAlternativeServiceName(self.name))
160
161
        logger.info(u"Changing Zeroconf service name to %r ...",
161
 
                    unicode(self.name))
 
162
                    self.name)
162
163
        syslogger.setFormatter(logging.Formatter
163
164
                               (u'Mandos (%s) [%%(process)d]:'
164
165
                                u' %%(levelname)s: %%(message)s'
165
166
                                % self.name))
166
167
        self.remove()
167
 
        self.add()
 
168
        try:
 
169
            self.add()
 
170
        except dbus.exceptions.DBusException, error:
 
171
            logger.critical(u"DBusException: %s", error)
 
172
            self.cleanup()
 
173
            os._exit(1)
168
174
        self.rename_count += 1
169
175
    def remove(self):
170
176
        """Derived from the Avahi example code"""
259
265
                     runtime with vars(self) as dict, so that for
260
266
                     instance %(name)s can be used in the command.
261
267
    current_checker_command: string; current running checker_command
 
268
    approval_delay: datetime.timedelta(); Time to wait for approval
 
269
    _approved:   bool(); 'None' if not yet approved/disapproved
 
270
    approval_duration: datetime.timedelta(); Duration of one approval
262
271
    """
263
272
    
264
273
    @staticmethod
275
284
    def interval_milliseconds(self):
276
285
        "Return the 'interval' attribute in milliseconds"
277
286
        return self._timedelta_to_milliseconds(self.interval)
 
287
 
 
288
    def approval_delay_milliseconds(self):
 
289
        return self._timedelta_to_milliseconds(self.approval_delay)
278
290
    
279
291
    def __init__(self, name = None, disable_hook=None, config=None):
280
292
        """Note: the 'checker' key in 'config' sets the
315
327
        self.checker_command = config[u"checker"]
316
328
        self.current_checker_command = None
317
329
        self.last_connect = None
 
330
        self._approved = None
 
331
        self.approved_by_default = config.get(u"approved_by_default",
 
332
                                              True)
 
333
        self.approvals_pending = 0
 
334
        self.approval_delay = string_to_delta(
 
335
            config[u"approval_delay"])
 
336
        self.approval_duration = string_to_delta(
 
337
            config[u"approval_duration"])
 
338
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
318
339
    
 
340
    def send_changedstate(self):
 
341
        self.changedstate.acquire()
 
342
        self.changedstate.notify_all()
 
343
        self.changedstate.release()
 
344
        
319
345
    def enable(self):
320
346
        """Start this client's checker and timeout hooks"""
321
347
        if getattr(self, u"enabled", False):
322
348
            # Already enabled
323
349
            return
 
350
        self.send_changedstate()
324
351
        self.last_enabled = datetime.datetime.utcnow()
325
352
        # Schedule a new checker to be started an 'interval' from now,
326
353
        # and every interval from then on.
340
367
        if not getattr(self, "enabled", False):
341
368
            return False
342
369
        if not quiet:
 
370
            self.send_changedstate()
 
371
        if not quiet:
343
372
            logger.info(u"Disabling client %s", self.name)
344
373
        if getattr(self, u"disable_initiator_tag", False):
345
374
            gobject.source_remove(self.disable_initiator_tag)
478
507
                raise
479
508
        self.checker = None
480
509
 
481
 
 
482
510
def dbus_service_property(dbus_interface, signature=u"v",
483
511
                          access=u"readwrite", byte_arrays=False):
484
512
    """Decorators for marking methods of a DBusObjectWithProperties to
678
706
    # dbus.service.Object doesn't use super(), so we can't either.
679
707
    
680
708
    def __init__(self, bus = None, *args, **kwargs):
 
709
        self._approvals_pending = 0
681
710
        self.bus = bus
682
711
        Client.__init__(self, *args, **kwargs)
683
712
        # Only now, when this client is initialized, can it show up on
687
716
                                  + self.name.replace(u".", u"_")))
688
717
        DBusObjectWithProperties.__init__(self, self.bus,
689
718
                                          self.dbus_object_path)
 
719
        
 
720
    def _get_approvals_pending(self):
 
721
        return self._approvals_pending
 
722
    def _set_approvals_pending(self, value):
 
723
        old_value = self._approvals_pending
 
724
        self._approvals_pending = value
 
725
        bval = bool(value)
 
726
        if (hasattr(self, "dbus_object_path")
 
727
            and bval is not bool(old_value)):
 
728
            dbus_bool = dbus.Boolean(bval, variant_level=1)
 
729
            self.PropertyChanged(dbus.String(u"ApprovalPending"),
 
730
                                 dbus_bool)
 
731
 
 
732
    approvals_pending = property(_get_approvals_pending,
 
733
                                 _set_approvals_pending)
 
734
    del _get_approvals_pending, _set_approvals_pending
690
735
    
691
736
    @staticmethod
692
737
    def _datetime_to_dbus(dt, variant_level=0):
699
744
        r = Client.enable(self)
700
745
        if oldstate != self.enabled:
701
746
            # Emit D-Bus signals
702
 
            self.PropertyChanged(dbus.String(u"enabled"),
 
747
            self.PropertyChanged(dbus.String(u"Enabled"),
703
748
                                 dbus.Boolean(True, variant_level=1))
704
749
            self.PropertyChanged(
705
 
                dbus.String(u"last_enabled"),
 
750
                dbus.String(u"LastEnabled"),
706
751
                self._datetime_to_dbus(self.last_enabled,
707
752
                                       variant_level=1))
708
753
        return r
712
757
        r = Client.disable(self, quiet=quiet)
713
758
        if not quiet and oldstate != self.enabled:
714
759
            # Emit D-Bus signal
715
 
            self.PropertyChanged(dbus.String(u"enabled"),
 
760
            self.PropertyChanged(dbus.String(u"Enabled"),
716
761
                                 dbus.Boolean(False, variant_level=1))
717
762
        return r
718
763
    
730
775
        self.checker_callback_tag = None
731
776
        self.checker = None
732
777
        # Emit D-Bus signal
733
 
        self.PropertyChanged(dbus.String(u"checker_running"),
 
778
        self.PropertyChanged(dbus.String(u"CheckerRunning"),
734
779
                             dbus.Boolean(False, variant_level=1))
735
780
        if os.WIFEXITED(condition):
736
781
            exitstatus = os.WEXITSTATUS(condition)
751
796
        r = Client.checked_ok(self, *args, **kwargs)
752
797
        # Emit D-Bus signal
753
798
        self.PropertyChanged(
754
 
            dbus.String(u"last_checked_ok"),
 
799
            dbus.String(u"LastCheckedOK"),
755
800
            (self._datetime_to_dbus(self.last_checked_ok,
756
801
                                    variant_level=1)))
757
802
        return r
769
814
            # Emit D-Bus signal
770
815
            self.CheckerStarted(self.current_checker_command)
771
816
            self.PropertyChanged(
772
 
                dbus.String(u"checker_running"),
 
817
                dbus.String(u"CheckerRunning"),
773
818
                dbus.Boolean(True, variant_level=1))
774
819
        return r
775
820
    
778
823
        r = Client.stop_checker(self, *args, **kwargs)
779
824
        if (old_checker is not None
780
825
            and getattr(self, u"checker", None) is None):
781
 
            self.PropertyChanged(dbus.String(u"checker_running"),
 
826
            self.PropertyChanged(dbus.String(u"CheckerRunning"),
782
827
                                 dbus.Boolean(False, variant_level=1))
783
828
        return r
 
829
 
 
830
    def _reset_approved(self):
 
831
        self._approved = None
 
832
        return False
 
833
    
 
834
    def approve(self, value=True):
 
835
        self.send_changedstate()
 
836
        self._approved = value
 
837
        gobject.timeout_add(self._timedelta_to_milliseconds
 
838
                            (self.approval_duration),
 
839
                            self._reset_approved)
 
840
    
784
841
    
785
842
    ## D-Bus methods, signals & properties
786
843
    _interface = u"se.bsnet.fukt.Mandos.Client"
808
865
    # GotSecret - signal
809
866
    @dbus.service.signal(_interface)
810
867
    def GotSecret(self):
811
 
        "D-Bus signal"
 
868
        """D-Bus signal
 
869
        Is sent after a successful transfer of secret from the Mandos
 
870
        server to mandos-client
 
871
        """
812
872
        pass
813
873
    
814
874
    # Rejected - signal
815
 
    @dbus.service.signal(_interface)
816
 
    def Rejected(self):
 
875
    @dbus.service.signal(_interface, signature=u"s")
 
876
    def Rejected(self, reason):
 
877
        "D-Bus signal"
 
878
        pass
 
879
    
 
880
    # NeedApproval - signal
 
881
    @dbus.service.signal(_interface, signature=u"tb")
 
882
    def NeedApproval(self, timeout, default):
817
883
        "D-Bus signal"
818
884
        pass
819
885
    
820
886
    ## Methods
821
 
    
 
887
 
 
888
    # Approve - method
 
889
    @dbus.service.method(_interface, in_signature=u"b")
 
890
    def Approve(self, value):
 
891
        self.approve(value)
 
892
 
822
893
    # CheckedOK - method
823
894
    @dbus.service.method(_interface)
824
895
    def CheckedOK(self):
849
920
    
850
921
    ## Properties
851
922
    
852
 
    # name - property
 
923
    # ApprovalPending - property
 
924
    @dbus_service_property(_interface, signature=u"b", access=u"read")
 
925
    def ApprovalPending_dbus_property(self):
 
926
        return dbus.Boolean(bool(self.approvals_pending))
 
927
    
 
928
    # ApprovedByDefault - property
 
929
    @dbus_service_property(_interface, signature=u"b",
 
930
                           access=u"readwrite")
 
931
    def ApprovedByDefault_dbus_property(self, value=None):
 
932
        if value is None:       # get
 
933
            return dbus.Boolean(self.approved_by_default)
 
934
        self.approved_by_default = bool(value)
 
935
        # Emit D-Bus signal
 
936
        self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
 
937
                             dbus.Boolean(value, variant_level=1))
 
938
    
 
939
    # ApprovalDelay - property
 
940
    @dbus_service_property(_interface, signature=u"t",
 
941
                           access=u"readwrite")
 
942
    def ApprovalDelay_dbus_property(self, value=None):
 
943
        if value is None:       # get
 
944
            return dbus.UInt64(self.approval_delay_milliseconds())
 
945
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
 
946
        # Emit D-Bus signal
 
947
        self.PropertyChanged(dbus.String(u"ApprovalDelay"),
 
948
                             dbus.UInt64(value, variant_level=1))
 
949
    
 
950
    # ApprovalDuration - property
 
951
    @dbus_service_property(_interface, signature=u"t",
 
952
                           access=u"readwrite")
 
953
    def ApprovalDuration_dbus_property(self, value=None):
 
954
        if value is None:       # get
 
955
            return dbus.UInt64(self._timedelta_to_milliseconds(
 
956
                    self.approval_duration))
 
957
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
 
958
        # Emit D-Bus signal
 
959
        self.PropertyChanged(dbus.String(u"ApprovalDuration"),
 
960
                             dbus.UInt64(value, variant_level=1))
 
961
    
 
962
    # Name - property
853
963
    @dbus_service_property(_interface, signature=u"s", access=u"read")
854
 
    def name_dbus_property(self):
 
964
    def Name_dbus_property(self):
855
965
        return dbus.String(self.name)
856
966
    
857
 
    # fingerprint - property
 
967
    # Fingerprint - property
858
968
    @dbus_service_property(_interface, signature=u"s", access=u"read")
859
 
    def fingerprint_dbus_property(self):
 
969
    def Fingerprint_dbus_property(self):
860
970
        return dbus.String(self.fingerprint)
861
971
    
862
 
    # host - property
 
972
    # Host - property
863
973
    @dbus_service_property(_interface, signature=u"s",
864
974
                           access=u"readwrite")
865
 
    def host_dbus_property(self, value=None):
 
975
    def Host_dbus_property(self, value=None):
866
976
        if value is None:       # get
867
977
            return dbus.String(self.host)
868
978
        self.host = value
869
979
        # Emit D-Bus signal
870
 
        self.PropertyChanged(dbus.String(u"host"),
 
980
        self.PropertyChanged(dbus.String(u"Host"),
871
981
                             dbus.String(value, variant_level=1))
872
982
    
873
 
    # created - property
 
983
    # Created - property
874
984
    @dbus_service_property(_interface, signature=u"s", access=u"read")
875
 
    def created_dbus_property(self):
 
985
    def Created_dbus_property(self):
876
986
        return dbus.String(self._datetime_to_dbus(self.created))
877
987
    
878
 
    # last_enabled - property
 
988
    # LastEnabled - property
879
989
    @dbus_service_property(_interface, signature=u"s", access=u"read")
880
 
    def last_enabled_dbus_property(self):
 
990
    def LastEnabled_dbus_property(self):
881
991
        if self.last_enabled is None:
882
992
            return dbus.String(u"")
883
993
        return dbus.String(self._datetime_to_dbus(self.last_enabled))
884
994
    
885
 
    # enabled - property
 
995
    # Enabled - property
886
996
    @dbus_service_property(_interface, signature=u"b",
887
997
                           access=u"readwrite")
888
 
    def enabled_dbus_property(self, value=None):
 
998
    def Enabled_dbus_property(self, value=None):
889
999
        if value is None:       # get
890
1000
            return dbus.Boolean(self.enabled)
891
1001
        if value:
893
1003
        else:
894
1004
            self.disable()
895
1005
    
896
 
    # last_checked_ok - property
 
1006
    # LastCheckedOK - property
897
1007
    @dbus_service_property(_interface, signature=u"s",
898
1008
                           access=u"readwrite")
899
 
    def last_checked_ok_dbus_property(self, value=None):
 
1009
    def LastCheckedOK_dbus_property(self, value=None):
900
1010
        if value is not None:
901
1011
            self.checked_ok()
902
1012
            return
905
1015
        return dbus.String(self._datetime_to_dbus(self
906
1016
                                                  .last_checked_ok))
907
1017
    
908
 
    # timeout - property
 
1018
    # Timeout - property
909
1019
    @dbus_service_property(_interface, signature=u"t",
910
1020
                           access=u"readwrite")
911
 
    def timeout_dbus_property(self, value=None):
 
1021
    def Timeout_dbus_property(self, value=None):
912
1022
        if value is None:       # get
913
1023
            return dbus.UInt64(self.timeout_milliseconds())
914
1024
        self.timeout = datetime.timedelta(0, 0, 0, value)
915
1025
        # Emit D-Bus signal
916
 
        self.PropertyChanged(dbus.String(u"timeout"),
 
1026
        self.PropertyChanged(dbus.String(u"Timeout"),
917
1027
                             dbus.UInt64(value, variant_level=1))
918
1028
        if getattr(self, u"disable_initiator_tag", None) is None:
919
1029
            return
933
1043
            self.disable_initiator_tag = (gobject.timeout_add
934
1044
                                          (time_to_die, self.disable))
935
1045
    
936
 
    # interval - property
 
1046
    # Interval - property
937
1047
    @dbus_service_property(_interface, signature=u"t",
938
1048
                           access=u"readwrite")
939
 
    def interval_dbus_property(self, value=None):
 
1049
    def Interval_dbus_property(self, value=None):
940
1050
        if value is None:       # get
941
1051
            return dbus.UInt64(self.interval_milliseconds())
942
1052
        self.interval = datetime.timedelta(0, 0, 0, value)
943
1053
        # Emit D-Bus signal
944
 
        self.PropertyChanged(dbus.String(u"interval"),
 
1054
        self.PropertyChanged(dbus.String(u"Interval"),
945
1055
                             dbus.UInt64(value, variant_level=1))
946
1056
        if getattr(self, u"checker_initiator_tag", None) is None:
947
1057
            return
951
1061
                                      (value, self.start_checker))
952
1062
        self.start_checker()    # Start one now, too
953
1063
 
954
 
    # checker - property
 
1064
    # Checker - property
955
1065
    @dbus_service_property(_interface, signature=u"s",
956
1066
                           access=u"readwrite")
957
 
    def checker_dbus_property(self, value=None):
 
1067
    def Checker_dbus_property(self, value=None):
958
1068
        if value is None:       # get
959
1069
            return dbus.String(self.checker_command)
960
1070
        self.checker_command = value
961
1071
        # Emit D-Bus signal
962
 
        self.PropertyChanged(dbus.String(u"checker"),
 
1072
        self.PropertyChanged(dbus.String(u"Checker"),
963
1073
                             dbus.String(self.checker_command,
964
1074
                                         variant_level=1))
965
1075
    
966
 
    # checker_running - property
 
1076
    # CheckerRunning - property
967
1077
    @dbus_service_property(_interface, signature=u"b",
968
1078
                           access=u"readwrite")
969
 
    def checker_running_dbus_property(self, value=None):
 
1079
    def CheckerRunning_dbus_property(self, value=None):
970
1080
        if value is None:       # get
971
1081
            return dbus.Boolean(self.checker is not None)
972
1082
        if value:
974
1084
        else:
975
1085
            self.stop_checker()
976
1086
    
977
 
    # object_path - property
 
1087
    # ObjectPath - property
978
1088
    @dbus_service_property(_interface, signature=u"o", access=u"read")
979
 
    def object_path_dbus_property(self):
 
1089
    def ObjectPath_dbus_property(self):
980
1090
        return self.dbus_object_path # is already a dbus.ObjectPath
981
1091
    
982
 
    # secret = property
 
1092
    # Secret = property
983
1093
    @dbus_service_property(_interface, signature=u"ay",
984
1094
                           access=u"write", byte_arrays=True)
985
 
    def secret_dbus_property(self, value):
 
1095
    def Secret_dbus_property(self, value):
986
1096
        self.secret = str(value)
987
1097
    
988
1098
    del _interface
989
1099
 
990
1100
 
 
1101
class ProxyClient(object):
 
1102
    def __init__(self, child_pipe, fpr, address):
 
1103
        self._pipe = child_pipe
 
1104
        self._pipe.send(('init', fpr, address))
 
1105
        if not self._pipe.recv():
 
1106
            raise KeyError()
 
1107
 
 
1108
    def __getattribute__(self, name):
 
1109
        if(name == '_pipe'):
 
1110
            return super(ProxyClient, self).__getattribute__(name)
 
1111
        self._pipe.send(('getattr', name))
 
1112
        data = self._pipe.recv()
 
1113
        if data[0] == 'data':
 
1114
            return data[1]
 
1115
        if data[0] == 'function':
 
1116
            def func(*args, **kwargs):
 
1117
                self._pipe.send(('funcall', name, args, kwargs))
 
1118
                return self._pipe.recv()[1]
 
1119
            return func
 
1120
 
 
1121
    def __setattr__(self, name, value):
 
1122
        if(name == '_pipe'):
 
1123
            return super(ProxyClient, self).__setattr__(name, value)
 
1124
        self._pipe.send(('setattr', name, value))
 
1125
 
 
1126
 
991
1127
class ClientHandler(socketserver.BaseRequestHandler, object):
992
1128
    """A class to handle client connections.
993
1129
    
995
1131
    Note: This will run in its own forked process."""
996
1132
    
997
1133
    def handle(self):
998
 
        logger.info(u"TCP connection from: %s",
999
 
                    unicode(self.client_address))
1000
 
        logger.debug(u"IPC Pipe FD: %d",
1001
 
                     self.server.child_pipe[1].fileno())
1002
 
        # Open IPC pipe to parent process
1003
 
        with contextlib.nested(self.server.child_pipe[1],
1004
 
                               self.server.parent_pipe[0]
1005
 
                               ) as (ipc, ipc_return):
 
1134
        with contextlib.closing(self.server.child_pipe) as child_pipe:
 
1135
            logger.info(u"TCP connection from: %s",
 
1136
                        unicode(self.client_address))
 
1137
            logger.debug(u"Pipe FD: %d",
 
1138
                         self.server.child_pipe.fileno())
 
1139
 
1006
1140
            session = (gnutls.connection
1007
1141
                       .ClientSession(self.request,
1008
1142
                                      gnutls.connection
1009
1143
                                      .X509Credentials()))
1010
 
            
 
1144
 
1011
1145
            # Note: gnutls.connection.X509Credentials is really a
1012
1146
            # generic GnuTLS certificate credentials object so long as
1013
1147
            # no X.509 keys are added to it.  Therefore, we can use it
1014
1148
            # here despite using OpenPGP certificates.
1015
 
            
 
1149
 
1016
1150
            #priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1017
1151
            #                      u"+AES-256-CBC", u"+SHA1",
1018
1152
            #                      u"+COMP-NULL", u"+CTYPE-OPENPGP",
1024
1158
            (gnutls.library.functions
1025
1159
             .gnutls_priority_set_direct(session._c_object,
1026
1160
                                         priority, None))
1027
 
            
 
1161
 
1028
1162
            # Start communication using the Mandos protocol
1029
1163
            # Get protocol number
1030
1164
            line = self.request.makefile().readline()
1035
1169
            except (ValueError, IndexError, RuntimeError), error:
1036
1170
                logger.error(u"Unknown protocol version: %s", error)
1037
1171
                return
1038
 
            
 
1172
 
1039
1173
            # Start GnuTLS connection
1040
1174
            try:
1041
1175
                session.handshake()
1045
1179
                # established.  Just abandon the request.
1046
1180
                return
1047
1181
            logger.debug(u"Handshake succeeded")
 
1182
 
 
1183
            approval_required = False
1048
1184
            try:
1049
1185
                try:
1050
1186
                    fpr = self.fingerprint(self.peer_certificate
1054
1190
                    return
1055
1191
                logger.debug(u"Fingerprint: %s", fpr)
1056
1192
 
1057
 
                for c in self.server.clients:
1058
 
                    if c.fingerprint == fpr:
1059
 
                        client = c
 
1193
                try:
 
1194
                    client = ProxyClient(child_pipe, fpr,
 
1195
                                         self.client_address)
 
1196
                except KeyError:
 
1197
                    return
 
1198
                
 
1199
                if client.approval_delay:
 
1200
                    delay = client.approval_delay
 
1201
                    client.approvals_pending += 1
 
1202
                    approval_required = True
 
1203
                
 
1204
                while True:
 
1205
                    if not client.enabled:
 
1206
                        logger.warning(u"Client %s is disabled",
 
1207
                                       client.name)
 
1208
                        if self.server.use_dbus:
 
1209
                            # Emit D-Bus signal
 
1210
                            client.Rejected("Disabled")                    
 
1211
                        return
 
1212
                    
 
1213
                    if client._approved or not client.approval_delay:
 
1214
                        #We are approved or approval is disabled
1060
1215
                        break
1061
 
                else:
1062
 
                    ipc.write(u"NOTFOUND %s %s\n"
1063
 
                              % (fpr, unicode(self.client_address)))
1064
 
                    return
1065
 
                
1066
 
                class ClientProxy(object):
1067
 
                    """Client proxy object.  Not for calling methods."""
1068
 
                    def __init__(self, client):
1069
 
                        self.client = client
1070
 
                    def __getattr__(self, name):
1071
 
                        if name.startswith("ipc_"):
1072
 
                            def tempfunc():
1073
 
                                ipc.write("%s %s\n" % (name[4:].upper(),
1074
 
                                                       self.client.name))
1075
 
                            return tempfunc
1076
 
                        if not hasattr(self.client, name):
1077
 
                            raise AttributeError
1078
 
                        ipc.write(u"GETATTR %s %s\n"
1079
 
                                  % (name, self.client.fingerprint))
1080
 
                        return pickle.load(ipc_return)
1081
 
                clientproxy = ClientProxy(client)
1082
 
                # Have to check if client.enabled, since it is
1083
 
                # possible that the client was disabled since the
1084
 
                # GnuTLS session was established.
1085
 
                if not clientproxy.enabled:
1086
 
                    clientproxy.ipc_disabled()
1087
 
                    return
1088
 
                
1089
 
                clientproxy.ipc_sending()
 
1216
                    elif client._approved is None:
 
1217
                        logger.info(u"Client %s needs approval",
 
1218
                                    client.name)
 
1219
                        if self.server.use_dbus:
 
1220
                            # Emit D-Bus signal
 
1221
                            client.NeedApproval(
 
1222
                                client.approval_delay_milliseconds(),
 
1223
                                client.approved_by_default)
 
1224
                    else:
 
1225
                        logger.warning(u"Client %s was not approved",
 
1226
                                       client.name)
 
1227
                        if self.server.use_dbus:
 
1228
                            # Emit D-Bus signal
 
1229
                            client.Rejected("Denied")
 
1230
                        return
 
1231
                    
 
1232
                    #wait until timeout or approved
 
1233
                    #x = float(client._timedelta_to_milliseconds(delay))
 
1234
                    time = datetime.datetime.now()
 
1235
                    client.changedstate.acquire()
 
1236
                    client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
 
1237
                    client.changedstate.release()
 
1238
                    time2 = datetime.datetime.now()
 
1239
                    if (time2 - time) >= delay:
 
1240
                        if not client.approved_by_default:
 
1241
                            logger.warning("Client %s timed out while"
 
1242
                                           " waiting for approval",
 
1243
                                           client.name)
 
1244
                            if self.server.use_dbus:
 
1245
                                # Emit D-Bus signal
 
1246
                                client.Rejected("Timed out")
 
1247
                            return
 
1248
                        else:
 
1249
                            break
 
1250
                    else:
 
1251
                        delay -= time2 - time
 
1252
                
1090
1253
                sent_size = 0
1091
1254
                while sent_size < len(client.secret):
1092
 
                    sent = session.send(client.secret[sent_size:])
 
1255
                    try:
 
1256
                        sent = session.send(client.secret[sent_size:])
 
1257
                    except (gnutls.errors.GNUTLSError), error:
 
1258
                        logger.warning("gnutls send failed")
 
1259
                        return
1093
1260
                    logger.debug(u"Sent: %d, remaining: %d",
1094
1261
                                 sent, len(client.secret)
1095
1262
                                 - (sent_size + sent))
1096
1263
                    sent_size += sent
 
1264
 
 
1265
                logger.info(u"Sending secret to %s", client.name)
 
1266
                # bump the timeout as if seen
 
1267
                client.checked_ok()
 
1268
                if self.server.use_dbus:
 
1269
                    # Emit D-Bus signal
 
1270
                    client.GotSecret()
 
1271
            
1097
1272
            finally:
1098
 
                session.bye()
 
1273
                if approval_required:
 
1274
                    client.approvals_pending -= 1
 
1275
                try:
 
1276
                    session.bye()
 
1277
                except (gnutls.errors.GNUTLSError), error:
 
1278
                    logger.warning("GnuTLS bye failed")
1099
1279
    
1100
1280
    @staticmethod
1101
1281
    def peer_certificate(session):
1161
1341
        return hex_fpr
1162
1342
 
1163
1343
 
1164
 
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
1165
 
    """Like socketserver.ForkingMixIn, but also pass a pipe pair."""
 
1344
class MultiprocessingMixIn(object):
 
1345
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
 
1346
    def sub_process_main(self, request, address):
 
1347
        try:
 
1348
            self.finish_request(request, address)
 
1349
        except:
 
1350
            self.handle_error(request, address)
 
1351
        self.close_request(request)
 
1352
            
 
1353
    def process_request(self, request, address):
 
1354
        """Start a new process to process the request."""
 
1355
        multiprocessing.Process(target = self.sub_process_main,
 
1356
                                args = (request, address)).start()
 
1357
 
 
1358
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
 
1359
    """ adds a pipe to the MixIn """
1166
1360
    def process_request(self, request, client_address):
1167
1361
        """Overrides and wraps the original process_request().
1168
1362
        
1169
1363
        This function creates a new pipe in self.pipe
1170
1364
        """
1171
 
        # Child writes to child_pipe
1172
 
        self.child_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1173
 
        # Parent writes to parent_pipe
1174
 
        self.parent_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1175
 
        super(ForkingMixInWithPipes,
 
1365
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
 
1366
 
 
1367
        super(MultiprocessingMixInWithPipe,
1176
1368
              self).process_request(request, client_address)
1177
 
        # Close unused ends for parent
1178
 
        self.parent_pipe[0].close() # close read end
1179
 
        self.child_pipe[1].close()  # close write end
1180
 
        self.add_pipe_fds(self.child_pipe[0], self.parent_pipe[1])
1181
 
    def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
 
1369
        self.child_pipe.close()
 
1370
        self.add_pipe(parent_pipe)
 
1371
 
 
1372
    def add_pipe(self, parent_pipe):
1182
1373
        """Dummy function; override as necessary"""
1183
 
        child_pipe_fd.close()
1184
 
        parent_pipe_fd.close()
1185
 
 
1186
 
 
1187
 
class IPv6_TCPServer(ForkingMixInWithPipes,
 
1374
        pass
 
1375
 
 
1376
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1188
1377
                     socketserver.TCPServer, object):
1189
1378
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1190
1379
    
1275
1464
            return socketserver.TCPServer.server_activate(self)
1276
1465
    def enable(self):
1277
1466
        self.enabled = True
1278
 
    def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
 
1467
    def add_pipe(self, parent_pipe):
1279
1468
        # Call "handle_ipc" for both data and EOF events
1280
 
        gobject.io_add_watch(child_pipe_fd.fileno(),
 
1469
        gobject.io_add_watch(parent_pipe.fileno(),
1281
1470
                             gobject.IO_IN | gobject.IO_HUP,
1282
1471
                             functools.partial(self.handle_ipc,
1283
 
                                               reply = parent_pipe_fd,
1284
 
                                               sender= child_pipe_fd))
1285
 
    def handle_ipc(self, source, condition, reply=None, sender=None):
 
1472
                                               parent_pipe = parent_pipe))
 
1473
        
 
1474
    def handle_ipc(self, source, condition, parent_pipe=None,
 
1475
                   client_object=None):
1286
1476
        condition_names = {
1287
1477
            gobject.IO_IN: u"IN",   # There is data to read.
1288
1478
            gobject.IO_OUT: u"OUT", # Data can be written (without
1299
1489
                                       if cond & condition)
1300
1490
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1301
1491
                     conditions_string)
1302
 
        
1303
 
        # Read a line from the file object
1304
 
        cmdline = sender.readline()
1305
 
        if not cmdline:             # Empty line means end of file
1306
 
            # close the IPC pipes
1307
 
            sender.close()
1308
 
            reply.close()
1309
 
            
1310
 
            # Stop calling this function
1311
 
            return False
1312
 
        
1313
 
        logger.debug(u"IPC command: %r", cmdline)
1314
 
        
1315
 
        # Parse and act on command
1316
 
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1317
 
        
1318
 
        if cmd == u"NOTFOUND":
1319
 
            fpr, address = args.split(None, 1)
1320
 
            logger.warning(u"Client not found for fingerprint: %s, ad"
1321
 
                           u"dress: %s", fpr, address)
1322
 
            if self.use_dbus:
1323
 
                # Emit D-Bus signal
1324
 
                mandos_dbus_service.ClientNotFound(fpr, address)
1325
 
        elif cmd == u"DISABLED":
1326
 
            for client in self.clients:
1327
 
                if client.name == args:
1328
 
                    logger.warning(u"Client %s is disabled", args)
1329
 
                    if self.use_dbus:
1330
 
                        # Emit D-Bus signal
1331
 
                        client.Rejected()
1332
 
                    break
1333
 
            else:
1334
 
                logger.error(u"Unknown client %s is disabled", args)
1335
 
        elif cmd == u"SENDING":
1336
 
            for client in self.clients:
1337
 
                if client.name == args:
1338
 
                    logger.info(u"Sending secret to %s", client.name)
1339
 
                    client.checked_ok()
1340
 
                    if self.use_dbus:
1341
 
                        # Emit D-Bus signal
1342
 
                        client.GotSecret()
1343
 
                    break
1344
 
            else:
1345
 
                logger.error(u"Sending secret to unknown client %s",
1346
 
                             args)
1347
 
        elif cmd == u"GETATTR":
1348
 
            attr_name, fpr = args.split(None, 1)
1349
 
            for client in self.clients:
1350
 
                if client.fingerprint == fpr:
1351
 
                    attr_value = getattr(client, attr_name, None)
1352
 
                    logger.debug("IPC reply: %r", attr_value)
1353
 
                    pickle.dump(attr_value, reply)
1354
 
                    break
1355
 
            else:
1356
 
                logger.error(u"Client %s on address %s requesting "
1357
 
                             u"attribute %s not found", fpr, address,
1358
 
                             attr_name)
1359
 
                pickle.dump(None, reply)
1360
 
        else:
1361
 
            logger.error(u"Unknown IPC command: %r", cmdline)
1362
 
        
1363
 
        # Keep calling this function
 
1492
 
 
1493
        # error or the other end of multiprocessing.Pipe has closed
 
1494
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
 
1495
            return False
 
1496
        
 
1497
        # Read a request from the child
 
1498
        request = parent_pipe.recv()
 
1499
        logger.debug(u"IPC request: %s", repr(request))
 
1500
        command = request[0]
 
1501
        
 
1502
        if command == 'init':
 
1503
            fpr = request[1]
 
1504
            address = request[2]
 
1505
            
 
1506
            for c in self.clients:
 
1507
                if c.fingerprint == fpr:
 
1508
                    client = c
 
1509
                    break
 
1510
            else:
 
1511
                logger.warning(u"Client not found for fingerprint: %s, ad"
 
1512
                               u"dress: %s", fpr, address)
 
1513
                if self.use_dbus:
 
1514
                    # Emit D-Bus signal
 
1515
                    mandos_dbus_service.ClientNotFound(fpr, address)
 
1516
                parent_pipe.send(False)
 
1517
                return False
 
1518
            
 
1519
            gobject.io_add_watch(parent_pipe.fileno(),
 
1520
                                 gobject.IO_IN | gobject.IO_HUP,
 
1521
                                 functools.partial(self.handle_ipc,
 
1522
                                                   parent_pipe = parent_pipe,
 
1523
                                                   client_object = client))
 
1524
            parent_pipe.send(True)
 
1525
            # remove the old hook in favor of the new above hook on same fileno
 
1526
            return False
 
1527
        if command == 'funcall':
 
1528
            funcname = request[1]
 
1529
            args = request[2]
 
1530
            kwargs = request[3]
 
1531
            
 
1532
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
 
1533
 
 
1534
        if command == 'getattr':
 
1535
            attrname = request[1]
 
1536
            if callable(client_object.__getattribute__(attrname)):
 
1537
                parent_pipe.send(('function',))
 
1538
            else:
 
1539
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
 
1540
        
 
1541
        if command == 'setattr':
 
1542
            attrname = request[1]
 
1543
            value = request[2]
 
1544
            setattr(client_object, attrname, value)
 
1545
 
1364
1546
        return True
1365
1547
 
1366
1548
 
1469
1651
    parser.add_option("--debug", action=u"store_true",
1470
1652
                      help=u"Debug mode; run in foreground and log to"
1471
1653
                      u" terminal")
 
1654
    parser.add_option("--debuglevel", type=u"string", metavar="Level",
 
1655
                      help=u"Debug level for stdout output")
1472
1656
    parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1473
1657
                      u" priority string (see GnuTLS documentation)")
1474
1658
    parser.add_option("--servicename", type=u"string",
1499
1683
                        u"servicename": u"Mandos",
1500
1684
                        u"use_dbus": u"True",
1501
1685
                        u"use_ipv6": u"True",
 
1686
                        u"debuglevel": u"",
1502
1687
                        }
1503
1688
    
1504
1689
    # Parse config file for server-global settings
1521
1706
    # options, if set.
1522
1707
    for option in (u"interface", u"address", u"port", u"debug",
1523
1708
                   u"priority", u"servicename", u"configdir",
1524
 
                   u"use_dbus", u"use_ipv6"):
 
1709
                   u"use_dbus", u"use_ipv6", u"debuglevel"):
1525
1710
        value = getattr(options, option)
1526
1711
        if value is not None:
1527
1712
            server_settings[option] = value
1536
1721
    
1537
1722
    # For convenience
1538
1723
    debug = server_settings[u"debug"]
 
1724
    debuglevel = server_settings[u"debuglevel"]
1539
1725
    use_dbus = server_settings[u"use_dbus"]
1540
1726
    use_ipv6 = server_settings[u"use_ipv6"]
1541
 
    
1542
 
    if not debug:
1543
 
        syslogger.setLevel(logging.WARNING)
1544
 
        console.setLevel(logging.WARNING)
1545
 
    
 
1727
 
1546
1728
    if server_settings[u"servicename"] != u"Mandos":
1547
1729
        syslogger.setFormatter(logging.Formatter
1548
1730
                               (u'Mandos (%s) [%%(process)d]:'
1554
1736
                        u"interval": u"5m",
1555
1737
                        u"checker": u"fping -q -- %%(host)s",
1556
1738
                        u"host": u"",
 
1739
                        u"approval_delay": u"0s",
 
1740
                        u"approval_duration": u"1s",
1557
1741
                        }
1558
1742
    client_config = configparser.SafeConfigParser(client_defaults)
1559
1743
    client_config.read(os.path.join(server_settings[u"configdir"],
1565
1749
    tcp_server = MandosServer((server_settings[u"address"],
1566
1750
                               server_settings[u"port"]),
1567
1751
                              ClientHandler,
1568
 
                              interface=server_settings[u"interface"],
 
1752
                              interface=(server_settings[u"interface"]
 
1753
                                         or None),
1569
1754
                              use_ipv6=use_ipv6,
1570
1755
                              gnutls_priority=
1571
1756
                              server_settings[u"priority"],
1597
1782
        if error[0] != errno.EPERM:
1598
1783
            raise error
1599
1784
    
1600
 
    # Enable all possible GnuTLS debugging
 
1785
    if not debug and not debuglevel:
 
1786
        syslogger.setLevel(logging.WARNING)
 
1787
        console.setLevel(logging.WARNING)
 
1788
    if debuglevel:
 
1789
        level = getattr(logging, debuglevel.upper())
 
1790
        syslogger.setLevel(level)
 
1791
        console.setLevel(level)
 
1792
 
1601
1793
    if debug:
 
1794
        # Enable all possible GnuTLS debugging
 
1795
        
1602
1796
        # "Use a log level over 10 to enable all debugging options."
1603
1797
        # - GnuTLS manual
1604
1798
        gnutls.library.functions.gnutls_global_set_log_level(11)
1609
1803
        
1610
1804
        (gnutls.library.functions
1611
1805
         .gnutls_global_set_log_function(debug_gnutls))
 
1806
        
 
1807
        # Redirect stdin so all checkers get /dev/null
 
1808
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
1809
        os.dup2(null, sys.stdin.fileno())
 
1810
        if null > 2:
 
1811
            os.close(null)
 
1812
    else:
 
1813
        # No console logging
 
1814
        logger.removeHandler(console)
 
1815
    
1612
1816
    
1613
1817
    global main_loop
1614
1818
    # From the Avahi example code
1632
1836
    if server_settings["interface"]:
1633
1837
        service.interface = (if_nametoindex
1634
1838
                             (str(server_settings[u"interface"])))
 
1839
 
 
1840
    if not debug:
 
1841
        # Close all input and output, do double fork, etc.
 
1842
        daemon()
 
1843
        
 
1844
    global multiprocessing_manager
 
1845
    multiprocessing_manager = multiprocessing.Manager()
1635
1846
    
1636
1847
    client_class = Client
1637
1848
    if use_dbus:
1638
1849
        client_class = functools.partial(ClientDBus, bus = bus)
 
1850
    def client_config_items(config, section):
 
1851
        special_settings = {
 
1852
            "approved_by_default":
 
1853
                lambda: config.getboolean(section,
 
1854
                                          "approved_by_default"),
 
1855
            }
 
1856
        for name, value in config.items(section):
 
1857
            try:
 
1858
                yield (name, special_settings[name]())
 
1859
            except KeyError:
 
1860
                yield (name, value)
 
1861
    
1639
1862
    tcp_server.clients.update(set(
1640
1863
            client_class(name = section,
1641
 
                         config= dict(client_config.items(section)))
 
1864
                         config= dict(client_config_items(
 
1865
                        client_config, section)))
1642
1866
            for section in client_config.sections()))
1643
1867
    if not tcp_server.clients:
1644
1868
        logger.warning(u"No clients defined")
1645
 
    
1646
 
    if debug:
1647
 
        # Redirect stdin so all checkers get /dev/null
1648
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1649
 
        os.dup2(null, sys.stdin.fileno())
1650
 
        if null > 2:
1651
 
            os.close(null)
1652
 
    else:
1653
 
        # No console logging
1654
 
        logger.removeHandler(console)
1655
 
        # Close all input and output, do double fork, etc.
1656
 
        daemon()
1657
 
    
 
1869
        
1658
1870
    try:
1659
1871
        with pidfile:
1660
1872
            pid = os.getpid()