/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk
3 by Björn Påhlsson
Python based server
1
#!/usr/bin/python
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
2
# -*- mode: python; coding: utf-8 -*-
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
3
# 
4
# Mandos server - give out binary blobs to connecting clients.
5
# 
6
# This program is partly derived from an example program for an Avahi
7
# service publisher, downloaded from
8
# <http://avahi.org/wiki/PythonPublishExample>.  This includes the
9
# following functions: "add_service", "remove_service",
10
# "server_state_changed", "entry_group_state_changed", and some lines
11
# in "main".
12
# 
13
# Everything else is Copyright © 2007-2008 Teddy Hogeborn and Björn
14
# Påhlsson.
15
# 
16
# This program is free software: you can redistribute it and/or modify
17
# it under the terms of the GNU General Public License as published by
18
# the Free Software Foundation, either version 3 of the License, or
19
# (at your option) any later version.
20
#
21
#     This program is distributed in the hope that it will be useful,
22
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
23
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
#     GNU General Public License for more details.
25
# 
26
# You should have received a copy of the GNU General Public License
27
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
28
# 
29
# Contact the authors at <https://www.fukt.bsnet.se/~belorn/> and
30
# <https://www.fukt.bsnet.se/~teddy/>.
31
# 
3 by Björn Påhlsson
Python based server
32
10 by Teddy Hogeborn
* server.py: Bug fix: Do "from __future__ import division".
33
from __future__ import division
34
3 by Björn Påhlsson
Python based server
35
import SocketServer
36
import socket
37
import select
38
from optparse import OptionParser
39
import datetime
40
import errno
41
import gnutls.crypto
42
import gnutls.connection
43
import gnutls.errors
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
44
import gnutls.library.functions
45
import gnutls.library.constants
46
import gnutls.library.types
3 by Björn Påhlsson
Python based server
47
import ConfigParser
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
48
import sys
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
49
import re
50
import os
51
import signal
10 by Teddy Hogeborn
* server.py: Bug fix: Do "from __future__ import division".
52
from sets import Set
53
import subprocess
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
54
import atexit
55
import stat
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
56
import logging
57
import logging.handlers
5 by Teddy Hogeborn
* server.py (server_metaclass): New.
58
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
59
import dbus
60
import gobject
61
import avahi
62
from dbus.mainloop.glib import DBusGMainLoop
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
63
import ctypes
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
64
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
65
# Brief description of the operation of this program:
66
# 
67
# This server announces itself as a Zeroconf service.  Connecting
68
# clients use the TLS protocol, with the unusual quirk that this
69
# server program acts as a TLS "client" while the connecting clients
70
# acts as a TLS "server".  The clients (acting as a TLS "server") must
71
# supply an OpenPGP certificate, and the fingerprint of this
72
# certificate is used by this server to look up (in a list read from a
73
# file at start time) which binary blob to give the client.  No other
74
# authentication or authorization is done by this server.
75
13 by Björn Påhlsson
Added following support:
76
77
logger = logging.Logger('mandos')
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
78
syslogger = logging.handlers.SysLogHandler\
79
            (facility = logging.handlers.SysLogHandler.LOG_DAEMON)
80
syslogger.setFormatter(logging.Formatter\
81
                        ('%(levelname)s: %(message)s'))
82
logger.addHandler(syslogger)
83
del syslogger
13 by Björn Påhlsson
Added following support:
84
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
85
# This variable is used to optionally bind to a specified interface.
86
# It is a global variable to fit in with the other variables from the
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
87
# Avahi example code.
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
88
serviceInterface = avahi.IF_UNSPEC
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
89
# From the Avahi example code:
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
90
serviceName = "Mandos"
91
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
92
servicePort = None                      # Not known at startup
93
serviceTXT = []                         # TXT record for the service
94
domain = ""                  # Domain to publish on, default to .local
95
host = ""          # Host to publish records for, default to localhost
96
group = None #our entry group
97
rename_count = 12       # Counter so we only rename after collisions a
98
                        # sensible number of times
99
# End of Avahi example code
100
101
3 by Björn Påhlsson
Python based server
102
class Client(object):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
103
    """A representation of a client host served by this server.
104
    Attributes:
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
105
    name:      string; from the config file, used in log messages
106
    fingerprint: string (40 or 32 hexadecimal digits); used to
107
                 uniquely identify the client
108
    secret:    bytestring; sent verbatim (over TLS) to client
109
    fqdn:      string (FQDN); available for use by the checker command
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
110
    created:   datetime.datetime()
111
    last_seen: datetime.datetime() or None if not yet seen
112
    timeout:   datetime.timedelta(); How long from last_seen until
113
                                     this client is invalid
114
    interval:  datetime.timedelta(); How often to start a new checker
115
    stop_hook: If set, called by stop() as stop_hook(self)
116
    checker:   subprocess.Popen(); a running checker process used
117
                                   to see if the client lives.
118
                                   Is None if no process is running.
119
    checker_initiator_tag: a gobject event source tag, or None
120
    stop_initiator_tag:    - '' -
121
    checker_callback_tag:  - '' -
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
122
    checker_command: string; External command which is run to check if
123
                     client lives.  %()s expansions are done at
124
                     runtime with vars(self) as dict, so that for
125
                     instance %(name)s can be used in the command.
126
    Private attibutes:
127
    _timeout: Real variable for 'timeout'
128
    _interval: Real variable for 'interval'
129
    _timeout_milliseconds: Used by gobject.timeout_add()
130
    _interval_milliseconds: - '' -
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
131
    """
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
132
    def _set_timeout(self, timeout):
133
        "Setter function for 'timeout' attribute"
134
        self._timeout = timeout
135
        self._timeout_milliseconds = ((self.timeout.days
136
                                       * 24 * 60 * 60 * 1000)
137
                                      + (self.timeout.seconds * 1000)
138
                                      + (self.timeout.microseconds
139
                                         // 1000))
140
    timeout = property(lambda self: self._timeout,
141
                       _set_timeout)
142
    del _set_timeout
143
    def _set_interval(self, interval):
144
        "Setter function for 'interval' attribute"
145
        self._interval = interval
146
        self._interval_milliseconds = ((self.interval.days
147
                                        * 24 * 60 * 60 * 1000)
148
                                       + (self.interval.seconds
149
                                          * 1000)
150
                                       + (self.interval.microseconds
151
                                          // 1000))
152
    interval = property(lambda self: self._interval,
153
                        _set_interval)
154
    del _set_interval
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
155
    def __init__(self, name=None, options=None, stop_hook=None,
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
156
                 fingerprint=None, secret=None, secfile=None,
157
                 fqdn=None, timeout=None, interval=-1, checker=None):
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
158
        """Note: the 'checker' argument sets the 'checker_command'
159
        attribute and not the 'checker' attribute.."""
3 by Björn Påhlsson
Python based server
160
        self.name = name
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
161
        # Uppercase and remove spaces from fingerprint
162
        # for later comparison purposes with return value of
163
        # the fingerprint() function
164
        self.fingerprint = fingerprint.upper().replace(u" ", u"")
165
        if secret:
166
            self.secret = secret.decode(u"base64")
167
        elif secfile:
168
            sf = open(secfile)
169
            self.secret = sf.read()
170
            sf.close()
3 by Björn Påhlsson
Python based server
171
        else:
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
172
            raise RuntimeError(u"No secret or secfile for client %s"
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
173
                               % self.name)
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
174
        self.fqdn = fqdn                # string
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
175
        self.created = datetime.datetime.now()
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
176
        self.last_seen = None
3 by Björn Påhlsson
Python based server
177
        if timeout is None:
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
178
            self.timeout = options.timeout
179
        else:
180
            self.timeout = string_to_delta(timeout)
3 by Björn Påhlsson
Python based server
181
        if interval == -1:
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
182
            self.interval = options.interval
10 by Teddy Hogeborn
* server.py: Bug fix: Do "from __future__ import division".
183
        else:
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
184
            self.interval = string_to_delta(interval)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
185
        self.stop_hook = stop_hook
186
        self.checker = None
187
        self.checker_initiator_tag = None
188
        self.stop_initiator_tag = None
189
        self.checker_callback_tag = None
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
190
        self.check_command = checker
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
191
    def start(self):
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
192
        """Start this client's checker and timeout hooks"""
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
193
        # Schedule a new checker to be started an 'interval' from now,
194
        # and every interval from then on.
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
195
        self.checker_initiator_tag = gobject.timeout_add\
196
                                     (self._interval_milliseconds,
197
                                      self.start_checker)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
198
        # Also start a new checker *right now*.
199
        self.start_checker()
200
        # Schedule a stop() when 'timeout' has passed
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
201
        self.stop_initiator_tag = gobject.timeout_add\
202
                                  (self._timeout_milliseconds,
203
                                   self.stop)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
204
    def stop(self):
205
        """Stop this client.
206
        The possibility that this client might be restarted is left
207
        open, but not currently used."""
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
208
        # If this client doesn't have a secret, it is already stopped.
209
        if self.secret:
210
            logger.debug(u"Stopping client %s", self.name)
211
            self.secret = None
212
        else:
213
            return False
214
        if hasattr(self, "stop_initiator_tag") \
215
               and self.stop_initiator_tag:
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
216
            gobject.source_remove(self.stop_initiator_tag)
217
            self.stop_initiator_tag = None
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
218
        if hasattr(self, "checker_initiator_tag") \
219
               and self.checker_initiator_tag:
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
220
            gobject.source_remove(self.checker_initiator_tag)
221
            self.checker_initiator_tag = None
222
        self.stop_checker()
223
        if self.stop_hook:
224
            self.stop_hook(self)
225
        # Do not run this again if called by a gobject.timeout_add
226
        return False
227
    def __del__(self):
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
228
        self.stop_hook = None
229
        self.stop()
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
230
    def checker_callback(self, pid, condition):
231
        """The checker has completed, so take appropriate actions."""
10 by Teddy Hogeborn
* server.py: Bug fix: Do "from __future__ import division".
232
        now = datetime.datetime.now()
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
233
        self.checker_callback_tag = None
234
        self.checker = None
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
235
        if os.WIFEXITED(condition) \
236
               and (os.WEXITSTATUS(condition) == 0):
13 by Björn Påhlsson
Added following support:
237
            logger.debug(u"Checker for %(name)s succeeded",
238
                         vars(self))
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
239
            self.last_seen = now
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
240
            gobject.source_remove(self.stop_initiator_tag)
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
241
            self.stop_initiator_tag = gobject.timeout_add\
242
                                      (self._timeout_milliseconds,
243
                                       self.stop)
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
244
        elif not os.WIFEXITED(condition):
13 by Björn Påhlsson
Added following support:
245
            logger.warning(u"Checker for %(name)s crashed?",
246
                           vars(self))
247
        else:
248
            logger.debug(u"Checker for %(name)s failed",
249
                         vars(self))
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
250
    def start_checker(self):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
251
        """Start a new checker subprocess if one is not running.
252
        If a checker already exists, leave it running and do
253
        nothing."""
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
254
        # The reason for not killing a running checker is that if we
255
        # did that, then if a checker (for some reason) started
256
        # running slowly and taking more than 'interval' time, the
257
        # client would inevitably timeout, since no checker would get
258
        # a chance to run to completion.  If we instead leave running
259
        # checkers alone, the checker would have to take more time
260
        # than 'timeout' for the client to be declared invalid, which
261
        # is as it should be.
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
262
        if self.checker is None:
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
263
            try:
264
                command = self.check_command % self.fqdn
265
            except TypeError:
266
                escaped_attrs = dict((key, re.escape(str(val)))
267
                                     for key, val in
268
                                     vars(self).iteritems())
13 by Björn Påhlsson
Added following support:
269
                try:
270
                    command = self.check_command % escaped_attrs
271
                except TypeError, error:
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
272
                    logger.critical(u'Could not format string "%s":'
273
                                    u' %s', self.check_command, error)
13 by Björn Påhlsson
Added following support:
274
                    return True # Try again later
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
275
            try:
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
276
                logger.debug(u"Starting checker %r for %s",
277
                             command, self.name)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
278
                self.checker = subprocess.\
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
279
                               Popen(command,
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
280
                                     close_fds=True, shell=True,
281
                                     cwd="/")
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
282
                self.checker_callback_tag = gobject.child_watch_add\
283
                                            (self.checker.pid,
284
                                             self.checker_callback)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
285
            except subprocess.OSError, error:
13 by Björn Påhlsson
Added following support:
286
                logger.error(u"Failed to start subprocess: %s",
287
                             error)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
288
        # Re-run this periodically if run by gobject.timeout_add
289
        return True
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
290
    def stop_checker(self):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
291
        """Force the checker process, if any, to stop."""
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
292
        if self.checker_callback_tag:
293
            gobject.source_remove(self.checker_callback_tag)
294
            self.checker_callback_tag = None
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
295
        if not hasattr(self, "checker") or self.checker is None:
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
296
            return
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
297
        logger.debug("Stopping checker for %(name)s", vars(self))
298
        try:
299
            os.kill(self.checker.pid, signal.SIGTERM)
300
            #os.sleep(0.5)
301
            #if self.checker.poll() is None:
302
            #    os.kill(self.checker.pid, signal.SIGKILL)
303
        except OSError, error:
304
            if error.errno != errno.ESRCH:
305
                raise
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
306
        self.checker = None
307
    def still_valid(self, now=None):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
308
        """Has the timeout not yet passed for this client?"""
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
309
        if now is None:
310
            now = datetime.datetime.now()
10 by Teddy Hogeborn
* server.py: Bug fix: Do "from __future__ import division".
311
        if self.last_seen is None:
312
            return now < (self.created + self.timeout)
313
        else:
314
            return now < (self.last_seen + self.timeout)
3 by Björn Påhlsson
Python based server
315
316
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
317
def peer_certificate(session):
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
318
    "Return the peer's OpenPGP certificate as a bytestring"
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
319
    # If not an OpenPGP certificate...
320
    if gnutls.library.functions.gnutls_certificate_type_get\
321
            (session._c_object) \
322
           != gnutls.library.constants.GNUTLS_CRT_OPENPGP:
323
        # ...do the normal thing
324
        return session.peer_certificate
325
    list_size = ctypes.c_uint()
326
    cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
327
        (session._c_object, ctypes.byref(list_size))
328
    if list_size.value == 0:
329
        return None
330
    cert = cert_list[0]
331
    return ctypes.string_at(cert.data, cert.size)
332
333
334
def fingerprint(openpgp):
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
335
    "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
336
    # New empty GnuTLS certificate
337
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
338
    gnutls.library.functions.gnutls_openpgp_crt_init\
339
        (ctypes.byref(crt))
340
    # New GnuTLS "datum" with the OpenPGP public key
341
    datum = gnutls.library.types.gnutls_datum_t\
342
        (ctypes.cast(ctypes.c_char_p(openpgp),
343
                     ctypes.POINTER(ctypes.c_ubyte)),
344
         ctypes.c_uint(len(openpgp)))
345
    # Import the OpenPGP public key into the certificate
346
    ret = gnutls.library.functions.gnutls_openpgp_crt_import\
347
        (crt,
348
         ctypes.byref(datum),
349
         gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
350
    # New buffer for the fingerprint
351
    buffer = ctypes.create_string_buffer(20)
352
    buffer_length = ctypes.c_size_t()
353
    # Get the fingerprint from the certificate into the buffer
354
    gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
355
        (crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
356
    # Deinit the certificate
357
    gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
358
    # Convert the buffer to a Python bytestring
359
    fpr = ctypes.string_at(buffer, buffer_length.value)
360
    # Convert the bytestring to hexadecimal notation
361
    hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
362
    return hex_fpr
363
364
3 by Björn Påhlsson
Python based server
365
class tcp_handler(SocketServer.BaseRequestHandler, object):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
366
    """A TCP request handler class.
367
    Instantiated by IPv6_TCPServer for each request to handle it.
368
    Note: This will run in its own forked process."""
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
369
    
3 by Björn Påhlsson
Python based server
370
    def handle(self):
13 by Björn Påhlsson
Added following support:
371
        logger.debug(u"TCP connection from: %s",
372
                     unicode(self.client_address))
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
373
        session = gnutls.connection.ClientSession(self.request,
374
                                                  gnutls.connection.\
375
                                                  X509Credentials())
376
        
377
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
378
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
379
        #                "+DHE-DSS"))
380
        priority = "SECURE256"
381
        
382
        gnutls.library.functions.gnutls_priority_set_direct\
383
            (session._c_object, priority, None);
384
        
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
385
        try:
386
            session.handshake()
387
        except gnutls.errors.GNUTLSError, error:
13 by Björn Påhlsson
Added following support:
388
            logger.debug(u"Handshake failed: %s", error)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
389
            # Do not run session.bye() here: the session is not
390
            # established.  Just abandon the request.
391
            return
3 by Björn Påhlsson
Python based server
392
        try:
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
393
            fpr = fingerprint(peer_certificate(session))
394
        except (TypeError, gnutls.errors.GNUTLSError), error:
13 by Björn Påhlsson
Added following support:
395
            logger.debug(u"Bad certificate: %s", error)
3 by Björn Påhlsson
Python based server
396
            session.bye()
397
            return
13 by Björn Påhlsson
Added following support:
398
        logger.debug(u"Fingerprint: %s", fpr)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
399
        client = None
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
400
        for c in self.server.clients:
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
401
            if c.fingerprint == fpr:
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
402
                client = c
403
                break
404
        # Have to check if client.still_valid(), since it is possible
405
        # that the client timed out while establishing the GnuTLS
406
        # session.
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
407
        if (not client) or (not client.still_valid()):
13 by Björn Påhlsson
Added following support:
408
            if client:
409
                logger.debug(u"Client %(name)s is invalid",
410
                             vars(client))
411
            else:
412
                logger.debug(u"Client not found for fingerprint: %s",
413
                             fpr)
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
414
            session.bye()
415
            return
416
        sent_size = 0
417
        while sent_size < len(client.secret):
418
            sent = session.send(client.secret[sent_size:])
13 by Björn Påhlsson
Added following support:
419
            logger.debug(u"Sent: %d, remaining: %d",
420
                         sent, len(client.secret)
421
                         - (sent_size + sent))
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
422
            sent_size += sent
3 by Björn Påhlsson
Python based server
423
        session.bye()
424
5 by Teddy Hogeborn
* server.py (server_metaclass): New.
425
3 by Björn Påhlsson
Python based server
426
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
427
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
428
    Attributes:
429
        options:        Command line options
430
        clients:        Set() of Client objects
431
    """
432
    address_family = socket.AF_INET6
433
    def __init__(self, *args, **kwargs):
434
        if "options" in kwargs:
435
            self.options = kwargs["options"]
436
            del kwargs["options"]
437
        if "clients" in kwargs:
438
            self.clients = kwargs["clients"]
439
            del kwargs["clients"]
440
        return super(type(self), self).__init__(*args, **kwargs)
441
    def server_bind(self):
442
        """This overrides the normal server_bind() function
443
        to bind to an interface if one was specified, and also NOT to
444
        bind to an address or port if they were not specified."""
445
        if self.options.interface:
446
            if not hasattr(socket, "SO_BINDTODEVICE"):
447
                # From /usr/include/asm-i486/socket.h
448
                socket.SO_BINDTODEVICE = 25
449
            try:
450
                self.socket.setsockopt(socket.SOL_SOCKET,
451
                                       socket.SO_BINDTODEVICE,
452
                                       self.options.interface)
453
            except socket.error, error:
454
                if error[0] == errno.EPERM:
13 by Björn Påhlsson
Added following support:
455
                    logger.warning(u"No permission to"
456
                                   u" bind to interface %s",
457
                                   self.options.interface)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
458
                else:
459
                    raise error
460
        # Only bind(2) the socket if we really need to.
461
        if self.server_address[0] or self.server_address[1]:
462
            if not self.server_address[0]:
463
                in6addr_any = "::"
464
                self.server_address = (in6addr_any,
465
                                       self.server_address[1])
466
            elif self.server_address[1] is None:
467
                self.server_address = (self.server_address[0],
468
                                       0)
469
            return super(type(self), self).server_bind()
10 by Teddy Hogeborn
* server.py: Bug fix: Do "from __future__ import division".
470
3 by Björn Påhlsson
Python based server
471
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
472
def string_to_delta(interval):
473
    """Parse a string and return a datetime.timedelta
474
475
    >>> string_to_delta('7d')
476
    datetime.timedelta(7)
477
    >>> string_to_delta('60s')
478
    datetime.timedelta(0, 60)
479
    >>> string_to_delta('60m')
480
    datetime.timedelta(0, 3600)
481
    >>> string_to_delta('24h')
482
    datetime.timedelta(1)
483
    >>> string_to_delta(u'1w')
484
    datetime.timedelta(7)
485
    """
486
    try:
487
        suffix=unicode(interval[-1])
488
        value=int(interval[:-1])
489
        if suffix == u"d":
490
            delta = datetime.timedelta(value)
491
        elif suffix == u"s":
492
            delta = datetime.timedelta(0, value)
493
        elif suffix == u"m":
494
            delta = datetime.timedelta(0, 0, 0, 0, value)
495
        elif suffix == u"h":
496
            delta = datetime.timedelta(0, 0, 0, 0, 0, value)
497
        elif suffix == u"w":
498
            delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
499
        else:
500
            raise ValueError
501
    except (ValueError, IndexError):
502
        raise ValueError
503
    return delta
504
8 by Teddy Hogeborn
* Makefile (client_debug): Bug fix; add quotes and / to CERT_ROOT.
505
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
506
def add_service():
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
507
    """Derived from the Avahi example code"""
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
508
    global group, serviceName, serviceType, servicePort, serviceTXT, \
509
           domain, host
510
    if group is None:
511
        group = dbus.Interface(
512
                bus.get_object( avahi.DBUS_NAME,
513
                                server.EntryGroupNew()),
514
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
515
        group.connect_to_signal('StateChanged',
516
                                entry_group_state_changed)
13 by Björn Påhlsson
Added following support:
517
    logger.debug(u"Adding service '%s' of type '%s' ...",
518
                 serviceName, serviceType)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
519
    
520
    group.AddService(
521
            serviceInterface,           # interface
522
            avahi.PROTO_INET6,          # protocol
523
            dbus.UInt32(0),             # flags
524
            serviceName, serviceType,
525
            domain, host,
526
            dbus.UInt16(servicePort),
527
            avahi.string_array_to_txt_array(serviceTXT))
528
    group.Commit()
529
530
531
def remove_service():
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
532
    """From the Avahi example code"""
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
533
    global group
534
    
535
    if not group is None:
536
        group.Reset()
537
538
539
def server_state_changed(state):
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
540
    """Derived from the Avahi example code"""
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
541
    if state == avahi.SERVER_COLLISION:
13 by Björn Påhlsson
Added following support:
542
        logger.warning(u"Server name collision")
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
543
        remove_service()
544
    elif state == avahi.SERVER_RUNNING:
545
        add_service()
546
547
548
def entry_group_state_changed(state, error):
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
549
    """Derived from the Avahi example code"""
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
550
    global serviceName, server, rename_count
551
    
13 by Björn Påhlsson
Added following support:
552
    logger.debug(u"state change: %i", state)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
553
    
554
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
13 by Björn Påhlsson
Added following support:
555
        logger.debug(u"Service established.")
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
556
    elif state == avahi.ENTRY_GROUP_COLLISION:
557
        
558
        rename_count = rename_count - 1
559
        if rename_count > 0:
560
            name = server.GetAlternativeServiceName(name)
13 by Björn Påhlsson
Added following support:
561
            logger.warning(u"Service name collision, "
562
                           u"changing name to '%s' ...", name)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
563
            remove_service()
564
            add_service()
565
            
566
        else:
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
567
            logger.error(u"No suitable service name found after %i"
568
                         u" retries, exiting.", n_rename)
569
            killme(1)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
570
    elif state == avahi.ENTRY_GROUP_FAILURE:
13 by Björn Påhlsson
Added following support:
571
        logger.error(u"Error in group state changed %s",
572
                     unicode(error))
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
573
        killme(1)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
574
575
576
def if_nametoindex(interface):
577
    """Call the C function if_nametoindex()"""
578
    try:
579
        libc = ctypes.cdll.LoadLibrary("libc.so.6")
580
        return libc.if_nametoindex(interface)
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
581
    except (OSError, AttributeError):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
582
        if "struct" not in sys.modules:
583
            import struct
584
        if "fcntl" not in sys.modules:
585
            import fcntl
586
        SIOCGIFINDEX = 0x8933      # From /usr/include/linux/sockios.h
587
        s = socket.socket()
588
        ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
589
                            struct.pack("16s16x", interface))
590
        s.close()
591
        interface_index = struct.unpack("I", ifreq[16:20])[0]
592
        return interface_index
593
594
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
595
def daemon(nochdir, noclose):
596
    """See daemon(3).  Standard BSD Unix function.
597
    This should really exist as os.daemon, but it doesn't (yet)."""
598
    if os.fork():
599
        sys.exit()
600
    os.setsid()
601
    if not nochdir:
602
        os.chdir("/")
603
    if not noclose:
604
        # Close all standard open file descriptors
605
        null = os.open("/dev/null", os.O_NOCTTY | os.O_RDWR)
606
        if not stat.S_ISCHR(os.fstat(null).st_mode):
607
            raise OSError(errno.ENODEV,
608
                          "/dev/null not a character device")
609
        os.dup2(null, sys.stdin.fileno())
610
        os.dup2(null, sys.stdout.fileno())
611
        os.dup2(null, sys.stderr.fileno())
612
        if null > 2:
613
            os.close(null)
614
615
616
def killme(status = 0):
617
    logger.debug("Stopping server with exit status %d", status)
618
    exitstatus = status
619
    if main_loop_started:
620
        main_loop.quit()
621
    else:
622
        sys.exit(status)
623
624
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
625
def main():
626
    global exitstatus
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
627
    exitstatus = 0
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
628
    global main_loop_started
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
629
    main_loop_started = False
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
630
    
3 by Björn Påhlsson
Python based server
631
    parser = OptionParser()
632
    parser.add_option("-i", "--interface", type="string",
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
633
                      default=None, metavar="IF",
634
                      help="Bind to interface IF")
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
635
    parser.add_option("-a", "--address", type="string", default=None,
636
                      help="Address to listen for requests on")
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
637
    parser.add_option("-p", "--port", type="int", default=None,
3 by Björn Påhlsson
Python based server
638
                      help="Port number to receive requests on")
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
639
    parser.add_option("--timeout", type="string", # Parsed later
640
                      default="1h",
3 by Björn Påhlsson
Python based server
641
                      help="Amount of downtime allowed for clients")
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
642
    parser.add_option("--interval", type="string", # Parsed later
643
                      default="5m",
644
                      help="How often to check that a client is up")
645
    parser.add_option("--check", action="store_true", default=False,
646
                      help="Run self-test")
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
647
    parser.add_option("--debug", action="store_true", default=False,
648
                      help="Debug mode")
3 by Björn Påhlsson
Python based server
649
    (options, args) = parser.parse_args()
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
650
    
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
651
    if options.check:
652
        import doctest
653
        doctest.testmod()
654
        sys.exit()
3 by Björn Påhlsson
Python based server
655
    
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
656
    # Parse the time arguments
3 by Björn Påhlsson
Python based server
657
    try:
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
658
        options.timeout = string_to_delta(options.timeout)
659
    except ValueError:
3 by Björn Påhlsson
Python based server
660
        parser.error("option --timeout: Unparseable time")
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
661
    try:
662
        options.interval = string_to_delta(options.interval)
663
    except ValueError:
664
        parser.error("option --interval: Unparseable time")
665
    
3 by Björn Påhlsson
Python based server
666
    # Parse config file
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
667
    defaults = { "checker": "fping -q -- %%(fqdn)s" }
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
668
    client_config = ConfigParser.SafeConfigParser(defaults)
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
669
    #client_config.readfp(open("global.conf"), "global.conf")
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
670
    client_config.read("mandos-clients.conf")
671
    
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
672
    global main_loop
673
    global bus
674
    global server
675
    # From the Avahi example code
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
676
    DBusGMainLoop(set_as_default=True )
677
    main_loop = gobject.MainLoop()
678
    bus = dbus.SystemBus()
679
    server = dbus.Interface(
680
            bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
681
            avahi.DBUS_INTERFACE_SERVER )
682
    # End of Avahi example code
683
    
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
684
    debug = options.debug
685
    
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
686
    if debug:
687
        console = logging.StreamHandler()
688
        # console.setLevel(logging.DEBUG)
689
        console.setFormatter(logging.Formatter\
690
                             ('%(levelname)s: %(message)s'))
691
        logger.addHandler(console)
692
        del console
693
    
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
694
    clients = Set()
695
    def remove_from_clients(client):
696
        clients.remove(client)
697
        if not clients:
13 by Björn Påhlsson
Added following support:
698
            logger.debug(u"No clients left, exiting")
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
699
            killme()
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
700
    
701
    clients.update(Set(Client(name=section, options=options,
702
                              stop_hook = remove_from_clients,
703
                              **(dict(client_config\
704
                                      .items(section))))
705
                       for section in client_config.sections()))
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
706
    
707
    if not debug:
708
        daemon(False, False)
709
    
710
    def cleanup():
711
        "Cleanup function; run on exit"
712
        global group
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
713
        # From the Avahi example code
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
714
        if not group is None:
715
            group.Free()
716
            group = None
717
        # End of Avahi example code
718
        
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
719
        while clients:
720
            client = clients.pop()
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
721
            client.stop_hook = None
722
            client.stop()
723
    
724
    atexit.register(cleanup)
725
    
726
    if not debug:
727
        signal.signal(signal.SIGINT, signal.SIG_IGN)
728
    signal.signal(signal.SIGHUP, lambda signum, frame: killme())
729
    signal.signal(signal.SIGTERM, lambda signum, frame: killme())
730
    
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
731
    for client in clients:
732
        client.start()
733
    
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
734
    tcp_server = IPv6_TCPServer((options.address, options.port),
3 by Björn Påhlsson
Python based server
735
                                tcp_handler,
736
                                options=options,
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
737
                                clients=clients)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
738
    # Find out what random port we got
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
739
    global servicePort
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
740
    servicePort = tcp_server.socket.getsockname()[1]
13 by Björn Påhlsson
Added following support:
741
    logger.debug(u"Now listening on port %d", servicePort)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
742
    
743
    if options.interface is not None:
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
744
        global serviceInterface
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
745
        serviceInterface = if_nametoindex(options.interface)
746
    
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
747
    # From the Avahi example code
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
748
    server.connect_to_signal("StateChanged", server_state_changed)
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
749
    try:
750
        server_state_changed(server.GetState())
751
    except dbus.exceptions.DBusException, error:
752
        logger.critical(u"DBusException: %s", error)
753
        killme(1)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
754
    # End of Avahi example code
755
    
756
    gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
757
                         lambda *args, **kwargs:
758
                         tcp_server.handle_request(*args[2:],
759
                                                   **kwargs) or True)
760
    try:
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
761
        logger.debug("Starting main loop")
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
762
        main_loop_started = True
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
763
        main_loop.run()
764
    except KeyboardInterrupt:
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
765
        if debug:
766
            print
767
    
768
    sys.exit(exitstatus)
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
769
770
if __name__ == '__main__':
771
    main()