2
2
# -*- mode: python; coding: utf-8 -*-
4
# Mandos server - give out binary blobs to connecting clients.
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
# 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".
14
# Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson
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.
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.
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/>.
29
# Contact the authors at <mandos@fukt.bsnet.se>.
32
4
from __future__ import division
61
31
from dbus.mainloop.glib import DBusGMainLoop
35
import logging.handlers
66
37
logger = logging.Logger('mandos')
67
38
syslogger = logging.handlers.SysLogHandler\
68
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
39
(facility = logging.handlers.SysLogHandler.LOG_DAEMON)
70
40
syslogger.setFormatter(logging.Formatter\
71
('Mandos: %(levelname)s: %(message)s'))
41
('%(levelname)s: %(message)s'))
72
42
logger.addHandler(syslogger)
75
class AvahiError(Exception):
76
def __init__(self, value):
79
return repr(self.value)
81
class AvahiServiceError(AvahiError):
84
class AvahiGroupError(AvahiError):
88
class AvahiService(object):
89
"""An Avahi (Zeroconf) service.
91
interface: integer; avahi.IF_UNSPEC or an interface index.
92
Used to optionally bind to the specified interface.
93
name: string; Example: 'Mandos'
94
type: string; Example: '_mandos._tcp'.
95
See <http://www.dns-sd.org/ServiceTypes.html>
96
port: integer; what port to announce
97
TXT: list of strings; TXT record for the service
98
domain: string; Domain to publish on, default to .local if empty.
99
host: string; Host to publish records for, default is localhost
100
max_renames: integer; maximum number of renames
101
rename_count: integer; counter so we only rename after collisions
102
a sensible number of times
104
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
105
type = None, port = None, TXT = None, domain = "",
106
host = "", max_renames = 32768):
107
self.interface = interface
117
self.rename_count = 0
119
"""Derived from the Avahi example code"""
120
if self.rename_count >= self.max_renames:
121
logger.critical(u"No suitable service name found after %i"
122
u" retries, exiting.", rename_count)
123
raise AvahiServiceError("Too many renames")
124
name = server.GetAlternativeServiceName(name)
125
logger.error(u"Changing name to %r ...", name)
126
syslogger.setFormatter(logging.Formatter\
127
('Mandos (%s): %%(levelname)s:'
128
' %%(message)s' % name))
131
self.rename_count += 1
133
"""Derived from the Avahi example code"""
134
if group is not None:
137
"""Derived from the Avahi example code"""
140
group = dbus.Interface\
141
(bus.get_object(avahi.DBUS_NAME,
142
server.EntryGroupNew()),
143
avahi.DBUS_INTERFACE_ENTRY_GROUP)
144
group.connect_to_signal('StateChanged',
145
entry_group_state_changed)
146
logger.debug(u"Adding service '%s' of type '%s' ...",
147
service.name, service.type)
149
self.interface, # interface
150
avahi.PROTO_INET6, # protocol
151
dbus.UInt32(0), # flags
152
self.name, self.type,
153
self.domain, self.host,
154
dbus.UInt16(self.port),
155
avahi.string_array_to_txt_array(self.TXT))
158
# From the Avahi example code:
159
group = None # our entry group
45
# This variable is used to optionally bind to a specified interface.
46
# It is a global variable to fit in with the other variables from the
47
# Avahi server example code.
48
serviceInterface = avahi.IF_UNSPEC
49
# From the Avahi server example code:
50
serviceName = "Mandos"
51
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
52
servicePort = None # Not known at startup
53
serviceTXT = [] # TXT record for the service
54
domain = "" # Domain to publish on, default to .local
55
host = "" # Host to publish records for, default to localhost
56
group = None #our entry group
57
rename_count = 12 # Counter so we only rename after collisions a
58
# sensible number of times
160
59
# End of Avahi example code
167
66
fingerprint: string (40 or 32 hexadecimal digits); used to
168
67
uniquely identify the client
169
68
secret: bytestring; sent verbatim (over TLS) to client
170
host: string; available for use by the checker command
171
created: datetime.datetime(); object creation, not client host
172
last_checked_ok: datetime.datetime() or None if not yet checked OK
173
timeout: datetime.timedelta(); How long from last_checked_ok
174
until this client is invalid
69
fqdn: string (FQDN); available for use by the checker command
70
created: datetime.datetime()
71
last_seen: datetime.datetime() or None if not yet seen
72
timeout: datetime.timedelta(); How long from last_seen until
73
this client is invalid
175
74
interval: datetime.timedelta(); How often to start a new checker
176
75
stop_hook: If set, called by stop() as stop_hook(self)
177
76
checker: subprocess.Popen(); a running checker process used
178
77
to see if the client lives.
179
'None' if no process is running.
78
Is None if no process is running.
180
79
checker_initiator_tag: a gobject event source tag, or None
181
80
stop_initiator_tag: - '' -
182
81
checker_callback_tag: - '' -
183
82
checker_command: string; External command which is run to check if
184
client lives. %() expansions are done at
83
client lives. %()s expansions are done at
185
84
runtime with vars(self) as dict, so that for
186
85
instance %(name)s can be used in the command.
187
86
Private attibutes:
188
87
_timeout: Real variable for 'timeout'
189
88
_interval: Real variable for 'interval'
190
_timeout_milliseconds: Used when calling gobject.timeout_add()
89
_timeout_milliseconds: Used by gobject.timeout_add()
191
90
_interval_milliseconds: - '' -
193
92
def _set_timeout(self, timeout):
213
112
interval = property(lambda self: self._interval,
215
114
del _set_interval
216
def __init__(self, name = None, stop_hook=None, config={}):
217
"""Note: the 'checker' key in 'config' sets the
218
'checker_command' attribute and *not* the 'checker'
115
def __init__(self, name=None, options=None, stop_hook=None,
116
fingerprint=None, secret=None, secfile=None,
117
fqdn=None, timeout=None, interval=-1, checker=None):
221
logger.debug(u"Creating client %r", self.name)
222
# Uppercase and remove spaces from fingerprint for later
223
# comparison purposes with return value from the fingerprint()
225
self.fingerprint = config["fingerprint"].upper()\
227
logger.debug(u" Fingerprint: %s", self.fingerprint)
228
if "secret" in config:
229
self.secret = config["secret"].decode(u"base64")
230
elif "secfile" in config:
231
sf = open(config["secfile"])
119
# Uppercase and remove spaces from fingerprint
120
# for later comparison purposes with return value of
121
# the fingerprint() function
122
self.fingerprint = fingerprint.upper().replace(u" ", u"")
124
self.secret = secret.decode(u"base64")
232
127
self.secret = sf.read()
235
raise TypeError(u"No secret or secfile for client %s"
237
self.host = config.get("host", "")
130
raise RuntimeError(u"No secret or secfile for client %s"
132
self.fqdn = fqdn # string
238
133
self.created = datetime.datetime.now()
239
self.last_checked_ok = None
240
self.timeout = string_to_delta(config["timeout"])
241
self.interval = string_to_delta(config["interval"])
134
self.last_seen = None
136
self.timeout = options.timeout
138
self.timeout = string_to_delta(timeout)
140
self.interval = options.interval
142
self.interval = string_to_delta(interval)
242
143
self.stop_hook = stop_hook
243
144
self.checker = None
244
145
self.checker_initiator_tag = None
245
146
self.stop_initiator_tag = None
246
147
self.checker_callback_tag = None
247
self.check_command = config["checker"]
148
self.check_command = checker
249
"""Start this client's checker and timeout hooks"""
150
"""Start this clients checker and timeout hooks"""
250
151
# Schedule a new checker to be started an 'interval' from now,
251
152
# and every interval from then on.
252
153
self.checker_initiator_tag = gobject.timeout_add\
262
163
"""Stop this client.
263
The possibility that a client might be restarted is left open,
264
but not currently used."""
265
# If this client doesn't have a secret, it is already stopped.
266
if hasattr(self, "secret") and self.secret:
267
logger.info(u"Stopping client %s", self.name)
271
if getattr(self, "stop_initiator_tag", False):
164
The possibility that this client might be restarted is left
165
open, but not currently used."""
166
logger.debug(u"Stopping client %s", self.name)
168
if self.stop_initiator_tag:
272
169
gobject.source_remove(self.stop_initiator_tag)
273
170
self.stop_initiator_tag = None
274
if getattr(self, "checker_initiator_tag", False):
171
if self.checker_initiator_tag:
275
172
gobject.source_remove(self.checker_initiator_tag)
276
173
self.checker_initiator_tag = None
277
174
self.stop_checker()
280
177
# Do not run this again if called by a gobject.timeout_add
282
179
def __del__(self):
283
self.stop_hook = None
180
# Some code duplication here and in stop()
181
if hasattr(self, "stop_initiator_tag") \
182
and self.stop_initiator_tag:
183
gobject.source_remove(self.stop_initiator_tag)
184
self.stop_initiator_tag = None
185
if hasattr(self, "checker_initiator_tag") \
186
and self.checker_initiator_tag:
187
gobject.source_remove(self.checker_initiator_tag)
188
self.checker_initiator_tag = None
285
190
def checker_callback(self, pid, condition):
286
191
"""The checker has completed, so take appropriate actions."""
287
192
now = datetime.datetime.now()
288
self.checker_callback_tag = None
290
193
if os.WIFEXITED(condition) \
291
194
and (os.WEXITSTATUS(condition) == 0):
292
logger.info(u"Checker for %(name)s succeeded",
294
self.last_checked_ok = now
195
logger.debug(u"Checker for %(name)s succeeded",
295
198
gobject.source_remove(self.stop_initiator_tag)
296
199
self.stop_initiator_tag = gobject.timeout_add\
297
200
(self._timeout_milliseconds,
300
203
logger.warning(u"Checker for %(name)s crashed?",
303
logger.info(u"Checker for %(name)s failed",
206
logger.debug(u"Checker for %(name)s failed",
209
self.checker_callback_tag = None
305
210
def start_checker(self):
306
211
"""Start a new checker subprocess if one is not running.
307
212
If a checker already exists, leave it running and do
309
# The reason for not killing a running checker is that if we
310
# did that, then if a checker (for some reason) started
311
# running slowly and taking more than 'interval' time, the
312
# client would inevitably timeout, since no checker would get
313
# a chance to run to completion. If we instead leave running
314
# checkers alone, the checker would have to take more time
315
# than 'timeout' for the client to be declared invalid, which
316
# is as it should be.
317
214
if self.checker is None:
319
# In case check_command has exactly one % operator
320
command = self.check_command % self.host
216
command = self.check_command % self.fqdn
321
217
except TypeError:
322
# Escape attributes for the shell
323
218
escaped_attrs = dict((key, re.escape(str(val)))
325
220
vars(self).iteritems())
327
222
command = self.check_command % escaped_attrs
328
223
except TypeError, error:
329
logger.error(u'Could not format string "%s":'
330
u' %s', self.check_command, error)
224
logger.critical(u'Could not format string "%s":'
225
u' %s', self.check_command, error)
331
226
return True # Try again later
333
logger.info(u"Starting checker %r for %s",
335
self.checker = subprocess.Popen(command,
228
logger.debug(u"Starting checker %r for %s",
230
self.checker = subprocess.\
232
close_fds=True, shell=True,
338
234
self.checker_callback_tag = gobject.child_watch_add\
339
235
(self.checker.pid,
340
236
self.checker_callback)
346
242
def stop_checker(self):
347
243
"""Force the checker process, if any, to stop."""
348
if self.checker_callback_tag:
349
gobject.source_remove(self.checker_callback_tag)
350
self.checker_callback_tag = None
351
if getattr(self, "checker", None) is None:
244
if not hasattr(self, "checker") or self.checker is None:
353
logger.debug(u"Stopping checker for %(name)s", vars(self))
355
os.kill(self.checker.pid, signal.SIGTERM)
357
#if self.checker.poll() is None:
358
# os.kill(self.checker.pid, signal.SIGKILL)
359
except OSError, error:
360
if error.errno != errno.ESRCH: # No such process
246
gobject.source_remove(self.checker_callback_tag)
247
self.checker_callback_tag = None
248
os.kill(self.checker.pid, signal.SIGTERM)
249
if self.checker.poll() is None:
250
os.kill(self.checker.pid, signal.SIGKILL)
362
251
self.checker = None
363
def still_valid(self):
252
def still_valid(self, now=None):
364
253
"""Has the timeout not yet passed for this client?"""
365
now = datetime.datetime.now()
366
if self.last_checked_ok is None:
255
now = datetime.datetime.now()
256
if self.last_seen is None:
367
257
return now < (self.created + self.timeout)
369
return now < (self.last_checked_ok + self.timeout)
259
return now < (self.last_seen + self.timeout)
372
262
def peer_certificate(session):
373
"Return the peer's OpenPGP certificate as a bytestring"
263
"Return an OpenPGP data packet string for the peer's certificate"
374
264
# If not an OpenPGP certificate...
375
265
if gnutls.library.functions.gnutls_certificate_type_get\
376
266
(session._c_object) \
389
279
def fingerprint(openpgp):
390
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
280
"Convert an OpenPGP data string to a hexdigit fingerprint string"
281
# New empty GnuTLS certificate
282
crt = gnutls.library.types.gnutls_openpgp_crt_t()
283
gnutls.library.functions.gnutls_openpgp_crt_init\
391
285
# New GnuTLS "datum" with the OpenPGP public key
392
286
datum = gnutls.library.types.gnutls_datum_t\
393
287
(ctypes.cast(ctypes.c_char_p(openpgp),
394
288
ctypes.POINTER(ctypes.c_ubyte)),
395
289
ctypes.c_uint(len(openpgp)))
396
# New empty GnuTLS certificate
397
crt = gnutls.library.types.gnutls_openpgp_crt_t()
398
gnutls.library.functions.gnutls_openpgp_crt_init\
400
290
# Import the OpenPGP public key into the certificate
401
gnutls.library.functions.gnutls_openpgp_crt_import\
402
(crt, ctypes.byref(datum),
403
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
291
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
294
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
404
295
# New buffer for the fingerprint
405
296
buffer = ctypes.create_string_buffer(20)
406
297
buffer_length = ctypes.c_size_t()
422
313
Note: This will run in its own forked process."""
424
315
def handle(self):
425
logger.info(u"TCP connection from: %s",
316
logger.debug(u"TCP connection from: %s",
426
317
unicode(self.client_address))
427
session = gnutls.connection.ClientSession\
428
(self.request, gnutls.connection.X509Credentials())
430
line = self.request.makefile().readline()
431
logger.debug(u"Protocol version: %r", line)
433
if int(line.strip().split()[0]) > 1:
435
except (ValueError, IndexError, RuntimeError), error:
436
logger.error(u"Unknown protocol version: %s", error)
439
# Note: gnutls.connection.X509Credentials is really a generic
440
# GnuTLS certificate credentials object so long as no X.509
441
# keys are added to it. Therefore, we can use it here despite
442
# using OpenPGP certificates.
318
session = gnutls.connection.ClientSession(self.request,
444
322
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
445
323
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
447
priority = "NORMAL" # Fallback default, since this
449
if self.server.settings["priority"]:
450
priority = self.server.settings["priority"]
325
priority = "SECURE256"
451
327
gnutls.library.functions.gnutls_priority_set_direct\
452
328
(session._c_object, priority, None);
455
331
session.handshake()
456
332
except gnutls.errors.GNUTLSError, error:
457
logger.warning(u"Handshake failed: %s", error)
333
logger.debug(u"Handshake failed: %s", error)
458
334
# Do not run session.bye() here: the session is not
459
335
# established. Just abandon the request.
462
338
fpr = fingerprint(peer_certificate(session))
463
339
except (TypeError, gnutls.errors.GNUTLSError), error:
464
logger.warning(u"Bad certificate: %s", error)
340
logger.debug(u"Bad certificate: %s", error)
467
343
logger.debug(u"Fingerprint: %s", fpr)
469
for c in self.server.clients:
470
346
if c.fingerprint == fpr:
474
logger.warning(u"Client not found for fingerprint: %s",
478
349
# Have to check if client.still_valid(), since it is possible
479
350
# that the client timed out while establishing the GnuTLS
481
if not client.still_valid():
482
logger.warning(u"Client %(name)s is invalid",
352
if (not client) or (not client.still_valid()):
354
logger.debug(u"Client %(name)s is invalid",
357
logger.debug(u"Client not found for fingerprint: %s",
512
387
"""This overrides the normal server_bind() function
513
388
to bind to an interface if one was specified, and also NOT to
514
389
bind to an address or port if they were not specified."""
515
if self.settings["interface"]:
516
# 25 is from /usr/include/asm-i486/socket.h
517
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
390
if self.options.interface:
391
if not hasattr(socket, "SO_BINDTODEVICE"):
392
# From /usr/include/asm-i486/socket.h
393
socket.SO_BINDTODEVICE = 25
519
395
self.socket.setsockopt(socket.SOL_SOCKET,
521
self.settings["interface"])
396
socket.SO_BINDTODEVICE,
397
self.options.interface)
522
398
except socket.error, error:
523
399
if error[0] == errno.EPERM:
524
logger.error(u"No permission to"
525
u" bind to interface %s",
526
self.settings["interface"])
400
logger.warning(u"No permission to"
401
u" bind to interface %s",
402
self.options.interface)
529
405
# Only bind(2) the socket if we really need to.
452
"""From the Avahi server example code"""
453
global group, serviceName, serviceType, servicePort, serviceTXT, \
456
group = dbus.Interface(
457
bus.get_object( avahi.DBUS_NAME,
458
server.EntryGroupNew()),
459
avahi.DBUS_INTERFACE_ENTRY_GROUP)
460
group.connect_to_signal('StateChanged',
461
entry_group_state_changed)
462
logger.debug(u"Adding service '%s' of type '%s' ...",
463
serviceName, serviceType)
466
serviceInterface, # interface
467
avahi.PROTO_INET6, # protocol
468
dbus.UInt32(0), # flags
469
serviceName, serviceType,
471
dbus.UInt16(servicePort),
472
avahi.string_array_to_txt_array(serviceTXT))
476
def remove_service():
477
"""From the Avahi server example code"""
480
if not group is None:
582
484
def server_state_changed(state):
583
"""Derived from the Avahi example code"""
485
"""From the Avahi server example code"""
584
486
if state == avahi.SERVER_COLLISION:
585
logger.error(u"Server name collision")
487
logger.warning(u"Server name collision")
587
489
elif state == avahi.SERVER_RUNNING:
591
493
def entry_group_state_changed(state, error):
592
"""Derived from the Avahi example code"""
494
"""From the Avahi server example code"""
495
global serviceName, server, rename_count
593
497
logger.debug(u"state change: %i", state)
595
499
if state == avahi.ENTRY_GROUP_ESTABLISHED:
596
500
logger.debug(u"Service established.")
597
501
elif state == avahi.ENTRY_GROUP_COLLISION:
598
logger.warning(u"Service name collision.")
503
rename_count = rename_count - 1
505
name = server.GetAlternativeServiceName(name)
506
logger.warning(u"Service name collision, "
507
u"changing name to '%s' ...", name)
512
logger.error(u"No suitable service name found after %i"
513
u" retries, exiting.", n_rename)
600
515
elif state == avahi.ENTRY_GROUP_FAILURE:
601
logger.critical(u"Error in group state changed %s",
603
raise AvahiGroupError("State changed: %s", str(error))
516
logger.error(u"Error in group state changed %s",
605
521
def if_nametoindex(interface):
606
"""Call the C function if_nametoindex(), or equivalent"""
607
global if_nametoindex
522
"""Call the C function if_nametoindex()"""
609
if "ctypes.util" not in sys.modules:
611
if_nametoindex = ctypes.cdll.LoadLibrary\
612
(ctypes.util.find_library("c")).if_nametoindex
524
libc = ctypes.cdll.LoadLibrary("libc.so.6")
525
return libc.if_nametoindex(interface)
613
526
except (OSError, AttributeError):
614
527
if "struct" not in sys.modules:
616
529
if "fcntl" not in sys.modules:
618
def if_nametoindex(interface):
619
"Get an interface index the hard way, i.e. using fcntl()"
620
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
622
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
623
struct.pack("16s16x", interface))
625
interface_index = struct.unpack("I", ifreq[16:20])[0]
626
return interface_index
627
return if_nametoindex(interface)
630
def daemon(nochdir = False, noclose = False):
531
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
533
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
534
struct.pack("16s16x", interface))
536
interface_index = struct.unpack("I", ifreq[16:20])[0]
537
return interface_index
540
def daemon(nochdir, noclose):
631
541
"""See daemon(3). Standard BSD Unix function.
632
542
This should really exist as os.daemon, but it doesn't (yet)."""
654
global main_loop_started
561
def killme(status = 0):
562
logger.debug("Stopping server with exit status %d", status)
564
if main_loop_started:
570
if __name__ == '__main__':
655
572
main_loop_started = False
657
parser = OptionParser(version = "Mandos server %s" % version)
573
parser = OptionParser()
658
574
parser.add_option("-i", "--interface", type="string",
659
metavar="IF", help="Bind to interface IF")
660
parser.add_option("-a", "--address", type="string",
661
help="Address to listen for requests on")
662
parser.add_option("-p", "--port", type="int",
575
default=None, metavar="IF",
576
help="Bind to interface IF")
577
parser.add_option("-p", "--port", type="int", default=None,
663
578
help="Port number to receive requests on")
579
parser.add_option("--timeout", type="string", # Parsed later
581
help="Amount of downtime allowed for clients")
582
parser.add_option("--interval", type="string", # Parsed later
584
help="How often to check that a client is up")
664
585
parser.add_option("--check", action="store_true", default=False,
665
586
help="Run self-test")
666
parser.add_option("--debug", action="store_true",
667
help="Debug mode; run in foreground and log to"
669
parser.add_option("--priority", type="string", help="GnuTLS"
670
" priority string (see GnuTLS documentation)")
671
parser.add_option("--servicename", type="string", metavar="NAME",
672
help="Zeroconf service name")
673
parser.add_option("--configdir", type="string",
674
default="/etc/mandos", metavar="DIR",
675
help="Directory to search for configuration"
587
parser.add_option("--debug", action="store_true", default=False,
677
589
(options, args) = parser.parse_args()
679
591
if options.check:
681
593
doctest.testmod()
684
# Default values for config file for server-global settings
685
server_defaults = { "interface": "",
690
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
691
"servicename": "Mandos",
694
# Parse config file for server-global settings
695
server_config = ConfigParser.SafeConfigParser(server_defaults)
697
server_config.read(os.path.join(options.configdir, "mandos.conf"))
698
server_section = "server"
699
# Convert the SafeConfigParser object to a dict
700
server_settings = dict(server_config.items(server_section))
701
# Use getboolean on the boolean config option
702
server_settings["debug"] = server_config.getboolean\
703
(server_section, "debug")
706
# Override the settings from the config file with command line
708
for option in ("interface", "address", "port", "debug",
709
"priority", "servicename", "configdir"):
710
value = getattr(options, option)
711
if value is not None:
712
server_settings[option] = value
714
# Now we have our good server settings in "server_settings"
716
debug = server_settings["debug"]
719
syslogger.setLevel(logging.WARNING)
721
if server_settings["servicename"] != "Mandos":
722
syslogger.setFormatter(logging.Formatter\
723
('Mandos (%s): %%(levelname)s:'
725
% server_settings["servicename"]))
727
# Parse config file with clients
728
client_defaults = { "timeout": "1h",
730
"checker": "fping -q -- %%(host)s",
732
client_config = ConfigParser.SafeConfigParser(client_defaults)
733
client_config.read(os.path.join(server_settings["configdir"],
737
service = AvahiService(name = server_settings["servicename"],
738
type = "_mandos._tcp", );
739
if server_settings["interface"]:
740
service.interface = if_nametoindex(server_settings["interface"])
745
# From the Avahi example code
596
# Parse the time arguments
598
options.timeout = string_to_delta(options.timeout)
600
parser.error("option --timeout: Unparseable time")
602
options.interval = string_to_delta(options.interval)
604
parser.error("option --interval: Unparseable time")
607
defaults = { "checker": "fping -q -- %%(fqdn)s" }
608
client_config = ConfigParser.SafeConfigParser(defaults)
609
#client_config.readfp(open("secrets.conf"), "secrets.conf")
610
client_config.read("mandos-clients.conf")
612
# From the Avahi server example code
746
613
DBusGMainLoop(set_as_default=True )
747
614
main_loop = gobject.MainLoop()
748
615
bus = dbus.SystemBus()
763
632
def remove_from_clients(client):
764
633
clients.remove(client)
766
logger.critical(u"No clients left, exiting")
635
logger.debug(u"No clients left, exiting")
769
clients.update(Set(Client(name = section,
638
clients.update(Set(Client(name=section, options=options,
770
639
stop_hook = remove_from_clients,
772
= dict(client_config.items(section)))
640
**(dict(client_config\
773
642
for section in client_config.sections()))
775
logger.critical(u"No clients defined")
781
pidfilename = "/var/run/mandos/mandos.pid"
784
pidfile = open(pidfilename, "w")
785
pidfile.write(str(pid) + "\n")
789
logger.error(u"Could not write %s file with PID %d",
790
pidfilename, os.getpid())
793
648
"Cleanup function; run on exit"
795
# From the Avahi example code
650
# From the Avahi server example code
796
651
if not group is None:
799
654
# End of Avahi example code
802
client = clients.pop()
656
for client in clients:
803
657
client.stop_hook = None
809
663
signal.signal(signal.SIGINT, signal.SIG_IGN)
810
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
811
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
664
signal.signal(signal.SIGHUP, lambda signum, frame: killme())
665
signal.signal(signal.SIGTERM, lambda signum, frame: killme())
813
667
for client in clients:
816
tcp_server = IPv6_TCPServer((server_settings["address"],
817
server_settings["port"]),
670
tcp_server = IPv6_TCPServer((None, options.port),
819
settings=server_settings,
821
# Find out what port we got
822
service.port = tcp_server.socket.getsockname()[1]
823
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
824
u" scope_id %d" % tcp_server.socket.getsockname())
826
#service.interface = tcp_server.socket.getsockname()[3]
829
# From the Avahi example code
830
server.connect_to_signal("StateChanged", server_state_changed)
832
server_state_changed(server.GetState())
833
except dbus.exceptions.DBusException, error:
834
logger.critical(u"DBusException: %s", error)
836
# End of Avahi example code
838
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
839
lambda *args, **kwargs:
840
tcp_server.handle_request\
841
(*args[2:], **kwargs) or True)
843
logger.debug(u"Starting main loop")
674
# Find out what random port we got
675
servicePort = tcp_server.socket.getsockname()[1]
676
logger.debug(u"Now listening on port %d", servicePort)
678
if options.interface is not None:
679
serviceInterface = if_nametoindex(options.interface)
681
# From the Avahi server example code
682
server.connect_to_signal("StateChanged", server_state_changed)
684
server_state_changed(server.GetState())
685
except dbus.exceptions.DBusException, error:
686
logger.critical(u"DBusException: %s", error)
688
# End of Avahi example code
690
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
691
lambda *args, **kwargs:
692
tcp_server.handle_request(*args[2:],
844
695
main_loop_started = True
846
except AvahiError, error:
847
logger.critical(u"AvahiError: %s" + unicode(error))
849
697
except KeyboardInterrupt:
853
if __name__ == '__main__':