51
19
from sets import Set
56
import logging.handlers
62
25
from dbus.mainloop.glib import DBusGMainLoop
68
logger = logging.Logger('mandos')
69
syslogger = logging.handlers.SysLogHandler\
70
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
72
syslogger.setFormatter(logging.Formatter\
73
('Mandos: %(levelname)s: %(message)s'))
74
logger.addHandler(syslogger)
76
console = logging.StreamHandler()
77
console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
79
logger.addHandler(console)
81
class AvahiError(Exception):
82
def __init__(self, value):
84
super(AvahiError, self).__init__()
86
return repr(self.value)
88
class AvahiServiceError(AvahiError):
91
class AvahiGroupError(AvahiError):
95
class AvahiService(object):
96
"""An Avahi (Zeroconf) service.
98
interface: integer; avahi.IF_UNSPEC or an interface index.
99
Used to optionally bind to the specified interface.
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
111
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
112
servicetype = None, port = None, TXT = None, domain = "",
113
host = "", max_renames = 32768):
114
self.interface = interface
116
self.type = servicetype
124
self.rename_count = 0
125
self.max_renames = max_renames
127
"""Derived from the Avahi example code"""
128
if self.rename_count >= self.max_renames:
129
logger.critical(u"No suitable Zeroconf service name found"
130
u" after %i retries, exiting.",
132
raise AvahiServiceError("Too many renames")
133
self.name = server.GetAlternativeServiceName(self.name)
134
logger.info(u"Changing Zeroconf service name to %r ...",
136
syslogger.setFormatter(logging.Formatter\
137
('Mandos (%s): %%(levelname)s:'
138
' %%(message)s' % self.name))
141
self.rename_count += 1
143
"""Derived from the Avahi example code"""
144
if group is not None:
147
"""Derived from the Avahi example code"""
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)
156
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
157
service.name, service.type)
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))
168
# From the Avahi example code:
169
group = None # our entry group
27
# This variable is used to optionally bind to a specified
29
serviceInterface = avahi.IF_UNSPEC
30
# It is a global variable to fit in with the rest of the
31
# variables from the Avahi server example code:
32
serviceName = "Mandos"
33
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
34
servicePort = None # Not known at startup
35
serviceTXT = [] # TXT record for the service
36
domain = "" # Domain to publish on, default to .local
37
host = "" # Host to publish records for, default to localhost
38
group = None #our entry group
39
rename_count = 12 # Counter so we only rename after collisions a
40
# sensible number of times
170
41
# End of Avahi example code
173
44
class Client(object):
174
45
"""A representation of a client host served by this server.
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
180
host: string; available for use by the checker command
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
48
fqdn: string, FQDN (used by the checker)
49
created: datetime.datetime()
50
last_seen: datetime.datetime() or None if not yet seen
51
timeout: datetime.timedelta(); How long from last_seen until
52
this client is invalid
185
53
interval: datetime.timedelta(); How often to start a new checker
54
timeout_milliseconds: Used by gobject.timeout_add()
55
interval_milliseconds: - '' -
186
56
stop_hook: If set, called by stop() as stop_hook(self)
187
57
checker: subprocess.Popen(); a running checker process used
188
58
to see if the client lives.
189
'None' if no process is running.
59
Is None if no process is running.
190
60
checker_initiator_tag: a gobject event source tag, or None
191
61
stop_initiator_tag: - '' -
192
62
checker_callback_tag: - '' -
193
checker_command: string; External command which is run to check if
194
client lives. %() expansions are done at
195
runtime with vars(self) as dict, so that for
196
instance %(name)s can be used in the command.
198
_timeout: Real variable for 'timeout'
199
_interval: Real variable for 'interval'
200
_timeout_milliseconds: Used when calling gobject.timeout_add()
201
_interval_milliseconds: - '' -
203
def _set_timeout(self, timeout):
204
"Setter function for 'timeout' attribute"
205
self._timeout = timeout
206
self._timeout_milliseconds = ((self.timeout.days
64
def __init__(self, name=None, options=None, stop_hook=None,
65
dn=None, password=None, passfile=None, fqdn=None,
66
timeout=None, interval=-1):
70
self.password = password
72
self.password = open(passfile).readall()
74
raise RuntimeError(u"No Password or Passfile for client %s"
76
self.fqdn = fqdn # string
77
self.created = datetime.datetime.now()
80
timeout = options.timeout
81
self.timeout = timeout
82
self.timeout_milliseconds = ((self.timeout.days
83
* 24 * 60 * 60 * 1000)
84
+ (self.timeout.seconds * 1000)
85
+ (self.timeout.microseconds
88
interval = options.interval
90
interval = string_to_delta(interval)
91
self.interval = interval
92
self.interval_milliseconds = ((self.interval.days
207
93
* 24 * 60 * 60 * 1000)
208
+ (self.timeout.seconds * 1000)
209
+ (self.timeout.microseconds
94
+ (self.interval.seconds * 1000)
95
+ (self.interval.microseconds
211
timeout = property(lambda self: self._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
221
+ (self.interval.microseconds
223
interval = property(lambda self: self._interval,
226
def __init__(self, name = None, stop_hook=None, config=None):
227
"""Note: the 'checker' key in 'config' sets the
228
'checker_command' attribute and *not* the 'checker'
233
logger.debug(u"Creating client %r", self.name)
234
# Uppercase and remove spaces from fingerprint for later
235
# comparison purposes with return value from the fingerprint()
237
self.fingerprint = config["fingerprint"].upper()\
239
logger.debug(u" Fingerprint: %s", self.fingerprint)
240
if "secret" in config:
241
self.secret = config["secret"].decode(u"base64")
242
elif "secfile" in config:
243
secfile = open(config["secfile"])
244
self.secret = secfile.read()
247
raise TypeError(u"No secret or secfile for client %s"
249
self.host = config.get("host", "")
250
self.created = datetime.datetime.now()
251
self.last_checked_ok = None
252
self.timeout = string_to_delta(config["timeout"])
253
self.interval = string_to_delta(config["interval"])
254
97
self.stop_hook = stop_hook
255
98
self.checker = None
256
99
self.checker_initiator_tag = None
257
100
self.stop_initiator_tag = None
258
101
self.checker_callback_tag = None
259
self.check_command = config["checker"]
261
"""Start this client's checker and timeout hooks"""
103
"""Start this clients checker and timeout hooks"""
262
104
# Schedule a new checker to be started an 'interval' from now,
263
105
# and every interval from then on.
264
self.checker_initiator_tag = gobject.timeout_add\
265
(self._interval_milliseconds,
106
self.checker_initiator_tag = gobject.\
107
timeout_add(self.interval_milliseconds,
267
109
# Also start a new checker *right now*.
268
110
self.start_checker()
269
111
# Schedule a stop() when 'timeout' has passed
270
self.stop_initiator_tag = gobject.timeout_add\
271
(self._timeout_milliseconds,
112
self.stop_initiator_tag = gobject.\
113
timeout_add(self.timeout_milliseconds,
274
116
"""Stop this client.
275
The possibility that a client might be restarted is left open,
276
but not currently used."""
277
# If this client doesn't have a secret, it is already stopped.
278
if hasattr(self, "secret") and self.secret:
279
logger.info(u"Stopping client %s", self.name)
283
if getattr(self, "stop_initiator_tag", False):
117
The possibility that this client might be restarted is left
118
open, but not currently used."""
119
# print "Stopping client", self.name
121
if self.stop_initiator_tag:
284
122
gobject.source_remove(self.stop_initiator_tag)
285
123
self.stop_initiator_tag = None
286
if getattr(self, "checker_initiator_tag", False):
124
if self.checker_initiator_tag:
287
125
gobject.source_remove(self.checker_initiator_tag)
288
126
self.checker_initiator_tag = None
289
127
self.stop_checker()
292
130
# Do not run this again if called by a gobject.timeout_add
294
132
def __del__(self):
295
self.stop_hook = None
133
# Some code duplication here and in stop()
134
if hasattr(self, "stop_initiator_tag") \
135
and self.stop_initiator_tag:
136
gobject.source_remove(self.stop_initiator_tag)
137
self.stop_initiator_tag = None
138
if hasattr(self, "checker_initiator_tag") \
139
and self.checker_initiator_tag:
140
gobject.source_remove(self.checker_initiator_tag)
141
self.checker_initiator_tag = None
297
143
def checker_callback(self, pid, condition):
298
144
"""The checker has completed, so take appropriate actions."""
299
145
now = datetime.datetime.now()
300
self.checker_callback_tag = None
302
146
if os.WIFEXITED(condition) \
303
147
and (os.WEXITSTATUS(condition) == 0):
304
logger.info(u"Checker for %(name)s succeeded",
306
self.last_checked_ok = now
148
#print "Checker for %(name)s succeeded" % vars(self)
307
150
gobject.source_remove(self.stop_initiator_tag)
308
self.stop_initiator_tag = gobject.timeout_add\
309
(self._timeout_milliseconds,
311
elif not os.WIFEXITED(condition):
312
logger.warning(u"Checker for %(name)s crashed?",
315
logger.info(u"Checker for %(name)s failed",
151
self.stop_initiator_tag = gobject.\
152
timeout_add(self.timeout_milliseconds,
155
# if not os.WIFEXITED(condition):
156
# print "Checker for %(name)s crashed?" % vars(self)
158
# print "Checker for %(name)s failed" % vars(self)
160
self.checker_callback_tag = None
317
161
def start_checker(self):
318
162
"""Start a new checker subprocess if one is not running.
319
163
If a checker already exists, leave it running and do
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.
329
165
if self.checker is None:
331
# In case check_command has exactly one % operator
332
command = self.check_command % self.host
334
# Escape attributes for the shell
335
escaped_attrs = dict((key, re.escape(str(val)))
337
vars(self).iteritems())
339
command = self.check_command % escaped_attrs
340
except TypeError, error:
341
logger.error(u'Could not format string "%s":'
342
u' %s', self.check_command, error)
343
return True # Try again later
345
logger.info(u"Starting checker %r for %s",
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.)
351
self.checker = subprocess.Popen(command,
354
self.checker_callback_tag = gobject.child_watch_add\
356
self.checker_callback)
357
except OSError, error:
358
logger.error(u"Failed to start subprocess: %s",
166
#print "Starting checker for", self.name
168
self.checker = subprocess.\
169
Popen("sleep 1; fping -q -- %s"
170
% re.escape(self.fqdn),
171
stdout=subprocess.PIPE,
172
close_fds=True, shell=True,
174
self.checker_callback_tag = gobject.\
175
child_watch_add(self.checker.pid,
178
except subprocess.OSError, error:
179
sys.stderr.write(u"Failed to start subprocess: %s\n"
360
181
# Re-run this periodically if run by gobject.timeout_add
362
183
def stop_checker(self):
363
184
"""Force the checker process, if any, to stop."""
364
if self.checker_callback_tag:
365
gobject.source_remove(self.checker_callback_tag)
366
self.checker_callback_tag = None
367
if getattr(self, "checker", None) is None:
185
if not hasattr(self, "checker") or self.checker is None:
369
logger.debug(u"Stopping checker for %(name)s", vars(self))
371
os.kill(self.checker.pid, signal.SIGTERM)
373
#if self.checker.poll() is None:
374
# os.kill(self.checker.pid, signal.SIGKILL)
375
except OSError, error:
376
if error.errno != errno.ESRCH: # No such process
187
gobject.source_remove(self.checker_callback_tag)
188
self.checker_callback_tag = None
189
os.kill(self.checker.pid, signal.SIGTERM)
190
if self.checker.poll() is None:
191
os.kill(self.checker.pid, signal.SIGKILL)
378
192
self.checker = None
379
def still_valid(self):
193
def still_valid(self, now=None):
380
194
"""Has the timeout not yet passed for this client?"""
381
now = datetime.datetime.now()
382
if self.last_checked_ok is None:
196
now = datetime.datetime.now()
197
if self.last_seen is None:
383
198
return now < (self.created + self.timeout)
385
return now < (self.last_checked_ok + self.timeout)
388
def peer_certificate(session):
389
"Return the peer's OpenPGP certificate as a bytestring"
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:
402
return ctypes.string_at(cert.data, cert.size)
405
def fingerprint(openpgp):
406
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
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)))
412
# New empty GnuTLS certificate
413
crt = gnutls.library.types.gnutls_openpgp_crt_t()
414
gnutls.library.functions.gnutls_openpgp_crt_init\
416
# Import the OpenPGP public key into the certificate
417
gnutls.library.functions.gnutls_openpgp_crt_import\
418
(crt, ctypes.byref(datum),
419
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
420
# Verify the self signature in the key
421
crtverify = ctypes.c_uint()
422
gnutls.library.functions.gnutls_openpgp_crt_verify_self\
423
(crt, 0, ctypes.byref(crtverify))
424
if crtverify.value != 0:
425
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
426
raise gnutls.errors.CertificateSecurityError("Verify failed")
427
# New buffer for the fingerprint
428
buf = ctypes.create_string_buffer(20)
429
buf_len = ctypes.c_size_t()
430
# Get the fingerprint from the certificate into the buffer
431
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
432
(crt, ctypes.byref(buf), ctypes.byref(buf_len))
433
# Deinit the certificate
434
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
435
# Convert the buffer to a Python bytestring
436
fpr = ctypes.string_at(buf, buf_len.value)
437
# Convert the bytestring to hexadecimal notation
438
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
442
class TCP_handler(SocketServer.BaseRequestHandler, object):
200
return now < (self.last_seen + self.timeout)
203
class tcp_handler(SocketServer.BaseRequestHandler, object):
443
204
"""A TCP request handler class.
444
205
Instantiated by IPv6_TCPServer for each request to handle it.
445
206
Note: This will run in its own forked process."""
447
207
def handle(self):
448
logger.info(u"TCP connection from: %s",
449
unicode(self.client_address))
450
session = gnutls.connection.ClientSession\
451
(self.request, gnutls.connection.X509Credentials())
453
line = self.request.makefile().readline()
454
logger.debug(u"Protocol version: %r", line)
456
if int(line.strip().split()[0]) > 1:
458
except (ValueError, IndexError, RuntimeError), error:
459
logger.error(u"Unknown protocol version: %s", error)
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.
467
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
468
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
470
priority = "NORMAL" # Fallback default, since this
472
if self.server.settings["priority"]:
473
priority = self.server.settings["priority"]
474
gnutls.library.functions.gnutls_priority_set_direct\
475
(session._c_object, priority, None)
208
#print u"TCP request came"
209
#print u"Request:", self.request
210
#print u"Client Address:", self.client_address
211
#print u"Server:", self.server
212
session = gnutls.connection.ServerSession(self.request,
478
216
session.handshake()
479
217
except gnutls.errors.GNUTLSError, error:
480
logger.warning(u"Handshake failed: %s", error)
218
#sys.stderr.write(u"Handshake failed: %s\n" % error)
481
219
# Do not run session.bye() here: the session is not
482
220
# established. Just abandon the request.
222
#if session.peer_certificate:
223
# print "DN:", session.peer_certificate.subject
485
fpr = fingerprint(peer_certificate(session))
486
except (TypeError, gnutls.errors.GNUTLSError), error:
487
logger.warning(u"Bad certificate: %s", error)
225
session.verify_peer()
226
except gnutls.errors.CertificateError, error:
227
#sys.stderr.write(u"Verify failed: %s\n" % error)
490
logger.debug(u"Fingerprint: %s", fpr)
492
for c in self.server.clients:
493
if c.fingerprint == fpr:
232
if c.dn == session.peer_certificate.subject:
497
logger.warning(u"Client not found for fingerprint: %s",
501
235
# Have to check if client.still_valid(), since it is possible
502
236
# that the client timed out while establishing the GnuTLS
504
if not client.still_valid():
505
logger.warning(u"Client %(name)s is invalid",
510
while sent_size < len(client.secret):
511
sent = session.send(client.secret[sent_size:])
512
logger.debug(u"Sent: %d, remaining: %d",
513
sent, len(client.secret)
514
- (sent_size + sent))
238
if client and client.still_valid():
239
session.send(client.password)
242
# sys.stderr.write(u"Client %(name)s is invalid\n"
245
# sys.stderr.write(u"Client not found for DN: %s\n"
246
# % session.peer_certificate.subject)
247
#session.send("gazonk")
519
252
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
520
253
"""IPv6 TCP server. Accepts 'None' as address and/or port.
522
settings: Server settings
255
options: Command line options
523
256
clients: Set() of Client objects
524
enabled: Boolean; whether this server is activated yet
257
credentials: GnuTLS X.509 credentials
526
259
address_family = socket.AF_INET6
527
260
def __init__(self, *args, **kwargs):
528
if "settings" in kwargs:
529
self.settings = kwargs["settings"]
530
del kwargs["settings"]
261
if "options" in kwargs:
262
self.options = kwargs["options"]
263
del kwargs["options"]
531
264
if "clients" in kwargs:
532
265
self.clients = kwargs["clients"]
533
266
del kwargs["clients"]
535
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
267
if "credentials" in kwargs:
268
self.credentials = kwargs["credentials"]
269
del kwargs["credentials"]
270
return super(type(self), self).__init__(*args, **kwargs)
536
271
def server_bind(self):
537
272
"""This overrides the normal server_bind() function
538
273
to bind to an interface if one was specified, and also NOT to
539
274
bind to an address or port if they were not specified."""
540
if self.settings["interface"]:
541
# 25 is from /usr/include/asm-i486/socket.h
542
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
275
if self.options.interface:
276
if not hasattr(socket, "SO_BINDTODEVICE"):
277
# From /usr/include/asm-i486/socket.h
278
socket.SO_BINDTODEVICE = 25
544
280
self.socket.setsockopt(socket.SOL_SOCKET,
546
self.settings["interface"])
281
socket.SO_BINDTODEVICE,
282
self.options.interface)
547
283
except socket.error, error:
548
284
if error[0] == errno.EPERM:
549
logger.error(u"No permission to"
550
u" bind to interface %s",
551
self.settings["interface"])
285
sys.stderr.write(u"Warning: No permission to bind to interface %s\n"
286
% self.options.interface)
554
289
# Only bind(2) the socket if we really need to.
588
311
datetime.timedelta(1)
589
312
>>> string_to_delta(u'1w')
590
313
datetime.timedelta(7)
591
>>> string_to_delta('5m 30s')
592
datetime.timedelta(0, 330)
594
timevalue = datetime.timedelta(0)
595
for s in interval.split():
597
suffix = unicode(s[-1])
600
delta = datetime.timedelta(value)
602
delta = datetime.timedelta(0, value)
604
delta = datetime.timedelta(0, 0, 0, 0, value)
606
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
608
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
611
except (ValueError, IndexError):
316
suffix=unicode(interval[-1])
317
value=int(interval[:-1])
319
delta = datetime.timedelta(value)
321
delta = datetime.timedelta(0, value)
323
delta = datetime.timedelta(0, 0, 0, 0, value)
325
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
327
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
330
except (ValueError, IndexError):
336
"""From the Avahi server example code"""
337
global group, serviceName, serviceType, servicePort, serviceTXT, \
340
group = dbus.Interface(
341
bus.get_object( avahi.DBUS_NAME,
342
server.EntryGroupNew()),
343
avahi.DBUS_INTERFACE_ENTRY_GROUP)
344
group.connect_to_signal('StateChanged',
345
entry_group_state_changed)
347
# print "Adding service '%s' of type '%s' ..." % (serviceName,
351
serviceInterface, # interface
352
avahi.PROTO_INET6, # protocol
353
dbus.UInt32(0), # flags
354
serviceName, serviceType,
356
dbus.UInt16(servicePort),
357
avahi.string_array_to_txt_array(serviceTXT))
361
def remove_service():
362
"""From the Avahi server example code"""
365
if not group is None:
617
369
def server_state_changed(state):
618
"""Derived from the Avahi example code"""
370
"""From the Avahi server example code"""
619
371
if state == avahi.SERVER_COLLISION:
620
logger.error(u"Zeroconf server name collision")
372
print "WARNING: Server name collision"
622
374
elif state == avahi.SERVER_RUNNING:
626
378
def entry_group_state_changed(state, error):
627
"""Derived from the Avahi example code"""
628
logger.debug(u"Avahi state change: %i", state)
379
"""From the Avahi server example code"""
380
global serviceName, server, rename_count
382
# print "state change: %i" % state
630
384
if state == avahi.ENTRY_GROUP_ESTABLISHED:
631
logger.debug(u"Zeroconf service established.")
386
# print "Service established."
632
387
elif state == avahi.ENTRY_GROUP_COLLISION:
633
logger.warning(u"Zeroconf service name collision.")
389
rename_count = rename_count - 1
391
name = server.GetAlternativeServiceName(name)
392
print "WARNING: Service name collision, changing name to '%s' ..." % name
397
print "ERROR: No suitable service name found after %i retries, exiting." % n_rename
635
399
elif state == avahi.ENTRY_GROUP_FAILURE:
636
logger.critical(u"Avahi: Error in group state changed %s",
638
raise AvahiGroupError("State changed: %s", str(error))
400
print "Error in group state changed", error
640
405
def if_nametoindex(interface):
641
"""Call the C function if_nametoindex(), or equivalent"""
642
global if_nametoindex
406
"""Call the C function if_nametoindex()"""
644
if_nametoindex = ctypes.cdll.LoadLibrary\
645
(ctypes.util.find_library("c")).if_nametoindex
646
except (OSError, AttributeError):
408
if "ctypes" not in sys.modules:
410
libc = ctypes.cdll.LoadLibrary("libc.so.6")
411
return libc.if_nametoindex(interface)
412
except (ImportError, OSError, AttributeError):
647
413
if "struct" not in sys.modules:
649
415
if "fcntl" not in sys.modules:
651
def if_nametoindex(interface):
652
"Get an interface index the hard way, i.e. using fcntl()"
653
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
655
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
656
struct.pack("16s16x", interface))
658
interface_index = struct.unpack("I", ifreq[16:20])[0]
659
return interface_index
660
return if_nametoindex(interface)
663
def daemon(nochdir = False, noclose = False):
664
"""See daemon(3). Standard BSD Unix function.
665
This should really exist as os.daemon, but it doesn't (yet)."""
674
# Close all standard open file descriptors
675
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
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())
687
parser = OptionParser(version = "%%prog %s" % version)
417
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
419
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
420
struct.pack("16s16x", interface))
422
interface_index = struct.unpack("I", ifreq[16:20])[0]
423
return interface_index
426
if __name__ == '__main__':
427
parser = OptionParser()
688
428
parser.add_option("-i", "--interface", type="string",
689
metavar="IF", help="Bind to interface IF")
690
parser.add_option("-a", "--address", type="string",
691
help="Address to listen for requests on")
692
parser.add_option("-p", "--port", type="int",
429
default=None, metavar="IF",
430
help="Bind to interface IF")
431
parser.add_option("--cert", type="string", default="cert.pem",
433
help="Public key certificate PEM file to use")
434
parser.add_option("--key", type="string", default="key.pem",
436
help="Private key PEM file to use")
437
parser.add_option("--ca", type="string", default="ca.pem",
439
help="Certificate Authority certificate PEM file to use")
440
parser.add_option("--crl", type="string", default="crl.pem",
442
help="Certificate Revokation List PEM file to use")
443
parser.add_option("-p", "--port", type="int", default=None,
693
444
help="Port number to receive requests on")
445
parser.add_option("--timeout", type="string", # Parsed later
447
help="Amount of downtime allowed for clients")
448
parser.add_option("--interval", type="string", # Parsed later
450
help="How often to check that a client is up")
694
451
parser.add_option("--check", action="store_true", default=False,
695
452
help="Run self-test")
696
parser.add_option("--debug", action="store_true",
697
help="Debug mode; run in foreground and log to"
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"
707
options = parser.parse_args()[0]
453
(options, args) = parser.parse_args()
709
455
if options.check:
711
457
doctest.testmod()
714
# Default values for config file for server-global settings
715
server_defaults = { "interface": "",
720
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
721
"servicename": "Mandos",
724
# Parse config file for server-global settings
725
server_config = ConfigParser.SafeConfigParser(server_defaults)
727
server_config.read(os.path.join(options.configdir, "mandos.conf"))
728
# Convert the SafeConfigParser object to a dict
729
server_settings = server_config.defaults()
730
# Use getboolean on the boolean config option
731
server_settings["debug"] = server_config.getboolean\
735
# Override the settings from the config file with command line
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
743
# Now we have our good server settings in "server_settings"
745
debug = server_settings["debug"]
748
syslogger.setLevel(logging.WARNING)
749
console.setLevel(logging.WARNING)
751
if server_settings["servicename"] != "Mandos":
752
syslogger.setFormatter(logging.Formatter\
753
('Mandos (%s): %%(levelname)s:'
755
% server_settings["servicename"]))
757
# Parse config file with clients
758
client_defaults = { "timeout": "1h",
760
"checker": "fping -q -- %(host)s",
763
client_config = ConfigParser.SafeConfigParser(client_defaults)
764
client_config.read(os.path.join(server_settings["configdir"],
768
tcp_server = IPv6_TCPServer((server_settings["address"],
769
server_settings["port"]),
771
settings=server_settings,
773
pidfilename = "/var/run/mandos.pid"
775
pidfile = open(pidfilename, "w")
776
except IOError, error:
777
logger.error("Could not open file %r", pidfilename)
782
uid = pwd.getpwnam("mandos").pw_uid
785
uid = pwd.getpwnam("nobody").pw_uid
789
gid = pwd.getpwnam("mandos").pw_gid
792
gid = pwd.getpwnam("nogroup").pw_gid
798
except OSError, error:
799
if error[0] != errno.EPERM:
803
service = AvahiService(name = server_settings["servicename"],
804
servicetype = "_mandos._tcp", )
805
if server_settings["interface"]:
806
service.interface = if_nametoindex\
807
(server_settings["interface"])
812
# From the Avahi example code
460
# Parse the time arguments
462
options.timeout = string_to_delta(options.timeout)
464
parser.error("option --timeout: Unparseable time")
466
options.interval = string_to_delta(options.interval)
468
parser.error("option --interval: Unparseable time")
470
cert = gnutls.crypto.X509Certificate(open(options.cert).read())
471
key = gnutls.crypto.X509PrivateKey(open(options.key).read())
472
ca = gnutls.crypto.X509Certificate(open(options.ca).read())
473
crl = gnutls.crypto.X509CRL(open(options.crl).read())
474
cred = gnutls.connection.X509Credentials(cert, key, [ca], [crl])
478
client_config = ConfigParser.SafeConfigParser(defaults)
479
#client_config.readfp(open("secrets.conf"), "secrets.conf")
480
client_config.read("mandos-clients.conf")
482
# From the Avahi server example code
813
483
DBusGMainLoop(set_as_default=True )
814
484
main_loop = gobject.MainLoop()
815
485
bus = dbus.SystemBus()
816
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
817
avahi.DBUS_PATH_SERVER),
818
avahi.DBUS_INTERFACE_SERVER)
486
server = dbus.Interface(
487
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
488
avahi.DBUS_INTERFACE_SERVER )
819
489
# End of Avahi example code
821
492
def remove_from_clients(client):
822
493
clients.remove(client)
824
logger.critical(u"No clients left, exiting")
495
print "No clients left, exiting"
827
clients.update(Set(Client(name = section,
498
clients.update(Set(Client(name=section, options=options,
828
499
stop_hook = remove_from_clients,
830
= dict(client_config.items(section)))
500
**(dict(client_config\
831
502
for section in client_config.sections()))
833
logger.critical(u"No clients defined")
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())
844
logger.removeHandler(console)
845
# Close all input and output, do double fork, etc.
850
pidfile.write(str(pid) + "\n")
854
logger.error(u"Could not write to file %r with PID %d",
857
# "pidfile" was never created
862
"Cleanup function; run on exit"
864
# From the Avahi example code
865
if not group is None:
868
# End of Avahi example code
871
client = clients.pop()
872
client.stop_hook = None
875
atexit.register(cleanup)
878
signal.signal(signal.SIGINT, signal.SIG_IGN)
879
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
880
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
882
503
for client in clients:
886
tcp_server.server_activate()
888
# Find out what port we got
889
service.port = tcp_server.socket.getsockname()[1]
890
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
891
u" scope_id %d" % tcp_server.socket.getsockname())
893
#service.interface = tcp_server.socket.getsockname()[3]
506
tcp_server = IPv6_TCPServer((None, options.port),
511
# Find out what random port we got
512
servicePort = tcp_server.socket.getsockname()[1]
513
#sys.stderr.write("Now listening on port %d\n" % servicePort)
515
if options.interface is not None:
516
serviceInterface = if_nametoindex(options.interface)
518
# From the Avahi server example code
519
server.connect_to_signal("StateChanged", server_state_changed)
520
server_state_changed(server.GetState())
521
# End of Avahi example code
523
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
524
lambda *args, **kwargs:
525
tcp_server.handle_request(*args[2:],
896
# From the Avahi example code
897
server.connect_to_signal("StateChanged", server_state_changed)
899
server_state_changed(server.GetState())
900
except dbus.exceptions.DBusException, error:
901
logger.critical(u"DBusException: %s", error)
903
# End of Avahi example code
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)
910
logger.debug(u"Starting main loop")
912
except AvahiError, error:
913
logger.critical(u"AvahiError: %s" + unicode(error))
915
529
except KeyboardInterrupt:
919
if __name__ == '__main__':
534
# From the Avahi server example code
535
if not group is None:
537
# End of Avahi example code
539
for client in clients:
540
client.stop_hook = None