/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-13 22:06:57 UTC
  • mto: This revision was merged to the branch mainline in revision 596.
  • Revision ID: teddy@recompile.se-20120613220657-qvq7c7nrndl3t413
* plugins.d/mandos-client.c (get_flags): Don't clobber errno.
  (up_interface): Removed; replaced with "interface_is_up".
  (interface_is_up, interface_is_running,
   lower_privileges_permanently, take_down_interface): New.
  (bring_up_interface): Return "error_t".  Use new functions
                        "interface_is_up", "get_flags", and
                        "interface_is_running".
  (main): Save all interfaces either autodetected or specified with
          --interface in argz vector "interfaces".  Save interfaces to
          take down on exit in argz vector "interfaces_to_take_down".
          Save interface names for DEVICE variable to network hooks as
          argz_vector "interfaces_hooks".  Bug fix: Be privileged
          while stopping network hooks.
* plugins.d/mandos-client.xml (SYNOPSIS): Changed --interface synopsis.
  (DESCRIPTION): Updated to document use of all interfaces.
  (OPTIONS): Updated description of "--interface".
* network-hooks.d/bridge: Parse comma-separated DEVICE environment
                          variable.
* network-hooks.d/openvpn: - '' -
* network-hooks.d/wireless: - '' -

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()
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
                        }
1949
1948
                try:
1950
1949
                    self.socket.setsockopt(socket.SOL_SOCKET,
1951
1950
                                           SO_BINDTODEVICE,
1952
 
                                           str(self.interface + '\0'))
 
1951
                                           str(self.interface
 
1952
                                               + '\0'))
1953
1953
                except socket.error as error:
1954
1954
                    if error.errno == errno.EPERM:
1955
 
                        logger.error("No permission to bind to"
1956
 
                                     " interface %s", self.interface)
 
1955
                        logger.error("No permission to"
 
1956
                                     " bind to interface %s",
 
1957
                                     self.interface)
1957
1958
                    elif error.errno == errno.ENOPROTOOPT:
1958
1959
                        logger.error("SO_BINDTODEVICE not available;"
1959
1960
                                     " cannot bind to interface %s",
1960
1961
                                     self.interface)
1961
1962
                    elif error.errno == errno.ENODEV:
1962
 
                        logger.error("Interface %s does not exist,"
1963
 
                                     " cannot bind", self.interface)
 
1963
                        logger.error("Interface %s does not"
 
1964
                                     " exist, cannot bind",
 
1965
                                     self.interface)
1964
1966
                    else:
1965
1967
                        raise
1966
1968
        # Only bind(2) the socket if we really need to.
2091
2093
        return True
2092
2094
 
2093
2095
 
2094
 
def rfc3339_duration_to_delta(duration):
2095
 
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
2096
 
    
2097
 
    >>> rfc3339_duration_to_delta("P7D")
2098
 
    datetime.timedelta(7)
2099
 
    >>> rfc3339_duration_to_delta("PT60S")
2100
 
    datetime.timedelta(0, 60)
2101
 
    >>> rfc3339_duration_to_delta("PT60M")
2102
 
    datetime.timedelta(0, 3600)
2103
 
    >>> rfc3339_duration_to_delta("PT24H")
2104
 
    datetime.timedelta(1)
2105
 
    >>> rfc3339_duration_to_delta("P1W")
2106
 
    datetime.timedelta(7)
2107
 
    >>> rfc3339_duration_to_delta("PT5M30S")
2108
 
    datetime.timedelta(0, 330)
2109
 
    >>> rfc3339_duration_to_delta("P1DT3M20S")
2110
 
    datetime.timedelta(1, 200)
2111
 
    """
2112
 
    
2113
 
    # Parsing an RFC 3339 duration with regular expressions is not
2114
 
    # possible - there would have to be multiple places for the same
2115
 
    # values, like seconds.  The current code, while more esoteric, is
2116
 
    # cleaner without depending on a parsing library.  If Python had a
2117
 
    # built-in library for parsing we would use it, but we'd like to
2118
 
    # avoid excessive use of external libraries.
2119
 
    
2120
 
    # New type for defining tokens, syntax, and semantics all-in-one
2121
 
    Token = collections.namedtuple("Token",
2122
 
                                   ("regexp", # To match token; if
2123
 
                                              # "value" is not None,
2124
 
                                              # must have a "group"
2125
 
                                              # containing digits
2126
 
                                    "value",  # datetime.timedelta or
2127
 
                                              # None
2128
 
                                    "followers")) # Tokens valid after
2129
 
                                                  # this token
2130
 
    # RFC 3339 "duration" tokens, syntax, and semantics; taken from
2131
 
    # the "duration" ABNF definition in RFC 3339, Appendix A.
2132
 
    token_end = Token(re.compile(r"$"), None, frozenset())
2133
 
    token_second = Token(re.compile(r"(\d+)S"),
2134
 
                         datetime.timedelta(seconds=1),
2135
 
                         frozenset((token_end,)))
2136
 
    token_minute = Token(re.compile(r"(\d+)M"),
2137
 
                         datetime.timedelta(minutes=1),
2138
 
                         frozenset((token_second, token_end)))
2139
 
    token_hour = Token(re.compile(r"(\d+)H"),
2140
 
                       datetime.timedelta(hours=1),
2141
 
                       frozenset((token_minute, token_end)))
2142
 
    token_time = Token(re.compile(r"T"),
2143
 
                       None,
2144
 
                       frozenset((token_hour, token_minute,
2145
 
                                  token_second)))
2146
 
    token_day = Token(re.compile(r"(\d+)D"),
2147
 
                      datetime.timedelta(days=1),
2148
 
                      frozenset((token_time, token_end)))
2149
 
    token_month = Token(re.compile(r"(\d+)M"),
2150
 
                        datetime.timedelta(weeks=4),
2151
 
                        frozenset((token_day, token_end)))
2152
 
    token_year = Token(re.compile(r"(\d+)Y"),
2153
 
                       datetime.timedelta(weeks=52),
2154
 
                       frozenset((token_month, token_end)))
2155
 
    token_week = Token(re.compile(r"(\d+)W"),
2156
 
                       datetime.timedelta(weeks=1),
2157
 
                       frozenset((token_end,)))
2158
 
    token_duration = Token(re.compile(r"P"), None,
2159
 
                           frozenset((token_year, token_month,
2160
 
                                      token_day, token_time,
2161
 
                                      token_week))),
2162
 
    # Define starting values
2163
 
    value = datetime.timedelta() # Value so far
2164
 
    found_token = None
2165
 
    followers = frozenset(token_duration,) # Following valid tokens
2166
 
    s = duration                # String left to parse
2167
 
    # Loop until end token is found
2168
 
    while found_token is not token_end:
2169
 
        # Search for any currently valid tokens
2170
 
        for token in followers:
2171
 
            match = token.regexp.match(s)
2172
 
            if match is not None:
2173
 
                # Token found
2174
 
                if token.value is not None:
2175
 
                    # Value found, parse digits
2176
 
                    factor = int(match.group(1), 10)
2177
 
                    # Add to value so far
2178
 
                    value += factor * token.value
2179
 
                # Strip token from string
2180
 
                s = token.regexp.sub("", s, 1)
2181
 
                # Go to found token
2182
 
                found_token = token
2183
 
                # Set valid next tokens
2184
 
                followers = found_token.followers
2185
 
                break
2186
 
        else:
2187
 
            # No currently valid tokens were found
2188
 
            raise ValueError("Invalid RFC 3339 duration")
2189
 
    # End token found
2190
 
    return value
2191
 
 
2192
 
 
2193
2096
def string_to_delta(interval):
2194
2097
    """Parse a string and return a datetime.timedelta
2195
2098
    
2206
2109
    >>> string_to_delta('5m 30s')
2207
2110
    datetime.timedelta(0, 330)
2208
2111
    """
2209
 
    
2210
 
    try:
2211
 
        return rfc3339_duration_to_delta(interval)
2212
 
    except ValueError:
2213
 
        pass
2214
 
    
2215
2112
    timevalue = datetime.timedelta(0)
2216
2113
    for s in interval.split():
2217
2114
        try:
2304
2201
                        " socket to use instead of creating one")
2305
2202
    parser.add_argument("--statedir", metavar="DIR",
2306
2203
                        help="Directory to save/restore state in")
2307
 
    parser.add_argument("--foreground", action="store_true",
2308
 
                        help="Run in foreground")
2309
2204
    
2310
2205
    options = parser.parse_args()
2311
2206
    
2327
2222
                        "debuglevel": "",
2328
2223
                        "restore": "True",
2329
2224
                        "socket": "",
2330
 
                        "statedir": "/var/lib/mandos",
2331
 
                        "foreground": "False",
 
2225
                        "statedir": "/var/lib/mandos"
2332
2226
                        }
2333
2227
    
2334
2228
    # Parse config file for server-global settings
2339
2233
    # Convert the SafeConfigParser object to a dict
2340
2234
    server_settings = server_config.defaults()
2341
2235
    # Use the appropriate methods on the non-string config options
2342
 
    for option in ("debug", "use_dbus", "use_ipv6", "foreground"):
 
2236
    for option in ("debug", "use_dbus", "use_ipv6"):
2343
2237
        server_settings[option] = server_config.getboolean("DEFAULT",
2344
2238
                                                           option)
2345
2239
    if server_settings["port"]:
2361
2255
    for option in ("interface", "address", "port", "debug",
2362
2256
                   "priority", "servicename", "configdir",
2363
2257
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2364
 
                   "statedir", "socket", "foreground"):
 
2258
                   "statedir", "socket"):
2365
2259
        value = getattr(options, option)
2366
2260
        if value is not None:
2367
2261
            server_settings[option] = value
2370
2264
    for option in server_settings.keys():
2371
2265
        if type(server_settings[option]) is str:
2372
2266
            server_settings[option] = unicode(server_settings[option])
2373
 
    # Debug implies foreground
2374
 
    if server_settings["debug"]:
2375
 
        server_settings["foreground"] = True
2376
2267
    # Now we have our good server settings in "server_settings"
2377
2268
    
2378
2269
    ##################################################################
2384
2275
    use_ipv6 = server_settings["use_ipv6"]
2385
2276
    stored_state_path = os.path.join(server_settings["statedir"],
2386
2277
                                     stored_state_file)
2387
 
    foreground = server_settings["foreground"]
2388
2278
    
2389
2279
    if debug:
2390
2280
        initlogger(debug, logging.DEBUG)
2422
2312
                              use_dbus=use_dbus,
2423
2313
                              socketfd=(server_settings["socket"]
2424
2314
                                        or None))
2425
 
    if not foreground:
 
2315
    if not debug:
2426
2316
        pidfilename = "/var/run/mandos.pid"
2427
 
        pidfile = None
2428
2317
        try:
2429
2318
            pidfile = open(pidfilename, "w")
2430
2319
        except IOError as e:
2469
2358
            os.close(null)
2470
2359
    
2471
2360
    # Need to fork before connecting to D-Bus
2472
 
    if not foreground:
 
2361
    if not debug:
2473
2362
        # Close all input and output, do double fork, etc.
2474
2363
        daemon()
2475
2364
    
2608
2497
    if not tcp_server.clients:
2609
2498
        logger.warning("No clients defined")
2610
2499
    
2611
 
    if not foreground:
2612
 
        if pidfile is not None:
2613
 
            try:
2614
 
                with pidfile:
2615
 
                    pid = os.getpid()
2616
 
                    pidfile.write(str(pid) + "\n".encode("utf-8"))
2617
 
            except IOError:
2618
 
                logger.error("Could not write to file %r with PID %d",
2619
 
                             pidfilename, pid)
2620
 
        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
2621
2512
        del pidfilename
2622
2513
    
2623
2514
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())