/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2012-05-06 16:13:00 UTC
  • Revision ID: teddy@recompile.se-20120506161300-43rls2rr4qub3zhw
* mandos: Use a class decorator instead of a metaclass to provide
          alternate D-Bus interface names on D-Bus object attributes.
  (alternate_dbus_interfaces): New class decorator.
  (AlternateDBusNamesMetaclass, ClientDBusTransitional,
   MandosDBusServiceTransitional): Removed; all users changed.
  (ClientDbus, MandosDBusService): Use new "alternate_dbus_interfaces"
                                   class decorator.

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
 
 
39
37
import SocketServer as socketserver
40
38
import socket
41
39
import argparse
68
66
import binascii
69
67
import tempfile
70
68
import itertools
71
 
import collections
72
69
 
73
70
import dbus
74
71
import dbus.service
89
86
    except ImportError:
90
87
        SO_BINDTODEVICE = None
91
88
 
92
 
version = "1.6.0"
 
89
version = "1.5.3"
93
90
stored_state_file = "clients.pickle"
94
91
 
95
92
logger = logging.getLogger()
152
149
    def __enter__(self):
153
150
        return self
154
151
    
155
 
    def __exit__(self, exc_type, exc_value, traceback):
 
152
    def __exit__ (self, exc_type, exc_value, traceback):
156
153
        self._cleanup()
157
154
        return False
158
155
    
380
377
                                 self.server_state_changed)
381
378
        self.server_state_changed(self.server.GetState())
382
379
 
383
 
 
384
380
class AvahiServiceToSyslog(AvahiService):
385
381
    def rename(self):
386
382
        """Add the new name to the syslog messages"""
391
387
                                .format(self.name)))
392
388
        return ret
393
389
 
394
 
 
395
390
def timedelta_to_milliseconds(td):
396
391
    "Convert a datetime.timedelta() to milliseconds"
397
392
    return ((td.days * 24 * 60 * 60 * 1000)
398
393
            + (td.seconds * 1000)
399
394
            + (td.microseconds // 1000))
400
395
 
401
 
 
402
396
class Client(object):
403
397
    """A representation of a client host served by this server.
404
398
    
443
437
    """
444
438
    
445
439
    runtime_expansions = ("approval_delay", "approval_duration",
446
 
                          "created", "enabled", "expires",
447
 
                          "fingerprint", "host", "interval",
448
 
                          "last_approval_request", "last_checked_ok",
 
440
                          "created", "enabled", "fingerprint",
 
441
                          "host", "interval", "last_checked_ok",
449
442
                          "last_enabled", "name", "timeout")
450
 
    client_defaults = { "timeout": "PT5M",
451
 
                        "extended_timeout": "PT15M",
452
 
                        "interval": "PT2M",
 
443
    client_defaults = { "timeout": "5m",
 
444
                        "extended_timeout": "15m",
 
445
                        "interval": "2m",
453
446
                        "checker": "fping -q -- %%(host)s",
454
447
                        "host": "",
455
 
                        "approval_delay": "PT0S",
456
 
                        "approval_duration": "PT1S",
 
448
                        "approval_delay": "0s",
 
449
                        "approval_duration": "1s",
457
450
                        "approved_by_default": "True",
458
451
                        "enabled": "True",
459
452
                        }
577
570
        if getattr(self, "enabled", False):
578
571
            # Already enabled
579
572
            return
 
573
        self.send_changedstate()
580
574
        self.expires = datetime.datetime.utcnow() + self.timeout
581
575
        self.enabled = True
582
576
        self.last_enabled = datetime.datetime.utcnow()
583
577
        self.init_checker()
584
 
        self.send_changedstate()
585
578
    
586
579
    def disable(self, quiet=True):
587
580
        """Disable this client."""
588
581
        if not getattr(self, "enabled", False):
589
582
            return False
590
583
        if not quiet:
 
584
            self.send_changedstate()
 
585
        if not quiet:
591
586
            logger.info("Disabling client %s", self.name)
592
 
        if getattr(self, "disable_initiator_tag", None) is not None:
 
587
        if getattr(self, "disable_initiator_tag", False):
593
588
            gobject.source_remove(self.disable_initiator_tag)
594
589
            self.disable_initiator_tag = None
595
590
        self.expires = None
596
 
        if getattr(self, "checker_initiator_tag", None) is not None:
 
591
        if getattr(self, "checker_initiator_tag", False):
597
592
            gobject.source_remove(self.checker_initiator_tag)
598
593
            self.checker_initiator_tag = None
599
594
        self.stop_checker()
600
595
        self.enabled = False
601
 
        if not quiet:
602
 
            self.send_changedstate()
603
596
        # Do not run this again if called by a gobject.timeout_add
604
597
        return False
605
598
    
609
602
    def init_checker(self):
610
603
        # Schedule a new checker to be started an 'interval' from now,
611
604
        # and every interval from then on.
612
 
        if self.checker_initiator_tag is not None:
613
 
            gobject.source_remove(self.checker_initiator_tag)
614
605
        self.checker_initiator_tag = (gobject.timeout_add
615
606
                                      (self.interval_milliseconds(),
616
607
                                       self.start_checker))
617
608
        # Schedule a disable() when 'timeout' has passed
618
 
        if self.disable_initiator_tag is not None:
619
 
            gobject.source_remove(self.disable_initiator_tag)
620
609
        self.disable_initiator_tag = (gobject.timeout_add
621
610
                                   (self.timeout_milliseconds(),
622
611
                                    self.disable))
653
642
            timeout = self.timeout
654
643
        if self.disable_initiator_tag is not None:
655
644
            gobject.source_remove(self.disable_initiator_tag)
656
 
            self.disable_initiator_tag = None
657
645
        if getattr(self, "enabled", False):
658
646
            self.disable_initiator_tag = (gobject.timeout_add
659
647
                                          (timedelta_to_milliseconds
669
657
        If a checker already exists, leave it running and do
670
658
        nothing."""
671
659
        # The reason for not killing a running checker is that if we
672
 
        # did that, and if a checker (for some reason) started running
673
 
        # slowly and taking more than 'interval' time, then the client
674
 
        # would inevitably timeout, since no checker would get a
675
 
        # chance to run to completion.  If we instead leave running
 
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
676
664
        # checkers alone, the checker would have to take more time
677
665
        # than 'timeout' for the client to be disabled, which is as it
678
666
        # should be.
692
680
                                      self.current_checker_command)
693
681
        # Start a new checker if needed
694
682
        if self.checker is None:
695
 
            # Escape attributes for the shell
696
 
            escaped_attrs = dict(
697
 
                (attr, re.escape(unicode(getattr(self, attr))))
698
 
                for attr in
699
 
                self.runtime_expansions)
700
683
            try:
701
 
                command = self.checker_command % escaped_attrs
702
 
            except TypeError as error:
703
 
                logger.error('Could not format string "%s"',
704
 
                             self.checker_command, exc_info=error)
705
 
                return True # Try again later
 
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
706
702
            self.current_checker_command = command
707
703
            try:
708
704
                logger.info("Starting checker %r for %s",
714
710
                self.checker = subprocess.Popen(command,
715
711
                                                close_fds=True,
716
712
                                                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)
717
723
            except OSError as error:
718
724
                logger.error("Failed to start subprocess",
719
725
                             exc_info=error)
720
 
                return True
721
 
            self.checker_callback_tag = (gobject.child_watch_add
722
 
                                         (self.checker.pid,
723
 
                                          self.checker_callback,
724
 
                                          data=command))
725
 
            # The checker may have completed before the gobject
726
 
            # watch was added.  Check for this.
727
 
            try:
728
 
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
729
 
            except OSError as error:
730
 
                if error.errno == errno.ECHILD:
731
 
                    # This should never happen
732
 
                    logger.error("Child process vanished",
733
 
                                 exc_info=error)
734
 
                    return True
735
 
                raise
736
 
            if pid:
737
 
                gobject.source_remove(self.checker_callback_tag)
738
 
                self.checker_callback(pid, status, command)
739
726
        # Re-run this periodically if run by gobject.timeout_add
740
727
        return True
741
728
    
992
979
                            tag.appendChild(ann_tag)
993
980
                # Add interface annotation tags
994
981
                for annotation, value in dict(
995
 
                    itertools.chain.from_iterable(
996
 
                        annotations().iteritems()
997
 
                        for name, annotations in
998
 
                        self._get_all_dbus_things("interface")
999
 
                        if name == if_tag.getAttribute("name")
1000
 
                        )).iteritems():
 
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():
1001
988
                    ann_tag = document.createElement("annotation")
1002
989
                    ann_tag.setAttribute("name", annotation)
1003
990
                    ann_tag.setAttribute("value", value)
1026
1013
        return xmlstring
1027
1014
 
1028
1015
 
1029
 
def datetime_to_dbus(dt, variant_level=0):
 
1016
def datetime_to_dbus (dt, variant_level=0):
1030
1017
    """Convert a UTC datetime.datetime() to a D-Bus type."""
1031
1018
    if dt is None:
1032
1019
        return dbus.String("", variant_level = variant_level)
1040
1027
    interface names according to the "alt_interface_names" mapping.
1041
1028
    Usage:
1042
1029
    
1043
 
    @alternate_dbus_interfaces({"org.example.Interface":
1044
 
                                    "net.example.AlternateInterface"})
 
1030
    @alternate_dbus_names({"org.example.Interface":
 
1031
                               "net.example.AlternateInterface"})
1045
1032
    class SampleDBusObject(dbus.service.Object):
1046
1033
        @dbus.service.method("org.example.Interface")
1047
1034
        def SampleDBusMethod():
1346
1333
        return False
1347
1334
    
1348
1335
    def approve(self, value=True):
 
1336
        self.send_changedstate()
1349
1337
        self.approved = value
1350
1338
        gobject.timeout_add(timedelta_to_milliseconds
1351
1339
                            (self.approval_duration),
1352
1340
                            self._reset_approved)
1353
 
        self.send_changedstate()
1354
1341
    
1355
1342
    ## D-Bus methods, signals & properties
1356
1343
    _interface = "se.recompile.Mandos.Client"
1540
1527
    def Timeout_dbus_property(self, value=None):
1541
1528
        if value is None:       # get
1542
1529
            return dbus.UInt64(self.timeout_milliseconds())
1543
 
        old_timeout = self.timeout
1544
1530
        self.timeout = datetime.timedelta(0, 0, 0, value)
1545
 
        # Reschedule disabling
 
1531
        # Reschedule timeout
1546
1532
        if self.enabled:
1547
1533
            now = datetime.datetime.utcnow()
1548
 
            self.expires += self.timeout - old_timeout
1549
 
            if self.expires <= now:
 
1534
            time_to_die = timedelta_to_milliseconds(
 
1535
                (self.last_checked_ok + self.timeout) - now)
 
1536
            if time_to_die <= 0:
1550
1537
                # The timeout has passed
1551
1538
                self.disable()
1552
1539
            else:
 
1540
                self.expires = (now +
 
1541
                                datetime.timedelta(milliseconds =
 
1542
                                                   time_to_die))
1553
1543
                if (getattr(self, "disable_initiator_tag", None)
1554
1544
                    is None):
1555
1545
                    return
1556
1546
                gobject.source_remove(self.disable_initiator_tag)
1557
 
                self.disable_initiator_tag = (
1558
 
                    gobject.timeout_add(
1559
 
                        timedelta_to_milliseconds(self.expires - now),
1560
 
                        self.disable))
 
1547
                self.disable_initiator_tag = (gobject.timeout_add
 
1548
                                              (time_to_die,
 
1549
                                               self.disable))
1561
1550
    
1562
1551
    # ExtendedTimeout - property
1563
1552
    @dbus_service_property(_interface, signature="t",
1751
1740
                    #wait until timeout or approved
1752
1741
                    time = datetime.datetime.now()
1753
1742
                    client.changedstate.acquire()
1754
 
                    client.changedstate.wait(
1755
 
                        float(timedelta_to_milliseconds(delay)
1756
 
                              / 1000))
 
1743
                    (client.changedstate.wait
 
1744
                     (float(client.timedelta_to_milliseconds(delay)
 
1745
                            / 1000)))
1757
1746
                    client.changedstate.release()
1758
1747
                    time2 = datetime.datetime.now()
1759
1748
                    if (time2 - time) >= delay:
1875
1864
    def process_request(self, request, address):
1876
1865
        """Start a new process to process the request."""
1877
1866
        proc = multiprocessing.Process(target = self.sub_process_main,
1878
 
                                       args = (request, address))
 
1867
                                       args = (request,
 
1868
                                               address))
1879
1869
        proc.start()
1880
1870
        return proc
1881
1871
 
1909
1899
        use_ipv6:       Boolean; to use IPv6 or not
1910
1900
    """
1911
1901
    def __init__(self, server_address, RequestHandlerClass,
1912
 
                 interface=None, use_ipv6=True, socketfd=None):
1913
 
        """If socketfd is set, use that file descriptor instead of
1914
 
        creating a new one with socket.socket().
1915
 
        """
 
1902
                 interface=None, use_ipv6=True):
1916
1903
        self.interface = interface
1917
1904
        if use_ipv6:
1918
1905
            self.address_family = socket.AF_INET6
1919
 
        if socketfd is not None:
1920
 
            # Save the file descriptor
1921
 
            self.socketfd = socketfd
1922
 
            # Save the original socket.socket() function
1923
 
            self.socket_socket = socket.socket
1924
 
            # To implement --socket, we monkey patch socket.socket.
1925
 
            # 
1926
 
            # (When socketserver.TCPServer is a new-style class, we
1927
 
            # could make self.socket into a property instead of monkey
1928
 
            # patching socket.socket.)
1929
 
            # 
1930
 
            # Create a one-time-only replacement for socket.socket()
1931
 
            @functools.wraps(socket.socket)
1932
 
            def socket_wrapper(*args, **kwargs):
1933
 
                # Restore original function so subsequent calls are
1934
 
                # not affected.
1935
 
                socket.socket = self.socket_socket
1936
 
                del self.socket_socket
1937
 
                # This time only, return a new socket object from the
1938
 
                # saved file descriptor.
1939
 
                return socket.fromfd(self.socketfd, *args, **kwargs)
1940
 
            # Replace socket.socket() function with wrapper
1941
 
            socket.socket = socket_wrapper
1942
 
        # The socketserver.TCPServer.__init__ will call
1943
 
        # socket.socket(), which might be our replacement,
1944
 
        # socket_wrapper(), if socketfd was set.
1945
1906
        socketserver.TCPServer.__init__(self, server_address,
1946
1907
                                        RequestHandlerClass)
1947
 
    
1948
1908
    def server_bind(self):
1949
1909
        """This overrides the normal server_bind() function
1950
1910
        to bind to an interface if one was specified, and also NOT to
1958
1918
                try:
1959
1919
                    self.socket.setsockopt(socket.SOL_SOCKET,
1960
1920
                                           SO_BINDTODEVICE,
1961
 
                                           str(self.interface + '\0'))
 
1921
                                           str(self.interface
 
1922
                                               + '\0'))
1962
1923
                except socket.error as error:
1963
 
                    if error.errno == errno.EPERM:
1964
 
                        logger.error("No permission to bind to"
1965
 
                                     " interface %s", self.interface)
1966
 
                    elif error.errno == errno.ENOPROTOOPT:
 
1924
                    if error[0] == errno.EPERM:
 
1925
                        logger.error("No permission to"
 
1926
                                     " bind to interface %s",
 
1927
                                     self.interface)
 
1928
                    elif error[0] == errno.ENOPROTOOPT:
1967
1929
                        logger.error("SO_BINDTODEVICE not available;"
1968
1930
                                     " cannot bind to interface %s",
1969
1931
                                     self.interface)
1970
 
                    elif error.errno == errno.ENODEV:
1971
 
                        logger.error("Interface %s does not exist,"
1972
 
                                     " cannot bind", self.interface)
1973
1932
                    else:
1974
1933
                        raise
1975
1934
        # Only bind(2) the socket if we really need to.
2005
1964
    """
2006
1965
    def __init__(self, server_address, RequestHandlerClass,
2007
1966
                 interface=None, use_ipv6=True, clients=None,
2008
 
                 gnutls_priority=None, use_dbus=True, socketfd=None):
 
1967
                 gnutls_priority=None, use_dbus=True):
2009
1968
        self.enabled = False
2010
1969
        self.clients = clients
2011
1970
        if self.clients is None:
2015
1974
        IPv6_TCPServer.__init__(self, server_address,
2016
1975
                                RequestHandlerClass,
2017
1976
                                interface = interface,
2018
 
                                use_ipv6 = use_ipv6,
2019
 
                                socketfd = socketfd)
 
1977
                                use_ipv6 = use_ipv6)
2020
1978
    def server_activate(self):
2021
1979
        if self.enabled:
2022
1980
            return socketserver.TCPServer.server_activate(self)
2035
1993
    
2036
1994
    def handle_ipc(self, source, condition, parent_pipe=None,
2037
1995
                   proc = None, client_object=None):
 
1996
        condition_names = {
 
1997
            gobject.IO_IN: "IN",   # There is data to read.
 
1998
            gobject.IO_OUT: "OUT", # Data can be written (without
 
1999
                                    # blocking).
 
2000
            gobject.IO_PRI: "PRI", # There is urgent data to read.
 
2001
            gobject.IO_ERR: "ERR", # Error condition.
 
2002
            gobject.IO_HUP: "HUP"  # Hung up (the connection has been
 
2003
                                    # broken, usually for pipes and
 
2004
                                    # sockets).
 
2005
            }
 
2006
        conditions_string = ' | '.join(name
 
2007
                                       for cond, name in
 
2008
                                       condition_names.iteritems()
 
2009
                                       if cond & condition)
2038
2010
        # error, or the other end of multiprocessing.Pipe has closed
2039
 
        if condition & (gobject.IO_ERR | gobject.IO_HUP):
 
2011
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
2040
2012
            # Wait for other process to exit
2041
2013
            proc.join()
2042
2014
            return False
2100
2072
        return True
2101
2073
 
2102
2074
 
2103
 
def rfc3339_duration_to_delta(duration):
2104
 
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
2105
 
    
2106
 
    >>> rfc3339_duration_to_delta("P7D")
2107
 
    datetime.timedelta(7)
2108
 
    >>> rfc3339_duration_to_delta("PT60S")
2109
 
    datetime.timedelta(0, 60)
2110
 
    >>> rfc3339_duration_to_delta("PT60M")
2111
 
    datetime.timedelta(0, 3600)
2112
 
    >>> rfc3339_duration_to_delta("PT24H")
2113
 
    datetime.timedelta(1)
2114
 
    >>> rfc3339_duration_to_delta("P1W")
2115
 
    datetime.timedelta(7)
2116
 
    >>> rfc3339_duration_to_delta("PT5M30S")
2117
 
    datetime.timedelta(0, 330)
2118
 
    >>> rfc3339_duration_to_delta("P1DT3M20S")
2119
 
    datetime.timedelta(1, 200)
2120
 
    """
2121
 
    
2122
 
    # Parsing an RFC 3339 duration with regular expressions is not
2123
 
    # possible - there would have to be multiple places for the same
2124
 
    # values, like seconds.  The current code, while more esoteric, is
2125
 
    # cleaner without depending on a parsing library.  If Python had a
2126
 
    # built-in library for parsing we would use it, but we'd like to
2127
 
    # avoid excessive use of external libraries.
2128
 
    
2129
 
    # New type for defining tokens, syntax, and semantics all-in-one
2130
 
    Token = collections.namedtuple("Token",
2131
 
                                   ("regexp", # To match token; if
2132
 
                                              # "value" is not None,
2133
 
                                              # must have a "group"
2134
 
                                              # containing digits
2135
 
                                    "value",  # datetime.timedelta or
2136
 
                                              # None
2137
 
                                    "followers")) # Tokens valid after
2138
 
                                                  # this token
2139
 
    # RFC 3339 "duration" tokens, syntax, and semantics; taken from
2140
 
    # the "duration" ABNF definition in RFC 3339, Appendix A.
2141
 
    token_end = Token(re.compile(r"$"), None, frozenset())
2142
 
    token_second = Token(re.compile(r"(\d+)S"),
2143
 
                         datetime.timedelta(seconds=1),
2144
 
                         frozenset((token_end,)))
2145
 
    token_minute = Token(re.compile(r"(\d+)M"),
2146
 
                         datetime.timedelta(minutes=1),
2147
 
                         frozenset((token_second, token_end)))
2148
 
    token_hour = Token(re.compile(r"(\d+)H"),
2149
 
                       datetime.timedelta(hours=1),
2150
 
                       frozenset((token_minute, token_end)))
2151
 
    token_time = Token(re.compile(r"T"),
2152
 
                       None,
2153
 
                       frozenset((token_hour, token_minute,
2154
 
                                  token_second)))
2155
 
    token_day = Token(re.compile(r"(\d+)D"),
2156
 
                      datetime.timedelta(days=1),
2157
 
                      frozenset((token_time, token_end)))
2158
 
    token_month = Token(re.compile(r"(\d+)M"),
2159
 
                        datetime.timedelta(weeks=4),
2160
 
                        frozenset((token_day, token_end)))
2161
 
    token_year = Token(re.compile(r"(\d+)Y"),
2162
 
                       datetime.timedelta(weeks=52),
2163
 
                       frozenset((token_month, token_end)))
2164
 
    token_week = Token(re.compile(r"(\d+)W"),
2165
 
                       datetime.timedelta(weeks=1),
2166
 
                       frozenset((token_end,)))
2167
 
    token_duration = Token(re.compile(r"P"), None,
2168
 
                           frozenset((token_year, token_month,
2169
 
                                      token_day, token_time,
2170
 
                                      token_week))),
2171
 
    # Define starting values
2172
 
    value = datetime.timedelta() # Value so far
2173
 
    found_token = None
2174
 
    followers = frozenset(token_duration,) # Following valid tokens
2175
 
    s = duration                # String left to parse
2176
 
    # Loop until end token is found
2177
 
    while found_token is not token_end:
2178
 
        # Search for any currently valid tokens
2179
 
        for token in followers:
2180
 
            match = token.regexp.match(s)
2181
 
            if match is not None:
2182
 
                # Token found
2183
 
                if token.value is not None:
2184
 
                    # Value found, parse digits
2185
 
                    factor = int(match.group(1), 10)
2186
 
                    # Add to value so far
2187
 
                    value += factor * token.value
2188
 
                # Strip token from string
2189
 
                s = token.regexp.sub("", s, 1)
2190
 
                # Go to found token
2191
 
                found_token = token
2192
 
                # Set valid next tokens
2193
 
                followers = found_token.followers
2194
 
                break
2195
 
        else:
2196
 
            # No currently valid tokens were found
2197
 
            raise ValueError("Invalid RFC 3339 duration")
2198
 
    # End token found
2199
 
    return value
2200
 
 
2201
 
 
2202
2075
def string_to_delta(interval):
2203
2076
    """Parse a string and return a datetime.timedelta
2204
2077
    
2215
2088
    >>> string_to_delta('5m 30s')
2216
2089
    datetime.timedelta(0, 330)
2217
2090
    """
2218
 
    
2219
 
    try:
2220
 
        return rfc3339_duration_to_delta(interval)
2221
 
    except ValueError:
2222
 
        pass
2223
 
    
2224
2091
    timevalue = datetime.timedelta(0)
2225
2092
    for s in interval.split():
2226
2093
        try:
2308
2175
    parser.add_argument("--no-restore", action="store_false",
2309
2176
                        dest="restore", help="Do not restore stored"
2310
2177
                        " state")
2311
 
    parser.add_argument("--socket", type=int,
2312
 
                        help="Specify a file descriptor to a network"
2313
 
                        " socket to use instead of creating one")
2314
2178
    parser.add_argument("--statedir", metavar="DIR",
2315
2179
                        help="Directory to save/restore state in")
2316
 
    parser.add_argument("--foreground", action="store_true",
2317
 
                        help="Run in foreground")
2318
2180
    
2319
2181
    options = parser.parse_args()
2320
2182
    
2335
2197
                        "use_ipv6": "True",
2336
2198
                        "debuglevel": "",
2337
2199
                        "restore": "True",
2338
 
                        "socket": "",
2339
 
                        "statedir": "/var/lib/mandos",
2340
 
                        "foreground": "False",
 
2200
                        "statedir": "/var/lib/mandos"
2341
2201
                        }
2342
2202
    
2343
2203
    # Parse config file for server-global settings
2348
2208
    # Convert the SafeConfigParser object to a dict
2349
2209
    server_settings = server_config.defaults()
2350
2210
    # Use the appropriate methods on the non-string config options
2351
 
    for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
 
2211
    for option in ("debug", "use_dbus", "use_ipv6"):
2352
2212
        server_settings[option] = server_config.getboolean("DEFAULT",
2353
2213
                                                           option)
2354
2214
    if server_settings["port"]:
2355
2215
        server_settings["port"] = server_config.getint("DEFAULT",
2356
2216
                                                       "port")
2357
 
    if server_settings["socket"]:
2358
 
        server_settings["socket"] = server_config.getint("DEFAULT",
2359
 
                                                         "socket")
2360
 
        # Later, stdin will, and stdout and stderr might, be dup'ed
2361
 
        # over with an opened os.devnull.  But we don't want this to
2362
 
        # happen with a supplied network socket.
2363
 
        if 0 <= server_settings["socket"] <= 2:
2364
 
            server_settings["socket"] = os.dup(server_settings
2365
 
                                               ["socket"])
2366
2217
    del server_config
2367
2218
    
2368
2219
    # Override the settings from the config file with command line
2370
2221
    for option in ("interface", "address", "port", "debug",
2371
2222
                   "priority", "servicename", "configdir",
2372
2223
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2373
 
                   "statedir", "socket", "foreground"):
 
2224
                   "statedir"):
2374
2225
        value = getattr(options, option)
2375
2226
        if value is not None:
2376
2227
            server_settings[option] = value
2379
2230
    for option in server_settings.keys():
2380
2231
        if type(server_settings[option]) is str:
2381
2232
            server_settings[option] = unicode(server_settings[option])
2382
 
    # Debug implies foreground
2383
 
    if server_settings["debug"]:
2384
 
        server_settings["foreground"] = True
2385
2233
    # Now we have our good server settings in "server_settings"
2386
2234
    
2387
2235
    ##################################################################
2393
2241
    use_ipv6 = server_settings["use_ipv6"]
2394
2242
    stored_state_path = os.path.join(server_settings["statedir"],
2395
2243
                                     stored_state_file)
2396
 
    foreground = server_settings["foreground"]
2397
2244
    
2398
2245
    if debug:
2399
2246
        initlogger(debug, logging.DEBUG)
2428
2275
                              use_ipv6=use_ipv6,
2429
2276
                              gnutls_priority=
2430
2277
                              server_settings["priority"],
2431
 
                              use_dbus=use_dbus,
2432
 
                              socketfd=(server_settings["socket"]
2433
 
                                        or None))
2434
 
    if not foreground:
 
2278
                              use_dbus=use_dbus)
 
2279
    if not debug:
2435
2280
        pidfilename = "/var/run/mandos.pid"
2436
 
        pidfile = None
2437
2281
        try:
2438
2282
            pidfile = open(pidfilename, "w")
2439
2283
        except IOError as e:
2454
2298
        os.setgid(gid)
2455
2299
        os.setuid(uid)
2456
2300
    except OSError as error:
2457
 
        if error.errno != errno.EPERM:
 
2301
        if error[0] != errno.EPERM:
2458
2302
            raise error
2459
2303
    
2460
2304
    if debug:
2478
2322
            os.close(null)
2479
2323
    
2480
2324
    # Need to fork before connecting to D-Bus
2481
 
    if not foreground:
 
2325
    if not debug:
2482
2326
        # Close all input and output, do double fork, etc.
2483
2327
        daemon()
2484
2328
    
2485
 
    # multiprocessing will use threads, so before we use gobject we
2486
 
    # need to inform gobject that threads will be used.
2487
2329
    gobject.threads_init()
2488
2330
    
2489
2331
    global main_loop
2617
2459
    if not tcp_server.clients:
2618
2460
        logger.warning("No clients defined")
2619
2461
    
2620
 
    if not foreground:
2621
 
        if pidfile is not None:
2622
 
            try:
2623
 
                with pidfile:
2624
 
                    pid = os.getpid()
2625
 
                    pidfile.write(str(pid) + "\n".encode("utf-8"))
2626
 
            except IOError:
2627
 
                logger.error("Could not write to file %r with PID %d",
2628
 
                             pidfilename, pid)
2629
 
        del pidfile
 
2462
    if not debug:
 
2463
        try:
 
2464
            with pidfile:
 
2465
                pid = os.getpid()
 
2466
                pidfile.write(str(pid) + "\n".encode("utf-8"))
 
2467
            del pidfile
 
2468
        except IOError:
 
2469
            logger.error("Could not write to file %r with PID %d",
 
2470
                         pidfilename, pid)
 
2471
        except NameError:
 
2472
            # "pidfile" was never created
 
2473
            pass
2630
2474
        del pidfilename
 
2475
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2631
2476
    
2632
2477
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2633
2478
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2733
2578
                del client_settings[client.name]["secret"]
2734
2579
        
2735
2580
        try:
2736
 
            with (tempfile.NamedTemporaryFile
2737
 
                  (mode='wb', suffix=".pickle", prefix='clients-',
2738
 
                   dir=os.path.dirname(stored_state_path),
2739
 
                   delete=False)) as stored_state:
 
2581
            tempfd, tempname = tempfile.mkstemp(suffix=".pickle",
 
2582
                                                prefix="clients-",
 
2583
                                                dir=os.path.dirname
 
2584
                                                (stored_state_path))
 
2585
            with os.fdopen(tempfd, "wb") as stored_state:
2740
2586
                pickle.dump((clients, client_settings), stored_state)
2741
 
                tempname=stored_state.name
2742
2587
            os.rename(tempname, stored_state_path)
2743
2588
        except (IOError, OSError) as e:
2744
2589
            if not debug: