/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:
25 by Teddy Hogeborn
* mandos-clients.conf ([DEFAULT]): New section.
90
serviceName = None
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
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
25 by Teddy Hogeborn
* mandos-clients.conf ([DEFAULT]): New section.
155
    def __init__(self, name=None, stop_hook=None, fingerprint=None,
156
                 secret=None, secfile=None, fqdn=None, timeout=None,
157
                 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
25 by Teddy Hogeborn
* mandos-clients.conf ([DEFAULT]): New section.
161
        logger.debug(u"Creating client %r", self.name)
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
162
        # Uppercase and remove spaces from fingerprint
163
        # for later comparison purposes with return value of
164
        # the fingerprint() function
165
        self.fingerprint = fingerprint.upper().replace(u" ", u"")
25 by Teddy Hogeborn
* mandos-clients.conf ([DEFAULT]): New section.
166
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
167
        if secret:
168
            self.secret = secret.decode(u"base64")
169
        elif secfile:
170
            sf = open(secfile)
171
            self.secret = sf.read()
172
            sf.close()
3 by Björn Påhlsson
Python based server
173
        else:
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
174
            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
175
                               % self.name)
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
176
        self.fqdn = fqdn                # string
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
177
        self.created = datetime.datetime.now()
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
178
        self.last_seen = None
25 by Teddy Hogeborn
* mandos-clients.conf ([DEFAULT]): New section.
179
        self.timeout = string_to_delta(timeout)
180
        self.interval = string_to_delta(interval)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
181
        self.stop_hook = stop_hook
182
        self.checker = None
183
        self.checker_initiator_tag = None
184
        self.stop_initiator_tag = None
185
        self.checker_callback_tag = None
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
186
        self.check_command = checker
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
187
    def start(self):
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
188
        """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
189
        # Schedule a new checker to be started an 'interval' from now,
190
        # and every interval from then on.
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
191
        self.checker_initiator_tag = gobject.timeout_add\
192
                                     (self._interval_milliseconds,
193
                                      self.start_checker)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
194
        # Also start a new checker *right now*.
195
        self.start_checker()
196
        # Schedule a stop() when 'timeout' has passed
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
197
        self.stop_initiator_tag = gobject.timeout_add\
198
                                  (self._timeout_milliseconds,
199
                                   self.stop)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
200
    def stop(self):
201
        """Stop this client.
202
        The possibility that this client might be restarted is left
203
        open, but not currently used."""
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
204
        # If this client doesn't have a secret, it is already stopped.
205
        if self.secret:
206
            logger.debug(u"Stopping client %s", self.name)
207
            self.secret = None
208
        else:
209
            return False
210
        if hasattr(self, "stop_initiator_tag") \
211
               and self.stop_initiator_tag:
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
212
            gobject.source_remove(self.stop_initiator_tag)
213
            self.stop_initiator_tag = None
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
214
        if hasattr(self, "checker_initiator_tag") \
215
               and self.checker_initiator_tag:
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
216
            gobject.source_remove(self.checker_initiator_tag)
217
            self.checker_initiator_tag = None
218
        self.stop_checker()
219
        if self.stop_hook:
220
            self.stop_hook(self)
221
        # Do not run this again if called by a gobject.timeout_add
222
        return False
223
    def __del__(self):
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
224
        self.stop_hook = None
225
        self.stop()
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
226
    def checker_callback(self, pid, condition):
227
        """The checker has completed, so take appropriate actions."""
10 by Teddy Hogeborn
* server.py: Bug fix: Do "from __future__ import division".
228
        now = datetime.datetime.now()
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
229
        self.checker_callback_tag = None
230
        self.checker = None
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
231
        if os.WIFEXITED(condition) \
232
               and (os.WEXITSTATUS(condition) == 0):
13 by Björn Påhlsson
Added following support:
233
            logger.debug(u"Checker for %(name)s succeeded",
234
                         vars(self))
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
235
            self.last_seen = now
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
236
            gobject.source_remove(self.stop_initiator_tag)
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
237
            self.stop_initiator_tag = gobject.timeout_add\
238
                                      (self._timeout_milliseconds,
239
                                       self.stop)
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
240
        elif not os.WIFEXITED(condition):
13 by Björn Påhlsson
Added following support:
241
            logger.warning(u"Checker for %(name)s crashed?",
242
                           vars(self))
243
        else:
244
            logger.debug(u"Checker for %(name)s failed",
245
                         vars(self))
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
246
    def start_checker(self):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
247
        """Start a new checker subprocess if one is not running.
248
        If a checker already exists, leave it running and do
249
        nothing."""
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
250
        # The reason for not killing a running checker is that if we
251
        # did that, then if a checker (for some reason) started
252
        # running slowly and taking more than 'interval' time, the
253
        # client would inevitably timeout, since no checker would get
254
        # a chance to run to completion.  If we instead leave running
255
        # checkers alone, the checker would have to take more time
256
        # than 'timeout' for the client to be declared invalid, which
257
        # is as it should be.
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
258
        if self.checker is None:
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
259
            try:
260
                command = self.check_command % self.fqdn
261
            except TypeError:
262
                escaped_attrs = dict((key, re.escape(str(val)))
263
                                     for key, val in
264
                                     vars(self).iteritems())
13 by Björn Påhlsson
Added following support:
265
                try:
266
                    command = self.check_command % escaped_attrs
267
                except TypeError, error:
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
268
                    logger.critical(u'Could not format string "%s":'
269
                                    u' %s', self.check_command, error)
13 by Björn Påhlsson
Added following support:
270
                    return True # Try again later
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
271
            try:
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
272
                logger.debug(u"Starting checker %r for %s",
273
                             command, self.name)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
274
                self.checker = subprocess.\
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
275
                               Popen(command,
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
276
                                     close_fds=True, shell=True,
277
                                     cwd="/")
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
278
                self.checker_callback_tag = gobject.child_watch_add\
279
                                            (self.checker.pid,
280
                                             self.checker_callback)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
281
            except subprocess.OSError, error:
13 by Björn Påhlsson
Added following support:
282
                logger.error(u"Failed to start subprocess: %s",
283
                             error)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
284
        # Re-run this periodically if run by gobject.timeout_add
285
        return True
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
286
    def stop_checker(self):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
287
        """Force the checker process, if any, to stop."""
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
288
        if self.checker_callback_tag:
289
            gobject.source_remove(self.checker_callback_tag)
290
            self.checker_callback_tag = None
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
291
        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.
292
            return
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
293
        logger.debug("Stopping checker for %(name)s", vars(self))
294
        try:
295
            os.kill(self.checker.pid, signal.SIGTERM)
296
            #os.sleep(0.5)
297
            #if self.checker.poll() is None:
298
            #    os.kill(self.checker.pid, signal.SIGKILL)
299
        except OSError, error:
300
            if error.errno != errno.ESRCH:
301
                raise
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
302
        self.checker = None
303
    def still_valid(self, now=None):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
304
        """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.
305
        if now is None:
306
            now = datetime.datetime.now()
10 by Teddy Hogeborn
* server.py: Bug fix: Do "from __future__ import division".
307
        if self.last_seen is None:
308
            return now < (self.created + self.timeout)
309
        else:
310
            return now < (self.last_seen + self.timeout)
3 by Björn Påhlsson
Python based server
311
312
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
313
def peer_certificate(session):
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
314
    "Return the peer's OpenPGP certificate as a bytestring"
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
315
    # If not an OpenPGP certificate...
316
    if gnutls.library.functions.gnutls_certificate_type_get\
317
            (session._c_object) \
318
           != gnutls.library.constants.GNUTLS_CRT_OPENPGP:
319
        # ...do the normal thing
320
        return session.peer_certificate
321
    list_size = ctypes.c_uint()
322
    cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
323
        (session._c_object, ctypes.byref(list_size))
324
    if list_size.value == 0:
325
        return None
326
    cert = cert_list[0]
327
    return ctypes.string_at(cert.data, cert.size)
328
329
330
def fingerprint(openpgp):
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
331
    "Convert an OpenPGP bytestring to a hexdigit fingerprint string"
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
332
    # New empty GnuTLS certificate
333
    crt = gnutls.library.types.gnutls_openpgp_crt_t()
334
    gnutls.library.functions.gnutls_openpgp_crt_init\
335
        (ctypes.byref(crt))
336
    # New GnuTLS "datum" with the OpenPGP public key
337
    datum = gnutls.library.types.gnutls_datum_t\
338
        (ctypes.cast(ctypes.c_char_p(openpgp),
339
                     ctypes.POINTER(ctypes.c_ubyte)),
340
         ctypes.c_uint(len(openpgp)))
341
    # Import the OpenPGP public key into the certificate
342
    ret = gnutls.library.functions.gnutls_openpgp_crt_import\
343
        (crt,
344
         ctypes.byref(datum),
345
         gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
346
    # New buffer for the fingerprint
347
    buffer = ctypes.create_string_buffer(20)
348
    buffer_length = ctypes.c_size_t()
349
    # Get the fingerprint from the certificate into the buffer
350
    gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
351
        (crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
352
    # Deinit the certificate
353
    gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
354
    # Convert the buffer to a Python bytestring
355
    fpr = ctypes.string_at(buffer, buffer_length.value)
356
    # Convert the bytestring to hexadecimal notation
357
    hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
358
    return hex_fpr
359
360
3 by Björn Påhlsson
Python based server
361
class tcp_handler(SocketServer.BaseRequestHandler, object):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
362
    """A TCP request handler class.
363
    Instantiated by IPv6_TCPServer for each request to handle it.
364
    Note: This will run in its own forked process."""
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
365
    
3 by Björn Påhlsson
Python based server
366
    def handle(self):
13 by Björn Påhlsson
Added following support:
367
        logger.debug(u"TCP connection from: %s",
368
                     unicode(self.client_address))
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
369
        session = gnutls.connection.ClientSession(self.request,
370
                                                  gnutls.connection.\
371
                                                  X509Credentials())
372
        
373
        #priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
374
        #                "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
375
        #                "+DHE-DSS"))
25 by Teddy Hogeborn
* mandos-clients.conf ([DEFAULT]): New section.
376
        priority = "NORMAL"
377
        if self.server.options.priority:
378
            priority = self.server.options.priority
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
379
        gnutls.library.functions.gnutls_priority_set_direct\
380
            (session._c_object, priority, None);
381
        
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
382
        try:
383
            session.handshake()
384
        except gnutls.errors.GNUTLSError, error:
13 by Björn Påhlsson
Added following support:
385
            logger.debug(u"Handshake failed: %s", error)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
386
            # Do not run session.bye() here: the session is not
387
            # established.  Just abandon the request.
388
            return
3 by Björn Påhlsson
Python based server
389
        try:
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
390
            fpr = fingerprint(peer_certificate(session))
391
        except (TypeError, gnutls.errors.GNUTLSError), error:
13 by Björn Påhlsson
Added following support:
392
            logger.debug(u"Bad certificate: %s", error)
3 by Björn Påhlsson
Python based server
393
            session.bye()
394
            return
13 by Björn Påhlsson
Added following support:
395
        logger.debug(u"Fingerprint: %s", fpr)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
396
        client = None
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
397
        for c in self.server.clients:
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
398
            if c.fingerprint == fpr:
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
399
                client = c
400
                break
401
        # Have to check if client.still_valid(), since it is possible
402
        # that the client timed out while establishing the GnuTLS
403
        # session.
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
404
        if (not client) or (not client.still_valid()):
13 by Björn Påhlsson
Added following support:
405
            if client:
406
                logger.debug(u"Client %(name)s is invalid",
407
                             vars(client))
408
            else:
409
                logger.debug(u"Client not found for fingerprint: %s",
410
                             fpr)
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
411
            session.bye()
412
            return
413
        sent_size = 0
414
        while sent_size < len(client.secret):
415
            sent = session.send(client.secret[sent_size:])
13 by Björn Påhlsson
Added following support:
416
            logger.debug(u"Sent: %d, remaining: %d",
417
                         sent, len(client.secret)
418
                         - (sent_size + sent))
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
419
            sent_size += sent
3 by Björn Påhlsson
Python based server
420
        session.bye()
421
5 by Teddy Hogeborn
* server.py (server_metaclass): New.
422
3 by Björn Påhlsson
Python based server
423
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
424
    """IPv6 TCP server.  Accepts 'None' as address and/or port.
425
    Attributes:
426
        options:        Command line options
427
        clients:        Set() of Client objects
428
    """
429
    address_family = socket.AF_INET6
430
    def __init__(self, *args, **kwargs):
431
        if "options" in kwargs:
432
            self.options = kwargs["options"]
433
            del kwargs["options"]
434
        if "clients" in kwargs:
435
            self.clients = kwargs["clients"]
436
            del kwargs["clients"]
437
        return super(type(self), self).__init__(*args, **kwargs)
438
    def server_bind(self):
439
        """This overrides the normal server_bind() function
440
        to bind to an interface if one was specified, and also NOT to
441
        bind to an address or port if they were not specified."""
442
        if self.options.interface:
443
            if not hasattr(socket, "SO_BINDTODEVICE"):
444
                # From /usr/include/asm-i486/socket.h
445
                socket.SO_BINDTODEVICE = 25
446
            try:
447
                self.socket.setsockopt(socket.SOL_SOCKET,
448
                                       socket.SO_BINDTODEVICE,
449
                                       self.options.interface)
450
            except socket.error, error:
451
                if error[0] == errno.EPERM:
13 by Björn Påhlsson
Added following support:
452
                    logger.warning(u"No permission to"
453
                                   u" bind to interface %s",
454
                                   self.options.interface)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
455
                else:
456
                    raise error
457
        # Only bind(2) the socket if we really need to.
458
        if self.server_address[0] or self.server_address[1]:
459
            if not self.server_address[0]:
460
                in6addr_any = "::"
461
                self.server_address = (in6addr_any,
462
                                       self.server_address[1])
463
            elif self.server_address[1] is None:
464
                self.server_address = (self.server_address[0],
465
                                       0)
466
            return super(type(self), self).server_bind()
10 by Teddy Hogeborn
* server.py: Bug fix: Do "from __future__ import division".
467
3 by Björn Påhlsson
Python based server
468
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
469
def string_to_delta(interval):
470
    """Parse a string and return a datetime.timedelta
471
472
    >>> string_to_delta('7d')
473
    datetime.timedelta(7)
474
    >>> string_to_delta('60s')
475
    datetime.timedelta(0, 60)
476
    >>> string_to_delta('60m')
477
    datetime.timedelta(0, 3600)
478
    >>> string_to_delta('24h')
479
    datetime.timedelta(1)
480
    >>> string_to_delta(u'1w')
481
    datetime.timedelta(7)
482
    """
483
    try:
484
        suffix=unicode(interval[-1])
485
        value=int(interval[:-1])
486
        if suffix == u"d":
487
            delta = datetime.timedelta(value)
488
        elif suffix == u"s":
489
            delta = datetime.timedelta(0, value)
490
        elif suffix == u"m":
491
            delta = datetime.timedelta(0, 0, 0, 0, value)
492
        elif suffix == u"h":
493
            delta = datetime.timedelta(0, 0, 0, 0, 0, value)
494
        elif suffix == u"w":
495
            delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
496
        else:
497
            raise ValueError
498
    except (ValueError, IndexError):
499
        raise ValueError
500
    return delta
501
8 by Teddy Hogeborn
* Makefile (client_debug): Bug fix; add quotes and / to CERT_ROOT.
502
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
503
def add_service():
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
504
    """Derived from the Avahi example code"""
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
505
    global group, serviceName, serviceType, servicePort, serviceTXT, \
506
           domain, host
507
    if group is None:
508
        group = dbus.Interface(
509
                bus.get_object( avahi.DBUS_NAME,
510
                                server.EntryGroupNew()),
511
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
512
        group.connect_to_signal('StateChanged',
513
                                entry_group_state_changed)
13 by Björn Påhlsson
Added following support:
514
    logger.debug(u"Adding service '%s' of type '%s' ...",
515
                 serviceName, serviceType)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
516
    
517
    group.AddService(
518
            serviceInterface,           # interface
519
            avahi.PROTO_INET6,          # protocol
520
            dbus.UInt32(0),             # flags
521
            serviceName, serviceType,
522
            domain, host,
523
            dbus.UInt16(servicePort),
524
            avahi.string_array_to_txt_array(serviceTXT))
525
    group.Commit()
526
527
528
def remove_service():
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
529
    """From the Avahi example code"""
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
530
    global group
531
    
532
    if not group is None:
533
        group.Reset()
534
535
536
def server_state_changed(state):
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
537
    """Derived from the Avahi example code"""
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
538
    if state == avahi.SERVER_COLLISION:
13 by Björn Påhlsson
Added following support:
539
        logger.warning(u"Server name collision")
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
540
        remove_service()
541
    elif state == avahi.SERVER_RUNNING:
542
        add_service()
543
544
545
def entry_group_state_changed(state, error):
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
546
    """Derived from the Avahi example code"""
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
547
    global serviceName, server, rename_count
548
    
13 by Björn Påhlsson
Added following support:
549
    logger.debug(u"state change: %i", state)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
550
    
551
    if state == avahi.ENTRY_GROUP_ESTABLISHED:
13 by Björn Påhlsson
Added following support:
552
        logger.debug(u"Service established.")
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
553
    elif state == avahi.ENTRY_GROUP_COLLISION:
554
        
555
        rename_count = rename_count - 1
556
        if rename_count > 0:
557
            name = server.GetAlternativeServiceName(name)
13 by Björn Påhlsson
Added following support:
558
            logger.warning(u"Service name collision, "
559
                           u"changing name to '%s' ...", name)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
560
            remove_service()
561
            add_service()
562
            
563
        else:
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
564
            logger.error(u"No suitable service name found after %i"
565
                         u" retries, exiting.", n_rename)
566
            killme(1)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
567
    elif state == avahi.ENTRY_GROUP_FAILURE:
13 by Björn Påhlsson
Added following support:
568
        logger.error(u"Error in group state changed %s",
569
                     unicode(error))
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
570
        killme(1)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
571
572
573
def if_nametoindex(interface):
574
    """Call the C function if_nametoindex()"""
575
    try:
576
        libc = ctypes.cdll.LoadLibrary("libc.so.6")
577
        return libc.if_nametoindex(interface)
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
578
    except (OSError, AttributeError):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
579
        if "struct" not in sys.modules:
580
            import struct
581
        if "fcntl" not in sys.modules:
582
            import fcntl
583
        SIOCGIFINDEX = 0x8933      # From /usr/include/linux/sockios.h
584
        s = socket.socket()
585
        ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
586
                            struct.pack("16s16x", interface))
587
        s.close()
588
        interface_index = struct.unpack("I", ifreq[16:20])[0]
589
        return interface_index
590
591
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
592
def daemon(nochdir, noclose):
593
    """See daemon(3).  Standard BSD Unix function.
594
    This should really exist as os.daemon, but it doesn't (yet)."""
595
    if os.fork():
596
        sys.exit()
597
    os.setsid()
598
    if not nochdir:
599
        os.chdir("/")
600
    if not noclose:
601
        # Close all standard open file descriptors
602
        null = os.open("/dev/null", os.O_NOCTTY | os.O_RDWR)
603
        if not stat.S_ISCHR(os.fstat(null).st_mode):
604
            raise OSError(errno.ENODEV,
605
                          "/dev/null not a character device")
606
        os.dup2(null, sys.stdin.fileno())
607
        os.dup2(null, sys.stdout.fileno())
608
        os.dup2(null, sys.stderr.fileno())
609
        if null > 2:
610
            os.close(null)
611
612
613
def killme(status = 0):
614
    logger.debug("Stopping server with exit status %d", status)
615
    exitstatus = status
616
    if main_loop_started:
617
        main_loop.quit()
618
    else:
619
        sys.exit(status)
620
621
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
622
def main():
623
    global exitstatus
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
624
    exitstatus = 0
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
625
    global main_loop_started
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
626
    main_loop_started = False
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
627
    
3 by Björn Påhlsson
Python based server
628
    parser = OptionParser()
629
    parser.add_option("-i", "--interface", type="string",
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
630
                      default=None, metavar="IF",
631
                      help="Bind to interface IF")
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
632
    parser.add_option("-a", "--address", type="string", default=None,
633
                      help="Address to listen for requests on")
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
634
    parser.add_option("-p", "--port", type="int", default=None,
3 by Björn Påhlsson
Python based server
635
                      help="Port number to receive requests on")
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
636
    parser.add_option("--check", action="store_true", default=False,
637
                      help="Run self-test")
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
638
    parser.add_option("--debug", action="store_true", default=False,
639
                      help="Debug mode")
25 by Teddy Hogeborn
* mandos-clients.conf ([DEFAULT]): New section.
640
    parser.add_option("--priority", type="string",
641
                      default="SECURE256",
642
                      help="GnuTLS priority string"
643
                      " (see GnuTLS documentation)")
644
    parser.add_option("--servicename", type="string",
645
                      default="Mandos", help="Zeroconf service name")
3 by Björn Påhlsson
Python based server
646
    (options, args) = parser.parse_args()
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
647
    
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
648
    if options.check:
649
        import doctest
650
        doctest.testmod()
651
        sys.exit()
3 by Björn Påhlsson
Python based server
652
    
653
    # Parse config file
25 by Teddy Hogeborn
* mandos-clients.conf ([DEFAULT]): New section.
654
    defaults = { "timeout": "1h",
655
                 "interval": "5m",
656
                 "checker": "fping -q -- %%(fqdn)s",
657
                 }
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
658
    client_config = ConfigParser.SafeConfigParser(defaults)
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
659
    #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
660
    client_config.read("mandos-clients.conf")
661
    
25 by Teddy Hogeborn
* mandos-clients.conf ([DEFAULT]): New section.
662
    global serviceName
663
    serviceName = options.servicename;
664
    
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
665
    global main_loop
666
    global bus
667
    global server
668
    # From the Avahi example code
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
669
    DBusGMainLoop(set_as_default=True )
670
    main_loop = gobject.MainLoop()
671
    bus = dbus.SystemBus()
672
    server = dbus.Interface(
673
            bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
674
            avahi.DBUS_INTERFACE_SERVER )
675
    # End of Avahi example code
676
    
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
677
    debug = options.debug
678
    
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
679
    if debug:
680
        console = logging.StreamHandler()
681
        # console.setLevel(logging.DEBUG)
682
        console.setFormatter(logging.Formatter\
683
                             ('%(levelname)s: %(message)s'))
684
        logger.addHandler(console)
685
        del console
686
    
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
687
    clients = Set()
688
    def remove_from_clients(client):
689
        clients.remove(client)
690
        if not clients:
13 by Björn Påhlsson
Added following support:
691
            logger.debug(u"No clients left, exiting")
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
692
            killme()
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
693
    
25 by Teddy Hogeborn
* mandos-clients.conf ([DEFAULT]): New section.
694
    clients.update(Set(Client(name=section,
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
695
                              stop_hook = remove_from_clients,
696
                              **(dict(client_config\
697
                                      .items(section))))
698
                       for section in client_config.sections()))
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
699
    
700
    if not debug:
701
        daemon(False, False)
702
    
703
    def cleanup():
704
        "Cleanup function; run on exit"
705
        global group
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
706
        # From the Avahi example code
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
707
        if not group is None:
708
            group.Free()
709
            group = None
710
        # End of Avahi example code
711
        
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
712
        while clients:
713
            client = clients.pop()
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
714
            client.stop_hook = None
715
            client.stop()
716
    
717
    atexit.register(cleanup)
718
    
719
    if not debug:
720
        signal.signal(signal.SIGINT, signal.SIG_IGN)
721
    signal.signal(signal.SIGHUP, lambda signum, frame: killme())
722
    signal.signal(signal.SIGTERM, lambda signum, frame: killme())
723
    
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
724
    for client in clients:
725
        client.start()
726
    
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
727
    tcp_server = IPv6_TCPServer((options.address, options.port),
3 by Björn Påhlsson
Python based server
728
                                tcp_handler,
729
                                options=options,
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
730
                                clients=clients)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
731
    # Find out what random port we got
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
732
    global servicePort
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
733
    servicePort = tcp_server.socket.getsockname()[1]
13 by Björn Påhlsson
Added following support:
734
    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
735
    
736
    if options.interface is not None:
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
737
        global serviceInterface
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
738
        serviceInterface = if_nametoindex(options.interface)
739
    
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
740
    # From the Avahi example code
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
741
    server.connect_to_signal("StateChanged", server_state_changed)
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
742
    try:
743
        server_state_changed(server.GetState())
744
    except dbus.exceptions.DBusException, error:
745
        logger.critical(u"DBusException: %s", error)
746
        killme(1)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
747
    # End of Avahi example code
748
    
749
    gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
750
                         lambda *args, **kwargs:
751
                         tcp_server.handle_request(*args[2:],
752
                                                   **kwargs) or True)
753
    try:
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
754
        logger.debug("Starting main loop")
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
755
        main_loop_started = True
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
756
        main_loop.run()
757
    except KeyboardInterrupt:
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
758
        if debug:
759
            print
760
    
761
    sys.exit(exitstatus)
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
762
763
if __name__ == '__main__':
764
    main()