/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-12-21 17:08:29 UTC
  • mto: This revision was merged to the branch mainline in revision 527.
  • Revision ID: teddy@recompile.se-20111221170829-gf93q0kejpgh3ur3
* debian/control (mandos-client/Depends): Added "initramfs-tools".

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
# along with this program.  If not, see
29
29
# <http://www.gnu.org/licenses/>.
30
30
31
 
# Contact the authors at <mandos@fukt.bsnet.se>.
 
31
# Contact the authors at <mandos@recompile.se>.
32
32
33
33
 
34
34
from __future__ import (division, absolute_import, print_function,
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
62
62
import functools
63
63
import cPickle as pickle
64
64
import multiprocessing
 
65
import types
65
66
 
66
67
import dbus
67
68
import dbus.service
82
83
        SO_BINDTODEVICE = None
83
84
 
84
85
 
85
 
version = "1.2.3"
 
86
version = "1.4.1"
86
87
 
87
 
#logger = logging.getLogger('mandos')
88
 
logger = logging.Logger('mandos')
 
88
logger = logging.getLogger()
89
89
syslogger = (logging.handlers.SysLogHandler
90
90
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
91
91
              address = str("/dev/log")))
95
95
logger.addHandler(syslogger)
96
96
 
97
97
console = logging.StreamHandler()
98
 
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
 
98
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
 
99
                                       ' [%(process)d]:'
99
100
                                       ' %(levelname)s:'
100
101
                                       ' %(message)s'))
101
102
logger.addHandler(console)
102
103
 
 
104
 
103
105
class AvahiError(Exception):
104
106
    def __init__(self, value, *args, **kwargs):
105
107
        self.value = value
151
153
        self.group = None       # our entry group
152
154
        self.server = None
153
155
        self.bus = bus
 
156
        self.entry_group_state_changed_match = None
154
157
    def rename(self):
155
158
        """Derived from the Avahi example code"""
156
159
        if self.rename_count >= self.max_renames:
158
161
                            " after %i retries, exiting.",
159
162
                            self.rename_count)
160
163
            raise AvahiServiceError("Too many renames")
161
 
        self.name = unicode(self.server.GetAlternativeServiceName(self.name))
 
164
        self.name = unicode(self.server
 
165
                            .GetAlternativeServiceName(self.name))
162
166
        logger.info("Changing Zeroconf service name to %r ...",
163
167
                    self.name)
164
 
        syslogger.setFormatter(logging.Formatter
165
 
                               ('Mandos (%s) [%%(process)d]:'
166
 
                                ' %%(levelname)s: %%(message)s'
167
 
                                % self.name))
168
168
        self.remove()
169
169
        try:
170
170
            self.add()
171
 
        except dbus.exceptions.DBusException, error:
 
171
        except dbus.exceptions.DBusException as error:
172
172
            logger.critical("DBusException: %s", error)
173
173
            self.cleanup()
174
174
            os._exit(1)
175
175
        self.rename_count += 1
176
176
    def remove(self):
177
177
        """Derived from the Avahi example code"""
 
178
        if self.entry_group_state_changed_match is not None:
 
179
            self.entry_group_state_changed_match.remove()
 
180
            self.entry_group_state_changed_match = None
178
181
        if self.group is not None:
179
182
            self.group.Reset()
180
183
    def add(self):
181
184
        """Derived from the Avahi example code"""
 
185
        self.remove()
182
186
        if self.group is None:
183
187
            self.group = dbus.Interface(
184
188
                self.bus.get_object(avahi.DBUS_NAME,
185
189
                                    self.server.EntryGroupNew()),
186
190
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
187
 
            self.group.connect_to_signal('StateChanged',
188
 
                                         self
189
 
                                         .entry_group_state_changed)
 
191
        self.entry_group_state_changed_match = (
 
192
            self.group.connect_to_signal(
 
193
                'StateChanged', self.entry_group_state_changed))
190
194
        logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
191
195
                     self.name, self.type)
192
196
        self.group.AddService(
215
219
    def cleanup(self):
216
220
        """Derived from the Avahi example code"""
217
221
        if self.group is not None:
218
 
            self.group.Free()
 
222
            try:
 
223
                self.group.Free()
 
224
            except (dbus.exceptions.UnknownMethodException,
 
225
                    dbus.exceptions.DBusException) as e:
 
226
                pass
219
227
            self.group = None
220
 
    def server_state_changed(self, state):
 
228
        self.remove()
 
229
    def server_state_changed(self, state, error=None):
221
230
        """Derived from the Avahi example code"""
222
231
        logger.debug("Avahi server state change: %i", state)
223
 
        if state == avahi.SERVER_COLLISION:
224
 
            logger.error("Zeroconf server name collision")
225
 
            self.remove()
 
232
        bad_states = { avahi.SERVER_INVALID:
 
233
                           "Zeroconf server invalid",
 
234
                       avahi.SERVER_REGISTERING: None,
 
235
                       avahi.SERVER_COLLISION:
 
236
                           "Zeroconf server name collision",
 
237
                       avahi.SERVER_FAILURE:
 
238
                           "Zeroconf server failure" }
 
239
        if state in bad_states:
 
240
            if bad_states[state] is not None:
 
241
                if error is None:
 
242
                    logger.error(bad_states[state])
 
243
                else:
 
244
                    logger.error(bad_states[state] + ": %r", error)
 
245
            self.cleanup()
226
246
        elif state == avahi.SERVER_RUNNING:
227
247
            self.add()
 
248
        else:
 
249
            if error is None:
 
250
                logger.debug("Unknown state: %r", state)
 
251
            else:
 
252
                logger.debug("Unknown state: %r: %r", state, error)
228
253
    def activate(self):
229
254
        """Derived from the Avahi example code"""
230
255
        if self.server is None:
231
256
            self.server = dbus.Interface(
232
257
                self.bus.get_object(avahi.DBUS_NAME,
233
 
                                    avahi.DBUS_PATH_SERVER),
 
258
                                    avahi.DBUS_PATH_SERVER,
 
259
                                    follow_name_owner_changes=True),
234
260
                avahi.DBUS_INTERFACE_SERVER)
235
261
        self.server.connect_to_signal("StateChanged",
236
262
                                 self.server_state_changed)
237
263
        self.server_state_changed(self.server.GetState())
238
264
 
 
265
class AvahiServiceToSyslog(AvahiService):
 
266
    def rename(self):
 
267
        """Add the new name to the syslog messages"""
 
268
        ret = AvahiService.rename(self)
 
269
        syslogger.setFormatter(logging.Formatter
 
270
                               ('Mandos (%s) [%%(process)d]:'
 
271
                                ' %%(levelname)s: %%(message)s'
 
272
                                % self.name))
 
273
        return ret
239
274
 
 
275
def _timedelta_to_milliseconds(td):
 
276
    "Convert a datetime.timedelta() to milliseconds"
 
277
    return ((td.days * 24 * 60 * 60 * 1000)
 
278
            + (td.seconds * 1000)
 
279
            + (td.microseconds // 1000))
 
280
        
240
281
class Client(object):
241
282
    """A representation of a client host served by this server.
242
283
    
270
311
    secret:     bytestring; sent verbatim (over TLS) to client
271
312
    timeout:    datetime.timedelta(); How long from last_checked_ok
272
313
                                      until this client is disabled
 
314
    extended_timeout:   extra long timeout when password has been sent
273
315
    runtime_expansions: Allowed attributes for runtime expansion.
 
316
    expires:    datetime.datetime(); time (UTC) when a client will be
 
317
                disabled, or None
274
318
    """
275
319
    
276
320
    runtime_expansions = ("approval_delay", "approval_duration",
278
322
                          "host", "interval", "last_checked_ok",
279
323
                          "last_enabled", "name", "timeout")
280
324
    
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
 
    
288
325
    def timeout_milliseconds(self):
289
326
        "Return the 'timeout' attribute in milliseconds"
290
 
        return self._timedelta_to_milliseconds(self.timeout)
 
327
        return _timedelta_to_milliseconds(self.timeout)
 
328
    
 
329
    def extended_timeout_milliseconds(self):
 
330
        "Return the 'extended_timeout' attribute in milliseconds"
 
331
        return _timedelta_to_milliseconds(self.extended_timeout)
291
332
    
292
333
    def interval_milliseconds(self):
293
334
        "Return the 'interval' attribute in milliseconds"
294
 
        return self._timedelta_to_milliseconds(self.interval)
295
 
 
 
335
        return _timedelta_to_milliseconds(self.interval)
 
336
    
296
337
    def approval_delay_milliseconds(self):
297
 
        return self._timedelta_to_milliseconds(self.approval_delay)
 
338
        return _timedelta_to_milliseconds(self.approval_delay)
298
339
    
299
340
    def __init__(self, name = None, disable_hook=None, config=None):
300
341
        """Note: the 'checker' key in 'config' sets the
327
368
        self.last_enabled = None
328
369
        self.last_checked_ok = None
329
370
        self.timeout = string_to_delta(config["timeout"])
 
371
        self.extended_timeout = string_to_delta(config
 
372
                                                ["extended_timeout"])
330
373
        self.interval = string_to_delta(config["interval"])
331
374
        self.disable_hook = disable_hook
332
375
        self.checker = None
333
376
        self.checker_initiator_tag = None
334
377
        self.disable_initiator_tag = None
 
378
        self.expires = None
335
379
        self.checker_callback_tag = None
336
380
        self.checker_command = config["checker"]
337
381
        self.current_checker_command = None
344
388
            config["approval_delay"])
345
389
        self.approval_duration = string_to_delta(
346
390
            config["approval_duration"])
347
 
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
 
391
        self.changedstate = (multiprocessing_manager
 
392
                             .Condition(multiprocessing_manager
 
393
                                        .Lock()))
348
394
    
349
395
    def send_changedstate(self):
350
396
        self.changedstate.acquire()
351
397
        self.changedstate.notify_all()
352
398
        self.changedstate.release()
353
 
        
 
399
    
354
400
    def enable(self):
355
401
        """Start this client's checker and timeout hooks"""
356
402
        if getattr(self, "enabled", False):
357
403
            # Already enabled
358
404
            return
359
405
        self.send_changedstate()
360
 
        self.last_enabled = datetime.datetime.utcnow()
361
406
        # Schedule a new checker to be started an 'interval' from now,
362
407
        # and every interval from then on.
363
408
        self.checker_initiator_tag = (gobject.timeout_add
364
409
                                      (self.interval_milliseconds(),
365
410
                                       self.start_checker))
366
411
        # Schedule a disable() when 'timeout' has passed
 
412
        self.expires = datetime.datetime.utcnow() + self.timeout
367
413
        self.disable_initiator_tag = (gobject.timeout_add
368
414
                                   (self.timeout_milliseconds(),
369
415
                                    self.disable))
370
416
        self.enabled = True
 
417
        self.last_enabled = datetime.datetime.utcnow()
371
418
        # Also start a new checker *right now*.
372
419
        self.start_checker()
373
420
    
382
429
        if getattr(self, "disable_initiator_tag", False):
383
430
            gobject.source_remove(self.disable_initiator_tag)
384
431
            self.disable_initiator_tag = None
 
432
        self.expires = None
385
433
        if getattr(self, "checker_initiator_tag", False):
386
434
            gobject.source_remove(self.checker_initiator_tag)
387
435
            self.checker_initiator_tag = None
413
461
            logger.warning("Checker for %(name)s crashed?",
414
462
                           vars(self))
415
463
    
416
 
    def checked_ok(self):
 
464
    def checked_ok(self, timeout=None):
417
465
        """Bump up the timeout for this client.
418
466
        
419
467
        This should only be called when the client has been seen,
420
468
        alive and well.
421
469
        """
 
470
        if timeout is None:
 
471
            timeout = self.timeout
422
472
        self.last_checked_ok = datetime.datetime.utcnow()
423
 
        gobject.source_remove(self.disable_initiator_tag)
424
 
        self.disable_initiator_tag = (gobject.timeout_add
425
 
                                      (self.timeout_milliseconds(),
426
 
                                       self.disable))
 
473
        if self.disable_initiator_tag is not None:
 
474
            gobject.source_remove(self.disable_initiator_tag)
 
475
        if getattr(self, "enabled", False):
 
476
            self.disable_initiator_tag = (gobject.timeout_add
 
477
                                          (_timedelta_to_milliseconds
 
478
                                           (timeout), self.disable))
 
479
            self.expires = datetime.datetime.utcnow() + timeout
427
480
    
428
481
    def need_approval(self):
429
482
        self.last_approval_request = datetime.datetime.utcnow()
445
498
        # If a checker exists, make sure it is not a zombie
446
499
        try:
447
500
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
448
 
        except (AttributeError, OSError), error:
 
501
        except (AttributeError, OSError) as error:
449
502
            if (isinstance(error, OSError)
450
503
                and error.errno != errno.ECHILD):
451
504
                raise error
469
522
                                       'replace')))
470
523
                    for attr in
471
524
                    self.runtime_expansions)
472
 
 
 
525
                
473
526
                try:
474
527
                    command = self.checker_command % escaped_attrs
475
 
                except TypeError, error:
 
528
                except TypeError as error:
476
529
                    logger.error('Could not format string "%s":'
477
530
                                 ' %s', self.checker_command, error)
478
531
                    return True # Try again later
497
550
                if pid:
498
551
                    gobject.source_remove(self.checker_callback_tag)
499
552
                    self.checker_callback(pid, status, command)
500
 
            except OSError, error:
 
553
            except OSError as error:
501
554
                logger.error("Failed to start subprocess: %s",
502
555
                             error)
503
556
        # Re-run this periodically if run by gobject.timeout_add
516
569
            #time.sleep(0.5)
517
570
            #if self.checker.poll() is None:
518
571
            #    os.kill(self.checker.pid, signal.SIGKILL)
519
 
        except OSError, error:
 
572
        except OSError as error:
520
573
            if error.errno != errno.ESRCH: # No such process
521
574
                raise
522
575
        self.checker = None
523
576
 
 
577
 
524
578
def dbus_service_property(dbus_interface, signature="v",
525
579
                          access="readwrite", byte_arrays=False):
526
580
    """Decorators for marking methods of a DBusObjectWithProperties to
572
626
 
573
627
class DBusObjectWithProperties(dbus.service.Object):
574
628
    """A D-Bus object with properties.
575
 
 
 
629
    
576
630
    Classes inheriting from this can use the dbus_service_property
577
631
    decorator to expose methods as D-Bus properties.  It exposes the
578
632
    standard Get(), Set(), and GetAll() methods on the D-Bus.
585
639
    def _get_all_dbus_properties(self):
586
640
        """Returns a generator of (name, attribute) pairs
587
641
        """
588
 
        return ((prop._dbus_name, prop)
 
642
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
 
643
                for cls in self.__class__.__mro__
589
644
                for name, prop in
590
 
                inspect.getmembers(self, self._is_dbus_property))
 
645
                inspect.getmembers(cls, self._is_dbus_property))
591
646
    
592
647
    def _get_dbus_property(self, interface_name, property_name):
593
648
        """Returns a bound method if one exists which is a D-Bus
594
649
        property with the specified name and interface.
595
650
        """
596
 
        for name in (property_name,
597
 
                     property_name + "_dbus_property"):
598
 
            prop = getattr(self, name, None)
599
 
            if (prop is None
600
 
                or not self._is_dbus_property(prop)
601
 
                or prop._dbus_name != property_name
602
 
                or (interface_name and prop._dbus_interface
603
 
                    and interface_name != prop._dbus_interface)):
604
 
                continue
605
 
            return prop
 
651
        for cls in  self.__class__.__mro__:
 
652
            for name, value in (inspect.getmembers
 
653
                                (cls, self._is_dbus_property)):
 
654
                if (value._dbus_name == property_name
 
655
                    and value._dbus_interface == interface_name):
 
656
                    return value.__get__(self)
 
657
        
606
658
        # No such property
607
659
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
608
660
                                   + interface_name + "."
642
694
    def GetAll(self, interface_name):
643
695
        """Standard D-Bus property GetAll() method, see D-Bus
644
696
        standard.
645
 
 
 
697
        
646
698
        Note: Will not include properties with access="write".
647
699
        """
648
700
        all = {}
704
756
            xmlstring = document.toxml("utf-8")
705
757
            document.unlink()
706
758
        except (AttributeError, xml.dom.DOMException,
707
 
                xml.parsers.expat.ExpatError), error:
 
759
                xml.parsers.expat.ExpatError) as error:
708
760
            logger.error("Failed to override Introspection method",
709
761
                         error)
710
762
        return xmlstring
711
763
 
712
764
 
 
765
def datetime_to_dbus (dt, variant_level=0):
 
766
    """Convert a UTC datetime.datetime() to a D-Bus type."""
 
767
    if dt is None:
 
768
        return dbus.String("", variant_level = variant_level)
 
769
    return dbus.String(dt.isoformat(),
 
770
                       variant_level=variant_level)
 
771
 
 
772
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
 
773
                                  .__metaclass__):
 
774
    """Applied to an empty subclass of a D-Bus object, this metaclass
 
775
    will add additional D-Bus attributes matching a certain pattern.
 
776
    """
 
777
    def __new__(mcs, name, bases, attr):
 
778
        # Go through all the base classes which could have D-Bus
 
779
        # methods, signals, or properties in them
 
780
        for base in (b for b in bases
 
781
                     if issubclass(b, dbus.service.Object)):
 
782
            # Go though all attributes of the base class
 
783
            for attrname, attribute in inspect.getmembers(base):
 
784
                # Ignore non-D-Bus attributes, and D-Bus attributes
 
785
                # with the wrong interface name
 
786
                if (not hasattr(attribute, "_dbus_interface")
 
787
                    or not attribute._dbus_interface
 
788
                    .startswith("se.recompile.Mandos")):
 
789
                    continue
 
790
                # Create an alternate D-Bus interface name based on
 
791
                # the current name
 
792
                alt_interface = (attribute._dbus_interface
 
793
                                 .replace("se.recompile.Mandos",
 
794
                                          "se.bsnet.fukt.Mandos"))
 
795
                # Is this a D-Bus signal?
 
796
                if getattr(attribute, "_dbus_is_signal", False):
 
797
                    # Extract the original non-method function by
 
798
                    # black magic
 
799
                    nonmethod_func = (dict(
 
800
                            zip(attribute.func_code.co_freevars,
 
801
                                attribute.__closure__))["func"]
 
802
                                      .cell_contents)
 
803
                    # Create a new, but exactly alike, function
 
804
                    # object, and decorate it to be a new D-Bus signal
 
805
                    # with the alternate D-Bus interface name
 
806
                    new_function = (dbus.service.signal
 
807
                                    (alt_interface,
 
808
                                     attribute._dbus_signature)
 
809
                                    (types.FunctionType(
 
810
                                nonmethod_func.func_code,
 
811
                                nonmethod_func.func_globals,
 
812
                                nonmethod_func.func_name,
 
813
                                nonmethod_func.func_defaults,
 
814
                                nonmethod_func.func_closure)))
 
815
                    # Define a creator of a function to call both the
 
816
                    # old and new functions, so both the old and new
 
817
                    # signals gets sent when the function is called
 
818
                    def fixscope(func1, func2):
 
819
                        """This function is a scope container to pass
 
820
                        func1 and func2 to the "call_both" function
 
821
                        outside of its arguments"""
 
822
                        def call_both(*args, **kwargs):
 
823
                            """This function will emit two D-Bus
 
824
                            signals by calling func1 and func2"""
 
825
                            func1(*args, **kwargs)
 
826
                            func2(*args, **kwargs)
 
827
                        return call_both
 
828
                    # Create the "call_both" function and add it to
 
829
                    # the class
 
830
                    attr[attrname] = fixscope(attribute,
 
831
                                              new_function)
 
832
                # Is this a D-Bus method?
 
833
                elif getattr(attribute, "_dbus_is_method", False):
 
834
                    # Create a new, but exactly alike, function
 
835
                    # object.  Decorate it to be a new D-Bus method
 
836
                    # with the alternate D-Bus interface name.  Add it
 
837
                    # to the class.
 
838
                    attr[attrname] = (dbus.service.method
 
839
                                      (alt_interface,
 
840
                                       attribute._dbus_in_signature,
 
841
                                       attribute._dbus_out_signature)
 
842
                                      (types.FunctionType
 
843
                                       (attribute.func_code,
 
844
                                        attribute.func_globals,
 
845
                                        attribute.func_name,
 
846
                                        attribute.func_defaults,
 
847
                                        attribute.func_closure)))
 
848
                # Is this a D-Bus property?
 
849
                elif getattr(attribute, "_dbus_is_property", False):
 
850
                    # Create a new, but exactly alike, function
 
851
                    # object, and decorate it to be a new D-Bus
 
852
                    # property with the alternate D-Bus interface
 
853
                    # name.  Add it to the class.
 
854
                    attr[attrname] = (dbus_service_property
 
855
                                      (alt_interface,
 
856
                                       attribute._dbus_signature,
 
857
                                       attribute._dbus_access,
 
858
                                       attribute
 
859
                                       ._dbus_get_args_options
 
860
                                       ["byte_arrays"])
 
861
                                      (types.FunctionType
 
862
                                       (attribute.func_code,
 
863
                                        attribute.func_globals,
 
864
                                        attribute.func_name,
 
865
                                        attribute.func_defaults,
 
866
                                        attribute.func_closure)))
 
867
        return type.__new__(mcs, name, bases, attr)
 
868
 
713
869
class ClientDBus(Client, DBusObjectWithProperties):
714
870
    """A Client class using D-Bus
715
871
    
737
893
        DBusObjectWithProperties.__init__(self, self.bus,
738
894
                                          self.dbus_object_path)
739
895
        
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)
 
896
    def notifychangeproperty(transform_func,
 
897
                             dbus_name, type_func=lambda x: x,
 
898
                             variant_level=1):
 
899
        """ Modify a variable so that it's a property which announces
 
900
        its changes to DBus.
751
901
 
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
 
902
        transform_fun: Function that takes a value and a variant_level
 
903
                       and transforms it to a D-Bus type.
 
904
        dbus_name: D-Bus name of the variable
 
905
        type_func: Function that transform the value before sending it
 
906
                   to the D-Bus.  Default: no transform
 
907
        variant_level: D-Bus variant level.  Default: 1
 
908
        """
 
909
        attrname = "_{0}".format(dbus_name)
 
910
        def setter(self, value):
 
911
            if hasattr(self, "dbus_object_path"):
 
912
                if (not hasattr(self, attrname) or
 
913
                    type_func(getattr(self, attrname, None))
 
914
                    != type_func(value)):
 
915
                    dbus_value = transform_func(type_func(value),
 
916
                                                variant_level
 
917
                                                =variant_level)
 
918
                    self.PropertyChanged(dbus.String(dbus_name),
 
919
                                         dbus_value)
 
920
            setattr(self, attrname, value)
 
921
        
 
922
        return property(lambda self: getattr(self, attrname), setter)
 
923
    
 
924
    
 
925
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
 
926
    approvals_pending = notifychangeproperty(dbus.Boolean,
 
927
                                             "ApprovalPending",
 
928
                                             type_func = bool)
 
929
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
 
930
    last_enabled = notifychangeproperty(datetime_to_dbus,
 
931
                                        "LastEnabled")
 
932
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
 
933
                                   type_func = lambda checker:
 
934
                                       checker is not None)
 
935
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
 
936
                                           "LastCheckedOK")
 
937
    last_approval_request = notifychangeproperty(
 
938
        datetime_to_dbus, "LastApprovalRequest")
 
939
    approved_by_default = notifychangeproperty(dbus.Boolean,
 
940
                                               "ApprovedByDefault")
 
941
    approval_delay = notifychangeproperty(dbus.UInt16,
 
942
                                          "ApprovalDelay",
 
943
                                          type_func =
 
944
                                          _timedelta_to_milliseconds)
 
945
    approval_duration = notifychangeproperty(
 
946
        dbus.UInt16, "ApprovalDuration",
 
947
        type_func = _timedelta_to_milliseconds)
 
948
    host = notifychangeproperty(dbus.String, "Host")
 
949
    timeout = notifychangeproperty(dbus.UInt16, "Timeout",
 
950
                                   type_func =
 
951
                                   _timedelta_to_milliseconds)
 
952
    extended_timeout = notifychangeproperty(
 
953
        dbus.UInt16, "ExtendedTimeout",
 
954
        type_func = _timedelta_to_milliseconds)
 
955
    interval = notifychangeproperty(dbus.UInt16,
 
956
                                    "Interval",
 
957
                                    type_func =
 
958
                                    _timedelta_to_milliseconds)
 
959
    checker_command = notifychangeproperty(dbus.String, "Checker")
 
960
    
 
961
    del notifychangeproperty
783
962
    
784
963
    def __del__(self, *args, **kwargs):
785
964
        try:
794
973
                         *args, **kwargs):
795
974
        self.checker_callback_tag = None
796
975
        self.checker = None
797
 
        # Emit D-Bus signal
798
 
        self.PropertyChanged(dbus.String("CheckerRunning"),
799
 
                             dbus.Boolean(False, variant_level=1))
800
976
        if os.WIFEXITED(condition):
801
977
            exitstatus = os.WEXITSTATUS(condition)
802
978
            # Emit D-Bus signal
812
988
        return Client.checker_callback(self, pid, condition, command,
813
989
                                       *args, **kwargs)
814
990
    
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
 
    
833
991
    def start_checker(self, *args, **kwargs):
834
992
        old_checker = self.checker
835
993
        if self.checker is not None:
842
1000
            and old_checker_pid != self.checker.pid):
843
1001
            # Emit D-Bus signal
844
1002
            self.CheckerStarted(self.current_checker_command)
845
 
            self.PropertyChanged(
846
 
                dbus.String("CheckerRunning"),
847
 
                dbus.Boolean(True, variant_level=1))
848
1003
        return r
849
1004
    
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
1005
    def _reset_approved(self):
860
1006
        self._approved = None
861
1007
        return False
863
1009
    def approve(self, value=True):
864
1010
        self.send_changedstate()
865
1011
        self._approved = value
866
 
        gobject.timeout_add(self._timedelta_to_milliseconds
 
1012
        gobject.timeout_add(_timedelta_to_milliseconds
867
1013
                            (self.approval_duration),
868
1014
                            self._reset_approved)
869
1015
    
870
1016
    
871
1017
    ## D-Bus methods, signals & properties
872
 
    _interface = "se.bsnet.fukt.Mandos.Client"
 
1018
    _interface = "se.recompile.Mandos.Client"
873
1019
    
874
1020
    ## Signals
875
1021
    
922
1068
    # CheckedOK - method
923
1069
    @dbus.service.method(_interface)
924
1070
    def CheckedOK(self):
925
 
        return self.checked_ok()
 
1071
        self.checked_ok()
926
1072
    
927
1073
    # Enable - method
928
1074
    @dbus.service.method(_interface)
961
1107
        if value is None:       # get
962
1108
            return dbus.Boolean(self.approved_by_default)
963
1109
        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
1110
    
968
1111
    # ApprovalDelay - property
969
1112
    @dbus_service_property(_interface, signature="t",
972
1115
        if value is None:       # get
973
1116
            return dbus.UInt64(self.approval_delay_milliseconds())
974
1117
        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
1118
    
979
1119
    # ApprovalDuration - property
980
1120
    @dbus_service_property(_interface, signature="t",
981
1121
                           access="readwrite")
982
1122
    def ApprovalDuration_dbus_property(self, value=None):
983
1123
        if value is None:       # get
984
 
            return dbus.UInt64(self._timedelta_to_milliseconds(
 
1124
            return dbus.UInt64(_timedelta_to_milliseconds(
985
1125
                    self.approval_duration))
986
1126
        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
1127
    
991
1128
    # Name - property
992
1129
    @dbus_service_property(_interface, signature="s", access="read")
1005
1142
        if value is None:       # get
1006
1143
            return dbus.String(self.host)
1007
1144
        self.host = value
1008
 
        # Emit D-Bus signal
1009
 
        self.PropertyChanged(dbus.String("Host"),
1010
 
                             dbus.String(value, variant_level=1))
1011
1145
    
1012
1146
    # Created - property
1013
1147
    @dbus_service_property(_interface, signature="s", access="read")
1014
1148
    def Created_dbus_property(self):
1015
 
        return dbus.String(self._datetime_to_dbus(self.created))
 
1149
        return dbus.String(datetime_to_dbus(self.created))
1016
1150
    
1017
1151
    # LastEnabled - property
1018
1152
    @dbus_service_property(_interface, signature="s", access="read")
1019
1153
    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))
 
1154
        return datetime_to_dbus(self.last_enabled)
1023
1155
    
1024
1156
    # Enabled - property
1025
1157
    @dbus_service_property(_interface, signature="b",
1039
1171
        if value is not None:
1040
1172
            self.checked_ok()
1041
1173
            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))
 
1174
        return datetime_to_dbus(self.last_checked_ok)
 
1175
    
 
1176
    # Expires - property
 
1177
    @dbus_service_property(_interface, signature="s", access="read")
 
1178
    def Expires_dbus_property(self):
 
1179
        return datetime_to_dbus(self.expires)
1046
1180
    
1047
1181
    # LastApprovalRequest - property
1048
1182
    @dbus_service_property(_interface, signature="s", access="read")
1049
1183
    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))
 
1184
        return datetime_to_dbus(self.last_approval_request)
1055
1185
    
1056
1186
    # Timeout - property
1057
1187
    @dbus_service_property(_interface, signature="t",
1060
1190
        if value is None:       # get
1061
1191
            return dbus.UInt64(self.timeout_milliseconds())
1062
1192
        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
1193
        if getattr(self, "disable_initiator_tag", None) is None:
1067
1194
            return
1068
1195
        # Reschedule timeout
1069
1196
        gobject.source_remove(self.disable_initiator_tag)
1070
1197
        self.disable_initiator_tag = None
1071
 
        time_to_die = (self.
1072
 
                       _timedelta_to_milliseconds((self
1073
 
                                                   .last_checked_ok
1074
 
                                                   + self.timeout)
1075
 
                                                  - datetime.datetime
1076
 
                                                  .utcnow()))
 
1198
        self.expires = None
 
1199
        time_to_die = _timedelta_to_milliseconds((self
 
1200
                                                  .last_checked_ok
 
1201
                                                  + self.timeout)
 
1202
                                                 - datetime.datetime
 
1203
                                                 .utcnow())
1077
1204
        if time_to_die <= 0:
1078
1205
            # The timeout has passed
1079
1206
            self.disable()
1080
1207
        else:
 
1208
            self.expires = (datetime.datetime.utcnow()
 
1209
                            + datetime.timedelta(milliseconds =
 
1210
                                                 time_to_die))
1081
1211
            self.disable_initiator_tag = (gobject.timeout_add
1082
1212
                                          (time_to_die, self.disable))
1083
1213
    
 
1214
    # ExtendedTimeout - property
 
1215
    @dbus_service_property(_interface, signature="t",
 
1216
                           access="readwrite")
 
1217
    def ExtendedTimeout_dbus_property(self, value=None):
 
1218
        if value is None:       # get
 
1219
            return dbus.UInt64(self.extended_timeout_milliseconds())
 
1220
        self.extended_timeout = datetime.timedelta(0, 0, 0, value)
 
1221
    
1084
1222
    # Interval - property
1085
1223
    @dbus_service_property(_interface, signature="t",
1086
1224
                           access="readwrite")
1088
1226
        if value is None:       # get
1089
1227
            return dbus.UInt64(self.interval_milliseconds())
1090
1228
        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
1229
        if getattr(self, "checker_initiator_tag", None) is None:
1095
1230
            return
1096
1231
        # Reschedule checker run
1098
1233
        self.checker_initiator_tag = (gobject.timeout_add
1099
1234
                                      (value, self.start_checker))
1100
1235
        self.start_checker()    # Start one now, too
1101
 
 
 
1236
    
1102
1237
    # Checker - property
1103
1238
    @dbus_service_property(_interface, signature="s",
1104
1239
                           access="readwrite")
1106
1241
        if value is None:       # get
1107
1242
            return dbus.String(self.checker_command)
1108
1243
        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
1244
    
1114
1245
    # CheckerRunning - property
1115
1246
    @dbus_service_property(_interface, signature="b",
1142
1273
        self._pipe.send(('init', fpr, address))
1143
1274
        if not self._pipe.recv():
1144
1275
            raise KeyError()
1145
 
 
 
1276
    
1146
1277
    def __getattribute__(self, name):
1147
1278
        if(name == '_pipe'):
1148
1279
            return super(ProxyClient, self).__getattribute__(name)
1155
1286
                self._pipe.send(('funcall', name, args, kwargs))
1156
1287
                return self._pipe.recv()[1]
1157
1288
            return func
1158
 
 
 
1289
    
1159
1290
    def __setattr__(self, name, value):
1160
1291
        if(name == '_pipe'):
1161
1292
            return super(ProxyClient, self).__setattr__(name, value)
1162
1293
        self._pipe.send(('setattr', name, value))
1163
1294
 
 
1295
class ClientDBusTransitional(ClientDBus):
 
1296
    __metaclass__ = AlternateDBusNamesMetaclass
1164
1297
 
1165
1298
class ClientHandler(socketserver.BaseRequestHandler, object):
1166
1299
    """A class to handle client connections.
1174
1307
                        unicode(self.client_address))
1175
1308
            logger.debug("Pipe FD: %d",
1176
1309
                         self.server.child_pipe.fileno())
1177
 
 
 
1310
            
1178
1311
            session = (gnutls.connection
1179
1312
                       .ClientSession(self.request,
1180
1313
                                      gnutls.connection
1181
1314
                                      .X509Credentials()))
1182
 
 
 
1315
            
1183
1316
            # Note: gnutls.connection.X509Credentials is really a
1184
1317
            # generic GnuTLS certificate credentials object so long as
1185
1318
            # no X.509 keys are added to it.  Therefore, we can use it
1186
1319
            # here despite using OpenPGP certificates.
1187
 
 
 
1320
            
1188
1321
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
1189
1322
            #                      "+AES-256-CBC", "+SHA1",
1190
1323
            #                      "+COMP-NULL", "+CTYPE-OPENPGP",
1196
1329
            (gnutls.library.functions
1197
1330
             .gnutls_priority_set_direct(session._c_object,
1198
1331
                                         priority, None))
1199
 
 
 
1332
            
1200
1333
            # Start communication using the Mandos protocol
1201
1334
            # Get protocol number
1202
1335
            line = self.request.makefile().readline()
1204
1337
            try:
1205
1338
                if int(line.strip().split()[0]) > 1:
1206
1339
                    raise RuntimeError
1207
 
            except (ValueError, IndexError, RuntimeError), error:
 
1340
            except (ValueError, IndexError, RuntimeError) as error:
1208
1341
                logger.error("Unknown protocol version: %s", error)
1209
1342
                return
1210
 
 
 
1343
            
1211
1344
            # Start GnuTLS connection
1212
1345
            try:
1213
1346
                session.handshake()
1214
 
            except gnutls.errors.GNUTLSError, error:
 
1347
            except gnutls.errors.GNUTLSError as error:
1215
1348
                logger.warning("Handshake failed: %s", error)
1216
1349
                # Do not run session.bye() here: the session is not
1217
1350
                # established.  Just abandon the request.
1218
1351
                return
1219
1352
            logger.debug("Handshake succeeded")
1220
 
 
 
1353
            
1221
1354
            approval_required = False
1222
1355
            try:
1223
1356
                try:
1224
1357
                    fpr = self.fingerprint(self.peer_certificate
1225
1358
                                           (session))
1226
 
                except (TypeError, gnutls.errors.GNUTLSError), error:
 
1359
                except (TypeError,
 
1360
                        gnutls.errors.GNUTLSError) as error:
1227
1361
                    logger.warning("Bad certificate: %s", error)
1228
1362
                    return
1229
1363
                logger.debug("Fingerprint: %s", fpr)
1230
 
 
 
1364
                
1231
1365
                try:
1232
1366
                    client = ProxyClient(child_pipe, fpr,
1233
1367
                                         self.client_address)
1241
1375
                
1242
1376
                while True:
1243
1377
                    if not client.enabled:
1244
 
                        logger.warning("Client %s is disabled",
 
1378
                        logger.info("Client %s is disabled",
1245
1379
                                       client.name)
1246
1380
                        if self.server.use_dbus:
1247
1381
                            # Emit D-Bus signal
1248
 
                            client.Rejected("Disabled")                    
 
1382
                            client.Rejected("Disabled")
1249
1383
                        return
1250
1384
                    
1251
1385
                    if client._approved or not client.approval_delay:
1268
1402
                        return
1269
1403
                    
1270
1404
                    #wait until timeout or approved
1271
 
                    #x = float(client._timedelta_to_milliseconds(delay))
1272
1405
                    time = datetime.datetime.now()
1273
1406
                    client.changedstate.acquire()
1274
 
                    client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
 
1407
                    (client.changedstate.wait
 
1408
                     (float(client._timedelta_to_milliseconds(delay)
 
1409
                            / 1000)))
1275
1410
                    client.changedstate.release()
1276
1411
                    time2 = datetime.datetime.now()
1277
1412
                    if (time2 - time) >= delay:
1292
1427
                while sent_size < len(client.secret):
1293
1428
                    try:
1294
1429
                        sent = session.send(client.secret[sent_size:])
1295
 
                    except (gnutls.errors.GNUTLSError), error:
 
1430
                    except gnutls.errors.GNUTLSError as error:
1296
1431
                        logger.warning("gnutls send failed")
1297
1432
                        return
1298
1433
                    logger.debug("Sent: %d, remaining: %d",
1299
1434
                                 sent, len(client.secret)
1300
1435
                                 - (sent_size + sent))
1301
1436
                    sent_size += sent
1302
 
 
 
1437
                
1303
1438
                logger.info("Sending secret to %s", client.name)
1304
 
                # bump the timeout as if seen
1305
 
                client.checked_ok()
 
1439
                # bump the timeout using extended_timeout
 
1440
                client.checked_ok(client.extended_timeout)
1306
1441
                if self.server.use_dbus:
1307
1442
                    # Emit D-Bus signal
1308
1443
                    client.GotSecret()
1312
1447
                    client.approvals_pending -= 1
1313
1448
                try:
1314
1449
                    session.bye()
1315
 
                except (gnutls.errors.GNUTLSError), error:
 
1450
                except gnutls.errors.GNUTLSError as error:
1316
1451
                    logger.warning("GnuTLS bye failed")
1317
1452
    
1318
1453
    @staticmethod
1387
1522
        except:
1388
1523
            self.handle_error(request, address)
1389
1524
        self.close_request(request)
1390
 
            
 
1525
    
1391
1526
    def process_request(self, request, address):
1392
1527
        """Start a new process to process the request."""
1393
 
        multiprocessing.Process(target = self.sub_process_main,
1394
 
                                args = (request, address)).start()
 
1528
        proc = multiprocessing.Process(target = self.sub_process_main,
 
1529
                                       args = (request,
 
1530
                                               address))
 
1531
        proc.start()
 
1532
        return proc
 
1533
 
1395
1534
 
1396
1535
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1397
1536
    """ adds a pipe to the MixIn """
1401
1540
        This function creates a new pipe in self.pipe
1402
1541
        """
1403
1542
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
1404
 
 
1405
 
        super(MultiprocessingMixInWithPipe,
1406
 
              self).process_request(request, client_address)
 
1543
        
 
1544
        proc = MultiprocessingMixIn.process_request(self, request,
 
1545
                                                    client_address)
1407
1546
        self.child_pipe.close()
1408
 
        self.add_pipe(parent_pipe)
1409
 
 
1410
 
    def add_pipe(self, parent_pipe):
 
1547
        self.add_pipe(parent_pipe, proc)
 
1548
    
 
1549
    def add_pipe(self, parent_pipe, proc):
1411
1550
        """Dummy function; override as necessary"""
1412
1551
        raise NotImplementedError
1413
1552
 
 
1553
 
1414
1554
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1415
1555
                     socketserver.TCPServer, object):
1416
1556
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1442
1582
                                           SO_BINDTODEVICE,
1443
1583
                                           str(self.interface
1444
1584
                                               + '\0'))
1445
 
                except socket.error, error:
 
1585
                except socket.error as error:
1446
1586
                    if error[0] == errno.EPERM:
1447
1587
                        logger.error("No permission to"
1448
1588
                                     " bind to interface %s",
1500
1640
    def server_activate(self):
1501
1641
        if self.enabled:
1502
1642
            return socketserver.TCPServer.server_activate(self)
 
1643
    
1503
1644
    def enable(self):
1504
1645
        self.enabled = True
1505
 
    def add_pipe(self, parent_pipe):
 
1646
    
 
1647
    def add_pipe(self, parent_pipe, proc):
1506
1648
        # Call "handle_ipc" for both data and EOF events
1507
1649
        gobject.io_add_watch(parent_pipe.fileno(),
1508
1650
                             gobject.IO_IN | gobject.IO_HUP,
1509
1651
                             functools.partial(self.handle_ipc,
1510
 
                                               parent_pipe = parent_pipe))
1511
 
        
 
1652
                                               parent_pipe =
 
1653
                                               parent_pipe,
 
1654
                                               proc = proc))
 
1655
    
1512
1656
    def handle_ipc(self, source, condition, parent_pipe=None,
1513
 
                   client_object=None):
 
1657
                   proc = None, client_object=None):
1514
1658
        condition_names = {
1515
1659
            gobject.IO_IN: "IN",   # There is data to read.
1516
1660
            gobject.IO_OUT: "OUT", # Data can be written (without
1525
1669
                                       for cond, name in
1526
1670
                                       condition_names.iteritems()
1527
1671
                                       if cond & condition)
1528
 
        # error or the other end of multiprocessing.Pipe has closed
 
1672
        # error, or the other end of multiprocessing.Pipe has closed
1529
1673
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
 
1674
            # Wait for other process to exit
 
1675
            proc.join()
1530
1676
            return False
1531
1677
        
1532
1678
        # Read a request from the child
1542
1688
                    client = c
1543
1689
                    break
1544
1690
            else:
1545
 
                logger.warning("Client not found for fingerprint: %s, ad"
1546
 
                               "dress: %s", fpr, address)
 
1691
                logger.info("Client not found for fingerprint: %s, ad"
 
1692
                            "dress: %s", fpr, address)
1547
1693
                if self.use_dbus:
1548
1694
                    # Emit D-Bus signal
1549
 
                    mandos_dbus_service.ClientNotFound(fpr, address[0])
 
1695
                    mandos_dbus_service.ClientNotFound(fpr,
 
1696
                                                       address[0])
1550
1697
                parent_pipe.send(False)
1551
1698
                return False
1552
1699
            
1553
1700
            gobject.io_add_watch(parent_pipe.fileno(),
1554
1701
                                 gobject.IO_IN | gobject.IO_HUP,
1555
1702
                                 functools.partial(self.handle_ipc,
1556
 
                                                   parent_pipe = parent_pipe,
1557
 
                                                   client_object = client))
 
1703
                                                   parent_pipe =
 
1704
                                                   parent_pipe,
 
1705
                                                   proc = proc,
 
1706
                                                   client_object =
 
1707
                                                   client))
1558
1708
            parent_pipe.send(True)
1559
 
            # remove the old hook in favor of the new above hook on same fileno
 
1709
            # remove the old hook in favor of the new above hook on
 
1710
            # same fileno
1560
1711
            return False
1561
1712
        if command == 'funcall':
1562
1713
            funcname = request[1]
1563
1714
            args = request[2]
1564
1715
            kwargs = request[3]
1565
1716
            
1566
 
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1567
 
 
 
1717
            parent_pipe.send(('data', getattr(client_object,
 
1718
                                              funcname)(*args,
 
1719
                                                         **kwargs)))
 
1720
        
1568
1721
        if command == 'getattr':
1569
1722
            attrname = request[1]
1570
1723
            if callable(client_object.__getattribute__(attrname)):
1571
1724
                parent_pipe.send(('function',))
1572
1725
            else:
1573
 
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
 
1726
                parent_pipe.send(('data', client_object
 
1727
                                  .__getattribute__(attrname)))
1574
1728
        
1575
1729
        if command == 'setattr':
1576
1730
            attrname = request[1]
1577
1731
            value = request[2]
1578
1732
            setattr(client_object, attrname, value)
1579
 
 
 
1733
        
1580
1734
        return True
1581
1735
 
1582
1736
 
1613
1767
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1614
1768
            else:
1615
1769
                raise ValueError("Unknown suffix %r" % suffix)
1616
 
        except (ValueError, IndexError), e:
 
1770
        except (ValueError, IndexError) as e:
1617
1771
            raise ValueError(*(e.args))
1618
1772
        timevalue += delta
1619
1773
    return timevalue
1673
1827
    ##################################################################
1674
1828
    # Parsing of options, both command line and config file
1675
1829
    
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]
 
1830
    parser = argparse.ArgumentParser()
 
1831
    parser.add_argument("-v", "--version", action="version",
 
1832
                        version = "%%(prog)s %s" % version,
 
1833
                        help="show version number and exit")
 
1834
    parser.add_argument("-i", "--interface", metavar="IF",
 
1835
                        help="Bind to interface IF")
 
1836
    parser.add_argument("-a", "--address",
 
1837
                        help="Address to listen for requests on")
 
1838
    parser.add_argument("-p", "--port", type=int,
 
1839
                        help="Port number to receive requests on")
 
1840
    parser.add_argument("--check", action="store_true",
 
1841
                        help="Run self-test")
 
1842
    parser.add_argument("--debug", action="store_true",
 
1843
                        help="Debug mode; run in foreground and log"
 
1844
                        " to terminal")
 
1845
    parser.add_argument("--debuglevel", metavar="LEVEL",
 
1846
                        help="Debug level for stdout output")
 
1847
    parser.add_argument("--priority", help="GnuTLS"
 
1848
                        " priority string (see GnuTLS documentation)")
 
1849
    parser.add_argument("--servicename",
 
1850
                        metavar="NAME", help="Zeroconf service name")
 
1851
    parser.add_argument("--configdir",
 
1852
                        default="/etc/mandos", metavar="DIR",
 
1853
                        help="Directory to search for configuration"
 
1854
                        " files")
 
1855
    parser.add_argument("--no-dbus", action="store_false",
 
1856
                        dest="use_dbus", help="Do not provide D-Bus"
 
1857
                        " system bus interface")
 
1858
    parser.add_argument("--no-ipv6", action="store_false",
 
1859
                        dest="use_ipv6", help="Do not use IPv6")
 
1860
    options = parser.parse_args()
1704
1861
    
1705
1862
    if options.check:
1706
1863
        import doctest
1758
1915
    debuglevel = server_settings["debuglevel"]
1759
1916
    use_dbus = server_settings["use_dbus"]
1760
1917
    use_ipv6 = server_settings["use_ipv6"]
1761
 
 
 
1918
    
1762
1919
    if server_settings["servicename"] != "Mandos":
1763
1920
        syslogger.setFormatter(logging.Formatter
1764
1921
                               ('Mandos (%s) [%%(process)d]:'
1766
1923
                                % server_settings["servicename"]))
1767
1924
    
1768
1925
    # Parse config file with clients
1769
 
    client_defaults = { "timeout": "1h",
1770
 
                        "interval": "5m",
 
1926
    client_defaults = { "timeout": "5m",
 
1927
                        "extended_timeout": "15m",
 
1928
                        "interval": "2m",
1771
1929
                        "checker": "fping -q -- %%(host)s",
1772
1930
                        "host": "",
1773
1931
                        "approval_delay": "0s",
1813
1971
    try:
1814
1972
        os.setgid(gid)
1815
1973
        os.setuid(uid)
1816
 
    except OSError, error:
 
1974
    except OSError as error:
1817
1975
        if error[0] != errno.EPERM:
1818
1976
            raise error
1819
1977
    
1820
1978
    if not debug and not debuglevel:
1821
 
        syslogger.setLevel(logging.WARNING)
1822
 
        console.setLevel(logging.WARNING)
 
1979
        logger.setLevel(logging.WARNING)
1823
1980
    if debuglevel:
1824
1981
        level = getattr(logging, debuglevel.upper())
1825
 
        syslogger.setLevel(level)
1826
 
        console.setLevel(level)
1827
 
 
 
1982
        logger.setLevel(level)
 
1983
    
1828
1984
    if debug:
 
1985
        logger.setLevel(logging.DEBUG)
1829
1986
        # Enable all possible GnuTLS debugging
1830
1987
        
1831
1988
        # "Use a log level over 10 to enable all debugging options."
1861
2018
    # End of Avahi example code
1862
2019
    if use_dbus:
1863
2020
        try:
1864
 
            bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
 
2021
            bus_name = dbus.service.BusName("se.recompile.Mandos",
1865
2022
                                            bus, do_not_queue=True)
1866
 
        except dbus.exceptions.NameExistsException, e:
 
2023
            old_bus_name = (dbus.service.BusName
 
2024
                            ("se.bsnet.fukt.Mandos", bus,
 
2025
                             do_not_queue=True))
 
2026
        except dbus.exceptions.NameExistsException as e:
1867
2027
            logger.error(unicode(e) + ", disabling D-Bus")
1868
2028
            use_dbus = False
1869
2029
            server_settings["use_dbus"] = False
1870
2030
            tcp_server.use_dbus = False
1871
2031
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1872
 
    service = AvahiService(name = server_settings["servicename"],
1873
 
                           servicetype = "_mandos._tcp",
1874
 
                           protocol = protocol, bus = bus)
 
2032
    service = AvahiServiceToSyslog(name =
 
2033
                                   server_settings["servicename"],
 
2034
                                   servicetype = "_mandos._tcp",
 
2035
                                   protocol = protocol, bus = bus)
1875
2036
    if server_settings["interface"]:
1876
2037
        service.interface = (if_nametoindex
1877
2038
                             (str(server_settings["interface"])))
1881
2042
    
1882
2043
    client_class = Client
1883
2044
    if use_dbus:
1884
 
        client_class = functools.partial(ClientDBus, bus = bus)
 
2045
        client_class = functools.partial(ClientDBusTransitional,
 
2046
                                         bus = bus)
1885
2047
    def client_config_items(config, section):
1886
2048
        special_settings = {
1887
2049
            "approved_by_default":
1917
2079
        del pidfilename
1918
2080
        
1919
2081
        signal.signal(signal.SIGINT, signal.SIG_IGN)
1920
 
 
 
2082
    
1921
2083
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1922
2084
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1923
2085
    
1926
2088
            """A D-Bus proxy object"""
1927
2089
            def __init__(self):
1928
2090
                dbus.service.Object.__init__(self, bus, "/")
1929
 
            _interface = "se.bsnet.fukt.Mandos"
 
2091
            _interface = "se.recompile.Mandos"
1930
2092
            
1931
2093
            @dbus.service.signal(_interface, signature="o")
1932
2094
            def ClientAdded(self, objpath):
1974
2136
            
1975
2137
            del _interface
1976
2138
        
1977
 
        mandos_dbus_service = MandosDBusService()
 
2139
        class MandosDBusServiceTransitional(MandosDBusService):
 
2140
            __metaclass__ = AlternateDBusNamesMetaclass
 
2141
        mandos_dbus_service = MandosDBusServiceTransitional()
1978
2142
    
1979
2143
    def cleanup():
1980
2144
        "Cleanup function; run on exit"
1981
2145
        service.cleanup()
1982
2146
        
 
2147
        multiprocessing.active_children()
1983
2148
        while tcp_server.clients:
1984
2149
            client = tcp_server.clients.pop()
1985
2150
            if use_dbus:
1989
2154
            client.disable(quiet=True)
1990
2155
            if use_dbus:
1991
2156
                # Emit D-Bus signal
1992
 
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
 
2157
                mandos_dbus_service.ClientRemoved(client
 
2158
                                                  .dbus_object_path,
1993
2159
                                                  client.name)
1994
2160
    
1995
2161
    atexit.register(cleanup)
2019
2185
        # From the Avahi example code
2020
2186
        try:
2021
2187
            service.activate()
2022
 
        except dbus.exceptions.DBusException, error:
 
2188
        except dbus.exceptions.DBusException as error:
2023
2189
            logger.critical("DBusException: %s", error)
2024
2190
            cleanup()
2025
2191
            sys.exit(1)
2032
2198
        
2033
2199
        logger.debug("Starting main loop")
2034
2200
        main_loop.run()
2035
 
    except AvahiError, error:
 
2201
    except AvahiError as error:
2036
2202
        logger.critical("AvahiError: %s", error)
2037
2203
        cleanup()
2038
2204
        sys.exit(1)
2044
2210
    # Must run before the D-Bus bus name gets deregistered
2045
2211
    cleanup()
2046
2212
 
 
2213
 
2047
2214
if __name__ == '__main__':
2048
2215
    main()