/mandos/trunk

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

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2009-02-07 04:50:39 UTC
  • Revision ID: teddy@fukt.bsnet.se-20090207045039-xkr6b80vtqwqrq8l
* Makefile (install-client-nokey): Move "initramfs-tools-script" from
                                   "/scripts/local-top/mandos" to
                                   "/scripts/init-premount/mandos".
  (uninstall-client): - '' -
* debian/mandos-client.dirs: - '' -
* initramfs-tools-script (PREREQ): Added "udev".

Show diffs side-by-side

added added

removed removed

Lines of Context:
6
6
# This program is partly derived from an example program for an Avahi
7
7
# service publisher, downloaded from
8
8
# <http://avahi.org/wiki/PythonPublishExample>.  This includes the
9
 
# methods "add", "remove", "server_state_changed",
10
 
# "entry_group_state_changed", "cleanup", and "activate" in the
11
 
# "AvahiService" class, and some lines in "main".
 
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
12
13
13
# Everything else is
14
14
# Copyright © 2008,2009 Teddy Hogeborn
33
33
 
34
34
from __future__ import division, with_statement, absolute_import
35
35
 
36
 
import SocketServer as socketserver
 
36
import SocketServer
37
37
import socket
38
38
import optparse
39
39
import datetime
44
44
import gnutls.library.functions
45
45
import gnutls.library.constants
46
46
import gnutls.library.types
47
 
import ConfigParser as configparser
 
47
import ConfigParser
48
48
import sys
49
49
import re
50
50
import os
51
51
import signal
 
52
from sets import Set
52
53
import subprocess
53
54
import atexit
54
55
import stat
56
57
import logging.handlers
57
58
import pwd
58
59
from contextlib import closing
59
 
import struct
60
 
import fcntl
61
 
import functools
62
60
 
63
61
import dbus
64
62
import dbus.service
68
66
import ctypes
69
67
import ctypes.util
70
68
 
71
 
try:
72
 
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
73
 
except AttributeError:
74
 
    try:
75
 
        from IN import SO_BINDTODEVICE
76
 
    except ImportError:
77
 
        SO_BINDTODEVICE = None
78
 
 
79
 
 
80
 
version = "1.0.12"
81
 
 
82
 
logger = logging.Logger(u'mandos')
 
69
version = "1.0.5"
 
70
 
 
71
logger = logging.Logger('mandos')
83
72
syslogger = (logging.handlers.SysLogHandler
84
73
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
85
74
              address = "/dev/log"))
86
75
syslogger.setFormatter(logging.Formatter
87
 
                       (u'Mandos [%(process)d]: %(levelname)s:'
88
 
                        u' %(message)s'))
 
76
                       ('Mandos [%(process)d]: %(levelname)s:'
 
77
                        ' %(message)s'))
89
78
logger.addHandler(syslogger)
90
79
 
91
80
console = logging.StreamHandler()
92
 
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
93
 
                                       u' %(levelname)s:'
94
 
                                       u' %(message)s'))
 
81
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
 
82
                                       ' %(levelname)s: %(message)s'))
95
83
logger.addHandler(console)
96
84
 
97
85
class AvahiError(Exception):
110
98
 
111
99
class AvahiService(object):
112
100
    """An Avahi (Zeroconf) service.
113
 
    
114
101
    Attributes:
115
102
    interface: integer; avahi.IF_UNSPEC or an interface index.
116
103
               Used to optionally bind to the specified interface.
117
 
    name: string; Example: u'Mandos'
118
 
    type: string; Example: u'_mandos._tcp'.
 
104
    name: string; Example: 'Mandos'
 
105
    type: string; Example: '_mandos._tcp'.
119
106
                  See <http://www.dns-sd.org/ServiceTypes.html>
120
107
    port: integer; what port to announce
121
108
    TXT: list of strings; TXT record for the service
124
111
    max_renames: integer; maximum number of renames
125
112
    rename_count: integer; counter so we only rename after collisions
126
113
                  a sensible number of times
127
 
    group: D-Bus Entry Group
128
 
    server: D-Bus Server
129
 
    bus: dbus.SystemBus()
130
114
    """
131
115
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
132
116
                 servicetype = None, port = None, TXT = None,
133
 
                 domain = u"", host = u"", max_renames = 32768,
134
 
                 protocol = avahi.PROTO_UNSPEC, bus = None):
 
117
                 domain = "", host = "", max_renames = 32768):
135
118
        self.interface = interface
136
119
        self.name = name
137
120
        self.type = servicetype
141
124
        self.host = host
142
125
        self.rename_count = 0
143
126
        self.max_renames = max_renames
144
 
        self.protocol = protocol
145
 
        self.group = None       # our entry group
146
 
        self.server = None
147
 
        self.bus = bus
148
127
    def rename(self):
149
128
        """Derived from the Avahi example code"""
150
129
        if self.rename_count >= self.max_renames:
152
131
                            u" after %i retries, exiting.",
153
132
                            self.rename_count)
154
133
            raise AvahiServiceError(u"Too many renames")
155
 
        self.name = self.server.GetAlternativeServiceName(self.name)
 
134
        self.name = server.GetAlternativeServiceName(self.name)
156
135
        logger.info(u"Changing Zeroconf service name to %r ...",
157
 
                    unicode(self.name))
 
136
                    str(self.name))
158
137
        syslogger.setFormatter(logging.Formatter
159
 
                               (u'Mandos (%s) [%%(process)d]:'
160
 
                                u' %%(levelname)s: %%(message)s'
161
 
                                % self.name))
 
138
                               ('Mandos (%s): %%(levelname)s:'
 
139
                                ' %%(message)s' % self.name))
162
140
        self.remove()
163
141
        self.add()
164
142
        self.rename_count += 1
165
143
    def remove(self):
166
144
        """Derived from the Avahi example code"""
167
 
        if self.group is not None:
168
 
            self.group.Reset()
 
145
        if group is not None:
 
146
            group.Reset()
169
147
    def add(self):
170
148
        """Derived from the Avahi example code"""
171
 
        if self.group is None:
172
 
            self.group = dbus.Interface(
173
 
                self.bus.get_object(avahi.DBUS_NAME,
174
 
                                    self.server.EntryGroupNew()),
175
 
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
176
 
            self.group.connect_to_signal('StateChanged',
177
 
                                         self
178
 
                                         .entry_group_state_changed)
 
149
        global group
 
150
        if group is None:
 
151
            group = dbus.Interface(bus.get_object
 
152
                                   (avahi.DBUS_NAME,
 
153
                                    server.EntryGroupNew()),
 
154
                                   avahi.DBUS_INTERFACE_ENTRY_GROUP)
 
155
            group.connect_to_signal('StateChanged',
 
156
                                    entry_group_state_changed)
179
157
        logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
180
 
                     self.name, self.type)
181
 
        self.group.AddService(
182
 
            self.interface,
183
 
            self.protocol,
184
 
            dbus.UInt32(0),     # flags
185
 
            self.name, self.type,
186
 
            self.domain, self.host,
187
 
            dbus.UInt16(self.port),
188
 
            avahi.string_array_to_txt_array(self.TXT))
189
 
        self.group.Commit()
190
 
    def entry_group_state_changed(self, state, error):
191
 
        """Derived from the Avahi example code"""
192
 
        logger.debug(u"Avahi state change: %i", state)
193
 
        
194
 
        if state == avahi.ENTRY_GROUP_ESTABLISHED:
195
 
            logger.debug(u"Zeroconf service established.")
196
 
        elif state == avahi.ENTRY_GROUP_COLLISION:
197
 
            logger.warning(u"Zeroconf service name collision.")
198
 
            self.rename()
199
 
        elif state == avahi.ENTRY_GROUP_FAILURE:
200
 
            logger.critical(u"Avahi: Error in group state changed %s",
201
 
                            unicode(error))
202
 
            raise AvahiGroupError(u"State changed: %s"
203
 
                                  % unicode(error))
204
 
    def cleanup(self):
205
 
        """Derived from the Avahi example code"""
206
 
        if self.group is not None:
207
 
            self.group.Free()
208
 
            self.group = None
209
 
    def server_state_changed(self, state):
210
 
        """Derived from the Avahi example code"""
211
 
        if state == avahi.SERVER_COLLISION:
212
 
            logger.error(u"Zeroconf server name collision")
213
 
            self.remove()
214
 
        elif state == avahi.SERVER_RUNNING:
215
 
            self.add()
216
 
    def activate(self):
217
 
        """Derived from the Avahi example code"""
218
 
        if self.server is None:
219
 
            self.server = dbus.Interface(
220
 
                self.bus.get_object(avahi.DBUS_NAME,
221
 
                                    avahi.DBUS_PATH_SERVER),
222
 
                avahi.DBUS_INTERFACE_SERVER)
223
 
        self.server.connect_to_signal(u"StateChanged",
224
 
                                 self.server_state_changed)
225
 
        self.server_state_changed(self.server.GetState())
226
 
 
227
 
 
228
 
class Client(object):
 
158
                     service.name, service.type)
 
159
        group.AddService(
 
160
                self.interface,         # interface
 
161
                avahi.PROTO_INET6,      # protocol
 
162
                dbus.UInt32(0),         # flags
 
163
                self.name, self.type,
 
164
                self.domain, self.host,
 
165
                dbus.UInt16(self.port),
 
166
                avahi.string_array_to_txt_array(self.TXT))
 
167
        group.Commit()
 
168
 
 
169
# From the Avahi example code:
 
170
group = None                            # our entry group
 
171
# End of Avahi example code
 
172
 
 
173
 
 
174
def _datetime_to_dbus(dt, variant_level=0):
 
175
    """Convert a UTC datetime.datetime() to a D-Bus type."""
 
176
    return dbus.String(dt.isoformat(), variant_level=variant_level)
 
177
 
 
178
 
 
179
class Client(dbus.service.Object):
229
180
    """A representation of a client host served by this server.
230
 
    
231
181
    Attributes:
232
182
    name:       string; from the config file, used in log messages and
233
183
                        D-Bus identifiers
253
203
                     client lives.  %() expansions are done at
254
204
                     runtime with vars(self) as dict, so that for
255
205
                     instance %(name)s can be used in the command.
256
 
    current_checker_command: string; current running checker_command
 
206
    use_dbus: bool(); Whether to provide D-Bus interface and signals
 
207
    dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
257
208
    """
258
 
    
259
 
    @staticmethod
260
 
    def _datetime_to_milliseconds(dt):
261
 
        "Convert a datetime.datetime() to milliseconds"
262
 
        return ((dt.days * 24 * 60 * 60 * 1000)
263
 
                + (dt.seconds * 1000)
264
 
                + (dt.microseconds // 1000))
265
 
    
266
209
    def timeout_milliseconds(self):
267
210
        "Return the 'timeout' attribute in milliseconds"
268
 
        return self._datetime_to_milliseconds(self.timeout)
 
211
        return ((self.timeout.days * 24 * 60 * 60 * 1000)
 
212
                + (self.timeout.seconds * 1000)
 
213
                + (self.timeout.microseconds // 1000))
269
214
    
270
215
    def interval_milliseconds(self):
271
216
        "Return the 'interval' attribute in milliseconds"
272
 
        return self._datetime_to_milliseconds(self.interval)
 
217
        return ((self.interval.days * 24 * 60 * 60 * 1000)
 
218
                + (self.interval.seconds * 1000)
 
219
                + (self.interval.microseconds // 1000))
273
220
    
274
 
    def __init__(self, name = None, disable_hook=None, config=None):
 
221
    def __init__(self, name = None, disable_hook=None, config=None,
 
222
                 use_dbus=True):
275
223
        """Note: the 'checker' key in 'config' sets the
276
224
        'checker_command' attribute and *not* the 'checker'
277
225
        attribute."""
279
227
        if config is None:
280
228
            config = {}
281
229
        logger.debug(u"Creating client %r", self.name)
 
230
        self.use_dbus = False   # During __init__
282
231
        # Uppercase and remove spaces from fingerprint for later
283
232
        # comparison purposes with return value from the fingerprint()
284
233
        # function
285
 
        self.fingerprint = (config[u"fingerprint"].upper()
 
234
        self.fingerprint = (config["fingerprint"].upper()
286
235
                            .replace(u" ", u""))
287
236
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
288
 
        if u"secret" in config:
289
 
            self.secret = config[u"secret"].decode(u"base64")
290
 
        elif u"secfile" in config:
 
237
        if "secret" in config:
 
238
            self.secret = config["secret"].decode(u"base64")
 
239
        elif "secfile" in config:
291
240
            with closing(open(os.path.expanduser
292
241
                              (os.path.expandvars
293
 
                               (config[u"secfile"])))) as secfile:
 
242
                               (config["secfile"])))) as secfile:
294
243
                self.secret = secfile.read()
295
244
        else:
296
245
            raise TypeError(u"No secret or secfile for client %s"
297
246
                            % self.name)
298
 
        self.host = config.get(u"host", u"")
 
247
        self.host = config.get("host", "")
299
248
        self.created = datetime.datetime.utcnow()
300
249
        self.enabled = False
301
250
        self.last_enabled = None
302
251
        self.last_checked_ok = None
303
 
        self.timeout = string_to_delta(config[u"timeout"])
304
 
        self.interval = string_to_delta(config[u"interval"])
 
252
        self.timeout = string_to_delta(config["timeout"])
 
253
        self.interval = string_to_delta(config["interval"])
305
254
        self.disable_hook = disable_hook
306
255
        self.checker = None
307
256
        self.checker_initiator_tag = None
308
257
        self.disable_initiator_tag = None
309
258
        self.checker_callback_tag = None
310
 
        self.checker_command = config[u"checker"]
311
 
        self.current_checker_command = None
 
259
        self.checker_command = config["checker"]
312
260
        self.last_connect = None
 
261
        # Only now, when this client is initialized, can it show up on
 
262
        # the D-Bus
 
263
        self.use_dbus = use_dbus
 
264
        if self.use_dbus:
 
265
            self.dbus_object_path = (dbus.ObjectPath
 
266
                                     ("/clients/"
 
267
                                      + self.name.replace(".", "_")))
 
268
            dbus.service.Object.__init__(self, bus,
 
269
                                         self.dbus_object_path)
313
270
    
314
271
    def enable(self):
315
272
        """Start this client's checker and timeout hooks"""
316
 
        if getattr(self, u"enabled", False):
317
 
            # Already enabled
318
 
            return
319
273
        self.last_enabled = datetime.datetime.utcnow()
320
274
        # Schedule a new checker to be started an 'interval' from now,
321
275
        # and every interval from then on.
329
283
                                   (self.timeout_milliseconds(),
330
284
                                    self.disable))
331
285
        self.enabled = True
 
286
        if self.use_dbus:
 
287
            # Emit D-Bus signals
 
288
            self.PropertyChanged(dbus.String(u"enabled"),
 
289
                                 dbus.Boolean(True, variant_level=1))
 
290
            self.PropertyChanged(dbus.String(u"last_enabled"),
 
291
                                 (_datetime_to_dbus(self.last_enabled,
 
292
                                                    variant_level=1)))
332
293
    
333
294
    def disable(self):
334
295
        """Disable this client."""
335
296
        if not getattr(self, "enabled", False):
336
297
            return False
337
298
        logger.info(u"Disabling client %s", self.name)
338
 
        if getattr(self, u"disable_initiator_tag", False):
 
299
        if getattr(self, "disable_initiator_tag", False):
339
300
            gobject.source_remove(self.disable_initiator_tag)
340
301
            self.disable_initiator_tag = None
341
 
        if getattr(self, u"checker_initiator_tag", False):
 
302
        if getattr(self, "checker_initiator_tag", False):
342
303
            gobject.source_remove(self.checker_initiator_tag)
343
304
            self.checker_initiator_tag = None
344
305
        self.stop_checker()
345
306
        if self.disable_hook:
346
307
            self.disable_hook(self)
347
308
        self.enabled = False
 
309
        if self.use_dbus:
 
310
            # Emit D-Bus signal
 
311
            self.PropertyChanged(dbus.String(u"enabled"),
 
312
                                 dbus.Boolean(False, variant_level=1))
348
313
        # Do not run this again if called by a gobject.timeout_add
349
314
        return False
350
315
    
356
321
        """The checker has completed, so take appropriate actions."""
357
322
        self.checker_callback_tag = None
358
323
        self.checker = None
 
324
        if self.use_dbus:
 
325
            # Emit D-Bus signal
 
326
            self.PropertyChanged(dbus.String(u"checker_running"),
 
327
                                 dbus.Boolean(False, variant_level=1))
359
328
        if os.WIFEXITED(condition):
360
329
            exitstatus = os.WEXITSTATUS(condition)
361
330
            if exitstatus == 0:
365
334
            else:
366
335
                logger.info(u"Checker for %(name)s failed",
367
336
                            vars(self))
 
337
            if self.use_dbus:
 
338
                # Emit D-Bus signal
 
339
                self.CheckerCompleted(dbus.Int16(exitstatus),
 
340
                                      dbus.Int64(condition),
 
341
                                      dbus.String(command))
368
342
        else:
369
343
            logger.warning(u"Checker for %(name)s crashed?",
370
344
                           vars(self))
 
345
            if self.use_dbus:
 
346
                # Emit D-Bus signal
 
347
                self.CheckerCompleted(dbus.Int16(-1),
 
348
                                      dbus.Int64(condition),
 
349
                                      dbus.String(command))
371
350
    
372
351
    def checked_ok(self):
373
352
        """Bump up the timeout for this client.
374
 
        
375
353
        This should only be called when the client has been seen,
376
354
        alive and well.
377
355
        """
380
358
        self.disable_initiator_tag = (gobject.timeout_add
381
359
                                      (self.timeout_milliseconds(),
382
360
                                       self.disable))
 
361
        if self.use_dbus:
 
362
            # Emit D-Bus signal
 
363
            self.PropertyChanged(
 
364
                dbus.String(u"last_checked_ok"),
 
365
                (_datetime_to_dbus(self.last_checked_ok,
 
366
                                   variant_level=1)))
383
367
    
384
368
    def start_checker(self):
385
369
        """Start a new checker subprocess if one is not running.
386
 
        
387
370
        If a checker already exists, leave it running and do
388
371
        nothing."""
389
372
        # The reason for not killing a running checker is that if we
394
377
        # checkers alone, the checker would have to take more time
395
378
        # than 'timeout' for the client to be declared invalid, which
396
379
        # is as it should be.
397
 
        
398
 
        # If a checker exists, make sure it is not a zombie
399
 
        if self.checker is not None:
400
 
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
401
 
            if pid:
402
 
                logger.warning(u"Checker was a zombie")
403
 
                gobject.source_remove(self.checker_callback_tag)
404
 
                self.checker_callback(pid, status,
405
 
                                      self.current_checker_command)
406
 
        # Start a new checker if needed
407
380
        if self.checker is None:
408
381
            try:
409
382
                # In case checker_command has exactly one % operator
410
383
                command = self.checker_command % self.host
411
384
            except TypeError:
412
385
                # Escape attributes for the shell
413
 
                escaped_attrs = dict((key,
414
 
                                      re.escape(unicode(str(val),
415
 
                                                        errors=
416
 
                                                        u'replace')))
 
386
                escaped_attrs = dict((key, re.escape(str(val)))
417
387
                                     for key, val in
418
388
                                     vars(self).iteritems())
419
389
                try:
422
392
                    logger.error(u'Could not format string "%s":'
423
393
                                 u' %s', self.checker_command, error)
424
394
                    return True # Try again later
425
 
            self.current_checker_command = command
426
395
            try:
427
396
                logger.info(u"Starting checker %r for %s",
428
397
                            command, self.name)
432
401
                # always replaced by /dev/null.)
433
402
                self.checker = subprocess.Popen(command,
434
403
                                                close_fds=True,
435
 
                                                shell=True, cwd=u"/")
 
404
                                                shell=True, cwd="/")
 
405
                if self.use_dbus:
 
406
                    # Emit D-Bus signal
 
407
                    self.CheckerStarted(command)
 
408
                    self.PropertyChanged(
 
409
                        dbus.String("checker_running"),
 
410
                        dbus.Boolean(True, variant_level=1))
436
411
                self.checker_callback_tag = (gobject.child_watch_add
437
412
                                             (self.checker.pid,
438
413
                                              self.checker_callback,
439
414
                                              data=command))
440
 
                # The checker may have completed before the gobject
441
 
                # watch was added.  Check for this.
442
 
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
443
 
                if pid:
444
 
                    gobject.source_remove(self.checker_callback_tag)
445
 
                    self.checker_callback(pid, status, command)
446
415
            except OSError, error:
447
416
                logger.error(u"Failed to start subprocess: %s",
448
417
                             error)
454
423
        if self.checker_callback_tag:
455
424
            gobject.source_remove(self.checker_callback_tag)
456
425
            self.checker_callback_tag = None
457
 
        if getattr(self, u"checker", None) is None:
 
426
        if getattr(self, "checker", None) is None:
458
427
            return
459
428
        logger.debug(u"Stopping checker for %(name)s", vars(self))
460
429
        try:
466
435
            if error.errno != errno.ESRCH: # No such process
467
436
                raise
468
437
        self.checker = None
 
438
        if self.use_dbus:
 
439
            self.PropertyChanged(dbus.String(u"checker_running"),
 
440
                                 dbus.Boolean(False, variant_level=1))
469
441
    
470
442
    def still_valid(self):
471
443
        """Has the timeout not yet passed for this client?"""
472
 
        if not getattr(self, u"enabled", False):
 
444
        if not getattr(self, "enabled", False):
473
445
            return False
474
446
        now = datetime.datetime.utcnow()
475
447
        if self.last_checked_ok is None:
476
448
            return now < (self.created + self.timeout)
477
449
        else:
478
450
            return now < (self.last_checked_ok + self.timeout)
479
 
 
480
 
 
481
 
class ClientDBus(Client, dbus.service.Object):
482
 
    """A Client class using D-Bus
483
 
    
484
 
    Attributes:
485
 
    dbus_object_path: dbus.ObjectPath
486
 
    bus: dbus.SystemBus()
487
 
    """
488
 
    # dbus.service.Object doesn't use super(), so we can't either.
489
 
    
490
 
    def __init__(self, bus = None, *args, **kwargs):
491
 
        self.bus = bus
492
 
        Client.__init__(self, *args, **kwargs)
493
 
        # Only now, when this client is initialized, can it show up on
494
 
        # the D-Bus
495
 
        self.dbus_object_path = (dbus.ObjectPath
496
 
                                 (u"/clients/"
497
 
                                  + self.name.replace(u".", u"_")))
498
 
        dbus.service.Object.__init__(self, self.bus,
499
 
                                     self.dbus_object_path)
500
 
    
501
 
    @staticmethod
502
 
    def _datetime_to_dbus(dt, variant_level=0):
503
 
        """Convert a UTC datetime.datetime() to a D-Bus type."""
504
 
        return dbus.String(dt.isoformat(),
505
 
                           variant_level=variant_level)
506
 
    
507
 
    def enable(self):
508
 
        oldstate = getattr(self, u"enabled", False)
509
 
        r = Client.enable(self)
510
 
        if oldstate != self.enabled:
511
 
            # Emit D-Bus signals
512
 
            self.PropertyChanged(dbus.String(u"enabled"),
513
 
                                 dbus.Boolean(True, variant_level=1))
514
 
            self.PropertyChanged(
515
 
                dbus.String(u"last_enabled"),
516
 
                self._datetime_to_dbus(self.last_enabled,
517
 
                                       variant_level=1))
518
 
        return r
519
 
    
520
 
    def disable(self, signal = True):
521
 
        oldstate = getattr(self, u"enabled", False)
522
 
        r = Client.disable(self)
523
 
        if signal and oldstate != self.enabled:
524
 
            # Emit D-Bus signal
525
 
            self.PropertyChanged(dbus.String(u"enabled"),
526
 
                                 dbus.Boolean(False, variant_level=1))
527
 
        return r
528
 
    
529
 
    def __del__(self, *args, **kwargs):
530
 
        try:
531
 
            self.remove_from_connection()
532
 
        except LookupError:
533
 
            pass
534
 
        if hasattr(dbus.service.Object, u"__del__"):
535
 
            dbus.service.Object.__del__(self, *args, **kwargs)
536
 
        Client.__del__(self, *args, **kwargs)
537
 
    
538
 
    def checker_callback(self, pid, condition, command,
539
 
                         *args, **kwargs):
540
 
        self.checker_callback_tag = None
541
 
        self.checker = None
542
 
        # Emit D-Bus signal
543
 
        self.PropertyChanged(dbus.String(u"checker_running"),
544
 
                             dbus.Boolean(False, variant_level=1))
545
 
        if os.WIFEXITED(condition):
546
 
            exitstatus = os.WEXITSTATUS(condition)
547
 
            # Emit D-Bus signal
548
 
            self.CheckerCompleted(dbus.Int16(exitstatus),
549
 
                                  dbus.Int64(condition),
550
 
                                  dbus.String(command))
551
 
        else:
552
 
            # Emit D-Bus signal
553
 
            self.CheckerCompleted(dbus.Int16(-1),
554
 
                                  dbus.Int64(condition),
555
 
                                  dbus.String(command))
556
 
        
557
 
        return Client.checker_callback(self, pid, condition, command,
558
 
                                       *args, **kwargs)
559
 
    
560
 
    def checked_ok(self, *args, **kwargs):
561
 
        r = Client.checked_ok(self, *args, **kwargs)
562
 
        # Emit D-Bus signal
563
 
        self.PropertyChanged(
564
 
            dbus.String(u"last_checked_ok"),
565
 
            (self._datetime_to_dbus(self.last_checked_ok,
566
 
                                    variant_level=1)))
567
 
        return r
568
 
    
569
 
    def start_checker(self, *args, **kwargs):
570
 
        old_checker = self.checker
571
 
        if self.checker is not None:
572
 
            old_checker_pid = self.checker.pid
573
 
        else:
574
 
            old_checker_pid = None
575
 
        r = Client.start_checker(self, *args, **kwargs)
576
 
        # Only if new checker process was started
577
 
        if (self.checker is not None
578
 
            and old_checker_pid != self.checker.pid):
579
 
            # Emit D-Bus signal
580
 
            self.CheckerStarted(self.current_checker_command)
581
 
            self.PropertyChanged(
582
 
                dbus.String(u"checker_running"),
583
 
                dbus.Boolean(True, variant_level=1))
584
 
        return r
585
 
    
586
 
    def stop_checker(self, *args, **kwargs):
587
 
        old_checker = getattr(self, u"checker", None)
588
 
        r = Client.stop_checker(self, *args, **kwargs)
589
 
        if (old_checker is not None
590
 
            and getattr(self, u"checker", None) is None):
591
 
            self.PropertyChanged(dbus.String(u"checker_running"),
592
 
                                 dbus.Boolean(False, variant_level=1))
593
 
        return r
594
451
    
595
452
    ## D-Bus methods & signals
596
453
    _interface = u"se.bsnet.fukt.Mandos.Client"
597
454
    
598
455
    # CheckedOK - method
599
 
    @dbus.service.method(_interface)
600
 
    def CheckedOK(self):
601
 
        return self.checked_ok()
 
456
    CheckedOK = dbus.service.method(_interface)(checked_ok)
 
457
    CheckedOK.__name__ = "CheckedOK"
602
458
    
603
459
    # CheckerCompleted - signal
604
 
    @dbus.service.signal(_interface, signature=u"nxs")
 
460
    @dbus.service.signal(_interface, signature="nxs")
605
461
    def CheckerCompleted(self, exitcode, waitstatus, command):
606
462
        "D-Bus signal"
607
463
        pass
608
464
    
609
465
    # CheckerStarted - signal
610
 
    @dbus.service.signal(_interface, signature=u"s")
 
466
    @dbus.service.signal(_interface, signature="s")
611
467
    def CheckerStarted(self, command):
612
468
        "D-Bus signal"
613
469
        pass
614
470
    
615
471
    # GetAllProperties - method
616
 
    @dbus.service.method(_interface, out_signature=u"a{sv}")
 
472
    @dbus.service.method(_interface, out_signature="a{sv}")
617
473
    def GetAllProperties(self):
618
474
        "D-Bus method"
619
475
        return dbus.Dictionary({
620
 
                dbus.String(u"name"):
 
476
                dbus.String("name"):
621
477
                    dbus.String(self.name, variant_level=1),
622
 
                dbus.String(u"fingerprint"):
 
478
                dbus.String("fingerprint"):
623
479
                    dbus.String(self.fingerprint, variant_level=1),
624
 
                dbus.String(u"host"):
 
480
                dbus.String("host"):
625
481
                    dbus.String(self.host, variant_level=1),
626
 
                dbus.String(u"created"):
627
 
                    self._datetime_to_dbus(self.created,
628
 
                                           variant_level=1),
629
 
                dbus.String(u"last_enabled"):
630
 
                    (self._datetime_to_dbus(self.last_enabled,
631
 
                                            variant_level=1)
 
482
                dbus.String("created"):
 
483
                    _datetime_to_dbus(self.created, variant_level=1),
 
484
                dbus.String("last_enabled"):
 
485
                    (_datetime_to_dbus(self.last_enabled,
 
486
                                       variant_level=1)
632
487
                     if self.last_enabled is not None
633
488
                     else dbus.Boolean(False, variant_level=1)),
634
 
                dbus.String(u"enabled"):
 
489
                dbus.String("enabled"):
635
490
                    dbus.Boolean(self.enabled, variant_level=1),
636
 
                dbus.String(u"last_checked_ok"):
637
 
                    (self._datetime_to_dbus(self.last_checked_ok,
638
 
                                            variant_level=1)
 
491
                dbus.String("last_checked_ok"):
 
492
                    (_datetime_to_dbus(self.last_checked_ok,
 
493
                                       variant_level=1)
639
494
                     if self.last_checked_ok is not None
640
495
                     else dbus.Boolean (False, variant_level=1)),
641
 
                dbus.String(u"timeout"):
 
496
                dbus.String("timeout"):
642
497
                    dbus.UInt64(self.timeout_milliseconds(),
643
498
                                variant_level=1),
644
 
                dbus.String(u"interval"):
 
499
                dbus.String("interval"):
645
500
                    dbus.UInt64(self.interval_milliseconds(),
646
501
                                variant_level=1),
647
 
                dbus.String(u"checker"):
 
502
                dbus.String("checker"):
648
503
                    dbus.String(self.checker_command,
649
504
                                variant_level=1),
650
 
                dbus.String(u"checker_running"):
 
505
                dbus.String("checker_running"):
651
506
                    dbus.Boolean(self.checker is not None,
652
507
                                 variant_level=1),
653
 
                dbus.String(u"object_path"):
 
508
                dbus.String("object_path"):
654
509
                    dbus.ObjectPath(self.dbus_object_path,
655
510
                                    variant_level=1)
656
 
                }, signature=u"sv")
 
511
                }, signature="sv")
657
512
    
658
513
    # IsStillValid - method
659
 
    @dbus.service.method(_interface, out_signature=u"b")
660
 
    def IsStillValid(self):
661
 
        return self.still_valid()
 
514
    IsStillValid = (dbus.service.method(_interface, out_signature="b")
 
515
                    (still_valid))
 
516
    IsStillValid.__name__ = "IsStillValid"
662
517
    
663
518
    # PropertyChanged - signal
664
 
    @dbus.service.signal(_interface, signature=u"sv")
 
519
    @dbus.service.signal(_interface, signature="sv")
665
520
    def PropertyChanged(self, property, value):
666
521
        "D-Bus signal"
667
522
        pass
668
523
    
669
 
    # ReceivedSecret - signal
670
 
    @dbus.service.signal(_interface)
671
 
    def ReceivedSecret(self):
672
 
        "D-Bus signal"
673
 
        pass
674
 
    
675
 
    # Rejected - signal
676
 
    @dbus.service.signal(_interface)
677
 
    def Rejected(self):
678
 
        "D-Bus signal"
679
 
        pass
680
 
    
681
524
    # SetChecker - method
682
 
    @dbus.service.method(_interface, in_signature=u"s")
 
525
    @dbus.service.method(_interface, in_signature="s")
683
526
    def SetChecker(self, checker):
684
527
        "D-Bus setter method"
685
528
        self.checker_command = checker
689
532
                                         variant_level=1))
690
533
    
691
534
    # SetHost - method
692
 
    @dbus.service.method(_interface, in_signature=u"s")
 
535
    @dbus.service.method(_interface, in_signature="s")
693
536
    def SetHost(self, host):
694
537
        "D-Bus setter method"
695
538
        self.host = host
698
541
                             dbus.String(self.host, variant_level=1))
699
542
    
700
543
    # SetInterval - method
701
 
    @dbus.service.method(_interface, in_signature=u"t")
 
544
    @dbus.service.method(_interface, in_signature="t")
702
545
    def SetInterval(self, milliseconds):
703
546
        self.interval = datetime.timedelta(0, 0, 0, milliseconds)
704
547
        # Emit D-Bus signal
705
548
        self.PropertyChanged(dbus.String(u"interval"),
706
 
                             (dbus.UInt64(self
707
 
                                          .interval_milliseconds(),
 
549
                             (dbus.UInt64(self.interval_milliseconds(),
708
550
                                          variant_level=1)))
709
551
    
710
552
    # SetSecret - method
711
 
    @dbus.service.method(_interface, in_signature=u"ay",
 
553
    @dbus.service.method(_interface, in_signature="ay",
712
554
                         byte_arrays=True)
713
555
    def SetSecret(self, secret):
714
556
        "D-Bus setter method"
715
557
        self.secret = str(secret)
716
558
    
717
559
    # SetTimeout - method
718
 
    @dbus.service.method(_interface, in_signature=u"t")
 
560
    @dbus.service.method(_interface, in_signature="t")
719
561
    def SetTimeout(self, milliseconds):
720
562
        self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
721
563
        # Emit D-Bus signal
724
566
                                          variant_level=1)))
725
567
    
726
568
    # Enable - method
727
 
    @dbus.service.method(_interface)
728
 
    def Enable(self):
729
 
        "D-Bus method"
730
 
        self.enable()
 
569
    Enable = dbus.service.method(_interface)(enable)
 
570
    Enable.__name__ = "Enable"
731
571
    
732
572
    # StartChecker - method
733
573
    @dbus.service.method(_interface)
742
582
        self.disable()
743
583
    
744
584
    # StopChecker - method
745
 
    @dbus.service.method(_interface)
746
 
    def StopChecker(self):
747
 
        self.stop_checker()
 
585
    StopChecker = dbus.service.method(_interface)(stop_checker)
 
586
    StopChecker.__name__ = "StopChecker"
748
587
    
749
588
    del _interface
750
589
 
751
590
 
752
 
class ClientHandler(socketserver.BaseRequestHandler, object):
753
 
    """A class to handle client connections.
754
 
    
755
 
    Instantiated once for each connection to handle it.
 
591
def peer_certificate(session):
 
592
    "Return the peer's OpenPGP certificate as a bytestring"
 
593
    # If not an OpenPGP certificate...
 
594
    if (gnutls.library.functions
 
595
        .gnutls_certificate_type_get(session._c_object)
 
596
        != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
 
597
        # ...do the normal thing
 
598
        return session.peer_certificate
 
599
    list_size = ctypes.c_uint(1)
 
600
    cert_list = (gnutls.library.functions
 
601
                 .gnutls_certificate_get_peers
 
602
                 (session._c_object, ctypes.byref(list_size)))
 
603
    if not bool(cert_list) and list_size.value != 0:
 
604
        raise gnutls.errors.GNUTLSError("error getting peer"
 
605
                                        " certificate")
 
606
    if list_size.value == 0:
 
607
        return None
 
608
    cert = cert_list[0]
 
609
    return ctypes.string_at(cert.data, cert.size)
 
610
 
 
611
 
 
612
def fingerprint(openpgp):
 
613
    "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
 
614
    # New GnuTLS "datum" with the OpenPGP public key
 
615
    datum = (gnutls.library.types
 
616
             .gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
 
617
                                         ctypes.POINTER
 
618
                                         (ctypes.c_ubyte)),
 
619
                             ctypes.c_uint(len(openpgp))))
 
620
    # New empty GnuTLS certificate
 
621
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
 
622
    (gnutls.library.functions
 
623
     .gnutls_openpgp_crt_init(ctypes.byref(crt)))
 
624
    # Import the OpenPGP public key into the certificate
 
625
    (gnutls.library.functions
 
626
     .gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
 
627
                                gnutls.library.constants
 
628
                                .GNUTLS_OPENPGP_FMT_RAW))
 
629
    # Verify the self signature in the key
 
630
    crtverify = ctypes.c_uint()
 
631
    (gnutls.library.functions
 
632
     .gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
 
633
    if crtverify.value != 0:
 
634
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
 
635
        raise gnutls.errors.CertificateSecurityError("Verify failed")
 
636
    # New buffer for the fingerprint
 
637
    buf = ctypes.create_string_buffer(20)
 
638
    buf_len = ctypes.c_size_t()
 
639
    # Get the fingerprint from the certificate into the buffer
 
640
    (gnutls.library.functions
 
641
     .gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
 
642
                                         ctypes.byref(buf_len)))
 
643
    # Deinit the certificate
 
644
    gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
 
645
    # Convert the buffer to a Python bytestring
 
646
    fpr = ctypes.string_at(buf, buf_len.value)
 
647
    # Convert the bytestring to hexadecimal notation
 
648
    hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
 
649
    return hex_fpr
 
650
 
 
651
 
 
652
class TCP_handler(SocketServer.BaseRequestHandler, object):
 
653
    """A TCP request handler class.
 
654
    Instantiated by IPv6_TCPServer for each request to handle it.
756
655
    Note: This will run in its own forked process."""
757
656
    
758
657
    def handle(self):
759
658
        logger.info(u"TCP connection from: %s",
760
659
                    unicode(self.client_address))
761
 
        logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
762
 
        # Open IPC pipe to parent process
763
 
        with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
764
 
            session = (gnutls.connection
765
 
                       .ClientSession(self.request,
766
 
                                      gnutls.connection
767
 
                                      .X509Credentials()))
768
 
            
769
 
            line = self.request.makefile().readline()
770
 
            logger.debug(u"Protocol version: %r", line)
771
 
            try:
772
 
                if int(line.strip().split()[0]) > 1:
773
 
                    raise RuntimeError
774
 
            except (ValueError, IndexError, RuntimeError), error:
775
 
                logger.error(u"Unknown protocol version: %s", error)
776
 
                return
777
 
            
778
 
            # Note: gnutls.connection.X509Credentials is really a
779
 
            # generic GnuTLS certificate credentials object so long as
780
 
            # no X.509 keys are added to it.  Therefore, we can use it
781
 
            # here despite using OpenPGP certificates.
782
 
            
783
 
            #priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
784
 
            #                      u"+AES-256-CBC", u"+SHA1",
785
 
            #                      u"+COMP-NULL", u"+CTYPE-OPENPGP",
786
 
            #                      u"+DHE-DSS"))
787
 
            # Use a fallback default, since this MUST be set.
788
 
            priority = self.server.gnutls_priority
789
 
            if priority is None:
790
 
                priority = u"NORMAL"
791
 
            (gnutls.library.functions
792
 
             .gnutls_priority_set_direct(session._c_object,
793
 
                                         priority, None))
794
 
            
795
 
            try:
796
 
                session.handshake()
797
 
            except gnutls.errors.GNUTLSError, error:
798
 
                logger.warning(u"Handshake failed: %s", error)
799
 
                # Do not run session.bye() here: the session is not
800
 
                # established.  Just abandon the request.
801
 
                return
802
 
            logger.debug(u"Handshake succeeded")
803
 
            try:
804
 
                fpr = self.fingerprint(self.peer_certificate(session))
805
 
            except (TypeError, gnutls.errors.GNUTLSError), error:
806
 
                logger.warning(u"Bad certificate: %s", error)
807
 
                session.bye()
808
 
                return
809
 
            logger.debug(u"Fingerprint: %s", fpr)
810
 
            
811
 
            for c in self.server.clients:
812
 
                if c.fingerprint == fpr:
813
 
                    client = c
814
 
                    break
815
 
            else:
816
 
                ipc.write(u"NOTFOUND %s %s\n"
817
 
                          % (fpr, unicode(self.client_address)))
818
 
                session.bye()
819
 
                return
820
 
            # Have to check if client.still_valid(), since it is
821
 
            # possible that the client timed out while establishing
822
 
            # the GnuTLS session.
823
 
            if not client.still_valid():
824
 
                ipc.write(u"INVALID %s\n" % client.name)
825
 
                session.bye()
826
 
                return
827
 
            ipc.write(u"SENDING %s\n" % client.name)
828
 
            sent_size = 0
829
 
            while sent_size < len(client.secret):
830
 
                sent = session.send(client.secret[sent_size:])
831
 
                logger.debug(u"Sent: %d, remaining: %d",
832
 
                             sent, len(client.secret)
833
 
                             - (sent_size + sent))
834
 
                sent_size += sent
835
 
            session.bye()
836
 
    
837
 
    @staticmethod
838
 
    def peer_certificate(session):
839
 
        "Return the peer's OpenPGP certificate as a bytestring"
840
 
        # If not an OpenPGP certificate...
841
 
        if (gnutls.library.functions
842
 
            .gnutls_certificate_type_get(session._c_object)
843
 
            != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
844
 
            # ...do the normal thing
845
 
            return session.peer_certificate
846
 
        list_size = ctypes.c_uint(1)
847
 
        cert_list = (gnutls.library.functions
848
 
                     .gnutls_certificate_get_peers
849
 
                     (session._c_object, ctypes.byref(list_size)))
850
 
        if not bool(cert_list) and list_size.value != 0:
851
 
            raise gnutls.errors.GNUTLSError(u"error getting peer"
852
 
                                            u" certificate")
853
 
        if list_size.value == 0:
854
 
            return None
855
 
        cert = cert_list[0]
856
 
        return ctypes.string_at(cert.data, cert.size)
857
 
    
858
 
    @staticmethod
859
 
    def fingerprint(openpgp):
860
 
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
861
 
        # New GnuTLS "datum" with the OpenPGP public key
862
 
        datum = (gnutls.library.types
863
 
                 .gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
864
 
                                             ctypes.POINTER
865
 
                                             (ctypes.c_ubyte)),
866
 
                                 ctypes.c_uint(len(openpgp))))
867
 
        # New empty GnuTLS certificate
868
 
        crt = gnutls.library.types.gnutls_openpgp_crt_t()
869
 
        (gnutls.library.functions
870
 
         .gnutls_openpgp_crt_init(ctypes.byref(crt)))
871
 
        # Import the OpenPGP public key into the certificate
872
 
        (gnutls.library.functions
873
 
         .gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
874
 
                                    gnutls.library.constants
875
 
                                    .GNUTLS_OPENPGP_FMT_RAW))
876
 
        # Verify the self signature in the key
877
 
        crtverify = ctypes.c_uint()
878
 
        (gnutls.library.functions
879
 
         .gnutls_openpgp_crt_verify_self(crt, 0,
880
 
                                         ctypes.byref(crtverify)))
881
 
        if crtverify.value != 0:
882
 
            gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
883
 
            raise (gnutls.errors.CertificateSecurityError
884
 
                   (u"Verify failed"))
885
 
        # New buffer for the fingerprint
886
 
        buf = ctypes.create_string_buffer(20)
887
 
        buf_len = ctypes.c_size_t()
888
 
        # Get the fingerprint from the certificate into the buffer
889
 
        (gnutls.library.functions
890
 
         .gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
891
 
                                             ctypes.byref(buf_len)))
892
 
        # Deinit the certificate
893
 
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
894
 
        # Convert the buffer to a Python bytestring
895
 
        fpr = ctypes.string_at(buf, buf_len.value)
896
 
        # Convert the bytestring to hexadecimal notation
897
 
        hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
898
 
        return hex_fpr
899
 
 
900
 
 
901
 
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
902
 
    """Like socketserver.ForkingMixIn, but also pass a pipe."""
903
 
    def process_request(self, request, client_address):
904
 
        """Overrides and wraps the original process_request().
905
 
        
906
 
        This function creates a new pipe in self.pipe
907
 
        """
908
 
        self.pipe = os.pipe()
909
 
        super(ForkingMixInWithPipe,
910
 
              self).process_request(request, client_address)
911
 
        os.close(self.pipe[1])  # close write end
912
 
        self.add_pipe(self.pipe[0])
913
 
    def add_pipe(self, pipe):
914
 
        """Dummy function; override as necessary"""
915
 
        os.close(pipe)
916
 
 
917
 
 
918
 
class IPv6_TCPServer(ForkingMixInWithPipe,
919
 
                     socketserver.TCPServer, object):
920
 
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
921
 
    
 
660
        session = (gnutls.connection
 
661
                   .ClientSession(self.request,
 
662
                                  gnutls.connection
 
663
                                  .X509Credentials()))
 
664
        
 
665
        line = self.request.makefile().readline()
 
666
        logger.debug(u"Protocol version: %r", line)
 
667
        try:
 
668
            if int(line.strip().split()[0]) > 1:
 
669
                raise RuntimeError
 
670
        except (ValueError, IndexError, RuntimeError), error:
 
671
            logger.error(u"Unknown protocol version: %s", error)
 
672
            return
 
673
        
 
674
        # Note: gnutls.connection.X509Credentials is really a generic
 
675
        # GnuTLS certificate credentials object so long as no X.509
 
676
        # keys are added to it.  Therefore, we can use it here despite
 
677
        # using OpenPGP certificates.
 
678
        
 
679
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
 
680
        #                     "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
 
681
        #                     "+DHE-DSS"))
 
682
        # Use a fallback default, since this MUST be set.
 
683
        priority = self.server.settings.get("priority", "NORMAL")
 
684
        (gnutls.library.functions
 
685
         .gnutls_priority_set_direct(session._c_object,
 
686
                                     priority, None))
 
687
        
 
688
        try:
 
689
            session.handshake()
 
690
        except gnutls.errors.GNUTLSError, error:
 
691
            logger.warning(u"Handshake failed: %s", error)
 
692
            # Do not run session.bye() here: the session is not
 
693
            # established.  Just abandon the request.
 
694
            return
 
695
        logger.debug(u"Handshake succeeded")
 
696
        try:
 
697
            fpr = fingerprint(peer_certificate(session))
 
698
        except (TypeError, gnutls.errors.GNUTLSError), error:
 
699
            logger.warning(u"Bad certificate: %s", error)
 
700
            session.bye()
 
701
            return
 
702
        logger.debug(u"Fingerprint: %s", fpr)
 
703
        
 
704
        for c in self.server.clients:
 
705
            if c.fingerprint == fpr:
 
706
                client = c
 
707
                break
 
708
        else:
 
709
            logger.warning(u"Client not found for fingerprint: %s",
 
710
                           fpr)
 
711
            session.bye()
 
712
            return
 
713
        # Have to check if client.still_valid(), since it is possible
 
714
        # that the client timed out while establishing the GnuTLS
 
715
        # session.
 
716
        if not client.still_valid():
 
717
            logger.warning(u"Client %(name)s is invalid",
 
718
                           vars(client))
 
719
            session.bye()
 
720
            return
 
721
        ## This won't work here, since we're in a fork.
 
722
        # client.checked_ok()
 
723
        sent_size = 0
 
724
        while sent_size < len(client.secret):
 
725
            sent = session.send(client.secret[sent_size:])
 
726
            logger.debug(u"Sent: %d, remaining: %d",
 
727
                         sent, len(client.secret)
 
728
                         - (sent_size + sent))
 
729
            sent_size += sent
 
730
        session.bye()
 
731
 
 
732
 
 
733
class IPv6_TCPServer(SocketServer.ForkingMixIn,
 
734
                     SocketServer.TCPServer, object):
 
735
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
922
736
    Attributes:
 
737
        settings:       Server settings
 
738
        clients:        Set() of Client objects
923
739
        enabled:        Boolean; whether this server is activated yet
924
 
        interface:      None or a network interface name (string)
925
 
        use_ipv6:       Boolean; to use IPv6 or not
926
740
    """
927
 
    def __init__(self, server_address, RequestHandlerClass,
928
 
                 interface=None, use_ipv6=True):
929
 
        self.interface = interface
930
 
        if use_ipv6:
931
 
            self.address_family = socket.AF_INET6
932
 
        socketserver.TCPServer.__init__(self, server_address,
933
 
                                        RequestHandlerClass)
 
741
    address_family = socket.AF_INET6
 
742
    def __init__(self, *args, **kwargs):
 
743
        if "settings" in kwargs:
 
744
            self.settings = kwargs["settings"]
 
745
            del kwargs["settings"]
 
746
        if "clients" in kwargs:
 
747
            self.clients = kwargs["clients"]
 
748
            del kwargs["clients"]
 
749
        self.enabled = False
 
750
        super(IPv6_TCPServer, self).__init__(*args, **kwargs)
934
751
    def server_bind(self):
935
752
        """This overrides the normal server_bind() function
936
753
        to bind to an interface if one was specified, and also NOT to
937
754
        bind to an address or port if they were not specified."""
938
 
        if self.interface is not None:
939
 
            if SO_BINDTODEVICE is None:
940
 
                logger.error(u"SO_BINDTODEVICE does not exist;"
941
 
                             u" cannot bind to interface %s",
942
 
                             self.interface)
943
 
            else:
944
 
                try:
945
 
                    self.socket.setsockopt(socket.SOL_SOCKET,
946
 
                                           SO_BINDTODEVICE,
947
 
                                           str(self.interface
948
 
                                               + u'\0'))
949
 
                except socket.error, error:
950
 
                    if error[0] == errno.EPERM:
951
 
                        logger.error(u"No permission to"
952
 
                                     u" bind to interface %s",
953
 
                                     self.interface)
954
 
                    elif error[0] == errno.ENOPROTOOPT:
955
 
                        logger.error(u"SO_BINDTODEVICE not available;"
956
 
                                     u" cannot bind to interface %s",
957
 
                                     self.interface)
958
 
                    else:
959
 
                        raise
 
755
        if self.settings["interface"]:
 
756
            # 25 is from /usr/include/asm-i486/socket.h
 
757
            SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
 
758
            try:
 
759
                self.socket.setsockopt(socket.SOL_SOCKET,
 
760
                                       SO_BINDTODEVICE,
 
761
                                       self.settings["interface"])
 
762
            except socket.error, error:
 
763
                if error[0] == errno.EPERM:
 
764
                    logger.error(u"No permission to"
 
765
                                 u" bind to interface %s",
 
766
                                 self.settings["interface"])
 
767
                else:
 
768
                    raise
960
769
        # Only bind(2) the socket if we really need to.
961
770
        if self.server_address[0] or self.server_address[1]:
962
771
            if not self.server_address[0]:
963
 
                if self.address_family == socket.AF_INET6:
964
 
                    any_address = u"::" # in6addr_any
965
 
                else:
966
 
                    any_address = socket.INADDR_ANY
967
 
                self.server_address = (any_address,
 
772
                in6addr_any = "::"
 
773
                self.server_address = (in6addr_any,
968
774
                                       self.server_address[1])
969
775
            elif not self.server_address[1]:
970
776
                self.server_address = (self.server_address[0],
971
777
                                       0)
972
 
#                 if self.interface:
 
778
#                 if self.settings["interface"]:
973
779
#                     self.server_address = (self.server_address[0],
974
780
#                                            0, # port
975
781
#                                            0, # flowinfo
976
782
#                                            if_nametoindex
977
 
#                                            (self.interface))
978
 
            return socketserver.TCPServer.server_bind(self)
979
 
 
980
 
 
981
 
class MandosServer(IPv6_TCPServer):
982
 
    """Mandos server.
983
 
    
984
 
    Attributes:
985
 
        clients:        set of Client objects
986
 
        gnutls_priority GnuTLS priority string
987
 
        use_dbus:       Boolean; to emit D-Bus signals or not
988
 
    
989
 
    Assumes a gobject.MainLoop event loop.
990
 
    """
991
 
    def __init__(self, server_address, RequestHandlerClass,
992
 
                 interface=None, use_ipv6=True, clients=None,
993
 
                 gnutls_priority=None, use_dbus=True):
994
 
        self.enabled = False
995
 
        self.clients = clients
996
 
        if self.clients is None:
997
 
            self.clients = set()
998
 
        self.use_dbus = use_dbus
999
 
        self.gnutls_priority = gnutls_priority
1000
 
        IPv6_TCPServer.__init__(self, server_address,
1001
 
                                RequestHandlerClass,
1002
 
                                interface = interface,
1003
 
                                use_ipv6 = use_ipv6)
 
783
#                                            (self.settings
 
784
#                                             ["interface"]))
 
785
            return super(IPv6_TCPServer, self).server_bind()
1004
786
    def server_activate(self):
1005
787
        if self.enabled:
1006
 
            return socketserver.TCPServer.server_activate(self)
 
788
            return super(IPv6_TCPServer, self).server_activate()
1007
789
    def enable(self):
1008
790
        self.enabled = True
1009
 
    def add_pipe(self, pipe):
1010
 
        # Call "handle_ipc" for both data and EOF events
1011
 
        gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1012
 
                             self.handle_ipc)
1013
 
    def handle_ipc(self, source, condition, file_objects={}):
1014
 
        condition_names = {
1015
 
            gobject.IO_IN: u"IN",   # There is data to read.
1016
 
            gobject.IO_OUT: u"OUT", # Data can be written (without
1017
 
                                    # blocking).
1018
 
            gobject.IO_PRI: u"PRI", # There is urgent data to read.
1019
 
            gobject.IO_ERR: u"ERR", # Error condition.
1020
 
            gobject.IO_HUP: u"HUP"  # Hung up (the connection has been
1021
 
                                    # broken, usually for pipes and
1022
 
                                    # sockets).
1023
 
            }
1024
 
        conditions_string = ' | '.join(name
1025
 
                                       for cond, name in
1026
 
                                       condition_names.iteritems()
1027
 
                                       if cond & condition)
1028
 
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1029
 
                     conditions_string)
1030
 
        
1031
 
        # Turn the pipe file descriptor into a Python file object
1032
 
        if source not in file_objects:
1033
 
            file_objects[source] = os.fdopen(source, u"r", 1)
1034
 
        
1035
 
        # Read a line from the file object
1036
 
        cmdline = file_objects[source].readline()
1037
 
        if not cmdline:             # Empty line means end of file
1038
 
            # close the IPC pipe
1039
 
            file_objects[source].close()
1040
 
            del file_objects[source]
1041
 
            
1042
 
            # Stop calling this function
1043
 
            return False
1044
 
        
1045
 
        logger.debug(u"IPC command: %r", cmdline)
1046
 
        
1047
 
        # Parse and act on command
1048
 
        cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1049
 
        
1050
 
        if cmd == u"NOTFOUND":
1051
 
            logger.warning(u"Client not found for fingerprint: %s",
1052
 
                           args)
1053
 
            if self.use_dbus:
1054
 
                # Emit D-Bus signal
1055
 
                mandos_dbus_service.ClientNotFound(args)
1056
 
        elif cmd == u"INVALID":
1057
 
            for client in self.clients:
1058
 
                if client.name == args:
1059
 
                    logger.warning(u"Client %s is invalid", args)
1060
 
                    if self.use_dbus:
1061
 
                        # Emit D-Bus signal
1062
 
                        client.Rejected()
1063
 
                    break
1064
 
            else:
1065
 
                logger.error(u"Unknown client %s is invalid", args)
1066
 
        elif cmd == u"SENDING":
1067
 
            for client in self.clients:
1068
 
                if client.name == args:
1069
 
                    logger.info(u"Sending secret to %s", client.name)
1070
 
                    client.checked_ok()
1071
 
                    if self.use_dbus:
1072
 
                        # Emit D-Bus signal
1073
 
                        client.ReceivedSecret()
1074
 
                    break
1075
 
            else:
1076
 
                logger.error(u"Sending secret to unknown client %s",
1077
 
                             args)
1078
 
        else:
1079
 
            logger.error(u"Unknown IPC command: %r", cmdline)
1080
 
        
1081
 
        # Keep calling this function
1082
 
        return True
1083
791
 
1084
792
 
1085
793
def string_to_delta(interval):
1086
794
    """Parse a string and return a datetime.timedelta
1087
795
    
1088
 
    >>> string_to_delta(u'7d')
 
796
    >>> string_to_delta('7d')
1089
797
    datetime.timedelta(7)
1090
 
    >>> string_to_delta(u'60s')
 
798
    >>> string_to_delta('60s')
1091
799
    datetime.timedelta(0, 60)
1092
 
    >>> string_to_delta(u'60m')
 
800
    >>> string_to_delta('60m')
1093
801
    datetime.timedelta(0, 3600)
1094
 
    >>> string_to_delta(u'24h')
 
802
    >>> string_to_delta('24h')
1095
803
    datetime.timedelta(1)
1096
804
    >>> string_to_delta(u'1w')
1097
805
    datetime.timedelta(7)
1098
 
    >>> string_to_delta(u'5m 30s')
 
806
    >>> string_to_delta('5m 30s')
1099
807
    datetime.timedelta(0, 330)
1100
808
    """
1101
809
    timevalue = datetime.timedelta(0)
1121
829
    return timevalue
1122
830
 
1123
831
 
 
832
def server_state_changed(state):
 
833
    """Derived from the Avahi example code"""
 
834
    if state == avahi.SERVER_COLLISION:
 
835
        logger.error(u"Zeroconf server name collision")
 
836
        service.remove()
 
837
    elif state == avahi.SERVER_RUNNING:
 
838
        service.add()
 
839
 
 
840
 
 
841
def entry_group_state_changed(state, error):
 
842
    """Derived from the Avahi example code"""
 
843
    logger.debug(u"Avahi state change: %i", state)
 
844
    
 
845
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
 
846
        logger.debug(u"Zeroconf service established.")
 
847
    elif state == avahi.ENTRY_GROUP_COLLISION:
 
848
        logger.warning(u"Zeroconf service name collision.")
 
849
        service.rename()
 
850
    elif state == avahi.ENTRY_GROUP_FAILURE:
 
851
        logger.critical(u"Avahi: Error in group state changed %s",
 
852
                        unicode(error))
 
853
        raise AvahiGroupError(u"State changed: %s" % unicode(error))
 
854
 
1124
855
def if_nametoindex(interface):
1125
 
    """Call the C function if_nametoindex(), or equivalent
1126
 
    
1127
 
    Note: This function cannot accept a unicode string."""
 
856
    """Call the C function if_nametoindex(), or equivalent"""
1128
857
    global if_nametoindex
1129
858
    try:
1130
859
        if_nametoindex = (ctypes.cdll.LoadLibrary
1131
 
                          (ctypes.util.find_library(u"c"))
 
860
                          (ctypes.util.find_library("c"))
1132
861
                          .if_nametoindex)
1133
862
    except (OSError, AttributeError):
1134
 
        logger.warning(u"Doing if_nametoindex the hard way")
 
863
        if "struct" not in sys.modules:
 
864
            import struct
 
865
        if "fcntl" not in sys.modules:
 
866
            import fcntl
1135
867
        def if_nametoindex(interface):
1136
868
            "Get an interface index the hard way, i.e. using fcntl()"
1137
869
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
1138
870
            with closing(socket.socket()) as s:
1139
871
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1140
 
                                    struct.pack(str(u"16s16x"),
1141
 
                                                interface))
1142
 
            interface_index = struct.unpack(str(u"I"),
1143
 
                                            ifreq[16:20])[0]
 
872
                                    struct.pack("16s16x", interface))
 
873
            interface_index = struct.unpack("I", ifreq[16:20])[0]
1144
874
            return interface_index
1145
875
    return if_nametoindex(interface)
1146
876
 
1147
877
 
1148
878
def daemon(nochdir = False, noclose = False):
1149
879
    """See daemon(3).  Standard BSD Unix function.
1150
 
    
1151
880
    This should really exist as os.daemon, but it doesn't (yet)."""
1152
881
    if os.fork():
1153
882
        sys.exit()
1154
883
    os.setsid()
1155
884
    if not nochdir:
1156
 
        os.chdir(u"/")
 
885
        os.chdir("/")
1157
886
    if os.fork():
1158
887
        sys.exit()
1159
888
    if not noclose:
1161
890
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1162
891
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1163
892
            raise OSError(errno.ENODEV,
1164
 
                          u"/dev/null not a character device")
 
893
                          "/dev/null not a character device")
1165
894
        os.dup2(null, sys.stdin.fileno())
1166
895
        os.dup2(null, sys.stdout.fileno())
1167
896
        os.dup2(null, sys.stderr.fileno())
1170
899
 
1171
900
 
1172
901
def main():
1173
 
    
1174
 
    ##################################################################
1175
 
    # Parsing of options, both command line and config file
1176
 
    
1177
902
    parser = optparse.OptionParser(version = "%%prog %s" % version)
1178
 
    parser.add_option("-i", u"--interface", type=u"string",
1179
 
                      metavar="IF", help=u"Bind to interface IF")
1180
 
    parser.add_option("-a", u"--address", type=u"string",
1181
 
                      help=u"Address to listen for requests on")
1182
 
    parser.add_option("-p", u"--port", type=u"int",
1183
 
                      help=u"Port number to receive requests on")
1184
 
    parser.add_option("--check", action=u"store_true",
1185
 
                      help=u"Run self-test")
1186
 
    parser.add_option("--debug", action=u"store_true",
1187
 
                      help=u"Debug mode; run in foreground and log to"
1188
 
                      u" terminal")
1189
 
    parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1190
 
                      u" priority string (see GnuTLS documentation)")
1191
 
    parser.add_option("--servicename", type=u"string",
1192
 
                      metavar=u"NAME", help=u"Zeroconf service name")
1193
 
    parser.add_option("--configdir", type=u"string",
1194
 
                      default=u"/etc/mandos", metavar=u"DIR",
1195
 
                      help=u"Directory to search for configuration"
1196
 
                      u" files")
1197
 
    parser.add_option("--no-dbus", action=u"store_false",
1198
 
                      dest=u"use_dbus", help=u"Do not provide D-Bus"
1199
 
                      u" system bus interface")
1200
 
    parser.add_option("--no-ipv6", action=u"store_false",
1201
 
                      dest=u"use_ipv6", help=u"Do not use IPv6")
 
903
    parser.add_option("-i", "--interface", type="string",
 
904
                      metavar="IF", help="Bind to interface IF")
 
905
    parser.add_option("-a", "--address", type="string",
 
906
                      help="Address to listen for requests on")
 
907
    parser.add_option("-p", "--port", type="int",
 
908
                      help="Port number to receive requests on")
 
909
    parser.add_option("--check", action="store_true",
 
910
                      help="Run self-test")
 
911
    parser.add_option("--debug", action="store_true",
 
912
                      help="Debug mode; run in foreground and log to"
 
913
                      " terminal")
 
914
    parser.add_option("--priority", type="string", help="GnuTLS"
 
915
                      " priority string (see GnuTLS documentation)")
 
916
    parser.add_option("--servicename", type="string", metavar="NAME",
 
917
                      help="Zeroconf service name")
 
918
    parser.add_option("--configdir", type="string",
 
919
                      default="/etc/mandos", metavar="DIR",
 
920
                      help="Directory to search for configuration"
 
921
                      " files")
 
922
    parser.add_option("--no-dbus", action="store_false",
 
923
                      dest="use_dbus",
 
924
                      help="Do not provide D-Bus system bus"
 
925
                      " interface")
1202
926
    options = parser.parse_args()[0]
1203
927
    
1204
928
    if options.check:
1207
931
        sys.exit()
1208
932
    
1209
933
    # Default values for config file for server-global settings
1210
 
    server_defaults = { u"interface": u"",
1211
 
                        u"address": u"",
1212
 
                        u"port": u"",
1213
 
                        u"debug": u"False",
1214
 
                        u"priority":
1215
 
                        u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1216
 
                        u"servicename": u"Mandos",
1217
 
                        u"use_dbus": u"True",
1218
 
                        u"use_ipv6": u"True",
 
934
    server_defaults = { "interface": "",
 
935
                        "address": "",
 
936
                        "port": "",
 
937
                        "debug": "False",
 
938
                        "priority":
 
939
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
 
940
                        "servicename": "Mandos",
 
941
                        "use_dbus": "True",
1219
942
                        }
1220
943
    
1221
944
    # Parse config file for server-global settings
1222
 
    server_config = configparser.SafeConfigParser(server_defaults)
 
945
    server_config = ConfigParser.SafeConfigParser(server_defaults)
1223
946
    del server_defaults
1224
 
    server_config.read(os.path.join(options.configdir,
1225
 
                                    u"mandos.conf"))
 
947
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
1226
948
    # Convert the SafeConfigParser object to a dict
1227
949
    server_settings = server_config.defaults()
1228
950
    # Use the appropriate methods on the non-string config options
1229
 
    for option in (u"debug", u"use_dbus", u"use_ipv6"):
1230
 
        server_settings[option] = server_config.getboolean(u"DEFAULT",
1231
 
                                                           option)
 
951
    server_settings["debug"] = server_config.getboolean("DEFAULT",
 
952
                                                        "debug")
 
953
    server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
 
954
                                                           "use_dbus")
1232
955
    if server_settings["port"]:
1233
 
        server_settings["port"] = server_config.getint(u"DEFAULT",
1234
 
                                                       u"port")
 
956
        server_settings["port"] = server_config.getint("DEFAULT",
 
957
                                                       "port")
1235
958
    del server_config
1236
959
    
1237
960
    # Override the settings from the config file with command line
1238
961
    # options, if set.
1239
 
    for option in (u"interface", u"address", u"port", u"debug",
1240
 
                   u"priority", u"servicename", u"configdir",
1241
 
                   u"use_dbus", u"use_ipv6"):
 
962
    for option in ("interface", "address", "port", "debug",
 
963
                   "priority", "servicename", "configdir",
 
964
                   "use_dbus"):
1242
965
        value = getattr(options, option)
1243
966
        if value is not None:
1244
967
            server_settings[option] = value
1245
968
    del options
1246
 
    # Force all strings to be unicode
1247
 
    for option in server_settings.keys():
1248
 
        if type(server_settings[option]) is str:
1249
 
            server_settings[option] = unicode(server_settings[option])
1250
969
    # Now we have our good server settings in "server_settings"
1251
970
    
1252
 
    ##################################################################
1253
 
    
1254
971
    # For convenience
1255
 
    debug = server_settings[u"debug"]
1256
 
    use_dbus = server_settings[u"use_dbus"]
1257
 
    use_ipv6 = server_settings[u"use_ipv6"]
 
972
    debug = server_settings["debug"]
 
973
    use_dbus = server_settings["use_dbus"]
1258
974
    
1259
975
    if not debug:
1260
976
        syslogger.setLevel(logging.WARNING)
1261
977
        console.setLevel(logging.WARNING)
1262
978
    
1263
 
    if server_settings[u"servicename"] != u"Mandos":
 
979
    if server_settings["servicename"] != "Mandos":
1264
980
        syslogger.setFormatter(logging.Formatter
1265
 
                               (u'Mandos (%s) [%%(process)d]:'
1266
 
                                u' %%(levelname)s: %%(message)s'
1267
 
                                % server_settings[u"servicename"]))
 
981
                               ('Mandos (%s): %%(levelname)s:'
 
982
                                ' %%(message)s'
 
983
                                % server_settings["servicename"]))
1268
984
    
1269
985
    # Parse config file with clients
1270
 
    client_defaults = { u"timeout": u"1h",
1271
 
                        u"interval": u"5m",
1272
 
                        u"checker": u"fping -q -- %%(host)s",
1273
 
                        u"host": u"",
 
986
    client_defaults = { "timeout": "1h",
 
987
                        "interval": "5m",
 
988
                        "checker": "fping -q -- %%(host)s",
 
989
                        "host": "",
1274
990
                        }
1275
 
    client_config = configparser.SafeConfigParser(client_defaults)
1276
 
    client_config.read(os.path.join(server_settings[u"configdir"],
1277
 
                                    u"clients.conf"))
1278
 
    
1279
 
    global mandos_dbus_service
1280
 
    mandos_dbus_service = None
1281
 
    
1282
 
    tcp_server = MandosServer((server_settings[u"address"],
1283
 
                               server_settings[u"port"]),
1284
 
                              ClientHandler,
1285
 
                              interface=server_settings[u"interface"],
1286
 
                              use_ipv6=use_ipv6,
1287
 
                              gnutls_priority=
1288
 
                              server_settings[u"priority"],
1289
 
                              use_dbus=use_dbus)
1290
 
    pidfilename = u"/var/run/mandos.pid"
 
991
    client_config = ConfigParser.SafeConfigParser(client_defaults)
 
992
    client_config.read(os.path.join(server_settings["configdir"],
 
993
                                    "clients.conf"))
 
994
    
 
995
    clients = Set()
 
996
    tcp_server = IPv6_TCPServer((server_settings["address"],
 
997
                                 server_settings["port"]),
 
998
                                TCP_handler,
 
999
                                settings=server_settings,
 
1000
                                clients=clients)
 
1001
    pidfilename = "/var/run/mandos.pid"
1291
1002
    try:
1292
 
        pidfile = open(pidfilename, u"w")
 
1003
        pidfile = open(pidfilename, "w")
1293
1004
    except IOError:
1294
 
        logger.error(u"Could not open file %r", pidfilename)
 
1005
        logger.error("Could not open file %r", pidfilename)
1295
1006
    
1296
1007
    try:
1297
 
        uid = pwd.getpwnam(u"_mandos").pw_uid
1298
 
        gid = pwd.getpwnam(u"_mandos").pw_gid
 
1008
        uid = pwd.getpwnam("_mandos").pw_uid
 
1009
        gid = pwd.getpwnam("_mandos").pw_gid
1299
1010
    except KeyError:
1300
1011
        try:
1301
 
            uid = pwd.getpwnam(u"mandos").pw_uid
1302
 
            gid = pwd.getpwnam(u"mandos").pw_gid
 
1012
            uid = pwd.getpwnam("mandos").pw_uid
 
1013
            gid = pwd.getpwnam("mandos").pw_gid
1303
1014
        except KeyError:
1304
1015
            try:
1305
 
                uid = pwd.getpwnam(u"nobody").pw_uid
1306
 
                gid = pwd.getpwnam(u"nobody").pw_gid
 
1016
                uid = pwd.getpwnam("nobody").pw_uid
 
1017
                gid = pwd.getpwnam("nogroup").pw_gid
1307
1018
            except KeyError:
1308
1019
                uid = 65534
1309
1020
                gid = 65534
1322
1033
        
1323
1034
        @gnutls.library.types.gnutls_log_func
1324
1035
        def debug_gnutls(level, string):
1325
 
            logger.debug(u"GnuTLS: %s", string[:-1])
 
1036
            logger.debug("GnuTLS: %s", string[:-1])
1326
1037
        
1327
1038
        (gnutls.library.functions
1328
1039
         .gnutls_global_set_log_function(debug_gnutls))
1329
1040
    
 
1041
    global service
 
1042
    service = AvahiService(name = server_settings["servicename"],
 
1043
                           servicetype = "_mandos._tcp", )
 
1044
    if server_settings["interface"]:
 
1045
        service.interface = (if_nametoindex
 
1046
                             (server_settings["interface"]))
 
1047
    
1330
1048
    global main_loop
 
1049
    global bus
 
1050
    global server
1331
1051
    # From the Avahi example code
1332
1052
    DBusGMainLoop(set_as_default=True )
1333
1053
    main_loop = gobject.MainLoop()
1334
1054
    bus = dbus.SystemBus()
 
1055
    server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
 
1056
                                           avahi.DBUS_PATH_SERVER),
 
1057
                            avahi.DBUS_INTERFACE_SERVER)
1335
1058
    # End of Avahi example code
1336
1059
    if use_dbus:
1337
1060
        bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1338
 
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1339
 
    service = AvahiService(name = server_settings[u"servicename"],
1340
 
                           servicetype = u"_mandos._tcp",
1341
 
                           protocol = protocol, bus = bus)
1342
 
    if server_settings["interface"]:
1343
 
        service.interface = (if_nametoindex
1344
 
                             (str(server_settings[u"interface"])))
1345
1061
    
1346
 
    client_class = Client
1347
 
    if use_dbus:
1348
 
        client_class = functools.partial(ClientDBus, bus = bus)
1349
 
    tcp_server.clients.update(set(
1350
 
            client_class(name = section,
1351
 
                         config= dict(client_config.items(section)))
1352
 
            for section in client_config.sections()))
1353
 
    if not tcp_server.clients:
 
1062
    clients.update(Set(Client(name = section,
 
1063
                              config
 
1064
                              = dict(client_config.items(section)),
 
1065
                              use_dbus = use_dbus)
 
1066
                       for section in client_config.sections()))
 
1067
    if not clients:
1354
1068
        logger.warning(u"No clients defined")
1355
1069
    
1356
1070
    if debug:
1366
1080
        daemon()
1367
1081
    
1368
1082
    try:
1369
 
        with closing(pidfile):
1370
 
            pid = os.getpid()
1371
 
            pidfile.write(str(pid) + "\n")
 
1083
        pid = os.getpid()
 
1084
        pidfile.write(str(pid) + "\n")
 
1085
        pidfile.close()
1372
1086
        del pidfile
1373
1087
    except IOError:
1374
1088
        logger.error(u"Could not write to file %r with PID %d",
1380
1094
    
1381
1095
    def cleanup():
1382
1096
        "Cleanup function; run on exit"
1383
 
        service.cleanup()
 
1097
        global group
 
1098
        # From the Avahi example code
 
1099
        if not group is None:
 
1100
            group.Free()
 
1101
            group = None
 
1102
        # End of Avahi example code
1384
1103
        
1385
 
        while tcp_server.clients:
1386
 
            client = tcp_server.clients.pop()
 
1104
        while clients:
 
1105
            client = clients.pop()
1387
1106
            client.disable_hook = None
1388
1107
            client.disable()
1389
1108
    
1395
1114
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1396
1115
    
1397
1116
    if use_dbus:
1398
 
        class MandosDBusService(dbus.service.Object):
 
1117
        class MandosServer(dbus.service.Object):
1399
1118
            """A D-Bus proxy object"""
1400
1119
            def __init__(self):
1401
 
                dbus.service.Object.__init__(self, bus, u"/")
 
1120
                dbus.service.Object.__init__(self, bus, "/")
1402
1121
            _interface = u"se.bsnet.fukt.Mandos"
1403
1122
            
1404
 
            @dbus.service.signal(_interface, signature=u"oa{sv}")
 
1123
            @dbus.service.signal(_interface, signature="oa{sv}")
1405
1124
            def ClientAdded(self, objpath, properties):
1406
1125
                "D-Bus signal"
1407
1126
                pass
1408
1127
            
1409
 
            @dbus.service.signal(_interface, signature=u"s")
1410
 
            def ClientNotFound(self, fingerprint):
1411
 
                "D-Bus signal"
1412
 
                pass
1413
 
            
1414
 
            @dbus.service.signal(_interface, signature=u"os")
 
1128
            @dbus.service.signal(_interface, signature="os")
1415
1129
            def ClientRemoved(self, objpath, name):
1416
1130
                "D-Bus signal"
1417
1131
                pass
1418
1132
            
1419
 
            @dbus.service.method(_interface, out_signature=u"ao")
 
1133
            @dbus.service.method(_interface, out_signature="ao")
1420
1134
            def GetAllClients(self):
1421
1135
                "D-Bus method"
1422
 
                return dbus.Array(c.dbus_object_path
1423
 
                                  for c in tcp_server.clients)
 
1136
                return dbus.Array(c.dbus_object_path for c in clients)
1424
1137
            
1425
 
            @dbus.service.method(_interface,
1426
 
                                 out_signature=u"a{oa{sv}}")
 
1138
            @dbus.service.method(_interface, out_signature="a{oa{sv}}")
1427
1139
            def GetAllClientsWithProperties(self):
1428
1140
                "D-Bus method"
1429
1141
                return dbus.Dictionary(
1430
1142
                    ((c.dbus_object_path, c.GetAllProperties())
1431
 
                     for c in tcp_server.clients),
1432
 
                    signature=u"oa{sv}")
 
1143
                     for c in clients),
 
1144
                    signature="oa{sv}")
1433
1145
            
1434
 
            @dbus.service.method(_interface, in_signature=u"o")
 
1146
            @dbus.service.method(_interface, in_signature="o")
1435
1147
            def RemoveClient(self, object_path):
1436
1148
                "D-Bus method"
1437
 
                for c in tcp_server.clients:
 
1149
                for c in clients:
1438
1150
                    if c.dbus_object_path == object_path:
1439
 
                        tcp_server.clients.remove(c)
1440
 
                        c.remove_from_connection()
 
1151
                        clients.remove(c)
1441
1152
                        # Don't signal anything except ClientRemoved
1442
 
                        c.disable(signal=False)
 
1153
                        c.use_dbus = False
 
1154
                        c.disable()
1443
1155
                        # Emit D-Bus signal
1444
1156
                        self.ClientRemoved(object_path, c.name)
1445
1157
                        return
1447
1159
            
1448
1160
            del _interface
1449
1161
        
1450
 
        mandos_dbus_service = MandosDBusService()
 
1162
        mandos_server = MandosServer()
1451
1163
    
1452
 
    for client in tcp_server.clients:
 
1164
    for client in clients:
1453
1165
        if use_dbus:
1454
1166
            # Emit D-Bus signal
1455
 
            mandos_dbus_service.ClientAdded(client.dbus_object_path,
1456
 
                                            client.GetAllProperties())
 
1167
            mandos_server.ClientAdded(client.dbus_object_path,
 
1168
                                      client.GetAllProperties())
1457
1169
        client.enable()
1458
1170
    
1459
1171
    tcp_server.enable()
1461
1173
    
1462
1174
    # Find out what port we got
1463
1175
    service.port = tcp_server.socket.getsockname()[1]
1464
 
    if use_ipv6:
1465
 
        logger.info(u"Now listening on address %r, port %d,"
1466
 
                    " flowinfo %d, scope_id %d"
1467
 
                    % tcp_server.socket.getsockname())
1468
 
    else:                       # IPv4
1469
 
        logger.info(u"Now listening on address %r, port %d"
1470
 
                    % tcp_server.socket.getsockname())
 
1176
    logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
 
1177
                u" scope_id %d" % tcp_server.socket.getsockname())
1471
1178
    
1472
1179
    #service.interface = tcp_server.socket.getsockname()[3]
1473
1180
    
1474
1181
    try:
1475
1182
        # From the Avahi example code
 
1183
        server.connect_to_signal("StateChanged", server_state_changed)
1476
1184
        try:
1477
 
            service.activate()
 
1185
            server_state_changed(server.GetState())
1478
1186
        except dbus.exceptions.DBusException, error:
1479
1187
            logger.critical(u"DBusException: %s", error)
1480
1188
            sys.exit(1)
1493
1201
    except KeyboardInterrupt:
1494
1202
        if debug:
1495
1203
            print >> sys.stderr
1496
 
        logger.debug(u"Server received KeyboardInterrupt")
1497
 
    logger.debug(u"Server exiting")
 
1204
        logger.debug("Server received KeyboardInterrupt")
 
1205
    logger.debug("Server exiting")
1498
1206
 
1499
1207
if __name__ == '__main__':
1500
1208
    main()