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
65
37
logger = logging.Logger('mandos')
66
38
syslogger = logging.handlers.SysLogHandler\
67
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
39
(facility = logging.handlers.SysLogHandler.LOG_DAEMON)
69
40
syslogger.setFormatter(logging.Formatter\
70
('Mandos: %(levelname)s: %(message)s'))
41
('%(levelname)s: %(message)s'))
71
42
logger.addHandler(syslogger)
74
class AvahiError(Exception):
75
def __init__(self, value):
78
return repr(self.value)
80
class AvahiServiceError(AvahiError):
83
class AvahiGroupError(AvahiError):
87
class AvahiService(object):
88
"""An Avahi (Zeroconf) service.
90
interface: integer; avahi.IF_UNSPEC or an interface index.
91
Used to optionally bind to the specified interface.
92
name: string; Example: 'Mandos'
93
type: string; Example: '_mandos._tcp'.
94
See <http://www.dns-sd.org/ServiceTypes.html>
95
port: integer; what port to announce
96
TXT: list of strings; TXT record for the service
97
domain: string; Domain to publish on, default to .local if empty.
98
host: string; Host to publish records for, default is localhost
99
max_renames: integer; maximum number of renames
100
rename_count: integer; counter so we only rename after collisions
101
a sensible number of times
103
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
104
type = None, port = None, TXT = None, domain = "",
105
host = "", max_renames = 32768):
106
self.interface = interface
116
self.rename_count = 0
118
"""Derived from the Avahi example code"""
119
if self.rename_count >= self.max_renames:
120
logger.critical(u"No suitable service name found after %i"
121
u" retries, exiting.", rename_count)
122
raise AvahiServiceError("Too many renames")
123
name = server.GetAlternativeServiceName(name)
124
logger.error(u"Changing name to %r ...", name)
125
syslogger.setFormatter(logging.Formatter\
126
('Mandos (%s): %%(levelname)s:'
127
' %%(message)s' % name))
130
self.rename_count += 1
132
"""Derived from the Avahi example code"""
133
if group is not None:
136
"""Derived from the Avahi example code"""
139
group = dbus.Interface\
140
(bus.get_object(avahi.DBUS_NAME,
141
server.EntryGroupNew()),
142
avahi.DBUS_INTERFACE_ENTRY_GROUP)
143
group.connect_to_signal('StateChanged',
144
entry_group_state_changed)
145
logger.debug(u"Adding service '%s' of type '%s' ...",
146
service.name, service.type)
148
self.interface, # interface
149
avahi.PROTO_INET6, # protocol
150
dbus.UInt32(0), # flags
151
self.name, self.type,
152
self.domain, self.host,
153
dbus.UInt16(self.port),
154
avahi.string_array_to_txt_array(self.TXT))
157
# From the Avahi example code:
158
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
159
59
# End of Avahi example code
166
66
fingerprint: string (40 or 32 hexadecimal digits); used to
167
67
uniquely identify the client
168
68
secret: bytestring; sent verbatim (over TLS) to client
169
host: string; available for use by the checker command
170
created: datetime.datetime(); object creation, not client host
171
last_checked_ok: datetime.datetime() or None if not yet checked OK
172
timeout: datetime.timedelta(); How long from last_checked_ok
173
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
174
74
interval: datetime.timedelta(); How often to start a new checker
175
75
stop_hook: If set, called by stop() as stop_hook(self)
176
76
checker: subprocess.Popen(); a running checker process used
177
77
to see if the client lives.
178
'None' if no process is running.
78
Is None if no process is running.
179
79
checker_initiator_tag: a gobject event source tag, or None
180
80
stop_initiator_tag: - '' -
181
81
checker_callback_tag: - '' -
182
82
checker_command: string; External command which is run to check if
183
client lives. %() expansions are done at
83
client lives. %()s expansions are done at
184
84
runtime with vars(self) as dict, so that for
185
85
instance %(name)s can be used in the command.
186
86
Private attibutes:
187
87
_timeout: Real variable for 'timeout'
188
88
_interval: Real variable for 'interval'
189
_timeout_milliseconds: Used when calling gobject.timeout_add()
89
_timeout_milliseconds: Used by gobject.timeout_add()
190
90
_interval_milliseconds: - '' -
192
92
def _set_timeout(self, timeout):
212
112
interval = property(lambda self: self._interval,
214
114
del _set_interval
215
def __init__(self, name = None, stop_hook=None, config={}):
216
"""Note: the 'checker' key in 'config' sets the
217
'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):
220
logger.debug(u"Creating client %r", self.name)
221
# Uppercase and remove spaces from fingerprint for later
222
# comparison purposes with return value from the fingerprint()
224
self.fingerprint = config["fingerprint"].upper()\
226
logger.debug(u" Fingerprint: %s", self.fingerprint)
227
if "secret" in config:
228
self.secret = config["secret"].decode(u"base64")
229
elif "secfile" in config:
230
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")
231
127
self.secret = sf.read()
234
raise TypeError(u"No secret or secfile for client %s"
236
self.host = config.get("host", "")
130
raise RuntimeError(u"No secret or secfile for client %s"
132
self.fqdn = fqdn # string
237
133
self.created = datetime.datetime.now()
238
self.last_checked_ok = None
239
self.timeout = string_to_delta(config["timeout"])
240
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)
241
143
self.stop_hook = stop_hook
242
144
self.checker = None
243
145
self.checker_initiator_tag = None
244
146
self.stop_initiator_tag = None
245
147
self.checker_callback_tag = None
246
self.check_command = config["checker"]
148
self.check_command = checker
248
"""Start this client's checker and timeout hooks"""
150
"""Start this clients checker and timeout hooks"""
249
151
# Schedule a new checker to be started an 'interval' from now,
250
152
# and every interval from then on.
251
153
self.checker_initiator_tag = gobject.timeout_add\
261
163
"""Stop this client.
262
The possibility that a client might be restarted is left open,
263
but not currently used."""
264
# If this client doesn't have a secret, it is already stopped.
265
if hasattr(self, "secret") and self.secret:
266
logger.info(u"Stopping client %s", self.name)
270
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:
271
169
gobject.source_remove(self.stop_initiator_tag)
272
170
self.stop_initiator_tag = None
273
if getattr(self, "checker_initiator_tag", False):
171
if self.checker_initiator_tag:
274
172
gobject.source_remove(self.checker_initiator_tag)
275
173
self.checker_initiator_tag = None
276
174
self.stop_checker()
279
177
# Do not run this again if called by a gobject.timeout_add
281
179
def __del__(self):
282
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
284
190
def checker_callback(self, pid, condition):
285
191
"""The checker has completed, so take appropriate actions."""
286
192
now = datetime.datetime.now()
287
self.checker_callback_tag = None
289
193
if os.WIFEXITED(condition) \
290
194
and (os.WEXITSTATUS(condition) == 0):
291
logger.info(u"Checker for %(name)s succeeded",
293
self.last_checked_ok = now
195
logger.debug(u"Checker for %(name)s succeeded",
294
198
gobject.source_remove(self.stop_initiator_tag)
295
199
self.stop_initiator_tag = gobject.timeout_add\
296
200
(self._timeout_milliseconds,
299
203
logger.warning(u"Checker for %(name)s crashed?",
302
logger.info(u"Checker for %(name)s failed",
206
logger.debug(u"Checker for %(name)s failed",
209
self.checker_callback_tag = None
304
210
def start_checker(self):
305
211
"""Start a new checker subprocess if one is not running.
306
212
If a checker already exists, leave it running and do
308
# The reason for not killing a running checker is that if we
309
# did that, then if a checker (for some reason) started
310
# running slowly and taking more than 'interval' time, the
311
# client would inevitably timeout, since no checker would get
312
# a chance to run to completion. If we instead leave running
313
# checkers alone, the checker would have to take more time
314
# than 'timeout' for the client to be declared invalid, which
315
# is as it should be.
316
214
if self.checker is None:
318
# In case check_command has exactly one % operator
319
command = self.check_command % self.host
216
command = self.check_command % self.fqdn
320
217
except TypeError:
321
# Escape attributes for the shell
322
218
escaped_attrs = dict((key, re.escape(str(val)))
324
220
vars(self).iteritems())
326
222
command = self.check_command % escaped_attrs
327
223
except TypeError, error:
328
logger.error(u'Could not format string "%s":'
329
u' %s', self.check_command, error)
224
logger.critical(u'Could not format string "%s":'
225
u' %s', self.check_command, error)
330
226
return True # Try again later
332
logger.info(u"Starting checker %r for %s",
334
self.checker = subprocess.Popen(command,
228
logger.debug(u"Starting checker %r for %s",
230
self.checker = subprocess.\
232
close_fds=True, shell=True,
337
234
self.checker_callback_tag = gobject.child_watch_add\
338
235
(self.checker.pid,
339
236
self.checker_callback)
345
242
def stop_checker(self):
346
243
"""Force the checker process, if any, to stop."""
347
if self.checker_callback_tag:
348
gobject.source_remove(self.checker_callback_tag)
349
self.checker_callback_tag = None
350
if getattr(self, "checker", None) is None:
244
if not hasattr(self, "checker") or self.checker is None:
352
logger.debug(u"Stopping checker for %(name)s", vars(self))
354
os.kill(self.checker.pid, signal.SIGTERM)
356
#if self.checker.poll() is None:
357
# os.kill(self.checker.pid, signal.SIGKILL)
358
except OSError, error:
359
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)
361
251
self.checker = None
362
def still_valid(self):
252
def still_valid(self, now=None):
363
253
"""Has the timeout not yet passed for this client?"""
364
now = datetime.datetime.now()
365
if self.last_checked_ok is None:
255
now = datetime.datetime.now()
256
if self.last_seen is None:
366
257
return now < (self.created + self.timeout)
368
return now < (self.last_checked_ok + self.timeout)
259
return now < (self.last_seen + self.timeout)
371
262
def peer_certificate(session):
372
"Return the peer's OpenPGP certificate as a bytestring"
263
"Return an OpenPGP data packet string for the peer's certificate"
373
264
# If not an OpenPGP certificate...
374
265
if gnutls.library.functions.gnutls_certificate_type_get\
375
266
(session._c_object) \
388
279
def fingerprint(openpgp):
389
"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\
390
285
# New GnuTLS "datum" with the OpenPGP public key
391
286
datum = gnutls.library.types.gnutls_datum_t\
392
287
(ctypes.cast(ctypes.c_char_p(openpgp),
393
288
ctypes.POINTER(ctypes.c_ubyte)),
394
289
ctypes.c_uint(len(openpgp)))
395
# New empty GnuTLS certificate
396
crt = gnutls.library.types.gnutls_openpgp_crt_t()
397
gnutls.library.functions.gnutls_openpgp_crt_init\
399
290
# Import the OpenPGP public key into the certificate
400
gnutls.library.functions.gnutls_openpgp_crt_import\
401
(crt, ctypes.byref(datum),
402
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
291
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
294
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
403
295
# New buffer for the fingerprint
404
296
buffer = ctypes.create_string_buffer(20)
405
297
buffer_length = ctypes.c_size_t()
421
313
Note: This will run in its own forked process."""
423
315
def handle(self):
424
logger.info(u"TCP connection from: %s",
316
logger.debug(u"TCP connection from: %s",
425
317
unicode(self.client_address))
426
session = gnutls.connection.ClientSession\
427
(self.request, gnutls.connection.X509Credentials())
429
line = self.request.makefile().readline()
430
logger.debug(u"Protocol version: %r", line)
432
if int(line.strip().split()[0]) > 1:
434
except (ValueError, IndexError, RuntimeError), error:
435
logger.error(u"Unknown protocol version: %s", error)
438
# Note: gnutls.connection.X509Credentials is really a generic
439
# GnuTLS certificate credentials object so long as no X.509
440
# keys are added to it. Therefore, we can use it here despite
441
# using OpenPGP certificates.
318
session = gnutls.connection.ClientSession(self.request,
443
322
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
444
323
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
446
priority = "NORMAL" # Fallback default, since this
448
if self.server.settings["priority"]:
449
priority = self.server.settings["priority"]
325
priority = "SECURE256"
450
327
gnutls.library.functions.gnutls_priority_set_direct\
451
328
(session._c_object, priority, None);
454
331
session.handshake()
455
332
except gnutls.errors.GNUTLSError, error:
456
logger.warning(u"Handshake failed: %s", error)
333
logger.debug(u"Handshake failed: %s", error)
457
334
# Do not run session.bye() here: the session is not
458
335
# established. Just abandon the request.
461
338
fpr = fingerprint(peer_certificate(session))
462
339
except (TypeError, gnutls.errors.GNUTLSError), error:
463
logger.warning(u"Bad certificate: %s", error)
340
logger.debug(u"Bad certificate: %s", error)
466
343
logger.debug(u"Fingerprint: %s", fpr)
468
for c in self.server.clients:
469
346
if c.fingerprint == fpr:
473
logger.warning(u"Client not found for fingerprint: %s",
477
349
# Have to check if client.still_valid(), since it is possible
478
350
# that the client timed out while establishing the GnuTLS
480
if not client.still_valid():
481
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",
511
387
"""This overrides the normal server_bind() function
512
388
to bind to an interface if one was specified, and also NOT to
513
389
bind to an address or port if they were not specified."""
514
if self.settings["interface"]:
515
# 25 is from /usr/include/asm-i486/socket.h
516
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
518
395
self.socket.setsockopt(socket.SOL_SOCKET,
520
self.settings["interface"])
396
socket.SO_BINDTODEVICE,
397
self.options.interface)
521
398
except socket.error, error:
522
399
if error[0] == errno.EPERM:
523
logger.error(u"No permission to"
524
u" bind to interface %s",
525
self.settings["interface"])
400
logger.warning(u"No permission to"
401
u" bind to interface %s",
402
self.options.interface)
528
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:
581
484
def server_state_changed(state):
582
"""Derived from the Avahi example code"""
485
"""From the Avahi server example code"""
583
486
if state == avahi.SERVER_COLLISION:
584
logger.error(u"Server name collision")
487
logger.warning(u"Server name collision")
586
489
elif state == avahi.SERVER_RUNNING:
590
493
def entry_group_state_changed(state, error):
591
"""Derived from the Avahi example code"""
494
"""From the Avahi server example code"""
495
global serviceName, server, rename_count
592
497
logger.debug(u"state change: %i", state)
594
499
if state == avahi.ENTRY_GROUP_ESTABLISHED:
595
500
logger.debug(u"Service established.")
596
501
elif state == avahi.ENTRY_GROUP_COLLISION:
597
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)
599
515
elif state == avahi.ENTRY_GROUP_FAILURE:
600
logger.critical(u"Error in group state changed %s",
602
raise AvahiGroupError("State changed: %s", str(error))
516
logger.error(u"Error in group state changed %s",
604
521
def if_nametoindex(interface):
605
"""Call the C function if_nametoindex(), or equivalent"""
606
global if_nametoindex
522
"""Call the C function if_nametoindex()"""
608
if "ctypes.util" not in sys.modules:
610
if_nametoindex = ctypes.cdll.LoadLibrary\
611
(ctypes.util.find_library("c")).if_nametoindex
524
libc = ctypes.cdll.LoadLibrary("libc.so.6")
525
return libc.if_nametoindex(interface)
612
526
except (OSError, AttributeError):
613
527
if "struct" not in sys.modules:
615
529
if "fcntl" not in sys.modules:
617
def if_nametoindex(interface):
618
"Get an interface index the hard way, i.e. using fcntl()"
619
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
621
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
622
struct.pack("16s16x", interface))
624
interface_index = struct.unpack("I", ifreq[16:20])[0]
625
return interface_index
626
return if_nametoindex(interface)
629
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):
630
541
"""See daemon(3). Standard BSD Unix function.
631
542
This should really exist as os.daemon, but it doesn't (yet)."""
653
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__':
654
572
main_loop_started = False
656
573
parser = OptionParser()
657
574
parser.add_option("-i", "--interface", type="string",
658
metavar="IF", help="Bind to interface IF")
659
parser.add_option("-a", "--address", type="string",
660
help="Address to listen for requests on")
661
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,
662
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")
663
585
parser.add_option("--check", action="store_true", default=False,
664
586
help="Run self-test")
665
parser.add_option("--debug", action="store_true",
666
help="Debug mode; run in foreground and log to"
668
parser.add_option("--priority", type="string", help="GnuTLS"
669
" priority string (see GnuTLS documentation)")
670
parser.add_option("--servicename", type="string", metavar="NAME",
671
help="Zeroconf service name")
672
parser.add_option("--configdir", type="string",
673
default="/etc/mandos", metavar="DIR",
674
help="Directory to search for configuration"
587
parser.add_option("--debug", action="store_true", default=False,
676
589
(options, args) = parser.parse_args()
678
591
if options.check:
680
593
doctest.testmod()
683
# Default values for config file for server-global settings
684
server_defaults = { "interface": "",
689
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
690
"servicename": "Mandos",
693
# Parse config file for server-global settings
694
server_config = ConfigParser.SafeConfigParser(server_defaults)
696
server_config.read(os.path.join(options.configdir, "mandos.conf"))
697
server_section = "server"
698
# Convert the SafeConfigParser object to a dict
699
server_settings = dict(server_config.items(server_section))
700
# Use getboolean on the boolean config option
701
server_settings["debug"] = server_config.getboolean\
702
(server_section, "debug")
705
# Override the settings from the config file with command line
707
for option in ("interface", "address", "port", "debug",
708
"priority", "servicename", "configdir"):
709
value = getattr(options, option)
710
if value is not None:
711
server_settings[option] = value
713
# Now we have our good server settings in "server_settings"
715
debug = server_settings["debug"]
718
syslogger.setLevel(logging.WARNING)
720
if server_settings["servicename"] != "Mandos":
721
syslogger.setFormatter(logging.Formatter\
722
('Mandos (%s): %%(levelname)s:'
724
% server_settings["servicename"]))
726
# Parse config file with clients
727
client_defaults = { "timeout": "1h",
729
"checker": "fping -q -- %%(host)s",
731
client_config = ConfigParser.SafeConfigParser(client_defaults)
732
client_config.read(os.path.join(server_settings["configdir"],
736
service = AvahiService(name = server_settings["servicename"],
737
type = "_mandos._tcp", );
738
if server_settings["interface"]:
739
service.interface = if_nametoindex(server_settings["interface"])
744
# 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
745
613
DBusGMainLoop(set_as_default=True )
746
614
main_loop = gobject.MainLoop()
747
615
bus = dbus.SystemBus()
762
632
def remove_from_clients(client):
763
633
clients.remove(client)
765
logger.critical(u"No clients left, exiting")
635
logger.debug(u"No clients left, exiting")
768
clients.update(Set(Client(name = section,
638
clients.update(Set(Client(name=section, options=options,
769
639
stop_hook = remove_from_clients,
771
= dict(client_config.items(section)))
640
**(dict(client_config\
772
642
for section in client_config.sections()))
774
logger.critical(u"No clients defined")
780
pidfilename = "/var/run/mandos/mandos.pid"
783
pidfile = open(pidfilename, "w")
784
pidfile.write(str(pid) + "\n")
788
logger.error(u"Could not write %s file with PID %d",
789
pidfilename, os.getpid())
792
648
"Cleanup function; run on exit"
794
# From the Avahi example code
650
# From the Avahi server example code
795
651
if not group is None:
798
654
# End of Avahi example code
801
client = clients.pop()
656
for client in clients:
802
657
client.stop_hook = None
808
663
signal.signal(signal.SIGINT, signal.SIG_IGN)
809
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
810
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())
812
667
for client in clients:
815
tcp_server = IPv6_TCPServer((server_settings["address"],
816
server_settings["port"]),
670
tcp_server = IPv6_TCPServer((None, options.port),
818
settings=server_settings,
820
# Find out what port we got
821
service.port = tcp_server.socket.getsockname()[1]
822
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
823
u" scope_id %d" % tcp_server.socket.getsockname())
825
#service.interface = tcp_server.socket.getsockname()[3]
828
# From the Avahi example code
829
server.connect_to_signal("StateChanged", server_state_changed)
831
server_state_changed(server.GetState())
832
except dbus.exceptions.DBusException, error:
833
logger.critical(u"DBusException: %s", error)
835
# End of Avahi example code
837
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
838
lambda *args, **kwargs:
839
tcp_server.handle_request\
840
(*args[2:], **kwargs) or True)
842
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:],
843
695
main_loop_started = True
845
except AvahiError, error:
846
logger.critical(u"AvahiError: %s" + unicode(error))
848
697
except KeyboardInterrupt:
852
if __name__ == '__main__':