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>.
4
32
from __future__ import division
31
61
from dbus.mainloop.glib import DBusGMainLoop
35
import logging.handlers
37
66
logger = logging.Logger('mandos')
38
67
syslogger = logging.handlers.SysLogHandler\
39
(facility = logging.handlers.SysLogHandler.LOG_DAEMON)
68
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
40
70
syslogger.setFormatter(logging.Formatter\
41
('%(levelname)s: %(message)s'))
71
('Mandos: %(levelname)s: %(message)s'))
42
72
logger.addHandler(syslogger)
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
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
59
160
# End of Avahi example code
66
167
fingerprint: string (40 or 32 hexadecimal digits); used to
67
168
uniquely identify the client
68
169
secret: bytestring; sent verbatim (over TLS) to client
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
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
74
175
interval: datetime.timedelta(); How often to start a new checker
75
176
stop_hook: If set, called by stop() as stop_hook(self)
76
177
checker: subprocess.Popen(); a running checker process used
77
178
to see if the client lives.
78
Is None if no process is running.
179
'None' if no process is running.
79
180
checker_initiator_tag: a gobject event source tag, or None
80
181
stop_initiator_tag: - '' -
81
182
checker_callback_tag: - '' -
82
183
checker_command: string; External command which is run to check if
83
client lives. %()s expansions are done at
184
client lives. %() expansions are done at
84
185
runtime with vars(self) as dict, so that for
85
186
instance %(name)s can be used in the command.
86
187
Private attibutes:
87
188
_timeout: Real variable for 'timeout'
88
189
_interval: Real variable for 'interval'
89
_timeout_milliseconds: Used by gobject.timeout_add()
190
_timeout_milliseconds: Used when calling gobject.timeout_add()
90
191
_interval_milliseconds: - '' -
92
193
def _set_timeout(self, timeout):
112
213
interval = property(lambda self: self._interval,
114
215
del _set_interval
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):
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'
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")
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"])
127
232
self.secret = sf.read()
130
raise RuntimeError(u"No secret or secfile for client %s"
132
self.fqdn = fqdn # string
235
raise TypeError(u"No secret or secfile for client %s"
237
self.host = config.get("host", "")
133
238
self.created = datetime.datetime.now()
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)
239
self.last_checked_ok = None
240
self.timeout = string_to_delta(config["timeout"])
241
self.interval = string_to_delta(config["interval"])
143
242
self.stop_hook = stop_hook
144
243
self.checker = None
145
244
self.checker_initiator_tag = None
146
245
self.stop_initiator_tag = None
147
246
self.checker_callback_tag = None
148
self.check_command = checker
247
self.check_command = config["checker"]
150
"""Start this clients checker and timeout hooks"""
249
"""Start this client's checker and timeout hooks"""
151
250
# Schedule a new checker to be started an 'interval' from now,
152
251
# and every interval from then on.
153
252
self.checker_initiator_tag = gobject.timeout_add\
163
262
"""Stop this client.
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:
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):
169
272
gobject.source_remove(self.stop_initiator_tag)
170
273
self.stop_initiator_tag = None
171
if self.checker_initiator_tag:
274
if getattr(self, "checker_initiator_tag", False):
172
275
gobject.source_remove(self.checker_initiator_tag)
173
276
self.checker_initiator_tag = None
174
277
self.stop_checker()
177
280
# Do not run this again if called by a gobject.timeout_add
179
282
def __del__(self):
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
283
self.stop_hook = None
190
285
def checker_callback(self, pid, condition):
191
286
"""The checker has completed, so take appropriate actions."""
192
287
now = datetime.datetime.now()
288
self.checker_callback_tag = None
193
290
if os.WIFEXITED(condition) \
194
291
and (os.WEXITSTATUS(condition) == 0):
195
logger.debug(u"Checker for %(name)s succeeded",
292
logger.info(u"Checker for %(name)s succeeded",
294
self.last_checked_ok = now
198
295
gobject.source_remove(self.stop_initiator_tag)
199
296
self.stop_initiator_tag = gobject.timeout_add\
200
297
(self._timeout_milliseconds,
203
300
logger.warning(u"Checker for %(name)s crashed?",
206
logger.debug(u"Checker for %(name)s failed",
209
self.checker_callback_tag = None
303
logger.info(u"Checker for %(name)s failed",
210
305
def start_checker(self):
211
306
"""Start a new checker subprocess if one is not running.
212
307
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.
214
317
if self.checker is None:
216
command = self.check_command % self.fqdn
319
# In case check_command has exactly one % operator
320
command = self.check_command % self.host
217
321
except TypeError:
322
# Escape attributes for the shell
218
323
escaped_attrs = dict((key, re.escape(str(val)))
220
325
vars(self).iteritems())
222
327
command = self.check_command % escaped_attrs
223
328
except TypeError, error:
224
logger.critical(u'Could not format string "%s":'
225
u' %s', self.check_command, error)
329
logger.error(u'Could not format string "%s":'
330
u' %s', self.check_command, error)
226
331
return True # Try again later
228
logger.debug(u"Starting checker %r for %s",
230
self.checker = subprocess.\
232
close_fds=True, shell=True,
333
logger.info(u"Starting checker %r for %s",
335
self.checker = subprocess.Popen(command,
234
338
self.checker_callback_tag = gobject.child_watch_add\
235
339
(self.checker.pid,
236
340
self.checker_callback)
242
346
def stop_checker(self):
243
347
"""Force the checker process, if any, to stop."""
244
if not hasattr(self, "checker") or self.checker is None:
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:
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)
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
251
362
self.checker = None
252
def still_valid(self, now=None):
363
def still_valid(self):
253
364
"""Has the timeout not yet passed for this client?"""
255
now = datetime.datetime.now()
256
if self.last_seen is None:
365
now = datetime.datetime.now()
366
if self.last_checked_ok is None:
257
367
return now < (self.created + self.timeout)
259
return now < (self.last_seen + self.timeout)
369
return now < (self.last_checked_ok + self.timeout)
262
372
def peer_certificate(session):
263
"Return an OpenPGP data packet string for the peer's certificate"
373
"Return the peer's OpenPGP certificate as a bytestring"
264
374
# If not an OpenPGP certificate...
265
375
if gnutls.library.functions.gnutls_certificate_type_get\
266
376
(session._c_object) \
279
389
def fingerprint(openpgp):
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\
390
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
285
391
# New GnuTLS "datum" with the OpenPGP public key
286
392
datum = gnutls.library.types.gnutls_datum_t\
287
393
(ctypes.cast(ctypes.c_char_p(openpgp),
288
394
ctypes.POINTER(ctypes.c_ubyte)),
289
395
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\
290
400
# Import the OpenPGP public key into the certificate
291
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
294
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
401
gnutls.library.functions.gnutls_openpgp_crt_import\
402
(crt, ctypes.byref(datum),
403
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
295
404
# New buffer for the fingerprint
296
405
buffer = ctypes.create_string_buffer(20)
297
406
buffer_length = ctypes.c_size_t()
313
422
Note: This will run in its own forked process."""
315
424
def handle(self):
316
logger.debug(u"TCP connection from: %s",
425
logger.info(u"TCP connection from: %s",
317
426
unicode(self.client_address))
318
session = gnutls.connection.ClientSession(self.request,
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.
322
444
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
323
445
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
325
priority = "SECURE256"
447
priority = "NORMAL" # Fallback default, since this
449
if self.server.settings["priority"]:
450
priority = self.server.settings["priority"]
327
451
gnutls.library.functions.gnutls_priority_set_direct\
328
452
(session._c_object, priority, None);
331
455
session.handshake()
332
456
except gnutls.errors.GNUTLSError, error:
333
logger.debug(u"Handshake failed: %s", error)
457
logger.warning(u"Handshake failed: %s", error)
334
458
# Do not run session.bye() here: the session is not
335
459
# established. Just abandon the request.
338
462
fpr = fingerprint(peer_certificate(session))
339
463
except (TypeError, gnutls.errors.GNUTLSError), error:
340
logger.debug(u"Bad certificate: %s", error)
464
logger.warning(u"Bad certificate: %s", error)
343
467
logger.debug(u"Fingerprint: %s", fpr)
469
for c in self.server.clients:
346
470
if c.fingerprint == fpr:
474
logger.warning(u"Client not found for fingerprint: %s",
349
478
# Have to check if client.still_valid(), since it is possible
350
479
# that the client timed out while establishing the GnuTLS
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",
481
if not client.still_valid():
482
logger.warning(u"Client %(name)s is invalid",
387
512
"""This overrides the normal server_bind() function
388
513
to bind to an interface if one was specified, and also NOT to
389
514
bind to an address or port if they were not specified."""
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
515
if self.settings["interface"]:
516
# 25 is from /usr/include/asm-i486/socket.h
517
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
395
519
self.socket.setsockopt(socket.SOL_SOCKET,
396
socket.SO_BINDTODEVICE,
397
self.options.interface)
521
self.settings["interface"])
398
522
except socket.error, error:
399
523
if error[0] == errno.EPERM:
400
logger.warning(u"No permission to"
401
u" bind to interface %s",
402
self.options.interface)
524
logger.error(u"No permission to"
525
u" bind to interface %s",
526
self.settings["interface"])
405
529
# 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:
484
582
def server_state_changed(state):
485
"""From the Avahi server example code"""
583
"""Derived from the Avahi example code"""
486
584
if state == avahi.SERVER_COLLISION:
487
logger.warning(u"Server name collision")
585
logger.error(u"Server name collision")
489
587
elif state == avahi.SERVER_RUNNING:
493
591
def entry_group_state_changed(state, error):
494
"""From the Avahi server example code"""
495
global serviceName, server, rename_count
592
"""Derived from the Avahi example code"""
497
593
logger.debug(u"state change: %i", state)
499
595
if state == avahi.ENTRY_GROUP_ESTABLISHED:
500
596
logger.debug(u"Service established.")
501
597
elif state == avahi.ENTRY_GROUP_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)
598
logger.warning(u"Service name collision.")
515
600
elif state == avahi.ENTRY_GROUP_FAILURE:
516
logger.error(u"Error in group state changed %s",
601
logger.critical(u"Error in group state changed %s",
603
raise AvahiGroupError("State changed: %s", str(error))
521
605
def if_nametoindex(interface):
522
"""Call the C function if_nametoindex()"""
606
"""Call the C function if_nametoindex(), or equivalent"""
607
global if_nametoindex
524
libc = ctypes.cdll.LoadLibrary("libc.so.6")
525
return libc.if_nametoindex(interface)
609
if "ctypes.util" not in sys.modules:
611
if_nametoindex = ctypes.cdll.LoadLibrary\
612
(ctypes.util.find_library("c")).if_nametoindex
526
613
except (OSError, AttributeError):
527
614
if "struct" not in sys.modules:
529
616
if "fcntl" not in sys.modules:
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):
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):
541
631
"""See daemon(3). Standard BSD Unix function.
542
632
This should really exist as os.daemon, but it doesn't (yet)."""
561
def killme(status = 0):
562
logger.debug("Stopping server with exit status %d", status)
564
if main_loop_started:
570
if __name__ == '__main__':
654
global main_loop_started
572
655
main_loop_started = False
573
parser = OptionParser()
657
parser = OptionParser(version = "Mandos server %s" % version)
574
658
parser.add_option("-i", "--interface", type="string",
575
default=None, metavar="IF",
576
help="Bind to interface IF")
577
parser.add_option("-p", "--port", type="int", default=None,
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",
578
663
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")
585
664
parser.add_option("--check", action="store_true", default=False,
586
665
help="Run self-test")
587
parser.add_option("--debug", action="store_true", default=False,
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"
589
677
(options, args) = parser.parse_args()
591
679
if options.check:
593
681
doctest.testmod()
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
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
613
746
DBusGMainLoop(set_as_default=True )
614
747
main_loop = gobject.MainLoop()
615
748
bus = dbus.SystemBus()
632
763
def remove_from_clients(client):
633
764
clients.remove(client)
635
logger.debug(u"No clients left, exiting")
766
logger.critical(u"No clients left, exiting")
638
clients.update(Set(Client(name=section, options=options,
769
clients.update(Set(Client(name = section,
639
770
stop_hook = remove_from_clients,
640
**(dict(client_config\
772
= dict(client_config.items(section)))
642
773
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())
648
793
"Cleanup function; run on exit"
650
# From the Avahi server example code
795
# From the Avahi example code
651
796
if not group is None:
654
799
# End of Avahi example code
656
for client in clients:
802
client = clients.pop()
657
803
client.stop_hook = None
663
809
signal.signal(signal.SIGINT, signal.SIG_IGN)
664
signal.signal(signal.SIGHUP, lambda signum, frame: killme())
665
signal.signal(signal.SIGTERM, lambda signum, frame: killme())
810
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
811
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
667
813
for client in clients:
670
tcp_server = IPv6_TCPServer((None, options.port),
816
tcp_server = IPv6_TCPServer((server_settings["address"],
817
server_settings["port"]),
819
settings=server_settings,
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:],
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")
695
844
main_loop_started = True
846
except AvahiError, error:
847
logger.critical(u"AvahiError: %s" + unicode(error))
697
849
except KeyboardInterrupt:
853
if __name__ == '__main__':