52
19
from sets import Set
57
import logging.handlers
63
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):
85
return repr(self.value)
87
class AvahiServiceError(AvahiError):
90
class AvahiGroupError(AvahiError):
94
class AvahiService(object):
95
"""An Avahi (Zeroconf) service.
97
interface: integer; avahi.IF_UNSPEC or an interface index.
98
Used to optionally bind to the specified interface.
99
name: string; Example: 'Mandos'
100
type: string; Example: '_mandos._tcp'.
101
See <http://www.dns-sd.org/ServiceTypes.html>
102
port: integer; what port to announce
103
TXT: list of strings; TXT record for the service
104
domain: string; Domain to publish on, default to .local if empty.
105
host: string; Host to publish records for, default is localhost
106
max_renames: integer; maximum number of renames
107
rename_count: integer; counter so we only rename after collisions
108
a sensible number of times
110
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
111
type = None, port = None, TXT = None, domain = "",
112
host = "", max_renames = 32768):
113
self.interface = interface
123
self.rename_count = 0
124
self.max_renames = max_renames
126
"""Derived from the Avahi example code"""
127
if self.rename_count >= self.max_renames:
128
logger.critical(u"No suitable Zeroconf service name found"
129
u" after %i retries, exiting.",
131
raise AvahiServiceError("Too many renames")
132
self.name = server.GetAlternativeServiceName(self.name)
133
logger.info(u"Changing Zeroconf service name to %r ...",
135
syslogger.setFormatter(logging.Formatter\
136
('Mandos (%s): %%(levelname)s:'
137
' %%(message)s' % self.name))
140
self.rename_count += 1
142
"""Derived from the Avahi example code"""
143
if group is not None:
146
"""Derived from the Avahi example code"""
149
group = dbus.Interface\
150
(bus.get_object(avahi.DBUS_NAME,
151
server.EntryGroupNew()),
152
avahi.DBUS_INTERFACE_ENTRY_GROUP)
153
group.connect_to_signal('StateChanged',
154
entry_group_state_changed)
155
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
156
service.name, service.type)
158
self.interface, # interface
159
avahi.PROTO_INET6, # protocol
160
dbus.UInt32(0), # flags
161
self.name, self.type,
162
self.domain, self.host,
163
dbus.UInt16(self.port),
164
avahi.string_array_to_txt_array(self.TXT))
167
# From the Avahi example code:
168
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
169
41
# End of Avahi example code
172
44
class Client(object):
173
45
"""A representation of a client host served by this server.
175
name: string; from the config file, used in log messages
176
fingerprint: string (40 or 32 hexadecimal digits); used to
177
uniquely identify the client
178
secret: bytestring; sent verbatim (over TLS) to client
179
host: string; available for use by the checker command
180
created: datetime.datetime(); object creation, not client host
181
last_checked_ok: datetime.datetime() or None if not yet checked OK
182
timeout: datetime.timedelta(); How long from last_checked_ok
183
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
184
53
interval: datetime.timedelta(); How often to start a new checker
54
timeout_milliseconds: Used by gobject.timeout_add()
55
interval_milliseconds: - '' -
185
56
stop_hook: If set, called by stop() as stop_hook(self)
186
57
checker: subprocess.Popen(); a running checker process used
187
58
to see if the client lives.
188
'None' if no process is running.
59
Is None if no process is running.
189
60
checker_initiator_tag: a gobject event source tag, or None
190
61
stop_initiator_tag: - '' -
191
62
checker_callback_tag: - '' -
192
checker_command: string; External command which is run to check if
193
client lives. %() expansions are done at
194
runtime with vars(self) as dict, so that for
195
instance %(name)s can be used in the command.
197
_timeout: Real variable for 'timeout'
198
_interval: Real variable for 'interval'
199
_timeout_milliseconds: Used when calling gobject.timeout_add()
200
_interval_milliseconds: - '' -
202
def _set_timeout(self, timeout):
203
"Setter function for 'timeout' attribute"
204
self._timeout = timeout
205
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
206
93
* 24 * 60 * 60 * 1000)
207
+ (self.timeout.seconds * 1000)
208
+ (self.timeout.microseconds
94
+ (self.interval.seconds * 1000)
95
+ (self.interval.microseconds
210
timeout = property(lambda self: self._timeout,
213
def _set_interval(self, interval):
214
"Setter function for 'interval' attribute"
215
self._interval = interval
216
self._interval_milliseconds = ((self.interval.days
217
* 24 * 60 * 60 * 1000)
218
+ (self.interval.seconds
220
+ (self.interval.microseconds
222
interval = property(lambda self: self._interval,
225
def __init__(self, name = None, stop_hook=None, config={}):
226
"""Note: the 'checker' key in 'config' sets the
227
'checker_command' attribute and *not* the 'checker'
230
logger.debug(u"Creating client %r", self.name)
231
# Uppercase and remove spaces from fingerprint for later
232
# comparison purposes with return value from the fingerprint()
234
self.fingerprint = config["fingerprint"].upper()\
236
logger.debug(u" Fingerprint: %s", self.fingerprint)
237
if "secret" in config:
238
self.secret = config["secret"].decode(u"base64")
239
elif "secfile" in config:
240
sf = open(config["secfile"])
241
self.secret = sf.read()
244
raise TypeError(u"No secret or secfile for client %s"
246
self.host = config.get("host", "")
247
self.created = datetime.datetime.now()
248
self.last_checked_ok = None
249
self.timeout = string_to_delta(config["timeout"])
250
self.interval = string_to_delta(config["interval"])
251
97
self.stop_hook = stop_hook
252
98
self.checker = None
253
99
self.checker_initiator_tag = None
254
100
self.stop_initiator_tag = None
255
101
self.checker_callback_tag = None
256
self.check_command = config["checker"]
258
"""Start this client's checker and timeout hooks"""
103
"""Start this clients checker and timeout hooks"""
259
104
# Schedule a new checker to be started an 'interval' from now,
260
105
# and every interval from then on.
261
self.checker_initiator_tag = gobject.timeout_add\
262
(self._interval_milliseconds,
106
self.checker_initiator_tag = gobject.\
107
timeout_add(self.interval_milliseconds,
264
109
# Also start a new checker *right now*.
265
110
self.start_checker()
266
111
# Schedule a stop() when 'timeout' has passed
267
self.stop_initiator_tag = gobject.timeout_add\
268
(self._timeout_milliseconds,
112
self.stop_initiator_tag = gobject.\
113
timeout_add(self.timeout_milliseconds,
271
116
"""Stop this client.
272
The possibility that a client might be restarted is left open,
273
but not currently used."""
274
# If this client doesn't have a secret, it is already stopped.
275
if hasattr(self, "secret") and self.secret:
276
logger.info(u"Stopping client %s", self.name)
280
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:
281
122
gobject.source_remove(self.stop_initiator_tag)
282
123
self.stop_initiator_tag = None
283
if getattr(self, "checker_initiator_tag", False):
124
if self.checker_initiator_tag:
284
125
gobject.source_remove(self.checker_initiator_tag)
285
126
self.checker_initiator_tag = None
286
127
self.stop_checker()
289
130
# Do not run this again if called by a gobject.timeout_add
291
132
def __del__(self):
292
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
294
143
def checker_callback(self, pid, condition):
295
144
"""The checker has completed, so take appropriate actions."""
296
145
now = datetime.datetime.now()
297
self.checker_callback_tag = None
299
146
if os.WIFEXITED(condition) \
300
147
and (os.WEXITSTATUS(condition) == 0):
301
logger.info(u"Checker for %(name)s succeeded",
303
self.last_checked_ok = now
148
#print "Checker for %(name)s succeeded" % vars(self)
304
150
gobject.source_remove(self.stop_initiator_tag)
305
self.stop_initiator_tag = gobject.timeout_add\
306
(self._timeout_milliseconds,
308
elif not os.WIFEXITED(condition):
309
logger.warning(u"Checker for %(name)s crashed?",
312
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
314
161
def start_checker(self):
315
162
"""Start a new checker subprocess if one is not running.
316
163
If a checker already exists, leave it running and do
318
# The reason for not killing a running checker is that if we
319
# did that, then if a checker (for some reason) started
320
# running slowly and taking more than 'interval' time, the
321
# client would inevitably timeout, since no checker would get
322
# a chance to run to completion. If we instead leave running
323
# checkers alone, the checker would have to take more time
324
# than 'timeout' for the client to be declared invalid, which
325
# is as it should be.
326
165
if self.checker is None:
328
# In case check_command has exactly one % operator
329
command = self.check_command % self.host
331
# Escape attributes for the shell
332
escaped_attrs = dict((key, re.escape(str(val)))
334
vars(self).iteritems())
336
command = self.check_command % escaped_attrs
337
except TypeError, error:
338
logger.error(u'Could not format string "%s":'
339
u' %s', self.check_command, error)
340
return True # Try again later
342
logger.info(u"Starting checker %r for %s",
344
# We don't need to redirect stdout and stderr, since
345
# in normal mode, that is already done by daemon(),
346
# and in debug mode we don't want to. (Stdin is
347
# always replaced by /dev/null.)
348
self.checker = subprocess.Popen(command,
351
self.checker_callback_tag = gobject.child_watch_add\
353
self.checker_callback)
354
except OSError, error:
355
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"
357
181
# Re-run this periodically if run by gobject.timeout_add
359
183
def stop_checker(self):
360
184
"""Force the checker process, if any, to stop."""
361
if self.checker_callback_tag:
362
gobject.source_remove(self.checker_callback_tag)
363
self.checker_callback_tag = None
364
if getattr(self, "checker", None) is None:
185
if not hasattr(self, "checker") or self.checker is None:
366
logger.debug(u"Stopping checker for %(name)s", vars(self))
368
os.kill(self.checker.pid, signal.SIGTERM)
370
#if self.checker.poll() is None:
371
# os.kill(self.checker.pid, signal.SIGKILL)
372
except OSError, error:
373
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)
375
192
self.checker = None
376
def still_valid(self):
193
def still_valid(self, now=None):
377
194
"""Has the timeout not yet passed for this client?"""
378
now = datetime.datetime.now()
379
if self.last_checked_ok is None:
196
now = datetime.datetime.now()
197
if self.last_seen is None:
380
198
return now < (self.created + self.timeout)
382
return now < (self.last_checked_ok + self.timeout)
385
def peer_certificate(session):
386
"Return the peer's OpenPGP certificate as a bytestring"
387
# If not an OpenPGP certificate...
388
if gnutls.library.functions.gnutls_certificate_type_get\
389
(session._c_object) \
390
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
391
# ...do the normal thing
392
return session.peer_certificate
393
list_size = ctypes.c_uint()
394
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
395
(session._c_object, ctypes.byref(list_size))
396
if list_size.value == 0:
399
return ctypes.string_at(cert.data, cert.size)
402
def fingerprint(openpgp):
403
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
404
# New GnuTLS "datum" with the OpenPGP public key
405
datum = gnutls.library.types.gnutls_datum_t\
406
(ctypes.cast(ctypes.c_char_p(openpgp),
407
ctypes.POINTER(ctypes.c_ubyte)),
408
ctypes.c_uint(len(openpgp)))
409
# New empty GnuTLS certificate
410
crt = gnutls.library.types.gnutls_openpgp_crt_t()
411
gnutls.library.functions.gnutls_openpgp_crt_init\
413
# Import the OpenPGP public key into the certificate
414
gnutls.library.functions.gnutls_openpgp_crt_import\
415
(crt, ctypes.byref(datum),
416
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
417
# Verify the self signature in the key
418
crtverify = ctypes.c_uint();
419
gnutls.library.functions.gnutls_openpgp_crt_verify_self\
420
(crt, 0, ctypes.byref(crtverify))
421
if crtverify.value != 0:
422
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
423
raise gnutls.errors.CertificateSecurityError("Verify failed")
424
# New buffer for the fingerprint
425
buffer = ctypes.create_string_buffer(20)
426
buffer_length = ctypes.c_size_t()
427
# Get the fingerprint from the certificate into the buffer
428
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
429
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
430
# Deinit the certificate
431
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
432
# Convert the buffer to a Python bytestring
433
fpr = ctypes.string_at(buffer, buffer_length.value)
434
# Convert the bytestring to hexadecimal notation
435
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
200
return now < (self.last_seen + self.timeout)
439
203
class tcp_handler(SocketServer.BaseRequestHandler, object):
440
204
"""A TCP request handler class.
441
205
Instantiated by IPv6_TCPServer for each request to handle it.
442
206
Note: This will run in its own forked process."""
444
207
def handle(self):
445
logger.info(u"TCP connection from: %s",
446
unicode(self.client_address))
447
session = gnutls.connection.ClientSession\
448
(self.request, gnutls.connection.X509Credentials())
450
line = self.request.makefile().readline()
451
logger.debug(u"Protocol version: %r", line)
453
if int(line.strip().split()[0]) > 1:
455
except (ValueError, IndexError, RuntimeError), error:
456
logger.error(u"Unknown protocol version: %s", error)
459
# Note: gnutls.connection.X509Credentials is really a generic
460
# GnuTLS certificate credentials object so long as no X.509
461
# keys are added to it. Therefore, we can use it here despite
462
# using OpenPGP certificates.
464
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
465
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
467
priority = "NORMAL" # Fallback default, since this
469
if self.server.settings["priority"]:
470
priority = self.server.settings["priority"]
471
gnutls.library.functions.gnutls_priority_set_direct\
472
(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,
475
216
session.handshake()
476
217
except gnutls.errors.GNUTLSError, error:
477
logger.warning(u"Handshake failed: %s", error)
218
#sys.stderr.write(u"Handshake failed: %s\n" % error)
478
219
# Do not run session.bye() here: the session is not
479
220
# established. Just abandon the request.
222
#if session.peer_certificate:
223
# print "DN:", session.peer_certificate.subject
482
fpr = fingerprint(peer_certificate(session))
483
except (TypeError, gnutls.errors.GNUTLSError), error:
484
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)
487
logger.debug(u"Fingerprint: %s", fpr)
489
for c in self.server.clients:
490
if c.fingerprint == fpr:
232
if c.dn == session.peer_certificate.subject:
494
logger.warning(u"Client not found for fingerprint: %s",
498
235
# Have to check if client.still_valid(), since it is possible
499
236
# that the client timed out while establishing the GnuTLS
501
if not client.still_valid():
502
logger.warning(u"Client %(name)s is invalid",
507
while sent_size < len(client.secret):
508
sent = session.send(client.secret[sent_size:])
509
logger.debug(u"Sent: %d, remaining: %d",
510
sent, len(client.secret)
511
- (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")
516
252
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
517
253
"""IPv6 TCP server. Accepts 'None' as address and/or port.
519
settings: Server settings
255
options: Command line options
520
256
clients: Set() of Client objects
521
enabled: Boolean; whether this server is activated yet
257
credentials: GnuTLS X.509 credentials
523
259
address_family = socket.AF_INET6
524
260
def __init__(self, *args, **kwargs):
525
if "settings" in kwargs:
526
self.settings = kwargs["settings"]
527
del kwargs["settings"]
261
if "options" in kwargs:
262
self.options = kwargs["options"]
263
del kwargs["options"]
528
264
if "clients" in kwargs:
529
265
self.clients = kwargs["clients"]
530
266
del kwargs["clients"]
267
if "credentials" in kwargs:
268
self.credentials = kwargs["credentials"]
269
del kwargs["credentials"]
532
270
return super(type(self), self).__init__(*args, **kwargs)
533
271
def server_bind(self):
534
272
"""This overrides the normal server_bind() function
535
273
to bind to an interface if one was specified, and also NOT to
536
274
bind to an address or port if they were not specified."""
537
if self.settings["interface"]:
538
# 25 is from /usr/include/asm-i486/socket.h
539
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
541
280
self.socket.setsockopt(socket.SOL_SOCKET,
543
self.settings["interface"])
281
socket.SO_BINDTODEVICE,
282
self.options.interface)
544
283
except socket.error, error:
545
284
if error[0] == errno.EPERM:
546
logger.error(u"No permission to"
547
u" bind to interface %s",
548
self.settings["interface"])
285
sys.stderr.write(u"Warning: No permission to bind to interface %s\n"
286
% self.options.interface)
551
289
# Only bind(2) the socket if we really need to.
585
311
datetime.timedelta(1)
586
312
>>> string_to_delta(u'1w')
587
313
datetime.timedelta(7)
588
>>> string_to_delta('5m 30s')
589
datetime.timedelta(0, 330)
591
timevalue = datetime.timedelta(0)
592
for s in interval.split():
594
suffix=unicode(s[-1])
597
delta = datetime.timedelta(value)
599
delta = datetime.timedelta(0, value)
601
delta = datetime.timedelta(0, 0, 0, 0, value)
603
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
605
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
608
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:
614
369
def server_state_changed(state):
615
"""Derived from the Avahi example code"""
370
"""From the Avahi server example code"""
616
371
if state == avahi.SERVER_COLLISION:
617
logger.error(u"Zeroconf server name collision")
372
print "WARNING: Server name collision"
619
374
elif state == avahi.SERVER_RUNNING:
623
378
def entry_group_state_changed(state, error):
624
"""Derived from the Avahi example code"""
625
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
627
384
if state == avahi.ENTRY_GROUP_ESTABLISHED:
628
logger.debug(u"Zeroconf service established.")
386
# print "Service established."
629
387
elif state == avahi.ENTRY_GROUP_COLLISION:
630
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
632
399
elif state == avahi.ENTRY_GROUP_FAILURE:
633
logger.critical(u"Avahi: Error in group state changed %s",
635
raise AvahiGroupError("State changed: %s", str(error))
400
print "Error in group state changed", error
637
405
def if_nametoindex(interface):
638
"""Call the C function if_nametoindex(), or equivalent"""
639
global if_nametoindex
406
"""Call the C function if_nametoindex()"""
641
if "ctypes.util" not in sys.modules:
643
if_nametoindex = ctypes.cdll.LoadLibrary\
644
(ctypes.util.find_library("c")).if_nametoindex
645
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):
646
413
if "struct" not in sys.modules:
648
415
if "fcntl" not in sys.modules:
650
def if_nametoindex(interface):
651
"Get an interface index the hard way, i.e. using fcntl()"
652
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
654
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
655
struct.pack("16s16x", interface))
657
interface_index = struct.unpack("I", ifreq[16:20])[0]
658
return interface_index
659
return if_nametoindex(interface)
662
def daemon(nochdir = False, noclose = False):
663
"""See daemon(3). Standard BSD Unix function.
664
This should really exist as os.daemon, but it doesn't (yet)."""
673
# Close all standard open file descriptors
674
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
675
if not stat.S_ISCHR(os.fstat(null).st_mode):
676
raise OSError(errno.ENODEV,
677
"/dev/null not a character device")
678
os.dup2(null, sys.stdin.fileno())
679
os.dup2(null, sys.stdout.fileno())
680
os.dup2(null, sys.stderr.fileno())
686
global main_loop_started
687
main_loop_started = False
689
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()
690
428
parser.add_option("-i", "--interface", type="string",
691
metavar="IF", help="Bind to interface IF")
692
parser.add_option("-a", "--address", type="string",
693
help="Address to listen for requests on")
694
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,
695
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")
696
451
parser.add_option("--check", action="store_true", default=False,
697
452
help="Run self-test")
698
parser.add_option("--debug", action="store_true",
699
help="Debug mode; run in foreground and log to"
701
parser.add_option("--priority", type="string", help="GnuTLS"
702
" priority string (see GnuTLS documentation)")
703
parser.add_option("--servicename", type="string", metavar="NAME",
704
help="Zeroconf service name")
705
parser.add_option("--configdir", type="string",
706
default="/etc/mandos", metavar="DIR",
707
help="Directory to search for configuration"
709
453
(options, args) = parser.parse_args()
711
455
if options.check:
713
457
doctest.testmod()
716
# Default values for config file for server-global settings
717
server_defaults = { "interface": "",
722
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
723
"servicename": "Mandos",
726
# Parse config file for server-global settings
727
server_config = ConfigParser.SafeConfigParser(server_defaults)
729
server_config.read(os.path.join(options.configdir, "mandos.conf"))
730
# Convert the SafeConfigParser object to a dict
731
server_settings = server_config.defaults()
732
# Use getboolean on the boolean config option
733
server_settings["debug"] = server_config.getboolean\
737
# Override the settings from the config file with command line
739
for option in ("interface", "address", "port", "debug",
740
"priority", "servicename", "configdir"):
741
value = getattr(options, option)
742
if value is not None:
743
server_settings[option] = value
745
# Now we have our good server settings in "server_settings"
747
debug = server_settings["debug"]
750
syslogger.setLevel(logging.WARNING)
751
console.setLevel(logging.WARNING)
753
if server_settings["servicename"] != "Mandos":
754
syslogger.setFormatter(logging.Formatter\
755
('Mandos (%s): %%(levelname)s:'
757
% server_settings["servicename"]))
759
# Parse config file with clients
760
client_defaults = { "timeout": "1h",
762
"checker": "fping -q -- %(host)s",
765
client_config = ConfigParser.SafeConfigParser(client_defaults)
766
client_config.read(os.path.join(server_settings["configdir"],
770
tcp_server = IPv6_TCPServer((server_settings["address"],
771
server_settings["port"]),
773
settings=server_settings,
775
pidfilename = "/var/run/mandos.pid"
777
pidfile = open(pidfilename, "w")
778
except IOError, error:
779
logger.error("Could not open file %r", pidfilename)
784
uid = pwd.getpwnam("mandos").pw_uid
787
uid = pwd.getpwnam("nobody").pw_uid
791
gid = pwd.getpwnam("mandos").pw_gid
794
gid = pwd.getpwnam("nogroup").pw_gid
800
except OSError, error:
801
if error[0] != errno.EPERM:
805
service = AvahiService(name = server_settings["servicename"],
806
type = "_mandos._tcp", );
807
if server_settings["interface"]:
808
service.interface = if_nametoindex\
809
(server_settings["interface"])
814
# 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
815
483
DBusGMainLoop(set_as_default=True )
816
484
main_loop = gobject.MainLoop()
817
485
bus = dbus.SystemBus()
818
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
819
avahi.DBUS_PATH_SERVER),
820
avahi.DBUS_INTERFACE_SERVER)
486
server = dbus.Interface(
487
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
488
avahi.DBUS_INTERFACE_SERVER )
821
489
# End of Avahi example code
823
492
def remove_from_clients(client):
824
493
clients.remove(client)
826
logger.critical(u"No clients left, exiting")
495
print "No clients left, exiting"
829
clients.update(Set(Client(name = section,
498
clients.update(Set(Client(name=section, options=options,
830
499
stop_hook = remove_from_clients,
832
= dict(client_config.items(section)))
500
**(dict(client_config\
833
502
for section in client_config.sections()))
835
logger.critical(u"No clients defined")
839
# Redirect stdin so all checkers get /dev/null
840
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
841
os.dup2(null, sys.stdin.fileno())
846
logger.removeHandler(console)
847
# Close all input and output, do double fork, etc.
852
pidfile.write(str(pid) + "\n")
856
logger.error(u"Could not write to file %r with PID %d",
859
# "pidfile" was never created
864
"Cleanup function; run on exit"
866
# From the Avahi example code
867
if not group is None:
870
# End of Avahi example code
873
client = clients.pop()
874
client.stop_hook = None
877
atexit.register(cleanup)
880
signal.signal(signal.SIGINT, signal.SIG_IGN)
881
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
882
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
884
503
for client in clients:
888
tcp_server.server_activate()
890
# Find out what port we got
891
service.port = tcp_server.socket.getsockname()[1]
892
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
893
u" scope_id %d" % tcp_server.socket.getsockname())
895
#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:],
898
# From the Avahi example code
899
server.connect_to_signal("StateChanged", server_state_changed)
901
server_state_changed(server.GetState())
902
except dbus.exceptions.DBusException, error:
903
logger.critical(u"DBusException: %s", error)
905
# End of Avahi example code
907
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
908
lambda *args, **kwargs:
909
tcp_server.handle_request\
910
(*args[2:], **kwargs) or True)
912
logger.debug(u"Starting main loop")
913
main_loop_started = True
915
except AvahiError, error:
916
logger.critical(u"AvahiError: %s" + unicode(error))
918
529
except KeyboardInterrupt:
922
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