2
 
# -*- mode: python; coding: utf-8 -*-
 
4
 
# Mandos server - give out binary blobs to connecting clients.
 
6
 
# This program is partly derived from an example program for an Avahi
 
7
 
# service publisher, downloaded from
 
8
 
# <http://avahi.org/wiki/PythonPublishExample>.  This includes the
 
9
 
# following functions: "add_service", "remove_service",
 
10
 
# "server_state_changed", "entry_group_state_changed", and some lines
 
13
 
# Everything else is Copyright © 2007-2008 Teddy Hogeborn and Björn
 
16
 
# This program is free software: you can redistribute it and/or modify
 
17
 
# it under the terms of the GNU General Public License as published by
 
18
 
# the Free Software Foundation, either version 3 of the License, or
 
19
 
# (at your option) any later version.
 
21
 
#     This program is distributed in the hope that it will be useful,
 
22
 
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
 
23
 
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
24
 
#     GNU General Public License for more details.
 
26
 
# You should have received a copy of the GNU General Public License
 
27
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
29
 
# Contact the authors at <https://www.fukt.bsnet.se/~belorn/> and
 
30
 
# <https://www.fukt.bsnet.se/~teddy/>.
 
33
3
from __future__ import division
 
 
52
19
from sets import Set
 
57
 
import logging.handlers
 
62
25
from dbus.mainloop.glib import DBusGMainLoop
 
65
 
# Brief description of the operation of this program:
 
67
 
# This server announces itself as a Zeroconf service.  Connecting
 
68
 
# clients use the TLS protocol, with the unusual quirk that this
 
69
 
# server program acts as a TLS "client" while the connecting clients
 
70
 
# acts as a TLS "server".  The clients (acting as a TLS "server") must
 
71
 
# supply an OpenPGP certificate, and the fingerprint of this
 
72
 
# certificate is used by this server to look up (in a list read from a
 
73
 
# file at start time) which binary blob to give the client.  No other
 
74
 
# authentication or authorization is done by this server.
 
77
 
logger = logging.Logger('mandos')
 
78
 
syslogger = logging.handlers.SysLogHandler\
 
79
 
            (facility = logging.handlers.SysLogHandler.LOG_DAEMON)
 
80
 
syslogger.setFormatter(logging.Formatter\
 
81
 
                        ('%(levelname)s: %(message)s'))
 
82
 
logger.addHandler(syslogger)
 
85
 
# This variable is used to optionally bind to a specified interface.
 
86
 
# It is a global variable to fit in with the other variables from the
 
 
27
# This variable is used to optionally bind to a specified
 
88
29
serviceInterface = avahi.IF_UNSPEC
 
89
 
# From the Avahi example code:
 
 
30
# It is a global variable to fit in with the rest of the
 
 
31
# variables from the Avahi server example code:
 
90
32
serviceName = "Mandos"
 
91
33
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
 
92
34
servicePort = None                      # Not known at startup
 
 
102
44
class Client(object):
 
103
45
    """A representation of a client host served by this server.
 
105
 
    name:      string; from the config file, used in log messages
 
106
 
    fingerprint: string (40 or 32 hexadecimal digits); used to
 
107
 
                 uniquely identify the client
 
108
 
    secret:    bytestring; sent verbatim (over TLS) to client
 
109
 
    fqdn:      string (FQDN); available for use by the checker command
 
 
48
    fqdn:      string, FQDN (used by the checker)
 
110
49
    created:   datetime.datetime()
 
111
50
    last_seen: datetime.datetime() or None if not yet seen
 
112
51
    timeout:   datetime.timedelta(); How long from last_seen until
 
113
52
                                     this client is invalid
 
114
53
    interval:  datetime.timedelta(); How often to start a new checker
 
 
54
    timeout_milliseconds: Used by gobject.timeout_add()
 
 
55
    interval_milliseconds: - '' -
 
115
56
    stop_hook: If set, called by stop() as stop_hook(self)
 
116
57
    checker:   subprocess.Popen(); a running checker process used
 
117
58
                                   to see if the client lives.
 
 
119
60
    checker_initiator_tag: a gobject event source tag, or None
 
120
61
    stop_initiator_tag:    - '' -
 
121
62
    checker_callback_tag:  - '' -
 
122
 
    checker_command: string; External command which is run to check if
 
123
 
                     client lives.  %()s expansions are done at
 
124
 
                     runtime with vars(self) as dict, so that for
 
125
 
                     instance %(name)s can be used in the command.
 
127
 
    _timeout: Real variable for 'timeout'
 
128
 
    _interval: Real variable for 'interval'
 
129
 
    _timeout_milliseconds: Used by gobject.timeout_add()
 
130
 
    _interval_milliseconds: - '' -
 
132
 
    def _set_timeout(self, timeout):
 
133
 
        "Setter function for 'timeout' attribute"
 
134
 
        self._timeout = timeout
 
135
 
        self._timeout_milliseconds = ((self.timeout.days
 
136
 
                                       * 24 * 60 * 60 * 1000)
 
137
 
                                      + (self.timeout.seconds * 1000)
 
138
 
                                      + (self.timeout.microseconds
 
140
 
    timeout = property(lambda self: self._timeout,
 
143
 
    def _set_interval(self, interval):
 
144
 
        "Setter function for 'interval' attribute"
 
145
 
        self._interval = interval
 
146
 
        self._interval_milliseconds = ((self.interval.days
 
147
 
                                        * 24 * 60 * 60 * 1000)
 
148
 
                                       + (self.interval.seconds
 
150
 
                                       + (self.interval.microseconds
 
152
 
    interval = property(lambda self: self._interval,
 
155
64
    def __init__(self, name=None, options=None, stop_hook=None,
 
156
 
                 fingerprint=None, secret=None, secfile=None,
 
157
 
                 fqdn=None, timeout=None, interval=-1, checker=None):
 
158
 
        """Note: the 'checker' argument sets the 'checker_command'
 
159
 
        attribute and not the 'checker' attribute.."""
 
 
65
                 dn=None, password=None, passfile=None, fqdn=None,
 
 
66
                 timeout=None, interval=-1):
 
161
 
        # Uppercase and remove spaces from fingerprint
 
162
 
        # for later comparison purposes with return value of
 
163
 
        # the fingerprint() function
 
164
 
        self.fingerprint = fingerprint.upper().replace(u" ", u"")
 
166
 
            self.secret = secret.decode(u"base64")
 
169
 
            self.secret = sf.read()
 
 
70
            self.password = password
 
 
72
            self.password = open(passfile).readall()
 
172
 
            raise RuntimeError(u"No secret or secfile for client %s"
 
 
74
            raise RuntimeError(u"No Password or Passfile for client %s"
 
174
76
        self.fqdn = fqdn                # string
 
175
77
        self.created = datetime.datetime.now()
 
176
78
        self.last_seen = None
 
177
79
        if timeout is None:
 
178
 
            self.timeout = options.timeout
 
180
 
            self.timeout = string_to_delta(timeout)
 
 
80
            timeout = options.timeout
 
 
81
        self.timeout = timeout
 
 
82
        self.timeout_milliseconds = ((self.timeout.days
 
 
83
                                      * 24 * 60 * 60 * 1000)
 
 
84
                                     + (self.timeout.seconds * 1000)
 
 
85
                                     + (self.timeout.microseconds
 
181
87
        if interval == -1:
 
182
 
            self.interval = options.interval
 
 
88
            interval = options.interval
 
184
 
            self.interval = string_to_delta(interval)
 
 
90
            interval = string_to_delta(interval)
 
 
91
        self.interval = interval
 
 
92
        self.interval_milliseconds = ((self.interval.days
 
 
93
                                       * 24 * 60 * 60 * 1000)
 
 
94
                                      + (self.interval.seconds * 1000)
 
 
95
                                      + (self.interval.microseconds
 
185
97
        self.stop_hook = stop_hook
 
186
98
        self.checker = None
 
187
99
        self.checker_initiator_tag = None
 
188
100
        self.stop_initiator_tag = None
 
189
101
        self.checker_callback_tag = None
 
190
 
        self.check_command = checker
 
192
 
        """Start this client's checker and timeout hooks"""
 
 
103
        """Start this clients checker and timeout hooks"""
 
193
104
        # Schedule a new checker to be started an 'interval' from now,
 
194
105
        # and every interval from then on.
 
195
 
        self.checker_initiator_tag = gobject.timeout_add\
 
196
 
                                     (self._interval_milliseconds,
 
 
106
        self.checker_initiator_tag = gobject.\
 
 
107
                                     timeout_add(self.interval_milliseconds,
 
198
109
        # Also start a new checker *right now*.
 
199
110
        self.start_checker()
 
200
111
        # Schedule a stop() when 'timeout' has passed
 
201
 
        self.stop_initiator_tag = gobject.timeout_add\
 
202
 
                                  (self._timeout_milliseconds,
 
 
112
        self.stop_initiator_tag = gobject.\
 
 
113
                                     timeout_add(self.timeout_milliseconds,
 
205
116
        """Stop this client.
 
206
117
        The possibility that this client might be restarted is left
 
207
118
        open, but not currently used."""
 
208
 
        # If this client doesn't have a secret, it is already stopped.
 
210
 
            logger.debug(u"Stopping client %s", self.name)
 
214
 
        if hasattr(self, "stop_initiator_tag") \
 
215
 
               and self.stop_initiator_tag:
 
 
119
        # print "Stopping client", self.name
 
 
121
        if self.stop_initiator_tag:
 
216
122
            gobject.source_remove(self.stop_initiator_tag)
 
217
123
            self.stop_initiator_tag = None
 
218
 
        if hasattr(self, "checker_initiator_tag") \
 
219
 
               and self.checker_initiator_tag:
 
 
124
        if self.checker_initiator_tag:
 
220
125
            gobject.source_remove(self.checker_initiator_tag)
 
221
126
            self.checker_initiator_tag = None
 
222
127
        self.stop_checker()
 
 
225
130
        # Do not run this again if called by a gobject.timeout_add
 
227
132
    def __del__(self):
 
228
 
        self.stop_hook = None
 
 
133
        # Some code duplication here and in stop()
 
 
134
        if hasattr(self, "stop_initiator_tag") \
 
 
135
               and self.stop_initiator_tag:
 
 
136
            gobject.source_remove(self.stop_initiator_tag)
 
 
137
            self.stop_initiator_tag = None
 
 
138
        if hasattr(self, "checker_initiator_tag") \
 
 
139
               and self.checker_initiator_tag:
 
 
140
            gobject.source_remove(self.checker_initiator_tag)
 
 
141
            self.checker_initiator_tag = None
 
230
143
    def checker_callback(self, pid, condition):
 
231
144
        """The checker has completed, so take appropriate actions."""
 
232
145
        now = datetime.datetime.now()
 
233
 
        self.checker_callback_tag = None
 
235
146
        if os.WIFEXITED(condition) \
 
236
147
               and (os.WEXITSTATUS(condition) == 0):
 
237
 
            logger.debug(u"Checker for %(name)s succeeded",
 
 
148
            #print "Checker for %(name)s succeeded" % vars(self)
 
239
149
            self.last_seen = now
 
240
150
            gobject.source_remove(self.stop_initiator_tag)
 
241
 
            self.stop_initiator_tag = gobject.timeout_add\
 
242
 
                                      (self._timeout_milliseconds,
 
244
 
        elif not os.WIFEXITED(condition):
 
245
 
            logger.warning(u"Checker for %(name)s crashed?",
 
248
 
            logger.debug(u"Checker for %(name)s failed",
 
 
151
            self.stop_initiator_tag = gobject.\
 
 
152
                                      timeout_add(self.timeout_milliseconds,
 
 
155
        #    if not os.WIFEXITED(condition):
 
 
156
        #        print "Checker for %(name)s crashed?" % vars(self)
 
 
158
        #        print "Checker for %(name)s failed" % vars(self)
 
 
160
        self.checker_callback_tag = None
 
250
161
    def start_checker(self):
 
251
162
        """Start a new checker subprocess if one is not running.
 
252
163
        If a checker already exists, leave it running and do
 
254
 
        # The reason for not killing a running checker is that if we
 
255
 
        # did that, then if a checker (for some reason) started
 
256
 
        # running slowly and taking more than 'interval' time, the
 
257
 
        # client would inevitably timeout, since no checker would get
 
258
 
        # a chance to run to completion.  If we instead leave running
 
259
 
        # checkers alone, the checker would have to take more time
 
260
 
        # than 'timeout' for the client to be declared invalid, which
 
261
 
        # is as it should be.
 
262
165
        if self.checker is None:
 
264
 
                command = self.check_command % self.fqdn
 
266
 
                escaped_attrs = dict((key, re.escape(str(val)))
 
268
 
                                     vars(self).iteritems())
 
270
 
                    command = self.check_command % escaped_attrs
 
271
 
                except TypeError, error:
 
272
 
                    logger.critical(u'Could not format string "%s":'
 
273
 
                                    u' %s', self.check_command, error)
 
274
 
                    return True # Try again later
 
276
 
                logger.debug(u"Starting checker %r for %s",
 
 
166
            #print "Starting checker for", self.name
 
278
168
                self.checker = subprocess.\
 
 
169
                               Popen("sleep 1; fping -q -- %s"
 
 
170
                                     % re.escape(self.fqdn),
 
 
171
                                     stdout=subprocess.PIPE,
 
280
172
                                     close_fds=True, shell=True,
 
282
 
                self.checker_callback_tag = gobject.child_watch_add\
 
284
 
                                             self.checker_callback)
 
 
174
                self.checker_callback_tag = gobject.\
 
 
175
                                            child_watch_add(self.checker.pid,
 
285
178
            except subprocess.OSError, error:
 
286
 
                logger.error(u"Failed to start subprocess: %s",
 
 
179
                sys.stderr.write(u"Failed to start subprocess: %s\n"
 
288
181
        # Re-run this periodically if run by gobject.timeout_add
 
290
183
    def stop_checker(self):
 
291
184
        """Force the checker process, if any, to stop."""
 
292
 
        if self.checker_callback_tag:
 
293
 
            gobject.source_remove(self.checker_callback_tag)
 
294
 
            self.checker_callback_tag = None
 
295
185
        if not hasattr(self, "checker") or self.checker is None:
 
297
 
        logger.debug("Stopping checker for %(name)s", vars(self))
 
299
 
            os.kill(self.checker.pid, signal.SIGTERM)
 
301
 
            #if self.checker.poll() is None:
 
302
 
            #    os.kill(self.checker.pid, signal.SIGKILL)
 
303
 
        except OSError, error:
 
304
 
            if error.errno != errno.ESRCH:
 
 
187
        gobject.source_remove(self.checker_callback_tag)
 
 
188
        self.checker_callback_tag = None
 
 
189
        os.kill(self.checker.pid, signal.SIGTERM)
 
 
190
        if self.checker.poll() is None:
 
 
191
            os.kill(self.checker.pid, signal.SIGKILL)
 
306
192
        self.checker = None
 
307
193
    def still_valid(self, now=None):
 
308
194
        """Has the timeout not yet passed for this client?"""
 
 
314
200
            return now < (self.last_seen + self.timeout)
 
317
 
def peer_certificate(session):
 
318
 
    "Return the peer's OpenPGP certificate as a bytestring"
 
319
 
    # If not an OpenPGP certificate...
 
320
 
    if gnutls.library.functions.gnutls_certificate_type_get\
 
321
 
            (session._c_object) \
 
322
 
           != gnutls.library.constants.GNUTLS_CRT_OPENPGP:
 
323
 
        # ...do the normal thing
 
324
 
        return session.peer_certificate
 
325
 
    list_size = ctypes.c_uint()
 
326
 
    cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
 
327
 
        (session._c_object, ctypes.byref(list_size))
 
328
 
    if list_size.value == 0:
 
331
 
    return ctypes.string_at(cert.data, cert.size)
 
334
 
def fingerprint(openpgp):
 
335
 
    "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
 
336
 
    # New empty GnuTLS certificate
 
337
 
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
 
338
 
    gnutls.library.functions.gnutls_openpgp_crt_init\
 
340
 
    # New GnuTLS "datum" with the OpenPGP public key
 
341
 
    datum = gnutls.library.types.gnutls_datum_t\
 
342
 
        (ctypes.cast(ctypes.c_char_p(openpgp),
 
343
 
                     ctypes.POINTER(ctypes.c_ubyte)),
 
344
 
         ctypes.c_uint(len(openpgp)))
 
345
 
    # Import the OpenPGP public key into the certificate
 
346
 
    ret = gnutls.library.functions.gnutls_openpgp_crt_import\
 
349
 
         gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
 
350
 
    # New buffer for the fingerprint
 
351
 
    buffer = ctypes.create_string_buffer(20)
 
352
 
    buffer_length = ctypes.c_size_t()
 
353
 
    # Get the fingerprint from the certificate into the buffer
 
354
 
    gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
 
355
 
        (crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
 
356
 
    # Deinit the certificate
 
357
 
    gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
 
358
 
    # Convert the buffer to a Python bytestring
 
359
 
    fpr = ctypes.string_at(buffer, buffer_length.value)
 
360
 
    # Convert the bytestring to hexadecimal notation
 
361
 
    hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
 
365
203
class tcp_handler(SocketServer.BaseRequestHandler, object):
 
366
204
    """A TCP request handler class.
 
367
205
    Instantiated by IPv6_TCPServer for each request to handle it.
 
368
206
    Note: This will run in its own forked process."""
 
370
207
    def handle(self):
 
371
 
        logger.debug(u"TCP connection from: %s",
 
372
 
                     unicode(self.client_address))
 
373
 
        session = gnutls.connection.ClientSession(self.request,
 
377
 
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
 
378
 
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
380
 
        priority = "SECURE256"
 
382
 
        gnutls.library.functions.gnutls_priority_set_direct\
 
383
 
            (session._c_object, priority, None);
 
 
208
        #print u"TCP request came"
 
 
209
        #print u"Request:", self.request
 
 
210
        #print u"Client Address:", self.client_address
 
 
211
        #print u"Server:", self.server
 
 
212
        session = gnutls.connection.ServerSession(self.request,
 
386
216
            session.handshake()
 
387
217
        except gnutls.errors.GNUTLSError, error:
 
388
 
            logger.debug(u"Handshake failed: %s", error)
 
 
218
            #sys.stderr.write(u"Handshake failed: %s\n" % error)
 
389
219
            # Do not run session.bye() here: the session is not
 
390
220
            # established.  Just abandon the request.
 
 
222
        #if session.peer_certificate:
 
 
223
        #    print "DN:", session.peer_certificate.subject
 
393
 
            fpr = fingerprint(peer_certificate(session))
 
394
 
        except (TypeError, gnutls.errors.GNUTLSError), error:
 
395
 
            logger.debug(u"Bad certificate: %s", error)
 
 
225
            session.verify_peer()
 
 
226
        except gnutls.errors.CertificateError, error:
 
 
227
            #sys.stderr.write(u"Verify failed: %s\n" % error)
 
398
 
        logger.debug(u"Fingerprint: %s", fpr)
 
400
 
        for c in self.server.clients:
 
401
 
            if c.fingerprint == fpr:
 
 
232
            if c.dn == session.peer_certificate.subject:
 
404
235
        # Have to check if client.still_valid(), since it is possible
 
405
236
        # that the client timed out while establishing the GnuTLS
 
407
 
        if (not client) or (not client.still_valid()):
 
409
 
                logger.debug(u"Client %(name)s is invalid",
 
412
 
                logger.debug(u"Client not found for fingerprint: %s",
 
417
 
        while sent_size < len(client.secret):
 
418
 
            sent = session.send(client.secret[sent_size:])
 
419
 
            logger.debug(u"Sent: %d, remaining: %d",
 
420
 
                         sent, len(client.secret)
 
421
 
                         - (sent_size + sent))
 
 
238
        if client and client.still_valid():
 
 
239
            session.send(client.password)
 
 
242
            #    sys.stderr.write(u"Client %(name)s is invalid\n"
 
 
245
            #    sys.stderr.write(u"Client not found for DN: %s\n"
 
 
246
            #                     % session.peer_certificate.subject)
 
 
247
            #session.send("gazonk")
 
 
539
369
def server_state_changed(state):
 
540
 
    """Derived from the Avahi example code"""
 
 
370
    """From the Avahi server example code"""
 
541
371
    if state == avahi.SERVER_COLLISION:
 
542
 
        logger.warning(u"Server name collision")
 
 
372
        print "WARNING: Server name collision"
 
544
374
    elif state == avahi.SERVER_RUNNING:
 
548
378
def entry_group_state_changed(state, error):
 
549
 
    """Derived from the Avahi example code"""
 
 
379
    """From the Avahi server example code"""
 
550
380
    global serviceName, server, rename_count
 
552
 
    logger.debug(u"state change: %i", state)
 
 
382
    # print "state change: %i" % state
 
554
384
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
 
555
 
        logger.debug(u"Service established.")
 
 
386
        # print "Service established."
 
556
387
    elif state == avahi.ENTRY_GROUP_COLLISION:
 
558
389
        rename_count = rename_count - 1
 
559
390
        if rename_count > 0:
 
560
391
            name = server.GetAlternativeServiceName(name)
 
561
 
            logger.warning(u"Service name collision, "
 
562
 
                           u"changing name to '%s' ...", name)
 
 
392
            print "WARNING: Service name collision, changing name to '%s' ..." % name
 
567
 
            logger.error(u"No suitable service name found after %i"
 
568
 
                         u" retries, exiting.", n_rename)
 
 
397
            print "ERROR: No suitable service name found after %i retries, exiting." % n_rename
 
570
399
    elif state == avahi.ENTRY_GROUP_FAILURE:
 
571
 
        logger.error(u"Error in group state changed %s",
 
 
400
        print "Error in group state changed", error
 
576
405
def if_nametoindex(interface):
 
577
406
    """Call the C function if_nametoindex()"""
 
 
408
        if "ctypes" not in sys.modules:
 
579
410
        libc = ctypes.cdll.LoadLibrary("libc.so.6")
 
580
411
        return libc.if_nametoindex(interface)
 
581
 
    except (OSError, AttributeError):
 
 
412
    except (ImportError, OSError, AttributeError):
 
582
413
        if "struct" not in sys.modules:
 
584
415
        if "fcntl" not in sys.modules:
 
 
592
423
        return interface_index
 
595
 
def daemon(nochdir, noclose):
 
596
 
    """See daemon(3).  Standard BSD Unix function.
 
597
 
    This should really exist as os.daemon, but it doesn't (yet)."""
 
604
 
        # Close all standard open file descriptors
 
605
 
        null = os.open("/dev/null", os.O_NOCTTY | os.O_RDWR)
 
606
 
        if not stat.S_ISCHR(os.fstat(null).st_mode):
 
607
 
            raise OSError(errno.ENODEV,
 
608
 
                          "/dev/null not a character device")
 
609
 
        os.dup2(null, sys.stdin.fileno())
 
610
 
        os.dup2(null, sys.stdout.fileno())
 
611
 
        os.dup2(null, sys.stderr.fileno())
 
616
 
def killme(status = 0):
 
617
 
    logger.debug("Stopping server with exit status %d", status)
 
619
 
    if main_loop_started:
 
628
 
    global main_loop_started
 
629
 
    main_loop_started = False
 
 
426
if __name__ == '__main__':
 
631
427
    parser = OptionParser()
 
632
428
    parser.add_option("-i", "--interface", type="string",
 
633
429
                      default=None, metavar="IF",
 
634
430
                      help="Bind to interface IF")
 
635
 
    parser.add_option("-a", "--address", type="string", default=None,
 
636
 
                      help="Address to listen for requests on")
 
 
431
    parser.add_option("--cert", type="string", default="cert.pem",
 
 
433
                      help="Public key certificate PEM file to use")
 
 
434
    parser.add_option("--key", type="string", default="key.pem",
 
 
436
                      help="Private key PEM file to use")
 
 
437
    parser.add_option("--ca", type="string", default="ca.pem",
 
 
439
                      help="Certificate Authority certificate PEM file to use")
 
 
440
    parser.add_option("--crl", type="string", default="crl.pem",
 
 
442
                      help="Certificate Revokation List PEM file to use")
 
637
443
    parser.add_option("-p", "--port", type="int", default=None,
 
638
444
                      help="Port number to receive requests on")
 
639
445
    parser.add_option("--timeout", type="string", # Parsed later
 
 
663
467
    except ValueError:
 
664
468
        parser.error("option --interval: Unparseable time")
 
 
470
    cert = gnutls.crypto.X509Certificate(open(options.cert).read())
 
 
471
    key = gnutls.crypto.X509PrivateKey(open(options.key).read())
 
 
472
    ca = gnutls.crypto.X509Certificate(open(options.ca).read())
 
 
473
    crl = gnutls.crypto.X509CRL(open(options.crl).read())
 
 
474
    cred = gnutls.connection.X509Credentials(cert, key, [ca], [crl])
 
666
476
    # Parse config file
 
667
 
    defaults = { "checker": "fping -q -- %%(fqdn)s" }
 
668
478
    client_config = ConfigParser.SafeConfigParser(defaults)
 
669
 
    #client_config.readfp(open("global.conf"), "global.conf")
 
 
479
    #client_config.readfp(open("secrets.conf"), "secrets.conf")
 
670
480
    client_config.read("mandos-clients.conf")
 
675
 
    # From the Avahi example code
 
 
482
    # From the Avahi server example code
 
676
483
    DBusGMainLoop(set_as_default=True )
 
677
484
    main_loop = gobject.MainLoop()
 
678
485
    bus = dbus.SystemBus()
 
 
681
488
            avahi.DBUS_INTERFACE_SERVER )
 
682
489
    # End of Avahi example code
 
684
 
    debug = options.debug
 
687
 
        console = logging.StreamHandler()
 
688
 
        # console.setLevel(logging.DEBUG)
 
689
 
        console.setFormatter(logging.Formatter\
 
690
 
                             ('%(levelname)s: %(message)s'))
 
691
 
        logger.addHandler(console)
 
695
492
    def remove_from_clients(client):
 
696
493
        clients.remove(client)
 
698
 
            logger.debug(u"No clients left, exiting")
 
 
495
            print "No clients left, exiting"
 
701
498
    clients.update(Set(Client(name=section, options=options,
 
702
499
                              stop_hook = remove_from_clients,
 
703
500
                              **(dict(client_config\
 
704
501
                                      .items(section))))
 
705
502
                       for section in client_config.sections()))
 
711
 
        "Cleanup function; run on exit"
 
713
 
        # From the Avahi example code
 
714
 
        if not group is None:
 
717
 
        # End of Avahi example code
 
720
 
            client = clients.pop()
 
721
 
            client.stop_hook = None
 
724
 
    atexit.register(cleanup)
 
727
 
        signal.signal(signal.SIGINT, signal.SIG_IGN)
 
728
 
    signal.signal(signal.SIGHUP, lambda signum, frame: killme())
 
729
 
    signal.signal(signal.SIGTERM, lambda signum, frame: killme())
 
731
503
    for client in clients:
 
734
 
    tcp_server = IPv6_TCPServer((options.address, options.port),
 
 
506
    tcp_server = IPv6_TCPServer((None, options.port),
 
738
511
    # Find out what random port we got
 
740
512
    servicePort = tcp_server.socket.getsockname()[1]
 
741
 
    logger.debug(u"Now listening on port %d", servicePort)
 
 
513
    #sys.stderr.write("Now listening on port %d\n" % servicePort)
 
743
515
    if options.interface is not None:
 
744
 
        global serviceInterface
 
745
516
        serviceInterface = if_nametoindex(options.interface)
 
747
 
    # From the Avahi example code
 
 
518
    # From the Avahi server example code
 
748
519
    server.connect_to_signal("StateChanged", server_state_changed)
 
750
 
        server_state_changed(server.GetState())
 
751
 
    except dbus.exceptions.DBusException, error:
 
752
 
        logger.critical(u"DBusException: %s", error)
 
 
520
    server_state_changed(server.GetState())
 
754
521
    # End of Avahi example code
 
756
523
    gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,