/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

  • Committer: Björn Påhlsson
  • Date: 2010-09-07 18:48:56 UTC
  • mto: (237.7.1 mandos)
  • mto: This revision was merged to the branch mainline in revision 270.
  • Revision ID: belorn@fukt.bsnet.se-20100907184856-waz6cvxbm7ranha2
added the actually plugin file for plymouth

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
81
83
 
82
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,
154
157
                            u" after %i retries, exiting.",
155
158
                            self.rename_count)
156
159
            raise AvahiServiceError(u"Too many renames")
157
 
        self.name = self.server.GetAlternativeServiceName(self.name)
 
160
        self.name = unicode(self.server.GetAlternativeServiceName(self.name))
158
161
        logger.info(u"Changing Zeroconf service name to %r ...",
159
 
                    unicode(self.name))
 
162
                    self.name)
160
163
        syslogger.setFormatter(logging.Formatter
161
164
                               (u'Mandos (%s) [%%(process)d]:'
162
165
                                u' %%(levelname)s: %%(message)s'
191
194
        self.group.Commit()
192
195
    def entry_group_state_changed(self, state, error):
193
196
        """Derived from the Avahi example code"""
194
 
        logger.debug(u"Avahi state change: %i", state)
 
197
        logger.debug(u"Avahi entry group state change: %i", state)
195
198
        
196
199
        if state == avahi.ENTRY_GROUP_ESTABLISHED:
197
200
            logger.debug(u"Zeroconf service established.")
210
213
            self.group = None
211
214
    def server_state_changed(self, state):
212
215
        """Derived from the Avahi example code"""
 
216
        logger.debug(u"Avahi server state change: %i", state)
213
217
        if state == avahi.SERVER_COLLISION:
214
218
            logger.error(u"Zeroconf server name collision")
215
219
            self.remove()
242
246
    enabled:    bool()
243
247
    last_checked_ok: datetime.datetime(); (UTC) or None
244
248
    timeout:    datetime.timedelta(); How long from last_checked_ok
245
 
                                      until this client is invalid
 
249
                                      until this client is disabled
246
250
    interval:   datetime.timedelta(); How often to start a new checker
247
251
    disable_hook:  If set, called by disable() as disable_hook(self)
248
252
    checker:    subprocess.Popen(); a running checker process used
256
260
                     runtime with vars(self) as dict, so that for
257
261
                     instance %(name)s can be used in the command.
258
262
    current_checker_command: string; current running checker_command
 
263
    approved_delay: datetime.timedelta(); Time to wait for approval
 
264
    _approved:   bool(); 'None' if not yet approved/disapproved
 
265
    approved_duration: datetime.timedelta(); Duration of one approval
259
266
    """
260
267
    
261
268
    @staticmethod
272
279
    def interval_milliseconds(self):
273
280
        "Return the 'interval' attribute in milliseconds"
274
281
        return self._timedelta_to_milliseconds(self.interval)
 
282
 
 
283
    def approved_delay_milliseconds(self):
 
284
        return self._timedelta_to_milliseconds(self.approved_delay)
275
285
    
276
286
    def __init__(self, name = None, disable_hook=None, config=None):
277
287
        """Note: the 'checker' key in 'config' sets the
290
300
        if u"secret" in config:
291
301
            self.secret = config[u"secret"].decode(u"base64")
292
302
        elif u"secfile" in config:
293
 
            with closing(open(os.path.expanduser
294
 
                              (os.path.expandvars
295
 
                               (config[u"secfile"])),
296
 
                              "rb")) as secfile:
 
303
            with open(os.path.expanduser(os.path.expandvars
 
304
                                         (config[u"secfile"])),
 
305
                      "rb") as secfile:
297
306
                self.secret = secfile.read()
298
307
        else:
299
308
            raise TypeError(u"No secret or secfile for client %s"
313
322
        self.checker_command = config[u"checker"]
314
323
        self.current_checker_command = None
315
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())
316
334
    
 
335
    def send_changedstate(self):
 
336
        self.changedstate.acquire()
 
337
        self.changedstate.notify_all()
 
338
        self.changedstate.release()
 
339
        
317
340
    def enable(self):
318
341
        """Start this client's checker and timeout hooks"""
319
342
        if getattr(self, u"enabled", False):
320
343
            # Already enabled
321
344
            return
 
345
        self.send_changedstate()
322
346
        self.last_enabled = datetime.datetime.utcnow()
323
347
        # Schedule a new checker to be started an 'interval' from now,
324
348
        # and every interval from then on.
338
362
        if not getattr(self, "enabled", False):
339
363
            return False
340
364
        if not quiet:
 
365
            self.send_changedstate()
 
366
        if not quiet:
341
367
            logger.info(u"Disabling client %s", self.name)
342
368
        if getattr(self, u"disable_initiator_tag", False):
343
369
            gobject.source_remove(self.disable_initiator_tag)
396
422
        # client would inevitably timeout, since no checker would get
397
423
        # a chance to run to completion.  If we instead leave running
398
424
        # checkers alone, the checker would have to take more time
399
 
        # than 'timeout' for the client to be declared invalid, which
400
 
        # is as it should be.
 
425
        # than 'timeout' for the client to be disabled, which is as it
 
426
        # should be.
401
427
        
402
428
        # If a checker exists, make sure it is not a zombie
403
429
        try:
475
501
            if error.errno != errno.ESRCH: # No such process
476
502
                raise
477
503
        self.checker = None
478
 
    
479
 
    def still_valid(self):
480
 
        """Has the timeout not yet passed for this client?"""
481
 
        if not getattr(self, u"enabled", False):
482
 
            return False
483
 
        now = datetime.datetime.utcnow()
484
 
        if self.last_checked_ok is None:
485
 
            return now < (self.created + self.timeout)
486
 
        else:
487
 
            return now < (self.last_checked_ok + self.timeout)
488
 
 
489
504
 
490
505
def dbus_service_property(dbus_interface, signature=u"v",
491
506
                          access=u"readwrite", byte_arrays=False):
499
514
    dbus.service.method, except there is only "signature", since the
500
515
    type from Get() and the type sent to Set() is the same.
501
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)
502
522
    def decorator(func):
503
523
        func._dbus_is_property = True
504
524
        func._dbus_interface = dbus_interface
590
610
        if prop._dbus_access == u"read":
591
611
            raise DBusPropertyAccessException(property_name)
592
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
593
617
            value = dbus.ByteArray(''.join(unichr(byte)
594
618
                                           for byte in value))
595
619
        prop(value)
677
701
    # dbus.service.Object doesn't use super(), so we can't either.
678
702
    
679
703
    def __init__(self, bus = None, *args, **kwargs):
 
704
        self._approvals_pending = 0
680
705
        self.bus = bus
681
706
        Client.__init__(self, *args, **kwargs)
682
707
        # Only now, when this client is initialized, can it show up on
686
711
                                  + self.name.replace(u".", u"_")))
687
712
        DBusObjectWithProperties.__init__(self, self.bus,
688
713
                                          self.dbus_object_path)
 
714
 
 
715
    def _get_approvals_pending(self):
 
716
        return self._approvals_pending
 
717
    def _set_approvals_pending(self, value):
 
718
        old_value = self._approvals_pending
 
719
        self._approvals_pending = value
 
720
        bval = bool(value)
 
721
        if (hasattr(self, "dbus_object_path")
 
722
            and bval is not bool(old_value)):
 
723
            dbus_bool = dbus.Boolean(bval, variant_level=1)
 
724
            self.PropertyChanged(dbus.String(u"approved_pending"),
 
725
                                 dbus_bool)
 
726
 
 
727
    approvals_pending = property(_get_approvals_pending,
 
728
                                 _set_approvals_pending)
 
729
    del _get_approvals_pending, _set_approvals_pending
689
730
    
690
731
    @staticmethod
691
732
    def _datetime_to_dbus(dt, variant_level=0):
780
821
            self.PropertyChanged(dbus.String(u"checker_running"),
781
822
                                 dbus.Boolean(False, variant_level=1))
782
823
        return r
783
 
    
784
 
    ## D-Bus methods & signals
 
824
 
 
825
    def _reset_approved(self):
 
826
        self._approved = None
 
827
        return False
 
828
    
 
829
    def approve(self, value=True):
 
830
        self.send_changedstate()
 
831
        self._approved = value
 
832
        gobject.timeout_add(self._timedelta_to_milliseconds(self.approved_duration),
 
833
                            self._reset_approved)
 
834
    
 
835
    
 
836
    ## D-Bus methods, signals & properties
785
837
    _interface = u"se.bsnet.fukt.Mandos.Client"
786
838
    
787
 
    # CheckedOK - method
788
 
    @dbus.service.method(_interface)
789
 
    def CheckedOK(self):
790
 
        return self.checked_ok()
 
839
    ## Signals
791
840
    
792
841
    # CheckerCompleted - signal
793
842
    @dbus.service.signal(_interface, signature=u"nxs")
810
859
    # GotSecret - signal
811
860
    @dbus.service.signal(_interface)
812
861
    def GotSecret(self):
813
 
        "D-Bus signal"
 
862
        """D-Bus signal
 
863
        Is sent after a successful transfer of secret from the Mandos
 
864
        server to mandos-client
 
865
        """
814
866
        pass
815
867
    
816
868
    # Rejected - signal
817
 
    @dbus.service.signal(_interface)
818
 
    def Rejected(self):
819
 
        "D-Bus signal"
820
 
        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()
821
891
    
822
892
    # Enable - method
823
893
    @dbus.service.method(_interface)
842
912
    def StopChecker(self):
843
913
        self.stop_checker()
844
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
    
845
941
    # name - property
846
942
    @dbus_service_property(_interface, signature=u"s", access=u"read")
847
943
    def name_dbus_property(self):
981
1077
    del _interface
982
1078
 
983
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
 
984
1106
class ClientHandler(socketserver.BaseRequestHandler, object):
985
1107
    """A class to handle client connections.
986
1108
    
988
1110
    Note: This will run in its own forked process."""
989
1111
    
990
1112
    def handle(self):
991
 
        logger.info(u"TCP connection from: %s",
992
 
                    unicode(self.client_address))
993
 
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
994
 
        # Open IPC pipe to parent process
995
 
        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
 
996
1119
            session = (gnutls.connection
997
1120
                       .ClientSession(self.request,
998
1121
                                      gnutls.connection
999
1122
                                      .X509Credentials()))
1000
 
            
1001
 
            line = self.request.makefile().readline()
1002
 
            logger.debug(u"Protocol version: %r", line)
1003
 
            try:
1004
 
                if int(line.strip().split()[0]) > 1:
1005
 
                    raise RuntimeError
1006
 
            except (ValueError, IndexError, RuntimeError), error:
1007
 
                logger.error(u"Unknown protocol version: %s", error)
1008
 
                return
1009
 
            
 
1123
 
1010
1124
            # Note: gnutls.connection.X509Credentials is really a
1011
1125
            # generic GnuTLS certificate credentials object so long as
1012
1126
            # no X.509 keys are added to it.  Therefore, we can use it
1013
1127
            # here despite using OpenPGP certificates.
1014
 
            
 
1128
 
1015
1129
            #priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1016
1130
            #                      u"+AES-256-CBC", u"+SHA1",
1017
1131
            #                      u"+COMP-NULL", u"+CTYPE-OPENPGP",
1023
1137
            (gnutls.library.functions
1024
1138
             .gnutls_priority_set_direct(session._c_object,
1025
1139
                                         priority, None))
1026
 
            
 
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
1027
1153
            try:
1028
1154
                session.handshake()
1029
1155
            except gnutls.errors.GNUTLSError, error:
1032
1158
                # established.  Just abandon the request.
1033
1159
                return
1034
1160
            logger.debug(u"Handshake succeeded")
 
1161
 
 
1162
            approval_required = False
1035
1163
            try:
1036
 
                fpr = self.fingerprint(self.peer_certificate(session))
1037
 
            except (TypeError, gnutls.errors.GNUTLSError), error:
1038
 
                logger.warning(u"Bad certificate: %s", error)
1039
 
                session.bye()
1040
 
                return
1041
 
            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
                    try:
 
1235
                        sent = session.send(client.secret[sent_size:])
 
1236
                    except (gnutls.errors.GNUTLSError), error:
 
1237
                        logger.warning("gnutls send failed")
 
1238
                        return
 
1239
                    logger.debug(u"Sent: %d, remaining: %d",
 
1240
                                 sent, len(client.secret)
 
1241
                                 - (sent_size + sent))
 
1242
                    sent_size += sent
 
1243
 
 
1244
                logger.info(u"Sending secret to %s", client.name)
 
1245
                # bump the timeout as if seen
 
1246
                client.checked_ok()
 
1247
                if self.server.use_dbus:
 
1248
                    # Emit D-Bus signal
 
1249
                    client.GotSecret()
1042
1250
            
1043
 
            for c in self.server.clients:
1044
 
                if c.fingerprint == fpr:
1045
 
                    client = c
1046
 
                    break
1047
 
            else:
1048
 
                ipc.write(u"NOTFOUND %s %s\n"
1049
 
                          % (fpr, unicode(self.client_address)))
1050
 
                session.bye()
1051
 
                return
1052
 
            # Have to check if client.still_valid(), since it is
1053
 
            # possible that the client timed out while establishing
1054
 
            # the GnuTLS session.
1055
 
            if not client.still_valid():
1056
 
                ipc.write(u"INVALID %s\n" % client.name)
1057
 
                session.bye()
1058
 
                return
1059
 
            ipc.write(u"SENDING %s\n" % client.name)
1060
 
            sent_size = 0
1061
 
            while sent_size < len(client.secret):
1062
 
                sent = session.send(client.secret[sent_size:])
1063
 
                logger.debug(u"Sent: %d, remaining: %d",
1064
 
                             sent, len(client.secret)
1065
 
                             - (sent_size + sent))
1066
 
                sent_size += sent
1067
 
            session.bye()
 
1251
            finally:
 
1252
                if approval_required:
 
1253
                    client.approvals_pending -= 1
 
1254
                try:
 
1255
                    session.bye()
 
1256
                except (gnutls.errors.GNUTLSError), error:
 
1257
                    logger.warning("gnutls bye failed")
1068
1258
    
1069
1259
    @staticmethod
1070
1260
    def peer_certificate(session):
1130
1320
        return hex_fpr
1131
1321
 
1132
1322
 
1133
 
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1134
 
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
 
1323
class MultiprocessingMixIn(object):
 
1324
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
 
1325
    def sub_process_main(self, request, address):
 
1326
        try:
 
1327
            self.finish_request(request, address)
 
1328
        except:
 
1329
            self.handle_error(request, address)
 
1330
        self.close_request(request)
 
1331
            
 
1332
    def process_request(self, request, address):
 
1333
        """Start a new process to process the request."""
 
1334
        multiprocessing.Process(target = self.sub_process_main,
 
1335
                                args = (request, address)).start()
 
1336
 
 
1337
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
 
1338
    """ adds a pipe to the MixIn """
1135
1339
    def process_request(self, request, client_address):
1136
1340
        """Overrides and wraps the original process_request().
1137
1341
        
1138
1342
        This function creates a new pipe in self.pipe
1139
1343
        """
1140
 
        self.pipe = os.pipe()
1141
 
        super(ForkingMixInWithPipe,
 
1344
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
 
1345
 
 
1346
        super(MultiprocessingMixInWithPipe,
1142
1347
              self).process_request(request, client_address)
1143
 
        os.close(self.pipe[1])  # close write end
1144
 
        self.add_pipe(self.pipe[0])
1145
 
    def add_pipe(self, pipe):
 
1348
        self.child_pipe.close()
 
1349
        self.add_pipe(parent_pipe)
 
1350
 
 
1351
    def add_pipe(self, parent_pipe):
1146
1352
        """Dummy function; override as necessary"""
1147
 
        os.close(pipe)
1148
 
 
1149
 
 
1150
 
class IPv6_TCPServer(ForkingMixInWithPipe,
 
1353
        pass
 
1354
 
 
1355
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1151
1356
                     socketserver.TCPServer, object):
1152
1357
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1153
1358
    
1238
1443
            return socketserver.TCPServer.server_activate(self)
1239
1444
    def enable(self):
1240
1445
        self.enabled = True
1241
 
    def add_pipe(self, pipe):
 
1446
    def add_pipe(self, parent_pipe):
1242
1447
        # Call "handle_ipc" for both data and EOF events
1243
 
        gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1244
 
                             self.handle_ipc)
1245
 
    def handle_ipc(self, source, condition, file_objects={}):
 
1448
        gobject.io_add_watch(parent_pipe.fileno(),
 
1449
                             gobject.IO_IN | gobject.IO_HUP,
 
1450
                             functools.partial(self.handle_ipc,
 
1451
                                               parent_pipe = parent_pipe))
 
1452
        
 
1453
    def handle_ipc(self, source, condition, parent_pipe=None,
 
1454
                   client_object=None):
1246
1455
        condition_names = {
1247
1456
            gobject.IO_IN: u"IN",   # There is data to read.
1248
1457
            gobject.IO_OUT: u"OUT", # Data can be written (without
1259
1468
                                       if cond & condition)
1260
1469
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1261
1470
                     conditions_string)
1262
 
        
1263
 
        # Turn the pipe file descriptor into a Python file object
1264
 
        if source not in file_objects:
1265
 
            file_objects[source] = os.fdopen(source, u"r", 1)
1266
 
        
1267
 
        # Read a line from the file object
1268
 
        cmdline = file_objects[source].readline()
1269
 
        if not cmdline:             # Empty line means end of file
1270
 
            # close the IPC pipe
1271
 
            file_objects[source].close()
1272
 
            del file_objects[source]
1273
 
            
1274
 
            # Stop calling this function
1275
 
            return False
1276
 
        
1277
 
        logger.debug(u"IPC command: %r", cmdline)
1278
 
        
1279
 
        # Parse and act on command
1280
 
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1281
 
        
1282
 
        if cmd == u"NOTFOUND":
1283
 
            logger.warning(u"Client not found for fingerprint: %s",
1284
 
                           args)
1285
 
            if self.use_dbus:
1286
 
                # Emit D-Bus signal
1287
 
                mandos_dbus_service.ClientNotFound(args)
1288
 
        elif cmd == u"INVALID":
1289
 
            for client in self.clients:
1290
 
                if client.name == args:
1291
 
                    logger.warning(u"Client %s is invalid", args)
1292
 
                    if self.use_dbus:
1293
 
                        # Emit D-Bus signal
1294
 
                        client.Rejected()
1295
 
                    break
1296
 
            else:
1297
 
                logger.error(u"Unknown client %s is invalid", args)
1298
 
        elif cmd == u"SENDING":
1299
 
            for client in self.clients:
1300
 
                if client.name == args:
1301
 
                    logger.info(u"Sending secret to %s", client.name)
1302
 
                    client.checked_ok()
1303
 
                    if self.use_dbus:
1304
 
                        # Emit D-Bus signal
1305
 
                        client.GotSecret()
1306
 
                    break
1307
 
            else:
1308
 
                logger.error(u"Sending secret to unknown client %s",
1309
 
                             args)
1310
 
        else:
1311
 
            logger.error(u"Unknown IPC command: %r", cmdline)
1312
 
        
1313
 
        # Keep calling this function
 
1471
 
 
1472
        # error or the other end of multiprocessing.Pipe has closed
 
1473
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
 
1474
            return False
 
1475
        
 
1476
        # Read a request from the child
 
1477
        request = parent_pipe.recv()
 
1478
        logger.debug(u"IPC request: %s", repr(request))
 
1479
        command = request[0]
 
1480
        
 
1481
        if command == 'init':
 
1482
            fpr = request[1]
 
1483
            address = request[2]
 
1484
            
 
1485
            for c in self.clients:
 
1486
                if c.fingerprint == fpr:
 
1487
                    client = c
 
1488
                    break
 
1489
            else:
 
1490
                logger.warning(u"Client not found for fingerprint: %s, ad"
 
1491
                               u"dress: %s", fpr, address)
 
1492
                if self.use_dbus:
 
1493
                    # Emit D-Bus signal
 
1494
                    mandos_dbus_service.ClientNotFound(fpr, address)
 
1495
                parent_pipe.send(False)
 
1496
                return False
 
1497
            
 
1498
            gobject.io_add_watch(parent_pipe.fileno(),
 
1499
                                 gobject.IO_IN | gobject.IO_HUP,
 
1500
                                 functools.partial(self.handle_ipc,
 
1501
                                                   parent_pipe = parent_pipe,
 
1502
                                                   client_object = client))
 
1503
            parent_pipe.send(True)
 
1504
            # remove the old hook in favor of the new above hook on same fileno
 
1505
            return False
 
1506
        if command == 'funcall':
 
1507
            funcname = request[1]
 
1508
            args = request[2]
 
1509
            kwargs = request[3]
 
1510
            
 
1511
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
 
1512
 
 
1513
        if command == 'getattr':
 
1514
            attrname = request[1]
 
1515
            if callable(client_object.__getattribute__(attrname)):
 
1516
                parent_pipe.send(('function',))
 
1517
            else:
 
1518
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
 
1519
        
 
1520
        if command == 'setattr':
 
1521
            attrname = request[1]
 
1522
            value = request[2]
 
1523
            setattr(client_object, attrname, value)
 
1524
 
1314
1525
        return True
1315
1526
 
1316
1527
 
1367
1578
        def if_nametoindex(interface):
1368
1579
            "Get an interface index the hard way, i.e. using fcntl()"
1369
1580
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1370
 
            with closing(socket.socket()) as s:
 
1581
            with contextlib.closing(socket.socket()) as s:
1371
1582
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1372
1583
                                    struct.pack(str(u"16s16x"),
1373
1584
                                                interface))
1419
1630
    parser.add_option("--debug", action=u"store_true",
1420
1631
                      help=u"Debug mode; run in foreground and log to"
1421
1632
                      u" terminal")
 
1633
    parser.add_option("--debuglevel", type=u"string", metavar="Level",
 
1634
                      help=u"Debug level for stdout output")
1422
1635
    parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1423
1636
                      u" priority string (see GnuTLS documentation)")
1424
1637
    parser.add_option("--servicename", type=u"string",
1449
1662
                        u"servicename": u"Mandos",
1450
1663
                        u"use_dbus": u"True",
1451
1664
                        u"use_ipv6": u"True",
 
1665
                        u"debuglevel": u"",
1452
1666
                        }
1453
1667
    
1454
1668
    # Parse config file for server-global settings
1471
1685
    # options, if set.
1472
1686
    for option in (u"interface", u"address", u"port", u"debug",
1473
1687
                   u"priority", u"servicename", u"configdir",
1474
 
                   u"use_dbus", u"use_ipv6"):
 
1688
                   u"use_dbus", u"use_ipv6", u"debuglevel"):
1475
1689
        value = getattr(options, option)
1476
1690
        if value is not None:
1477
1691
            server_settings[option] = value
1486
1700
    
1487
1701
    # For convenience
1488
1702
    debug = server_settings[u"debug"]
 
1703
    debuglevel = server_settings[u"debuglevel"]
1489
1704
    use_dbus = server_settings[u"use_dbus"]
1490
1705
    use_ipv6 = server_settings[u"use_ipv6"]
1491
 
    
1492
 
    if not debug:
1493
 
        syslogger.setLevel(logging.WARNING)
1494
 
        console.setLevel(logging.WARNING)
1495
 
    
 
1706
 
1496
1707
    if server_settings[u"servicename"] != u"Mandos":
1497
1708
        syslogger.setFormatter(logging.Formatter
1498
1709
                               (u'Mandos (%s) [%%(process)d]:'
1504
1715
                        u"interval": u"5m",
1505
1716
                        u"checker": u"fping -q -- %%(host)s",
1506
1717
                        u"host": u"",
 
1718
                        u"approved_delay": u"0s",
 
1719
                        u"approved_duration": u"1s",
1507
1720
                        }
1508
1721
    client_config = configparser.SafeConfigParser(client_defaults)
1509
1722
    client_config.read(os.path.join(server_settings[u"configdir"],
1548
1761
            raise error
1549
1762
    
1550
1763
    # Enable all possible GnuTLS debugging
 
1764
 
 
1765
 
 
1766
    if not debug and not debuglevel:
 
1767
        syslogger.setLevel(logging.WARNING)
 
1768
        console.setLevel(logging.WARNING)
 
1769
    if debuglevel:
 
1770
        level = getattr(logging, debuglevel.upper())
 
1771
        syslogger.setLevel(level)
 
1772
        console.setLevel(level)
 
1773
 
1551
1774
    if debug:
1552
1775
        # "Use a log level over 10 to enable all debugging options."
1553
1776
        # - GnuTLS manual
1559
1782
        
1560
1783
        (gnutls.library.functions
1561
1784
         .gnutls_global_set_log_function(debug_gnutls))
 
1785
 
 
1786
        # Redirect stdin so all checkers get /dev/null
 
1787
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
1788
        os.dup2(null, sys.stdin.fileno())
 
1789
        if null > 2:
 
1790
            os.close(null)
 
1791
    else:
 
1792
        # No console logging
 
1793
        logger.removeHandler(console)
 
1794
 
1562
1795
    
1563
1796
    global main_loop
1564
1797
    # From the Avahi example code
1582
1815
    if server_settings["interface"]:
1583
1816
        service.interface = (if_nametoindex
1584
1817
                             (str(server_settings[u"interface"])))
 
1818
 
 
1819
    if not debug:
 
1820
        # Close all input and output, do double fork, etc.
 
1821
        daemon()
 
1822
        
 
1823
    global multiprocessing_manager
 
1824
    multiprocessing_manager = multiprocessing.Manager()
1585
1825
    
1586
1826
    client_class = Client
1587
1827
    if use_dbus:
1588
1828
        client_class = functools.partial(ClientDBus, bus = bus)
 
1829
    def client_config_items(config, section):
 
1830
        special_settings = {
 
1831
            "approved_by_default":
 
1832
                lambda: config.getboolean(section,
 
1833
                                          "approved_by_default"),
 
1834
            }
 
1835
        for name, value in config.items(section):
 
1836
            try:
 
1837
                yield (name, special_settings[name]())
 
1838
            except KeyError:
 
1839
                yield (name, value)
 
1840
    
1589
1841
    tcp_server.clients.update(set(
1590
1842
            client_class(name = section,
1591
 
                         config= dict(client_config.items(section)))
 
1843
                         config= dict(client_config_items(
 
1844
                        client_config, section)))
1592
1845
            for section in client_config.sections()))
1593
1846
    if not tcp_server.clients:
1594
1847
        logger.warning(u"No clients defined")
1595
 
    
1596
 
    if debug:
1597
 
        # Redirect stdin so all checkers get /dev/null
1598
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1599
 
        os.dup2(null, sys.stdin.fileno())
1600
 
        if null > 2:
1601
 
            os.close(null)
1602
 
    else:
1603
 
        # No console logging
1604
 
        logger.removeHandler(console)
1605
 
        # Close all input and output, do double fork, etc.
1606
 
        daemon()
1607
 
    
 
1848
        
1608
1849
    try:
1609
 
        with closing(pidfile):
 
1850
        with pidfile:
1610
1851
            pid = os.getpid()
1611
1852
            pidfile.write(str(pid) + "\n")
1612
1853
        del pidfile
1630
1871
                dbus.service.Object.__init__(self, bus, u"/")
1631
1872
            _interface = u"se.bsnet.fukt.Mandos"
1632
1873
            
1633
 
            @dbus.service.signal(_interface, signature=u"oa{sv}")
1634
 
            def ClientAdded(self, objpath, properties):
 
1874
            @dbus.service.signal(_interface, signature=u"o")
 
1875
            def ClientAdded(self, objpath):
1635
1876
                "D-Bus signal"
1636
1877
                pass
1637
1878
            
1638
 
            @dbus.service.signal(_interface, signature=u"s")
1639
 
            def ClientNotFound(self, fingerprint):
 
1879
            @dbus.service.signal(_interface, signature=u"ss")
 
1880
            def ClientNotFound(self, fingerprint, address):
1640
1881
                "D-Bus signal"
1641
1882
                pass
1642
1883
            
1699
1940
    for client in tcp_server.clients:
1700
1941
        if use_dbus:
1701
1942
            # Emit D-Bus signal
1702
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
1703
 
                                            client.GetAll(u""))
 
1943
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
1704
1944
        client.enable()
1705
1945
    
1706
1946
    tcp_server.enable()