/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

  • Committer: Teddy Hogeborn
  • Date: 2012-06-17 14:55:31 UTC
  • mto: (301.1.1 release) (237.7.272 trunk)
  • mto: This revision was merged to the branch mainline in revision 302.
  • Revision ID: teddy@recompile.se-20120617145531-o24z982oerm6xb6s
* mandos: New "--foreground" option.

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
from __future__ import (division, absolute_import, print_function,
35
35
                        unicode_literals)
36
36
 
 
37
from future_builtins import *
 
38
 
37
39
import SocketServer as socketserver
38
40
import socket
39
41
import argparse
86
88
    except ImportError:
87
89
        SO_BINDTODEVICE = None
88
90
 
89
 
version = "1.5.3"
 
91
version = "1.5.5"
90
92
stored_state_file = "clients.pickle"
91
93
 
92
94
logger = logging.getLogger()
149
151
    def __enter__(self):
150
152
        return self
151
153
    
152
 
    def __exit__ (self, exc_type, exc_value, traceback):
 
154
    def __exit__(self, exc_type, exc_value, traceback):
153
155
        self._cleanup()
154
156
        return False
155
157
    
377
379
                                 self.server_state_changed)
378
380
        self.server_state_changed(self.server.GetState())
379
381
 
 
382
 
380
383
class AvahiServiceToSyslog(AvahiService):
381
384
    def rename(self):
382
385
        """Add the new name to the syslog messages"""
387
390
                                .format(self.name)))
388
391
        return ret
389
392
 
 
393
 
390
394
def timedelta_to_milliseconds(td):
391
395
    "Convert a datetime.timedelta() to milliseconds"
392
396
    return ((td.days * 24 * 60 * 60 * 1000)
393
397
            + (td.seconds * 1000)
394
398
            + (td.microseconds // 1000))
395
399
 
 
400
 
396
401
class Client(object):
397
402
    """A representation of a client host served by this server.
398
403
    
437
442
    """
438
443
    
439
444
    runtime_expansions = ("approval_delay", "approval_duration",
440
 
                          "created", "enabled", "fingerprint",
441
 
                          "host", "interval", "last_checked_ok",
 
445
                          "created", "enabled", "expires",
 
446
                          "fingerprint", "host", "interval",
 
447
                          "last_approval_request", "last_checked_ok",
442
448
                          "last_enabled", "name", "timeout")
443
449
    client_defaults = { "timeout": "5m",
444
450
                        "extended_timeout": "15m",
570
576
        if getattr(self, "enabled", False):
571
577
            # Already enabled
572
578
            return
573
 
        self.send_changedstate()
574
579
        self.expires = datetime.datetime.utcnow() + self.timeout
575
580
        self.enabled = True
576
581
        self.last_enabled = datetime.datetime.utcnow()
577
582
        self.init_checker()
 
583
        self.send_changedstate()
578
584
    
579
585
    def disable(self, quiet=True):
580
586
        """Disable this client."""
581
587
        if not getattr(self, "enabled", False):
582
588
            return False
583
589
        if not quiet:
584
 
            self.send_changedstate()
585
 
        if not quiet:
586
590
            logger.info("Disabling client %s", self.name)
587
 
        if getattr(self, "disable_initiator_tag", False):
 
591
        if getattr(self, "disable_initiator_tag", None) is not None:
588
592
            gobject.source_remove(self.disable_initiator_tag)
589
593
            self.disable_initiator_tag = None
590
594
        self.expires = None
591
 
        if getattr(self, "checker_initiator_tag", False):
 
595
        if getattr(self, "checker_initiator_tag", None) is not None:
592
596
            gobject.source_remove(self.checker_initiator_tag)
593
597
            self.checker_initiator_tag = None
594
598
        self.stop_checker()
595
599
        self.enabled = False
 
600
        if not quiet:
 
601
            self.send_changedstate()
596
602
        # Do not run this again if called by a gobject.timeout_add
597
603
        return False
598
604
    
602
608
    def init_checker(self):
603
609
        # Schedule a new checker to be started an 'interval' from now,
604
610
        # and every interval from then on.
 
611
        if self.checker_initiator_tag is not None:
 
612
            gobject.source_remove(self.checker_initiator_tag)
605
613
        self.checker_initiator_tag = (gobject.timeout_add
606
614
                                      (self.interval_milliseconds(),
607
615
                                       self.start_checker))
608
616
        # Schedule a disable() when 'timeout' has passed
 
617
        if self.disable_initiator_tag is not None:
 
618
            gobject.source_remove(self.disable_initiator_tag)
609
619
        self.disable_initiator_tag = (gobject.timeout_add
610
620
                                   (self.timeout_milliseconds(),
611
621
                                    self.disable))
642
652
            timeout = self.timeout
643
653
        if self.disable_initiator_tag is not None:
644
654
            gobject.source_remove(self.disable_initiator_tag)
 
655
            self.disable_initiator_tag = None
645
656
        if getattr(self, "enabled", False):
646
657
            self.disable_initiator_tag = (gobject.timeout_add
647
658
                                          (timedelta_to_milliseconds
657
668
        If a checker already exists, leave it running and do
658
669
        nothing."""
659
670
        # The reason for not killing a running checker is that if we
660
 
        # did that, then if a checker (for some reason) started
661
 
        # running slowly and taking more than 'interval' time, the
662
 
        # client would inevitably timeout, since no checker would get
663
 
        # a chance to run to completion.  If we instead leave running
 
671
        # did that, and if a checker (for some reason) started running
 
672
        # slowly and taking more than 'interval' time, then the client
 
673
        # would inevitably timeout, since no checker would get a
 
674
        # chance to run to completion.  If we instead leave running
664
675
        # checkers alone, the checker would have to take more time
665
676
        # than 'timeout' for the client to be disabled, which is as it
666
677
        # should be.
680
691
                                      self.current_checker_command)
681
692
        # Start a new checker if needed
682
693
        if self.checker is None:
 
694
            # Escape attributes for the shell
 
695
            escaped_attrs = dict(
 
696
                (attr, re.escape(unicode(getattr(self, attr))))
 
697
                for attr in
 
698
                self.runtime_expansions)
683
699
            try:
684
 
                # In case checker_command has exactly one % operator
685
 
                command = self.checker_command % self.host
686
 
            except TypeError:
687
 
                # Escape attributes for the shell
688
 
                escaped_attrs = dict(
689
 
                    (attr,
690
 
                     re.escape(unicode(str(getattr(self, attr, "")),
691
 
                                       errors=
692
 
                                       'replace')))
693
 
                    for attr in
694
 
                    self.runtime_expansions)
695
 
                
696
 
                try:
697
 
                    command = self.checker_command % escaped_attrs
698
 
                except TypeError as error:
699
 
                    logger.error('Could not format string "%s"',
700
 
                                 self.checker_command, exc_info=error)
701
 
                    return True # Try again later
 
700
                command = self.checker_command % escaped_attrs
 
701
            except TypeError as error:
 
702
                logger.error('Could not format string "%s"',
 
703
                             self.checker_command, exc_info=error)
 
704
                return True # Try again later
702
705
            self.current_checker_command = command
703
706
            try:
704
707
                logger.info("Starting checker %r for %s",
710
713
                self.checker = subprocess.Popen(command,
711
714
                                                close_fds=True,
712
715
                                                shell=True, cwd="/")
713
 
                self.checker_callback_tag = (gobject.child_watch_add
714
 
                                             (self.checker.pid,
715
 
                                              self.checker_callback,
716
 
                                              data=command))
717
 
                # The checker may have completed before the gobject
718
 
                # watch was added.  Check for this.
719
 
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
720
 
                if pid:
721
 
                    gobject.source_remove(self.checker_callback_tag)
722
 
                    self.checker_callback(pid, status, command)
723
716
            except OSError as error:
724
717
                logger.error("Failed to start subprocess",
725
718
                             exc_info=error)
 
719
            self.checker_callback_tag = (gobject.child_watch_add
 
720
                                         (self.checker.pid,
 
721
                                          self.checker_callback,
 
722
                                          data=command))
 
723
            # The checker may have completed before the gobject
 
724
            # watch was added.  Check for this.
 
725
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
 
726
            if pid:
 
727
                gobject.source_remove(self.checker_callback_tag)
 
728
                self.checker_callback(pid, status, command)
726
729
        # Re-run this periodically if run by gobject.timeout_add
727
730
        return True
728
731
    
979
982
                            tag.appendChild(ann_tag)
980
983
                # Add interface annotation tags
981
984
                for annotation, value in dict(
982
 
                    itertools.chain(
983
 
                        *(annotations().iteritems()
984
 
                          for name, annotations in
985
 
                          self._get_all_dbus_things("interface")
986
 
                          if name == if_tag.getAttribute("name")
987
 
                          ))).iteritems():
 
985
                    itertools.chain.from_iterable(
 
986
                        annotations().iteritems()
 
987
                        for name, annotations in
 
988
                        self._get_all_dbus_things("interface")
 
989
                        if name == if_tag.getAttribute("name")
 
990
                        )).iteritems():
988
991
                    ann_tag = document.createElement("annotation")
989
992
                    ann_tag.setAttribute("name", annotation)
990
993
                    ann_tag.setAttribute("value", value)
1013
1016
        return xmlstring
1014
1017
 
1015
1018
 
1016
 
def datetime_to_dbus (dt, variant_level=0):
 
1019
def datetime_to_dbus(dt, variant_level=0):
1017
1020
    """Convert a UTC datetime.datetime() to a D-Bus type."""
1018
1021
    if dt is None:
1019
1022
        return dbus.String("", variant_level = variant_level)
1021
1024
                       variant_level=variant_level)
1022
1025
 
1023
1026
 
1024
 
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
1025
 
                                  .__metaclass__):
1026
 
    """Applied to an empty subclass of a D-Bus object, this metaclass
1027
 
    will add additional D-Bus attributes matching a certain pattern.
 
1027
def alternate_dbus_interfaces(alt_interface_names, deprecate=True):
 
1028
    """A class decorator; applied to a subclass of
 
1029
    dbus.service.Object, it will add alternate D-Bus attributes with
 
1030
    interface names according to the "alt_interface_names" mapping.
 
1031
    Usage:
 
1032
    
 
1033
    @alternate_dbus_interfaces({"org.example.Interface":
 
1034
                                    "net.example.AlternateInterface"})
 
1035
    class SampleDBusObject(dbus.service.Object):
 
1036
        @dbus.service.method("org.example.Interface")
 
1037
        def SampleDBusMethod():
 
1038
            pass
 
1039
    
 
1040
    The above "SampleDBusMethod" on "SampleDBusObject" will be
 
1041
    reachable via two interfaces: "org.example.Interface" and
 
1042
    "net.example.AlternateInterface", the latter of which will have
 
1043
    its D-Bus annotation "org.freedesktop.DBus.Deprecated" set to
 
1044
    "true", unless "deprecate" is passed with a False value.
 
1045
    
 
1046
    This works for methods and signals, and also for D-Bus properties
 
1047
    (from DBusObjectWithProperties) and interfaces (from the
 
1048
    dbus_interface_annotations decorator).
1028
1049
    """
1029
 
    def __new__(mcs, name, bases, attr):
1030
 
        # Go through all the base classes which could have D-Bus
1031
 
        # methods, signals, or properties in them
1032
 
        old_interface_names = []
1033
 
        for base in (b for b in bases
1034
 
                     if issubclass(b, dbus.service.Object)):
1035
 
            # Go though all attributes of the base class
1036
 
            for attrname, attribute in inspect.getmembers(base):
 
1050
    def wrapper(cls):
 
1051
        for orig_interface_name, alt_interface_name in (
 
1052
            alt_interface_names.iteritems()):
 
1053
            attr = {}
 
1054
            interface_names = set()
 
1055
            # Go though all attributes of the class
 
1056
            for attrname, attribute in inspect.getmembers(cls):
1037
1057
                # Ignore non-D-Bus attributes, and D-Bus attributes
1038
1058
                # with the wrong interface name
1039
1059
                if (not hasattr(attribute, "_dbus_interface")
1040
1060
                    or not attribute._dbus_interface
1041
 
                    .startswith("se.recompile.Mandos")):
 
1061
                    .startswith(orig_interface_name)):
1042
1062
                    continue
1043
1063
                # Create an alternate D-Bus interface name based on
1044
1064
                # the current name
1045
1065
                alt_interface = (attribute._dbus_interface
1046
 
                                 .replace("se.recompile.Mandos",
1047
 
                                          "se.bsnet.fukt.Mandos"))
1048
 
                if alt_interface != attribute._dbus_interface:
1049
 
                    old_interface_names.append(alt_interface)
 
1066
                                 .replace(orig_interface_name,
 
1067
                                          alt_interface_name))
 
1068
                interface_names.add(alt_interface)
1050
1069
                # Is this a D-Bus signal?
1051
1070
                if getattr(attribute, "_dbus_is_signal", False):
1052
1071
                    # Extract the original non-method function by
1074
1093
                    except AttributeError:
1075
1094
                        pass
1076
1095
                    # Define a creator of a function to call both the
1077
 
                    # old and new functions, so both the old and new
1078
 
                    # signals gets sent when the function is called
 
1096
                    # original and alternate functions, so both the
 
1097
                    # original and alternate signals gets sent when
 
1098
                    # the function is called
1079
1099
                    def fixscope(func1, func2):
1080
1100
                        """This function is a scope container to pass
1081
1101
                        func1 and func2 to the "call_both" function
1088
1108
                        return call_both
1089
1109
                    # Create the "call_both" function and add it to
1090
1110
                    # the class
1091
 
                    attr[attrname] = fixscope(attribute,
1092
 
                                              new_function)
 
1111
                    attr[attrname] = fixscope(attribute, new_function)
1093
1112
                # Is this a D-Bus method?
1094
1113
                elif getattr(attribute, "_dbus_is_method", False):
1095
1114
                    # Create a new, but exactly alike, function
1151
1170
                                        attribute.func_name,
1152
1171
                                        attribute.func_defaults,
1153
1172
                                        attribute.func_closure)))
1154
 
        # Deprecate all old interfaces
1155
 
        iname="_AlternateDBusNamesMetaclass_interface_annotation{0}"
1156
 
        for old_interface_name in old_interface_names:
1157
 
            @dbus_interface_annotations(old_interface_name)
1158
 
            def func(self):
1159
 
                return { "org.freedesktop.DBus.Deprecated": "true" }
1160
 
            # Find an unused name
1161
 
            for aname in (iname.format(i) for i in itertools.count()):
1162
 
                if aname not in attr:
1163
 
                    attr[aname] = func
1164
 
                    break
1165
 
        return type.__new__(mcs, name, bases, attr)
1166
 
 
1167
 
 
 
1173
            if deprecate:
 
1174
                # Deprecate all alternate interfaces
 
1175
                iname="_AlternateDBusNames_interface_annotation{0}"
 
1176
                for interface_name in interface_names:
 
1177
                    @dbus_interface_annotations(interface_name)
 
1178
                    def func(self):
 
1179
                        return { "org.freedesktop.DBus.Deprecated":
 
1180
                                     "true" }
 
1181
                    # Find an unused name
 
1182
                    for aname in (iname.format(i)
 
1183
                                  for i in itertools.count()):
 
1184
                        if aname not in attr:
 
1185
                            attr[aname] = func
 
1186
                            break
 
1187
            if interface_names:
 
1188
                # Replace the class with a new subclass of it with
 
1189
                # methods, signals, etc. as created above.
 
1190
                cls = type(b"{0}Alternate".format(cls.__name__),
 
1191
                           (cls,), attr)
 
1192
        return cls
 
1193
    return wrapper
 
1194
 
 
1195
 
 
1196
@alternate_dbus_interfaces({"se.recompile.Mandos":
 
1197
                                "se.bsnet.fukt.Mandos"})
1168
1198
class ClientDBus(Client, DBusObjectWithProperties):
1169
1199
    """A Client class using D-Bus
1170
1200
    
1306
1336
        return False
1307
1337
    
1308
1338
    def approve(self, value=True):
1309
 
        self.send_changedstate()
1310
1339
        self.approved = value
1311
1340
        gobject.timeout_add(timedelta_to_milliseconds
1312
1341
                            (self.approval_duration),
1313
1342
                            self._reset_approved)
 
1343
        self.send_changedstate()
1314
1344
    
1315
1345
    ## D-Bus methods, signals & properties
1316
1346
    _interface = "se.recompile.Mandos.Client"
1500
1530
    def Timeout_dbus_property(self, value=None):
1501
1531
        if value is None:       # get
1502
1532
            return dbus.UInt64(self.timeout_milliseconds())
 
1533
        old_timeout = self.timeout
1503
1534
        self.timeout = datetime.timedelta(0, 0, 0, value)
1504
 
        # Reschedule timeout
 
1535
        # Reschedule disabling
1505
1536
        if self.enabled:
1506
1537
            now = datetime.datetime.utcnow()
1507
 
            time_to_die = timedelta_to_milliseconds(
1508
 
                (self.last_checked_ok + self.timeout) - now)
1509
 
            if time_to_die <= 0:
 
1538
            self.expires += self.timeout - old_timeout
 
1539
            if self.expires <= now:
1510
1540
                # The timeout has passed
1511
1541
                self.disable()
1512
1542
            else:
1513
 
                self.expires = (now +
1514
 
                                datetime.timedelta(milliseconds =
1515
 
                                                   time_to_die))
1516
1543
                if (getattr(self, "disable_initiator_tag", None)
1517
1544
                    is None):
1518
1545
                    return
1519
1546
                gobject.source_remove(self.disable_initiator_tag)
1520
 
                self.disable_initiator_tag = (gobject.timeout_add
1521
 
                                              (time_to_die,
1522
 
                                               self.disable))
 
1547
                self.disable_initiator_tag = (
 
1548
                    gobject.timeout_add(
 
1549
                        timedelta_to_milliseconds(self.expires - now),
 
1550
                        self.disable))
1523
1551
    
1524
1552
    # ExtendedTimeout - property
1525
1553
    @dbus_service_property(_interface, signature="t",
1604
1632
        self._pipe.send(('setattr', name, value))
1605
1633
 
1606
1634
 
1607
 
class ClientDBusTransitional(ClientDBus):
1608
 
    __metaclass__ = AlternateDBusNamesMetaclass
1609
 
 
1610
 
 
1611
1635
class ClientHandler(socketserver.BaseRequestHandler, object):
1612
1636
    """A class to handle client connections.
1613
1637
    
1717
1741
                    #wait until timeout or approved
1718
1742
                    time = datetime.datetime.now()
1719
1743
                    client.changedstate.acquire()
1720
 
                    (client.changedstate.wait
1721
 
                     (float(client.timedelta_to_milliseconds(delay)
1722
 
                            / 1000)))
 
1744
                    client.changedstate.wait(
 
1745
                        float(timedelta_to_milliseconds(delay)
 
1746
                              / 1000))
1723
1747
                    client.changedstate.release()
1724
1748
                    time2 = datetime.datetime.now()
1725
1749
                    if (time2 - time) >= delay:
1741
1765
                    try:
1742
1766
                        sent = session.send(client.secret[sent_size:])
1743
1767
                    except gnutls.errors.GNUTLSError as error:
1744
 
                        logger.warning("gnutls send failed")
 
1768
                        logger.warning("gnutls send failed",
 
1769
                                       exc_info=error)
1745
1770
                        return
1746
1771
                    logger.debug("Sent: %d, remaining: %d",
1747
1772
                                 sent, len(client.secret)
1761
1786
                try:
1762
1787
                    session.bye()
1763
1788
                except gnutls.errors.GNUTLSError as error:
1764
 
                    logger.warning("GnuTLS bye failed")
 
1789
                    logger.warning("GnuTLS bye failed",
 
1790
                                   exc_info=error)
1765
1791
    
1766
1792
    @staticmethod
1767
1793
    def peer_certificate(session):
1839
1865
    def process_request(self, request, address):
1840
1866
        """Start a new process to process the request."""
1841
1867
        proc = multiprocessing.Process(target = self.sub_process_main,
1842
 
                                       args = (request,
1843
 
                                               address))
 
1868
                                       args = (request, address))
1844
1869
        proc.start()
1845
1870
        return proc
1846
1871
 
1874
1899
        use_ipv6:       Boolean; to use IPv6 or not
1875
1900
    """
1876
1901
    def __init__(self, server_address, RequestHandlerClass,
1877
 
                 interface=None, use_ipv6=True):
 
1902
                 interface=None, use_ipv6=True, socketfd=None):
 
1903
        """If socketfd is set, use that file descriptor instead of
 
1904
        creating a new one with socket.socket().
 
1905
        """
1878
1906
        self.interface = interface
1879
1907
        if use_ipv6:
1880
1908
            self.address_family = socket.AF_INET6
 
1909
        if socketfd is not None:
 
1910
            # Save the file descriptor
 
1911
            self.socketfd = socketfd
 
1912
            # Save the original socket.socket() function
 
1913
            self.socket_socket = socket.socket
 
1914
            # To implement --socket, we monkey patch socket.socket.
 
1915
            # 
 
1916
            # (When socketserver.TCPServer is a new-style class, we
 
1917
            # could make self.socket into a property instead of monkey
 
1918
            # patching socket.socket.)
 
1919
            # 
 
1920
            # Create a one-time-only replacement for socket.socket()
 
1921
            @functools.wraps(socket.socket)
 
1922
            def socket_wrapper(*args, **kwargs):
 
1923
                # Restore original function so subsequent calls are
 
1924
                # not affected.
 
1925
                socket.socket = self.socket_socket
 
1926
                del self.socket_socket
 
1927
                # This time only, return a new socket object from the
 
1928
                # saved file descriptor.
 
1929
                return socket.fromfd(self.socketfd, *args, **kwargs)
 
1930
            # Replace socket.socket() function with wrapper
 
1931
            socket.socket = socket_wrapper
 
1932
        # The socketserver.TCPServer.__init__ will call
 
1933
        # socket.socket(), which might be our replacement,
 
1934
        # socket_wrapper(), if socketfd was set.
1881
1935
        socketserver.TCPServer.__init__(self, server_address,
1882
1936
                                        RequestHandlerClass)
 
1937
    
1883
1938
    def server_bind(self):
1884
1939
        """This overrides the normal server_bind() function
1885
1940
        to bind to an interface if one was specified, and also NOT to
1893
1948
                try:
1894
1949
                    self.socket.setsockopt(socket.SOL_SOCKET,
1895
1950
                                           SO_BINDTODEVICE,
1896
 
                                           str(self.interface
1897
 
                                               + '\0'))
 
1951
                                           str(self.interface + '\0'))
1898
1952
                except socket.error as error:
1899
 
                    if error[0] == errno.EPERM:
1900
 
                        logger.error("No permission to"
1901
 
                                     " bind to interface %s",
1902
 
                                     self.interface)
1903
 
                    elif error[0] == errno.ENOPROTOOPT:
 
1953
                    if error.errno == errno.EPERM:
 
1954
                        logger.error("No permission to bind to"
 
1955
                                     " interface %s", self.interface)
 
1956
                    elif error.errno == errno.ENOPROTOOPT:
1904
1957
                        logger.error("SO_BINDTODEVICE not available;"
1905
1958
                                     " cannot bind to interface %s",
1906
1959
                                     self.interface)
 
1960
                    elif error.errno == errno.ENODEV:
 
1961
                        logger.error("Interface %s does not exist,"
 
1962
                                     " cannot bind", self.interface)
1907
1963
                    else:
1908
1964
                        raise
1909
1965
        # Only bind(2) the socket if we really need to.
1939
1995
    """
1940
1996
    def __init__(self, server_address, RequestHandlerClass,
1941
1997
                 interface=None, use_ipv6=True, clients=None,
1942
 
                 gnutls_priority=None, use_dbus=True):
 
1998
                 gnutls_priority=None, use_dbus=True, socketfd=None):
1943
1999
        self.enabled = False
1944
2000
        self.clients = clients
1945
2001
        if self.clients is None:
1949
2005
        IPv6_TCPServer.__init__(self, server_address,
1950
2006
                                RequestHandlerClass,
1951
2007
                                interface = interface,
1952
 
                                use_ipv6 = use_ipv6)
 
2008
                                use_ipv6 = use_ipv6,
 
2009
                                socketfd = socketfd)
1953
2010
    def server_activate(self):
1954
2011
        if self.enabled:
1955
2012
            return socketserver.TCPServer.server_activate(self)
1968
2025
    
1969
2026
    def handle_ipc(self, source, condition, parent_pipe=None,
1970
2027
                   proc = None, client_object=None):
1971
 
        condition_names = {
1972
 
            gobject.IO_IN: "IN",   # There is data to read.
1973
 
            gobject.IO_OUT: "OUT", # Data can be written (without
1974
 
                                    # blocking).
1975
 
            gobject.IO_PRI: "PRI", # There is urgent data to read.
1976
 
            gobject.IO_ERR: "ERR", # Error condition.
1977
 
            gobject.IO_HUP: "HUP"  # Hung up (the connection has been
1978
 
                                    # broken, usually for pipes and
1979
 
                                    # sockets).
1980
 
            }
1981
 
        conditions_string = ' | '.join(name
1982
 
                                       for cond, name in
1983
 
                                       condition_names.iteritems()
1984
 
                                       if cond & condition)
1985
2028
        # error, or the other end of multiprocessing.Pipe has closed
1986
 
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
 
2029
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
1987
2030
            # Wait for other process to exit
1988
2031
            proc.join()
1989
2032
            return False
2150
2193
    parser.add_argument("--no-restore", action="store_false",
2151
2194
                        dest="restore", help="Do not restore stored"
2152
2195
                        " state")
 
2196
    parser.add_argument("--socket", type=int,
 
2197
                        help="Specify a file descriptor to a network"
 
2198
                        " socket to use instead of creating one")
2153
2199
    parser.add_argument("--statedir", metavar="DIR",
2154
2200
                        help="Directory to save/restore state in")
 
2201
    parser.add_argument("--foreground", action="store_true",
 
2202
                        help="Run in foreground")
2155
2203
    
2156
2204
    options = parser.parse_args()
2157
2205
    
2172
2220
                        "use_ipv6": "True",
2173
2221
                        "debuglevel": "",
2174
2222
                        "restore": "True",
2175
 
                        "statedir": "/var/lib/mandos"
 
2223
                        "socket": "",
 
2224
                        "statedir": "/var/lib/mandos",
 
2225
                        "foreground": "False",
2176
2226
                        }
2177
2227
    
2178
2228
    # Parse config file for server-global settings
2183
2233
    # Convert the SafeConfigParser object to a dict
2184
2234
    server_settings = server_config.defaults()
2185
2235
    # Use the appropriate methods on the non-string config options
2186
 
    for option in ("debug", "use_dbus", "use_ipv6"):
 
2236
    for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
2187
2237
        server_settings[option] = server_config.getboolean("DEFAULT",
2188
2238
                                                           option)
2189
2239
    if server_settings["port"]:
2190
2240
        server_settings["port"] = server_config.getint("DEFAULT",
2191
2241
                                                       "port")
 
2242
    if server_settings["socket"]:
 
2243
        server_settings["socket"] = server_config.getint("DEFAULT",
 
2244
                                                         "socket")
 
2245
        # Later, stdin will, and stdout and stderr might, be dup'ed
 
2246
        # over with an opened os.devnull.  But we don't want this to
 
2247
        # happen with a supplied network socket.
 
2248
        if 0 <= server_settings["socket"] <= 2:
 
2249
            server_settings["socket"] = os.dup(server_settings
 
2250
                                               ["socket"])
2192
2251
    del server_config
2193
2252
    
2194
2253
    # Override the settings from the config file with command line
2196
2255
    for option in ("interface", "address", "port", "debug",
2197
2256
                   "priority", "servicename", "configdir",
2198
2257
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2199
 
                   "statedir"):
 
2258
                   "statedir", "socket", "foreground"):
2200
2259
        value = getattr(options, option)
2201
2260
        if value is not None:
2202
2261
            server_settings[option] = value
2205
2264
    for option in server_settings.keys():
2206
2265
        if type(server_settings[option]) is str:
2207
2266
            server_settings[option] = unicode(server_settings[option])
 
2267
    # Debug implies foreground
 
2268
    if server_settings["debug"]:
 
2269
        server_settings["foreground"] = True
2208
2270
    # Now we have our good server settings in "server_settings"
2209
2271
    
2210
2272
    ##################################################################
2216
2278
    use_ipv6 = server_settings["use_ipv6"]
2217
2279
    stored_state_path = os.path.join(server_settings["statedir"],
2218
2280
                                     stored_state_file)
 
2281
    foreground = server_settings["foreground"]
2219
2282
    
2220
2283
    if debug:
2221
2284
        initlogger(debug, logging.DEBUG)
2250
2313
                              use_ipv6=use_ipv6,
2251
2314
                              gnutls_priority=
2252
2315
                              server_settings["priority"],
2253
 
                              use_dbus=use_dbus)
2254
 
    if not debug:
 
2316
                              use_dbus=use_dbus,
 
2317
                              socketfd=(server_settings["socket"]
 
2318
                                        or None))
 
2319
    if not foreground:
2255
2320
        pidfilename = "/var/run/mandos.pid"
 
2321
        pidfile = None
2256
2322
        try:
2257
2323
            pidfile = open(pidfilename, "w")
2258
 
        except IOError:
2259
 
            logger.error("Could not open file %r", pidfilename)
 
2324
        except IOError as e:
 
2325
            logger.error("Could not open file %r", pidfilename,
 
2326
                         exc_info=e)
2260
2327
    
2261
2328
    for name in ("_mandos", "mandos", "nobody"):
2262
2329
        try:
2272
2339
        os.setgid(gid)
2273
2340
        os.setuid(uid)
2274
2341
    except OSError as error:
2275
 
        if error[0] != errno.EPERM:
 
2342
        if error.errno != errno.EPERM:
2276
2343
            raise error
2277
2344
    
2278
2345
    if debug:
2296
2363
            os.close(null)
2297
2364
    
2298
2365
    # Need to fork before connecting to D-Bus
2299
 
    if not debug:
 
2366
    if not foreground:
2300
2367
        # Close all input and output, do double fork, etc.
2301
2368
        daemon()
2302
2369
    
 
2370
    # multiprocessing will use threads, so before we use gobject we
 
2371
    # need to inform gobject that threads will be used.
2303
2372
    gobject.threads_init()
2304
2373
    
2305
2374
    global main_loop
2316
2385
                            ("se.bsnet.fukt.Mandos", bus,
2317
2386
                             do_not_queue=True))
2318
2387
        except dbus.exceptions.NameExistsException as e:
2319
 
            logger.error(unicode(e) + ", disabling D-Bus")
 
2388
            logger.error("Disabling D-Bus:", exc_info=e)
2320
2389
            use_dbus = False
2321
2390
            server_settings["use_dbus"] = False
2322
2391
            tcp_server.use_dbus = False
2334
2403
    
2335
2404
    client_class = Client
2336
2405
    if use_dbus:
2337
 
        client_class = functools.partial(ClientDBusTransitional,
2338
 
                                         bus = bus)
 
2406
        client_class = functools.partial(ClientDBus, bus = bus)
2339
2407
    
2340
2408
    client_settings = Client.config_parser(client_config)
2341
2409
    old_client_settings = {}
2349
2417
                                                     (stored_state))
2350
2418
            os.remove(stored_state_path)
2351
2419
        except IOError as e:
2352
 
            logger.warning("Could not load persistent state: {0}"
2353
 
                           .format(e))
2354
 
            if e.errno != errno.ENOENT:
 
2420
            if e.errno == errno.ENOENT:
 
2421
                logger.warning("Could not load persistent state: {0}"
 
2422
                                .format(os.strerror(e.errno)))
 
2423
            else:
 
2424
                logger.critical("Could not load persistent state:",
 
2425
                                exc_info=e)
2355
2426
                raise
2356
2427
        except EOFError as e:
2357
2428
            logger.warning("Could not load persistent state: "
2358
 
                           "EOFError: {0}".format(e))
 
2429
                           "EOFError:", exc_info=e)
2359
2430
    
2360
2431
    with PGPEngine() as pgp:
2361
2432
        for client_name, client in clients_data.iteritems():
2431
2502
    if not tcp_server.clients:
2432
2503
        logger.warning("No clients defined")
2433
2504
    
2434
 
    if not debug:
2435
 
        try:
2436
 
            with pidfile:
2437
 
                pid = os.getpid()
2438
 
                pidfile.write(str(pid) + "\n".encode("utf-8"))
2439
 
            del pidfile
2440
 
        except IOError:
2441
 
            logger.error("Could not write to file %r with PID %d",
2442
 
                         pidfilename, pid)
2443
 
        except NameError:
2444
 
            # "pidfile" was never created
2445
 
            pass
 
2505
    if not foreground:
 
2506
        if pidfile is not None:
 
2507
            try:
 
2508
                with pidfile:
 
2509
                    pid = os.getpid()
 
2510
                    pidfile.write(str(pid) + "\n".encode("utf-8"))
 
2511
            except IOError:
 
2512
                logger.error("Could not write to file %r with PID %d",
 
2513
                             pidfilename, pid)
 
2514
        del pidfile
2446
2515
        del pidfilename
2447
 
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2448
2516
    
2449
2517
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2450
2518
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2451
2519
    
2452
2520
    if use_dbus:
 
2521
        @alternate_dbus_interfaces({"se.recompile.Mandos":
 
2522
                                        "se.bsnet.fukt.Mandos"})
2453
2523
        class MandosDBusService(DBusObjectWithProperties):
2454
2524
            """A D-Bus proxy object"""
2455
2525
            def __init__(self):
2509
2579
            
2510
2580
            del _interface
2511
2581
        
2512
 
        class MandosDBusServiceTransitional(MandosDBusService):
2513
 
            __metaclass__ = AlternateDBusNamesMetaclass
2514
 
        mandos_dbus_service = MandosDBusServiceTransitional()
 
2582
        mandos_dbus_service = MandosDBusService()
2515
2583
    
2516
2584
    def cleanup():
2517
2585
        "Cleanup function; run on exit"
2550
2618
                del client_settings[client.name]["secret"]
2551
2619
        
2552
2620
        try:
2553
 
            tempfd, tempname = tempfile.mkstemp(suffix=".pickle",
2554
 
                                                prefix="clients-",
2555
 
                                                dir=os.path.dirname
2556
 
                                                (stored_state_path))
2557
 
            with os.fdopen(tempfd, "wb") as stored_state:
 
2621
            with (tempfile.NamedTemporaryFile
 
2622
                  (mode='wb', suffix=".pickle", prefix='clients-',
 
2623
                   dir=os.path.dirname(stored_state_path),
 
2624
                   delete=False)) as stored_state:
2558
2625
                pickle.dump((clients, client_settings), stored_state)
 
2626
                tempname=stored_state.name
2559
2627
            os.rename(tempname, stored_state_path)
2560
2628
        except (IOError, OSError) as e:
2561
 
            logger.warning("Could not save persistent state: {0}"
2562
 
                           .format(e))
2563
2629
            if not debug:
2564
2630
                try:
2565
2631
                    os.remove(tempname)
2566
2632
                except NameError:
2567
2633
                    pass
2568
 
            if e.errno not in set((errno.ENOENT, errno.EACCES,
2569
 
                                   errno.EEXIST)):
 
2634
            if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
 
2635
                logger.warning("Could not save persistent state: {0}"
 
2636
                               .format(os.strerror(e.errno)))
 
2637
            else:
 
2638
                logger.warning("Could not save persistent state:",
 
2639
                               exc_info=e)
2570
2640
                raise e
2571
2641
        
2572
2642
        # Delete all clients, and settings from config