/mandos/release

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

« back to all changes in this revision

Viewing changes to mandos

* mandos (Client.start_checker): Bug fix: Fix race condition with
                                 short intervals.

Show diffs side-by-side

added added

removed removed

Lines of Context:
55
55
import logging
56
56
import logging.handlers
57
57
import pwd
58
 
import contextlib
 
58
from contextlib import closing
59
59
import struct
60
60
import fcntl
61
61
import functools
62
 
import cPickle as pickle
63
 
import multiprocessing
64
62
 
65
63
import dbus
66
64
import dbus.service
81
79
        SO_BINDTODEVICE = None
82
80
 
83
81
 
84
 
version = "1.0.14"
 
82
version = "1.0.12"
85
83
 
86
 
#logger = logging.getLogger(u'mandos')
87
84
logger = logging.Logger(u'mandos')
88
85
syslogger = (logging.handlers.SysLogHandler
89
86
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
157
154
                            u" after %i retries, exiting.",
158
155
                            self.rename_count)
159
156
            raise AvahiServiceError(u"Too many renames")
160
 
        self.name = unicode(self.server.GetAlternativeServiceName(self.name))
 
157
        self.name = self.server.GetAlternativeServiceName(self.name)
161
158
        logger.info(u"Changing Zeroconf service name to %r ...",
162
 
                    self.name)
 
159
                    unicode(self.name))
163
160
        syslogger.setFormatter(logging.Formatter
164
161
                               (u'Mandos (%s) [%%(process)d]:'
165
162
                                u' %%(levelname)s: %%(message)s'
166
163
                                % self.name))
167
164
        self.remove()
168
 
        try:
169
 
            self.add()
170
 
        except dbus.exceptions.DBusException, error:
171
 
            logger.critical(u"DBusException: %s", error)
172
 
            self.cleanup()
173
 
            os._exit(1)
 
165
        self.add()
174
166
        self.rename_count += 1
175
167
    def remove(self):
176
168
        """Derived from the Avahi example code"""
199
191
        self.group.Commit()
200
192
    def entry_group_state_changed(self, state, error):
201
193
        """Derived from the Avahi example code"""
202
 
        logger.debug(u"Avahi entry group state change: %i", state)
 
194
        logger.debug(u"Avahi state change: %i", state)
203
195
        
204
196
        if state == avahi.ENTRY_GROUP_ESTABLISHED:
205
197
            logger.debug(u"Zeroconf service established.")
218
210
            self.group = None
219
211
    def server_state_changed(self, state):
220
212
        """Derived from the Avahi example code"""
221
 
        logger.debug(u"Avahi server state change: %i", state)
222
213
        if state == avahi.SERVER_COLLISION:
223
214
            logger.error(u"Zeroconf server name collision")
224
215
            self.remove()
240
231
    """A representation of a client host served by this server.
241
232
    
242
233
    Attributes:
243
 
    _approved:   bool(); 'None' if not yet approved/disapproved
244
 
    approval_delay: datetime.timedelta(); Time to wait for approval
245
 
    approval_duration: datetime.timedelta(); Duration of one approval
 
234
    name:       string; from the config file, used in log messages and
 
235
                        D-Bus identifiers
 
236
    fingerprint: string (40 or 32 hexadecimal digits); used to
 
237
                 uniquely identify the client
 
238
    secret:     bytestring; sent verbatim (over TLS) to client
 
239
    host:       string; available for use by the checker command
 
240
    created:    datetime.datetime(); (UTC) object creation
 
241
    last_enabled: datetime.datetime(); (UTC)
 
242
    enabled:    bool()
 
243
    last_checked_ok: datetime.datetime(); (UTC) or None
 
244
    timeout:    datetime.timedelta(); How long from last_checked_ok
 
245
                                      until this client is invalid
 
246
    interval:   datetime.timedelta(); How often to start a new checker
 
247
    disable_hook:  If set, called by disable() as disable_hook(self)
246
248
    checker:    subprocess.Popen(); a running checker process used
247
249
                                    to see if the client lives.
248
250
                                    'None' if no process is running.
 
251
    checker_initiator_tag: a gobject event source tag, or None
 
252
    disable_initiator_tag: - '' -
249
253
    checker_callback_tag:  - '' -
250
 
    checker_command: string; External command which is run to check
251
 
                     if client lives.  %() expansions are done at
 
254
    checker_command: string; External command which is run to check if
 
255
                     client lives.  %() expansions are done at
252
256
                     runtime with vars(self) as dict, so that for
253
257
                     instance %(name)s can be used in the command.
254
 
    checker_initiator_tag: a gobject event source tag, or None
255
 
    created:    datetime.datetime(); (UTC) object creation
256
258
    current_checker_command: string; current running checker_command
257
 
    disable_hook:  If set, called by disable() as disable_hook(self)
258
 
    disable_initiator_tag: - '' -
259
 
    enabled:    bool()
260
 
    fingerprint: string (40 or 32 hexadecimal digits); used to
261
 
                 uniquely identify the client
262
 
    host:       string; available for use by the checker command
263
 
    interval:   datetime.timedelta(); How often to start a new checker
264
 
    last_checked_ok: datetime.datetime(); (UTC) or None
265
 
    last_enabled: datetime.datetime(); (UTC)
266
 
    name:       string; from the config file, used in log messages and
267
 
                        D-Bus identifiers
268
 
    secret:     bytestring; sent verbatim (over TLS) to client
269
 
    timeout:    datetime.timedelta(); How long from last_checked_ok
270
 
                                      until this client is disabled
271
 
    runtime_expansions: Allowed attributes for runtime expansion.
272
259
    """
273
260
    
274
 
    runtime_expansions = (u"approval_delay", u"approval_duration",
275
 
                          u"created", u"enabled", u"fingerprint",
276
 
                          u"host", u"interval", u"last_checked_ok",
277
 
                          u"last_enabled", u"name", u"timeout")
278
 
    
279
261
    @staticmethod
280
262
    def _timedelta_to_milliseconds(td):
281
263
        "Convert a datetime.timedelta() to milliseconds"
290
272
    def interval_milliseconds(self):
291
273
        "Return the 'interval' attribute in milliseconds"
292
274
        return self._timedelta_to_milliseconds(self.interval)
293
 
 
294
 
    def approval_delay_milliseconds(self):
295
 
        return self._timedelta_to_milliseconds(self.approval_delay)
296
275
    
297
276
    def __init__(self, name = None, disable_hook=None, config=None):
298
277
        """Note: the 'checker' key in 'config' sets the
311
290
        if u"secret" in config:
312
291
            self.secret = config[u"secret"].decode(u"base64")
313
292
        elif u"secfile" in config:
314
 
            with open(os.path.expanduser(os.path.expandvars
315
 
                                         (config[u"secfile"])),
316
 
                      "rb") as secfile:
 
293
            with closing(open(os.path.expanduser
 
294
                              (os.path.expandvars
 
295
                               (config[u"secfile"])))) as secfile:
317
296
                self.secret = secfile.read()
318
297
        else:
319
298
            raise TypeError(u"No secret or secfile for client %s"
333
312
        self.checker_command = config[u"checker"]
334
313
        self.current_checker_command = None
335
314
        self.last_connect = None
336
 
        self._approved = None
337
 
        self.approved_by_default = config.get(u"approved_by_default",
338
 
                                              True)
339
 
        self.approvals_pending = 0
340
 
        self.approval_delay = string_to_delta(
341
 
            config[u"approval_delay"])
342
 
        self.approval_duration = string_to_delta(
343
 
            config[u"approval_duration"])
344
 
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
345
315
    
346
 
    def send_changedstate(self):
347
 
        self.changedstate.acquire()
348
 
        self.changedstate.notify_all()
349
 
        self.changedstate.release()
350
 
        
351
316
    def enable(self):
352
317
        """Start this client's checker and timeout hooks"""
353
318
        if getattr(self, u"enabled", False):
354
319
            # Already enabled
355
320
            return
356
 
        self.send_changedstate()
357
321
        self.last_enabled = datetime.datetime.utcnow()
358
322
        # Schedule a new checker to be started an 'interval' from now,
359
323
        # and every interval from then on.
360
324
        self.checker_initiator_tag = (gobject.timeout_add
361
325
                                      (self.interval_milliseconds(),
362
326
                                       self.start_checker))
 
327
        # Also start a new checker *right now*.
 
328
        self.start_checker()
363
329
        # Schedule a disable() when 'timeout' has passed
364
330
        self.disable_initiator_tag = (gobject.timeout_add
365
331
                                   (self.timeout_milliseconds(),
366
332
                                    self.disable))
367
333
        self.enabled = True
368
 
        # Also start a new checker *right now*.
369
 
        self.start_checker()
370
334
    
371
 
    def disable(self, quiet=True):
 
335
    def disable(self):
372
336
        """Disable this client."""
373
337
        if not getattr(self, "enabled", False):
374
338
            return False
375
 
        if not quiet:
376
 
            self.send_changedstate()
377
 
        if not quiet:
378
 
            logger.info(u"Disabling client %s", self.name)
 
339
        logger.info(u"Disabling client %s", self.name)
379
340
        if getattr(self, u"disable_initiator_tag", False):
380
341
            gobject.source_remove(self.disable_initiator_tag)
381
342
            self.disable_initiator_tag = None
433
394
        # client would inevitably timeout, since no checker would get
434
395
        # a chance to run to completion.  If we instead leave running
435
396
        # checkers alone, the checker would have to take more time
436
 
        # than 'timeout' for the client to be disabled, which is as it
437
 
        # should be.
 
397
        # than 'timeout' for the client to be declared invalid, which
 
398
        # is as it should be.
438
399
        
439
400
        # If a checker exists, make sure it is not a zombie
440
401
        try:
456
417
                command = self.checker_command % self.host
457
418
            except TypeError:
458
419
                # Escape attributes for the shell
459
 
                escaped_attrs = dict(
460
 
                    (attr,
461
 
                     re.escape(unicode(str(getattr(self, attr, u"")),
462
 
                                       errors=
463
 
                                       u'replace')))
464
 
                    for attr in
465
 
                    self.runtime_expansions)
466
 
 
 
420
                escaped_attrs = dict((key,
 
421
                                      re.escape(unicode(str(val),
 
422
                                                        errors=
 
423
                                                        u'replace')))
 
424
                                     for key, val in
 
425
                                     vars(self).iteritems())
467
426
                try:
468
427
                    command = self.checker_command % escaped_attrs
469
428
                except TypeError, error:
507
466
        logger.debug(u"Stopping checker for %(name)s", vars(self))
508
467
        try:
509
468
            os.kill(self.checker.pid, signal.SIGTERM)
510
 
            #time.sleep(0.5)
 
469
            #os.sleep(0.5)
511
470
            #if self.checker.poll() is None:
512
471
            #    os.kill(self.checker.pid, signal.SIGKILL)
513
472
        except OSError, error:
514
473
            if error.errno != errno.ESRCH: # No such process
515
474
                raise
516
475
        self.checker = None
 
476
    
 
477
    def still_valid(self):
 
478
        """Has the timeout not yet passed for this client?"""
 
479
        if not getattr(self, u"enabled", False):
 
480
            return False
 
481
        now = datetime.datetime.utcnow()
 
482
        if self.last_checked_ok is None:
 
483
            return now < (self.created + self.timeout)
 
484
        else:
 
485
            return now < (self.last_checked_ok + self.timeout)
 
486
 
517
487
 
518
488
def dbus_service_property(dbus_interface, signature=u"v",
519
489
                          access=u"readwrite", byte_arrays=False):
527
497
    dbus.service.method, except there is only "signature", since the
528
498
    type from Get() and the type sent to Set() is the same.
529
499
    """
530
 
    # Encoding deeply encoded byte arrays is not supported yet by the
531
 
    # "Set" method, so we fail early here:
532
 
    if byte_arrays and signature != u"ay":
533
 
        raise ValueError(u"Byte arrays not supported for non-'ay'"
534
 
                         u" signature %r" % signature)
535
500
    def decorator(func):
536
501
        func._dbus_is_property = True
537
502
        func._dbus_interface = dbus_interface
623
588
        if prop._dbus_access == u"read":
624
589
            raise DBusPropertyAccessException(property_name)
625
590
        if prop._dbus_get_args_options[u"byte_arrays"]:
626
 
            # The byte_arrays option is not supported yet on
627
 
            # signatures other than "ay".
628
 
            if prop._dbus_signature != u"ay":
629
 
                raise ValueError
630
591
            value = dbus.ByteArray(''.join(unichr(byte)
631
592
                                           for byte in value))
632
593
        prop(value)
664
625
        """Standard D-Bus method, overloaded to insert property tags.
665
626
        """
666
627
        xmlstring = dbus.service.Object.Introspect(self, object_path,
667
 
                                                   connection)
668
 
        try:
669
 
            document = xml.dom.minidom.parseString(xmlstring)
670
 
            def make_tag(document, name, prop):
671
 
                e = document.createElement(u"property")
672
 
                e.setAttribute(u"name", name)
673
 
                e.setAttribute(u"type", prop._dbus_signature)
674
 
                e.setAttribute(u"access", prop._dbus_access)
675
 
                return e
676
 
            for if_tag in document.getElementsByTagName(u"interface"):
677
 
                for tag in (make_tag(document, name, prop)
678
 
                            for name, prop
679
 
                            in self._get_all_dbus_properties()
680
 
                            if prop._dbus_interface
681
 
                            == if_tag.getAttribute(u"name")):
682
 
                    if_tag.appendChild(tag)
683
 
                # Add the names to the return values for the
684
 
                # "org.freedesktop.DBus.Properties" methods
685
 
                if (if_tag.getAttribute(u"name")
686
 
                    == u"org.freedesktop.DBus.Properties"):
687
 
                    for cn in if_tag.getElementsByTagName(u"method"):
688
 
                        if cn.getAttribute(u"name") == u"Get":
689
 
                            for arg in cn.getElementsByTagName(u"arg"):
690
 
                                if (arg.getAttribute(u"direction")
691
 
                                    == u"out"):
692
 
                                    arg.setAttribute(u"name", u"value")
693
 
                        elif cn.getAttribute(u"name") == u"GetAll":
694
 
                            for arg in cn.getElementsByTagName(u"arg"):
695
 
                                if (arg.getAttribute(u"direction")
696
 
                                    == u"out"):
697
 
                                    arg.setAttribute(u"name", u"props")
698
 
            xmlstring = document.toxml(u"utf-8")
699
 
            document.unlink()
700
 
        except (AttributeError, xml.dom.DOMException,
701
 
                xml.parsers.expat.ExpatError), error:
702
 
            logger.error(u"Failed to override Introspection method",
703
 
                         error)
 
628
                                           connection)
 
629
        document = xml.dom.minidom.parseString(xmlstring)
 
630
        del xmlstring
 
631
        def make_tag(document, name, prop):
 
632
            e = document.createElement(u"property")
 
633
            e.setAttribute(u"name", name)
 
634
            e.setAttribute(u"type", prop._dbus_signature)
 
635
            e.setAttribute(u"access", prop._dbus_access)
 
636
            return e
 
637
        for if_tag in document.getElementsByTagName(u"interface"):
 
638
            for tag in (make_tag(document, name, prop)
 
639
                        for name, prop
 
640
                        in self._get_all_dbus_properties()
 
641
                        if prop._dbus_interface
 
642
                        == if_tag.getAttribute(u"name")):
 
643
                if_tag.appendChild(tag)
 
644
        xmlstring = document.toxml(u"utf-8")
 
645
        document.unlink()
704
646
        return xmlstring
705
647
 
706
648
 
711
653
    dbus_object_path: dbus.ObjectPath
712
654
    bus: dbus.SystemBus()
713
655
    """
714
 
    
715
 
    runtime_expansions = (Client.runtime_expansions
716
 
                          + (u"dbus_object_path",))
717
 
    
718
656
    # dbus.service.Object doesn't use super(), so we can't either.
719
657
    
720
658
    def __init__(self, bus = None, *args, **kwargs):
721
 
        self._approvals_pending = 0
722
659
        self.bus = bus
723
660
        Client.__init__(self, *args, **kwargs)
724
661
        # Only now, when this client is initialized, can it show up on
728
665
                                  + self.name.replace(u".", u"_")))
729
666
        DBusObjectWithProperties.__init__(self, self.bus,
730
667
                                          self.dbus_object_path)
731
 
        
732
 
    def _get_approvals_pending(self):
733
 
        return self._approvals_pending
734
 
    def _set_approvals_pending(self, value):
735
 
        old_value = self._approvals_pending
736
 
        self._approvals_pending = value
737
 
        bval = bool(value)
738
 
        if (hasattr(self, "dbus_object_path")
739
 
            and bval is not bool(old_value)):
740
 
            dbus_bool = dbus.Boolean(bval, variant_level=1)
741
 
            self.PropertyChanged(dbus.String(u"ApprovalPending"),
742
 
                                 dbus_bool)
743
 
 
744
 
    approvals_pending = property(_get_approvals_pending,
745
 
                                 _set_approvals_pending)
746
 
    del _get_approvals_pending, _set_approvals_pending
747
668
    
748
669
    @staticmethod
749
670
    def _datetime_to_dbus(dt, variant_level=0):
756
677
        r = Client.enable(self)
757
678
        if oldstate != self.enabled:
758
679
            # Emit D-Bus signals
759
 
            self.PropertyChanged(dbus.String(u"Enabled"),
 
680
            self.PropertyChanged(dbus.String(u"enabled"),
760
681
                                 dbus.Boolean(True, variant_level=1))
761
682
            self.PropertyChanged(
762
 
                dbus.String(u"LastEnabled"),
 
683
                dbus.String(u"last_enabled"),
763
684
                self._datetime_to_dbus(self.last_enabled,
764
685
                                       variant_level=1))
765
686
        return r
766
687
    
767
 
    def disable(self, quiet = False):
 
688
    def disable(self, signal = True):
768
689
        oldstate = getattr(self, u"enabled", False)
769
 
        r = Client.disable(self, quiet=quiet)
770
 
        if not quiet and oldstate != self.enabled:
 
690
        r = Client.disable(self)
 
691
        if signal and oldstate != self.enabled:
771
692
            # Emit D-Bus signal
772
 
            self.PropertyChanged(dbus.String(u"Enabled"),
 
693
            self.PropertyChanged(dbus.String(u"enabled"),
773
694
                                 dbus.Boolean(False, variant_level=1))
774
695
        return r
775
696
    
787
708
        self.checker_callback_tag = None
788
709
        self.checker = None
789
710
        # Emit D-Bus signal
790
 
        self.PropertyChanged(dbus.String(u"CheckerRunning"),
 
711
        self.PropertyChanged(dbus.String(u"checker_running"),
791
712
                             dbus.Boolean(False, variant_level=1))
792
713
        if os.WIFEXITED(condition):
793
714
            exitstatus = os.WEXITSTATUS(condition)
808
729
        r = Client.checked_ok(self, *args, **kwargs)
809
730
        # Emit D-Bus signal
810
731
        self.PropertyChanged(
811
 
            dbus.String(u"LastCheckedOK"),
 
732
            dbus.String(u"last_checked_ok"),
812
733
            (self._datetime_to_dbus(self.last_checked_ok,
813
734
                                    variant_level=1)))
814
735
        return r
826
747
            # Emit D-Bus signal
827
748
            self.CheckerStarted(self.current_checker_command)
828
749
            self.PropertyChanged(
829
 
                dbus.String(u"CheckerRunning"),
 
750
                dbus.String(u"checker_running"),
830
751
                dbus.Boolean(True, variant_level=1))
831
752
        return r
832
753
    
835
756
        r = Client.stop_checker(self, *args, **kwargs)
836
757
        if (old_checker is not None
837
758
            and getattr(self, u"checker", None) is None):
838
 
            self.PropertyChanged(dbus.String(u"CheckerRunning"),
 
759
            self.PropertyChanged(dbus.String(u"checker_running"),
839
760
                                 dbus.Boolean(False, variant_level=1))
840
761
        return r
841
 
 
842
 
    def _reset_approved(self):
843
 
        self._approved = None
844
 
        return False
845
 
    
846
 
    def approve(self, value=True):
847
 
        self.send_changedstate()
848
 
        self._approved = value
849
 
        gobject.timeout_add(self._timedelta_to_milliseconds
850
 
                            (self.approval_duration),
851
 
                            self._reset_approved)
852
 
    
853
 
    
854
 
    ## D-Bus methods, signals & properties
 
762
    
 
763
    ## D-Bus methods & signals
855
764
    _interface = u"se.bsnet.fukt.Mandos.Client"
856
765
    
857
 
    ## Signals
 
766
    # CheckedOK - method
 
767
    @dbus.service.method(_interface)
 
768
    def CheckedOK(self):
 
769
        return self.checked_ok()
858
770
    
859
771
    # CheckerCompleted - signal
860
772
    @dbus.service.signal(_interface, signature=u"nxs")
874
786
        "D-Bus signal"
875
787
        pass
876
788
    
877
 
    # GotSecret - signal
 
789
    # ReceivedSecret - signal
878
790
    @dbus.service.signal(_interface)
879
 
    def GotSecret(self):
880
 
        """D-Bus signal
881
 
        Is sent after a successful transfer of secret from the Mandos
882
 
        server to mandos-client
883
 
        """
 
791
    def ReceivedSecret(self):
 
792
        "D-Bus signal"
884
793
        pass
885
794
    
886
795
    # Rejected - signal
887
 
    @dbus.service.signal(_interface, signature=u"s")
888
 
    def Rejected(self, reason):
889
 
        "D-Bus signal"
890
 
        pass
891
 
    
892
 
    # NeedApproval - signal
893
 
    @dbus.service.signal(_interface, signature=u"tb")
894
 
    def NeedApproval(self, timeout, default):
895
 
        "D-Bus signal"
896
 
        pass
897
 
    
898
 
    ## Methods
899
 
 
900
 
    # Approve - method
901
 
    @dbus.service.method(_interface, in_signature=u"b")
902
 
    def Approve(self, value):
903
 
        self.approve(value)
904
 
 
905
 
    # CheckedOK - method
906
 
    @dbus.service.method(_interface)
907
 
    def CheckedOK(self):
908
 
        return self.checked_ok()
 
796
    @dbus.service.signal(_interface)
 
797
    def Rejected(self):
 
798
        "D-Bus signal"
 
799
        pass
909
800
    
910
801
    # Enable - method
911
802
    @dbus.service.method(_interface)
930
821
    def StopChecker(self):
931
822
        self.stop_checker()
932
823
    
933
 
    ## Properties
934
 
    
935
 
    # ApprovalPending - property
936
 
    @dbus_service_property(_interface, signature=u"b", access=u"read")
937
 
    def ApprovalPending_dbus_property(self):
938
 
        return dbus.Boolean(bool(self.approvals_pending))
939
 
    
940
 
    # ApprovedByDefault - property
941
 
    @dbus_service_property(_interface, signature=u"b",
942
 
                           access=u"readwrite")
943
 
    def ApprovedByDefault_dbus_property(self, value=None):
944
 
        if value is None:       # get
945
 
            return dbus.Boolean(self.approved_by_default)
946
 
        self.approved_by_default = bool(value)
947
 
        # Emit D-Bus signal
948
 
        self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
949
 
                             dbus.Boolean(value, variant_level=1))
950
 
    
951
 
    # ApprovalDelay - property
952
 
    @dbus_service_property(_interface, signature=u"t",
953
 
                           access=u"readwrite")
954
 
    def ApprovalDelay_dbus_property(self, value=None):
955
 
        if value is None:       # get
956
 
            return dbus.UInt64(self.approval_delay_milliseconds())
957
 
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
958
 
        # Emit D-Bus signal
959
 
        self.PropertyChanged(dbus.String(u"ApprovalDelay"),
960
 
                             dbus.UInt64(value, variant_level=1))
961
 
    
962
 
    # ApprovalDuration - property
963
 
    @dbus_service_property(_interface, signature=u"t",
964
 
                           access=u"readwrite")
965
 
    def ApprovalDuration_dbus_property(self, value=None):
966
 
        if value is None:       # get
967
 
            return dbus.UInt64(self._timedelta_to_milliseconds(
968
 
                    self.approval_duration))
969
 
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
970
 
        # Emit D-Bus signal
971
 
        self.PropertyChanged(dbus.String(u"ApprovalDuration"),
972
 
                             dbus.UInt64(value, variant_level=1))
973
 
    
974
 
    # Name - property
 
824
    # name - property
975
825
    @dbus_service_property(_interface, signature=u"s", access=u"read")
976
 
    def Name_dbus_property(self):
 
826
    def name_dbus_property(self):
977
827
        return dbus.String(self.name)
978
828
    
979
 
    # Fingerprint - property
 
829
    # fingerprint - property
980
830
    @dbus_service_property(_interface, signature=u"s", access=u"read")
981
 
    def Fingerprint_dbus_property(self):
 
831
    def fingerprint_dbus_property(self):
982
832
        return dbus.String(self.fingerprint)
983
833
    
984
 
    # Host - property
 
834
    # host - property
985
835
    @dbus_service_property(_interface, signature=u"s",
986
836
                           access=u"readwrite")
987
 
    def Host_dbus_property(self, value=None):
 
837
    def host_dbus_property(self, value=None):
988
838
        if value is None:       # get
989
839
            return dbus.String(self.host)
990
840
        self.host = value
991
841
        # Emit D-Bus signal
992
 
        self.PropertyChanged(dbus.String(u"Host"),
 
842
        self.PropertyChanged(dbus.String(u"host"),
993
843
                             dbus.String(value, variant_level=1))
994
844
    
995
 
    # Created - property
 
845
    # created - property
996
846
    @dbus_service_property(_interface, signature=u"s", access=u"read")
997
 
    def Created_dbus_property(self):
 
847
    def created_dbus_property(self):
998
848
        return dbus.String(self._datetime_to_dbus(self.created))
999
849
    
1000
 
    # LastEnabled - property
 
850
    # last_enabled - property
1001
851
    @dbus_service_property(_interface, signature=u"s", access=u"read")
1002
 
    def LastEnabled_dbus_property(self):
 
852
    def last_enabled_dbus_property(self):
1003
853
        if self.last_enabled is None:
1004
854
            return dbus.String(u"")
1005
855
        return dbus.String(self._datetime_to_dbus(self.last_enabled))
1006
856
    
1007
 
    # Enabled - property
 
857
    # enabled - property
1008
858
    @dbus_service_property(_interface, signature=u"b",
1009
859
                           access=u"readwrite")
1010
 
    def Enabled_dbus_property(self, value=None):
 
860
    def enabled_dbus_property(self, value=None):
1011
861
        if value is None:       # get
1012
862
            return dbus.Boolean(self.enabled)
1013
863
        if value:
1015
865
        else:
1016
866
            self.disable()
1017
867
    
1018
 
    # LastCheckedOK - property
 
868
    # last_checked_ok - property
1019
869
    @dbus_service_property(_interface, signature=u"s",
1020
870
                           access=u"readwrite")
1021
 
    def LastCheckedOK_dbus_property(self, value=None):
 
871
    def last_checked_ok_dbus_property(self, value=None):
1022
872
        if value is not None:
1023
873
            self.checked_ok()
1024
874
            return
1027
877
        return dbus.String(self._datetime_to_dbus(self
1028
878
                                                  .last_checked_ok))
1029
879
    
1030
 
    # Timeout - property
 
880
    # timeout - property
1031
881
    @dbus_service_property(_interface, signature=u"t",
1032
882
                           access=u"readwrite")
1033
 
    def Timeout_dbus_property(self, value=None):
 
883
    def timeout_dbus_property(self, value=None):
1034
884
        if value is None:       # get
1035
885
            return dbus.UInt64(self.timeout_milliseconds())
1036
886
        self.timeout = datetime.timedelta(0, 0, 0, value)
1037
887
        # Emit D-Bus signal
1038
 
        self.PropertyChanged(dbus.String(u"Timeout"),
 
888
        self.PropertyChanged(dbus.String(u"timeout"),
1039
889
                             dbus.UInt64(value, variant_level=1))
1040
890
        if getattr(self, u"disable_initiator_tag", None) is None:
1041
891
            return
1055
905
            self.disable_initiator_tag = (gobject.timeout_add
1056
906
                                          (time_to_die, self.disable))
1057
907
    
1058
 
    # Interval - property
 
908
    # interval - property
1059
909
    @dbus_service_property(_interface, signature=u"t",
1060
910
                           access=u"readwrite")
1061
 
    def Interval_dbus_property(self, value=None):
 
911
    def interval_dbus_property(self, value=None):
1062
912
        if value is None:       # get
1063
913
            return dbus.UInt64(self.interval_milliseconds())
1064
914
        self.interval = datetime.timedelta(0, 0, 0, value)
1065
915
        # Emit D-Bus signal
1066
 
        self.PropertyChanged(dbus.String(u"Interval"),
 
916
        self.PropertyChanged(dbus.String(u"interval"),
1067
917
                             dbus.UInt64(value, variant_level=1))
1068
918
        if getattr(self, u"checker_initiator_tag", None) is None:
1069
919
            return
1073
923
                                      (value, self.start_checker))
1074
924
        self.start_checker()    # Start one now, too
1075
925
 
1076
 
    # Checker - property
 
926
    # checker - property
1077
927
    @dbus_service_property(_interface, signature=u"s",
1078
928
                           access=u"readwrite")
1079
 
    def Checker_dbus_property(self, value=None):
 
929
    def checker_dbus_property(self, value=None):
1080
930
        if value is None:       # get
1081
931
            return dbus.String(self.checker_command)
1082
932
        self.checker_command = value
1083
933
        # Emit D-Bus signal
1084
 
        self.PropertyChanged(dbus.String(u"Checker"),
 
934
        self.PropertyChanged(dbus.String(u"checker"),
1085
935
                             dbus.String(self.checker_command,
1086
936
                                         variant_level=1))
1087
937
    
1088
 
    # CheckerRunning - property
 
938
    # checker_running - property
1089
939
    @dbus_service_property(_interface, signature=u"b",
1090
940
                           access=u"readwrite")
1091
 
    def CheckerRunning_dbus_property(self, value=None):
 
941
    def checker_running_dbus_property(self, value=None):
1092
942
        if value is None:       # get
1093
943
            return dbus.Boolean(self.checker is not None)
1094
944
        if value:
1096
946
        else:
1097
947
            self.stop_checker()
1098
948
    
1099
 
    # ObjectPath - property
 
949
    # object_path - property
1100
950
    @dbus_service_property(_interface, signature=u"o", access=u"read")
1101
 
    def ObjectPath_dbus_property(self):
 
951
    def object_path_dbus_property(self):
1102
952
        return self.dbus_object_path # is already a dbus.ObjectPath
1103
953
    
1104
 
    # Secret = property
 
954
    # secret = property
1105
955
    @dbus_service_property(_interface, signature=u"ay",
1106
956
                           access=u"write", byte_arrays=True)
1107
 
    def Secret_dbus_property(self, value):
 
957
    def secret_dbus_property(self, value):
1108
958
        self.secret = str(value)
1109
959
    
1110
960
    del _interface
1111
961
 
1112
962
 
1113
 
class ProxyClient(object):
1114
 
    def __init__(self, child_pipe, fpr, address):
1115
 
        self._pipe = child_pipe
1116
 
        self._pipe.send(('init', fpr, address))
1117
 
        if not self._pipe.recv():
1118
 
            raise KeyError()
1119
 
 
1120
 
    def __getattribute__(self, name):
1121
 
        if(name == '_pipe'):
1122
 
            return super(ProxyClient, self).__getattribute__(name)
1123
 
        self._pipe.send(('getattr', name))
1124
 
        data = self._pipe.recv()
1125
 
        if data[0] == 'data':
1126
 
            return data[1]
1127
 
        if data[0] == 'function':
1128
 
            def func(*args, **kwargs):
1129
 
                self._pipe.send(('funcall', name, args, kwargs))
1130
 
                return self._pipe.recv()[1]
1131
 
            return func
1132
 
 
1133
 
    def __setattr__(self, name, value):
1134
 
        if(name == '_pipe'):
1135
 
            return super(ProxyClient, self).__setattr__(name, value)
1136
 
        self._pipe.send(('setattr', name, value))
1137
 
 
1138
 
 
1139
963
class ClientHandler(socketserver.BaseRequestHandler, object):
1140
964
    """A class to handle client connections.
1141
965
    
1143
967
    Note: This will run in its own forked process."""
1144
968
    
1145
969
    def handle(self):
1146
 
        with contextlib.closing(self.server.child_pipe) as child_pipe:
1147
 
            logger.info(u"TCP connection from: %s",
1148
 
                        unicode(self.client_address))
1149
 
            logger.debug(u"Pipe FD: %d",
1150
 
                         self.server.child_pipe.fileno())
1151
 
 
 
970
        logger.info(u"TCP connection from: %s",
 
971
                    unicode(self.client_address))
 
972
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
 
973
        # Open IPC pipe to parent process
 
974
        with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
1152
975
            session = (gnutls.connection
1153
976
                       .ClientSession(self.request,
1154
977
                                      gnutls.connection
1155
978
                                      .X509Credentials()))
1156
 
 
 
979
            
 
980
            line = self.request.makefile().readline()
 
981
            logger.debug(u"Protocol version: %r", line)
 
982
            try:
 
983
                if int(line.strip().split()[0]) > 1:
 
984
                    raise RuntimeError
 
985
            except (ValueError, IndexError, RuntimeError), error:
 
986
                logger.error(u"Unknown protocol version: %s", error)
 
987
                return
 
988
            
1157
989
            # Note: gnutls.connection.X509Credentials is really a
1158
990
            # generic GnuTLS certificate credentials object so long as
1159
991
            # no X.509 keys are added to it.  Therefore, we can use it
1160
992
            # here despite using OpenPGP certificates.
1161
 
 
 
993
            
1162
994
            #priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1163
995
            #                      u"+AES-256-CBC", u"+SHA1",
1164
996
            #                      u"+COMP-NULL", u"+CTYPE-OPENPGP",
1170
1002
            (gnutls.library.functions
1171
1003
             .gnutls_priority_set_direct(session._c_object,
1172
1004
                                         priority, None))
1173
 
 
1174
 
            # Start communication using the Mandos protocol
1175
 
            # Get protocol number
1176
 
            line = self.request.makefile().readline()
1177
 
            logger.debug(u"Protocol version: %r", line)
1178
 
            try:
1179
 
                if int(line.strip().split()[0]) > 1:
1180
 
                    raise RuntimeError
1181
 
            except (ValueError, IndexError, RuntimeError), error:
1182
 
                logger.error(u"Unknown protocol version: %s", error)
1183
 
                return
1184
 
 
1185
 
            # Start GnuTLS connection
 
1005
            
1186
1006
            try:
1187
1007
                session.handshake()
1188
1008
            except gnutls.errors.GNUTLSError, error:
1191
1011
                # established.  Just abandon the request.
1192
1012
                return
1193
1013
            logger.debug(u"Handshake succeeded")
1194
 
 
1195
 
            approval_required = False
1196
1014
            try:
1197
 
                try:
1198
 
                    fpr = self.fingerprint(self.peer_certificate
1199
 
                                           (session))
1200
 
                except (TypeError, gnutls.errors.GNUTLSError), error:
1201
 
                    logger.warning(u"Bad certificate: %s", error)
1202
 
                    return
1203
 
                logger.debug(u"Fingerprint: %s", fpr)
1204
 
 
1205
 
                try:
1206
 
                    client = ProxyClient(child_pipe, fpr,
1207
 
                                         self.client_address)
1208
 
                except KeyError:
1209
 
                    return
1210
 
                
1211
 
                if client.approval_delay:
1212
 
                    delay = client.approval_delay
1213
 
                    client.approvals_pending += 1
1214
 
                    approval_required = True
1215
 
                
1216
 
                while True:
1217
 
                    if not client.enabled:
1218
 
                        logger.warning(u"Client %s is disabled",
1219
 
                                       client.name)
1220
 
                        if self.server.use_dbus:
1221
 
                            # Emit D-Bus signal
1222
 
                            client.Rejected("Disabled")                    
1223
 
                        return
1224
 
                    
1225
 
                    if client._approved or not client.approval_delay:
1226
 
                        #We are approved or approval is disabled
1227
 
                        break
1228
 
                    elif client._approved is None:
1229
 
                        logger.info(u"Client %s needs approval",
1230
 
                                    client.name)
1231
 
                        if self.server.use_dbus:
1232
 
                            # Emit D-Bus signal
1233
 
                            client.NeedApproval(
1234
 
                                client.approval_delay_milliseconds(),
1235
 
                                client.approved_by_default)
1236
 
                    else:
1237
 
                        logger.warning(u"Client %s was not approved",
1238
 
                                       client.name)
1239
 
                        if self.server.use_dbus:
1240
 
                            # Emit D-Bus signal
1241
 
                            client.Rejected("Denied")
1242
 
                        return
1243
 
                    
1244
 
                    #wait until timeout or approved
1245
 
                    #x = float(client._timedelta_to_milliseconds(delay))
1246
 
                    time = datetime.datetime.now()
1247
 
                    client.changedstate.acquire()
1248
 
                    client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1249
 
                    client.changedstate.release()
1250
 
                    time2 = datetime.datetime.now()
1251
 
                    if (time2 - time) >= delay:
1252
 
                        if not client.approved_by_default:
1253
 
                            logger.warning("Client %s timed out while"
1254
 
                                           " waiting for approval",
1255
 
                                           client.name)
1256
 
                            if self.server.use_dbus:
1257
 
                                # Emit D-Bus signal
1258
 
                                client.Rejected("Approval timed out")
1259
 
                            return
1260
 
                        else:
1261
 
                            break
1262
 
                    else:
1263
 
                        delay -= time2 - time
1264
 
                
1265
 
                sent_size = 0
1266
 
                while sent_size < len(client.secret):
1267
 
                    try:
1268
 
                        sent = session.send(client.secret[sent_size:])
1269
 
                    except (gnutls.errors.GNUTLSError), error:
1270
 
                        logger.warning("gnutls send failed")
1271
 
                        return
1272
 
                    logger.debug(u"Sent: %d, remaining: %d",
1273
 
                                 sent, len(client.secret)
1274
 
                                 - (sent_size + sent))
1275
 
                    sent_size += sent
1276
 
 
1277
 
                logger.info(u"Sending secret to %s", client.name)
1278
 
                # bump the timeout as if seen
1279
 
                client.checked_ok()
1280
 
                if self.server.use_dbus:
1281
 
                    # Emit D-Bus signal
1282
 
                    client.GotSecret()
 
1015
                fpr = self.fingerprint(self.peer_certificate(session))
 
1016
            except (TypeError, gnutls.errors.GNUTLSError), error:
 
1017
                logger.warning(u"Bad certificate: %s", error)
 
1018
                session.bye()
 
1019
                return
 
1020
            logger.debug(u"Fingerprint: %s", fpr)
1283
1021
            
1284
 
            finally:
1285
 
                if approval_required:
1286
 
                    client.approvals_pending -= 1
1287
 
                try:
1288
 
                    session.bye()
1289
 
                except (gnutls.errors.GNUTLSError), error:
1290
 
                    logger.warning("GnuTLS bye failed")
 
1022
            for c in self.server.clients:
 
1023
                if c.fingerprint == fpr:
 
1024
                    client = c
 
1025
                    break
 
1026
            else:
 
1027
                ipc.write(u"NOTFOUND %s %s\n"
 
1028
                          % (fpr, unicode(self.client_address)))
 
1029
                session.bye()
 
1030
                return
 
1031
            # Have to check if client.still_valid(), since it is
 
1032
            # possible that the client timed out while establishing
 
1033
            # the GnuTLS session.
 
1034
            if not client.still_valid():
 
1035
                ipc.write(u"INVALID %s\n" % client.name)
 
1036
                session.bye()
 
1037
                return
 
1038
            ipc.write(u"SENDING %s\n" % client.name)
 
1039
            sent_size = 0
 
1040
            while sent_size < len(client.secret):
 
1041
                sent = session.send(client.secret[sent_size:])
 
1042
                logger.debug(u"Sent: %d, remaining: %d",
 
1043
                             sent, len(client.secret)
 
1044
                             - (sent_size + sent))
 
1045
                sent_size += sent
 
1046
            session.bye()
1291
1047
    
1292
1048
    @staticmethod
1293
1049
    def peer_certificate(session):
1353
1109
        return hex_fpr
1354
1110
 
1355
1111
 
1356
 
class MultiprocessingMixIn(object):
1357
 
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
1358
 
    def sub_process_main(self, request, address):
1359
 
        try:
1360
 
            self.finish_request(request, address)
1361
 
        except:
1362
 
            self.handle_error(request, address)
1363
 
        self.close_request(request)
1364
 
            
1365
 
    def process_request(self, request, address):
1366
 
        """Start a new process to process the request."""
1367
 
        multiprocessing.Process(target = self.sub_process_main,
1368
 
                                args = (request, address)).start()
1369
 
 
1370
 
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1371
 
    """ adds a pipe to the MixIn """
 
1112
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
 
1113
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
1372
1114
    def process_request(self, request, client_address):
1373
1115
        """Overrides and wraps the original process_request().
1374
1116
        
1375
1117
        This function creates a new pipe in self.pipe
1376
1118
        """
1377
 
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
1378
 
 
1379
 
        super(MultiprocessingMixInWithPipe,
 
1119
        self.pipe = os.pipe()
 
1120
        super(ForkingMixInWithPipe,
1380
1121
              self).process_request(request, client_address)
1381
 
        self.child_pipe.close()
1382
 
        self.add_pipe(parent_pipe)
1383
 
 
1384
 
    def add_pipe(self, parent_pipe):
 
1122
        os.close(self.pipe[1])  # close write end
 
1123
        self.add_pipe(self.pipe[0])
 
1124
    def add_pipe(self, pipe):
1385
1125
        """Dummy function; override as necessary"""
1386
 
        pass
1387
 
 
1388
 
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
 
1126
        os.close(pipe)
 
1127
 
 
1128
 
 
1129
class IPv6_TCPServer(ForkingMixInWithPipe,
1389
1130
                     socketserver.TCPServer, object):
1390
1131
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
1391
1132
    
1476
1217
            return socketserver.TCPServer.server_activate(self)
1477
1218
    def enable(self):
1478
1219
        self.enabled = True
1479
 
    def add_pipe(self, parent_pipe):
 
1220
    def add_pipe(self, pipe):
1480
1221
        # Call "handle_ipc" for both data and EOF events
1481
 
        gobject.io_add_watch(parent_pipe.fileno(),
1482
 
                             gobject.IO_IN | gobject.IO_HUP,
1483
 
                             functools.partial(self.handle_ipc,
1484
 
                                               parent_pipe = parent_pipe))
1485
 
        
1486
 
    def handle_ipc(self, source, condition, parent_pipe=None,
1487
 
                   client_object=None):
 
1222
        gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
 
1223
                             self.handle_ipc)
 
1224
    def handle_ipc(self, source, condition, file_objects={}):
1488
1225
        condition_names = {
1489
1226
            gobject.IO_IN: u"IN",   # There is data to read.
1490
1227
            gobject.IO_OUT: u"OUT", # Data can be written (without
1499
1236
                                       for cond, name in
1500
1237
                                       condition_names.iteritems()
1501
1238
                                       if cond & condition)
1502
 
        # error or the other end of multiprocessing.Pipe has closed
1503
 
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1504
 
            return False
1505
 
        
1506
 
        # Read a request from the child
1507
 
        request = parent_pipe.recv()
1508
 
        command = request[0]
1509
 
        
1510
 
        if command == 'init':
1511
 
            fpr = request[1]
1512
 
            address = request[2]
1513
 
            
1514
 
            for c in self.clients:
1515
 
                if c.fingerprint == fpr:
1516
 
                    client = c
1517
 
                    break
1518
 
            else:
1519
 
                logger.warning(u"Client not found for fingerprint: %s, ad"
1520
 
                               u"dress: %s", fpr, address)
1521
 
                if self.use_dbus:
1522
 
                    # Emit D-Bus signal
1523
 
                    mandos_dbus_service.ClientNotFound(fpr, address)
1524
 
                parent_pipe.send(False)
1525
 
                return False
1526
 
            
1527
 
            gobject.io_add_watch(parent_pipe.fileno(),
1528
 
                                 gobject.IO_IN | gobject.IO_HUP,
1529
 
                                 functools.partial(self.handle_ipc,
1530
 
                                                   parent_pipe = parent_pipe,
1531
 
                                                   client_object = client))
1532
 
            parent_pipe.send(True)
1533
 
            # remove the old hook in favor of the new above hook on same fileno
1534
 
            return False
1535
 
        if command == 'funcall':
1536
 
            funcname = request[1]
1537
 
            args = request[2]
1538
 
            kwargs = request[3]
1539
 
            
1540
 
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1541
 
 
1542
 
        if command == 'getattr':
1543
 
            attrname = request[1]
1544
 
            if callable(client_object.__getattribute__(attrname)):
1545
 
                parent_pipe.send(('function',))
1546
 
            else:
1547
 
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1548
 
        
1549
 
        if command == 'setattr':
1550
 
            attrname = request[1]
1551
 
            value = request[2]
1552
 
            setattr(client_object, attrname, value)
1553
 
 
 
1239
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
 
1240
                     conditions_string)
 
1241
        
 
1242
        # Turn the pipe file descriptor into a Python file object
 
1243
        if source not in file_objects:
 
1244
            file_objects[source] = os.fdopen(source, u"r", 1)
 
1245
        
 
1246
        # Read a line from the file object
 
1247
        cmdline = file_objects[source].readline()
 
1248
        if not cmdline:             # Empty line means end of file
 
1249
            # close the IPC pipe
 
1250
            file_objects[source].close()
 
1251
            del file_objects[source]
 
1252
            
 
1253
            # Stop calling this function
 
1254
            return False
 
1255
        
 
1256
        logger.debug(u"IPC command: %r", cmdline)
 
1257
        
 
1258
        # Parse and act on command
 
1259
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
 
1260
        
 
1261
        if cmd == u"NOTFOUND":
 
1262
            logger.warning(u"Client not found for fingerprint: %s",
 
1263
                           args)
 
1264
            if self.use_dbus:
 
1265
                # Emit D-Bus signal
 
1266
                mandos_dbus_service.ClientNotFound(args)
 
1267
        elif cmd == u"INVALID":
 
1268
            for client in self.clients:
 
1269
                if client.name == args:
 
1270
                    logger.warning(u"Client %s is invalid", args)
 
1271
                    if self.use_dbus:
 
1272
                        # Emit D-Bus signal
 
1273
                        client.Rejected()
 
1274
                    break
 
1275
            else:
 
1276
                logger.error(u"Unknown client %s is invalid", args)
 
1277
        elif cmd == u"SENDING":
 
1278
            for client in self.clients:
 
1279
                if client.name == args:
 
1280
                    logger.info(u"Sending secret to %s", client.name)
 
1281
                    client.checked_ok()
 
1282
                    if self.use_dbus:
 
1283
                        # Emit D-Bus signal
 
1284
                        client.ReceivedSecret()
 
1285
                    break
 
1286
            else:
 
1287
                logger.error(u"Sending secret to unknown client %s",
 
1288
                             args)
 
1289
        else:
 
1290
            logger.error(u"Unknown IPC command: %r", cmdline)
 
1291
        
 
1292
        # Keep calling this function
1554
1293
        return True
1555
1294
 
1556
1295
 
1586
1325
            elif suffix == u"w":
1587
1326
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1588
1327
            else:
1589
 
                raise ValueError(u"Unknown suffix %r" % suffix)
1590
 
        except (ValueError, IndexError), e:
1591
 
            raise ValueError(e.message)
 
1328
                raise ValueError
 
1329
        except (ValueError, IndexError):
 
1330
            raise ValueError
1592
1331
        timevalue += delta
1593
1332
    return timevalue
1594
1333
 
1607
1346
        def if_nametoindex(interface):
1608
1347
            "Get an interface index the hard way, i.e. using fcntl()"
1609
1348
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1610
 
            with contextlib.closing(socket.socket()) as s:
 
1349
            with closing(socket.socket()) as s:
1611
1350
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1612
1351
                                    struct.pack(str(u"16s16x"),
1613
1352
                                                interface))
1633
1372
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1634
1373
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1635
1374
            raise OSError(errno.ENODEV,
1636
 
                          u"%s not a character device"
1637
 
                          % os.path.devnull)
 
1375
                          u"/dev/null not a character device")
1638
1376
        os.dup2(null, sys.stdin.fileno())
1639
1377
        os.dup2(null, sys.stdout.fileno())
1640
1378
        os.dup2(null, sys.stderr.fileno())
1659
1397
    parser.add_option("--debug", action=u"store_true",
1660
1398
                      help=u"Debug mode; run in foreground and log to"
1661
1399
                      u" terminal")
1662
 
    parser.add_option("--debuglevel", type=u"string", metavar="Level",
1663
 
                      help=u"Debug level for stdout output")
1664
1400
    parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1665
1401
                      u" priority string (see GnuTLS documentation)")
1666
1402
    parser.add_option("--servicename", type=u"string",
1691
1427
                        u"servicename": u"Mandos",
1692
1428
                        u"use_dbus": u"True",
1693
1429
                        u"use_ipv6": u"True",
1694
 
                        u"debuglevel": u"",
1695
1430
                        }
1696
1431
    
1697
1432
    # Parse config file for server-global settings
1714
1449
    # options, if set.
1715
1450
    for option in (u"interface", u"address", u"port", u"debug",
1716
1451
                   u"priority", u"servicename", u"configdir",
1717
 
                   u"use_dbus", u"use_ipv6", u"debuglevel"):
 
1452
                   u"use_dbus", u"use_ipv6"):
1718
1453
        value = getattr(options, option)
1719
1454
        if value is not None:
1720
1455
            server_settings[option] = value
1729
1464
    
1730
1465
    # For convenience
1731
1466
    debug = server_settings[u"debug"]
1732
 
    debuglevel = server_settings[u"debuglevel"]
1733
1467
    use_dbus = server_settings[u"use_dbus"]
1734
1468
    use_ipv6 = server_settings[u"use_ipv6"]
1735
 
 
 
1469
    
 
1470
    if not debug:
 
1471
        syslogger.setLevel(logging.WARNING)
 
1472
        console.setLevel(logging.WARNING)
 
1473
    
1736
1474
    if server_settings[u"servicename"] != u"Mandos":
1737
1475
        syslogger.setFormatter(logging.Formatter
1738
1476
                               (u'Mandos (%s) [%%(process)d]:'
1744
1482
                        u"interval": u"5m",
1745
1483
                        u"checker": u"fping -q -- %%(host)s",
1746
1484
                        u"host": u"",
1747
 
                        u"approval_delay": u"0s",
1748
 
                        u"approval_duration": u"1s",
1749
1485
                        }
1750
1486
    client_config = configparser.SafeConfigParser(client_defaults)
1751
1487
    client_config.read(os.path.join(server_settings[u"configdir"],
1757
1493
    tcp_server = MandosServer((server_settings[u"address"],
1758
1494
                               server_settings[u"port"]),
1759
1495
                              ClientHandler,
1760
 
                              interface=(server_settings[u"interface"]
1761
 
                                         or None),
 
1496
                              interface=server_settings[u"interface"],
1762
1497
                              use_ipv6=use_ipv6,
1763
1498
                              gnutls_priority=
1764
1499
                              server_settings[u"priority"],
1765
1500
                              use_dbus=use_dbus)
1766
 
    if not debug:
1767
 
        pidfilename = u"/var/run/mandos.pid"
1768
 
        try:
1769
 
            pidfile = open(pidfilename, u"w")
1770
 
        except IOError:
1771
 
            logger.error(u"Could not open file %r", pidfilename)
 
1501
    pidfilename = u"/var/run/mandos.pid"
 
1502
    try:
 
1503
        pidfile = open(pidfilename, u"w")
 
1504
    except IOError:
 
1505
        logger.error(u"Could not open file %r", pidfilename)
1772
1506
    
1773
1507
    try:
1774
1508
        uid = pwd.getpwnam(u"_mandos").pw_uid
1791
1525
        if error[0] != errno.EPERM:
1792
1526
            raise error
1793
1527
    
1794
 
    if not debug and not debuglevel:
1795
 
        syslogger.setLevel(logging.WARNING)
1796
 
        console.setLevel(logging.WARNING)
1797
 
    if debuglevel:
1798
 
        level = getattr(logging, debuglevel.upper())
1799
 
        syslogger.setLevel(level)
1800
 
        console.setLevel(level)
1801
 
 
 
1528
    # Enable all possible GnuTLS debugging
1802
1529
    if debug:
1803
 
        # Enable all possible GnuTLS debugging
1804
 
        
1805
1530
        # "Use a log level over 10 to enable all debugging options."
1806
1531
        # - GnuTLS manual
1807
1532
        gnutls.library.functions.gnutls_global_set_log_level(11)
1812
1537
        
1813
1538
        (gnutls.library.functions
1814
1539
         .gnutls_global_set_log_function(debug_gnutls))
1815
 
        
1816
 
        # Redirect stdin so all checkers get /dev/null
1817
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1818
 
        os.dup2(null, sys.stdin.fileno())
1819
 
        if null > 2:
1820
 
            os.close(null)
1821
 
    else:
1822
 
        # No console logging
1823
 
        logger.removeHandler(console)
1824
 
    
1825
1540
    
1826
1541
    global main_loop
1827
1542
    # From the Avahi example code
1830
1545
    bus = dbus.SystemBus()
1831
1546
    # End of Avahi example code
1832
1547
    if use_dbus:
1833
 
        try:
1834
 
            bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1835
 
                                            bus, do_not_queue=True)
1836
 
        except dbus.exceptions.NameExistsException, e:
1837
 
            logger.error(unicode(e) + u", disabling D-Bus")
1838
 
            use_dbus = False
1839
 
            server_settings[u"use_dbus"] = False
1840
 
            tcp_server.use_dbus = False
 
1548
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1841
1549
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1842
1550
    service = AvahiService(name = server_settings[u"servicename"],
1843
1551
                           servicetype = u"_mandos._tcp",
1845
1553
    if server_settings["interface"]:
1846
1554
        service.interface = (if_nametoindex
1847
1555
                             (str(server_settings[u"interface"])))
1848
 
 
1849
 
    if not debug:
1850
 
        # Close all input and output, do double fork, etc.
1851
 
        daemon()
1852
 
        
1853
 
    global multiprocessing_manager
1854
 
    multiprocessing_manager = multiprocessing.Manager()
1855
1556
    
1856
1557
    client_class = Client
1857
1558
    if use_dbus:
1858
1559
        client_class = functools.partial(ClientDBus, bus = bus)
1859
 
    def client_config_items(config, section):
1860
 
        special_settings = {
1861
 
            "approved_by_default":
1862
 
                lambda: config.getboolean(section,
1863
 
                                          "approved_by_default"),
1864
 
            }
1865
 
        for name, value in config.items(section):
1866
 
            try:
1867
 
                yield (name, special_settings[name]())
1868
 
            except KeyError:
1869
 
                yield (name, value)
1870
 
    
1871
1560
    tcp_server.clients.update(set(
1872
1561
            client_class(name = section,
1873
 
                         config= dict(client_config_items(
1874
 
                        client_config, section)))
 
1562
                         config= dict(client_config.items(section)))
1875
1563
            for section in client_config.sections()))
1876
1564
    if not tcp_server.clients:
1877
1565
        logger.warning(u"No clients defined")
 
1566
    
 
1567
    if debug:
 
1568
        # Redirect stdin so all checkers get /dev/null
 
1569
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
1570
        os.dup2(null, sys.stdin.fileno())
 
1571
        if null > 2:
 
1572
            os.close(null)
 
1573
    else:
 
1574
        # No console logging
 
1575
        logger.removeHandler(console)
 
1576
        # Close all input and output, do double fork, etc.
 
1577
        daemon()
 
1578
    
 
1579
    try:
 
1580
        with closing(pidfile):
 
1581
            pid = os.getpid()
 
1582
            pidfile.write(str(pid) + "\n")
 
1583
        del pidfile
 
1584
    except IOError:
 
1585
        logger.error(u"Could not write to file %r with PID %d",
 
1586
                     pidfilename, pid)
 
1587
    except NameError:
 
1588
        # "pidfile" was never created
 
1589
        pass
 
1590
    del pidfilename
 
1591
    
 
1592
    def cleanup():
 
1593
        "Cleanup function; run on exit"
 
1594
        service.cleanup()
1878
1595
        
 
1596
        while tcp_server.clients:
 
1597
            client = tcp_server.clients.pop()
 
1598
            client.disable_hook = None
 
1599
            client.disable()
 
1600
    
 
1601
    atexit.register(cleanup)
 
1602
    
1879
1603
    if not debug:
1880
 
        try:
1881
 
            with pidfile:
1882
 
                pid = os.getpid()
1883
 
                pidfile.write(str(pid) + "\n")
1884
 
            del pidfile
1885
 
        except IOError:
1886
 
            logger.error(u"Could not write to file %r with PID %d",
1887
 
                         pidfilename, pid)
1888
 
        except NameError:
1889
 
            # "pidfile" was never created
1890
 
            pass
1891
 
        del pidfilename
1892
 
        
1893
1604
        signal.signal(signal.SIGINT, signal.SIG_IGN)
1894
 
 
1895
1605
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1896
1606
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1897
1607
    
1902
1612
                dbus.service.Object.__init__(self, bus, u"/")
1903
1613
            _interface = u"se.bsnet.fukt.Mandos"
1904
1614
            
1905
 
            @dbus.service.signal(_interface, signature=u"o")
1906
 
            def ClientAdded(self, objpath):
 
1615
            @dbus.service.signal(_interface, signature=u"oa{sv}")
 
1616
            def ClientAdded(self, objpath, properties):
1907
1617
                "D-Bus signal"
1908
1618
                pass
1909
1619
            
1910
 
            @dbus.service.signal(_interface, signature=u"ss")
1911
 
            def ClientNotFound(self, fingerprint, address):
 
1620
            @dbus.service.signal(_interface, signature=u"s")
 
1621
            def ClientNotFound(self, fingerprint):
1912
1622
                "D-Bus signal"
1913
1623
                pass
1914
1624
            
1940
1650
                        tcp_server.clients.remove(c)
1941
1651
                        c.remove_from_connection()
1942
1652
                        # Don't signal anything except ClientRemoved
1943
 
                        c.disable(quiet=True)
 
1653
                        c.disable(signal=False)
1944
1654
                        # Emit D-Bus signal
1945
1655
                        self.ClientRemoved(object_path, c.name)
1946
1656
                        return
1947
 
                raise KeyError(object_path)
 
1657
                raise KeyError
1948
1658
            
1949
1659
            del _interface
1950
1660
        
1951
1661
        mandos_dbus_service = MandosDBusService()
1952
1662
    
1953
 
    def cleanup():
1954
 
        "Cleanup function; run on exit"
1955
 
        service.cleanup()
1956
 
        
1957
 
        while tcp_server.clients:
1958
 
            client = tcp_server.clients.pop()
1959
 
            if use_dbus:
1960
 
                client.remove_from_connection()
1961
 
            client.disable_hook = None
1962
 
            # Don't signal anything except ClientRemoved
1963
 
            client.disable(quiet=True)
1964
 
            if use_dbus:
1965
 
                # Emit D-Bus signal
1966
 
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
1967
 
                                                  client.name)
1968
 
    
1969
 
    atexit.register(cleanup)
1970
 
    
1971
1663
    for client in tcp_server.clients:
1972
1664
        if use_dbus:
1973
1665
            # Emit D-Bus signal
1974
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
 
1666
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
 
1667
                                            client.GetAll(u""))
1975
1668
        client.enable()
1976
1669
    
1977
1670
    tcp_server.enable()
1995
1688
            service.activate()
1996
1689
        except dbus.exceptions.DBusException, error:
1997
1690
            logger.critical(u"DBusException: %s", error)
1998
 
            cleanup()
1999
1691
            sys.exit(1)
2000
1692
        # End of Avahi example code
2001
1693
        
2008
1700
        main_loop.run()
2009
1701
    except AvahiError, error:
2010
1702
        logger.critical(u"AvahiError: %s", error)
2011
 
        cleanup()
2012
1703
        sys.exit(1)
2013
1704
    except KeyboardInterrupt:
2014
1705
        if debug:
2015
1706
            print >> sys.stderr
2016
1707
        logger.debug(u"Server received KeyboardInterrupt")
2017
1708
    logger.debug(u"Server exiting")
2018
 
    # Must run before the D-Bus bus name gets deregistered
2019
 
    cleanup()
2020
1709
 
2021
1710
if __name__ == '__main__':
2022
1711
    main()