/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-26 22:56:38 UTC
  • mfrom: (589.1.1 socket-option)
  • Revision ID: teddy@recompile.se-20120526225638-4hvqyrvmj0036lfn
Merge "--socket" option for server.

This is suggested by the GNU Coding Standards' Table of Long Options,
and will probably also allow socket activation (e.g. by systemd(8)).

Show diffs side-by-side

added added

removed removed

Lines of Context:
88
88
    except ImportError:
89
89
        SO_BINDTODEVICE = None
90
90
 
91
 
version = "1.5.3"
 
91
version = "1.5.4"
92
92
stored_state_file = "clients.pickle"
93
93
 
94
94
logger = logging.getLogger()
151
151
    def __enter__(self):
152
152
        return self
153
153
    
154
 
    def __exit__ (self, exc_type, exc_value, traceback):
 
154
    def __exit__(self, exc_type, exc_value, traceback):
155
155
        self._cleanup()
156
156
        return False
157
157
    
379
379
                                 self.server_state_changed)
380
380
        self.server_state_changed(self.server.GetState())
381
381
 
 
382
 
382
383
class AvahiServiceToSyslog(AvahiService):
383
384
    def rename(self):
384
385
        """Add the new name to the syslog messages"""
389
390
                                .format(self.name)))
390
391
        return ret
391
392
 
 
393
 
392
394
def timedelta_to_milliseconds(td):
393
395
    "Convert a datetime.timedelta() to milliseconds"
394
396
    return ((td.days * 24 * 60 * 60 * 1000)
395
397
            + (td.seconds * 1000)
396
398
            + (td.microseconds // 1000))
397
399
 
 
400
 
398
401
class Client(object):
399
402
    """A representation of a client host served by this server.
400
403
    
439
442
    """
440
443
    
441
444
    runtime_expansions = ("approval_delay", "approval_duration",
442
 
                          "created", "enabled", "fingerprint",
443
 
                          "host", "interval", "last_checked_ok",
 
445
                          "created", "enabled", "expires",
 
446
                          "fingerprint", "host", "interval",
 
447
                          "last_approval_request", "last_checked_ok",
444
448
                          "last_enabled", "name", "timeout")
445
449
    client_defaults = { "timeout": "5m",
446
450
                        "extended_timeout": "15m",
687
691
                                      self.current_checker_command)
688
692
        # Start a new checker if needed
689
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)
690
699
            try:
691
 
                # In case checker_command has exactly one % operator
692
 
                command = self.checker_command % self.host
693
 
            except TypeError:
694
 
                # Escape attributes for the shell
695
 
                escaped_attrs = dict(
696
 
                    (attr,
697
 
                     re.escape(unicode(str(getattr(self, attr, "")),
698
 
                                       errors=
699
 
                                       'replace')))
700
 
                    for attr in
701
 
                    self.runtime_expansions)
702
 
                
703
 
                try:
704
 
                    command = self.checker_command % escaped_attrs
705
 
                except TypeError as error:
706
 
                    logger.error('Could not format string "%s"',
707
 
                                 self.checker_command, exc_info=error)
708
 
                    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
709
705
            self.current_checker_command = command
710
706
            try:
711
707
                logger.info("Starting checker %r for %s",
717
713
                self.checker = subprocess.Popen(command,
718
714
                                                close_fds=True,
719
715
                                                shell=True, cwd="/")
720
 
                self.checker_callback_tag = (gobject.child_watch_add
721
 
                                             (self.checker.pid,
722
 
                                              self.checker_callback,
723
 
                                              data=command))
724
 
                # The checker may have completed before the gobject
725
 
                # watch was added.  Check for this.
726
 
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
727
 
                if pid:
728
 
                    gobject.source_remove(self.checker_callback_tag)
729
 
                    self.checker_callback(pid, status, command)
730
716
            except OSError as error:
731
717
                logger.error("Failed to start subprocess",
732
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)
733
729
        # Re-run this periodically if run by gobject.timeout_add
734
730
        return True
735
731
    
1020
1016
        return xmlstring
1021
1017
 
1022
1018
 
1023
 
def datetime_to_dbus (dt, variant_level=0):
 
1019
def datetime_to_dbus(dt, variant_level=0):
1024
1020
    """Convert a UTC datetime.datetime() to a D-Bus type."""
1025
1021
    if dt is None:
1026
1022
        return dbus.String("", variant_level = variant_level)
1034
1030
    interface names according to the "alt_interface_names" mapping.
1035
1031
    Usage:
1036
1032
    
1037
 
    @alternate_dbus_names({"org.example.Interface":
1038
 
                               "net.example.AlternateInterface"})
 
1033
    @alternate_dbus_interfaces({"org.example.Interface":
 
1034
                                    "net.example.AlternateInterface"})
1039
1035
    class SampleDBusObject(dbus.service.Object):
1040
1036
        @dbus.service.method("org.example.Interface")
1041
1037
        def SampleDBusMethod():
1903
1899
        use_ipv6:       Boolean; to use IPv6 or not
1904
1900
    """
1905
1901
    def __init__(self, server_address, RequestHandlerClass,
1906
 
                 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
        """
1907
1906
        self.interface = interface
1908
1907
        if use_ipv6:
1909
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.
1910
1935
        socketserver.TCPServer.__init__(self, server_address,
1911
1936
                                        RequestHandlerClass)
 
1937
    
1912
1938
    def server_bind(self):
1913
1939
        """This overrides the normal server_bind() function
1914
1940
        to bind to an interface if one was specified, and also NOT to
1925
1951
                                           str(self.interface
1926
1952
                                               + '\0'))
1927
1953
                except socket.error as error:
1928
 
                    if error[0] == errno.EPERM:
 
1954
                    if error.errno == errno.EPERM:
1929
1955
                        logger.error("No permission to"
1930
1956
                                     " bind to interface %s",
1931
1957
                                     self.interface)
1932
 
                    elif error[0] == errno.ENOPROTOOPT:
 
1958
                    elif error.errno == errno.ENOPROTOOPT:
1933
1959
                        logger.error("SO_BINDTODEVICE not available;"
1934
1960
                                     " cannot bind to interface %s",
1935
1961
                                     self.interface)
 
1962
                    elif error.errno == errno.ENODEV:
 
1963
                        logger.error("Interface %s does not"
 
1964
                                     " exist, cannot bind",
 
1965
                                     self.interface)
1936
1966
                    else:
1937
1967
                        raise
1938
1968
        # Only bind(2) the socket if we really need to.
1968
1998
    """
1969
1999
    def __init__(self, server_address, RequestHandlerClass,
1970
2000
                 interface=None, use_ipv6=True, clients=None,
1971
 
                 gnutls_priority=None, use_dbus=True):
 
2001
                 gnutls_priority=None, use_dbus=True, socketfd=None):
1972
2002
        self.enabled = False
1973
2003
        self.clients = clients
1974
2004
        if self.clients is None:
1978
2008
        IPv6_TCPServer.__init__(self, server_address,
1979
2009
                                RequestHandlerClass,
1980
2010
                                interface = interface,
1981
 
                                use_ipv6 = use_ipv6)
 
2011
                                use_ipv6 = use_ipv6,
 
2012
                                socketfd = socketfd)
1982
2013
    def server_activate(self):
1983
2014
        if self.enabled:
1984
2015
            return socketserver.TCPServer.server_activate(self)
2165
2196
    parser.add_argument("--no-restore", action="store_false",
2166
2197
                        dest="restore", help="Do not restore stored"
2167
2198
                        " state")
 
2199
    parser.add_argument("--socket", type=int,
 
2200
                        help="Specify a file descriptor to a network"
 
2201
                        " socket to use instead of creating one")
2168
2202
    parser.add_argument("--statedir", metavar="DIR",
2169
2203
                        help="Directory to save/restore state in")
2170
2204
    
2187
2221
                        "use_ipv6": "True",
2188
2222
                        "debuglevel": "",
2189
2223
                        "restore": "True",
 
2224
                        "socket": "",
2190
2225
                        "statedir": "/var/lib/mandos"
2191
2226
                        }
2192
2227
    
2204
2239
    if server_settings["port"]:
2205
2240
        server_settings["port"] = server_config.getint("DEFAULT",
2206
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"])
2207
2251
    del server_config
2208
2252
    
2209
2253
    # Override the settings from the config file with command line
2211
2255
    for option in ("interface", "address", "port", "debug",
2212
2256
                   "priority", "servicename", "configdir",
2213
2257
                   "use_dbus", "use_ipv6", "debuglevel", "restore",
2214
 
                   "statedir"):
 
2258
                   "statedir", "socket"):
2215
2259
        value = getattr(options, option)
2216
2260
        if value is not None:
2217
2261
            server_settings[option] = value
2265
2309
                              use_ipv6=use_ipv6,
2266
2310
                              gnutls_priority=
2267
2311
                              server_settings["priority"],
2268
 
                              use_dbus=use_dbus)
 
2312
                              use_dbus=use_dbus,
 
2313
                              socketfd=(server_settings["socket"]
 
2314
                                        or None))
2269
2315
    if not debug:
2270
2316
        pidfilename = "/var/run/mandos.pid"
2271
2317
        try:
2288
2334
        os.setgid(gid)
2289
2335
        os.setuid(uid)
2290
2336
    except OSError as error:
2291
 
        if error[0] != errno.EPERM:
 
2337
        if error.errno != errno.EPERM:
2292
2338
            raise error
2293
2339
    
2294
2340
    if debug:
2316
2362
        # Close all input and output, do double fork, etc.
2317
2363
        daemon()
2318
2364
    
 
2365
    # multiprocessing will use threads, so before we use gobject we
 
2366
    # need to inform gobject that threads will be used.
2319
2367
    gobject.threads_init()
2320
2368
    
2321
2369
    global main_loop
2462
2510
            # "pidfile" was never created
2463
2511
            pass
2464
2512
        del pidfilename
2465
 
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2466
2513
    
2467
2514
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2468
2515
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())