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
 
 
64
# Brief description of the operation of this program:
 
 
66
# This server announces itself as a Zeroconf service.  Connecting
 
 
67
# clients use the TLS protocol, with the unusual quirk that this
 
 
68
# server program acts as a TLS "client" while a connecting client acts
 
 
69
# as a TLS "server".  The client (acting as a TLS "server") must
 
 
70
# supply an OpenPGP certificate, and the fingerprint of this
 
 
71
# certificate is used by this server to look up (in a list read from a
 
 
72
# file at start time) which binary blob to give the client.  No other
 
 
73
# authentication or authorization is done by this server.
 
 
76
logger = logging.Logger('mandos')
 
 
77
syslogger = logging.handlers.SysLogHandler\
 
 
78
            (facility = logging.handlers.SysLogHandler.LOG_DAEMON)
 
 
79
syslogger.setFormatter(logging.Formatter\
 
 
80
                        ('%(levelname)s: %(message)s'))
 
 
81
logger.addHandler(syslogger)
 
 
85
class AvahiError(Exception):
 
 
86
    def __init__(self, value):
 
 
89
        return repr(self.value)
 
 
91
class AvahiServiceError(AvahiError):
 
 
94
class AvahiGroupError(AvahiError):
 
 
98
class AvahiService(object):
 
 
100
    interface: integer; avahi.IF_UNSPEC or an interface index.
 
 
101
               Used to optionally bind to the specified interface.
 
 
102
    name = string; Example: "Mandos"
 
 
103
    type = string; Example: "_mandos._tcp".
 
 
104
                   See <http://www.dns-sd.org/ServiceTypes.html>
 
 
105
    port = integer; what port to announce
 
 
106
    TXT = list of strings; TXT record for the service
 
 
107
    domain = string; Domain to publish on, default to .local if empty.
 
 
108
    host = string; Host to publish records for, default to localhost
 
 
110
    max_renames = integer; maximum number of renames
 
 
111
    rename_count = integer; counter so we only rename after collisions
 
 
112
                   a sensible number of times
 
 
114
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
 
 
115
                 type = None, port = None, TXT = None, domain = "",
 
 
116
                 host = "", max_renames = 12):
 
 
117
        """An Avahi (Zeroconf) service. """
 
 
118
        self.interface = interface
 
 
128
        self.rename_count = 0
 
 
130
        """Derived from the Avahi example code"""
 
 
131
        if self.rename_count >= self.max_renames:
 
 
132
            logger.critical(u"No suitable service name found after %i"
 
 
133
                            u" retries, exiting.", rename_count)
 
 
134
            raise AvahiServiceError("Too many renames")
 
 
135
        name = server.GetAlternativeServiceName(name)
 
 
136
        logger.notice(u"Changing name to %r ...", name)
 
 
139
        self.rename_count += 1
 
 
141
        """Derived from the Avahi example code"""
 
 
142
        if group is not None:
 
 
145
        """Derived from the Avahi example code"""
 
 
148
            group = dbus.Interface\
 
 
149
                    (bus.get_object(avahi.DBUS_NAME,
 
 
150
                                    server.EntryGroupNew()),
 
 
151
                     avahi.DBUS_INTERFACE_ENTRY_GROUP)
 
 
152
            group.connect_to_signal('StateChanged',
 
 
153
                                    entry_group_state_changed)
 
 
154
        logger.debug(u"Adding service '%s' of type '%s' ...",
 
 
155
                     service.name, service.type)
 
 
157
                self.interface,         # interface
 
 
158
                avahi.PROTO_INET6,      # protocol
 
 
159
                dbus.UInt32(0),         # flags
 
 
160
                self.name, self.type,
 
 
161
                self.domain, self.host,
 
 
162
                dbus.UInt16(self.port),
 
 
163
                avahi.string_array_to_txt_array(self.TXT))
 
 
166
# From the Avahi example code:
 
 
167
group = None                            # our entry group
 
 
168
# End of Avahi example code
 
14
171
class Client(object):
 
15
 
    def __init__(self, name=None, dn=None, password=None,
 
16
 
                 passfile=None, fqdn=None, timeout=None,
 
 
172
    """A representation of a client host served by this server.
 
 
174
    name:      string; from the config file, used in log messages
 
 
175
    fingerprint: string (40 or 32 hexadecimal digits); used to
 
 
176
                 uniquely identify the client
 
 
177
    secret:    bytestring; sent verbatim (over TLS) to client
 
 
178
    fqdn:      string (FQDN); available for use by the checker command
 
 
179
    created:   datetime.datetime(); object creation, not client host
 
 
180
    last_checked_ok: datetime.datetime() or None if not yet checked OK
 
 
181
    timeout:   datetime.timedelta(); How long from last_checked_ok
 
 
182
                                     until this client is invalid
 
 
183
    interval:  datetime.timedelta(); How often to start a new checker
 
 
184
    stop_hook: If set, called by stop() as stop_hook(self)
 
 
185
    checker:   subprocess.Popen(); a running checker process used
 
 
186
                                   to see if the client lives.
 
 
187
                                   'None' if no process is running.
 
 
188
    checker_initiator_tag: a gobject event source tag, or None
 
 
189
    stop_initiator_tag:    - '' -
 
 
190
    checker_callback_tag:  - '' -
 
 
191
    checker_command: string; External command which is run to check if
 
 
192
                     client lives.  %() expansions are done at
 
 
193
                     runtime with vars(self) as dict, so that for
 
 
194
                     instance %(name)s can be used in the command.
 
 
196
    _timeout: Real variable for 'timeout'
 
 
197
    _interval: Real variable for 'interval'
 
 
198
    _timeout_milliseconds: Used when calling gobject.timeout_add()
 
 
199
    _interval_milliseconds: - '' -
 
 
201
    def _set_timeout(self, timeout):
 
 
202
        "Setter function for 'timeout' attribute"
 
 
203
        self._timeout = timeout
 
 
204
        self._timeout_milliseconds = ((self.timeout.days
 
 
205
                                       * 24 * 60 * 60 * 1000)
 
 
206
                                      + (self.timeout.seconds * 1000)
 
 
207
                                      + (self.timeout.microseconds
 
 
209
    timeout = property(lambda self: self._timeout,
 
 
212
    def _set_interval(self, interval):
 
 
213
        "Setter function for 'interval' attribute"
 
 
214
        self._interval = interval
 
 
215
        self._interval_milliseconds = ((self.interval.days
 
 
216
                                        * 24 * 60 * 60 * 1000)
 
 
217
                                       + (self.interval.seconds
 
 
219
                                       + (self.interval.microseconds
 
 
221
    interval = property(lambda self: self._interval,
 
 
224
    def __init__(self, name=None, stop_hook=None, fingerprint=None,
 
 
225
                 secret=None, secfile=None, fqdn=None, timeout=None,
 
 
226
                 interval=-1, checker=None):
 
 
227
        """Note: the 'checker' argument sets the 'checker_command'
 
 
228
        attribute and not the 'checker' attribute.."""
 
21
 
            self.password = password
 
23
 
            self.password = open(passfile).readall()
 
 
230
        logger.debug(u"Creating client %r", self.name)
 
 
231
        # Uppercase and remove spaces from fingerprint
 
 
232
        # for later comparison purposes with return value of
 
 
233
        # the fingerprint() function
 
 
234
        self.fingerprint = fingerprint.upper().replace(u" ", u"")
 
 
235
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
 
 
237
            self.secret = secret.decode(u"base64")
 
 
240
            self.secret = sf.read()
 
25
 
            print "No Password or Passfile in client config file"
 
26
 
            # raise RuntimeError XXX
 
27
 
            self.password = "gazonk"
 
 
243
            raise TypeError(u"No secret or secfile for client %s"
 
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
 
 
246
        self.created = datetime.datetime.now()
 
 
247
        self.last_checked_ok = None
 
 
248
        self.timeout = string_to_delta(timeout)
 
 
249
        self.interval = string_to_delta(interval)
 
 
250
        self.stop_hook = stop_hook
 
 
252
        self.checker_initiator_tag = None
 
 
253
        self.stop_initiator_tag = None
 
 
254
        self.checker_callback_tag = None
 
 
255
        self.check_command = checker
 
 
257
        """Start this client's checker and timeout hooks"""
 
 
258
        # Schedule a new checker to be started an 'interval' from now,
 
 
259
        # and every interval from then on.
 
 
260
        self.checker_initiator_tag = gobject.timeout_add\
 
 
261
                                     (self._interval_milliseconds,
 
 
263
        # Also start a new checker *right now*.
 
 
265
        # Schedule a stop() when 'timeout' has passed
 
 
266
        self.stop_initiator_tag = gobject.timeout_add\
 
 
267
                                  (self._timeout_milliseconds,
 
 
271
        The possibility that a client might be restarted is left open,
 
 
272
        but not currently used."""
 
 
273
        # If this client doesn't have a secret, it is already stopped.
 
 
275
            logger.debug(u"Stopping client %s", self.name)
 
 
279
        if getattr(self, "stop_initiator_tag", False):
 
 
280
            gobject.source_remove(self.stop_initiator_tag)
 
 
281
            self.stop_initiator_tag = None
 
 
282
        if getattr(self, "checker_initiator_tag", False):
 
 
283
            gobject.source_remove(self.checker_initiator_tag)
 
 
284
            self.checker_initiator_tag = None
 
 
288
        # Do not run this again if called by a gobject.timeout_add
 
 
291
        self.stop_hook = None
 
 
293
    def checker_callback(self, pid, condition):
 
 
294
        """The checker has completed, so take appropriate actions."""
 
 
295
        now = datetime.datetime.now()
 
 
296
        self.checker_callback_tag = None
 
 
298
        if os.WIFEXITED(condition) \
 
 
299
               and (os.WEXITSTATUS(condition) == 0):
 
 
300
            logger.debug(u"Checker for %(name)s succeeded",
 
 
302
            self.last_checked_ok = now
 
 
303
            gobject.source_remove(self.stop_initiator_tag)
 
 
304
            self.stop_initiator_tag = gobject.timeout_add\
 
 
305
                                      (self._timeout_milliseconds,
 
 
307
        elif not os.WIFEXITED(condition):
 
 
308
            logger.warning(u"Checker for %(name)s crashed?",
 
 
311
            logger.debug(u"Checker for %(name)s failed",
 
 
313
    def start_checker(self):
 
 
314
        """Start a new checker subprocess if one is not running.
 
 
315
        If a checker already exists, leave it running and do
 
 
317
        # The reason for not killing a running checker is that if we
 
 
318
        # did that, then if a checker (for some reason) started
 
 
319
        # running slowly and taking more than 'interval' time, the
 
 
320
        # client would inevitably timeout, since no checker would get
 
 
321
        # a chance to run to completion.  If we instead leave running
 
 
322
        # checkers alone, the checker would have to take more time
 
 
323
        # than 'timeout' for the client to be declared invalid, which
 
 
324
        # is as it should be.
 
 
325
        if self.checker is None:
 
 
327
                # In case check_command has exactly one % operator
 
 
328
                command = self.check_command % self.fqdn
 
 
330
                # Escape attributes for the shell
 
 
331
                escaped_attrs = dict((key, re.escape(str(val)))
 
 
333
                                     vars(self).iteritems())
 
 
335
                    command = self.check_command % escaped_attrs
 
 
336
                except TypeError, error:
 
 
337
                    logger.error(u'Could not format string "%s":'
 
 
338
                                 u' %s', self.check_command, error)
 
 
339
                    return True # Try again later
 
 
341
                logger.debug(u"Starting checker %r for %s",
 
 
343
                self.checker = subprocess.Popen(command,
 
 
346
                self.checker_callback_tag = gobject.child_watch_add\
 
 
348
                                             self.checker_callback)
 
 
349
            except subprocess.OSError, error:
 
 
350
                logger.error(u"Failed to start subprocess: %s",
 
 
352
        # Re-run this periodically if run by gobject.timeout_add
 
 
354
    def stop_checker(self):
 
 
355
        """Force the checker process, if any, to stop."""
 
 
356
        if self.checker_callback_tag:
 
 
357
            gobject.source_remove(self.checker_callback_tag)
 
 
358
            self.checker_callback_tag = None
 
 
359
        if getattr(self, "checker", None) is None:
 
 
361
        logger.debug("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"
 
 
363
            os.kill(self.checker.pid, signal.SIGTERM)
 
 
365
            #if self.checker.poll() is None:
 
 
366
            #    os.kill(self.checker.pid, signal.SIGKILL)
 
 
367
        except OSError, error:
 
 
368
            if error.errno != errno.ESRCH: # No such process
 
 
371
    def still_valid(self):
 
 
372
        """Has the timeout not yet passed for this client?"""
 
 
373
        now = datetime.datetime.now()
 
 
374
        if self.last_checked_ok is None:
 
 
375
            return now < (self.created + self.timeout)
 
 
377
            return now < (self.last_checked_ok + self.timeout)
 
 
380
def peer_certificate(session):
 
 
381
    "Return the peer's OpenPGP certificate as a bytestring"
 
 
382
    # If not an OpenPGP certificate...
 
 
383
    if gnutls.library.functions.gnutls_certificate_type_get\
 
 
384
            (session._c_object) \
 
 
385
           != gnutls.library.constants.GNUTLS_CRT_OPENPGP:
 
 
386
        # ...do the normal thing
 
 
387
        return session.peer_certificate
 
 
388
    list_size = ctypes.c_uint()
 
 
389
    cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
 
 
390
        (session._c_object, ctypes.byref(list_size))
 
 
391
    if list_size.value == 0:
 
 
394
    return ctypes.string_at(cert.data, cert.size)
 
 
397
def fingerprint(openpgp):
 
 
398
    "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
 
 
399
    # New empty GnuTLS certificate
 
 
400
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
 
 
401
    gnutls.library.functions.gnutls_openpgp_crt_init\
 
 
403
    # New GnuTLS "datum" with the OpenPGP public key
 
 
404
    datum = gnutls.library.types.gnutls_datum_t\
 
 
405
        (ctypes.cast(ctypes.c_char_p(openpgp),
 
 
406
                     ctypes.POINTER(ctypes.c_ubyte)),
 
 
407
         ctypes.c_uint(len(openpgp)))
 
 
408
    # Import the OpenPGP public key into the certificate
 
 
409
    ret = gnutls.library.functions.gnutls_openpgp_crt_import\
 
 
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.debug(u"TCP connection from: %s",
 
 
435
                     unicode(self.client_address))
 
 
436
        session = gnutls.connection.ClientSession\
 
 
437
                  (self.request, gnutls.connection.X509Credentials())
 
 
438
        # Note: gnutls.connection.X509Credentials is really a generic
 
 
439
        # GnuTLS certificate credentials object so long as no X.509
 
 
440
        # keys are added to it.  Therefore, we can use it here despite
 
 
441
        # using OpenPGP certificates.
 
 
443
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
 
 
444
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
 
446
        priority = "NORMAL"             # Fallback default, since this
 
 
448
        if self.server.settings["priority"]:
 
 
449
            priority = self.server.settings["priority"]
 
 
450
        gnutls.library.functions.gnutls_priority_set_direct\
 
 
451
            (session._c_object, priority, None);
 
 
455
        except gnutls.errors.GNUTLSError, error:
 
 
456
            logger.debug(u"Handshake failed: %s", error)
 
 
457
            # Do not run session.bye() here: the session is not
 
 
458
            # established.  Just abandon the request.
 
 
461
            fpr = fingerprint(peer_certificate(session))
 
 
462
        except (TypeError, gnutls.errors.GNUTLSError), error:
 
 
463
            logger.debug(u"Bad certificate: %s", error)
 
 
466
        logger.debug(u"Fingerprint: %s", fpr)
 
 
468
        for c in self.server.clients:
 
 
469
            if c.fingerprint == fpr:
 
 
473
            logger.debug(u"Client not found for fingerprint: %s", fpr)
 
 
476
        # Have to check if client.still_valid(), since it is possible
 
 
477
        # that the client timed out while establishing the GnuTLS
 
 
479
        if not client.still_valid():
 
 
480
            logger.debug(u"Client %(name)s is invalid", vars(client))
 
 
484
        while sent_size < len(client.secret):
 
 
485
            sent = session.send(client.secret[sent_size:])
 
 
486
            logger.debug(u"Sent: %d, remaining: %d",
 
 
487
                         sent, len(client.secret)
 
 
488
                         - (sent_size + sent))
 
111
493
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
 
112
 
    __init__ = init_with_options
 
 
494
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
 
 
496
        settings:       Server settings
 
 
497
        clients:        Set() of Client objects
 
113
499
    address_family = socket.AF_INET6
 
114
 
    allow_reuse_address = True
 
115
 
    request_queue_size = 1024
 
116
 
    server_bind = server_bind
 
 
500
    def __init__(self, *args, **kwargs):
 
 
501
        if "settings" in kwargs:
 
 
502
            self.settings = kwargs["settings"]
 
 
503
            del kwargs["settings"]
 
 
504
        if "clients" in kwargs:
 
 
505
            self.clients = kwargs["clients"]
 
 
506
            del kwargs["clients"]
 
 
507
        return super(type(self), self).__init__(*args, **kwargs)
 
 
508
    def server_bind(self):
 
 
509
        """This overrides the normal server_bind() function
 
 
510
        to bind to an interface if one was specified, and also NOT to
 
 
511
        bind to an address or port if they were not specified."""
 
 
512
        if self.settings["interface"] != avahi.IF_UNSPEC:
 
 
513
            # 25 is from /usr/include/asm-i486/socket.h
 
 
514
            SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
 
 
516
                self.socket.setsockopt(socket.SOL_SOCKET,
 
 
518
                                       self.settings["interface"])
 
 
519
            except socket.error, error:
 
 
520
                if error[0] == errno.EPERM:
 
 
521
                    logger.warning(u"No permission to"
 
 
522
                                   u" bind to interface %s",
 
 
523
                                   self.settings["interface"])
 
 
526
        # Only bind(2) the socket if we really need to.
 
 
527
        if self.server_address[0] or self.server_address[1]:
 
 
528
            if not self.server_address[0]:
 
 
530
                self.server_address = (in6addr_any,
 
 
531
                                       self.server_address[1])
 
 
532
            elif self.server_address[1] is None:
 
 
533
                self.server_address = (self.server_address[0],
 
 
535
            return super(type(self), self).server_bind()
 
 
538
def string_to_delta(interval):
 
 
539
    """Parse a string and return a datetime.timedelta
 
 
541
    >>> string_to_delta('7d')
 
 
542
    datetime.timedelta(7)
 
 
543
    >>> string_to_delta('60s')
 
 
544
    datetime.timedelta(0, 60)
 
 
545
    >>> string_to_delta('60m')
 
 
546
    datetime.timedelta(0, 3600)
 
 
547
    >>> string_to_delta('24h')
 
 
548
    datetime.timedelta(1)
 
 
549
    >>> string_to_delta(u'1w')
 
 
550
    datetime.timedelta(7)
 
 
553
        suffix=unicode(interval[-1])
 
 
554
        value=int(interval[:-1])
 
 
556
            delta = datetime.timedelta(value)
 
 
558
            delta = datetime.timedelta(0, value)
 
 
560
            delta = datetime.timedelta(0, 0, 0, 0, value)
 
 
562
            delta = datetime.timedelta(0, 0, 0, 0, 0, value)
 
 
564
            delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
 
 
567
    except (ValueError, IndexError):
 
 
572
def server_state_changed(state):
 
 
573
    """Derived from the Avahi example code"""
 
 
574
    if state == avahi.SERVER_COLLISION:
 
 
575
        logger.warning(u"Server name collision")
 
 
577
    elif state == avahi.SERVER_RUNNING:
 
 
581
def entry_group_state_changed(state, error):
 
 
582
    """Derived from the Avahi example code"""
 
 
583
    logger.debug(u"state change: %i", state)
 
 
585
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
 
 
586
        logger.debug(u"Service established.")
 
 
587
    elif state == avahi.ENTRY_GROUP_COLLISION:
 
 
588
        logger.warning(u"Service name collision.")
 
 
590
    elif state == avahi.ENTRY_GROUP_FAILURE:
 
 
591
        logger.critical(u"Error in group state changed %s",
 
 
593
        raise AvahiGroupError("State changed: %s", str(error))
 
 
595
def if_nametoindex(interface, _func=[None]):
 
 
596
    """Call the C function if_nametoindex(), or equivalent"""
 
 
597
    if _func[0] is not None:
 
 
598
        return _func[0](interface)
 
 
600
        if "ctypes.util" not in sys.modules:
 
 
604
                libc = ctypes.cdll.LoadLibrary\
 
 
605
                       (ctypes.util.find_library("c"))
 
 
606
                func[0] = libc.if_nametoindex
 
 
607
                return _func[0](interface)
 
 
611
    except (OSError, AttributeError):
 
 
612
        if "struct" not in sys.modules:
 
 
614
        if "fcntl" not in sys.modules:
 
 
616
        def the_hard_way(interface):
 
 
617
            "Get an interface index the hard way, i.e. using fcntl()"
 
 
618
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
 
 
620
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
 
621
                                struct.pack("16s16x", interface))
 
 
623
            interface_index = struct.unpack("I", ifreq[16:20])[0]
 
 
624
            return interface_index
 
 
625
        _func[0] = the_hard_way
 
 
626
        return _func[0](interface)
 
 
629
def daemon(nochdir, noclose):
 
 
630
    """See daemon(3).  Standard BSD Unix function.
 
 
631
    This should really exist as os.daemon, but it doesn't (yet)."""
 
 
638
        # Close all standard open file descriptors
 
 
639
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
 
640
        if not stat.S_ISCHR(os.fstat(null).st_mode):
 
 
641
            raise OSError(errno.ENODEV,
 
 
642
                          "/dev/null not a character device")
 
 
643
        os.dup2(null, sys.stdin.fileno())
 
 
644
        os.dup2(null, sys.stdout.fileno())
 
 
645
        os.dup2(null, sys.stderr.fileno())
 
 
651
    global main_loop_started
 
 
652
    main_loop_started = False
 
124
654
    parser = OptionParser()
 
125
655
    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,
 
 
656
                      metavar="IF", help="Bind to interface IF")
 
 
657
    parser.add_option("-a", "--address", type="string",
 
 
658
                      help="Address to listen for requests on")
 
 
659
    parser.add_option("-p", "--port", type="int",
 
141
660
                      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")
 
 
661
    parser.add_option("--check", action="store_true", default=False,
 
 
662
                      help="Run self-test")
 
 
663
    parser.add_option("--debug", action="store_true", default=False,
 
 
664
                      help="Debug mode; run in foreground and log to"
 
 
666
    parser.add_option("--priority", type="string", help="GnuTLS"
 
 
667
                      " priority string (see GnuTLS documentation)")
 
 
668
    parser.add_option("--servicename", type="string", metavar="NAME",
 
 
669
                      help="Zeroconf service name")
 
 
670
    parser.add_option("--configdir", type="string",
 
 
671
                      default="/etc/mandos", metavar="DIR",
 
 
672
                      help="Directory to search for configuration"
 
147
674
    (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),
 
 
681
    # Default values for config file for server-global settings
 
 
682
    server_defaults = { "interface": "",
 
 
687
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
 
 
688
                        "servicename": "Mandos",
 
 
691
    # Parse config file for server-global settings
 
 
692
    server_config = ConfigParser.SafeConfigParser(server_defaults)
 
 
694
    server_config.read(os.path.join(options.configdir, "server.conf"))
 
 
695
    server_section = "server"
 
 
696
    # Convert the SafeConfigParser object to a dict
 
 
697
    server_settings = dict(server_config.items(server_section))
 
 
698
    # Use getboolean on the boolean config option
 
 
699
    server_settings["debug"] = server_config.getboolean\
 
 
700
                               (server_section, "debug")
 
 
702
    if not server_settings["interface"]:
 
 
703
        server_settings["interface"] = avahi.IF_UNSPEC
 
 
705
    # Override the settings from the config file with command line
 
 
707
    for option in ("interface", "address", "port", "debug",
 
 
708
                   "priority", "servicename", "configdir"):
 
 
709
        value = getattr(options, option)
 
 
710
        if value is not None:
 
 
711
            server_settings[option] = value
 
 
713
    # Now we have our good server settings in "server_settings"
 
 
715
    # Parse config file with clients
 
 
716
    client_defaults = { "timeout": "1h",
 
 
718
                        "checker": "fping -q -- %%(fqdn)s",
 
 
720
    client_config = ConfigParser.SafeConfigParser(client_defaults)
 
 
721
    client_config.read(os.path.join(server_settings["configdir"],
 
 
725
    service = AvahiService(name = server_settings["servicename"],
 
 
726
                           type = "_mandos._tcp", );
 
 
731
    # From the Avahi example code
 
 
732
    DBusGMainLoop(set_as_default=True )
 
 
733
    main_loop = gobject.MainLoop()
 
 
734
    bus = dbus.SystemBus()
 
 
735
    server = dbus.Interface(
 
 
736
            bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
 
 
737
            avahi.DBUS_INTERFACE_SERVER )
 
 
738
    # End of Avahi example code
 
 
740
    debug = server_settings["debug"]
 
 
743
        console = logging.StreamHandler()
 
 
744
        # console.setLevel(logging.DEBUG)
 
 
745
        console.setFormatter(logging.Formatter\
 
 
746
                             ('%(levelname)s: %(message)s'))
 
 
747
        logger.addHandler(console)
 
 
751
    def remove_from_clients(client):
 
 
752
        clients.remove(client)
 
 
754
            logger.debug(u"No clients left, exiting")
 
 
757
    clients.update(Set(Client(name=section,
 
 
758
                              stop_hook = remove_from_clients,
 
 
759
                              **(dict(client_config\
 
 
761
                       for section in client_config.sections()))
 
 
767
        "Cleanup function; run on exit"
 
 
769
        # From the Avahi example code
 
 
770
        if not group is None:
 
 
773
        # End of Avahi example code
 
 
776
            client = clients.pop()
 
 
777
            client.stop_hook = None
 
 
780
    atexit.register(cleanup)
 
 
783
        signal.signal(signal.SIGINT, signal.SIG_IGN)
 
 
784
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
 
 
785
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
 
 
787
    for client in clients:
 
 
790
    tcp_server = IPv6_TCPServer((server_settings["address"],
 
 
791
                                 server_settings["port"]),
 
194
 
        in_, out, err = select.select((udp_server,
 
197
 
            server.handle_request()
 
200
 
if __name__ == "__main__":
 
 
793
                                settings=server_settings,
 
 
795
    # Find out what port we got
 
 
796
    service.port = tcp_server.socket.getsockname()[1]
 
 
797
    logger.debug(u"Now listening on port %d", service.port)
 
 
799
    if not server_settings["interface"]:
 
 
800
        service.interface = if_nametoindex\
 
 
801
                            (server_settings["interface"])
 
 
804
        # From the Avahi example code
 
 
805
        server.connect_to_signal("StateChanged", server_state_changed)
 
 
807
            server_state_changed(server.GetState())
 
 
808
        except dbus.exceptions.DBusException, error:
 
 
809
            logger.critical(u"DBusException: %s", error)
 
 
811
        # End of Avahi example code
 
 
813
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
 
 
814
                             lambda *args, **kwargs:
 
 
815
                             tcp_server.handle_request\
 
 
816
                             (*args[2:], **kwargs) or True)
 
 
818
        logger.debug("Starting main loop")
 
 
819
        main_loop_started = True
 
 
821
    except AvahiError, error:
 
 
822
        logger.critical(u"AvahiError: %s" + unicode(error))
 
 
824
    except KeyboardInterrupt:
 
 
828
if __name__ == '__main__':