9
40
import gnutls.crypto
 
10
41
import gnutls.connection
 
11
42
import gnutls.errors
 
 
43
import gnutls.library.functions
 
 
44
import gnutls.library.constants
 
 
45
import gnutls.library.types
 
12
46
import ConfigParser
 
 
56
import logging.handlers
 
 
61
from dbus.mainloop.glib import DBusGMainLoop
 
 
66
logger = logging.Logger('mandos')
 
 
67
syslogger = logging.handlers.SysLogHandler\
 
 
68
            (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
 
 
70
syslogger.setFormatter(logging.Formatter\
 
 
71
                        ('Mandos: %(levelname)s: %(message)s'))
 
 
72
logger.addHandler(syslogger)
 
 
74
console = logging.StreamHandler()
 
 
75
console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
 
 
77
logger.addHandler(console)
 
 
79
class AvahiError(Exception):
 
 
80
    def __init__(self, value):
 
 
83
        return repr(self.value)
 
 
85
class AvahiServiceError(AvahiError):
 
 
88
class AvahiGroupError(AvahiError):
 
 
92
class AvahiService(object):
 
 
93
    """An Avahi (Zeroconf) service.
 
 
95
    interface: integer; avahi.IF_UNSPEC or an interface index.
 
 
96
               Used to optionally bind to the specified interface.
 
 
97
    name: string; Example: 'Mandos'
 
 
98
    type: string; Example: '_mandos._tcp'.
 
 
99
                  See <http://www.dns-sd.org/ServiceTypes.html>
 
 
100
    port: integer; what port to announce
 
 
101
    TXT: list of strings; TXT record for the service
 
 
102
    domain: string; Domain to publish on, default to .local if empty.
 
 
103
    host: string; Host to publish records for, default is localhost
 
 
104
    max_renames: integer; maximum number of renames
 
 
105
    rename_count: integer; counter so we only rename after collisions
 
 
106
                  a sensible number of times
 
 
108
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
 
 
109
                 type = None, port = None, TXT = None, domain = "",
 
 
110
                 host = "", max_renames = 32768):
 
 
111
        self.interface = interface
 
 
121
        self.rename_count = 0
 
 
122
        self.max_renames = max_renames
 
 
124
        """Derived from the Avahi example code"""
 
 
125
        if self.rename_count >= self.max_renames:
 
 
126
            logger.critical(u"No suitable service name found after %i"
 
 
127
                            u" retries, exiting.", rename_count)
 
 
128
            raise AvahiServiceError("Too many renames")
 
 
129
        self.name = server.GetAlternativeServiceName(self.name)
 
 
130
        logger.info(u"Changing name to %r ...", str(self.name))
 
 
131
        syslogger.setFormatter(logging.Formatter\
 
 
132
                               ('Mandos (%s): %%(levelname)s:'
 
 
133
                               ' %%(message)s' % self.name))
 
 
136
        self.rename_count += 1
 
 
138
        """Derived from the Avahi example code"""
 
 
139
        if group is not None:
 
 
142
        """Derived from the Avahi example code"""
 
 
145
            group = dbus.Interface\
 
 
146
                    (bus.get_object(avahi.DBUS_NAME,
 
 
147
                                    server.EntryGroupNew()),
 
 
148
                     avahi.DBUS_INTERFACE_ENTRY_GROUP)
 
 
149
            group.connect_to_signal('StateChanged',
 
 
150
                                    entry_group_state_changed)
 
 
151
        logger.debug(u"Adding service '%s' of type '%s' ...",
 
 
152
                     service.name, service.type)
 
 
154
                self.interface,         # interface
 
 
155
                avahi.PROTO_INET6,      # protocol
 
 
156
                dbus.UInt32(0),         # flags
 
 
157
                self.name, self.type,
 
 
158
                self.domain, self.host,
 
 
159
                dbus.UInt16(self.port),
 
 
160
                avahi.string_array_to_txt_array(self.TXT))
 
 
163
# From the Avahi example code:
 
 
164
group = None                            # our entry group
 
 
165
# End of Avahi example code
 
14
168
class Client(object):
 
15
 
    def __init__(self, name=None, dn=None, password=None,
 
16
 
                 passfile=None, fqdn=None, timeout=None,
 
 
169
    """A representation of a client host served by this server.
 
 
171
    name:      string; from the config file, used in log messages
 
 
172
    fingerprint: string (40 or 32 hexadecimal digits); used to
 
 
173
                 uniquely identify the client
 
 
174
    secret:    bytestring; sent verbatim (over TLS) to client
 
 
175
    host:      string; available for use by the checker command
 
 
176
    created:   datetime.datetime(); object creation, not client host
 
 
177
    last_checked_ok: datetime.datetime() or None if not yet checked OK
 
 
178
    timeout:   datetime.timedelta(); How long from last_checked_ok
 
 
179
                                     until this client is invalid
 
 
180
    interval:  datetime.timedelta(); How often to start a new checker
 
 
181
    stop_hook: If set, called by stop() as stop_hook(self)
 
 
182
    checker:   subprocess.Popen(); a running checker process used
 
 
183
                                   to see if the client lives.
 
 
184
                                   'None' if no process is running.
 
 
185
    checker_initiator_tag: a gobject event source tag, or None
 
 
186
    stop_initiator_tag:    - '' -
 
 
187
    checker_callback_tag:  - '' -
 
 
188
    checker_command: string; External command which is run to check if
 
 
189
                     client lives.  %() expansions are done at
 
 
190
                     runtime with vars(self) as dict, so that for
 
 
191
                     instance %(name)s can be used in the command.
 
 
193
    _timeout: Real variable for 'timeout'
 
 
194
    _interval: Real variable for 'interval'
 
 
195
    _timeout_milliseconds: Used when calling gobject.timeout_add()
 
 
196
    _interval_milliseconds: - '' -
 
 
198
    def _set_timeout(self, timeout):
 
 
199
        "Setter function for 'timeout' attribute"
 
 
200
        self._timeout = timeout
 
 
201
        self._timeout_milliseconds = ((self.timeout.days
 
 
202
                                       * 24 * 60 * 60 * 1000)
 
 
203
                                      + (self.timeout.seconds * 1000)
 
 
204
                                      + (self.timeout.microseconds
 
 
206
    timeout = property(lambda self: self._timeout,
 
 
209
    def _set_interval(self, interval):
 
 
210
        "Setter function for 'interval' attribute"
 
 
211
        self._interval = interval
 
 
212
        self._interval_milliseconds = ((self.interval.days
 
 
213
                                        * 24 * 60 * 60 * 1000)
 
 
214
                                       + (self.interval.seconds
 
 
216
                                       + (self.interval.microseconds
 
 
218
    interval = property(lambda self: self._interval,
 
 
221
    def __init__(self, name = None, stop_hook=None, config={}):
 
 
222
        """Note: the 'checker' key in 'config' sets the
 
 
223
        '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
 
 
226
        logger.debug(u"Creating client %r", self.name)
 
 
227
        # Uppercase and remove spaces from fingerprint for later
 
 
228
        # comparison purposes with return value from the fingerprint()
 
 
230
        self.fingerprint = config["fingerprint"].upper()\
 
 
232
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
 
 
233
        if "secret" in config:
 
 
234
            self.secret = config["secret"].decode(u"base64")
 
 
235
        elif "secfile" in config:
 
 
236
            sf = open(config["secfile"])
 
 
237
            self.secret = sf.read()
 
 
240
            raise TypeError(u"No secret or secfile for client %s"
 
 
242
        self.host = config.get("host", "")
 
 
243
        self.created = datetime.datetime.now()
 
 
244
        self.last_checked_ok = None
 
 
245
        self.timeout = string_to_delta(config["timeout"])
 
 
246
        self.interval = string_to_delta(config["interval"])
 
 
247
        self.stop_hook = stop_hook
 
 
249
        self.checker_initiator_tag = None
 
 
250
        self.stop_initiator_tag = None
 
 
251
        self.checker_callback_tag = None
 
 
252
        self.check_command = config["checker"]
 
 
254
        """Start this client's checker and timeout hooks"""
 
 
255
        # Schedule a new checker to be started an 'interval' from now,
 
 
256
        # and every interval from then on.
 
 
257
        self.checker_initiator_tag = gobject.timeout_add\
 
 
258
                                     (self._interval_milliseconds,
 
 
260
        # Also start a new checker *right now*.
 
 
262
        # Schedule a stop() when 'timeout' has passed
 
 
263
        self.stop_initiator_tag = gobject.timeout_add\
 
 
264
                                  (self._timeout_milliseconds,
 
 
268
        The possibility that a client might be restarted is left open,
 
 
269
        but not currently used."""
 
 
270
        # If this client doesn't have a secret, it is already stopped.
 
 
271
        if hasattr(self, "secret") and self.secret:
 
 
272
            logger.info(u"Stopping client %s", self.name)
 
 
276
        if getattr(self, "stop_initiator_tag", False):
 
 
277
            gobject.source_remove(self.stop_initiator_tag)
 
 
278
            self.stop_initiator_tag = None
 
 
279
        if getattr(self, "checker_initiator_tag", False):
 
 
280
            gobject.source_remove(self.checker_initiator_tag)
 
 
281
            self.checker_initiator_tag = None
 
 
285
        # Do not run this again if called by a gobject.timeout_add
 
 
288
        self.stop_hook = None
 
 
290
    def checker_callback(self, pid, condition):
 
 
291
        """The checker has completed, so take appropriate actions."""
 
 
292
        now = datetime.datetime.now()
 
 
293
        self.checker_callback_tag = None
 
 
295
        if os.WIFEXITED(condition) \
 
 
296
               and (os.WEXITSTATUS(condition) == 0):
 
 
297
            logger.info(u"Checker for %(name)s succeeded",
 
 
299
            self.last_checked_ok = now
 
 
300
            gobject.source_remove(self.stop_initiator_tag)
 
 
301
            self.stop_initiator_tag = gobject.timeout_add\
 
 
302
                                      (self._timeout_milliseconds,
 
 
304
        elif not os.WIFEXITED(condition):
 
 
305
            logger.warning(u"Checker for %(name)s crashed?",
 
 
308
            logger.info(u"Checker for %(name)s failed",
 
 
310
    def start_checker(self):
 
 
311
        """Start a new checker subprocess if one is not running.
 
 
312
        If a checker already exists, leave it running and do
 
 
314
        # The reason for not killing a running checker is that if we
 
 
315
        # did that, then if a checker (for some reason) started
 
 
316
        # running slowly and taking more than 'interval' time, the
 
 
317
        # client would inevitably timeout, since no checker would get
 
 
318
        # a chance to run to completion.  If we instead leave running
 
 
319
        # checkers alone, the checker would have to take more time
 
 
320
        # than 'timeout' for the client to be declared invalid, which
 
 
321
        # is as it should be.
 
 
322
        if self.checker is None:
 
 
324
                # In case check_command has exactly one % operator
 
 
325
                command = self.check_command % self.host
 
 
327
                # Escape attributes for the shell
 
 
328
                escaped_attrs = dict((key, re.escape(str(val)))
 
 
330
                                     vars(self).iteritems())
 
 
332
                    command = self.check_command % escaped_attrs
 
 
333
                except TypeError, error:
 
 
334
                    logger.error(u'Could not format string "%s":'
 
 
335
                                 u' %s', self.check_command, error)
 
 
336
                    return True # Try again later
 
 
338
                logger.info(u"Starting checker %r for %s",
 
 
340
                # We don't need to redirect stdout and stderr, since
 
 
341
                # in normal mode, that is already done by daemon(),
 
 
342
                # and in debug mode we don't want to.  (Stdin is
 
 
343
                # always replaced by /dev/null.)
 
 
344
                self.checker = subprocess.Popen(command,
 
 
347
                self.checker_callback_tag = gobject.child_watch_add\
 
 
349
                                             self.checker_callback)
 
 
350
            except OSError, error:
 
 
351
                logger.error(u"Failed to start subprocess: %s",
 
 
353
        # Re-run this periodically if run by gobject.timeout_add
 
 
355
    def stop_checker(self):
 
 
356
        """Force the checker process, if any, to stop."""
 
 
357
        if self.checker_callback_tag:
 
 
358
            gobject.source_remove(self.checker_callback_tag)
 
 
359
            self.checker_callback_tag = None
 
 
360
        if getattr(self, "checker", None) is None:
 
 
362
        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"
 
 
364
            os.kill(self.checker.pid, signal.SIGTERM)
 
 
366
            #if self.checker.poll() is None:
 
 
367
            #    os.kill(self.checker.pid, signal.SIGKILL)
 
 
368
        except OSError, error:
 
 
369
            if error.errno != errno.ESRCH: # No such process
 
 
372
    def still_valid(self):
 
 
373
        """Has the timeout not yet passed for this client?"""
 
 
374
        now = datetime.datetime.now()
 
 
375
        if self.last_checked_ok is None:
 
 
376
            return now < (self.created + self.timeout)
 
 
378
            return now < (self.last_checked_ok + self.timeout)
 
 
381
def peer_certificate(session):
 
 
382
    "Return the peer's OpenPGP certificate as a bytestring"
 
 
383
    # If not an OpenPGP certificate...
 
 
384
    if gnutls.library.functions.gnutls_certificate_type_get\
 
 
385
            (session._c_object) \
 
 
386
           != gnutls.library.constants.GNUTLS_CRT_OPENPGP:
 
 
387
        # ...do the normal thing
 
 
388
        return session.peer_certificate
 
 
389
    list_size = ctypes.c_uint()
 
 
390
    cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
 
 
391
        (session._c_object, ctypes.byref(list_size))
 
 
392
    if list_size.value == 0:
 
 
395
    return ctypes.string_at(cert.data, cert.size)
 
 
398
def fingerprint(openpgp):
 
 
399
    "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
 
 
400
    # New GnuTLS "datum" with the OpenPGP public key
 
 
401
    datum = gnutls.library.types.gnutls_datum_t\
 
 
402
        (ctypes.cast(ctypes.c_char_p(openpgp),
 
 
403
                     ctypes.POINTER(ctypes.c_ubyte)),
 
 
404
         ctypes.c_uint(len(openpgp)))
 
 
405
    # New empty GnuTLS certificate
 
 
406
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
 
 
407
    gnutls.library.functions.gnutls_openpgp_crt_init\
 
 
409
    # Import the OpenPGP public key into the certificate
 
 
410
    gnutls.library.functions.gnutls_openpgp_crt_import\
 
 
411
                    (crt, ctypes.byref(datum),
 
 
412
                     gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
 
 
413
    # New buffer for the fingerprint
 
 
414
    buffer = ctypes.create_string_buffer(20)
 
 
415
    buffer_length = ctypes.c_size_t()
 
 
416
    # Get the fingerprint from the certificate into the buffer
 
 
417
    gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
 
 
418
        (crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
 
 
419
    # Deinit the certificate
 
 
420
    gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
 
 
421
    # Convert the buffer to a Python bytestring
 
 
422
    fpr = ctypes.string_at(buffer, buffer_length.value)
 
 
423
    # Convert the bytestring to hexadecimal notation
 
 
424
    hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
 
85
428
class tcp_handler(SocketServer.BaseRequestHandler, object):
 
 
429
    """A TCP request handler class.
 
 
430
    Instantiated by IPv6_TCPServer for each request to handle it.
 
 
431
    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")
 
 
434
        logger.info(u"TCP connection from: %s",
 
 
435
                     unicode(self.client_address))
 
 
436
        session = gnutls.connection.ClientSession\
 
 
437
                  (self.request, gnutls.connection.X509Credentials())
 
 
439
        line = self.request.makefile().readline()
 
 
440
        logger.debug(u"Protocol version: %r", line)
 
 
442
            if int(line.strip().split()[0]) > 1:
 
 
444
        except (ValueError, IndexError, RuntimeError), error:
 
 
445
            logger.error(u"Unknown protocol version: %s", error)
 
 
448
        # Note: gnutls.connection.X509Credentials is really a generic
 
 
449
        # GnuTLS certificate credentials object so long as no X.509
 
 
450
        # keys are added to it.  Therefore, we can use it here despite
 
 
451
        # using OpenPGP certificates.
 
 
453
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
 
 
454
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
 
456
        priority = "NORMAL"             # Fallback default, since this
 
 
458
        if self.server.settings["priority"]:
 
 
459
            priority = self.server.settings["priority"]
 
 
460
        gnutls.library.functions.gnutls_priority_set_direct\
 
 
461
            (session._c_object, priority, None);
 
 
465
        except gnutls.errors.GNUTLSError, error:
 
 
466
            logger.warning(u"Handshake failed: %s", error)
 
 
467
            # Do not run session.bye() here: the session is not
 
 
468
            # established.  Just abandon the request.
 
 
471
            fpr = fingerprint(peer_certificate(session))
 
 
472
        except (TypeError, gnutls.errors.GNUTLSError), error:
 
 
473
            logger.warning(u"Bad certificate: %s", error)
 
 
476
        logger.debug(u"Fingerprint: %s", fpr)
 
 
478
        for c in self.server.clients:
 
 
479
            if c.fingerprint == fpr:
 
 
483
            logger.warning(u"Client not found for fingerprint: %s",
 
 
487
        # Have to check if client.still_valid(), since it is possible
 
 
488
        # that the client timed out while establishing the GnuTLS
 
 
490
        if not client.still_valid():
 
 
491
            logger.warning(u"Client %(name)s is invalid",
 
 
496
        while sent_size < len(client.secret):
 
 
497
            sent = session.send(client.secret[sent_size:])
 
 
498
            logger.debug(u"Sent: %d, remaining: %d",
 
 
499
                         sent, len(client.secret)
 
 
500
                         - (sent_size + sent))
 
111
505
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
 
112
 
    __init__ = init_with_options
 
 
506
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
 
 
508
        settings:       Server settings
 
 
509
        clients:        Set() of Client objects
 
113
511
    address_family = socket.AF_INET6
 
114
 
    allow_reuse_address = True
 
115
 
    request_queue_size = 1024
 
116
 
    server_bind = server_bind
 
 
512
    def __init__(self, *args, **kwargs):
 
 
513
        if "settings" in kwargs:
 
 
514
            self.settings = kwargs["settings"]
 
 
515
            del kwargs["settings"]
 
 
516
        if "clients" in kwargs:
 
 
517
            self.clients = kwargs["clients"]
 
 
518
            del kwargs["clients"]
 
 
519
        return super(type(self), self).__init__(*args, **kwargs)
 
 
520
    def server_bind(self):
 
 
521
        """This overrides the normal server_bind() function
 
 
522
        to bind to an interface if one was specified, and also NOT to
 
 
523
        bind to an address or port if they were not specified."""
 
 
524
        if self.settings["interface"]:
 
 
525
            # 25 is from /usr/include/asm-i486/socket.h
 
 
526
            SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
 
 
528
                self.socket.setsockopt(socket.SOL_SOCKET,
 
 
530
                                       self.settings["interface"])
 
 
531
            except socket.error, error:
 
 
532
                if error[0] == errno.EPERM:
 
 
533
                    logger.error(u"No permission to"
 
 
534
                                 u" bind to interface %s",
 
 
535
                                 self.settings["interface"])
 
 
538
        # Only bind(2) the socket if we really need to.
 
 
539
        if self.server_address[0] or self.server_address[1]:
 
 
540
            if not self.server_address[0]:
 
 
542
                self.server_address = (in6addr_any,
 
 
543
                                       self.server_address[1])
 
 
544
            elif not self.server_address[1]:
 
 
545
                self.server_address = (self.server_address[0],
 
 
547
#                 if self.settings["interface"]:
 
 
548
#                     self.server_address = (self.server_address[0],
 
 
554
            return super(type(self), self).server_bind()
 
 
557
def string_to_delta(interval):
 
 
558
    """Parse a string and return a datetime.timedelta
 
 
560
    >>> string_to_delta('7d')
 
 
561
    datetime.timedelta(7)
 
 
562
    >>> string_to_delta('60s')
 
 
563
    datetime.timedelta(0, 60)
 
 
564
    >>> string_to_delta('60m')
 
 
565
    datetime.timedelta(0, 3600)
 
 
566
    >>> string_to_delta('24h')
 
 
567
    datetime.timedelta(1)
 
 
568
    >>> string_to_delta(u'1w')
 
 
569
    datetime.timedelta(7)
 
 
570
    >>> string_to_delta('5m 30s')
 
 
571
    datetime.timedelta(0, 330)
 
 
573
    timevalue = datetime.timedelta(0)
 
 
574
    for s in interval.split():
 
 
576
            suffix=unicode(s[-1])
 
 
579
                delta = datetime.timedelta(value)
 
 
581
                delta = datetime.timedelta(0, value)
 
 
583
                delta = datetime.timedelta(0, 0, 0, 0, value)
 
 
585
                delta = datetime.timedelta(0, 0, 0, 0, 0, value)
 
 
587
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
 
 
590
        except (ValueError, IndexError):
 
 
596
def server_state_changed(state):
 
 
597
    """Derived from the Avahi example code"""
 
 
598
    if state == avahi.SERVER_COLLISION:
 
 
599
        logger.error(u"Server name collision")
 
 
601
    elif state == avahi.SERVER_RUNNING:
 
 
605
def entry_group_state_changed(state, error):
 
 
606
    """Derived from the Avahi example code"""
 
 
607
    logger.debug(u"state change: %i", state)
 
 
609
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
 
 
610
        logger.debug(u"Service established.")
 
 
611
    elif state == avahi.ENTRY_GROUP_COLLISION:
 
 
612
        logger.warning(u"Service name collision.")
 
 
614
    elif state == avahi.ENTRY_GROUP_FAILURE:
 
 
615
        logger.critical(u"Error in group state changed %s",
 
 
617
        raise AvahiGroupError("State changed: %s", str(error))
 
 
619
def if_nametoindex(interface):
 
 
620
    """Call the C function if_nametoindex(), or equivalent"""
 
 
621
    global if_nametoindex
 
 
623
        if "ctypes.util" not in sys.modules:
 
 
625
        if_nametoindex = ctypes.cdll.LoadLibrary\
 
 
626
            (ctypes.util.find_library("c")).if_nametoindex
 
 
627
    except (OSError, AttributeError):
 
 
628
        if "struct" not in sys.modules:
 
 
630
        if "fcntl" not in sys.modules:
 
 
632
        def if_nametoindex(interface):
 
 
633
            "Get an interface index the hard way, i.e. using fcntl()"
 
 
634
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
 
 
636
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
 
637
                                struct.pack("16s16x", interface))
 
 
639
            interface_index = struct.unpack("I", ifreq[16:20])[0]
 
 
640
            return interface_index
 
 
641
    return if_nametoindex(interface)
 
 
644
def daemon(nochdir = False, noclose = False):
 
 
645
    """See daemon(3).  Standard BSD Unix function.
 
 
646
    This should really exist as os.daemon, but it doesn't (yet)."""
 
 
655
        # Close all standard open file descriptors
 
 
656
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
 
657
        if not stat.S_ISCHR(os.fstat(null).st_mode):
 
 
658
            raise OSError(errno.ENODEV,
 
 
659
                          "/dev/null not a character device")
 
 
660
        os.dup2(null, sys.stdin.fileno())
 
 
661
        os.dup2(null, sys.stdout.fileno())
 
 
662
        os.dup2(null, sys.stderr.fileno())
 
124
 
    parser = OptionParser()
 
 
668
    global main_loop_started
 
 
669
    main_loop_started = False
 
 
671
    parser = OptionParser(version = "%%prog %s" % version)
 
125
672
    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,
 
 
673
                      metavar="IF", help="Bind to interface IF")
 
 
674
    parser.add_option("-a", "--address", type="string",
 
 
675
                      help="Address to listen for requests on")
 
 
676
    parser.add_option("-p", "--port", type="int",
 
141
677
                      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")
 
 
678
    parser.add_option("--check", action="store_true", default=False,
 
 
679
                      help="Run self-test")
 
 
680
    parser.add_option("--debug", action="store_true",
 
 
681
                      help="Debug mode; run in foreground and log to"
 
 
683
    parser.add_option("--priority", type="string", help="GnuTLS"
 
 
684
                      " priority string (see GnuTLS documentation)")
 
 
685
    parser.add_option("--servicename", type="string", metavar="NAME",
 
 
686
                      help="Zeroconf service name")
 
 
687
    parser.add_option("--configdir", type="string",
 
 
688
                      default="/etc/mandos", metavar="DIR",
 
 
689
                      help="Directory to search for configuration"
 
147
691
    (options, args) = parser.parse_args()
 
149
 
    # Parse the time argument
 
 
698
    # Default values for config file for server-global settings
 
 
699
    server_defaults = { "interface": "",
 
 
704
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
 
 
705
                        "servicename": "Mandos",
 
 
708
    # Parse config file for server-global settings
 
 
709
    server_config = ConfigParser.SafeConfigParser(server_defaults)
 
 
711
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
 
 
712
    # Convert the SafeConfigParser object to a dict
 
 
713
    server_settings = server_config.defaults()
 
 
714
    # Use getboolean on the boolean config option
 
 
715
    server_settings["debug"] = server_config.getboolean\
 
 
719
    # Override the settings from the config file with command line
 
 
721
    for option in ("interface", "address", "port", "debug",
 
 
722
                   "priority", "servicename", "configdir"):
 
 
723
        value = getattr(options, option)
 
 
724
        if value is not None:
 
 
725
            server_settings[option] = value
 
 
727
    # Now we have our good server settings in "server_settings"
 
 
729
    debug = server_settings["debug"]
 
 
732
        syslogger.setLevel(logging.WARNING)
 
 
733
        console.setLevel(logging.WARNING)
 
 
735
    if server_settings["servicename"] != "Mandos":
 
 
736
        syslogger.setFormatter(logging.Formatter\
 
 
737
                               ('Mandos (%s): %%(levelname)s:'
 
 
739
                                % server_settings["servicename"]))
 
 
741
    # Parse config file with clients
 
 
742
    client_defaults = { "timeout": "1h",
 
 
744
                        "checker": "fping -q -- %(host)s",
 
 
747
    client_config = ConfigParser.SafeConfigParser(client_defaults)
 
 
748
    client_config.read(os.path.join(server_settings["configdir"],
 
 
752
    service = AvahiService(name = server_settings["servicename"],
 
 
753
                           type = "_mandos._tcp", );
 
 
754
    if server_settings["interface"]:
 
 
755
        service.interface = if_nametoindex(server_settings["interface"])
 
 
760
    # From the Avahi example code
 
 
761
    DBusGMainLoop(set_as_default=True )
 
 
762
    main_loop = gobject.MainLoop()
 
 
763
    bus = dbus.SystemBus()
 
 
764
    server = dbus.Interface(
 
 
765
            bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
 
 
766
            avahi.DBUS_INTERFACE_SERVER )
 
 
767
    # End of Avahi example code
 
 
770
    def remove_from_clients(client):
 
 
771
        clients.remove(client)
 
 
773
            logger.critical(u"No clients left, exiting")
 
 
776
    clients.update(Set(Client(name = section,
 
 
777
                              stop_hook = remove_from_clients,
 
 
779
                              = dict(client_config.items(section)))
 
 
780
                       for section in client_config.sections()))
 
 
782
        logger.critical(u"No clients defined")
 
 
786
        # Redirect stdin so all checkers get /dev/null
 
 
787
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
 
788
        os.dup2(null, sys.stdin.fileno())
 
 
793
        logger.removeHandler(console)
 
 
794
        # Close all input and output, do double fork, etc.
 
 
797
    pidfilename = "/var/run/mandos/mandos.pid"
 
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),
 
 
800
        pidfile = open(pidfilename, "w")
 
 
801
        pidfile.write(str(pid) + "\n")
 
 
805
        logger.error(u"Could not write %s file with PID %d",
 
 
806
                     pidfilename, os.getpid())
 
 
809
        "Cleanup function; run on exit"
 
 
811
        # From the Avahi example code
 
 
812
        if not group is None:
 
 
815
        # End of Avahi example code
 
 
818
            client = clients.pop()
 
 
819
            client.stop_hook = None
 
 
822
    atexit.register(cleanup)
 
 
825
        signal.signal(signal.SIGINT, signal.SIG_IGN)
 
 
826
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
 
 
827
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
 
 
829
    for client in clients:
 
 
832
    tcp_server = IPv6_TCPServer((server_settings["address"],
 
 
833
                                 server_settings["port"]),
 
194
 
        in_, out, err = select.select((udp_server,
 
197
 
            server.handle_request()
 
200
 
if __name__ == "__main__":
 
 
835
                                settings=server_settings,
 
 
837
    # Find out what port we got
 
 
838
    service.port = tcp_server.socket.getsockname()[1]
 
 
839
    logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
 
 
840
                u" scope_id %d" % tcp_server.socket.getsockname())
 
 
842
    #service.interface = tcp_server.socket.getsockname()[3]
 
 
845
        # From the Avahi example code
 
 
846
        server.connect_to_signal("StateChanged", server_state_changed)
 
 
848
            server_state_changed(server.GetState())
 
 
849
        except dbus.exceptions.DBusException, error:
 
 
850
            logger.critical(u"DBusException: %s", error)
 
 
852
        # End of Avahi example code
 
 
854
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
 
 
855
                             lambda *args, **kwargs:
 
 
856
                             tcp_server.handle_request\
 
 
857
                             (*args[2:], **kwargs) or True)
 
 
859
        logger.debug(u"Starting main loop")
 
 
860
        main_loop_started = True
 
 
862
    except AvahiError, error:
 
 
863
        logger.critical(u"AvahiError: %s" + unicode(error))
 
 
865
    except KeyboardInterrupt:
 
 
869
if __name__ == '__main__':