/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Björn Påhlsson
  • Date: 2010-09-12 18:12:11 UTC
  • mto: (237.4.3 mandos-release)
  • mto: This revision was merged to the branch mainline in revision 428.
  • Revision ID: belorn@fukt.bsnet.se-20100912181211-wvkt0sk37zhx7tws
mandos-client: Added never ending loop for --connect
mandos-ctl: Better option parsing.

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'
163
166
                                % self.name))
164
167
        self.remove()
165
 
        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)
166
174
        self.rename_count += 1
167
175
    def remove(self):
168
176
        """Derived from the Avahi example code"""
191
199
        self.group.Commit()
192
200
    def entry_group_state_changed(self, state, error):
193
201
        """Derived from the Avahi example code"""
194
 
        logger.debug(u"Avahi state change: %i", state)
 
202
        logger.debug(u"Avahi entry group state change: %i", state)
195
203
        
196
204
        if state == avahi.ENTRY_GROUP_ESTABLISHED:
197
205
            logger.debug(u"Zeroconf service established.")
210
218
            self.group = None
211
219
    def server_state_changed(self, state):
212
220
        """Derived from the Avahi example code"""
 
221
        logger.debug(u"Avahi server state change: %i", state)
213
222
        if state == avahi.SERVER_COLLISION:
214
223
            logger.error(u"Zeroconf server name collision")
215
224
            self.remove()
242
251
    enabled:    bool()
243
252
    last_checked_ok: datetime.datetime(); (UTC) or None
244
253
    timeout:    datetime.timedelta(); How long from last_checked_ok
245
 
                                      until this client is invalid
 
254
                                      until this client is disabled
246
255
    interval:   datetime.timedelta(); How often to start a new checker
247
256
    disable_hook:  If set, called by disable() as disable_hook(self)
248
257
    checker:    subprocess.Popen(); a running checker process used
256
265
                     runtime with vars(self) as dict, so that for
257
266
                     instance %(name)s can be used in the command.
258
267
    current_checker_command: string; current running checker_command
 
268
    approved_delay: datetime.timedelta(); Time to wait for approval
 
269
    _approved:   bool(); 'None' if not yet approved/disapproved
 
270
    approved_duration: datetime.timedelta(); Duration of one approval
259
271
    """
260
272
    
261
273
    @staticmethod
272
284
    def interval_milliseconds(self):
273
285
        "Return the 'interval' attribute in milliseconds"
274
286
        return self._timedelta_to_milliseconds(self.interval)
 
287
 
 
288
    def approved_delay_milliseconds(self):
 
289
        return self._timedelta_to_milliseconds(self.approved_delay)
275
290
    
276
291
    def __init__(self, name = None, disable_hook=None, config=None):
277
292
        """Note: the 'checker' key in 'config' sets the
290
305
        if u"secret" in config:
291
306
            self.secret = config[u"secret"].decode(u"base64")
292
307
        elif u"secfile" in config:
293
 
            with closing(open(os.path.expanduser
294
 
                              (os.path.expandvars
295
 
                               (config[u"secfile"])),
296
 
                              "rb")) as secfile:
 
308
            with open(os.path.expanduser(os.path.expandvars
 
309
                                         (config[u"secfile"])),
 
310
                      "rb") as secfile:
297
311
                self.secret = secfile.read()
298
312
        else:
299
313
            raise TypeError(u"No secret or secfile for client %s"
313
327
        self.checker_command = config[u"checker"]
314
328
        self.current_checker_command = None
315
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.approved_delay = string_to_delta(
 
335
            config[u"approved_delay"])
 
336
        self.approved_duration = string_to_delta(
 
337
            config[u"approved_duration"])
 
338
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
316
339
    
 
340
    def send_changedstate(self):
 
341
        self.changedstate.acquire()
 
342
        self.changedstate.notify_all()
 
343
        self.changedstate.release()
 
344
        
317
345
    def enable(self):
318
346
        """Start this client's checker and timeout hooks"""
319
347
        if getattr(self, u"enabled", False):
320
348
            # Already enabled
321
349
            return
 
350
        self.send_changedstate()
322
351
        self.last_enabled = datetime.datetime.utcnow()
323
352
        # Schedule a new checker to be started an 'interval' from now,
324
353
        # and every interval from then on.
333
362
        # Also start a new checker *right now*.
334
363
        self.start_checker()
335
364
    
336
 
    def disable(self, log=True):
 
365
    def disable(self, quiet=True):
337
366
        """Disable this client."""
338
367
        if not getattr(self, "enabled", False):
339
368
            return False
340
 
        if log:
 
369
        if not quiet:
 
370
            self.send_changedstate()
 
371
        if not quiet:
341
372
            logger.info(u"Disabling client %s", self.name)
342
373
        if getattr(self, u"disable_initiator_tag", False):
343
374
            gobject.source_remove(self.disable_initiator_tag)
396
427
        # client would inevitably timeout, since no checker would get
397
428
        # a chance to run to completion.  If we instead leave running
398
429
        # 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.
 
430
        # than 'timeout' for the client to be disabled, which is as it
 
431
        # should be.
401
432
        
402
433
        # If a checker exists, make sure it is not a zombie
403
434
        try:
475
506
            if error.errno != errno.ESRCH: # No such process
476
507
                raise
477
508
        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
509
 
490
510
def dbus_service_property(dbus_interface, signature=u"v",
491
511
                          access=u"readwrite", byte_arrays=False):
499
519
    dbus.service.method, except there is only "signature", since the
500
520
    type from Get() and the type sent to Set() is the same.
501
521
    """
 
522
    # Encoding deeply encoded byte arrays is not supported yet by the
 
523
    # "Set" method, so we fail early here:
 
524
    if byte_arrays and signature != u"ay":
 
525
        raise ValueError(u"Byte arrays not supported for non-'ay'"
 
526
                         u" signature %r" % signature)
502
527
    def decorator(func):
503
528
        func._dbus_is_property = True
504
529
        func._dbus_interface = dbus_interface
590
615
        if prop._dbus_access == u"read":
591
616
            raise DBusPropertyAccessException(property_name)
592
617
        if prop._dbus_get_args_options[u"byte_arrays"]:
 
618
            # The byte_arrays option is not supported yet on
 
619
            # signatures other than "ay".
 
620
            if prop._dbus_signature != u"ay":
 
621
                raise ValueError
593
622
            value = dbus.ByteArray(''.join(unichr(byte)
594
623
                                           for byte in value))
595
624
        prop(value)
677
706
    # dbus.service.Object doesn't use super(), so we can't either.
678
707
    
679
708
    def __init__(self, bus = None, *args, **kwargs):
 
709
        self._approvals_pending = 0
680
710
        self.bus = bus
681
711
        Client.__init__(self, *args, **kwargs)
682
712
        # Only now, when this client is initialized, can it show up on
686
716
                                  + self.name.replace(u".", u"_")))
687
717
        DBusObjectWithProperties.__init__(self, self.bus,
688
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"approved_pending"),
 
730
                                 dbus_bool)
 
731
 
 
732
    approvals_pending = property(_get_approvals_pending,
 
733
                                 _set_approvals_pending)
 
734
    del _get_approvals_pending, _set_approvals_pending
689
735
    
690
736
    @staticmethod
691
737
    def _datetime_to_dbus(dt, variant_level=0):
706
752
                                       variant_level=1))
707
753
        return r
708
754
    
709
 
    def disable(self, signal = True):
 
755
    def disable(self, quiet = False):
710
756
        oldstate = getattr(self, u"enabled", False)
711
 
        r = Client.disable(self, log=signal)
712
 
        if signal and oldstate != self.enabled:
 
757
        r = Client.disable(self, quiet=quiet)
 
758
        if not quiet and oldstate != self.enabled:
713
759
            # Emit D-Bus signal
714
760
            self.PropertyChanged(dbus.String(u"enabled"),
715
761
                                 dbus.Boolean(False, variant_level=1))
780
826
            self.PropertyChanged(dbus.String(u"checker_running"),
781
827
                                 dbus.Boolean(False, variant_level=1))
782
828
        return r
783
 
    
784
 
    ## D-Bus methods & signals
 
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(self.approved_duration),
 
838
                            self._reset_approved)
 
839
    
 
840
    
 
841
    ## D-Bus methods, signals & properties
785
842
    _interface = u"se.bsnet.fukt.Mandos.Client"
786
843
    
787
 
    # CheckedOK - method
788
 
    @dbus.service.method(_interface)
789
 
    def CheckedOK(self):
790
 
        return self.checked_ok()
 
844
    ## Signals
791
845
    
792
846
    # CheckerCompleted - signal
793
847
    @dbus.service.signal(_interface, signature=u"nxs")
810
864
    # GotSecret - signal
811
865
    @dbus.service.signal(_interface)
812
866
    def GotSecret(self):
813
 
        "D-Bus signal"
 
867
        """D-Bus signal
 
868
        Is sent after a successful transfer of secret from the Mandos
 
869
        server to mandos-client
 
870
        """
814
871
        pass
815
872
    
816
873
    # Rejected - signal
817
 
    @dbus.service.signal(_interface)
818
 
    def Rejected(self):
819
 
        "D-Bus signal"
820
 
        pass
 
874
    @dbus.service.signal(_interface, signature=u"s")
 
875
    def Rejected(self, reason):
 
876
        "D-Bus signal"
 
877
        pass
 
878
    
 
879
    # NeedApproval - signal
 
880
    @dbus.service.signal(_interface, signature=u"db")
 
881
    def NeedApproval(self, timeout, default):
 
882
        "D-Bus signal"
 
883
        pass
 
884
    
 
885
    ## Methods
 
886
 
 
887
    # Approve - method
 
888
    @dbus.service.method(_interface, in_signature=u"b")
 
889
    def Approve(self, value):
 
890
        self.approve(value)
 
891
 
 
892
    # CheckedOK - method
 
893
    @dbus.service.method(_interface)
 
894
    def CheckedOK(self):
 
895
        return self.checked_ok()
821
896
    
822
897
    # Enable - method
823
898
    @dbus.service.method(_interface)
842
917
    def StopChecker(self):
843
918
        self.stop_checker()
844
919
    
 
920
    ## Properties
 
921
    
 
922
    # approved_pending - property
 
923
    @dbus_service_property(_interface, signature=u"b", access=u"read")
 
924
    def approved_pending_dbus_property(self):
 
925
        return dbus.Boolean(bool(self.approvals_pending))
 
926
    
 
927
    # approved_by_default - property
 
928
    @dbus_service_property(_interface, signature=u"b",
 
929
                           access=u"readwrite")
 
930
    def approved_by_default_dbus_property(self):
 
931
        return dbus.Boolean(self.approved_by_default)
 
932
    
 
933
    # approved_delay - property
 
934
    @dbus_service_property(_interface, signature=u"t",
 
935
                           access=u"readwrite")
 
936
    def approved_delay_dbus_property(self):
 
937
        return dbus.UInt64(self.approved_delay_milliseconds())
 
938
    
 
939
    # approved_duration - property
 
940
    @dbus_service_property(_interface, signature=u"t",
 
941
                           access=u"readwrite")
 
942
    def approved_duration_dbus_property(self):
 
943
        return dbus.UInt64(self._timedelta_to_milliseconds(
 
944
                self.approved_duration))
 
945
    
845
946
    # name - property
846
947
    @dbus_service_property(_interface, signature=u"s", access=u"read")
847
948
    def name_dbus_property(self):
981
1082
    del _interface
982
1083
 
983
1084
 
 
1085
class ProxyClient(object):
 
1086
    def __init__(self, child_pipe, fpr, address):
 
1087
        self._pipe = child_pipe
 
1088
        self._pipe.send(('init', fpr, address))
 
1089
        if not self._pipe.recv():
 
1090
            raise KeyError()
 
1091
 
 
1092
    def __getattribute__(self, name):
 
1093
        if(name == '_pipe'):
 
1094
            return super(ProxyClient, self).__getattribute__(name)
 
1095
        self._pipe.send(('getattr', name))
 
1096
        data = self._pipe.recv()
 
1097
        if data[0] == 'data':
 
1098
            return data[1]
 
1099
        if data[0] == 'function':
 
1100
            def func(*args, **kwargs):
 
1101
                self._pipe.send(('funcall', name, args, kwargs))
 
1102
                return self._pipe.recv()[1]
 
1103
            return func
 
1104
 
 
1105
    def __setattr__(self, name, value):
 
1106
        if(name == '_pipe'):
 
1107
            return super(ProxyClient, self).__setattr__(name, value)
 
1108
        self._pipe.send(('setattr', name, value))
 
1109
 
 
1110
 
984
1111
class ClientHandler(socketserver.BaseRequestHandler, object):
985
1112
    """A class to handle client connections.
986
1113
    
988
1115
    Note: This will run in its own forked process."""
989
1116
    
990
1117
    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:
 
1118
        with contextlib.closing(self.server.child_pipe) as child_pipe:
 
1119
            logger.info(u"TCP connection from: %s",
 
1120
                        unicode(self.client_address))
 
1121
            logger.debug(u"Pipe FD: %d",
 
1122
                         self.server.child_pipe.fileno())
 
1123
 
996
1124
            session = (gnutls.connection
997
1125
                       .ClientSession(self.request,
998
1126
                                      gnutls.connection
999
1127
                                      .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
 
            
 
1128
 
1010
1129
            # Note: gnutls.connection.X509Credentials is really a
1011
1130
            # generic GnuTLS certificate credentials object so long as
1012
1131
            # no X.509 keys are added to it.  Therefore, we can use it
1013
1132
            # here despite using OpenPGP certificates.
1014
 
            
 
1133
 
1015
1134
            #priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1016
1135
            #                      u"+AES-256-CBC", u"+SHA1",
1017
1136
            #                      u"+COMP-NULL", u"+CTYPE-OPENPGP",
1023
1142
            (gnutls.library.functions
1024
1143
             .gnutls_priority_set_direct(session._c_object,
1025
1144
                                         priority, None))
1026
 
            
 
1145
 
 
1146
            # Start communication using the Mandos protocol
 
1147
            # Get protocol number
 
1148
            line = self.request.makefile().readline()
 
1149
            logger.debug(u"Protocol version: %r", line)
 
1150
            try:
 
1151
                if int(line.strip().split()[0]) > 1:
 
1152
                    raise RuntimeError
 
1153
            except (ValueError, IndexError, RuntimeError), error:
 
1154
                logger.error(u"Unknown protocol version: %s", error)
 
1155
                return
 
1156
 
 
1157
            # Start GnuTLS connection
1027
1158
            try:
1028
1159
                session.handshake()
1029
1160
            except gnutls.errors.GNUTLSError, error:
1032
1163
                # established.  Just abandon the request.
1033
1164
                return
1034
1165
            logger.debug(u"Handshake succeeded")
 
1166
 
 
1167
            approval_required = False
1035
1168
            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)
 
1169
                try:
 
1170
                    fpr = self.fingerprint(self.peer_certificate
 
1171
                                           (session))
 
1172
                except (TypeError, gnutls.errors.GNUTLSError), error:
 
1173
                    logger.warning(u"Bad certificate: %s", error)
 
1174
                    return
 
1175
                logger.debug(u"Fingerprint: %s", fpr)
 
1176
 
 
1177
                try:
 
1178
                    client = ProxyClient(child_pipe, fpr,
 
1179
                                         self.client_address)
 
1180
                except KeyError:
 
1181
                    return
 
1182
                
 
1183
                if client.approved_delay:
 
1184
                    delay = client.approved_delay
 
1185
                    client.approvals_pending += 1
 
1186
                    approval_required = True
 
1187
                
 
1188
                while True:
 
1189
                    if not client.enabled:
 
1190
                        logger.warning(u"Client %s is disabled",
 
1191
                                       client.name)
 
1192
                        if self.server.use_dbus:
 
1193
                            # Emit D-Bus signal
 
1194
                            client.Rejected("Disabled")                    
 
1195
                        return
 
1196
                    
 
1197
                    if client._approved or not client.approved_delay:
 
1198
                        #We are approved or approval is disabled
 
1199
                        break
 
1200
                    elif client._approved is None:
 
1201
                        logger.info(u"Client %s need approval",
 
1202
                                    client.name)
 
1203
                        if self.server.use_dbus:
 
1204
                            # Emit D-Bus signal
 
1205
                            client.NeedApproval(
 
1206
                                client.approved_delay_milliseconds(),
 
1207
                                client.approved_by_default)
 
1208
                    else:
 
1209
                        logger.warning(u"Client %s was not approved",
 
1210
                                       client.name)
 
1211
                        if self.server.use_dbus:
 
1212
                            # Emit D-Bus signal
 
1213
                            client.Rejected("Disapproved")
 
1214
                        return
 
1215
                    
 
1216
                    #wait until timeout or approved
 
1217
                    #x = float(client._timedelta_to_milliseconds(delay))
 
1218
                    time = datetime.datetime.now()
 
1219
                    client.changedstate.acquire()
 
1220
                    client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
 
1221
                    client.changedstate.release()
 
1222
                    time2 = datetime.datetime.now()
 
1223
                    if (time2 - time) >= delay:
 
1224
                        if not client.approved_by_default:
 
1225
                            logger.warning("Client %s timed out while"
 
1226
                                           " waiting for approval",
 
1227
                                           client.name)
 
1228
                            if self.server.use_dbus:
 
1229
                                # Emit D-Bus signal
 
1230
                                client.Rejected("Approval timed out")
 
1231
                            return
 
1232
                        else:
 
1233
                            break
 
1234
                    else:
 
1235
                        delay -= time2 - time
 
1236
                
 
1237
                sent_size = 0
 
1238
                while sent_size < len(client.secret):
 
1239
                    try:
 
1240
                        sent = session.send(client.secret[sent_size:])
 
1241
                    except (gnutls.errors.GNUTLSError), error:
 
1242
                        logger.warning("gnutls send failed")
 
1243
                        return
 
1244
                    logger.debug(u"Sent: %d, remaining: %d",
 
1245
                                 sent, len(client.secret)
 
1246
                                 - (sent_size + sent))
 
1247
                    sent_size += sent
 
1248
 
 
1249
                logger.info(u"Sending secret to %s", client.name)
 
1250
                # bump the timeout as if seen
 
1251
                client.checked_ok()
 
1252
                if self.server.use_dbus:
 
1253
                    # Emit D-Bus signal
 
1254
                    client.GotSecret()
1042
1255
            
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()
 
1256
            finally:
 
1257
                if approval_required:
 
1258
                    client.approvals_pending -= 1
 
1259
                try:
 
1260
                    session.bye()
 
1261
                except (gnutls.errors.GNUTLSError), error:
 
1262
                    logger.warning("gnutls bye failed")
1068
1263
    
1069
1264
    @staticmethod
1070
1265
    def peer_certificate(session):
1130
1325
        return hex_fpr
1131
1326
 
1132
1327
 
1133
 
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1134
 
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
 
1328
class MultiprocessingMixIn(object):
 
1329
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
 
1330
    def sub_process_main(self, request, address):
 
1331
        try:
 
1332
            self.finish_request(request, address)
 
1333
        except:
 
1334
            self.handle_error(request, address)
 
1335
        self.close_request(request)
 
1336
            
 
1337
    def process_request(self, request, address):
 
1338
        """Start a new process to process the request."""
 
1339
        multiprocessing.Process(target = self.sub_process_main,
 
1340
                                args = (request, address)).start()
 
1341
 
 
1342
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
 
1343
    """ adds a pipe to the MixIn """
1135
1344
    def process_request(self, request, client_address):
1136
1345
        """Overrides and wraps the original process_request().
1137
1346
        
1138
1347
        This function creates a new pipe in self.pipe
1139
1348
        """
1140
 
        self.pipe = os.pipe()
1141
 
        super(ForkingMixInWithPipe,
 
1349
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
 
1350
 
 
1351
        super(MultiprocessingMixInWithPipe,
1142
1352
              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):
 
1353
        self.child_pipe.close()
 
1354
        self.add_pipe(parent_pipe)
 
1355
 
 
1356
    def add_pipe(self, parent_pipe):
1146
1357
        """Dummy function; override as necessary"""
1147
 
        os.close(pipe)
1148
 
 
1149
 
 
1150
 
class IPv6_TCPServer(ForkingMixInWithPipe,
 
1358
        pass
 
1359
 
 
1360
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1151
1361
                     socketserver.TCPServer, object):
1152
1362
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1153
1363
    
1238
1448
            return socketserver.TCPServer.server_activate(self)
1239
1449
    def enable(self):
1240
1450
        self.enabled = True
1241
 
    def add_pipe(self, pipe):
 
1451
    def add_pipe(self, parent_pipe):
1242
1452
        # 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={}):
 
1453
        gobject.io_add_watch(parent_pipe.fileno(),
 
1454
                             gobject.IO_IN | gobject.IO_HUP,
 
1455
                             functools.partial(self.handle_ipc,
 
1456
                                               parent_pipe = parent_pipe))
 
1457
        
 
1458
    def handle_ipc(self, source, condition, parent_pipe=None,
 
1459
                   client_object=None):
1246
1460
        condition_names = {
1247
1461
            gobject.IO_IN: u"IN",   # There is data to read.
1248
1462
            gobject.IO_OUT: u"OUT", # Data can be written (without
1257
1471
                                       for cond, name in
1258
1472
                                       condition_names.iteritems()
1259
1473
                                       if cond & condition)
1260
 
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1261
 
                     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
 
1474
        # error or the other end of multiprocessing.Pipe has closed
 
1475
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
 
1476
            return False
 
1477
        
 
1478
        # Read a request from the child
 
1479
        request = parent_pipe.recv()
 
1480
        command = request[0]
 
1481
        
 
1482
        if command == 'init':
 
1483
            fpr = request[1]
 
1484
            address = request[2]
 
1485
            
 
1486
            for c in self.clients:
 
1487
                if c.fingerprint == fpr:
 
1488
                    client = c
 
1489
                    break
 
1490
            else:
 
1491
                logger.warning(u"Client not found for fingerprint: %s, ad"
 
1492
                               u"dress: %s", fpr, address)
 
1493
                if self.use_dbus:
 
1494
                    # Emit D-Bus signal
 
1495
                    mandos_dbus_service.ClientNotFound(fpr, address)
 
1496
                parent_pipe.send(False)
 
1497
                return False
 
1498
            
 
1499
            gobject.io_add_watch(parent_pipe.fileno(),
 
1500
                                 gobject.IO_IN | gobject.IO_HUP,
 
1501
                                 functools.partial(self.handle_ipc,
 
1502
                                                   parent_pipe = parent_pipe,
 
1503
                                                   client_object = client))
 
1504
            parent_pipe.send(True)
 
1505
            # remove the old hook in favor of the new above hook on same fileno
 
1506
            return False
 
1507
        if command == 'funcall':
 
1508
            funcname = request[1]
 
1509
            args = request[2]
 
1510
            kwargs = request[3]
 
1511
            
 
1512
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
 
1513
 
 
1514
        if command == 'getattr':
 
1515
            attrname = request[1]
 
1516
            if callable(client_object.__getattribute__(attrname)):
 
1517
                parent_pipe.send(('function',))
 
1518
            else:
 
1519
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
 
1520
        
 
1521
        if command == 'setattr':
 
1522
            attrname = request[1]
 
1523
            value = request[2]
 
1524
            setattr(client_object, attrname, value)
 
1525
 
1314
1526
        return True
1315
1527
 
1316
1528
 
1367
1579
        def if_nametoindex(interface):
1368
1580
            "Get an interface index the hard way, i.e. using fcntl()"
1369
1581
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1370
 
            with closing(socket.socket()) as s:
 
1582
            with contextlib.closing(socket.socket()) as s:
1371
1583
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1372
1584
                                    struct.pack(str(u"16s16x"),
1373
1585
                                                interface))
1419
1631
    parser.add_option("--debug", action=u"store_true",
1420
1632
                      help=u"Debug mode; run in foreground and log to"
1421
1633
                      u" terminal")
 
1634
    parser.add_option("--debuglevel", type=u"string", metavar="Level",
 
1635
                      help=u"Debug level for stdout output")
1422
1636
    parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1423
1637
                      u" priority string (see GnuTLS documentation)")
1424
1638
    parser.add_option("--servicename", type=u"string",
1449
1663
                        u"servicename": u"Mandos",
1450
1664
                        u"use_dbus": u"True",
1451
1665
                        u"use_ipv6": u"True",
 
1666
                        u"debuglevel": u"",
1452
1667
                        }
1453
1668
    
1454
1669
    # Parse config file for server-global settings
1471
1686
    # options, if set.
1472
1687
    for option in (u"interface", u"address", u"port", u"debug",
1473
1688
                   u"priority", u"servicename", u"configdir",
1474
 
                   u"use_dbus", u"use_ipv6"):
 
1689
                   u"use_dbus", u"use_ipv6", u"debuglevel"):
1475
1690
        value = getattr(options, option)
1476
1691
        if value is not None:
1477
1692
            server_settings[option] = value
1486
1701
    
1487
1702
    # For convenience
1488
1703
    debug = server_settings[u"debug"]
 
1704
    debuglevel = server_settings[u"debuglevel"]
1489
1705
    use_dbus = server_settings[u"use_dbus"]
1490
1706
    use_ipv6 = server_settings[u"use_ipv6"]
1491
 
    
1492
 
    if not debug:
1493
 
        syslogger.setLevel(logging.WARNING)
1494
 
        console.setLevel(logging.WARNING)
1495
 
    
 
1707
 
1496
1708
    if server_settings[u"servicename"] != u"Mandos":
1497
1709
        syslogger.setFormatter(logging.Formatter
1498
1710
                               (u'Mandos (%s) [%%(process)d]:'
1504
1716
                        u"interval": u"5m",
1505
1717
                        u"checker": u"fping -q -- %%(host)s",
1506
1718
                        u"host": u"",
 
1719
                        u"approved_delay": u"0s",
 
1720
                        u"approved_duration": u"1s",
1507
1721
                        }
1508
1722
    client_config = configparser.SafeConfigParser(client_defaults)
1509
1723
    client_config.read(os.path.join(server_settings[u"configdir"],
1515
1729
    tcp_server = MandosServer((server_settings[u"address"],
1516
1730
                               server_settings[u"port"]),
1517
1731
                              ClientHandler,
1518
 
                              interface=server_settings[u"interface"],
 
1732
                              interface=(server_settings[u"interface"]
 
1733
                                         or None),
1519
1734
                              use_ipv6=use_ipv6,
1520
1735
                              gnutls_priority=
1521
1736
                              server_settings[u"priority"],
1548
1763
            raise error
1549
1764
    
1550
1765
    # Enable all possible GnuTLS debugging
 
1766
 
 
1767
 
 
1768
    if not debug and not debuglevel:
 
1769
        syslogger.setLevel(logging.WARNING)
 
1770
        console.setLevel(logging.WARNING)
 
1771
    if debuglevel:
 
1772
        level = getattr(logging, debuglevel.upper())
 
1773
        syslogger.setLevel(level)
 
1774
        console.setLevel(level)
 
1775
 
1551
1776
    if debug:
1552
1777
        # "Use a log level over 10 to enable all debugging options."
1553
1778
        # - GnuTLS manual
1559
1784
        
1560
1785
        (gnutls.library.functions
1561
1786
         .gnutls_global_set_log_function(debug_gnutls))
 
1787
 
 
1788
        # Redirect stdin so all checkers get /dev/null
 
1789
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
1790
        os.dup2(null, sys.stdin.fileno())
 
1791
        if null > 2:
 
1792
            os.close(null)
 
1793
    else:
 
1794
        # No console logging
 
1795
        logger.removeHandler(console)
 
1796
 
1562
1797
    
1563
1798
    global main_loop
1564
1799
    # From the Avahi example code
1567
1802
    bus = dbus.SystemBus()
1568
1803
    # End of Avahi example code
1569
1804
    if use_dbus:
1570
 
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
 
1805
        try:
 
1806
            bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
 
1807
                                            bus, do_not_queue=True)
 
1808
        except dbus.exceptions.NameExistsException, e:
 
1809
            logger.error(unicode(e) + u", disabling D-Bus")
 
1810
            use_dbus = False
 
1811
            server_settings[u"use_dbus"] = False
 
1812
            tcp_server.use_dbus = False
1571
1813
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1572
1814
    service = AvahiService(name = server_settings[u"servicename"],
1573
1815
                           servicetype = u"_mandos._tcp",
1575
1817
    if server_settings["interface"]:
1576
1818
        service.interface = (if_nametoindex
1577
1819
                             (str(server_settings[u"interface"])))
 
1820
 
 
1821
    if not debug:
 
1822
        # Close all input and output, do double fork, etc.
 
1823
        daemon()
 
1824
        
 
1825
    global multiprocessing_manager
 
1826
    multiprocessing_manager = multiprocessing.Manager()
1578
1827
    
1579
1828
    client_class = Client
1580
1829
    if use_dbus:
1581
1830
        client_class = functools.partial(ClientDBus, bus = bus)
 
1831
    def client_config_items(config, section):
 
1832
        special_settings = {
 
1833
            "approved_by_default":
 
1834
                lambda: config.getboolean(section,
 
1835
                                          "approved_by_default"),
 
1836
            }
 
1837
        for name, value in config.items(section):
 
1838
            try:
 
1839
                yield (name, special_settings[name]())
 
1840
            except KeyError:
 
1841
                yield (name, value)
 
1842
    
1582
1843
    tcp_server.clients.update(set(
1583
1844
            client_class(name = section,
1584
 
                         config= dict(client_config.items(section)))
 
1845
                         config= dict(client_config_items(
 
1846
                        client_config, section)))
1585
1847
            for section in client_config.sections()))
1586
1848
    if not tcp_server.clients:
1587
1849
        logger.warning(u"No clients defined")
1588
 
    
1589
 
    if debug:
1590
 
        # Redirect stdin so all checkers get /dev/null
1591
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1592
 
        os.dup2(null, sys.stdin.fileno())
1593
 
        if null > 2:
1594
 
            os.close(null)
1595
 
    else:
1596
 
        # No console logging
1597
 
        logger.removeHandler(console)
1598
 
        # Close all input and output, do double fork, etc.
1599
 
        daemon()
1600
 
    
 
1850
        
1601
1851
    try:
1602
 
        with closing(pidfile):
 
1852
        with pidfile:
1603
1853
            pid = os.getpid()
1604
1854
            pidfile.write(str(pid) + "\n")
1605
1855
        del pidfile
1623
1873
                dbus.service.Object.__init__(self, bus, u"/")
1624
1874
            _interface = u"se.bsnet.fukt.Mandos"
1625
1875
            
1626
 
            @dbus.service.signal(_interface, signature=u"oa{sv}")
1627
 
            def ClientAdded(self, objpath, properties):
 
1876
            @dbus.service.signal(_interface, signature=u"o")
 
1877
            def ClientAdded(self, objpath):
1628
1878
                "D-Bus signal"
1629
1879
                pass
1630
1880
            
1631
 
            @dbus.service.signal(_interface, signature=u"s")
1632
 
            def ClientNotFound(self, fingerprint):
 
1881
            @dbus.service.signal(_interface, signature=u"ss")
 
1882
            def ClientNotFound(self, fingerprint, address):
1633
1883
                "D-Bus signal"
1634
1884
                pass
1635
1885
            
1661
1911
                        tcp_server.clients.remove(c)
1662
1912
                        c.remove_from_connection()
1663
1913
                        # Don't signal anything except ClientRemoved
1664
 
                        c.disable(signal=False)
 
1914
                        c.disable(quiet=True)
1665
1915
                        # Emit D-Bus signal
1666
1916
                        self.ClientRemoved(object_path, c.name)
1667
1917
                        return
1677
1927
        
1678
1928
        while tcp_server.clients:
1679
1929
            client = tcp_server.clients.pop()
1680
 
            client.remove_from_connection()
 
1930
            if use_dbus:
 
1931
                client.remove_from_connection()
1681
1932
            client.disable_hook = None
1682
1933
            # Don't signal anything except ClientRemoved
1683
 
            client.disable(signal=False)
 
1934
            client.disable(quiet=True)
1684
1935
            if use_dbus:
1685
1936
                # Emit D-Bus signal
1686
1937
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
1691
1942
    for client in tcp_server.clients:
1692
1943
        if use_dbus:
1693
1944
            # Emit D-Bus signal
1694
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
1695
 
                                            client.GetAll(u""))
 
1945
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
1696
1946
        client.enable()
1697
1947
    
1698
1948
    tcp_server.enable()