/mandos/release

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/release

« back to all changes in this revision

Viewing changes to server.py

  • Committer: Teddy Hogeborn
  • Date: 2008-06-30 01:43:39 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080630014339-rsuztydpl2w5ml83
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
             old broadcast-UDP-to-port-49001 method.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python
2
 
# -*- mode: python; coding: utf-8 -*-
3
 
4
 
# Mandos server - give out binary blobs to connecting clients.
5
 
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
 
# methods "add" and "remove" in the "AvahiService" class, the
10
 
# "server_state_changed" and "entry_group_state_changed" functions,
11
 
# and some lines in "main".
12
 
13
 
# Everything else is
14
 
# Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson
15
 
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.
20
 
#
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.
25
 
26
 
# You should have received a copy of the GNU General Public License
27
 
# along with this program.  If not, see
28
 
# <http://www.gnu.org/licenses/>.
29
 
30
 
# Contact the authors at <mandos@fukt.bsnet.se>.
31
 
32
2
 
33
3
from __future__ import division
34
4
 
41
11
import gnutls.crypto
42
12
import gnutls.connection
43
13
import gnutls.errors
44
 
import gnutls.library.functions
45
 
import gnutls.library.constants
46
 
import gnutls.library.types
47
14
import ConfigParser
48
15
import sys
49
16
import re
51
18
import signal
52
19
from sets import Set
53
20
import subprocess
54
 
import atexit
55
 
import stat
56
 
import logging
57
 
import logging.handlers
58
21
 
59
22
import dbus
60
23
import gobject
61
24
import avahi
62
25
from dbus.mainloop.glib import DBusGMainLoop
63
 
import ctypes
64
 
 
65
 
version = "1.0"
66
 
 
67
 
logger = logging.Logger('mandos')
68
 
syslogger = logging.handlers.SysLogHandler\
69
 
            (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
70
 
             address = "/dev/log")
71
 
syslogger.setFormatter(logging.Formatter\
72
 
                        ('Mandos: %(levelname)s: %(message)s'))
73
 
logger.addHandler(syslogger)
74
 
 
75
 
console = logging.StreamHandler()
76
 
console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
77
 
                                       ' %(message)s'))
78
 
logger.addHandler(console)
79
 
 
80
 
class AvahiError(Exception):
81
 
    def __init__(self, value):
82
 
        self.value = value
83
 
    def __str__(self):
84
 
        return repr(self.value)
85
 
 
86
 
class AvahiServiceError(AvahiError):
87
 
    pass
88
 
 
89
 
class AvahiGroupError(AvahiError):
90
 
    pass
91
 
 
92
 
 
93
 
class AvahiService(object):
94
 
    """An Avahi (Zeroconf) service.
95
 
    Attributes:
96
 
    interface: integer; avahi.IF_UNSPEC or an interface index.
97
 
               Used to optionally bind to the specified interface.
98
 
    name: string; Example: 'Mandos'
99
 
    type: string; Example: '_mandos._tcp'.
100
 
                  See <http://www.dns-sd.org/ServiceTypes.html>
101
 
    port: integer; what port to announce
102
 
    TXT: list of strings; TXT record for the service
103
 
    domain: string; Domain to publish on, default to .local if empty.
104
 
    host: string; Host to publish records for, default is localhost
105
 
    max_renames: integer; maximum number of renames
106
 
    rename_count: integer; counter so we only rename after collisions
107
 
                  a sensible number of times
108
 
    """
109
 
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
110
 
                 type = None, port = None, TXT = None, domain = "",
111
 
                 host = "", max_renames = 32768):
112
 
        self.interface = interface
113
 
        self.name = name
114
 
        self.type = type
115
 
        self.port = port
116
 
        if TXT is None:
117
 
            self.TXT = []
118
 
        else:
119
 
            self.TXT = TXT
120
 
        self.domain = domain
121
 
        self.host = host
122
 
        self.rename_count = 0
123
 
        self.max_renames = max_renames
124
 
    def rename(self):
125
 
        """Derived from the Avahi example code"""
126
 
        if self.rename_count >= self.max_renames:
127
 
            logger.critical(u"No suitable Zeroconf service name found"
128
 
                            u" after %i retries, exiting.",
129
 
                            rename_count)
130
 
            raise AvahiServiceError("Too many renames")
131
 
        self.name = server.GetAlternativeServiceName(self.name)
132
 
        logger.info(u"Changing Zeroconf service name to %r ...",
133
 
                    str(self.name))
134
 
        syslogger.setFormatter(logging.Formatter\
135
 
                               ('Mandos (%s): %%(levelname)s:'
136
 
                               ' %%(message)s' % self.name))
137
 
        self.remove()
138
 
        self.add()
139
 
        self.rename_count += 1
140
 
    def remove(self):
141
 
        """Derived from the Avahi example code"""
142
 
        if group is not None:
143
 
            group.Reset()
144
 
    def add(self):
145
 
        """Derived from the Avahi example code"""
146
 
        global group
147
 
        if group is None:
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 Zeroconf service '%s' of type '%s' ...",
155
 
                     service.name, service.type)
156
 
        group.AddService(
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))
164
 
        group.Commit()
165
 
 
166
 
# From the Avahi example code:
167
 
group = None                            # our entry group
 
26
 
 
27
# This variable is used to optionally bind to a specified
 
28
# interface.
 
29
serviceInterface = avahi.IF_UNSPEC
 
30
# It is a global variable to fit in with the rest of the
 
31
# variables from the Avahi server example code:
 
32
serviceName = "Mandos"
 
33
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
 
34
servicePort = None                      # Not known at startup
 
35
serviceTXT = []                         # TXT record for the service
 
36
domain = ""                  # Domain to publish on, default to .local
 
37
host = ""          # Host to publish records for, default to localhost
 
38
group = None #our entry group
 
39
rename_count = 12       # Counter so we only rename after collisions a
 
40
                        # sensible number of times
168
41
# End of Avahi example code
169
42
 
170
43
 
171
44
class Client(object):
172
45
    """A representation of a client host served by this server.
173
46
    Attributes:
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
 
    host:      string; 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
 
47
    password:  string
 
48
    fqdn:      string, FQDN (used by the checker)
 
49
    created:   datetime.datetime()
 
50
    last_seen: datetime.datetime() or None if not yet seen
 
51
    timeout:   datetime.timedelta(); How long from last_seen until
 
52
                                     this client is invalid
183
53
    interval:  datetime.timedelta(); How often to start a new checker
 
54
    timeout_milliseconds: Used by gobject.timeout_add()
 
55
    interval_milliseconds: - '' -
184
56
    stop_hook: If set, called by stop() as stop_hook(self)
185
57
    checker:   subprocess.Popen(); a running checker process used
186
58
                                   to see if the client lives.
187
 
                                   'None' if no process is running.
 
59
                                   Is None if no process is running.
188
60
    checker_initiator_tag: a gobject event source tag, or None
189
61
    stop_initiator_tag:    - '' -
190
62
    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.
195
 
    Private attibutes:
196
 
    _timeout: Real variable for 'timeout'
197
 
    _interval: Real variable for 'interval'
198
 
    _timeout_milliseconds: Used when calling gobject.timeout_add()
199
 
    _interval_milliseconds: - '' -
200
63
    """
201
 
    def _set_timeout(self, timeout):
202
 
        "Setter function for 'timeout' attribute"
203
 
        self._timeout = timeout
204
 
        self._timeout_milliseconds = ((self.timeout.days
 
64
    def __init__(self, name=None, options=None, stop_hook=None,
 
65
                 dn=None, password=None, passfile=None, fqdn=None,
 
66
                 timeout=None, interval=-1):
 
67
        self.name = name
 
68
        self.dn = dn
 
69
        if password:
 
70
            self.password = password
 
71
        elif passfile:
 
72
            self.password = open(passfile).readall()
 
73
        else:
 
74
            raise RuntimeError(u"No Password or Passfile for client %s"
 
75
                               % self.name)
 
76
        self.fqdn = fqdn                # string
 
77
        self.created = datetime.datetime.now()
 
78
        self.last_seen = None
 
79
        if timeout is None:
 
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
 
86
                                        // 1000))
 
87
        if interval == -1:
 
88
            interval = options.interval
 
89
        else:
 
90
            interval = string_to_delta(interval)
 
91
        self.interval = interval
 
92
        self.interval_milliseconds = ((self.interval.days
205
93
                                       * 24 * 60 * 60 * 1000)
206
 
                                      + (self.timeout.seconds * 1000)
207
 
                                      + (self.timeout.microseconds
 
94
                                      + (self.interval.seconds * 1000)
 
95
                                      + (self.interval.microseconds
208
96
                                         // 1000))
209
 
    timeout = property(lambda self: self._timeout,
210
 
                       _set_timeout)
211
 
    del _set_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
218
 
                                          * 1000)
219
 
                                       + (self.interval.microseconds
220
 
                                          // 1000))
221
 
    interval = property(lambda self: self._interval,
222
 
                        _set_interval)
223
 
    del _set_interval
224
 
    def __init__(self, name = None, stop_hook=None, config={}):
225
 
        """Note: the 'checker' key in 'config' sets the
226
 
        'checker_command' attribute and *not* the 'checker'
227
 
        attribute."""
228
 
        self.name = name
229
 
        logger.debug(u"Creating client %r", self.name)
230
 
        # Uppercase and remove spaces from fingerprint for later
231
 
        # comparison purposes with return value from the fingerprint()
232
 
        # function
233
 
        self.fingerprint = config["fingerprint"].upper()\
234
 
                           .replace(u" ", u"")
235
 
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
236
 
        if "secret" in config:
237
 
            self.secret = config["secret"].decode(u"base64")
238
 
        elif "secfile" in config:
239
 
            sf = open(config["secfile"])
240
 
            self.secret = sf.read()
241
 
            sf.close()
242
 
        else:
243
 
            raise TypeError(u"No secret or secfile for client %s"
244
 
                            % self.name)
245
 
        self.host = config.get("host", "")
246
 
        self.created = datetime.datetime.now()
247
 
        self.last_checked_ok = None
248
 
        self.timeout = string_to_delta(config["timeout"])
249
 
        self.interval = string_to_delta(config["interval"])
250
97
        self.stop_hook = stop_hook
251
98
        self.checker = None
252
99
        self.checker_initiator_tag = None
253
100
        self.stop_initiator_tag = None
254
101
        self.checker_callback_tag = None
255
 
        self.check_command = config["checker"]
256
102
    def start(self):
257
 
        """Start this client's checker and timeout hooks"""
 
103
        """Start this clients checker and timeout hooks"""
258
104
        # Schedule a new checker to be started an 'interval' from now,
259
105
        # and every interval from then on.
260
 
        self.checker_initiator_tag = gobject.timeout_add\
261
 
                                     (self._interval_milliseconds,
262
 
                                      self.start_checker)
 
106
        self.checker_initiator_tag = gobject.\
 
107
                                     timeout_add(self.interval_milliseconds,
 
108
                                                 self.start_checker)
263
109
        # Also start a new checker *right now*.
264
110
        self.start_checker()
265
111
        # Schedule a stop() when 'timeout' has passed
266
 
        self.stop_initiator_tag = gobject.timeout_add\
267
 
                                  (self._timeout_milliseconds,
268
 
                                   self.stop)
 
112
        self.stop_initiator_tag = gobject.\
 
113
                                     timeout_add(self.timeout_milliseconds,
 
114
                                                 self.stop)
269
115
    def stop(self):
270
116
        """Stop this client.
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.
274
 
        if hasattr(self, "secret") and self.secret:
275
 
            logger.info(u"Stopping client %s", self.name)
276
 
            self.secret = None
277
 
        else:
278
 
            return False
279
 
        if getattr(self, "stop_initiator_tag", False):
 
117
        The possibility that this client might be restarted is left
 
118
        open, but not currently used."""
 
119
        # print "Stopping client", self.name
 
120
        self.password = None
 
121
        if self.stop_initiator_tag:
280
122
            gobject.source_remove(self.stop_initiator_tag)
281
123
            self.stop_initiator_tag = None
282
 
        if getattr(self, "checker_initiator_tag", False):
 
124
        if self.checker_initiator_tag:
283
125
            gobject.source_remove(self.checker_initiator_tag)
284
126
            self.checker_initiator_tag = None
285
127
        self.stop_checker()
288
130
        # Do not run this again if called by a gobject.timeout_add
289
131
        return False
290
132
    def __del__(self):
291
 
        self.stop_hook = None
292
 
        self.stop()
 
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
 
142
        self.stop_checker()
293
143
    def checker_callback(self, pid, condition):
294
144
        """The checker has completed, so take appropriate actions."""
295
145
        now = datetime.datetime.now()
296
 
        self.checker_callback_tag = None
297
 
        self.checker = None
298
146
        if os.WIFEXITED(condition) \
299
147
               and (os.WEXITSTATUS(condition) == 0):
300
 
            logger.info(u"Checker for %(name)s succeeded",
301
 
                        vars(self))
302
 
            self.last_checked_ok = now
 
148
            #print "Checker for %(name)s succeeded" % vars(self)
 
149
            self.last_seen = now
303
150
            gobject.source_remove(self.stop_initiator_tag)
304
 
            self.stop_initiator_tag = gobject.timeout_add\
305
 
                                      (self._timeout_milliseconds,
306
 
                                       self.stop)
307
 
        elif not os.WIFEXITED(condition):
308
 
            logger.warning(u"Checker for %(name)s crashed?",
309
 
                           vars(self))
310
 
        else:
311
 
            logger.info(u"Checker for %(name)s failed",
312
 
                        vars(self))
 
151
            self.stop_initiator_tag = gobject.\
 
152
                                      timeout_add(self.timeout_milliseconds,
 
153
                                                  self.stop)
 
154
        #else:
 
155
        #    if not os.WIFEXITED(condition):
 
156
        #        print "Checker for %(name)s crashed?" % vars(self)
 
157
        #    else:
 
158
        #        print "Checker for %(name)s failed" % vars(self)
 
159
        self.checker = None
 
160
        self.checker_callback_tag = None
313
161
    def start_checker(self):
314
162
        """Start a new checker subprocess if one is not running.
315
163
        If a checker already exists, leave it running and do
316
164
        nothing."""
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
165
        if self.checker is None:
326
 
            try:
327
 
                # In case check_command has exactly one % operator
328
 
                command = self.check_command % self.host
329
 
            except TypeError:
330
 
                # Escape attributes for the shell
331
 
                escaped_attrs = dict((key, re.escape(str(val)))
332
 
                                     for key, val in
333
 
                                     vars(self).iteritems())
334
 
                try:
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
340
 
            try:
341
 
                logger.info(u"Starting checker %r for %s",
342
 
                            command, self.name)
343
 
                # We don't need to redirect stdout and stderr, since
344
 
                # in normal mode, that is already done by daemon(),
345
 
                # and in debug mode we don't want to.  (Stdin is
346
 
                # always replaced by /dev/null.)
347
 
                self.checker = subprocess.Popen(command,
348
 
                                                close_fds=True,
349
 
                                                shell=True, cwd="/")
350
 
                self.checker_callback_tag = gobject.child_watch_add\
351
 
                                            (self.checker.pid,
352
 
                                             self.checker_callback)
353
 
            except OSError, error:
354
 
                logger.error(u"Failed to start subprocess: %s",
355
 
                             error)
 
166
            #print "Starting checker for", self.name
 
167
            try:
 
168
                self.checker = subprocess.\
 
169
                               Popen("sleep 1; fping -q -- %s"
 
170
                                     % re.escape(self.fqdn),
 
171
                                     stdout=subprocess.PIPE,
 
172
                                     close_fds=True, shell=True,
 
173
                                     cwd="/")
 
174
                self.checker_callback_tag = gobject.\
 
175
                                            child_watch_add(self.checker.pid,
 
176
                                                            self.\
 
177
                                                            checker_callback)
 
178
            except subprocess.OSError, error:
 
179
                sys.stderr.write(u"Failed to start subprocess: %s\n"
 
180
                                 % error)
356
181
        # Re-run this periodically if run by gobject.timeout_add
357
182
        return True
358
183
    def stop_checker(self):
359
184
        """Force the checker process, if any, to stop."""
360
 
        if self.checker_callback_tag:
361
 
            gobject.source_remove(self.checker_callback_tag)
362
 
            self.checker_callback_tag = None
363
 
        if getattr(self, "checker", None) is None:
 
185
        if not hasattr(self, "checker") or self.checker is None:
364
186
            return
365
 
        logger.debug(u"Stopping checker for %(name)s", vars(self))
366
 
        try:
367
 
            os.kill(self.checker.pid, signal.SIGTERM)
368
 
            #os.sleep(0.5)
369
 
            #if self.checker.poll() is None:
370
 
            #    os.kill(self.checker.pid, signal.SIGKILL)
371
 
        except OSError, error:
372
 
            if error.errno != errno.ESRCH: # No such process
373
 
                raise
 
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)
374
192
        self.checker = None
375
 
    def still_valid(self):
 
193
    def still_valid(self, now=None):
376
194
        """Has the timeout not yet passed for this client?"""
377
 
        now = datetime.datetime.now()
378
 
        if self.last_checked_ok is None:
 
195
        if now is None:
 
196
            now = datetime.datetime.now()
 
197
        if self.last_seen is None:
379
198
            return now < (self.created + self.timeout)
380
199
        else:
381
 
            return now < (self.last_checked_ok + self.timeout)
382
 
 
383
 
 
384
 
def peer_certificate(session):
385
 
    "Return the peer's OpenPGP certificate as a bytestring"
386
 
    # If not an OpenPGP certificate...
387
 
    if gnutls.library.functions.gnutls_certificate_type_get\
388
 
            (session._c_object) \
389
 
           != gnutls.library.constants.GNUTLS_CRT_OPENPGP:
390
 
        # ...do the normal thing
391
 
        return session.peer_certificate
392
 
    list_size = ctypes.c_uint()
393
 
    cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
394
 
        (session._c_object, ctypes.byref(list_size))
395
 
    if list_size.value == 0:
396
 
        return None
397
 
    cert = cert_list[0]
398
 
    return ctypes.string_at(cert.data, cert.size)
399
 
 
400
 
 
401
 
def fingerprint(openpgp):
402
 
    "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
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
 
    # New empty GnuTLS certificate
409
 
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
410
 
    gnutls.library.functions.gnutls_openpgp_crt_init\
411
 
        (ctypes.byref(crt))
412
 
    # Import the OpenPGP public key into the certificate
413
 
    gnutls.library.functions.gnutls_openpgp_crt_import\
414
 
                    (crt, ctypes.byref(datum),
415
 
                     gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
416
 
    # Verify the self signature in the key
417
 
    crtverify = ctypes.c_uint();
418
 
    gnutls.library.functions.gnutls_openpgp_crt_verify_self\
419
 
        (crt, 0, ctypes.byref(crtverify))
420
 
    if crtverify.value != 0:
421
 
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
422
 
        raise gnutls.errors.CertificateSecurityError("Verify failed")
423
 
    # New buffer for the fingerprint
424
 
    buffer = ctypes.create_string_buffer(20)
425
 
    buffer_length = ctypes.c_size_t()
426
 
    # Get the fingerprint from the certificate into the buffer
427
 
    gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
428
 
        (crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
429
 
    # Deinit the certificate
430
 
    gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
431
 
    # Convert the buffer to a Python bytestring
432
 
    fpr = ctypes.string_at(buffer, buffer_length.value)
433
 
    # Convert the bytestring to hexadecimal notation
434
 
    hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
435
 
    return hex_fpr
 
200
            return now < (self.last_seen + self.timeout)
436
201
 
437
202
 
438
203
class tcp_handler(SocketServer.BaseRequestHandler, object):
439
204
    """A TCP request handler class.
440
205
    Instantiated by IPv6_TCPServer for each request to handle it.
441
206
    Note: This will run in its own forked process."""
442
 
    
443
207
    def handle(self):
444
 
        logger.info(u"TCP connection from: %s",
445
 
                     unicode(self.client_address))
446
 
        session = gnutls.connection.ClientSession\
447
 
                  (self.request, gnutls.connection.X509Credentials())
448
 
        
449
 
        line = self.request.makefile().readline()
450
 
        logger.debug(u"Protocol version: %r", line)
451
 
        try:
452
 
            if int(line.strip().split()[0]) > 1:
453
 
                raise RuntimeError
454
 
        except (ValueError, IndexError, RuntimeError), error:
455
 
            logger.error(u"Unknown protocol version: %s", error)
456
 
            return
457
 
        
458
 
        # Note: gnutls.connection.X509Credentials is really a generic
459
 
        # GnuTLS certificate credentials object so long as no X.509
460
 
        # keys are added to it.  Therefore, we can use it here despite
461
 
        # using OpenPGP certificates.
462
 
        
463
 
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
464
 
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
465
 
        #                "+DHE-DSS"))
466
 
        priority = "NORMAL"             # Fallback default, since this
467
 
                                        # MUST be set.
468
 
        if self.server.settings["priority"]:
469
 
            priority = self.server.settings["priority"]
470
 
        gnutls.library.functions.gnutls_priority_set_direct\
471
 
            (session._c_object, priority, None);
472
 
        
 
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,
 
213
                                                  self.server\
 
214
                                                  .credentials)
473
215
        try:
474
216
            session.handshake()
475
217
        except gnutls.errors.GNUTLSError, error:
476
 
            logger.warning(u"Handshake failed: %s", error)
 
218
            #sys.stderr.write(u"Handshake failed: %s\n" % error)
477
219
            # Do not run session.bye() here: the session is not
478
220
            # established.  Just abandon the request.
479
221
            return
 
222
        #if session.peer_certificate:
 
223
        #    print "DN:", session.peer_certificate.subject
480
224
        try:
481
 
            fpr = fingerprint(peer_certificate(session))
482
 
        except (TypeError, gnutls.errors.GNUTLSError), error:
483
 
            logger.warning(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)
484
228
            session.bye()
485
229
            return
486
 
        logger.debug(u"Fingerprint: %s", fpr)
487
230
        client = None
488
 
        for c in self.server.clients:
489
 
            if c.fingerprint == fpr:
 
231
        for c in clients:
 
232
            if c.dn == session.peer_certificate.subject:
490
233
                client = c
491
234
                break
492
 
        if not client:
493
 
            logger.warning(u"Client not found for fingerprint: %s",
494
 
                           fpr)
495
 
            session.bye()
496
 
            return
497
235
        # Have to check if client.still_valid(), since it is possible
498
236
        # that the client timed out while establishing the GnuTLS
499
237
        # session.
500
 
        if not client.still_valid():
501
 
            logger.warning(u"Client %(name)s is invalid",
502
 
                           vars(client))
503
 
            session.bye()
504
 
            return
505
 
        sent_size = 0
506
 
        while sent_size < len(client.secret):
507
 
            sent = session.send(client.secret[sent_size:])
508
 
            logger.debug(u"Sent: %d, remaining: %d",
509
 
                         sent, len(client.secret)
510
 
                         - (sent_size + sent))
511
 
            sent_size += sent
 
238
        if client and client.still_valid():
 
239
            session.send(client.password)
 
240
        else:
 
241
            #if client:
 
242
            #    sys.stderr.write(u"Client %(name)s is invalid\n"
 
243
            #                     % vars(client))
 
244
            #else:
 
245
            #    sys.stderr.write(u"Client not found for DN: %s\n"
 
246
            #                     % session.peer_certificate.subject)
 
247
            #session.send("gazonk")
 
248
            pass
512
249
        session.bye()
513
250
 
514
251
 
515
252
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
516
253
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
517
254
    Attributes:
518
 
        settings:       Server settings
 
255
        options:        Command line options
519
256
        clients:        Set() of Client objects
 
257
        credentials:    GnuTLS X.509 credentials
520
258
    """
521
259
    address_family = socket.AF_INET6
522
260
    def __init__(self, *args, **kwargs):
523
 
        if "settings" in kwargs:
524
 
            self.settings = kwargs["settings"]
525
 
            del kwargs["settings"]
 
261
        if "options" in kwargs:
 
262
            self.options = kwargs["options"]
 
263
            del kwargs["options"]
526
264
        if "clients" in kwargs:
527
265
            self.clients = kwargs["clients"]
528
266
            del kwargs["clients"]
 
267
        if "credentials" in kwargs:
 
268
            self.credentials = kwargs["credentials"]
 
269
            del kwargs["credentials"]
529
270
        return super(type(self), self).__init__(*args, **kwargs)
530
271
    def server_bind(self):
531
272
        """This overrides the normal server_bind() function
532
273
        to bind to an interface if one was specified, and also NOT to
533
274
        bind to an address or port if they were not specified."""
534
 
        if self.settings["interface"]:
535
 
            # 25 is from /usr/include/asm-i486/socket.h
536
 
            SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
 
275
        if self.options.interface:
 
276
            if not hasattr(socket, "SO_BINDTODEVICE"):
 
277
                # From /usr/include/asm-i486/socket.h
 
278
                socket.SO_BINDTODEVICE = 25
537
279
            try:
538
280
                self.socket.setsockopt(socket.SOL_SOCKET,
539
 
                                       SO_BINDTODEVICE,
540
 
                                       self.settings["interface"])
 
281
                                       socket.SO_BINDTODEVICE,
 
282
                                       self.options.interface)
541
283
            except socket.error, error:
542
284
                if error[0] == errno.EPERM:
543
 
                    logger.error(u"No permission to"
544
 
                                 u" bind to interface %s",
545
 
                                 self.settings["interface"])
 
285
                    sys.stderr.write(u"Warning: No permission to bind to interface %s\n"
 
286
                                     % self.options.interface)
546
287
                else:
547
288
                    raise error
548
289
        # Only bind(2) the socket if we really need to.
551
292
                in6addr_any = "::"
552
293
                self.server_address = (in6addr_any,
553
294
                                       self.server_address[1])
554
 
            elif not self.server_address[1]:
 
295
            elif self.server_address[1] is None:
555
296
                self.server_address = (self.server_address[0],
556
297
                                       0)
557
 
#                 if self.settings["interface"]:
558
 
#                     self.server_address = (self.server_address[0],
559
 
#                                            0, # port
560
 
#                                            0, # flowinfo
561
 
#                                            if_nametoindex
562
 
#                                            (self.settings
563
 
#                                             ["interface"]))
564
298
            return super(type(self), self).server_bind()
565
299
 
566
300
 
577
311
    datetime.timedelta(1)
578
312
    >>> string_to_delta(u'1w')
579
313
    datetime.timedelta(7)
580
 
    >>> string_to_delta('5m 30s')
581
 
    datetime.timedelta(0, 330)
582
314
    """
583
 
    timevalue = datetime.timedelta(0)
584
 
    for s in interval.split():
585
 
        try:
586
 
            suffix=unicode(s[-1])
587
 
            value=int(s[:-1])
588
 
            if suffix == u"d":
589
 
                delta = datetime.timedelta(value)
590
 
            elif suffix == u"s":
591
 
                delta = datetime.timedelta(0, value)
592
 
            elif suffix == u"m":
593
 
                delta = datetime.timedelta(0, 0, 0, 0, value)
594
 
            elif suffix == u"h":
595
 
                delta = datetime.timedelta(0, 0, 0, 0, 0, value)
596
 
            elif suffix == u"w":
597
 
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
598
 
            else:
599
 
                raise ValueError
600
 
        except (ValueError, IndexError):
 
315
    try:
 
316
        suffix=unicode(interval[-1])
 
317
        value=int(interval[:-1])
 
318
        if suffix == u"d":
 
319
            delta = datetime.timedelta(value)
 
320
        elif suffix == u"s":
 
321
            delta = datetime.timedelta(0, value)
 
322
        elif suffix == u"m":
 
323
            delta = datetime.timedelta(0, 0, 0, 0, value)
 
324
        elif suffix == u"h":
 
325
            delta = datetime.timedelta(0, 0, 0, 0, 0, value)
 
326
        elif suffix == u"w":
 
327
            delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
 
328
        else:
601
329
            raise ValueError
602
 
        timevalue += delta
603
 
    return timevalue
 
330
    except (ValueError, IndexError):
 
331
        raise ValueError
 
332
    return delta
 
333
 
 
334
 
 
335
def add_service():
 
336
    """From the Avahi server example code"""
 
337
    global group, serviceName, serviceType, servicePort, serviceTXT, \
 
338
           domain, host
 
339
    if group is None:
 
340
        group = dbus.Interface(
 
341
                bus.get_object( avahi.DBUS_NAME,
 
342
                                server.EntryGroupNew()),
 
343
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
 
344
        group.connect_to_signal('StateChanged',
 
345
                                entry_group_state_changed)
 
346
    
 
347
    # print "Adding service '%s' of type '%s' ..." % (serviceName,
 
348
    #                                                 serviceType)
 
349
    
 
350
    group.AddService(
 
351
            serviceInterface,           # interface
 
352
            avahi.PROTO_INET6,          # protocol
 
353
            dbus.UInt32(0),             # flags
 
354
            serviceName, serviceType,
 
355
            domain, host,
 
356
            dbus.UInt16(servicePort),
 
357
            avahi.string_array_to_txt_array(serviceTXT))
 
358
    group.Commit()
 
359
 
 
360
 
 
361
def remove_service():
 
362
    """From the Avahi server example code"""
 
363
    global group
 
364
    
 
365
    if not group is None:
 
366
        group.Reset()
604
367
 
605
368
 
606
369
def server_state_changed(state):
607
 
    """Derived from the Avahi example code"""
 
370
    """From the Avahi server example code"""
608
371
    if state == avahi.SERVER_COLLISION:
609
 
        logger.error(u"Zeroconf server name collision")
610
 
        service.remove()
 
372
        print "WARNING: Server name collision"
 
373
        remove_service()
611
374
    elif state == avahi.SERVER_RUNNING:
612
 
        service.add()
 
375
        add_service()
613
376
 
614
377
 
615
378
def entry_group_state_changed(state, error):
616
 
    """Derived from the Avahi example code"""
617
 
    logger.debug(u"Avahi state change: %i", state)
 
379
    """From the Avahi server example code"""
 
380
    global serviceName, server, rename_count
 
381
    
 
382
    # print "state change: %i" % state
618
383
    
619
384
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
620
 
        logger.debug(u"Zeroconf service established.")
 
385
        pass
 
386
        # print "Service established."
621
387
    elif state == avahi.ENTRY_GROUP_COLLISION:
622
 
        logger.warning(u"Zeroconf service name collision.")
623
 
        service.rename()
 
388
        
 
389
        rename_count = rename_count - 1
 
390
        if rename_count > 0:
 
391
            name = server.GetAlternativeServiceName(name)
 
392
            print "WARNING: Service name collision, changing name to '%s' ..." % name
 
393
            remove_service()
 
394
            add_service()
 
395
            
 
396
        else:
 
397
            print "ERROR: No suitable service name found after %i retries, exiting." % n_rename
 
398
            main_loop.quit()
624
399
    elif state == avahi.ENTRY_GROUP_FAILURE:
625
 
        logger.critical(u"Avahi: Error in group state changed %s",
626
 
                        unicode(error))
627
 
        raise AvahiGroupError("State changed: %s", str(error))
 
400
        print "Error in group state changed", error
 
401
        main_loop.quit()
 
402
        return
 
403
 
628
404
 
629
405
def if_nametoindex(interface):
630
 
    """Call the C function if_nametoindex(), or equivalent"""
631
 
    global if_nametoindex
 
406
    """Call the C function if_nametoindex()"""
632
407
    try:
633
 
        if "ctypes.util" not in sys.modules:
634
 
            import ctypes.util
635
 
        if_nametoindex = ctypes.cdll.LoadLibrary\
636
 
            (ctypes.util.find_library("c")).if_nametoindex
637
 
    except (OSError, AttributeError):
 
408
        if "ctypes" not in sys.modules:
 
409
            import ctypes
 
410
        libc = ctypes.cdll.LoadLibrary("libc.so.6")
 
411
        return libc.if_nametoindex(interface)
 
412
    except (ImportError, OSError, AttributeError):
638
413
        if "struct" not in sys.modules:
639
414
            import struct
640
415
        if "fcntl" not in sys.modules:
641
416
            import fcntl
642
 
        def if_nametoindex(interface):
643
 
            "Get an interface index the hard way, i.e. using fcntl()"
644
 
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
645
 
            s = socket.socket()
646
 
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
647
 
                                struct.pack("16s16x", interface))
648
 
            s.close()
649
 
            interface_index = struct.unpack("I", ifreq[16:20])[0]
650
 
            return interface_index
651
 
    return if_nametoindex(interface)
652
 
 
653
 
 
654
 
def daemon(nochdir = False, noclose = False):
655
 
    """See daemon(3).  Standard BSD Unix function.
656
 
    This should really exist as os.daemon, but it doesn't (yet)."""
657
 
    if os.fork():
658
 
        sys.exit()
659
 
    os.setsid()
660
 
    if not nochdir:
661
 
        os.chdir("/")
662
 
    if os.fork():
663
 
        sys.exit()
664
 
    if not noclose:
665
 
        # Close all standard open file descriptors
666
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
667
 
        if not stat.S_ISCHR(os.fstat(null).st_mode):
668
 
            raise OSError(errno.ENODEV,
669
 
                          "/dev/null not a character device")
670
 
        os.dup2(null, sys.stdin.fileno())
671
 
        os.dup2(null, sys.stdout.fileno())
672
 
        os.dup2(null, sys.stderr.fileno())
673
 
        if null > 2:
674
 
            os.close(null)
675
 
 
676
 
 
677
 
def main():
678
 
    global main_loop_started
679
 
    main_loop_started = False
680
 
    
681
 
    parser = OptionParser(version = "%%prog %s" % version)
 
417
        SIOCGIFINDEX = 0x8933      # From /usr/include/linux/sockios.h
 
418
        s = socket.socket()
 
419
        ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
420
                            struct.pack("16s16x", interface))
 
421
        s.close()
 
422
        interface_index = struct.unpack("I", ifreq[16:20])[0]
 
423
        return interface_index
 
424
 
 
425
 
 
426
if __name__ == '__main__':
 
427
    parser = OptionParser()
682
428
    parser.add_option("-i", "--interface", type="string",
683
 
                      metavar="IF", help="Bind to interface IF")
684
 
    parser.add_option("-a", "--address", type="string",
685
 
                      help="Address to listen for requests on")
686
 
    parser.add_option("-p", "--port", type="int",
 
429
                      default=None, metavar="IF",
 
430
                      help="Bind to interface IF")
 
431
    parser.add_option("--cert", type="string", default="cert.pem",
 
432
                      metavar="FILE",
 
433
                      help="Public key certificate PEM file to use")
 
434
    parser.add_option("--key", type="string", default="key.pem",
 
435
                      metavar="FILE",
 
436
                      help="Private key PEM file to use")
 
437
    parser.add_option("--ca", type="string", default="ca.pem",
 
438
                      metavar="FILE",
 
439
                      help="Certificate Authority certificate PEM file to use")
 
440
    parser.add_option("--crl", type="string", default="crl.pem",
 
441
                      metavar="FILE",
 
442
                      help="Certificate Revokation List PEM file to use")
 
443
    parser.add_option("-p", "--port", type="int", default=None,
687
444
                      help="Port number to receive requests on")
 
445
    parser.add_option("--timeout", type="string", # Parsed later
 
446
                      default="1h",
 
447
                      help="Amount of downtime allowed for clients")
 
448
    parser.add_option("--interval", type="string", # Parsed later
 
449
                      default="5m",
 
450
                      help="How often to check that a client is up")
688
451
    parser.add_option("--check", action="store_true", default=False,
689
452
                      help="Run self-test")
690
 
    parser.add_option("--debug", action="store_true",
691
 
                      help="Debug mode; run in foreground and log to"
692
 
                      " terminal")
693
 
    parser.add_option("--priority", type="string", help="GnuTLS"
694
 
                      " priority string (see GnuTLS documentation)")
695
 
    parser.add_option("--servicename", type="string", metavar="NAME",
696
 
                      help="Zeroconf service name")
697
 
    parser.add_option("--configdir", type="string",
698
 
                      default="/etc/mandos", metavar="DIR",
699
 
                      help="Directory to search for configuration"
700
 
                      " files")
701
453
    (options, args) = parser.parse_args()
702
454
    
703
455
    if options.check:
705
457
        doctest.testmod()
706
458
        sys.exit()
707
459
    
708
 
    # Default values for config file for server-global settings
709
 
    server_defaults = { "interface": "",
710
 
                        "address": "",
711
 
                        "port": "",
712
 
                        "debug": "False",
713
 
                        "priority":
714
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
715
 
                        "servicename": "Mandos",
716
 
                        }
717
 
    
718
 
    # Parse config file for server-global settings
719
 
    server_config = ConfigParser.SafeConfigParser(server_defaults)
720
 
    del server_defaults
721
 
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
722
 
    # Convert the SafeConfigParser object to a dict
723
 
    server_settings = server_config.defaults()
724
 
    # Use getboolean on the boolean config option
725
 
    server_settings["debug"] = server_config.getboolean\
726
 
                               ("DEFAULT", "debug")
727
 
    del server_config
728
 
    
729
 
    # Override the settings from the config file with command line
730
 
    # options, if set.
731
 
    for option in ("interface", "address", "port", "debug",
732
 
                   "priority", "servicename", "configdir"):
733
 
        value = getattr(options, option)
734
 
        if value is not None:
735
 
            server_settings[option] = value
736
 
    del options
737
 
    # Now we have our good server settings in "server_settings"
738
 
    
739
 
    debug = server_settings["debug"]
740
 
    
741
 
    if not debug:
742
 
        syslogger.setLevel(logging.WARNING)
743
 
        console.setLevel(logging.WARNING)
744
 
    
745
 
    if server_settings["servicename"] != "Mandos":
746
 
        syslogger.setFormatter(logging.Formatter\
747
 
                               ('Mandos (%s): %%(levelname)s:'
748
 
                                ' %%(message)s'
749
 
                                % server_settings["servicename"]))
750
 
    
751
 
    # Parse config file with clients
752
 
    client_defaults = { "timeout": "1h",
753
 
                        "interval": "5m",
754
 
                        "checker": "fping -q -- %(host)s",
755
 
                        "host": "",
756
 
                        }
757
 
    client_config = ConfigParser.SafeConfigParser(client_defaults)
758
 
    client_config.read(os.path.join(server_settings["configdir"],
759
 
                                    "clients.conf"))
760
 
    
761
 
    global service
762
 
    service = AvahiService(name = server_settings["servicename"],
763
 
                           type = "_mandos._tcp", );
764
 
    if server_settings["interface"]:
765
 
        service.interface = if_nametoindex\
766
 
                            (server_settings["interface"])
767
 
    
768
 
    global main_loop
769
 
    global bus
770
 
    global server
771
 
    # From the Avahi example code
 
460
    # Parse the time arguments
 
461
    try:
 
462
        options.timeout = string_to_delta(options.timeout)
 
463
    except ValueError:
 
464
        parser.error("option --timeout: Unparseable time")
 
465
    try:
 
466
        options.interval = string_to_delta(options.interval)
 
467
    except ValueError:
 
468
        parser.error("option --interval: Unparseable time")
 
469
    
 
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])
 
475
    
 
476
    # Parse config file
 
477
    defaults = {}
 
478
    client_config = ConfigParser.SafeConfigParser(defaults)
 
479
    #client_config.readfp(open("secrets.conf"), "secrets.conf")
 
480
    client_config.read("mandos-clients.conf")
 
481
    
 
482
    # From the Avahi server example code
772
483
    DBusGMainLoop(set_as_default=True )
773
484
    main_loop = gobject.MainLoop()
774
485
    bus = dbus.SystemBus()
775
 
    server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
776
 
                                           avahi.DBUS_PATH_SERVER),
777
 
                            avahi.DBUS_INTERFACE_SERVER)
 
486
    server = dbus.Interface(
 
487
            bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
 
488
            avahi.DBUS_INTERFACE_SERVER )
778
489
    # End of Avahi example code
779
490
    
780
491
    clients = Set()
781
492
    def remove_from_clients(client):
782
493
        clients.remove(client)
783
494
        if not clients:
784
 
            logger.critical(u"No clients left, exiting")
785
 
            sys.exit()
 
495
            print "No clients left, exiting"
 
496
            main_loop.quit()
786
497
    
787
 
    clients.update(Set(Client(name = section,
 
498
    clients.update(Set(Client(name=section, options=options,
788
499
                              stop_hook = remove_from_clients,
789
 
                              config
790
 
                              = dict(client_config.items(section)))
 
500
                              **(dict(client_config\
 
501
                                      .items(section))))
791
502
                       for section in client_config.sections()))
792
 
    if not clients:
793
 
        logger.critical(u"No clients defined")
794
 
        sys.exit(1)
795
 
    
796
 
    if debug:
797
 
        # Redirect stdin so all checkers get /dev/null
798
 
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
799
 
        os.dup2(null, sys.stdin.fileno())
800
 
        if null > 2:
801
 
            os.close(null)
802
 
    else:
803
 
        # No console logging
804
 
        logger.removeHandler(console)
805
 
        # Close all input and output, do double fork, etc.
806
 
        daemon()
807
 
    
808
 
    pidfilename = "/var/run/mandos/mandos.pid"
809
 
    pid = os.getpid()
810
 
    try:
811
 
        pidfile = open(pidfilename, "w")
812
 
        pidfile.write(str(pid) + "\n")
813
 
        pidfile.close()
814
 
        del pidfile
815
 
    except IOError, err:
816
 
        logger.error(u"Could not write %s file with PID %d",
817
 
                     pidfilename, os.getpid())
818
 
    
819
 
    def cleanup():
820
 
        "Cleanup function; run on exit"
821
 
        global group
822
 
        # From the Avahi example code
823
 
        if not group is None:
824
 
            group.Free()
825
 
            group = None
826
 
        # End of Avahi example code
827
 
        
828
 
        while clients:
829
 
            client = clients.pop()
830
 
            client.stop_hook = None
831
 
            client.stop()
832
 
    
833
 
    atexit.register(cleanup)
834
 
    
835
 
    if not debug:
836
 
        signal.signal(signal.SIGINT, signal.SIG_IGN)
837
 
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
838
 
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
839
 
    
840
503
    for client in clients:
841
504
        client.start()
842
505
    
843
 
    tcp_server = IPv6_TCPServer((server_settings["address"],
844
 
                                 server_settings["port"]),
 
506
    tcp_server = IPv6_TCPServer((None, options.port),
845
507
                                tcp_handler,
846
 
                                settings=server_settings,
847
 
                                clients=clients)
848
 
    # Find out what port we got
849
 
    service.port = tcp_server.socket.getsockname()[1]
850
 
    logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
851
 
                u" scope_id %d" % tcp_server.socket.getsockname())
852
 
    
853
 
    #service.interface = tcp_server.socket.getsockname()[3]
854
 
    
 
508
                                options=options,
 
509
                                clients=clients,
 
510
                                credentials=cred)
 
511
    # Find out what random port we got
 
512
    servicePort = tcp_server.socket.getsockname()[1]
 
513
    #sys.stderr.write("Now listening on port %d\n" % servicePort)
 
514
    
 
515
    if options.interface is not None:
 
516
        serviceInterface = if_nametoindex(options.interface)
 
517
    
 
518
    # From the Avahi server example code
 
519
    server.connect_to_signal("StateChanged", server_state_changed)
 
520
    server_state_changed(server.GetState())
 
521
    # End of Avahi example code
 
522
    
 
523
    gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
 
524
                         lambda *args, **kwargs:
 
525
                         tcp_server.handle_request(*args[2:],
 
526
                                                   **kwargs) or True)
855
527
    try:
856
 
        # From the Avahi example code
857
 
        server.connect_to_signal("StateChanged", server_state_changed)
858
 
        try:
859
 
            server_state_changed(server.GetState())
860
 
        except dbus.exceptions.DBusException, error:
861
 
            logger.critical(u"DBusException: %s", error)
862
 
            sys.exit(1)
863
 
        # End of Avahi example code
864
 
        
865
 
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
866
 
                             lambda *args, **kwargs:
867
 
                             tcp_server.handle_request\
868
 
                             (*args[2:], **kwargs) or True)
869
 
        
870
 
        logger.debug(u"Starting main loop")
871
 
        main_loop_started = True
872
528
        main_loop.run()
873
 
    except AvahiError, error:
874
 
        logger.critical(u"AvahiError: %s" + unicode(error))
875
 
        sys.exit(1)
876
529
    except KeyboardInterrupt:
877
 
        if debug:
878
 
            print
 
530
        print
 
531
    
 
532
    # Cleanup here
879
533
 
880
 
if __name__ == '__main__':
881
 
    main()
 
534
    # From the Avahi server example code
 
535
    if not group is None:
 
536
        group.Free()
 
537
    # End of Avahi example code
 
538
    
 
539
    for client in clients:
 
540
        client.stop_hook = None
 
541
        client.stop()