9
41
import gnutls.crypto
 
10
42
import gnutls.connection
 
11
43
import gnutls.errors
 
 
44
import gnutls.library.functions
 
 
45
import gnutls.library.constants
 
 
46
import gnutls.library.types
 
12
47
import ConfigParser
 
 
57
import logging.handlers
 
 
63
from dbus.mainloop.glib import DBusGMainLoop
 
 
68
logger = logging.Logger('mandos')
 
 
69
syslogger = logging.handlers.SysLogHandler\
 
 
70
            (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
 
 
72
syslogger.setFormatter(logging.Formatter\
 
 
73
                        ('Mandos: %(levelname)s: %(message)s'))
 
 
74
logger.addHandler(syslogger)
 
 
76
console = logging.StreamHandler()
 
 
77
console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
 
 
79
logger.addHandler(console)
 
 
81
class AvahiError(Exception):
 
 
82
    def __init__(self, value):
 
 
85
        return repr(self.value)
 
 
87
class AvahiServiceError(AvahiError):
 
 
90
class AvahiGroupError(AvahiError):
 
 
94
class AvahiService(object):
 
 
95
    """An Avahi (Zeroconf) service.
 
 
97
    interface: integer; avahi.IF_UNSPEC or an interface index.
 
 
98
               Used to optionally bind to the specified interface.
 
 
99
    name: string; Example: 'Mandos'
 
 
100
    type: string; Example: '_mandos._tcp'.
 
 
101
                  See <http://www.dns-sd.org/ServiceTypes.html>
 
 
102
    port: integer; what port to announce
 
 
103
    TXT: list of strings; TXT record for the service
 
 
104
    domain: string; Domain to publish on, default to .local if empty.
 
 
105
    host: string; Host to publish records for, default is localhost
 
 
106
    max_renames: integer; maximum number of renames
 
 
107
    rename_count: integer; counter so we only rename after collisions
 
 
108
                  a sensible number of times
 
 
110
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
 
 
111
                 type = None, port = None, TXT = None, domain = "",
 
 
112
                 host = "", max_renames = 32768):
 
 
113
        self.interface = interface
 
 
123
        self.rename_count = 0
 
 
124
        self.max_renames = max_renames
 
 
126
        """Derived from the Avahi example code"""
 
 
127
        if self.rename_count >= self.max_renames:
 
 
128
            logger.critical(u"No suitable Zeroconf service name found"
 
 
129
                            u" after %i retries, exiting.",
 
 
131
            raise AvahiServiceError("Too many renames")
 
 
132
        self.name = server.GetAlternativeServiceName(self.name)
 
 
133
        logger.info(u"Changing Zeroconf service name to %r ...",
 
 
135
        syslogger.setFormatter(logging.Formatter\
 
 
136
                               ('Mandos (%s): %%(levelname)s:'
 
 
137
                               ' %%(message)s' % self.name))
 
 
140
        self.rename_count += 1
 
 
142
        """Derived from the Avahi example code"""
 
 
143
        if group is not None:
 
 
146
        """Derived from the Avahi example code"""
 
 
149
            group = dbus.Interface\
 
 
150
                    (bus.get_object(avahi.DBUS_NAME,
 
 
151
                                    server.EntryGroupNew()),
 
 
152
                     avahi.DBUS_INTERFACE_ENTRY_GROUP)
 
 
153
            group.connect_to_signal('StateChanged',
 
 
154
                                    entry_group_state_changed)
 
 
155
        logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
 
 
156
                     service.name, service.type)
 
 
158
                self.interface,         # interface
 
 
159
                avahi.PROTO_INET6,      # protocol
 
 
160
                dbus.UInt32(0),         # flags
 
 
161
                self.name, self.type,
 
 
162
                self.domain, self.host,
 
 
163
                dbus.UInt16(self.port),
 
 
164
                avahi.string_array_to_txt_array(self.TXT))
 
 
167
# From the Avahi example code:
 
 
168
group = None                            # our entry group
 
 
169
# End of Avahi example code
 
14
172
class Client(object):
 
15
 
    def __init__(self, name=None, dn=None, password=None,
 
16
 
                 passfile=None, fqdn=None, timeout=None,
 
 
173
    """A representation of a client host served by this server.
 
 
175
    name:      string; from the config file, used in log messages
 
 
176
    fingerprint: string (40 or 32 hexadecimal digits); used to
 
 
177
                 uniquely identify the client
 
 
178
    secret:    bytestring; sent verbatim (over TLS) to client
 
 
179
    host:      string; available for use by the checker command
 
 
180
    created:   datetime.datetime(); object creation, not client host
 
 
181
    last_checked_ok: datetime.datetime() or None if not yet checked OK
 
 
182
    timeout:   datetime.timedelta(); How long from last_checked_ok
 
 
183
                                     until this client is invalid
 
 
184
    interval:  datetime.timedelta(); How often to start a new checker
 
 
185
    stop_hook: If set, called by stop() as stop_hook(self)
 
 
186
    checker:   subprocess.Popen(); a running checker process used
 
 
187
                                   to see if the client lives.
 
 
188
                                   'None' if no process is running.
 
 
189
    checker_initiator_tag: a gobject event source tag, or None
 
 
190
    stop_initiator_tag:    - '' -
 
 
191
    checker_callback_tag:  - '' -
 
 
192
    checker_command: string; External command which is run to check if
 
 
193
                     client lives.  %() expansions are done at
 
 
194
                     runtime with vars(self) as dict, so that for
 
 
195
                     instance %(name)s can be used in the command.
 
 
197
    _timeout: Real variable for 'timeout'
 
 
198
    _interval: Real variable for 'interval'
 
 
199
    _timeout_milliseconds: Used when calling gobject.timeout_add()
 
 
200
    _interval_milliseconds: - '' -
 
 
202
    def _set_timeout(self, timeout):
 
 
203
        "Setter function for 'timeout' attribute"
 
 
204
        self._timeout = timeout
 
 
205
        self._timeout_milliseconds = ((self.timeout.days
 
 
206
                                       * 24 * 60 * 60 * 1000)
 
 
207
                                      + (self.timeout.seconds * 1000)
 
 
208
                                      + (self.timeout.microseconds
 
 
210
    timeout = property(lambda self: self._timeout,
 
 
213
    def _set_interval(self, interval):
 
 
214
        "Setter function for 'interval' attribute"
 
 
215
        self._interval = interval
 
 
216
        self._interval_milliseconds = ((self.interval.days
 
 
217
                                        * 24 * 60 * 60 * 1000)
 
 
218
                                       + (self.interval.seconds
 
 
220
                                       + (self.interval.microseconds
 
 
222
    interval = property(lambda self: self._interval,
 
 
225
    def __init__(self, name = None, stop_hook=None, config={}):
 
 
226
        """Note: the 'checker' key in 'config' sets the
 
 
227
        'checker_command' attribute and *not* the 'checker'
 
21
 
            self.password = password
 
23
 
            self.password = open(passfile).readall()
 
25
 
            print "No Password or Passfile in client config file"
 
26
 
            # raise RuntimeError XXX
 
27
 
            self.password = "gazonk"
 
32
 
            timeout = self.server.options.timeout
 
33
 
        self.timeout = timeout
 
35
 
            interval = self.server.options.interval
 
36
 
        self.interval = interval
 
38
 
def server_bind(self):
 
39
 
    if self.options.interface:
 
40
 
        if not hasattr(socket, "SO_BINDTODEVICE"):
 
41
 
            # From /usr/include/asm-i486/socket.h
 
42
 
            socket.SO_BINDTODEVICE = 25
 
 
230
        logger.debug(u"Creating client %r", self.name)
 
 
231
        # Uppercase and remove spaces from fingerprint for later
 
 
232
        # comparison purposes with return value from the fingerprint()
 
 
234
        self.fingerprint = config["fingerprint"].upper()\
 
 
236
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
 
 
237
        if "secret" in config:
 
 
238
            self.secret = config["secret"].decode(u"base64")
 
 
239
        elif "secfile" in config:
 
 
240
            sf = open(config["secfile"])
 
 
241
            self.secret = sf.read()
 
 
244
            raise TypeError(u"No secret or secfile for client %s"
 
 
246
        self.host = config.get("host", "")
 
 
247
        self.created = datetime.datetime.now()
 
 
248
        self.last_checked_ok = None
 
 
249
        self.timeout = string_to_delta(config["timeout"])
 
 
250
        self.interval = string_to_delta(config["interval"])
 
 
251
        self.stop_hook = stop_hook
 
 
253
        self.checker_initiator_tag = None
 
 
254
        self.stop_initiator_tag = None
 
 
255
        self.checker_callback_tag = None
 
 
256
        self.check_command = config["checker"]
 
 
258
        """Start this client's checker and timeout hooks"""
 
 
259
        # Schedule a new checker to be started an 'interval' from now,
 
 
260
        # and every interval from then on.
 
 
261
        self.checker_initiator_tag = gobject.timeout_add\
 
 
262
                                     (self._interval_milliseconds,
 
 
264
        # Also start a new checker *right now*.
 
 
266
        # Schedule a stop() when 'timeout' has passed
 
 
267
        self.stop_initiator_tag = gobject.timeout_add\
 
 
268
                                  (self._timeout_milliseconds,
 
 
272
        The possibility that a client might be restarted is left open,
 
 
273
        but not currently used."""
 
 
274
        # If this client doesn't have a secret, it is already stopped.
 
 
275
        if hasattr(self, "secret") and self.secret:
 
 
276
            logger.info(u"Stopping client %s", self.name)
 
 
280
        if getattr(self, "stop_initiator_tag", False):
 
 
281
            gobject.source_remove(self.stop_initiator_tag)
 
 
282
            self.stop_initiator_tag = None
 
 
283
        if getattr(self, "checker_initiator_tag", False):
 
 
284
            gobject.source_remove(self.checker_initiator_tag)
 
 
285
            self.checker_initiator_tag = None
 
 
289
        # Do not run this again if called by a gobject.timeout_add
 
 
292
        self.stop_hook = None
 
 
294
    def checker_callback(self, pid, condition):
 
 
295
        """The checker has completed, so take appropriate actions."""
 
 
296
        now = datetime.datetime.now()
 
 
297
        self.checker_callback_tag = None
 
 
299
        if os.WIFEXITED(condition) \
 
 
300
               and (os.WEXITSTATUS(condition) == 0):
 
 
301
            logger.info(u"Checker for %(name)s succeeded",
 
 
303
            self.last_checked_ok = now
 
 
304
            gobject.source_remove(self.stop_initiator_tag)
 
 
305
            self.stop_initiator_tag = gobject.timeout_add\
 
 
306
                                      (self._timeout_milliseconds,
 
 
308
        elif not os.WIFEXITED(condition):
 
 
309
            logger.warning(u"Checker for %(name)s crashed?",
 
 
312
            logger.info(u"Checker for %(name)s failed",
 
 
314
    def start_checker(self):
 
 
315
        """Start a new checker subprocess if one is not running.
 
 
316
        If a checker already exists, leave it running and do
 
 
318
        # The reason for not killing a running checker is that if we
 
 
319
        # did that, then if a checker (for some reason) started
 
 
320
        # running slowly and taking more than 'interval' time, the
 
 
321
        # client would inevitably timeout, since no checker would get
 
 
322
        # a chance to run to completion.  If we instead leave running
 
 
323
        # checkers alone, the checker would have to take more time
 
 
324
        # than 'timeout' for the client to be declared invalid, which
 
 
325
        # is as it should be.
 
 
326
        if self.checker is None:
 
 
328
                # In case check_command has exactly one % operator
 
 
329
                command = self.check_command % self.host
 
 
331
                # Escape attributes for the shell
 
 
332
                escaped_attrs = dict((key, re.escape(str(val)))
 
 
334
                                     vars(self).iteritems())
 
 
336
                    command = self.check_command % escaped_attrs
 
 
337
                except TypeError, error:
 
 
338
                    logger.error(u'Could not format string "%s":'
 
 
339
                                 u' %s', self.check_command, error)
 
 
340
                    return True # Try again later
 
 
342
                logger.info(u"Starting checker %r for %s",
 
 
344
                # We don't need to redirect stdout and stderr, since
 
 
345
                # in normal mode, that is already done by daemon(),
 
 
346
                # and in debug mode we don't want to.  (Stdin is
 
 
347
                # always replaced by /dev/null.)
 
 
348
                self.checker = subprocess.Popen(command,
 
 
351
                self.checker_callback_tag = gobject.child_watch_add\
 
 
353
                                             self.checker_callback)
 
 
354
            except OSError, error:
 
 
355
                logger.error(u"Failed to start subprocess: %s",
 
 
357
        # Re-run this periodically if run by gobject.timeout_add
 
 
359
    def stop_checker(self):
 
 
360
        """Force the checker process, if any, to stop."""
 
 
361
        if self.checker_callback_tag:
 
 
362
            gobject.source_remove(self.checker_callback_tag)
 
 
363
            self.checker_callback_tag = None
 
 
364
        if getattr(self, "checker", None) is None:
 
 
366
        logger.debug(u"Stopping checker for %(name)s", vars(self))
 
44
 
            self.socket.setsockopt(socket.SOL_SOCKET,
 
45
 
                                   socket.SO_BINDTODEVICE,
 
46
 
                                   self.options.interface)
 
47
 
        except socket.error, error:
 
48
 
            if error[0] == errno.EPERM:
 
49
 
                print "Warning: Denied permission to bind to interface", \
 
50
 
                      self.options.interface
 
53
 
    return super(type(self), self).server_bind()
 
56
 
def init_with_options(self, *args, **kwargs):
 
57
 
    if "options" in kwargs:
 
58
 
        self.options = kwargs["options"]
 
60
 
    if "clients" in kwargs:
 
61
 
        self.clients = kwargs["clients"]
 
63
 
    if "credentials" in kwargs:
 
64
 
        self.credentials = kwargs["credentials"]
 
65
 
        del kwargs["credentials"]
 
66
 
    return super(type(self), self).__init__(*args, **kwargs)
 
69
 
class udp_handler(SocketServer.DatagramRequestHandler, object):
 
71
 
        self.wfile.write("Polo")
 
72
 
        print "UDP request answered"
 
75
 
class IPv6_UDPServer(SocketServer.UDPServer, object):
 
76
 
    __init__ = init_with_options
 
77
 
    address_family = socket.AF_INET6
 
78
 
    allow_reuse_address = True
 
79
 
    server_bind = server_bind
 
80
 
    def verify_request(self, request, client_address):
 
81
 
        print "UDP request came"
 
82
 
        return request[0] == "Marco"
 
 
368
            os.kill(self.checker.pid, signal.SIGTERM)
 
 
370
            #if self.checker.poll() is None:
 
 
371
            #    os.kill(self.checker.pid, signal.SIGKILL)
 
 
372
        except OSError, error:
 
 
373
            if error.errno != errno.ESRCH: # No such process
 
 
376
    def still_valid(self):
 
 
377
        """Has the timeout not yet passed for this client?"""
 
 
378
        now = datetime.datetime.now()
 
 
379
        if self.last_checked_ok is None:
 
 
380
            return now < (self.created + self.timeout)
 
 
382
            return now < (self.last_checked_ok + self.timeout)
 
 
385
def peer_certificate(session):
 
 
386
    "Return the peer's OpenPGP certificate as a bytestring"
 
 
387
    # If not an OpenPGP certificate...
 
 
388
    if gnutls.library.functions.gnutls_certificate_type_get\
 
 
389
            (session._c_object) \
 
 
390
           != gnutls.library.constants.GNUTLS_CRT_OPENPGP:
 
 
391
        # ...do the normal thing
 
 
392
        return session.peer_certificate
 
 
393
    list_size = ctypes.c_uint()
 
 
394
    cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
 
 
395
        (session._c_object, ctypes.byref(list_size))
 
 
396
    if list_size.value == 0:
 
 
399
    return ctypes.string_at(cert.data, cert.size)
 
 
402
def fingerprint(openpgp):
 
 
403
    "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
 
 
404
    # New GnuTLS "datum" with the OpenPGP public key
 
 
405
    datum = gnutls.library.types.gnutls_datum_t\
 
 
406
        (ctypes.cast(ctypes.c_char_p(openpgp),
 
 
407
                     ctypes.POINTER(ctypes.c_ubyte)),
 
 
408
         ctypes.c_uint(len(openpgp)))
 
 
409
    # New empty GnuTLS certificate
 
 
410
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
 
 
411
    gnutls.library.functions.gnutls_openpgp_crt_init\
 
 
413
    # Import the OpenPGP public key into the certificate
 
 
414
    gnutls.library.functions.gnutls_openpgp_crt_import\
 
 
415
                    (crt, ctypes.byref(datum),
 
 
416
                     gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
 
 
417
    # Verify the self signature in the key
 
 
418
    crtverify = ctypes.c_uint();
 
 
419
    gnutls.library.functions.gnutls_openpgp_crt_verify_self\
 
 
420
        (crt, 0, ctypes.byref(crtverify))
 
 
421
    if crtverify.value != 0:
 
 
422
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
 
 
423
        raise gnutls.errors.CertificateSecurityError("Verify failed")
 
 
424
    # New buffer for the fingerprint
 
 
425
    buffer = ctypes.create_string_buffer(20)
 
 
426
    buffer_length = ctypes.c_size_t()
 
 
427
    # Get the fingerprint from the certificate into the buffer
 
 
428
    gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
 
 
429
        (crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
 
 
430
    # Deinit the certificate
 
 
431
    gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
 
 
432
    # Convert the buffer to a Python bytestring
 
 
433
    fpr = ctypes.string_at(buffer, buffer_length.value)
 
 
434
    # Convert the bytestring to hexadecimal notation
 
 
435
    hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
 
85
439
class tcp_handler(SocketServer.BaseRequestHandler, object):
 
 
440
    """A TCP request handler class.
 
 
441
    Instantiated by IPv6_TCPServer for each request to handle it.
 
 
442
    Note: This will run in its own forked process."""
 
87
 
        print "TCP request came"
 
88
 
        print "Request:", self.request
 
89
 
        print "Client Address:", self.client_address
 
90
 
        print "Server:", self.server
 
91
 
        session = gnutls.connection.ServerSession(self.request,
 
92
 
                                                  self.server.credentials)
 
94
 
        if session.peer_certificate:
 
95
 
            print "DN:", session.peer_certificate.subject
 
98
 
        except gnutls.errors.CertificateError, error:
 
99
 
            print "Verify failed", error
 
103
 
            session.send(dict((client.dn, client.password)
 
104
 
                              for client in self.server.clients)
 
105
 
                         [session.peer_certificate.subject])
 
107
 
            session.send("gazonk")
 
 
445
        logger.info(u"TCP connection from: %s",
 
 
446
                     unicode(self.client_address))
 
 
447
        session = gnutls.connection.ClientSession\
 
 
448
                  (self.request, gnutls.connection.X509Credentials())
 
 
450
        line = self.request.makefile().readline()
 
 
451
        logger.debug(u"Protocol version: %r", line)
 
 
453
            if int(line.strip().split()[0]) > 1:
 
 
455
        except (ValueError, IndexError, RuntimeError), error:
 
 
456
            logger.error(u"Unknown protocol version: %s", error)
 
 
459
        # Note: gnutls.connection.X509Credentials is really a generic
 
 
460
        # GnuTLS certificate credentials object so long as no X.509
 
 
461
        # keys are added to it.  Therefore, we can use it here despite
 
 
462
        # using OpenPGP certificates.
 
 
464
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
 
 
465
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
 
467
        priority = "NORMAL"             # Fallback default, since this
 
 
469
        if self.server.settings["priority"]:
 
 
470
            priority = self.server.settings["priority"]
 
 
471
        gnutls.library.functions.gnutls_priority_set_direct\
 
 
472
            (session._c_object, priority, None);
 
 
476
        except gnutls.errors.GNUTLSError, error:
 
 
477
            logger.warning(u"Handshake failed: %s", error)
 
 
478
            # Do not run session.bye() here: the session is not
 
 
479
            # established.  Just abandon the request.
 
 
482
            fpr = fingerprint(peer_certificate(session))
 
 
483
        except (TypeError, gnutls.errors.GNUTLSError), error:
 
 
484
            logger.warning(u"Bad certificate: %s", error)
 
 
487
        logger.debug(u"Fingerprint: %s", fpr)
 
 
489
        for c in self.server.clients:
 
 
490
            if c.fingerprint == fpr:
 
 
494
            logger.warning(u"Client not found for fingerprint: %s",
 
 
498
        # Have to check if client.still_valid(), since it is possible
 
 
499
        # that the client timed out while establishing the GnuTLS
 
 
501
        if not client.still_valid():
 
 
502
            logger.warning(u"Client %(name)s is invalid",
 
 
507
        while sent_size < len(client.secret):
 
 
508
            sent = session.send(client.secret[sent_size:])
 
 
509
            logger.debug(u"Sent: %d, remaining: %d",
 
 
510
                         sent, len(client.secret)
 
 
511
                         - (sent_size + sent))
 
111
516
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
 
112
 
    __init__ = init_with_options
 
 
517
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
 
 
519
        settings:       Server settings
 
 
520
        clients:        Set() of Client objects
 
 
521
        enabled:        Boolean; whether this server is activated yet
 
113
523
    address_family = socket.AF_INET6
 
114
 
    allow_reuse_address = True
 
115
 
    request_queue_size = 1024
 
116
 
    server_bind = server_bind
 
 
524
    def __init__(self, *args, **kwargs):
 
 
525
        if "settings" in kwargs:
 
 
526
            self.settings = kwargs["settings"]
 
 
527
            del kwargs["settings"]
 
 
528
        if "clients" in kwargs:
 
 
529
            self.clients = kwargs["clients"]
 
 
530
            del kwargs["clients"]
 
 
532
        return super(type(self), self).__init__(*args, **kwargs)
 
 
533
    def server_bind(self):
 
 
534
        """This overrides the normal server_bind() function
 
 
535
        to bind to an interface if one was specified, and also NOT to
 
 
536
        bind to an address or port if they were not specified."""
 
 
537
        if self.settings["interface"]:
 
 
538
            # 25 is from /usr/include/asm-i486/socket.h
 
 
539
            SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
 
 
541
                self.socket.setsockopt(socket.SOL_SOCKET,
 
 
543
                                       self.settings["interface"])
 
 
544
            except socket.error, error:
 
 
545
                if error[0] == errno.EPERM:
 
 
546
                    logger.error(u"No permission to"
 
 
547
                                 u" bind to interface %s",
 
 
548
                                 self.settings["interface"])
 
 
551
        # Only bind(2) the socket if we really need to.
 
 
552
        if self.server_address[0] or self.server_address[1]:
 
 
553
            if not self.server_address[0]:
 
 
555
                self.server_address = (in6addr_any,
 
 
556
                                       self.server_address[1])
 
 
557
            elif not self.server_address[1]:
 
 
558
                self.server_address = (self.server_address[0],
 
 
560
#                 if self.settings["interface"]:
 
 
561
#                     self.server_address = (self.server_address[0],
 
 
567
            return super(type(self), self).server_bind()
 
 
568
    def server_activate(self):
 
 
570
            return super(type(self), self).server_activate()
 
 
575
def string_to_delta(interval):
 
 
576
    """Parse a string and return a datetime.timedelta
 
 
578
    >>> string_to_delta('7d')
 
 
579
    datetime.timedelta(7)
 
 
580
    >>> string_to_delta('60s')
 
 
581
    datetime.timedelta(0, 60)
 
 
582
    >>> string_to_delta('60m')
 
 
583
    datetime.timedelta(0, 3600)
 
 
584
    >>> string_to_delta('24h')
 
 
585
    datetime.timedelta(1)
 
 
586
    >>> string_to_delta(u'1w')
 
 
587
    datetime.timedelta(7)
 
 
588
    >>> string_to_delta('5m 30s')
 
 
589
    datetime.timedelta(0, 330)
 
 
591
    timevalue = datetime.timedelta(0)
 
 
592
    for s in interval.split():
 
 
594
            suffix=unicode(s[-1])
 
 
597
                delta = datetime.timedelta(value)
 
 
599
                delta = datetime.timedelta(0, value)
 
 
601
                delta = datetime.timedelta(0, 0, 0, 0, value)
 
 
603
                delta = datetime.timedelta(0, 0, 0, 0, 0, value)
 
 
605
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
 
 
608
        except (ValueError, IndexError):
 
 
614
def server_state_changed(state):
 
 
615
    """Derived from the Avahi example code"""
 
 
616
    if state == avahi.SERVER_COLLISION:
 
 
617
        logger.error(u"Zeroconf server name collision")
 
 
619
    elif state == avahi.SERVER_RUNNING:
 
 
623
def entry_group_state_changed(state, error):
 
 
624
    """Derived from the Avahi example code"""
 
 
625
    logger.debug(u"Avahi state change: %i", state)
 
 
627
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
 
 
628
        logger.debug(u"Zeroconf service established.")
 
 
629
    elif state == avahi.ENTRY_GROUP_COLLISION:
 
 
630
        logger.warning(u"Zeroconf service name collision.")
 
 
632
    elif state == avahi.ENTRY_GROUP_FAILURE:
 
 
633
        logger.critical(u"Avahi: Error in group state changed %s",
 
 
635
        raise AvahiGroupError("State changed: %s", str(error))
 
 
637
def if_nametoindex(interface):
 
 
638
    """Call the C function if_nametoindex(), or equivalent"""
 
 
639
    global if_nametoindex
 
 
641
        if "ctypes.util" not in sys.modules:
 
 
643
        if_nametoindex = ctypes.cdll.LoadLibrary\
 
 
644
            (ctypes.util.find_library("c")).if_nametoindex
 
 
645
    except (OSError, AttributeError):
 
 
646
        if "struct" not in sys.modules:
 
 
648
        if "fcntl" not in sys.modules:
 
 
650
        def if_nametoindex(interface):
 
 
651
            "Get an interface index the hard way, i.e. using fcntl()"
 
 
652
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
 
 
654
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
 
655
                                struct.pack("16s16x", interface))
 
 
657
            interface_index = struct.unpack("I", ifreq[16:20])[0]
 
 
658
            return interface_index
 
 
659
    return if_nametoindex(interface)
 
 
662
def daemon(nochdir = False, noclose = False):
 
 
663
    """See daemon(3).  Standard BSD Unix function.
 
 
664
    This should really exist as os.daemon, but it doesn't (yet)."""
 
 
673
        # Close all standard open file descriptors
 
 
674
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
 
675
        if not stat.S_ISCHR(os.fstat(null).st_mode):
 
 
676
            raise OSError(errno.ENODEV,
 
 
677
                          "/dev/null not a character device")
 
 
678
        os.dup2(null, sys.stdin.fileno())
 
 
679
        os.dup2(null, sys.stdout.fileno())
 
 
680
        os.dup2(null, sys.stderr.fileno())
 
124
 
    parser = OptionParser()
 
 
686
    global main_loop_started
 
 
687
    main_loop_started = False
 
 
689
    parser = OptionParser(version = "%%prog %s" % version)
 
125
690
    parser.add_option("-i", "--interface", type="string",
 
126
 
                      default="eth0", metavar="IF",
 
127
 
                      help="Interface to bind to")
 
128
 
    parser.add_option("--cert", type="string", default="cert.pem",
 
130
 
                      help="Public key certificate to use")
 
131
 
    parser.add_option("--key", type="string", default="key.pem",
 
133
 
                      help="Private key to use")
 
134
 
    parser.add_option("--ca", type="string", default="ca.pem",
 
136
 
                      help="Certificate Authority certificate to use")
 
137
 
    parser.add_option("--crl", type="string", default="crl.pem",
 
139
 
                      help="Certificate Revokation List to use")
 
140
 
    parser.add_option("-p", "--port", type="int", default=49001,
 
 
691
                      metavar="IF", help="Bind to interface IF")
 
 
692
    parser.add_option("-a", "--address", type="string",
 
 
693
                      help="Address to listen for requests on")
 
 
694
    parser.add_option("-p", "--port", type="int",
 
141
695
                      help="Port number to receive requests on")
 
142
 
    parser.add_option("--dh", type="int", metavar="BITS",
 
143
 
                      help="DH group to use")
 
144
 
    parser.add_option("-t", "--timeout", type="string", # Parsed later
 
146
 
                      help="Amount of downtime allowed for clients")
 
 
696
    parser.add_option("--check", action="store_true", default=False,
 
 
697
                      help="Run self-test")
 
 
698
    parser.add_option("--debug", action="store_true",
 
 
699
                      help="Debug mode; run in foreground and log to"
 
 
701
    parser.add_option("--priority", type="string", help="GnuTLS"
 
 
702
                      " priority string (see GnuTLS documentation)")
 
 
703
    parser.add_option("--servicename", type="string", metavar="NAME",
 
 
704
                      help="Zeroconf service name")
 
 
705
    parser.add_option("--configdir", type="string",
 
 
706
                      default="/etc/mandos", metavar="DIR",
 
 
707
                      help="Directory to search for configuration"
 
147
709
    (options, args) = parser.parse_args()
 
149
 
    # Parse the time argument
 
151
 
        suffix=options.timeout[-1]
 
152
 
        value=int(options.timeout[:-1])
 
154
 
            options.timeout = datetime.timedelta(value)
 
156
 
            options.timeout = datetime.timedelta(0, value)
 
158
 
            options.timeout = datetime.timedelta(0, 0, 0, 0, value)
 
160
 
            options.timeout = datetime.timedelta(0, 0, 0, 0, 0, value)
 
162
 
            options.timeout = datetime.timedelta(0, 0, 0, 0, 0, 0,
 
166
 
    except (ValueError, IndexError):
 
167
 
        parser.error("option --timeout: Unparseable time")
 
169
 
    cert = gnutls.crypto.X509Certificate(open(options.cert).read())
 
170
 
    key = gnutls.crypto.X509PrivateKey(open(options.key).read())
 
171
 
    ca = gnutls.crypto.X509Certificate(open(options.ca).read())
 
172
 
    crl = gnutls.crypto.X509CRL(open(options.crl).read())
 
173
 
    cred = gnutls.connection.X509Credentials(cert, key, [ca], [crl])
 
177
 
    client_config_object = ConfigParser.SafeConfigParser(defaults)
 
178
 
    client_config_object.read("mandos-clients.conf")
 
179
 
    clients = [Client(name=section,
 
180
 
                      **(dict(client_config_object.items(section))))
 
181
 
               for section in client_config_object.sections()]
 
183
 
    udp_server = IPv6_UDPServer((in6addr_any, options.port),
 
187
 
    tcp_server = IPv6_TCPServer((in6addr_any, options.port),
 
 
716
    # Default values for config file for server-global settings
 
 
717
    server_defaults = { "interface": "",
 
 
722
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
 
 
723
                        "servicename": "Mandos",
 
 
726
    # Parse config file for server-global settings
 
 
727
    server_config = ConfigParser.SafeConfigParser(server_defaults)
 
 
729
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
 
 
730
    # Convert the SafeConfigParser object to a dict
 
 
731
    server_settings = server_config.defaults()
 
 
732
    # Use getboolean on the boolean config option
 
 
733
    server_settings["debug"] = server_config.getboolean\
 
 
737
    # Override the settings from the config file with command line
 
 
739
    for option in ("interface", "address", "port", "debug",
 
 
740
                   "priority", "servicename", "configdir"):
 
 
741
        value = getattr(options, option)
 
 
742
        if value is not None:
 
 
743
            server_settings[option] = value
 
 
745
    # Now we have our good server settings in "server_settings"
 
 
747
    debug = server_settings["debug"]
 
 
750
        syslogger.setLevel(logging.WARNING)
 
 
751
        console.setLevel(logging.WARNING)
 
 
753
    if server_settings["servicename"] != "Mandos":
 
 
754
        syslogger.setFormatter(logging.Formatter\
 
 
755
                               ('Mandos (%s): %%(levelname)s:'
 
 
757
                                % server_settings["servicename"]))
 
 
759
    # Parse config file with clients
 
 
760
    client_defaults = { "timeout": "1h",
 
 
762
                        "checker": "fping -q -- %(host)s",
 
 
765
    client_config = ConfigParser.SafeConfigParser(client_defaults)
 
 
766
    client_config.read(os.path.join(server_settings["configdir"],
 
 
770
    tcp_server = IPv6_TCPServer((server_settings["address"],
 
 
771
                                 server_settings["port"]),
 
194
 
        in_, out, err = select.select((udp_server,
 
197
 
            server.handle_request()
 
200
 
if __name__ == "__main__":
 
 
773
                                settings=server_settings,
 
 
775
    pidfilename = "/var/run/mandos.pid"
 
 
777
        pidfile = open(pidfilename, "w")
 
 
778
    except IOError, error:
 
 
779
        logger.error("Could not open file %r", pidfilename)
 
 
784
        uid = pwd.getpwnam("mandos").pw_uid
 
 
787
            uid = pwd.getpwnam("nobody").pw_uid
 
 
791
        gid = pwd.getpwnam("mandos").pw_gid
 
 
794
            gid = pwd.getpwnam("nogroup").pw_gid
 
 
800
    except OSError, error:
 
 
801
        if error[0] != errno.EPERM:
 
 
805
    service = AvahiService(name = server_settings["servicename"],
 
 
806
                           type = "_mandos._tcp", );
 
 
807
    if server_settings["interface"]:
 
 
808
        service.interface = if_nametoindex\
 
 
809
                            (server_settings["interface"])
 
 
814
    # From the Avahi example code
 
 
815
    DBusGMainLoop(set_as_default=True )
 
 
816
    main_loop = gobject.MainLoop()
 
 
817
    bus = dbus.SystemBus()
 
 
818
    server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
 
 
819
                                           avahi.DBUS_PATH_SERVER),
 
 
820
                            avahi.DBUS_INTERFACE_SERVER)
 
 
821
    # End of Avahi example code
 
 
823
    def remove_from_clients(client):
 
 
824
        clients.remove(client)
 
 
826
            logger.critical(u"No clients left, exiting")
 
 
829
    clients.update(Set(Client(name = section,
 
 
830
                              stop_hook = remove_from_clients,
 
 
832
                              = dict(client_config.items(section)))
 
 
833
                       for section in client_config.sections()))
 
 
835
        logger.critical(u"No clients defined")
 
 
839
        # Redirect stdin so all checkers get /dev/null
 
 
840
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
 
841
        os.dup2(null, sys.stdin.fileno())
 
 
846
        logger.removeHandler(console)
 
 
847
        # Close all input and output, do double fork, etc.
 
 
852
        pidfile.write(str(pid) + "\n")
 
 
856
        logger.error(u"Could not write to file %r with PID %d",
 
 
859
        # "pidfile" was never created
 
 
864
        "Cleanup function; run on exit"
 
 
866
        # From the Avahi example code
 
 
867
        if not group is None:
 
 
870
        # End of Avahi example code
 
 
873
            client = clients.pop()
 
 
874
            client.stop_hook = None
 
 
877
    atexit.register(cleanup)
 
 
880
        signal.signal(signal.SIGINT, signal.SIG_IGN)
 
 
881
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
 
 
882
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
 
 
884
    for client in clients:
 
 
888
    tcp_server.server_activate()
 
 
890
    # Find out what port we got
 
 
891
    service.port = tcp_server.socket.getsockname()[1]
 
 
892
    logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
 
 
893
                u" scope_id %d" % tcp_server.socket.getsockname())
 
 
895
    #service.interface = tcp_server.socket.getsockname()[3]
 
 
898
        # From the Avahi example code
 
 
899
        server.connect_to_signal("StateChanged", server_state_changed)
 
 
901
            server_state_changed(server.GetState())
 
 
902
        except dbus.exceptions.DBusException, error:
 
 
903
            logger.critical(u"DBusException: %s", error)
 
 
905
        # End of Avahi example code
 
 
907
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
 
 
908
                             lambda *args, **kwargs:
 
 
909
                             tcp_server.handle_request\
 
 
910
                             (*args[2:], **kwargs) or True)
 
 
912
        logger.debug(u"Starting main loop")
 
 
913
        main_loop_started = True
 
 
915
    except AvahiError, error:
 
 
916
        logger.critical(u"AvahiError: %s" + unicode(error))
 
 
918
    except KeyboardInterrupt:
 
 
922
if __name__ == '__main__':