/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-02-27 17:26:35 UTC
  • Revision ID: teddy@fukt.bsnet.se-20110227172635-hw1sire7k3vuo1co
Update copyright year to "2011" wherever appropriate.

Show diffs side-by-side

added added

removed removed

Lines of Context:
36
36
 
37
37
import SocketServer as socketserver
38
38
import socket
39
 
import argparse
 
39
import optparse
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.3.1"
 
85
version = "1.2.3"
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
155
154
    def rename(self):
156
155
        """Derived from the Avahi example code"""
157
156
        if self.rename_count >= self.max_renames:
169
168
        self.remove()
170
169
        try:
171
170
            self.add()
172
 
        except dbus.exceptions.DBusException as error:
 
171
        except dbus.exceptions.DBusException, error:
173
172
            logger.critical("DBusException: %s", error)
174
173
            self.cleanup()
175
174
            os._exit(1)
176
175
        self.rename_count += 1
177
176
    def remove(self):
178
177
        """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
182
178
        if self.group is not None:
183
179
            self.group.Reset()
184
180
    def add(self):
185
181
        """Derived from the Avahi example code"""
186
 
        self.remove()
187
182
        if self.group is None:
188
183
            self.group = dbus.Interface(
189
184
                self.bus.get_object(avahi.DBUS_NAME,
190
185
                                    self.server.EntryGroupNew()),
191
186
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
192
 
        self.entry_group_state_changed_match = (
193
 
            self.group.connect_to_signal(
194
 
                'StateChanged', self .entry_group_state_changed))
 
187
            self.group.connect_to_signal('StateChanged',
 
188
                                         self
 
189
                                         .entry_group_state_changed)
195
190
        logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
196
191
                     self.name, self.type)
197
192
        self.group.AddService(
220
215
    def cleanup(self):
221
216
        """Derived from the Avahi example code"""
222
217
        if self.group is not None:
223
 
            try:
224
 
                self.group.Free()
225
 
            except (dbus.exceptions.UnknownMethodException,
226
 
                    dbus.exceptions.DBusException) as e:
227
 
                pass
 
218
            self.group.Free()
228
219
            self.group = None
229
 
        self.remove()
230
 
    def server_state_changed(self, state, error=None):
 
220
    def server_state_changed(self, state):
231
221
        """Derived from the Avahi example code"""
232
222
        logger.debug("Avahi server state change: %i", state)
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()
 
223
        if state == avahi.SERVER_COLLISION:
 
224
            logger.error("Zeroconf server name collision")
 
225
            self.remove()
247
226
        elif state == avahi.SERVER_RUNNING:
248
227
            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)
254
228
    def activate(self):
255
229
        """Derived from the Avahi example code"""
256
230
        if self.server is None:
257
231
            self.server = dbus.Interface(
258
232
                self.bus.get_object(avahi.DBUS_NAME,
259
 
                                    avahi.DBUS_PATH_SERVER,
260
 
                                    follow_name_owner_changes=True),
 
233
                                    avahi.DBUS_PATH_SERVER),
261
234
                avahi.DBUS_INTERFACE_SERVER)
262
235
        self.server.connect_to_signal("StateChanged",
263
236
                                 self.server_state_changed)
264
237
        self.server_state_changed(self.server.GetState())
265
238
 
266
239
 
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
 
        
273
240
class Client(object):
274
241
    """A representation of a client host served by this server.
275
242
    
303
270
    secret:     bytestring; sent verbatim (over TLS) to client
304
271
    timeout:    datetime.timedelta(); How long from last_checked_ok
305
272
                                      until this client is disabled
306
 
    extended_timeout:   extra long timeout when password has been sent
307
273
    runtime_expansions: Allowed attributes for runtime expansion.
308
 
    expires:    datetime.datetime(); time (UTC) when a client will be
309
 
                disabled, or None
310
274
    """
311
275
    
312
276
    runtime_expansions = ("approval_delay", "approval_duration",
314
278
                          "host", "interval", "last_checked_ok",
315
279
                          "last_enabled", "name", "timeout")
316
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
    
317
288
    def timeout_milliseconds(self):
318
289
        "Return the 'timeout' attribute in milliseconds"
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)    
 
290
        return self._timedelta_to_milliseconds(self.timeout)
324
291
    
325
292
    def interval_milliseconds(self):
326
293
        "Return the 'interval' attribute in milliseconds"
327
 
        return _timedelta_to_milliseconds(self.interval)
328
 
    
 
294
        return self._timedelta_to_milliseconds(self.interval)
 
295
 
329
296
    def approval_delay_milliseconds(self):
330
 
        return _timedelta_to_milliseconds(self.approval_delay)
 
297
        return self._timedelta_to_milliseconds(self.approval_delay)
331
298
    
332
299
    def __init__(self, name = None, disable_hook=None, config=None):
333
300
        """Note: the 'checker' key in 'config' sets the
360
327
        self.last_enabled = None
361
328
        self.last_checked_ok = None
362
329
        self.timeout = string_to_delta(config["timeout"])
363
 
        self.extended_timeout = string_to_delta(config["extended_timeout"])
364
330
        self.interval = string_to_delta(config["interval"])
365
331
        self.disable_hook = disable_hook
366
332
        self.checker = None
367
333
        self.checker_initiator_tag = None
368
334
        self.disable_initiator_tag = None
369
 
        self.expires = None
370
335
        self.checker_callback_tag = None
371
336
        self.checker_command = config["checker"]
372
337
        self.current_checker_command = None
392
357
            # Already enabled
393
358
            return
394
359
        self.send_changedstate()
 
360
        self.last_enabled = datetime.datetime.utcnow()
395
361
        # Schedule a new checker to be started an 'interval' from now,
396
362
        # and every interval from then on.
397
363
        self.checker_initiator_tag = (gobject.timeout_add
398
364
                                      (self.interval_milliseconds(),
399
365
                                       self.start_checker))
400
366
        # Schedule a disable() when 'timeout' has passed
401
 
        self.expires = datetime.datetime.utcnow() + self.timeout
402
367
        self.disable_initiator_tag = (gobject.timeout_add
403
368
                                   (self.timeout_milliseconds(),
404
369
                                    self.disable))
405
370
        self.enabled = True
406
 
        self.last_enabled = datetime.datetime.utcnow()
407
371
        # Also start a new checker *right now*.
408
372
        self.start_checker()
409
373
    
418
382
        if getattr(self, "disable_initiator_tag", False):
419
383
            gobject.source_remove(self.disable_initiator_tag)
420
384
            self.disable_initiator_tag = None
421
 
        self.expires = None
422
385
        if getattr(self, "checker_initiator_tag", False):
423
386
            gobject.source_remove(self.checker_initiator_tag)
424
387
            self.checker_initiator_tag = None
450
413
            logger.warning("Checker for %(name)s crashed?",
451
414
                           vars(self))
452
415
    
453
 
    def checked_ok(self, timeout=None):
 
416
    def checked_ok(self):
454
417
        """Bump up the timeout for this client.
455
418
        
456
419
        This should only be called when the client has been seen,
457
420
        alive and well.
458
421
        """
459
 
        if timeout is None:
460
 
            timeout = self.timeout
461
422
        self.last_checked_ok = datetime.datetime.utcnow()
462
423
        gobject.source_remove(self.disable_initiator_tag)
463
 
        self.expires = datetime.datetime.utcnow() + timeout
464
424
        self.disable_initiator_tag = (gobject.timeout_add
465
 
                                      (_timedelta_to_milliseconds(timeout),
 
425
                                      (self.timeout_milliseconds(),
466
426
                                       self.disable))
467
427
    
468
428
    def need_approval(self):
485
445
        # If a checker exists, make sure it is not a zombie
486
446
        try:
487
447
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
488
 
        except (AttributeError, OSError) as error:
 
448
        except (AttributeError, OSError), error:
489
449
            if (isinstance(error, OSError)
490
450
                and error.errno != errno.ECHILD):
491
451
                raise error
509
469
                                       'replace')))
510
470
                    for attr in
511
471
                    self.runtime_expansions)
512
 
                
 
472
 
513
473
                try:
514
474
                    command = self.checker_command % escaped_attrs
515
 
                except TypeError as error:
 
475
                except TypeError, error:
516
476
                    logger.error('Could not format string "%s":'
517
477
                                 ' %s', self.checker_command, error)
518
478
                    return True # Try again later
537
497
                if pid:
538
498
                    gobject.source_remove(self.checker_callback_tag)
539
499
                    self.checker_callback(pid, status, command)
540
 
            except OSError as error:
 
500
            except OSError, error:
541
501
                logger.error("Failed to start subprocess: %s",
542
502
                             error)
543
503
        # Re-run this periodically if run by gobject.timeout_add
556
516
            #time.sleep(0.5)
557
517
            #if self.checker.poll() is None:
558
518
            #    os.kill(self.checker.pid, signal.SIGKILL)
559
 
        except OSError as error:
 
519
        except OSError, error:
560
520
            if error.errno != errno.ESRCH: # No such process
561
521
                raise
562
522
        self.checker = None
563
523
 
564
 
 
565
524
def dbus_service_property(dbus_interface, signature="v",
566
525
                          access="readwrite", byte_arrays=False):
567
526
    """Decorators for marking methods of a DBusObjectWithProperties to
613
572
 
614
573
class DBusObjectWithProperties(dbus.service.Object):
615
574
    """A D-Bus object with properties.
616
 
    
 
575
 
617
576
    Classes inheriting from this can use the dbus_service_property
618
577
    decorator to expose methods as D-Bus properties.  It exposes the
619
578
    standard Get(), Set(), and GetAll() methods on the D-Bus.
683
642
    def GetAll(self, interface_name):
684
643
        """Standard D-Bus property GetAll() method, see D-Bus
685
644
        standard.
686
 
        
 
645
 
687
646
        Note: Will not include properties with access="write".
688
647
        """
689
648
        all = {}
745
704
            xmlstring = document.toxml("utf-8")
746
705
            document.unlink()
747
706
        except (AttributeError, xml.dom.DOMException,
748
 
                xml.parsers.expat.ExpatError) as error:
 
707
                xml.parsers.expat.ExpatError), error:
749
708
            logger.error("Failed to override Introspection method",
750
709
                         error)
751
710
        return xmlstring
752
711
 
753
712
 
754
 
def datetime_to_dbus (dt, variant_level=0):
755
 
    """Convert a UTC datetime.datetime() to a D-Bus type."""
756
 
    if dt is None:
757
 
        return dbus.String("", variant_level = variant_level)
758
 
    return dbus.String(dt.isoformat(),
759
 
                       variant_level=variant_level)
760
 
 
761
 
 
762
713
class ClientDBus(Client, DBusObjectWithProperties):
763
714
    """A Client class using D-Bus
764
715
    
786
737
        DBusObjectWithProperties.__init__(self, self.bus,
787
738
                                          self.dbus_object_path)
788
739
        
789
 
    def notifychangeproperty(transform_func,
790
 
                             dbus_name, type_func=lambda x: x,
791
 
                             variant_level=1):
792
 
        """ Modify a variable so that its a property that announce its
793
 
        changes to DBus.
794
 
        transform_fun: Function that takes a value and transform it to
795
 
                       DBus type.
796
 
        dbus_name: DBus name of the variable
797
 
        type_func: Function that transform the value before sending it
798
 
                   to DBus
799
 
        variant_level: DBus variant level. default: 1
800
 
        """
801
 
        real_value = [None,]
802
 
        def setter(self, value):
803
 
            old_value = real_value[0]
804
 
            real_value[0] = value
805
 
            if hasattr(self, "dbus_object_path"):
806
 
                if type_func(old_value) != type_func(real_value[0]):
807
 
                    dbus_value = transform_func(type_func(real_value[0]),
808
 
                                                variant_level)
809
 
                    self.PropertyChanged(dbus.String(dbus_name),
810
 
                                         dbus_value)
811
 
        
812
 
        return property(lambda self: real_value[0], setter)
813
 
    
814
 
    
815
 
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
816
 
    approvals_pending = notifychangeproperty(dbus.Boolean,
817
 
                                             "ApprovalPending",
818
 
                                             type_func = bool)
819
 
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
820
 
    last_enabled = notifychangeproperty(datetime_to_dbus,
821
 
                                        "LastEnabled")
822
 
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
823
 
                                   type_func = lambda checker: checker is not None)
824
 
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
825
 
                                           "LastCheckedOK")
826
 
    last_approval_request = notifychangeproperty(datetime_to_dbus,
827
 
                                                 "LastApprovalRequest")
828
 
    approved_by_default = notifychangeproperty(dbus.Boolean,
829
 
                                               "ApprovedByDefault")
830
 
    approval_delay = notifychangeproperty(dbus.UInt16, "ApprovalDelay",
831
 
                                          type_func = _timedelta_to_milliseconds)
832
 
    approval_duration = notifychangeproperty(dbus.UInt16, "ApprovalDuration",
833
 
                                             type_func = _timedelta_to_milliseconds)
834
 
    host = notifychangeproperty(dbus.String, "Host")
835
 
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
836
 
                                   type_func = _timedelta_to_milliseconds)
837
 
    extended_timeout = notifychangeproperty(dbus.UInt16, "ExtendedTimeout",
838
 
                                            type_func = _timedelta_to_milliseconds)
839
 
    interval = notifychangeproperty(dbus.UInt16, "Interval",
840
 
                                    type_func = _timedelta_to_milliseconds)
841
 
    checker_command = notifychangeproperty(dbus.String, "Checker")
842
 
    
843
 
    del notifychangeproperty
 
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
844
783
    
845
784
    def __del__(self, *args, **kwargs):
846
785
        try:
855
794
                         *args, **kwargs):
856
795
        self.checker_callback_tag = None
857
796
        self.checker = None
 
797
        # Emit D-Bus signal
 
798
        self.PropertyChanged(dbus.String("CheckerRunning"),
 
799
                             dbus.Boolean(False, variant_level=1))
858
800
        if os.WIFEXITED(condition):
859
801
            exitstatus = os.WEXITSTATUS(condition)
860
802
            # Emit D-Bus signal
870
812
        return Client.checker_callback(self, pid, condition, command,
871
813
                                       *args, **kwargs)
872
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
    
873
833
    def start_checker(self, *args, **kwargs):
874
834
        old_checker = self.checker
875
835
        if self.checker is not None:
882
842
            and old_checker_pid != self.checker.pid):
883
843
            # Emit D-Bus signal
884
844
            self.CheckerStarted(self.current_checker_command)
 
845
            self.PropertyChanged(
 
846
                dbus.String("CheckerRunning"),
 
847
                dbus.Boolean(True, variant_level=1))
885
848
        return r
886
849
    
 
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
 
887
859
    def _reset_approved(self):
888
860
        self._approved = None
889
861
        return False
891
863
    def approve(self, value=True):
892
864
        self.send_changedstate()
893
865
        self._approved = value
894
 
        gobject.timeout_add(_timedelta_to_milliseconds
 
866
        gobject.timeout_add(self._timedelta_to_milliseconds
895
867
                            (self.approval_duration),
896
868
                            self._reset_approved)
897
869
    
950
922
    # CheckedOK - method
951
923
    @dbus.service.method(_interface)
952
924
    def CheckedOK(self):
953
 
        self.checked_ok()
 
925
        return self.checked_ok()
954
926
    
955
927
    # Enable - method
956
928
    @dbus.service.method(_interface)
989
961
        if value is None:       # get
990
962
            return dbus.Boolean(self.approved_by_default)
991
963
        self.approved_by_default = bool(value)
 
964
        # Emit D-Bus signal
 
965
        self.PropertyChanged(dbus.String("ApprovedByDefault"),
 
966
                             dbus.Boolean(value, variant_level=1))
992
967
    
993
968
    # ApprovalDelay - property
994
969
    @dbus_service_property(_interface, signature="t",
997
972
        if value is None:       # get
998
973
            return dbus.UInt64(self.approval_delay_milliseconds())
999
974
        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))
1000
978
    
1001
979
    # ApprovalDuration - property
1002
980
    @dbus_service_property(_interface, signature="t",
1003
981
                           access="readwrite")
1004
982
    def ApprovalDuration_dbus_property(self, value=None):
1005
983
        if value is None:       # get
1006
 
            return dbus.UInt64(_timedelta_to_milliseconds(
 
984
            return dbus.UInt64(self._timedelta_to_milliseconds(
1007
985
                    self.approval_duration))
1008
986
        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))
1009
990
    
1010
991
    # Name - property
1011
992
    @dbus_service_property(_interface, signature="s", access="read")
1024
1005
        if value is None:       # get
1025
1006
            return dbus.String(self.host)
1026
1007
        self.host = value
 
1008
        # Emit D-Bus signal
 
1009
        self.PropertyChanged(dbus.String("Host"),
 
1010
                             dbus.String(value, variant_level=1))
1027
1011
    
1028
1012
    # Created - property
1029
1013
    @dbus_service_property(_interface, signature="s", access="read")
1030
1014
    def Created_dbus_property(self):
1031
 
        return dbus.String(datetime_to_dbus(self.created))
 
1015
        return dbus.String(self._datetime_to_dbus(self.created))
1032
1016
    
1033
1017
    # LastEnabled - property
1034
1018
    @dbus_service_property(_interface, signature="s", access="read")
1035
1019
    def LastEnabled_dbus_property(self):
1036
 
        return datetime_to_dbus(self.last_enabled)
 
1020
        if self.last_enabled is None:
 
1021
            return dbus.String("")
 
1022
        return dbus.String(self._datetime_to_dbus(self.last_enabled))
1037
1023
    
1038
1024
    # Enabled - property
1039
1025
    @dbus_service_property(_interface, signature="b",
1053
1039
        if value is not None:
1054
1040
            self.checked_ok()
1055
1041
            return
1056
 
        return datetime_to_dbus(self.last_checked_ok)
1057
 
    
1058
 
    # Expires - property
1059
 
    @dbus_service_property(_interface, signature="s", access="read")
1060
 
    def Expires_dbus_property(self):
1061
 
        return datetime_to_dbus(self.expires)
 
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))
1062
1046
    
1063
1047
    # LastApprovalRequest - property
1064
1048
    @dbus_service_property(_interface, signature="s", access="read")
1065
1049
    def LastApprovalRequest_dbus_property(self):
1066
 
        return datetime_to_dbus(self.last_approval_request)
 
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))
1067
1055
    
1068
1056
    # Timeout - property
1069
1057
    @dbus_service_property(_interface, signature="t",
1072
1060
        if value is None:       # get
1073
1061
            return dbus.UInt64(self.timeout_milliseconds())
1074
1062
        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))
1075
1066
        if getattr(self, "disable_initiator_tag", None) is None:
1076
1067
            return
1077
1068
        # Reschedule timeout
1078
1069
        gobject.source_remove(self.disable_initiator_tag)
1079
1070
        self.disable_initiator_tag = None
1080
 
        self.expires = None
1081
1071
        time_to_die = (self.
1082
1072
                       _timedelta_to_milliseconds((self
1083
1073
                                                   .last_checked_ok
1088
1078
            # The timeout has passed
1089
1079
            self.disable()
1090
1080
        else:
1091
 
            self.expires = (datetime.datetime.utcnow()
1092
 
                            + datetime.timedelta(milliseconds = time_to_die))
1093
1081
            self.disable_initiator_tag = (gobject.timeout_add
1094
1082
                                          (time_to_die, self.disable))
1095
1083
    
1096
 
    # ExtendedTimeout - property
1097
 
    @dbus_service_property(_interface, signature="t",
1098
 
                           access="readwrite")
1099
 
    def ExtendedTimeout_dbus_property(self, value=None):
1100
 
        if value is None:       # get
1101
 
            return dbus.UInt64(self.extended_timeout_milliseconds())
1102
 
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1103
 
    
1104
1084
    # Interval - property
1105
1085
    @dbus_service_property(_interface, signature="t",
1106
1086
                           access="readwrite")
1108
1088
        if value is None:       # get
1109
1089
            return dbus.UInt64(self.interval_milliseconds())
1110
1090
        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))
1111
1094
        if getattr(self, "checker_initiator_tag", None) is None:
1112
1095
            return
1113
1096
        # Reschedule checker run
1115
1098
        self.checker_initiator_tag = (gobject.timeout_add
1116
1099
                                      (value, self.start_checker))
1117
1100
        self.start_checker()    # Start one now, too
1118
 
    
 
1101
 
1119
1102
    # Checker - property
1120
1103
    @dbus_service_property(_interface, signature="s",
1121
1104
                           access="readwrite")
1123
1106
        if value is None:       # get
1124
1107
            return dbus.String(self.checker_command)
1125
1108
        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))
1126
1113
    
1127
1114
    # CheckerRunning - property
1128
1115
    @dbus_service_property(_interface, signature="b",
1155
1142
        self._pipe.send(('init', fpr, address))
1156
1143
        if not self._pipe.recv():
1157
1144
            raise KeyError()
1158
 
    
 
1145
 
1159
1146
    def __getattribute__(self, name):
1160
1147
        if(name == '_pipe'):
1161
1148
            return super(ProxyClient, self).__getattribute__(name)
1168
1155
                self._pipe.send(('funcall', name, args, kwargs))
1169
1156
                return self._pipe.recv()[1]
1170
1157
            return func
1171
 
    
 
1158
 
1172
1159
    def __setattr__(self, name, value):
1173
1160
        if(name == '_pipe'):
1174
1161
            return super(ProxyClient, self).__setattr__(name, value)
1187
1174
                        unicode(self.client_address))
1188
1175
            logger.debug("Pipe FD: %d",
1189
1176
                         self.server.child_pipe.fileno())
1190
 
            
 
1177
 
1191
1178
            session = (gnutls.connection
1192
1179
                       .ClientSession(self.request,
1193
1180
                                      gnutls.connection
1194
1181
                                      .X509Credentials()))
1195
 
            
 
1182
 
1196
1183
            # Note: gnutls.connection.X509Credentials is really a
1197
1184
            # generic GnuTLS certificate credentials object so long as
1198
1185
            # no X.509 keys are added to it.  Therefore, we can use it
1199
1186
            # here despite using OpenPGP certificates.
1200
 
            
 
1187
 
1201
1188
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
1202
1189
            #                      "+AES-256-CBC", "+SHA1",
1203
1190
            #                      "+COMP-NULL", "+CTYPE-OPENPGP",
1209
1196
            (gnutls.library.functions
1210
1197
             .gnutls_priority_set_direct(session._c_object,
1211
1198
                                         priority, None))
1212
 
            
 
1199
 
1213
1200
            # Start communication using the Mandos protocol
1214
1201
            # Get protocol number
1215
1202
            line = self.request.makefile().readline()
1217
1204
            try:
1218
1205
                if int(line.strip().split()[0]) > 1:
1219
1206
                    raise RuntimeError
1220
 
            except (ValueError, IndexError, RuntimeError) as error:
 
1207
            except (ValueError, IndexError, RuntimeError), error:
1221
1208
                logger.error("Unknown protocol version: %s", error)
1222
1209
                return
1223
 
            
 
1210
 
1224
1211
            # Start GnuTLS connection
1225
1212
            try:
1226
1213
                session.handshake()
1227
 
            except gnutls.errors.GNUTLSError as error:
 
1214
            except gnutls.errors.GNUTLSError, error:
1228
1215
                logger.warning("Handshake failed: %s", error)
1229
1216
                # Do not run session.bye() here: the session is not
1230
1217
                # established.  Just abandon the request.
1231
1218
                return
1232
1219
            logger.debug("Handshake succeeded")
1233
 
            
 
1220
 
1234
1221
            approval_required = False
1235
1222
            try:
1236
1223
                try:
1237
1224
                    fpr = self.fingerprint(self.peer_certificate
1238
1225
                                           (session))
1239
 
                except (TypeError,
1240
 
                        gnutls.errors.GNUTLSError) as error:
 
1226
                except (TypeError, gnutls.errors.GNUTLSError), error:
1241
1227
                    logger.warning("Bad certificate: %s", error)
1242
1228
                    return
1243
1229
                logger.debug("Fingerprint: %s", fpr)
1244
 
                
 
1230
 
1245
1231
                try:
1246
1232
                    client = ProxyClient(child_pipe, fpr,
1247
1233
                                         self.client_address)
1255
1241
                
1256
1242
                while True:
1257
1243
                    if not client.enabled:
1258
 
                        logger.info("Client %s is disabled",
 
1244
                        logger.warning("Client %s is disabled",
1259
1245
                                       client.name)
1260
1246
                        if self.server.use_dbus:
1261
1247
                            # Emit D-Bus signal
1306
1292
                while sent_size < len(client.secret):
1307
1293
                    try:
1308
1294
                        sent = session.send(client.secret[sent_size:])
1309
 
                    except gnutls.errors.GNUTLSError as error:
 
1295
                    except (gnutls.errors.GNUTLSError), error:
1310
1296
                        logger.warning("gnutls send failed")
1311
1297
                        return
1312
1298
                    logger.debug("Sent: %d, remaining: %d",
1313
1299
                                 sent, len(client.secret)
1314
1300
                                 - (sent_size + sent))
1315
1301
                    sent_size += sent
1316
 
                
 
1302
 
1317
1303
                logger.info("Sending secret to %s", client.name)
1318
1304
                # bump the timeout as if seen
1319
 
                client.checked_ok(client.extended_timeout)
 
1305
                client.checked_ok()
1320
1306
                if self.server.use_dbus:
1321
1307
                    # Emit D-Bus signal
1322
1308
                    client.GotSecret()
1326
1312
                    client.approvals_pending -= 1
1327
1313
                try:
1328
1314
                    session.bye()
1329
 
                except gnutls.errors.GNUTLSError as error:
 
1315
                except (gnutls.errors.GNUTLSError), error:
1330
1316
                    logger.warning("GnuTLS bye failed")
1331
1317
    
1332
1318
    @staticmethod
1407
1393
        multiprocessing.Process(target = self.sub_process_main,
1408
1394
                                args = (request, address)).start()
1409
1395
 
1410
 
 
1411
1396
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1412
1397
    """ adds a pipe to the MixIn """
1413
1398
    def process_request(self, request, client_address):
1416
1401
        This function creates a new pipe in self.pipe
1417
1402
        """
1418
1403
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
1419
 
        
 
1404
 
1420
1405
        super(MultiprocessingMixInWithPipe,
1421
1406
              self).process_request(request, client_address)
1422
1407
        self.child_pipe.close()
1423
1408
        self.add_pipe(parent_pipe)
1424
 
    
 
1409
 
1425
1410
    def add_pipe(self, parent_pipe):
1426
1411
        """Dummy function; override as necessary"""
1427
1412
        raise NotImplementedError
1428
1413
 
1429
 
 
1430
1414
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1431
1415
                     socketserver.TCPServer, object):
1432
1416
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1458
1442
                                           SO_BINDTODEVICE,
1459
1443
                                           str(self.interface
1460
1444
                                               + '\0'))
1461
 
                except socket.error as error:
 
1445
                except socket.error, error:
1462
1446
                    if error[0] == errno.EPERM:
1463
1447
                        logger.error("No permission to"
1464
1448
                                     " bind to interface %s",
1558
1542
                    client = c
1559
1543
                    break
1560
1544
            else:
1561
 
                logger.info("Client not found for fingerprint: %s, ad"
1562
 
                            "dress: %s", fpr, address)
 
1545
                logger.warning("Client not found for fingerprint: %s, ad"
 
1546
                               "dress: %s", fpr, address)
1563
1547
                if self.use_dbus:
1564
1548
                    # Emit D-Bus signal
1565
1549
                    mandos_dbus_service.ClientNotFound(fpr, address[0])
1580
1564
            kwargs = request[3]
1581
1565
            
1582
1566
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1583
 
        
 
1567
 
1584
1568
        if command == 'getattr':
1585
1569
            attrname = request[1]
1586
1570
            if callable(client_object.__getattribute__(attrname)):
1592
1576
            attrname = request[1]
1593
1577
            value = request[2]
1594
1578
            setattr(client_object, attrname, value)
1595
 
        
 
1579
 
1596
1580
        return True
1597
1581
 
1598
1582
 
1629
1613
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1630
1614
            else:
1631
1615
                raise ValueError("Unknown suffix %r" % suffix)
1632
 
        except (ValueError, IndexError) as e:
 
1616
        except (ValueError, IndexError), e:
1633
1617
            raise ValueError(*(e.args))
1634
1618
        timevalue += delta
1635
1619
    return timevalue
1689
1673
    ##################################################################
1690
1674
    # Parsing of options, both command line and config file
1691
1675
    
1692
 
    parser = argparse.ArgumentParser()
1693
 
    parser.add_argument("-v", "--version", action="version",
1694
 
                        version = "%%(prog)s %s" % version,
1695
 
                        help="show version number and exit")
1696
 
    parser.add_argument("-i", "--interface", metavar="IF",
1697
 
                        help="Bind to interface IF")
1698
 
    parser.add_argument("-a", "--address",
1699
 
                        help="Address to listen for requests on")
1700
 
    parser.add_argument("-p", "--port", type=int,
1701
 
                        help="Port number to receive requests on")
1702
 
    parser.add_argument("--check", action="store_true",
1703
 
                        help="Run self-test")
1704
 
    parser.add_argument("--debug", action="store_true",
1705
 
                        help="Debug mode; run in foreground and log"
1706
 
                        " to terminal")
1707
 
    parser.add_argument("--debuglevel", metavar="LEVEL",
1708
 
                        help="Debug level for stdout output")
1709
 
    parser.add_argument("--priority", help="GnuTLS"
1710
 
                        " priority string (see GnuTLS documentation)")
1711
 
    parser.add_argument("--servicename",
1712
 
                        metavar="NAME", help="Zeroconf service name")
1713
 
    parser.add_argument("--configdir",
1714
 
                        default="/etc/mandos", metavar="DIR",
1715
 
                        help="Directory to search for configuration"
1716
 
                        " files")
1717
 
    parser.add_argument("--no-dbus", action="store_false",
1718
 
                        dest="use_dbus", help="Do not provide D-Bus"
1719
 
                        " system bus interface")
1720
 
    parser.add_argument("--no-ipv6", action="store_false",
1721
 
                        dest="use_ipv6", help="Do not use IPv6")
1722
 
    options = parser.parse_args()
 
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]
1723
1704
    
1724
1705
    if options.check:
1725
1706
        import doctest
1777
1758
    debuglevel = server_settings["debuglevel"]
1778
1759
    use_dbus = server_settings["use_dbus"]
1779
1760
    use_ipv6 = server_settings["use_ipv6"]
1780
 
    
 
1761
 
1781
1762
    if server_settings["servicename"] != "Mandos":
1782
1763
        syslogger.setFormatter(logging.Formatter
1783
1764
                               ('Mandos (%s) [%%(process)d]:'
1785
1766
                                % server_settings["servicename"]))
1786
1767
    
1787
1768
    # Parse config file with clients
1788
 
    client_defaults = { "timeout": "5m",
1789
 
                        "extended_timeout": "15m",
1790
 
                        "interval": "2m",
 
1769
    client_defaults = { "timeout": "1h",
 
1770
                        "interval": "5m",
1791
1771
                        "checker": "fping -q -- %%(host)s",
1792
1772
                        "host": "",
1793
1773
                        "approval_delay": "0s",
1833
1813
    try:
1834
1814
        os.setgid(gid)
1835
1815
        os.setuid(uid)
1836
 
    except OSError as error:
 
1816
    except OSError, error:
1837
1817
        if error[0] != errno.EPERM:
1838
1818
            raise error
1839
1819
    
1844
1824
        level = getattr(logging, debuglevel.upper())
1845
1825
        syslogger.setLevel(level)
1846
1826
        console.setLevel(level)
1847
 
    
 
1827
 
1848
1828
    if debug:
1849
1829
        # Enable all possible GnuTLS debugging
1850
1830
        
1883
1863
        try:
1884
1864
            bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
1885
1865
                                            bus, do_not_queue=True)
1886
 
        except dbus.exceptions.NameExistsException as e:
 
1866
        except dbus.exceptions.NameExistsException, e:
1887
1867
            logger.error(unicode(e) + ", disabling D-Bus")
1888
1868
            use_dbus = False
1889
1869
            server_settings["use_dbus"] = False
1937
1917
        del pidfilename
1938
1918
        
1939
1919
        signal.signal(signal.SIGINT, signal.SIG_IGN)
1940
 
    
 
1920
 
1941
1921
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1942
1922
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1943
1923
    
2039
2019
        # From the Avahi example code
2040
2020
        try:
2041
2021
            service.activate()
2042
 
        except dbus.exceptions.DBusException as error:
 
2022
        except dbus.exceptions.DBusException, error:
2043
2023
            logger.critical("DBusException: %s", error)
2044
2024
            cleanup()
2045
2025
            sys.exit(1)
2052
2032
        
2053
2033
        logger.debug("Starting main loop")
2054
2034
        main_loop.run()
2055
 
    except AvahiError as error:
 
2035
    except AvahiError, error:
2056
2036
        logger.critical("AvahiError: %s", error)
2057
2037
        cleanup()
2058
2038
        sys.exit(1)
2064
2044
    # Must run before the D-Bus bus name gets deregistered
2065
2045
    cleanup()
2066
2046
 
2067
 
 
2068
2047
if __name__ == '__main__':
2069
2048
    main()