/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 mandos

  • Committer: Teddy Hogeborn
  • Date: 2008-11-09 06:40:29 UTC
  • mto: (237.2.1 mandos) (24.1.113 mandos)
  • mto: This revision was merged to the branch mainline in revision 238.
  • Revision ID: teddy@fukt.bsnet.se-20081109064029-df71jpoce308cq3v
First steps of a D-Bus interface to the server.

* mandos: Also import "dbus.service".
  (Client): Inherit from "dbus.service.Object", which is a new-style
            class, so inheriting from "object" is no longer necessary.
  (Client.interface): New temporary variable which only exists during
                     class definition.

  (Client.getName, Client.getFingerprint): New D-Bus getter methods.
  (Client.setSecret): New D-Bus setter method.
  (Client._set_timeout): Emit D-Bus signal "TimeoutChanged".
  (Client.getTimeout): New D-Bus getter method.
  (Client.TimeoutChanged): New D-Bus signal.
  (Client._set_interval): Emit D-Bus signal "IntervalChanged".
  (Client.getInterval): New D-Bus getter method.
  (Client.intervalChanged): New D-Bus signal.
  (Client.__init__): Also call "dbus.service.Object.__init__".
  (Client.started): New boolean attribute.
  (Client.start, Client.stop): Update "self.started", and emit D-Bus
                               signal "StateChanged".
  (Client.StateChanged): New D-Bus signal.
  (Client.stop): Use "self.started" instead of misusing "self.secret".
                 Also simplify code by using "getattr" instead of
                 "hasattr".
  (Client.checker_callback): Emit D-Bus signal "CheckerCompleted".
  (Client.CheckerCompleted): New D-Bus signal.
  (Client.bumpTimeout): D-Bus method name for "bump_timeout".
  (Client.start_checker): Emit D-Bus signal "CheckerStarted".
  (Client.CheckerStarted): New D-Bus signal.
  (Client.checkerIsRunning): New D-Bus method.
  (Client.StopChecker): D-Bus method name for "stop_checker".
  (Client.still_valid): First check "self.started".
  (Client.stillValid): D-Bus method name for "still_valid".

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
# and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson
 
14
# Copyright © 2008 Teddy Hogeborn & Björn Påhlsson
15
15
16
16
# This program is free software: you can redistribute it and/or modify
17
17
# it under the terms of the GNU General Public License as published by
24
24
#     GNU General Public License for more details.
25
25
26
26
# You should have received a copy of the GNU General Public License
27
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
27
# along with this program.  If not, see
 
28
# <http://www.gnu.org/licenses/>.
28
29
29
30
# Contact the authors at <mandos@fukt.bsnet.se>.
30
31
31
32
 
32
 
from __future__ import division
 
33
from __future__ import division, with_statement, absolute_import
33
34
 
34
35
import SocketServer
35
36
import socket
36
 
import select
37
37
from optparse import OptionParser
38
38
import datetime
39
39
import errno
54
54
import stat
55
55
import logging
56
56
import logging.handlers
 
57
import pwd
 
58
from contextlib import closing
57
59
 
58
60
import dbus
 
61
import dbus.service
59
62
import gobject
60
63
import avahi
61
64
from dbus.mainloop.glib import DBusGMainLoop
62
65
import ctypes
 
66
import ctypes.util
63
67
 
64
 
version = "1.0"
 
68
version = "1.0.2"
65
69
 
66
70
logger = logging.Logger('mandos')
67
71
syslogger = logging.handlers.SysLogHandler\
79
83
class AvahiError(Exception):
80
84
    def __init__(self, value):
81
85
        self.value = value
 
86
        super(AvahiError, self).__init__()
82
87
    def __str__(self):
83
88
        return repr(self.value)
84
89
 
106
111
                  a sensible number of times
107
112
    """
108
113
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
109
 
                 type = None, port = None, TXT = None, domain = "",
 
114
                 servicetype = None, port = None, TXT = None, domain = "",
110
115
                 host = "", max_renames = 32768):
111
116
        self.interface = interface
112
117
        self.name = name
113
 
        self.type = type
 
118
        self.type = servicetype
114
119
        self.port = port
115
120
        if TXT is None:
116
121
            self.TXT = []
123
128
    def rename(self):
124
129
        """Derived from the Avahi example code"""
125
130
        if self.rename_count >= self.max_renames:
126
 
            logger.critical(u"No suitable service name found after %i"
127
 
                            u" retries, exiting.", rename_count)
 
131
            logger.critical(u"No suitable Zeroconf service name found"
 
132
                            u" after %i retries, exiting.",
 
133
                            self.rename_count)
128
134
            raise AvahiServiceError("Too many renames")
129
135
        self.name = server.GetAlternativeServiceName(self.name)
130
 
        logger.info(u"Changing name to %r ...", str(self.name))
 
136
        logger.info(u"Changing Zeroconf service name to %r ...",
 
137
                    str(self.name))
131
138
        syslogger.setFormatter(logging.Formatter\
132
139
                               ('Mandos (%s): %%(levelname)s:'
133
140
                               ' %%(message)s' % self.name))
148
155
                     avahi.DBUS_INTERFACE_ENTRY_GROUP)
149
156
            group.connect_to_signal('StateChanged',
150
157
                                    entry_group_state_changed)
151
 
        logger.debug(u"Adding service '%s' of type '%s' ...",
 
158
        logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
152
159
                     service.name, service.type)
153
160
        group.AddService(
154
161
                self.interface,         # interface
165
172
# End of Avahi example code
166
173
 
167
174
 
168
 
class Client(object):
 
175
class Client(dbus.service.Object):
169
176
    """A representation of a client host served by this server.
170
177
    Attributes:
171
178
    name:      string; from the config file, used in log messages
174
181
    secret:    bytestring; sent verbatim (over TLS) to client
175
182
    host:      string; available for use by the checker command
176
183
    created:   datetime.datetime(); object creation, not client host
 
184
    started:   bool()
177
185
    last_checked_ok: datetime.datetime() or None if not yet checked OK
178
186
    timeout:   datetime.timedelta(); How long from last_checked_ok
179
187
                                     until this client is invalid
195
203
    _timeout_milliseconds: Used when calling gobject.timeout_add()
196
204
    _interval_milliseconds: - '' -
197
205
    """
 
206
    interface = u"org.mandos_system.Mandos.Clients"
 
207
    
 
208
    @dbus.service.method(interface, out_signature="s")
 
209
    def getName(self):
 
210
        "D-Bus getter method"
 
211
        return self.name
 
212
    
 
213
    @dbus.service.method(interface, out_signature="s")
 
214
    def getFingerprint(self):
 
215
        "D-Bus getter method"
 
216
        return self.fingerprint
 
217
    
 
218
    @dbus.service.method(interface, in_signature="ay",
 
219
                         byte_arrays=True)
 
220
    def setSecret(self, secret):
 
221
        "D-Bus setter method"
 
222
        self.secret = secret
 
223
    
198
224
    def _set_timeout(self, timeout):
199
 
        "Setter function for 'timeout' attribute"
 
225
        "Setter function for the 'timeout' attribute"
200
226
        self._timeout = timeout
201
227
        self._timeout_milliseconds = ((self.timeout.days
202
228
                                       * 24 * 60 * 60 * 1000)
203
229
                                      + (self.timeout.seconds * 1000)
204
230
                                      + (self.timeout.microseconds
205
231
                                         // 1000))
206
 
    timeout = property(lambda self: self._timeout,
207
 
                       _set_timeout)
 
232
        # Emit D-Bus signal
 
233
        self.TimeoutChanged(self._timeout_milliseconds)
 
234
    timeout = property(lambda self: self._timeout, _set_timeout)
208
235
    del _set_timeout
 
236
    
 
237
    @dbus.service.method(interface, out_signature="t")
 
238
    def getTimeout(self):
 
239
        "D-Bus getter method"
 
240
        return self._timeout_milliseconds
 
241
    
 
242
    @dbus.service.signal(interface, signature="t")
 
243
    def TimeoutChanged(self, t):
 
244
        "D-Bus signal"
 
245
        pass
 
246
    
209
247
    def _set_interval(self, interval):
210
 
        "Setter function for 'interval' attribute"
 
248
        "Setter function for the 'interval' attribute"
211
249
        self._interval = interval
212
250
        self._interval_milliseconds = ((self.interval.days
213
251
                                        * 24 * 60 * 60 * 1000)
215
253
                                          * 1000)
216
254
                                       + (self.interval.microseconds
217
255
                                          // 1000))
218
 
    interval = property(lambda self: self._interval,
219
 
                        _set_interval)
 
256
        # Emit D-Bus signal
 
257
        self.IntervalChanged(self._interval_milliseconds)
 
258
    interval = property(lambda self: self._interval, _set_interval)
220
259
    del _set_interval
221
 
    def __init__(self, name = None, stop_hook=None, config={}):
 
260
    
 
261
    @dbus.service.method(interface, out_signature="t")
 
262
    def getInterval(self):
 
263
        "D-Bus getter method"
 
264
        return self._interval_milliseconds
 
265
    
 
266
    @dbus.service.signal(interface, signature="t")
 
267
    def IntervalChanged(self, t):
 
268
        "D-Bus signal"
 
269
        pass
 
270
    
 
271
    def __init__(self, name = None, stop_hook=None, config=None):
222
272
        """Note: the 'checker' key in 'config' sets the
223
273
        'checker_command' attribute and *not* the 'checker'
224
274
        attribute."""
 
275
        dbus.service.Object.__init__(self, bus,
 
276
                                     "/Mandos/Clients/%s"
 
277
                                     % name.replace(".", "_"))
 
278
        if config is None:
 
279
            config = {}
225
280
        self.name = name
226
281
        logger.debug(u"Creating client %r", self.name)
227
282
        # Uppercase and remove spaces from fingerprint for later
233
288
        if "secret" in config:
234
289
            self.secret = config["secret"].decode(u"base64")
235
290
        elif "secfile" in config:
236
 
            sf = open(config["secfile"])
237
 
            self.secret = sf.read()
238
 
            sf.close()
 
291
            with closing(open(os.path.expanduser
 
292
                              (os.path.expandvars
 
293
                               (config["secfile"])))) \
 
294
                               as secfile:
 
295
                self.secret = secfile.read()
239
296
        else:
240
297
            raise TypeError(u"No secret or secfile for client %s"
241
298
                            % self.name)
242
299
        self.host = config.get("host", "")
243
300
        self.created = datetime.datetime.now()
 
301
        self.started = False
244
302
        self.last_checked_ok = None
245
303
        self.timeout = string_to_delta(config["timeout"])
246
304
        self.interval = string_to_delta(config["interval"])
250
308
        self.stop_initiator_tag = None
251
309
        self.checker_callback_tag = None
252
310
        self.check_command = config["checker"]
 
311
    
253
312
    def start(self):
254
313
        """Start this client's checker and timeout hooks"""
 
314
        self.started = True
255
315
        # Schedule a new checker to be started an 'interval' from now,
256
316
        # and every interval from then on.
257
317
        self.checker_initiator_tag = gobject.timeout_add\
263
323
        self.stop_initiator_tag = gobject.timeout_add\
264
324
                                  (self._timeout_milliseconds,
265
325
                                   self.stop)
 
326
        # Emit D-Bus signal
 
327
        self.StateChanged(True)
 
328
    
 
329
    @dbus.service.signal(interface, signature="b")
 
330
    def StateChanged(self, started):
 
331
        "D-Bus signal"
 
332
        pass
 
333
    
266
334
    def stop(self):
267
 
        """Stop this client.
268
 
        The possibility that a client might be restarted is left open,
269
 
        but not currently used."""
270
 
        # If this client doesn't have a secret, it is already stopped.
271
 
        if hasattr(self, "secret") and self.secret:
 
335
        """Stop this client."""
 
336
        if getattr(self, "started", False):
272
337
            logger.info(u"Stopping client %s", self.name)
273
 
            self.secret = None
274
338
        else:
275
339
            return False
276
340
        if getattr(self, "stop_initiator_tag", False):
282
346
        self.stop_checker()
283
347
        if self.stop_hook:
284
348
            self.stop_hook(self)
 
349
        # Emit D-Bus signal
 
350
        self.StateChanged(False)
285
351
        # Do not run this again if called by a gobject.timeout_add
286
352
        return False
 
353
    # D-Bus variant
 
354
    Stop = dbus.service.method(interface)(stop)
 
355
    
287
356
    def __del__(self):
288
357
        self.stop_hook = None
289
358
        self.stop()
 
359
    
290
360
    def checker_callback(self, pid, condition):
291
361
        """The checker has completed, so take appropriate actions."""
292
 
        now = datetime.datetime.now()
293
362
        self.checker_callback_tag = None
294
363
        self.checker = None
295
364
        if os.WIFEXITED(condition) \
296
365
               and (os.WEXITSTATUS(condition) == 0):
297
366
            logger.info(u"Checker for %(name)s succeeded",
298
367
                        vars(self))
299
 
            self.last_checked_ok = now
300
 
            gobject.source_remove(self.stop_initiator_tag)
301
 
            self.stop_initiator_tag = gobject.timeout_add\
302
 
                                      (self._timeout_milliseconds,
303
 
                                       self.stop)
 
368
            # Emit D-Bus signal
 
369
            self.CheckerCompleted(True)
 
370
            self.bump_timeout()
304
371
        elif not os.WIFEXITED(condition):
305
372
            logger.warning(u"Checker for %(name)s crashed?",
306
373
                           vars(self))
 
374
            # Emit D-Bus signal
 
375
            self.CheckerCompleted(False)
307
376
        else:
308
377
            logger.info(u"Checker for %(name)s failed",
309
378
                        vars(self))
 
379
            # Emit D-Bus signal
 
380
            self.CheckerCompleted(False)
 
381
    
 
382
    @dbus.service.signal(interface, signature="b")
 
383
    def CheckerCompleted(self, success):
 
384
        "D-Bus signal"
 
385
        pass
 
386
    
 
387
    def bump_timeout(self):
 
388
        """Bump up the timeout for this client.
 
389
        This should only be called when the client has been seen,
 
390
        alive and well.
 
391
        """
 
392
        self.last_checked_ok = datetime.datetime.now()
 
393
        gobject.source_remove(self.stop_initiator_tag)
 
394
        self.stop_initiator_tag = gobject.timeout_add\
 
395
            (self._timeout_milliseconds, self.stop)
 
396
    # D-Bus variant
 
397
    bumpTimeout = dbus.service.method(interface)(bump_timeout)
 
398
    
310
399
    def start_checker(self):
311
400
        """Start a new checker subprocess if one is not running.
312
401
        If a checker already exists, leave it running and do
337
426
            try:
338
427
                logger.info(u"Starting checker %r for %s",
339
428
                            command, self.name)
 
429
                # We don't need to redirect stdout and stderr, since
 
430
                # in normal mode, that is already done by daemon(),
 
431
                # and in debug mode we don't want to.  (Stdin is
 
432
                # always replaced by /dev/null.)
340
433
                self.checker = subprocess.Popen(command,
341
434
                                                close_fds=True,
342
435
                                                shell=True, cwd="/")
343
436
                self.checker_callback_tag = gobject.child_watch_add\
344
437
                                            (self.checker.pid,
345
438
                                             self.checker_callback)
346
 
            except subprocess.OSError, error:
 
439
                # Emit D-Bus signal
 
440
                self.CheckerStarted(command)
 
441
            except OSError, error:
347
442
                logger.error(u"Failed to start subprocess: %s",
348
443
                             error)
349
444
        # Re-run this periodically if run by gobject.timeout_add
350
445
        return True
 
446
    
 
447
    @dbus.service.signal(interface, signature="s")
 
448
    def CheckerStarted(self, command):
 
449
        pass
 
450
    
 
451
    @dbus.service.method(interface, out_signature="b")
 
452
    def checkerIsRunning(self):
 
453
        "D-Bus getter method"
 
454
        return self.checker is not None
 
455
    
351
456
    def stop_checker(self):
352
457
        """Force the checker process, if any, to stop."""
353
458
        if self.checker_callback_tag:
365
470
            if error.errno != errno.ESRCH: # No such process
366
471
                raise
367
472
        self.checker = None
 
473
    # D-Bus variant
 
474
    StopChecker = dbus.service.method(interface)(stop_checker)
 
475
    
368
476
    def still_valid(self):
369
477
        """Has the timeout not yet passed for this client?"""
 
478
        if not self.started:
 
479
            return False
370
480
        now = datetime.datetime.now()
371
481
        if self.last_checked_ok is None:
372
482
            return now < (self.created + self.timeout)
373
483
        else:
374
484
            return now < (self.last_checked_ok + self.timeout)
 
485
    # D-Bus variant
 
486
    stillValid = dbus.service.method(interface, out_signature="b")\
 
487
        (still_valid)
 
488
    
 
489
    del interface
375
490
 
376
491
 
377
492
def peer_certificate(session):
406
521
    gnutls.library.functions.gnutls_openpgp_crt_import\
407
522
                    (crt, ctypes.byref(datum),
408
523
                     gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
 
524
    # Verify the self signature in the key
 
525
    crtverify = ctypes.c_uint()
 
526
    gnutls.library.functions.gnutls_openpgp_crt_verify_self\
 
527
        (crt, 0, ctypes.byref(crtverify))
 
528
    if crtverify.value != 0:
 
529
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
 
530
        raise gnutls.errors.CertificateSecurityError("Verify failed")
409
531
    # New buffer for the fingerprint
410
 
    buffer = ctypes.create_string_buffer(20)
411
 
    buffer_length = ctypes.c_size_t()
 
532
    buf = ctypes.create_string_buffer(20)
 
533
    buf_len = ctypes.c_size_t()
412
534
    # Get the fingerprint from the certificate into the buffer
413
535
    gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
414
 
        (crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
 
536
        (crt, ctypes.byref(buf), ctypes.byref(buf_len))
415
537
    # Deinit the certificate
416
538
    gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
417
539
    # Convert the buffer to a Python bytestring
418
 
    fpr = ctypes.string_at(buffer, buffer_length.value)
 
540
    fpr = ctypes.string_at(buf, buf_len.value)
419
541
    # Convert the bytestring to hexadecimal notation
420
542
    hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
421
543
    return hex_fpr
422
544
 
423
545
 
424
 
class tcp_handler(SocketServer.BaseRequestHandler, object):
 
546
class TCP_handler(SocketServer.BaseRequestHandler, object):
425
547
    """A TCP request handler class.
426
548
    Instantiated by IPv6_TCPServer for each request to handle it.
427
549
    Note: This will run in its own forked process."""
428
550
    
429
551
    def handle(self):
430
552
        logger.info(u"TCP connection from: %s",
431
 
                     unicode(self.client_address))
 
553
                    unicode(self.client_address))
432
554
        session = gnutls.connection.ClientSession\
433
555
                  (self.request, gnutls.connection.X509Credentials())
434
556
        
449
571
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
450
572
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
451
573
        #                "+DHE-DSS"))
452
 
        priority = "NORMAL"             # Fallback default, since this
453
 
                                        # MUST be set.
454
 
        if self.server.settings["priority"]:
455
 
            priority = self.server.settings["priority"]
 
574
        # Use a fallback default, since this MUST be set.
 
575
        priority = self.server.settings.get("priority", "NORMAL")
456
576
        gnutls.library.functions.gnutls_priority_set_direct\
457
 
            (session._c_object, priority, None);
 
577
            (session._c_object, priority, None)
458
578
        
459
579
        try:
460
580
            session.handshake()
488
608
                           vars(client))
489
609
            session.bye()
490
610
            return
 
611
        ## This won't work here, since we're in a fork.
 
612
        # client.bump_timeout()
491
613
        sent_size = 0
492
614
        while sent_size < len(client.secret):
493
615
            sent = session.send(client.secret[sent_size:])
498
620
        session.bye()
499
621
 
500
622
 
501
 
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
 
623
class IPv6_TCPServer(SocketServer.ForkingMixIn,
 
624
                     SocketServer.TCPServer, object):
502
625
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
503
626
    Attributes:
504
627
        settings:       Server settings
505
628
        clients:        Set() of Client objects
 
629
        enabled:        Boolean; whether this server is activated yet
506
630
    """
507
631
    address_family = socket.AF_INET6
508
632
    def __init__(self, *args, **kwargs):
512
636
        if "clients" in kwargs:
513
637
            self.clients = kwargs["clients"]
514
638
            del kwargs["clients"]
515
 
        return super(type(self), self).__init__(*args, **kwargs)
 
639
        self.enabled = False
 
640
        super(IPv6_TCPServer, self).__init__(*args, **kwargs)
516
641
    def server_bind(self):
517
642
        """This overrides the normal server_bind() function
518
643
        to bind to an interface if one was specified, and also NOT to
547
672
#                                            if_nametoindex
548
673
#                                            (self.settings
549
674
#                                             ["interface"]))
550
 
            return super(type(self), self).server_bind()
 
675
            return super(IPv6_TCPServer, self).server_bind()
 
676
    def server_activate(self):
 
677
        if self.enabled:
 
678
            return super(IPv6_TCPServer, self).server_activate()
 
679
    def enable(self):
 
680
        self.enabled = True
551
681
 
552
682
 
553
683
def string_to_delta(interval):
563
693
    datetime.timedelta(1)
564
694
    >>> string_to_delta(u'1w')
565
695
    datetime.timedelta(7)
 
696
    >>> string_to_delta('5m 30s')
 
697
    datetime.timedelta(0, 330)
566
698
    """
567
 
    try:
568
 
        suffix=unicode(interval[-1])
569
 
        value=int(interval[:-1])
570
 
        if suffix == u"d":
571
 
            delta = datetime.timedelta(value)
572
 
        elif suffix == u"s":
573
 
            delta = datetime.timedelta(0, value)
574
 
        elif suffix == u"m":
575
 
            delta = datetime.timedelta(0, 0, 0, 0, value)
576
 
        elif suffix == u"h":
577
 
            delta = datetime.timedelta(0, 0, 0, 0, 0, value)
578
 
        elif suffix == u"w":
579
 
            delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
580
 
        else:
 
699
    timevalue = datetime.timedelta(0)
 
700
    for s in interval.split():
 
701
        try:
 
702
            suffix = unicode(s[-1])
 
703
            value = int(s[:-1])
 
704
            if suffix == u"d":
 
705
                delta = datetime.timedelta(value)
 
706
            elif suffix == u"s":
 
707
                delta = datetime.timedelta(0, value)
 
708
            elif suffix == u"m":
 
709
                delta = datetime.timedelta(0, 0, 0, 0, value)
 
710
            elif suffix == u"h":
 
711
                delta = datetime.timedelta(0, 0, 0, 0, 0, value)
 
712
            elif suffix == u"w":
 
713
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
 
714
            else:
 
715
                raise ValueError
 
716
        except (ValueError, IndexError):
581
717
            raise ValueError
582
 
    except (ValueError, IndexError):
583
 
        raise ValueError
584
 
    return delta
 
718
        timevalue += delta
 
719
    return timevalue
585
720
 
586
721
 
587
722
def server_state_changed(state):
588
723
    """Derived from the Avahi example code"""
589
724
    if state == avahi.SERVER_COLLISION:
590
 
        logger.error(u"Server name collision")
 
725
        logger.error(u"Zeroconf server name collision")
591
726
        service.remove()
592
727
    elif state == avahi.SERVER_RUNNING:
593
728
        service.add()
595
730
 
596
731
def entry_group_state_changed(state, error):
597
732
    """Derived from the Avahi example code"""
598
 
    logger.debug(u"state change: %i", state)
 
733
    logger.debug(u"Avahi state change: %i", state)
599
734
    
600
735
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
601
 
        logger.debug(u"Service established.")
 
736
        logger.debug(u"Zeroconf service established.")
602
737
    elif state == avahi.ENTRY_GROUP_COLLISION:
603
 
        logger.warning(u"Service name collision.")
 
738
        logger.warning(u"Zeroconf service name collision.")
604
739
        service.rename()
605
740
    elif state == avahi.ENTRY_GROUP_FAILURE:
606
 
        logger.critical(u"Error in group state changed %s",
 
741
        logger.critical(u"Avahi: Error in group state changed %s",
607
742
                        unicode(error))
608
743
        raise AvahiGroupError("State changed: %s", str(error))
609
744
 
611
746
    """Call the C function if_nametoindex(), or equivalent"""
612
747
    global if_nametoindex
613
748
    try:
614
 
        if "ctypes.util" not in sys.modules:
615
 
            import ctypes.util
616
749
        if_nametoindex = ctypes.cdll.LoadLibrary\
617
750
            (ctypes.util.find_library("c")).if_nametoindex
618
751
    except (OSError, AttributeError):
623
756
        def if_nametoindex(interface):
624
757
            "Get an interface index the hard way, i.e. using fcntl()"
625
758
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
626
 
            s = socket.socket()
627
 
            ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
628
 
                                struct.pack("16s16x", interface))
629
 
            s.close()
 
759
            with closing(socket.socket()) as s:
 
760
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
761
                                    struct.pack("16s16x", interface))
630
762
            interface_index = struct.unpack("I", ifreq[16:20])[0]
631
763
            return interface_index
632
764
    return if_nametoindex(interface)
656
788
 
657
789
 
658
790
def main():
659
 
    global main_loop_started
660
 
    main_loop_started = False
661
 
    
662
791
    parser = OptionParser(version = "%%prog %s" % version)
663
792
    parser.add_option("-i", "--interface", type="string",
664
793
                      metavar="IF", help="Bind to interface IF")
679
808
                      default="/etc/mandos", metavar="DIR",
680
809
                      help="Directory to search for configuration"
681
810
                      " files")
682
 
    (options, args) = parser.parse_args()
 
811
    options = parser.parse_args()[0]
683
812
    
684
813
    if options.check:
685
814
        import doctest
700
829
    server_config = ConfigParser.SafeConfigParser(server_defaults)
701
830
    del server_defaults
702
831
    server_config.read(os.path.join(options.configdir, "mandos.conf"))
703
 
    server_section = "server"
704
832
    # Convert the SafeConfigParser object to a dict
705
 
    server_settings = dict(server_config.items(server_section))
 
833
    server_settings = server_config.defaults()
706
834
    # Use getboolean on the boolean config option
707
835
    server_settings["debug"] = server_config.getboolean\
708
 
                               (server_section, "debug")
 
836
                               ("DEFAULT", "debug")
709
837
    del server_config
710
838
    
711
839
    # Override the settings from the config file with command line
733
861
    # Parse config file with clients
734
862
    client_defaults = { "timeout": "1h",
735
863
                        "interval": "5m",
736
 
                        "checker": "fping -q -- %%(host)s",
 
864
                        "checker": "fping -q -- %(host)s",
 
865
                        "host": "",
737
866
                        }
738
867
    client_config = ConfigParser.SafeConfigParser(client_defaults)
739
868
    client_config.read(os.path.join(server_settings["configdir"],
740
869
                                    "clients.conf"))
741
870
    
 
871
    clients = Set()
 
872
    tcp_server = IPv6_TCPServer((server_settings["address"],
 
873
                                 server_settings["port"]),
 
874
                                TCP_handler,
 
875
                                settings=server_settings,
 
876
                                clients=clients)
 
877
    pidfilename = "/var/run/mandos.pid"
 
878
    try:
 
879
        pidfile = open(pidfilename, "w")
 
880
    except IOError, error:
 
881
        logger.error("Could not open file %r", pidfilename)
 
882
    
 
883
    uid = 65534
 
884
    gid = 65534
 
885
    try:
 
886
        uid = pwd.getpwnam("mandos").pw_uid
 
887
    except KeyError:
 
888
        try:
 
889
            uid = pwd.getpwnam("nobody").pw_uid
 
890
        except KeyError:
 
891
            pass
 
892
    try:
 
893
        gid = pwd.getpwnam("mandos").pw_gid
 
894
    except KeyError:
 
895
        try:
 
896
            gid = pwd.getpwnam("nogroup").pw_gid
 
897
        except KeyError:
 
898
            pass
 
899
    try:
 
900
        os.setuid(uid)
 
901
        os.setgid(gid)
 
902
    except OSError, error:
 
903
        if error[0] != errno.EPERM:
 
904
            raise error
 
905
    
742
906
    global service
743
907
    service = AvahiService(name = server_settings["servicename"],
744
 
                           type = "_mandos._tcp", );
 
908
                           servicetype = "_mandos._tcp", )
745
909
    if server_settings["interface"]:
746
 
        service.interface = if_nametoindex(server_settings["interface"])
 
910
        service.interface = if_nametoindex\
 
911
                            (server_settings["interface"])
747
912
    
748
913
    global main_loop
749
914
    global bus
752
917
    DBusGMainLoop(set_as_default=True )
753
918
    main_loop = gobject.MainLoop()
754
919
    bus = dbus.SystemBus()
755
 
    server = dbus.Interface(
756
 
            bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
757
 
            avahi.DBUS_INTERFACE_SERVER )
 
920
    server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
 
921
                                           avahi.DBUS_PATH_SERVER),
 
922
                            avahi.DBUS_INTERFACE_SERVER)
758
923
    # End of Avahi example code
759
924
    
760
 
    clients = Set()
761
925
    def remove_from_clients(client):
762
926
        clients.remove(client)
763
927
        if not clients:
773
937
        logger.critical(u"No clients defined")
774
938
        sys.exit(1)
775
939
    
776
 
    if not debug:
 
940
    if debug:
 
941
        # Redirect stdin so all checkers get /dev/null
 
942
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
 
943
        os.dup2(null, sys.stdin.fileno())
 
944
        if null > 2:
 
945
            os.close(null)
 
946
    else:
 
947
        # No console logging
777
948
        logger.removeHandler(console)
 
949
        # Close all input and output, do double fork, etc.
778
950
        daemon()
779
951
    
780
 
    pidfilename = "/var/run/mandos/mandos.pid"
781
 
    pid = os.getpid()
782
952
    try:
783
 
        pidfile = open(pidfilename, "w")
 
953
        pid = os.getpid()
784
954
        pidfile.write(str(pid) + "\n")
785
955
        pidfile.close()
786
956
        del pidfile
787
 
    except IOError, err:
788
 
        logger.error(u"Could not write %s file with PID %d",
789
 
                     pidfilename, os.getpid())
 
957
    except IOError:
 
958
        logger.error(u"Could not write to file %r with PID %d",
 
959
                     pidfilename, pid)
 
960
    except NameError:
 
961
        # "pidfile" was never created
 
962
        pass
 
963
    del pidfilename
790
964
    
791
965
    def cleanup():
792
966
        "Cleanup function; run on exit"
812
986
    for client in clients:
813
987
        client.start()
814
988
    
815
 
    tcp_server = IPv6_TCPServer((server_settings["address"],
816
 
                                 server_settings["port"]),
817
 
                                tcp_handler,
818
 
                                settings=server_settings,
819
 
                                clients=clients)
 
989
    tcp_server.enable()
 
990
    tcp_server.server_activate()
 
991
    
820
992
    # Find out what port we got
821
993
    service.port = tcp_server.socket.getsockname()[1]
822
994
    logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
840
1012
                             (*args[2:], **kwargs) or True)
841
1013
        
842
1014
        logger.debug(u"Starting main loop")
843
 
        main_loop_started = True
844
1015
        main_loop.run()
845
1016
    except AvahiError, error:
846
1017
        logger.critical(u"AvahiError: %s" + unicode(error))