/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-20 14:37:17 UTC
  • mfrom: (237.4.31 release)
  • Revision ID: teddy@recompile.se-20120520143717-48406ypmuwgwl32f
Merge from release branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
68
68
import binascii
69
69
import tempfile
70
70
import itertools
71
 
import collections
72
71
 
73
72
import dbus
74
73
import dbus.service
89
88
    except ImportError:
90
89
        SO_BINDTODEVICE = None
91
90
 
92
 
version = "1.6.0"
 
91
version = "1.5.4"
93
92
stored_state_file = "clients.pickle"
94
93
 
95
94
logger = logging.getLogger()
152
151
    def __enter__(self):
153
152
        return self
154
153
    
155
 
    def __exit__(self, exc_type, exc_value, traceback):
 
154
    def __exit__ (self, exc_type, exc_value, traceback):
156
155
        self._cleanup()
157
156
        return False
158
157
    
234
233
               Used to optionally bind to the specified interface.
235
234
    name: string; Example: 'Mandos'
236
235
    type: string; Example: '_mandos._tcp'.
237
 
     See <https://www.iana.org/assignments/service-names-port-numbers>
 
236
                  See <http://www.dns-sd.org/ServiceTypes.html>
238
237
    port: integer; what port to announce
239
238
    TXT: list of strings; TXT record for the service
240
239
    domain: string; Domain to publish on, default to .local if empty.
447
446
                          "fingerprint", "host", "interval",
448
447
                          "last_approval_request", "last_checked_ok",
449
448
                          "last_enabled", "name", "timeout")
450
 
    client_defaults = { "timeout": "PT5M",
451
 
                        "extended_timeout": "PT15M",
452
 
                        "interval": "PT2M",
 
449
    client_defaults = { "timeout": "5m",
 
450
                        "extended_timeout": "15m",
 
451
                        "interval": "2m",
453
452
                        "checker": "fping -q -- %%(host)s",
454
453
                        "host": "",
455
 
                        "approval_delay": "PT0S",
456
 
                        "approval_duration": "PT1S",
 
454
                        "approval_delay": "0s",
 
455
                        "approval_duration": "1s",
457
456
                        "approved_by_default": "True",
458
457
                        "enabled": "True",
459
458
                        }
717
716
            except OSError as error:
718
717
                logger.error("Failed to start subprocess",
719
718
                             exc_info=error)
720
 
                return True
721
719
            self.checker_callback_tag = (gobject.child_watch_add
722
720
                                         (self.checker.pid,
723
721
                                          self.checker_callback,
724
722
                                          data=command))
725
723
            # The checker may have completed before the gobject
726
724
            # 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
 
725
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
736
726
            if pid:
737
727
                gobject.source_remove(self.checker_callback_tag)
738
728
                self.checker_callback(pid, status, command)
1026
1016
        return xmlstring
1027
1017
 
1028
1018
 
1029
 
def datetime_to_dbus(dt, variant_level=0):
 
1019
def datetime_to_dbus (dt, variant_level=0):
1030
1020
    """Convert a UTC datetime.datetime() to a D-Bus type."""
1031
1021
    if dt is None:
1032
1022
        return dbus.String("", variant_level = variant_level)
1040
1030
    interface names according to the "alt_interface_names" mapping.
1041
1031
    Usage:
1042
1032
    
1043
 
    @alternate_dbus_interfaces({"org.example.Interface":
1044
 
                                    "net.example.AlternateInterface"})
 
1033
    @alternate_dbus_names({"org.example.Interface":
 
1034
                               "net.example.AlternateInterface"})
1045
1035
    class SampleDBusObject(dbus.service.Object):
1046
1036
        @dbus.service.method("org.example.Interface")
1047
1037
        def SampleDBusMethod():
1078
1068
                interface_names.add(alt_interface)
1079
1069
                # Is this a D-Bus signal?
1080
1070
                if getattr(attribute, "_dbus_is_signal", False):
1081
 
                    # Extract the original non-method undecorated
1082
 
                    # function by black magic
 
1071
                    # Extract the original non-method function by
 
1072
                    # black magic
1083
1073
                    nonmethod_func = (dict(
1084
1074
                            zip(attribute.func_code.co_freevars,
1085
1075
                                attribute.__closure__))["func"]
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
1924
                    if error.errno == errno.EPERM:
1964
 
                        logger.error("No permission to bind to"
1965
 
                                     " interface %s", self.interface)
 
1925
                        logger.error("No permission to"
 
1926
                                     " bind to interface %s",
 
1927
                                     self.interface)
1966
1928
                    elif error.errno == errno.ENOPROTOOPT:
1967
1929
                        logger.error("SO_BINDTODEVICE not available;"
1968
1930
                                     " cannot bind to interface %s",
1969
1931
                                     self.interface)
1970
1932
                    elif error.errno == errno.ENODEV:
1971
 
                        logger.error("Interface %s does not exist,"
1972
 
                                     " cannot bind", self.interface)
 
1933
                        logger.error("Interface %s does not"
 
1934
                                     " exist, cannot bind",
 
1935
                                     self.interface)
1973
1936
                    else:
1974
1937
                        raise
1975
1938
        # Only bind(2) the socket if we really need to.
2005
1968
    """
2006
1969
    def __init__(self, server_address, RequestHandlerClass,
2007
1970
                 interface=None, use_ipv6=True, clients=None,
2008
 
                 gnutls_priority=None, use_dbus=True, socketfd=None):
 
1971
                 gnutls_priority=None, use_dbus=True):
2009
1972
        self.enabled = False
2010
1973
        self.clients = clients
2011
1974
        if self.clients is None:
2015
1978
        IPv6_TCPServer.__init__(self, server_address,
2016
1979
                                RequestHandlerClass,
2017
1980
                                interface = interface,
2018
 
                                use_ipv6 = use_ipv6,
2019
 
                                socketfd = socketfd)
 
1981
                                use_ipv6 = use_ipv6)
2020
1982
    def server_activate(self):
2021
1983
        if self.enabled:
2022
1984
            return socketserver.TCPServer.server_activate(self)
2100
2062
        return True
2101
2063
 
2102
2064
 
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
2065
def string_to_delta(interval):
2203
2066
    """Parse a string and return a datetime.timedelta
2204
2067
    
2215
2078
    >>> string_to_delta('5m 30s')
2216
2079
    datetime.timedelta(0, 330)
2217
2080
    """
2218
 
    
2219
 
    try:
2220
 
        return rfc3339_duration_to_delta(interval)
2221
 
    except ValueError:
2222
 
        pass
2223
 
    
2224
2081
    timevalue = datetime.timedelta(0)
2225
2082
    for s in interval.split():
2226
2083
        try:
2308
2165
    parser.add_argument("--no-restore", action="store_false",
2309
2166
                        dest="restore", help="Do not restore stored"
2310
2167
                        " 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
2168
    parser.add_argument("--statedir", metavar="DIR",
2315
2169
                        help="Directory to save/restore state in")
2316
 
    parser.add_argument("--foreground", action="store_true",
2317
 
                        help="Run in foreground")
2318
2170
    
2319
2171
    options = parser.parse_args()
2320
2172
    
2335
2187
                        "use_ipv6": "True",
2336
2188
                        "debuglevel": "",
2337
2189
                        "restore": "True",
2338
 
                        "socket": "",
2339
 
                        "statedir": "/var/lib/mandos",
2340
 
                        "foreground": "False",
 
2190
                        "statedir": "/var/lib/mandos"
2341
2191
                        }
2342
2192
    
2343
2193
    # Parse config file for server-global settings
2348
2198
    # Convert the SafeConfigParser object to a dict
2349
2199
    server_settings = server_config.defaults()
2350
2200
    # Use the appropriate methods on the non-string config options
2351
 
    for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
 
2201
    for option in ("debug", "use_dbus", "use_ipv6"):
2352
2202
        server_settings[option] = server_config.getboolean("DEFAULT",
2353
2203
                                                           option)
2354
2204
    if server_settings["port"]:
2355
2205
        server_settings["port"] = server_config.getint("DEFAULT",
2356
2206
                                                       "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
2207
    del server_config
2367
2208
    
2368
2209
    # Override the settings from the config file with command line
2370
2211
    for option in ("interface", "address", "port", "debug",
2371
2212
                   "priority", "servicename", "configdir",
2372
2213
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2373
 
                   "statedir", "socket", "foreground"):
 
2214
                   "statedir"):
2374
2215
        value = getattr(options, option)
2375
2216
        if value is not None:
2376
2217
            server_settings[option] = value
2379
2220
    for option in server_settings.keys():
2380
2221
        if type(server_settings[option]) is str:
2381
2222
            server_settings[option] = unicode(server_settings[option])
2382
 
    # Debug implies foreground
2383
 
    if server_settings["debug"]:
2384
 
        server_settings["foreground"] = True
2385
2223
    # Now we have our good server settings in "server_settings"
2386
2224
    
2387
2225
    ##################################################################
2393
2231
    use_ipv6 = server_settings["use_ipv6"]
2394
2232
    stored_state_path = os.path.join(server_settings["statedir"],
2395
2233
                                     stored_state_file)
2396
 
    foreground = server_settings["foreground"]
2397
2234
    
2398
2235
    if debug:
2399
2236
        initlogger(debug, logging.DEBUG)
2428
2265
                              use_ipv6=use_ipv6,
2429
2266
                              gnutls_priority=
2430
2267
                              server_settings["priority"],
2431
 
                              use_dbus=use_dbus,
2432
 
                              socketfd=(server_settings["socket"]
2433
 
                                        or None))
2434
 
    if not foreground:
 
2268
                              use_dbus=use_dbus)
 
2269
    if not debug:
2435
2270
        pidfilename = "/var/run/mandos.pid"
2436
 
        pidfile = None
2437
2271
        try:
2438
2272
            pidfile = open(pidfilename, "w")
2439
2273
        except IOError as e:
2478
2312
            os.close(null)
2479
2313
    
2480
2314
    # Need to fork before connecting to D-Bus
2481
 
    if not foreground:
 
2315
    if not debug:
2482
2316
        # Close all input and output, do double fork, etc.
2483
2317
        daemon()
2484
2318
    
2485
 
    # multiprocessing will use threads, so before we use gobject we
2486
 
    # need to inform gobject that threads will be used.
2487
2319
    gobject.threads_init()
2488
2320
    
2489
2321
    global main_loop
2617
2449
    if not tcp_server.clients:
2618
2450
        logger.warning("No clients defined")
2619
2451
    
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
 
2452
    if not debug:
 
2453
        try:
 
2454
            with pidfile:
 
2455
                pid = os.getpid()
 
2456
                pidfile.write(str(pid) + "\n".encode("utf-8"))
 
2457
            del pidfile
 
2458
        except IOError:
 
2459
            logger.error("Could not write to file %r with PID %d",
 
2460
                         pidfilename, pid)
 
2461
        except NameError:
 
2462
            # "pidfile" was never created
 
2463
            pass
2630
2464
        del pidfilename
2631
2465
    
2632
2466
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())