/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-06-01 18:39:03 UTC
  • mfrom: (237.8.1 release)
  • mto: (237.4.33 release)
  • mto: This revision was merged to the branch mainline in revision 593.
  • Revision ID: teddy@recompile.se-20120601183903-hydodgfmlau5d109
Tags: version-1.5.5-1
* Makefile (version): Changed to "1.5.5".
* NEWS (Version 1.5.5): New entry.
* debian/changelog (1.5.5-1): - '' -

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.5"
93
92
stored_state_file = "clients.pickle"
94
93
 
95
94
logger = logging.getLogger()
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)
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"]
1958
1948
                try:
1959
1949
                    self.socket.setsockopt(socket.SOL_SOCKET,
1960
1950
                                           SO_BINDTODEVICE,
1961
 
                                           str(self.interface + '\0'))
 
1951
                                           str(self.interface
 
1952
                                               + '\0'))
1962
1953
                except socket.error as error:
1963
1954
                    if error.errno == errno.EPERM:
1964
 
                        logger.error("No permission to bind to"
1965
 
                                     " interface %s", self.interface)
 
1955
                        logger.error("No permission to"
 
1956
                                     " bind to interface %s",
 
1957
                                     self.interface)
1966
1958
                    elif error.errno == errno.ENOPROTOOPT:
1967
1959
                        logger.error("SO_BINDTODEVICE not available;"
1968
1960
                                     " cannot bind to interface %s",
1969
1961
                                     self.interface)
1970
1962
                    elif error.errno == errno.ENODEV:
1971
 
                        logger.error("Interface %s does not exist,"
1972
 
                                     " cannot bind", self.interface)
 
1963
                        logger.error("Interface %s does not"
 
1964
                                     " exist, cannot bind",
 
1965
                                     self.interface)
1973
1966
                    else:
1974
1967
                        raise
1975
1968
        # Only bind(2) the socket if we really need to.
2100
2093
        return True
2101
2094
 
2102
2095
 
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
2096
def string_to_delta(interval):
2203
2097
    """Parse a string and return a datetime.timedelta
2204
2098
    
2215
2109
    >>> string_to_delta('5m 30s')
2216
2110
    datetime.timedelta(0, 330)
2217
2111
    """
2218
 
    
2219
 
    try:
2220
 
        return rfc3339_duration_to_delta(interval)
2221
 
    except ValueError:
2222
 
        pass
2223
 
    
2224
2112
    timevalue = datetime.timedelta(0)
2225
2113
    for s in interval.split():
2226
2114
        try:
2313
2201
                        " socket to use instead of creating one")
2314
2202
    parser.add_argument("--statedir", metavar="DIR",
2315
2203
                        help="Directory to save/restore state in")
2316
 
    parser.add_argument("--foreground", action="store_true",
2317
 
                        help="Run in foreground")
2318
2204
    
2319
2205
    options = parser.parse_args()
2320
2206
    
2336
2222
                        "debuglevel": "",
2337
2223
                        "restore": "True",
2338
2224
                        "socket": "",
2339
 
                        "statedir": "/var/lib/mandos",
2340
 
                        "foreground": "False",
 
2225
                        "statedir": "/var/lib/mandos"
2341
2226
                        }
2342
2227
    
2343
2228
    # Parse config file for server-global settings
2348
2233
    # Convert the SafeConfigParser object to a dict
2349
2234
    server_settings = server_config.defaults()
2350
2235
    # Use the appropriate methods on the non-string config options
2351
 
    for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
 
2236
    for option in ("debug", "use_dbus", "use_ipv6"):
2352
2237
        server_settings[option] = server_config.getboolean("DEFAULT",
2353
2238
                                                           option)
2354
2239
    if server_settings["port"]:
2370
2255
    for option in ("interface", "address", "port", "debug",
2371
2256
                   "priority", "servicename", "configdir",
2372
2257
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2373
 
                   "statedir", "socket", "foreground"):
 
2258
                   "statedir", "socket"):
2374
2259
        value = getattr(options, option)
2375
2260
        if value is not None:
2376
2261
            server_settings[option] = value
2379
2264
    for option in server_settings.keys():
2380
2265
        if type(server_settings[option]) is str:
2381
2266
            server_settings[option] = unicode(server_settings[option])
2382
 
    # Debug implies foreground
2383
 
    if server_settings["debug"]:
2384
 
        server_settings["foreground"] = True
2385
2267
    # Now we have our good server settings in "server_settings"
2386
2268
    
2387
2269
    ##################################################################
2393
2275
    use_ipv6 = server_settings["use_ipv6"]
2394
2276
    stored_state_path = os.path.join(server_settings["statedir"],
2395
2277
                                     stored_state_file)
2396
 
    foreground = server_settings["foreground"]
2397
2278
    
2398
2279
    if debug:
2399
2280
        initlogger(debug, logging.DEBUG)
2431
2312
                              use_dbus=use_dbus,
2432
2313
                              socketfd=(server_settings["socket"]
2433
2314
                                        or None))
2434
 
    if not foreground:
 
2315
    if not debug:
2435
2316
        pidfilename = "/var/run/mandos.pid"
2436
 
        pidfile = None
2437
2317
        try:
2438
2318
            pidfile = open(pidfilename, "w")
2439
2319
        except IOError as e:
2478
2358
            os.close(null)
2479
2359
    
2480
2360
    # Need to fork before connecting to D-Bus
2481
 
    if not foreground:
 
2361
    if not debug:
2482
2362
        # Close all input and output, do double fork, etc.
2483
2363
        daemon()
2484
2364
    
2617
2497
    if not tcp_server.clients:
2618
2498
        logger.warning("No clients defined")
2619
2499
    
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
 
2500
    if not debug:
 
2501
        try:
 
2502
            with pidfile:
 
2503
                pid = os.getpid()
 
2504
                pidfile.write(str(pid) + "\n".encode("utf-8"))
 
2505
            del pidfile
 
2506
        except IOError:
 
2507
            logger.error("Could not write to file %r with PID %d",
 
2508
                         pidfilename, pid)
 
2509
        except NameError:
 
2510
            # "pidfile" was never created
 
2511
            pass
2630
2512
        del pidfilename
2631
2513
    
2632
2514
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())