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