/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: 2011-09-26 19:36:18 UTC
  • mfrom: (24.1.184 mandos)
  • Revision ID: teddy@fukt.bsnet.se-20110926193618-vtj5c9hena1maixx
Merge from Björn

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
# "AvahiService" class, and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008-2010 Teddy Hogeborn
15
 
# Copyright © 2008-2010 Björn Påhlsson
 
14
# Copyright © 2008-2011 Teddy Hogeborn
 
15
# Copyright © 2008-2011 Björn Påhlsson
16
16
17
17
# This program is free software: you can redistribute it and/or modify
18
18
# it under the terms of the GNU General Public License as published by
36
36
 
37
37
import SocketServer as socketserver
38
38
import socket
39
 
import optparse
 
39
import argparse
40
40
import datetime
41
41
import errno
42
42
import gnutls.crypto
82
82
        SO_BINDTODEVICE = None
83
83
 
84
84
 
85
 
version = "1.2.3"
 
85
version = "1.3.1"
86
86
 
87
87
#logger = logging.getLogger('mandos')
88
88
logger = logging.Logger('mandos')
151
151
        self.group = None       # our entry group
152
152
        self.server = None
153
153
        self.bus = bus
 
154
        self.entry_group_state_changed_match = None
154
155
    def rename(self):
155
156
        """Derived from the Avahi example code"""
156
157
        if self.rename_count >= self.max_renames:
168
169
        self.remove()
169
170
        try:
170
171
            self.add()
171
 
        except dbus.exceptions.DBusException, error:
 
172
        except dbus.exceptions.DBusException as error:
172
173
            logger.critical("DBusException: %s", error)
173
174
            self.cleanup()
174
175
            os._exit(1)
175
176
        self.rename_count += 1
176
177
    def remove(self):
177
178
        """Derived from the Avahi example code"""
 
179
        if self.entry_group_state_changed_match is not None:
 
180
            self.entry_group_state_changed_match.remove()
 
181
            self.entry_group_state_changed_match = None
178
182
        if self.group is not None:
179
183
            self.group.Reset()
180
184
    def add(self):
181
185
        """Derived from the Avahi example code"""
 
186
        self.remove()
182
187
        if self.group is None:
183
188
            self.group = dbus.Interface(
184
189
                self.bus.get_object(avahi.DBUS_NAME,
185
190
                                    self.server.EntryGroupNew()),
186
191
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
187
 
            self.group.connect_to_signal('StateChanged',
188
 
                                         self
189
 
                                         .entry_group_state_changed)
 
192
        self.entry_group_state_changed_match = (
 
193
            self.group.connect_to_signal(
 
194
                'StateChanged', self .entry_group_state_changed))
190
195
        logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
191
196
                     self.name, self.type)
192
197
        self.group.AddService(
215
220
    def cleanup(self):
216
221
        """Derived from the Avahi example code"""
217
222
        if self.group is not None:
218
 
            self.group.Free()
 
223
            try:
 
224
                self.group.Free()
 
225
            except (dbus.exceptions.UnknownMethodException,
 
226
                    dbus.exceptions.DBusException) as e:
 
227
                pass
219
228
            self.group = None
220
 
    def server_state_changed(self, state):
 
229
        self.remove()
 
230
    def server_state_changed(self, state, error=None):
221
231
        """Derived from the Avahi example code"""
222
232
        logger.debug("Avahi server state change: %i", state)
223
 
        if state == avahi.SERVER_COLLISION:
224
 
            logger.error("Zeroconf server name collision")
225
 
            self.remove()
 
233
        bad_states = { avahi.SERVER_INVALID:
 
234
                           "Zeroconf server invalid",
 
235
                       avahi.SERVER_REGISTERING: None,
 
236
                       avahi.SERVER_COLLISION:
 
237
                           "Zeroconf server name collision",
 
238
                       avahi.SERVER_FAILURE:
 
239
                           "Zeroconf server failure" }
 
240
        if state in bad_states:
 
241
            if bad_states[state] is not None:
 
242
                if error is None:
 
243
                    logger.error(bad_states[state])
 
244
                else:
 
245
                    logger.error(bad_states[state] + ": %r", error)
 
246
            self.cleanup()
226
247
        elif state == avahi.SERVER_RUNNING:
227
248
            self.add()
 
249
        else:
 
250
            if error is None:
 
251
                logger.debug("Unknown state: %r", state)
 
252
            else:
 
253
                logger.debug("Unknown state: %r: %r", state, error)
228
254
    def activate(self):
229
255
        """Derived from the Avahi example code"""
230
256
        if self.server is None:
231
257
            self.server = dbus.Interface(
232
258
                self.bus.get_object(avahi.DBUS_NAME,
233
 
                                    avahi.DBUS_PATH_SERVER),
 
259
                                    avahi.DBUS_PATH_SERVER,
 
260
                                    follow_name_owner_changes=True),
234
261
                avahi.DBUS_INTERFACE_SERVER)
235
262
        self.server.connect_to_signal("StateChanged",
236
263
                                 self.server_state_changed)
237
264
        self.server_state_changed(self.server.GetState())
238
265
 
239
266
 
 
267
def _timedelta_to_milliseconds(td):
 
268
    "Convert a datetime.timedelta() to milliseconds"
 
269
    return ((td.days * 24 * 60 * 60 * 1000)
 
270
            + (td.seconds * 1000)
 
271
            + (td.microseconds // 1000))
 
272
        
240
273
class Client(object):
241
274
    """A representation of a client host served by this server.
242
275
    
270
303
    secret:     bytestring; sent verbatim (over TLS) to client
271
304
    timeout:    datetime.timedelta(); How long from last_checked_ok
272
305
                                      until this client is disabled
 
306
    extended_timeout:   extra long timeout when password has been sent
273
307
    runtime_expansions: Allowed attributes for runtime expansion.
 
308
    expires:    datetime.datetime(); time (UTC) when a client will be
 
309
                disabled, or None
274
310
    """
275
311
    
276
312
    runtime_expansions = ("approval_delay", "approval_duration",
277
313
                          "created", "enabled", "fingerprint",
278
314
                          "host", "interval", "last_checked_ok",
279
315
                          "last_enabled", "name", "timeout")
280
 
    
281
 
    @staticmethod
282
 
    def _timedelta_to_milliseconds(td):
283
 
        "Convert a datetime.timedelta() to milliseconds"
284
 
        return ((td.days * 24 * 60 * 60 * 1000)
285
 
                + (td.seconds * 1000)
286
 
                + (td.microseconds // 1000))
287
 
    
 
316
        
288
317
    def timeout_milliseconds(self):
289
318
        "Return the 'timeout' attribute in milliseconds"
290
 
        return self._timedelta_to_milliseconds(self.timeout)
 
319
        return _timedelta_to_milliseconds(self.timeout)
 
320
 
 
321
    def extended_timeout_milliseconds(self):
 
322
        "Return the 'extended_timeout' attribute in milliseconds"
 
323
        return _timedelta_to_milliseconds(self.extended_timeout)    
291
324
    
292
325
    def interval_milliseconds(self):
293
326
        "Return the 'interval' attribute in milliseconds"
294
 
        return self._timedelta_to_milliseconds(self.interval)
 
327
        return _timedelta_to_milliseconds(self.interval)
295
328
 
296
329
    def approval_delay_milliseconds(self):
297
 
        return self._timedelta_to_milliseconds(self.approval_delay)
 
330
        return _timedelta_to_milliseconds(self.approval_delay)
298
331
    
299
332
    def __init__(self, name = None, disable_hook=None, config=None):
300
333
        """Note: the 'checker' key in 'config' sets the
327
360
        self.last_enabled = None
328
361
        self.last_checked_ok = None
329
362
        self.timeout = string_to_delta(config["timeout"])
 
363
        self.extended_timeout = string_to_delta(config["extended_timeout"])
330
364
        self.interval = string_to_delta(config["interval"])
331
365
        self.disable_hook = disable_hook
332
366
        self.checker = None
333
367
        self.checker_initiator_tag = None
334
368
        self.disable_initiator_tag = None
 
369
        self.expires = None
335
370
        self.checker_callback_tag = None
336
371
        self.checker_command = config["checker"]
337
372
        self.current_checker_command = None
357
392
            # Already enabled
358
393
            return
359
394
        self.send_changedstate()
360
 
        self.last_enabled = datetime.datetime.utcnow()
361
395
        # Schedule a new checker to be started an 'interval' from now,
362
396
        # and every interval from then on.
363
397
        self.checker_initiator_tag = (gobject.timeout_add
364
398
                                      (self.interval_milliseconds(),
365
399
                                       self.start_checker))
366
400
        # Schedule a disable() when 'timeout' has passed
 
401
        self.expires = datetime.datetime.utcnow() + self.timeout
367
402
        self.disable_initiator_tag = (gobject.timeout_add
368
403
                                   (self.timeout_milliseconds(),
369
404
                                    self.disable))
370
405
        self.enabled = True
 
406
        self.last_enabled = datetime.datetime.utcnow()
371
407
        # Also start a new checker *right now*.
372
408
        self.start_checker()
373
409
    
382
418
        if getattr(self, "disable_initiator_tag", False):
383
419
            gobject.source_remove(self.disable_initiator_tag)
384
420
            self.disable_initiator_tag = None
 
421
        self.expires = None
385
422
        if getattr(self, "checker_initiator_tag", False):
386
423
            gobject.source_remove(self.checker_initiator_tag)
387
424
            self.checker_initiator_tag = None
413
450
            logger.warning("Checker for %(name)s crashed?",
414
451
                           vars(self))
415
452
    
416
 
    def checked_ok(self):
 
453
    def checked_ok(self, timeout=None):
417
454
        """Bump up the timeout for this client.
418
455
        
419
456
        This should only be called when the client has been seen,
420
457
        alive and well.
421
458
        """
 
459
        if timeout is None:
 
460
            timeout = self.timeout
422
461
        self.last_checked_ok = datetime.datetime.utcnow()
423
462
        gobject.source_remove(self.disable_initiator_tag)
 
463
        self.expires = datetime.datetime.utcnow() + timeout
424
464
        self.disable_initiator_tag = (gobject.timeout_add
425
 
                                      (self.timeout_milliseconds(),
 
465
                                      (_timedelta_to_milliseconds(timeout),
426
466
                                       self.disable))
427
467
    
428
468
    def need_approval(self):
445
485
        # If a checker exists, make sure it is not a zombie
446
486
        try:
447
487
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
448
 
        except (AttributeError, OSError), error:
 
488
        except (AttributeError, OSError) as error:
449
489
            if (isinstance(error, OSError)
450
490
                and error.errno != errno.ECHILD):
451
491
                raise error
472
512
 
473
513
                try:
474
514
                    command = self.checker_command % escaped_attrs
475
 
                except TypeError, error:
 
515
                except TypeError as error:
476
516
                    logger.error('Could not format string "%s":'
477
517
                                 ' %s', self.checker_command, error)
478
518
                    return True # Try again later
497
537
                if pid:
498
538
                    gobject.source_remove(self.checker_callback_tag)
499
539
                    self.checker_callback(pid, status, command)
500
 
            except OSError, error:
 
540
            except OSError as error:
501
541
                logger.error("Failed to start subprocess: %s",
502
542
                             error)
503
543
        # Re-run this periodically if run by gobject.timeout_add
516
556
            #time.sleep(0.5)
517
557
            #if self.checker.poll() is None:
518
558
            #    os.kill(self.checker.pid, signal.SIGKILL)
519
 
        except OSError, error:
 
559
        except OSError as error:
520
560
            if error.errno != errno.ESRCH: # No such process
521
561
                raise
522
562
        self.checker = None
704
744
            xmlstring = document.toxml("utf-8")
705
745
            document.unlink()
706
746
        except (AttributeError, xml.dom.DOMException,
707
 
                xml.parsers.expat.ExpatError), error:
 
747
                xml.parsers.expat.ExpatError) as error:
708
748
            logger.error("Failed to override Introspection method",
709
749
                         error)
710
750
        return xmlstring
711
751
 
712
752
 
 
753
def datetime_to_dbus (dt, variant_level=0):
 
754
    """Convert a UTC datetime.datetime() to a D-Bus type."""
 
755
    if dt is None:
 
756
        return dbus.String("", variant_level = variant_level)
 
757
    return dbus.String(dt.isoformat(),
 
758
                       variant_level=variant_level)
 
759
 
713
760
class ClientDBus(Client, DBusObjectWithProperties):
714
761
    """A Client class using D-Bus
715
762
    
737
784
        DBusObjectWithProperties.__init__(self, self.bus,
738
785
                                          self.dbus_object_path)
739
786
        
740
 
    def _get_approvals_pending(self):
741
 
        return self._approvals_pending
742
 
    def _set_approvals_pending(self, value):
743
 
        old_value = self._approvals_pending
744
 
        self._approvals_pending = value
745
 
        bval = bool(value)
746
 
        if (hasattr(self, "dbus_object_path")
747
 
            and bval is not bool(old_value)):
748
 
            dbus_bool = dbus.Boolean(bval, variant_level=1)
749
 
            self.PropertyChanged(dbus.String("ApprovalPending"),
750
 
                                 dbus_bool)
751
 
 
752
 
    approvals_pending = property(_get_approvals_pending,
753
 
                                 _set_approvals_pending)
754
 
    del _get_approvals_pending, _set_approvals_pending
755
 
    
756
 
    @staticmethod
757
 
    def _datetime_to_dbus(dt, variant_level=0):
758
 
        """Convert a UTC datetime.datetime() to a D-Bus type."""
759
 
        return dbus.String(dt.isoformat(),
760
 
                           variant_level=variant_level)
761
 
    
762
 
    def enable(self):
763
 
        oldstate = getattr(self, "enabled", False)
764
 
        r = Client.enable(self)
765
 
        if oldstate != self.enabled:
766
 
            # Emit D-Bus signals
767
 
            self.PropertyChanged(dbus.String("Enabled"),
768
 
                                 dbus.Boolean(True, variant_level=1))
769
 
            self.PropertyChanged(
770
 
                dbus.String("LastEnabled"),
771
 
                self._datetime_to_dbus(self.last_enabled,
772
 
                                       variant_level=1))
773
 
        return r
774
 
    
775
 
    def disable(self, quiet = False):
776
 
        oldstate = getattr(self, "enabled", False)
777
 
        r = Client.disable(self, quiet=quiet)
778
 
        if not quiet and oldstate != self.enabled:
779
 
            # Emit D-Bus signal
780
 
            self.PropertyChanged(dbus.String("Enabled"),
781
 
                                 dbus.Boolean(False, variant_level=1))
782
 
        return r
 
787
    def notifychangeproperty(transform_func,
 
788
                             dbus_name, type_func=lambda x: x,
 
789
                             variant_level=1):
 
790
        """ Modify a variable so that its a property that announce its
 
791
        changes to DBus.
 
792
        transform_fun: Function that takes a value and transform it to
 
793
                       DBus type.
 
794
        dbus_name: DBus name of the variable
 
795
        type_func: Function that transform the value before sending it
 
796
                   to DBus
 
797
        variant_level: DBus variant level. default: 1
 
798
        """
 
799
        real_value = [None,]
 
800
        def setter(self, value):
 
801
            old_value = real_value[0]
 
802
            real_value[0] = value
 
803
            if hasattr(self, "dbus_object_path"):
 
804
                if type_func(old_value) != type_func(real_value[0]):
 
805
                    dbus_value = transform_func(type_func(real_value[0]),
 
806
                                                variant_level)
 
807
                    self.PropertyChanged(dbus.String(dbus_name),
 
808
                                         dbus_value)
 
809
 
 
810
        return property(lambda self: real_value[0], setter)
 
811
 
 
812
 
 
813
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
 
814
    approvals_pending = notifychangeproperty(dbus.Boolean,
 
815
                                             "ApprovalPending",
 
816
                                             type_func = bool)
 
817
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
 
818
    last_enabled = notifychangeproperty(datetime_to_dbus,
 
819
                                        "LastEnabled")
 
820
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
 
821
                                   type_func = lambda checker: checker is not None)
 
822
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
 
823
                                           "LastCheckedOK")
 
824
    last_approval_request = notifychangeproperty(datetime_to_dbus,
 
825
                                                 "LastApprovalRequest")
 
826
    approved_by_default = notifychangeproperty(dbus.Boolean,
 
827
                                               "ApprovedByDefault")
 
828
    approval_delay = notifychangeproperty(dbus.UInt16, "ApprovalDelay",
 
829
                                          type_func = _timedelta_to_milliseconds)
 
830
    approval_duration = notifychangeproperty(dbus.UInt16, "ApprovalDuration",
 
831
                                             type_func = _timedelta_to_milliseconds)
 
832
    host = notifychangeproperty(dbus.String, "Host")
 
833
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
 
834
                                   type_func = _timedelta_to_milliseconds)
 
835
    extended_timeout = notifychangeproperty(dbus.UInt16, "ExtendedTimeout",
 
836
                                            type_func = _timedelta_to_milliseconds)
 
837
    interval = notifychangeproperty(dbus.UInt16, "Interval",
 
838
                                    type_func = _timedelta_to_milliseconds)
 
839
    checker_command = notifychangeproperty(dbus.String, "Checker")
 
840
    
 
841
    del notifychangeproperty
783
842
    
784
843
    def __del__(self, *args, **kwargs):
785
844
        try:
794
853
                         *args, **kwargs):
795
854
        self.checker_callback_tag = None
796
855
        self.checker = None
797
 
        # Emit D-Bus signal
798
 
        self.PropertyChanged(dbus.String("CheckerRunning"),
799
 
                             dbus.Boolean(False, variant_level=1))
800
856
        if os.WIFEXITED(condition):
801
857
            exitstatus = os.WEXITSTATUS(condition)
802
858
            # Emit D-Bus signal
811
867
        
812
868
        return Client.checker_callback(self, pid, condition, command,
813
869
                                       *args, **kwargs)
814
 
    
815
 
    def checked_ok(self, *args, **kwargs):
816
 
        r = Client.checked_ok(self, *args, **kwargs)
817
 
        # Emit D-Bus signal
818
 
        self.PropertyChanged(
819
 
            dbus.String("LastCheckedOK"),
820
 
            (self._datetime_to_dbus(self.last_checked_ok,
821
 
                                    variant_level=1)))
822
 
        return r
823
 
    
824
 
    def need_approval(self, *args, **kwargs):
825
 
        r = Client.need_approval(self, *args, **kwargs)
826
 
        # Emit D-Bus signal
827
 
        self.PropertyChanged(
828
 
            dbus.String("LastApprovalRequest"),
829
 
            (self._datetime_to_dbus(self.last_approval_request,
830
 
                                    variant_level=1)))
831
 
        return r
832
 
    
 
870
 
833
871
    def start_checker(self, *args, **kwargs):
834
872
        old_checker = self.checker
835
873
        if self.checker is not None:
842
880
            and old_checker_pid != self.checker.pid):
843
881
            # Emit D-Bus signal
844
882
            self.CheckerStarted(self.current_checker_command)
845
 
            self.PropertyChanged(
846
 
                dbus.String("CheckerRunning"),
847
 
                dbus.Boolean(True, variant_level=1))
848
883
        return r
849
884
    
850
 
    def stop_checker(self, *args, **kwargs):
851
 
        old_checker = getattr(self, "checker", None)
852
 
        r = Client.stop_checker(self, *args, **kwargs)
853
 
        if (old_checker is not None
854
 
            and getattr(self, "checker", None) is None):
855
 
            self.PropertyChanged(dbus.String("CheckerRunning"),
856
 
                                 dbus.Boolean(False, variant_level=1))
857
 
        return r
858
 
 
859
885
    def _reset_approved(self):
860
886
        self._approved = None
861
887
        return False
863
889
    def approve(self, value=True):
864
890
        self.send_changedstate()
865
891
        self._approved = value
866
 
        gobject.timeout_add(self._timedelta_to_milliseconds
 
892
        gobject.timeout_add(_timedelta_to_milliseconds
867
893
                            (self.approval_duration),
868
894
                            self._reset_approved)
869
895
    
922
948
    # CheckedOK - method
923
949
    @dbus.service.method(_interface)
924
950
    def CheckedOK(self):
925
 
        return self.checked_ok()
 
951
        self.checked_ok()
926
952
    
927
953
    # Enable - method
928
954
    @dbus.service.method(_interface)
961
987
        if value is None:       # get
962
988
            return dbus.Boolean(self.approved_by_default)
963
989
        self.approved_by_default = bool(value)
964
 
        # Emit D-Bus signal
965
 
        self.PropertyChanged(dbus.String("ApprovedByDefault"),
966
 
                             dbus.Boolean(value, variant_level=1))
967
990
    
968
991
    # ApprovalDelay - property
969
992
    @dbus_service_property(_interface, signature="t",
972
995
        if value is None:       # get
973
996
            return dbus.UInt64(self.approval_delay_milliseconds())
974
997
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
975
 
        # Emit D-Bus signal
976
 
        self.PropertyChanged(dbus.String("ApprovalDelay"),
977
 
                             dbus.UInt64(value, variant_level=1))
978
998
    
979
999
    # ApprovalDuration - property
980
1000
    @dbus_service_property(_interface, signature="t",
981
1001
                           access="readwrite")
982
1002
    def ApprovalDuration_dbus_property(self, value=None):
983
1003
        if value is None:       # get
984
 
            return dbus.UInt64(self._timedelta_to_milliseconds(
 
1004
            return dbus.UInt64(_timedelta_to_milliseconds(
985
1005
                    self.approval_duration))
986
1006
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
987
 
        # Emit D-Bus signal
988
 
        self.PropertyChanged(dbus.String("ApprovalDuration"),
989
 
                             dbus.UInt64(value, variant_level=1))
990
1007
    
991
1008
    # Name - property
992
1009
    @dbus_service_property(_interface, signature="s", access="read")
1005
1022
        if value is None:       # get
1006
1023
            return dbus.String(self.host)
1007
1024
        self.host = value
1008
 
        # Emit D-Bus signal
1009
 
        self.PropertyChanged(dbus.String("Host"),
1010
 
                             dbus.String(value, variant_level=1))
1011
1025
    
1012
1026
    # Created - property
1013
1027
    @dbus_service_property(_interface, signature="s", access="read")
1014
1028
    def Created_dbus_property(self):
1015
 
        return dbus.String(self._datetime_to_dbus(self.created))
 
1029
        return dbus.String(datetime_to_dbus(self.created))
1016
1030
    
1017
1031
    # LastEnabled - property
1018
1032
    @dbus_service_property(_interface, signature="s", access="read")
1019
1033
    def LastEnabled_dbus_property(self):
1020
 
        if self.last_enabled is None:
1021
 
            return dbus.String("")
1022
 
        return dbus.String(self._datetime_to_dbus(self.last_enabled))
 
1034
        return datetime_to_dbus(self.last_enabled)
1023
1035
    
1024
1036
    # Enabled - property
1025
1037
    @dbus_service_property(_interface, signature="b",
1039
1051
        if value is not None:
1040
1052
            self.checked_ok()
1041
1053
            return
1042
 
        if self.last_checked_ok is None:
1043
 
            return dbus.String("")
1044
 
        return dbus.String(self._datetime_to_dbus(self
1045
 
                                                  .last_checked_ok))
 
1054
        return datetime_to_dbus(self.last_checked_ok)
 
1055
    
 
1056
    # Expires - property
 
1057
    @dbus_service_property(_interface, signature="s", access="read")
 
1058
    def Expires_dbus_property(self):
 
1059
        return datetime_to_dbus(self.expires)
1046
1060
    
1047
1061
    # LastApprovalRequest - property
1048
1062
    @dbus_service_property(_interface, signature="s", access="read")
1049
1063
    def LastApprovalRequest_dbus_property(self):
1050
 
        if self.last_approval_request is None:
1051
 
            return dbus.String("")
1052
 
        return dbus.String(self.
1053
 
                           _datetime_to_dbus(self
1054
 
                                             .last_approval_request))
 
1064
        return datetime_to_dbus(self.last_approval_request)
1055
1065
    
1056
1066
    # Timeout - property
1057
1067
    @dbus_service_property(_interface, signature="t",
1060
1070
        if value is None:       # get
1061
1071
            return dbus.UInt64(self.timeout_milliseconds())
1062
1072
        self.timeout = datetime.timedelta(0, 0, 0, value)
1063
 
        # Emit D-Bus signal
1064
 
        self.PropertyChanged(dbus.String("Timeout"),
1065
 
                             dbus.UInt64(value, variant_level=1))
1066
1073
        if getattr(self, "disable_initiator_tag", None) is None:
1067
1074
            return
1068
1075
        # Reschedule timeout
1069
1076
        gobject.source_remove(self.disable_initiator_tag)
1070
1077
        self.disable_initiator_tag = None
 
1078
        self.expires = None
1071
1079
        time_to_die = (self.
1072
1080
                       _timedelta_to_milliseconds((self
1073
1081
                                                   .last_checked_ok
1078
1086
            # The timeout has passed
1079
1087
            self.disable()
1080
1088
        else:
 
1089
            self.expires = (datetime.datetime.utcnow()
 
1090
                            + datetime.timedelta(milliseconds = time_to_die))
1081
1091
            self.disable_initiator_tag = (gobject.timeout_add
1082
1092
                                          (time_to_die, self.disable))
1083
 
    
 
1093
 
 
1094
    # ExtendedTimeout - property
 
1095
    @dbus_service_property(_interface, signature="t",
 
1096
                           access="readwrite")
 
1097
    def ExtendedTimeout_dbus_property(self, value=None):
 
1098
        if value is None:       # get
 
1099
            return dbus.UInt64(self.extended_timeout_milliseconds())
 
1100
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
 
1101
 
1084
1102
    # Interval - property
1085
1103
    @dbus_service_property(_interface, signature="t",
1086
1104
                           access="readwrite")
1088
1106
        if value is None:       # get
1089
1107
            return dbus.UInt64(self.interval_milliseconds())
1090
1108
        self.interval = datetime.timedelta(0, 0, 0, value)
1091
 
        # Emit D-Bus signal
1092
 
        self.PropertyChanged(dbus.String("Interval"),
1093
 
                             dbus.UInt64(value, variant_level=1))
1094
1109
        if getattr(self, "checker_initiator_tag", None) is None:
1095
1110
            return
1096
1111
        # Reschedule checker run
1106
1121
        if value is None:       # get
1107
1122
            return dbus.String(self.checker_command)
1108
1123
        self.checker_command = value
1109
 
        # Emit D-Bus signal
1110
 
        self.PropertyChanged(dbus.String("Checker"),
1111
 
                             dbus.String(self.checker_command,
1112
 
                                         variant_level=1))
1113
1124
    
1114
1125
    # CheckerRunning - property
1115
1126
    @dbus_service_property(_interface, signature="b",
1204
1215
            try:
1205
1216
                if int(line.strip().split()[0]) > 1:
1206
1217
                    raise RuntimeError
1207
 
            except (ValueError, IndexError, RuntimeError), error:
 
1218
            except (ValueError, IndexError, RuntimeError) as error:
1208
1219
                logger.error("Unknown protocol version: %s", error)
1209
1220
                return
1210
1221
 
1211
1222
            # Start GnuTLS connection
1212
1223
            try:
1213
1224
                session.handshake()
1214
 
            except gnutls.errors.GNUTLSError, error:
 
1225
            except gnutls.errors.GNUTLSError as error:
1215
1226
                logger.warning("Handshake failed: %s", error)
1216
1227
                # Do not run session.bye() here: the session is not
1217
1228
                # established.  Just abandon the request.
1223
1234
                try:
1224
1235
                    fpr = self.fingerprint(self.peer_certificate
1225
1236
                                           (session))
1226
 
                except (TypeError, gnutls.errors.GNUTLSError), error:
 
1237
                except (TypeError,
 
1238
                        gnutls.errors.GNUTLSError) as error:
1227
1239
                    logger.warning("Bad certificate: %s", error)
1228
1240
                    return
1229
1241
                logger.debug("Fingerprint: %s", fpr)
1241
1253
                
1242
1254
                while True:
1243
1255
                    if not client.enabled:
1244
 
                        logger.warning("Client %s is disabled",
 
1256
                        logger.info("Client %s is disabled",
1245
1257
                                       client.name)
1246
1258
                        if self.server.use_dbus:
1247
1259
                            # Emit D-Bus signal
1292
1304
                while sent_size < len(client.secret):
1293
1305
                    try:
1294
1306
                        sent = session.send(client.secret[sent_size:])
1295
 
                    except (gnutls.errors.GNUTLSError), error:
 
1307
                    except gnutls.errors.GNUTLSError as error:
1296
1308
                        logger.warning("gnutls send failed")
1297
1309
                        return
1298
1310
                    logger.debug("Sent: %d, remaining: %d",
1302
1314
 
1303
1315
                logger.info("Sending secret to %s", client.name)
1304
1316
                # bump the timeout as if seen
1305
 
                client.checked_ok()
 
1317
                client.checked_ok(client.extended_timeout)
1306
1318
                if self.server.use_dbus:
1307
1319
                    # Emit D-Bus signal
1308
1320
                    client.GotSecret()
1312
1324
                    client.approvals_pending -= 1
1313
1325
                try:
1314
1326
                    session.bye()
1315
 
                except (gnutls.errors.GNUTLSError), error:
 
1327
                except gnutls.errors.GNUTLSError as error:
1316
1328
                    logger.warning("GnuTLS bye failed")
1317
1329
    
1318
1330
    @staticmethod
1409
1421
 
1410
1422
    def add_pipe(self, parent_pipe):
1411
1423
        """Dummy function; override as necessary"""
1412
 
        pass
 
1424
        raise NotImplementedError
1413
1425
 
1414
1426
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1415
1427
                     socketserver.TCPServer, object):
1442
1454
                                           SO_BINDTODEVICE,
1443
1455
                                           str(self.interface
1444
1456
                                               + '\0'))
1445
 
                except socket.error, error:
 
1457
                except socket.error as error:
1446
1458
                    if error[0] == errno.EPERM:
1447
1459
                        logger.error("No permission to"
1448
1460
                                     " bind to interface %s",
1542
1554
                    client = c
1543
1555
                    break
1544
1556
            else:
1545
 
                logger.warning("Client not found for fingerprint: %s, ad"
1546
 
                               "dress: %s", fpr, address)
 
1557
                logger.info("Client not found for fingerprint: %s, ad"
 
1558
                            "dress: %s", fpr, address)
1547
1559
                if self.use_dbus:
1548
1560
                    # Emit D-Bus signal
1549
1561
                    mandos_dbus_service.ClientNotFound(fpr, address[0])
1613
1625
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1614
1626
            else:
1615
1627
                raise ValueError("Unknown suffix %r" % suffix)
1616
 
        except (ValueError, IndexError), e:
1617
 
            raise ValueError(e.message)
 
1628
        except (ValueError, IndexError) as e:
 
1629
            raise ValueError(*(e.args))
1618
1630
        timevalue += delta
1619
1631
    return timevalue
1620
1632
 
1673
1685
    ##################################################################
1674
1686
    # Parsing of options, both command line and config file
1675
1687
    
1676
 
    parser = optparse.OptionParser(version = "%%prog %s" % version)
1677
 
    parser.add_option("-i", "--interface", type="string",
1678
 
                      metavar="IF", help="Bind to interface IF")
1679
 
    parser.add_option("-a", "--address", type="string",
1680
 
                      help="Address to listen for requests on")
1681
 
    parser.add_option("-p", "--port", type="int",
1682
 
                      help="Port number to receive requests on")
1683
 
    parser.add_option("--check", action="store_true",
1684
 
                      help="Run self-test")
1685
 
    parser.add_option("--debug", action="store_true",
1686
 
                      help="Debug mode; run in foreground and log to"
1687
 
                      " terminal")
1688
 
    parser.add_option("--debuglevel", type="string", metavar="LEVEL",
1689
 
                      help="Debug level for stdout output")
1690
 
    parser.add_option("--priority", type="string", help="GnuTLS"
1691
 
                      " priority string (see GnuTLS documentation)")
1692
 
    parser.add_option("--servicename", type="string",
1693
 
                      metavar="NAME", help="Zeroconf service name")
1694
 
    parser.add_option("--configdir", type="string",
1695
 
                      default="/etc/mandos", metavar="DIR",
1696
 
                      help="Directory to search for configuration"
1697
 
                      " files")
1698
 
    parser.add_option("--no-dbus", action="store_false",
1699
 
                      dest="use_dbus", help="Do not provide D-Bus"
1700
 
                      " system bus interface")
1701
 
    parser.add_option("--no-ipv6", action="store_false",
1702
 
                      dest="use_ipv6", help="Do not use IPv6")
1703
 
    options = parser.parse_args()[0]
 
1688
    parser = argparse.ArgumentParser()
 
1689
    parser.add_argument("-v", "--version", action="version",
 
1690
                        version = "%%(prog)s %s" % version,
 
1691
                        help="show version number and exit")
 
1692
    parser.add_argument("-i", "--interface", metavar="IF",
 
1693
                        help="Bind to interface IF")
 
1694
    parser.add_argument("-a", "--address",
 
1695
                        help="Address to listen for requests on")
 
1696
    parser.add_argument("-p", "--port", type=int,
 
1697
                        help="Port number to receive requests on")
 
1698
    parser.add_argument("--check", action="store_true",
 
1699
                        help="Run self-test")
 
1700
    parser.add_argument("--debug", action="store_true",
 
1701
                        help="Debug mode; run in foreground and log"
 
1702
                        " to terminal")
 
1703
    parser.add_argument("--debuglevel", metavar="LEVEL",
 
1704
                        help="Debug level for stdout output")
 
1705
    parser.add_argument("--priority", help="GnuTLS"
 
1706
                        " priority string (see GnuTLS documentation)")
 
1707
    parser.add_argument("--servicename",
 
1708
                        metavar="NAME", help="Zeroconf service name")
 
1709
    parser.add_argument("--configdir",
 
1710
                        default="/etc/mandos", metavar="DIR",
 
1711
                        help="Directory to search for configuration"
 
1712
                        " files")
 
1713
    parser.add_argument("--no-dbus", action="store_false",
 
1714
                        dest="use_dbus", help="Do not provide D-Bus"
 
1715
                        " system bus interface")
 
1716
    parser.add_argument("--no-ipv6", action="store_false",
 
1717
                        dest="use_ipv6", help="Do not use IPv6")
 
1718
    options = parser.parse_args()
1704
1719
    
1705
1720
    if options.check:
1706
1721
        import doctest
1766
1781
                                % server_settings["servicename"]))
1767
1782
    
1768
1783
    # Parse config file with clients
1769
 
    client_defaults = { "timeout": "1h",
1770
 
                        "interval": "5m",
 
1784
    client_defaults = { "timeout": "5m",
 
1785
                        "extended_timeout": "15m",
 
1786
                        "interval": "2m",
1771
1787
                        "checker": "fping -q -- %%(host)s",
1772
1788
                        "host": "",
1773
1789
                        "approval_delay": "0s",
1813
1829
    try:
1814
1830
        os.setgid(gid)
1815
1831
        os.setuid(uid)
1816
 
    except OSError, error:
 
1832
    except OSError as error:
1817
1833
        if error[0] != errno.EPERM:
1818
1834
            raise error
1819
1835
    
1863
1879
        try:
1864
1880
            bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
1865
1881
                                            bus, do_not_queue=True)
1866
 
        except dbus.exceptions.NameExistsException, e:
 
1882
        except dbus.exceptions.NameExistsException as e:
1867
1883
            logger.error(unicode(e) + ", disabling D-Bus")
1868
1884
            use_dbus = False
1869
1885
            server_settings["use_dbus"] = False
2019
2035
        # From the Avahi example code
2020
2036
        try:
2021
2037
            service.activate()
2022
 
        except dbus.exceptions.DBusException, error:
 
2038
        except dbus.exceptions.DBusException as error:
2023
2039
            logger.critical("DBusException: %s", error)
2024
2040
            cleanup()
2025
2041
            sys.exit(1)
2032
2048
        
2033
2049
        logger.debug("Starting main loop")
2034
2050
        main_loop.run()
2035
 
    except AvahiError, error:
 
2051
    except AvahiError as error:
2036
2052
        logger.critical("AvahiError: %s", error)
2037
2053
        cleanup()
2038
2054
        sys.exit(1)