52
19
from sets import Set
57
import logging.handlers
62
25
from dbus.mainloop.glib import DBusGMainLoop
67
logger = logging.Logger('mandos')
68
syslogger = logging.handlers.SysLogHandler\
69
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
71
syslogger.setFormatter(logging.Formatter\
72
('Mandos: %(levelname)s: %(message)s'))
73
logger.addHandler(syslogger)
75
console = logging.StreamHandler()
76
console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
78
logger.addHandler(console)
80
class AvahiError(Exception):
81
def __init__(self, value):
84
return repr(self.value)
86
class AvahiServiceError(AvahiError):
89
class AvahiGroupError(AvahiError):
93
class AvahiService(object):
94
"""An Avahi (Zeroconf) service.
96
interface: integer; avahi.IF_UNSPEC or an interface index.
97
Used to optionally bind to the specified interface.
98
name: string; Example: 'Mandos'
99
type: string; Example: '_mandos._tcp'.
100
See <http://www.dns-sd.org/ServiceTypes.html>
101
port: integer; what port to announce
102
TXT: list of strings; TXT record for the service
103
domain: string; Domain to publish on, default to .local if empty.
104
host: string; Host to publish records for, default is localhost
105
max_renames: integer; maximum number of renames
106
rename_count: integer; counter so we only rename after collisions
107
a sensible number of times
109
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
110
type = None, port = None, TXT = None, domain = "",
111
host = "", max_renames = 32768):
112
self.interface = interface
122
self.rename_count = 0
123
self.max_renames = max_renames
125
"""Derived from the Avahi example code"""
126
if self.rename_count >= self.max_renames:
127
logger.critical(u"No suitable Zeroconf service name found"
128
u" after %i retries, exiting.",
130
raise AvahiServiceError("Too many renames")
131
self.name = server.GetAlternativeServiceName(self.name)
132
logger.info(u"Changing Zeroconf service name to %r ...",
134
syslogger.setFormatter(logging.Formatter\
135
('Mandos (%s): %%(levelname)s:'
136
' %%(message)s' % self.name))
139
self.rename_count += 1
141
"""Derived from the Avahi example code"""
142
if group is not None:
145
"""Derived from the Avahi example code"""
148
group = dbus.Interface\
149
(bus.get_object(avahi.DBUS_NAME,
150
server.EntryGroupNew()),
151
avahi.DBUS_INTERFACE_ENTRY_GROUP)
152
group.connect_to_signal('StateChanged',
153
entry_group_state_changed)
154
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
155
service.name, service.type)
157
self.interface, # interface
158
avahi.PROTO_INET6, # protocol
159
dbus.UInt32(0), # flags
160
self.name, self.type,
161
self.domain, self.host,
162
dbus.UInt16(self.port),
163
avahi.string_array_to_txt_array(self.TXT))
166
# From the Avahi example code:
167
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
168
41
# End of Avahi example code
171
44
class Client(object):
172
45
"""A representation of a client host served by this server.
174
name: string; from the config file, used in log messages
175
fingerprint: string (40 or 32 hexadecimal digits); used to
176
uniquely identify the client
177
secret: bytestring; sent verbatim (over TLS) to client
178
host: string; available for use by the checker command
179
created: datetime.datetime(); object creation, not client host
180
last_checked_ok: datetime.datetime() or None if not yet checked OK
181
timeout: datetime.timedelta(); How long from last_checked_ok
182
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
183
53
interval: datetime.timedelta(); How often to start a new checker
54
timeout_milliseconds: Used by gobject.timeout_add()
55
interval_milliseconds: - '' -
184
56
stop_hook: If set, called by stop() as stop_hook(self)
185
57
checker: subprocess.Popen(); a running checker process used
186
58
to see if the client lives.
187
'None' if no process is running.
59
Is None if no process is running.
188
60
checker_initiator_tag: a gobject event source tag, or None
189
61
stop_initiator_tag: - '' -
190
62
checker_callback_tag: - '' -
191
checker_command: string; External command which is run to check if
192
client lives. %() expansions are done at
193
runtime with vars(self) as dict, so that for
194
instance %(name)s can be used in the command.
196
_timeout: Real variable for 'timeout'
197
_interval: Real variable for 'interval'
198
_timeout_milliseconds: Used when calling gobject.timeout_add()
199
_interval_milliseconds: - '' -
201
def _set_timeout(self, timeout):
202
"Setter function for 'timeout' attribute"
203
self._timeout = timeout
204
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
205
93
* 24 * 60 * 60 * 1000)
206
+ (self.timeout.seconds * 1000)
207
+ (self.timeout.microseconds
94
+ (self.interval.seconds * 1000)
95
+ (self.interval.microseconds
209
timeout = property(lambda self: self._timeout,
212
def _set_interval(self, interval):
213
"Setter function for 'interval' attribute"
214
self._interval = interval
215
self._interval_milliseconds = ((self.interval.days
216
* 24 * 60 * 60 * 1000)
217
+ (self.interval.seconds
219
+ (self.interval.microseconds
221
interval = property(lambda self: self._interval,
224
def __init__(self, name = None, stop_hook=None, config={}):
225
"""Note: the 'checker' key in 'config' sets the
226
'checker_command' attribute and *not* the 'checker'
229
logger.debug(u"Creating client %r", self.name)
230
# Uppercase and remove spaces from fingerprint for later
231
# comparison purposes with return value from the fingerprint()
233
self.fingerprint = config["fingerprint"].upper()\
235
logger.debug(u" Fingerprint: %s", self.fingerprint)
236
if "secret" in config:
237
self.secret = config["secret"].decode(u"base64")
238
elif "secfile" in config:
239
sf = open(config["secfile"])
240
self.secret = sf.read()
243
raise TypeError(u"No secret or secfile for client %s"
245
self.host = config.get("host", "")
246
self.created = datetime.datetime.now()
247
self.last_checked_ok = None
248
self.timeout = string_to_delta(config["timeout"])
249
self.interval = string_to_delta(config["interval"])
250
97
self.stop_hook = stop_hook
251
98
self.checker = None
252
99
self.checker_initiator_tag = None
253
100
self.stop_initiator_tag = None
254
101
self.checker_callback_tag = None
255
self.check_command = config["checker"]
257
"""Start this client's checker and timeout hooks"""
103
"""Start this clients checker and timeout hooks"""
258
104
# Schedule a new checker to be started an 'interval' from now,
259
105
# and every interval from then on.
260
self.checker_initiator_tag = gobject.timeout_add\
261
(self._interval_milliseconds,
106
self.checker_initiator_tag = gobject.\
107
timeout_add(self.interval_milliseconds,
263
109
# Also start a new checker *right now*.
264
110
self.start_checker()
265
111
# Schedule a stop() when 'timeout' has passed
266
self.stop_initiator_tag = gobject.timeout_add\
267
(self._timeout_milliseconds,
112
self.stop_initiator_tag = gobject.\
113
timeout_add(self.timeout_milliseconds,
270
116
"""Stop this client.
271
The possibility that a client might be restarted is left open,
272
but not currently used."""
273
# If this client doesn't have a secret, it is already stopped.
274
if hasattr(self, "secret") and self.secret:
275
logger.info(u"Stopping client %s", self.name)
279
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:
280
122
gobject.source_remove(self.stop_initiator_tag)
281
123
self.stop_initiator_tag = None
282
if getattr(self, "checker_initiator_tag", False):
124
if self.checker_initiator_tag:
283
125
gobject.source_remove(self.checker_initiator_tag)
284
126
self.checker_initiator_tag = None
285
127
self.stop_checker()
288
130
# Do not run this again if called by a gobject.timeout_add
290
132
def __del__(self):
291
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
293
143
def checker_callback(self, pid, condition):
294
144
"""The checker has completed, so take appropriate actions."""
295
145
now = datetime.datetime.now()
296
self.checker_callback_tag = None
298
146
if os.WIFEXITED(condition) \
299
147
and (os.WEXITSTATUS(condition) == 0):
300
logger.info(u"Checker for %(name)s succeeded",
302
self.last_checked_ok = now
148
#print "Checker for %(name)s succeeded" % vars(self)
303
150
gobject.source_remove(self.stop_initiator_tag)
304
self.stop_initiator_tag = gobject.timeout_add\
305
(self._timeout_milliseconds,
307
elif not os.WIFEXITED(condition):
308
logger.warning(u"Checker for %(name)s crashed?",
311
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
313
161
def start_checker(self):
314
162
"""Start a new checker subprocess if one is not running.
315
163
If a checker already exists, leave it running and do
317
# The reason for not killing a running checker is that if we
318
# did that, then if a checker (for some reason) started
319
# running slowly and taking more than 'interval' time, the
320
# client would inevitably timeout, since no checker would get
321
# a chance to run to completion. If we instead leave running
322
# checkers alone, the checker would have to take more time
323
# than 'timeout' for the client to be declared invalid, which
324
# is as it should be.
325
165
if self.checker is None:
327
# In case check_command has exactly one % operator
328
command = self.check_command % self.host
330
# Escape attributes for the shell
331
escaped_attrs = dict((key, re.escape(str(val)))
333
vars(self).iteritems())
335
command = self.check_command % escaped_attrs
336
except TypeError, error:
337
logger.error(u'Could not format string "%s":'
338
u' %s', self.check_command, error)
339
return True # Try again later
341
logger.info(u"Starting checker %r for %s",
343
# We don't need to redirect stdout and stderr, since
344
# in normal mode, that is already done by daemon(),
345
# and in debug mode we don't want to. (Stdin is
346
# always replaced by /dev/null.)
347
self.checker = subprocess.Popen(command,
350
self.checker_callback_tag = gobject.child_watch_add\
352
self.checker_callback)
353
except OSError, error:
354
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"
356
181
# Re-run this periodically if run by gobject.timeout_add
358
183
def stop_checker(self):
359
184
"""Force the checker process, if any, to stop."""
360
if self.checker_callback_tag:
361
gobject.source_remove(self.checker_callback_tag)
362
self.checker_callback_tag = None
363
if getattr(self, "checker", None) is None:
185
if not hasattr(self, "checker") or self.checker is None:
365
logger.debug(u"Stopping checker for %(name)s", vars(self))
367
os.kill(self.checker.pid, signal.SIGTERM)
369
#if self.checker.poll() is None:
370
# os.kill(self.checker.pid, signal.SIGKILL)
371
except OSError, error:
372
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)
374
192
self.checker = None
375
def still_valid(self):
193
def still_valid(self, now=None):
376
194
"""Has the timeout not yet passed for this client?"""
377
now = datetime.datetime.now()
378
if self.last_checked_ok is None:
196
now = datetime.datetime.now()
197
if self.last_seen is None:
379
198
return now < (self.created + self.timeout)
381
return now < (self.last_checked_ok + self.timeout)
384
def peer_certificate(session):
385
"Return the peer's OpenPGP certificate as a bytestring"
386
# If not an OpenPGP certificate...
387
if gnutls.library.functions.gnutls_certificate_type_get\
388
(session._c_object) \
389
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
390
# ...do the normal thing
391
return session.peer_certificate
392
list_size = ctypes.c_uint()
393
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
394
(session._c_object, ctypes.byref(list_size))
395
if list_size.value == 0:
398
return ctypes.string_at(cert.data, cert.size)
401
def fingerprint(openpgp):
402
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
403
# New GnuTLS "datum" with the OpenPGP public key
404
datum = gnutls.library.types.gnutls_datum_t\
405
(ctypes.cast(ctypes.c_char_p(openpgp),
406
ctypes.POINTER(ctypes.c_ubyte)),
407
ctypes.c_uint(len(openpgp)))
408
# New empty GnuTLS certificate
409
crt = gnutls.library.types.gnutls_openpgp_crt_t()
410
gnutls.library.functions.gnutls_openpgp_crt_init\
412
# Import the OpenPGP public key into the certificate
413
gnutls.library.functions.gnutls_openpgp_crt_import\
414
(crt, ctypes.byref(datum),
415
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
416
# Verify the self signature in the key
417
crtverify = ctypes.c_uint();
418
gnutls.library.functions.gnutls_openpgp_crt_verify_self\
419
(crt, 0, ctypes.byref(crtverify))
420
if crtverify.value != 0:
421
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
422
raise gnutls.errors.CertificateSecurityError("Verify failed")
423
# New buffer for the fingerprint
424
buffer = ctypes.create_string_buffer(20)
425
buffer_length = ctypes.c_size_t()
426
# Get the fingerprint from the certificate into the buffer
427
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
428
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
429
# Deinit the certificate
430
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
431
# Convert the buffer to a Python bytestring
432
fpr = ctypes.string_at(buffer, buffer_length.value)
433
# Convert the bytestring to hexadecimal notation
434
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
200
return now < (self.last_seen + self.timeout)
438
203
class tcp_handler(SocketServer.BaseRequestHandler, object):
439
204
"""A TCP request handler class.
440
205
Instantiated by IPv6_TCPServer for each request to handle it.
441
206
Note: This will run in its own forked process."""
443
207
def handle(self):
444
logger.info(u"TCP connection from: %s",
445
unicode(self.client_address))
446
session = gnutls.connection.ClientSession\
447
(self.request, gnutls.connection.X509Credentials())
449
line = self.request.makefile().readline()
450
logger.debug(u"Protocol version: %r", line)
452
if int(line.strip().split()[0]) > 1:
454
except (ValueError, IndexError, RuntimeError), error:
455
logger.error(u"Unknown protocol version: %s", error)
458
# Note: gnutls.connection.X509Credentials is really a generic
459
# GnuTLS certificate credentials object so long as no X.509
460
# keys are added to it. Therefore, we can use it here despite
461
# using OpenPGP certificates.
463
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
464
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
466
priority = "NORMAL" # Fallback default, since this
468
if self.server.settings["priority"]:
469
priority = self.server.settings["priority"]
470
gnutls.library.functions.gnutls_priority_set_direct\
471
(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,
474
216
session.handshake()
475
217
except gnutls.errors.GNUTLSError, error:
476
logger.warning(u"Handshake failed: %s", error)
218
#sys.stderr.write(u"Handshake failed: %s\n" % error)
477
219
# Do not run session.bye() here: the session is not
478
220
# established. Just abandon the request.
222
#if session.peer_certificate:
223
# print "DN:", session.peer_certificate.subject
481
fpr = fingerprint(peer_certificate(session))
482
except (TypeError, gnutls.errors.GNUTLSError), error:
483
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)
486
logger.debug(u"Fingerprint: %s", fpr)
488
for c in self.server.clients:
489
if c.fingerprint == fpr:
232
if c.dn == session.peer_certificate.subject:
493
logger.warning(u"Client not found for fingerprint: %s",
497
235
# Have to check if client.still_valid(), since it is possible
498
236
# that the client timed out while establishing the GnuTLS
500
if not client.still_valid():
501
logger.warning(u"Client %(name)s is invalid",
506
while sent_size < len(client.secret):
507
sent = session.send(client.secret[sent_size:])
508
logger.debug(u"Sent: %d, remaining: %d",
509
sent, len(client.secret)
510
- (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")
515
252
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
516
253
"""IPv6 TCP server. Accepts 'None' as address and/or port.
518
settings: Server settings
255
options: Command line options
519
256
clients: Set() of Client objects
257
credentials: GnuTLS X.509 credentials
521
259
address_family = socket.AF_INET6
522
260
def __init__(self, *args, **kwargs):
523
if "settings" in kwargs:
524
self.settings = kwargs["settings"]
525
del kwargs["settings"]
261
if "options" in kwargs:
262
self.options = kwargs["options"]
263
del kwargs["options"]
526
264
if "clients" in kwargs:
527
265
self.clients = kwargs["clients"]
528
266
del kwargs["clients"]
267
if "credentials" in kwargs:
268
self.credentials = kwargs["credentials"]
269
del kwargs["credentials"]
529
270
return super(type(self), self).__init__(*args, **kwargs)
530
271
def server_bind(self):
531
272
"""This overrides the normal server_bind() function
532
273
to bind to an interface if one was specified, and also NOT to
533
274
bind to an address or port if they were not specified."""
534
if self.settings["interface"]:
535
# 25 is from /usr/include/asm-i486/socket.h
536
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
538
280
self.socket.setsockopt(socket.SOL_SOCKET,
540
self.settings["interface"])
281
socket.SO_BINDTODEVICE,
282
self.options.interface)
541
283
except socket.error, error:
542
284
if error[0] == errno.EPERM:
543
logger.error(u"No permission to"
544
u" bind to interface %s",
545
self.settings["interface"])
285
sys.stderr.write(u"Warning: No permission to bind to interface %s\n"
286
% self.options.interface)
548
289
# Only bind(2) the socket if we really need to.
577
311
datetime.timedelta(1)
578
312
>>> string_to_delta(u'1w')
579
313
datetime.timedelta(7)
580
>>> string_to_delta('5m 30s')
581
datetime.timedelta(0, 330)
583
timevalue = datetime.timedelta(0)
584
for s in interval.split():
586
suffix=unicode(s[-1])
589
delta = datetime.timedelta(value)
591
delta = datetime.timedelta(0, value)
593
delta = datetime.timedelta(0, 0, 0, 0, value)
595
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
597
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
600
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:
606
369
def server_state_changed(state):
607
"""Derived from the Avahi example code"""
370
"""From the Avahi server example code"""
608
371
if state == avahi.SERVER_COLLISION:
609
logger.error(u"Zeroconf server name collision")
372
print "WARNING: Server name collision"
611
374
elif state == avahi.SERVER_RUNNING:
615
378
def entry_group_state_changed(state, error):
616
"""Derived from the Avahi example code"""
617
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
619
384
if state == avahi.ENTRY_GROUP_ESTABLISHED:
620
logger.debug(u"Zeroconf service established.")
386
# print "Service established."
621
387
elif state == avahi.ENTRY_GROUP_COLLISION:
622
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
624
399
elif state == avahi.ENTRY_GROUP_FAILURE:
625
logger.critical(u"Avahi: Error in group state changed %s",
627
raise AvahiGroupError("State changed: %s", str(error))
400
print "Error in group state changed", error
629
405
def if_nametoindex(interface):
630
"""Call the C function if_nametoindex(), or equivalent"""
631
global if_nametoindex
406
"""Call the C function if_nametoindex()"""
633
if "ctypes.util" not in sys.modules:
635
if_nametoindex = ctypes.cdll.LoadLibrary\
636
(ctypes.util.find_library("c")).if_nametoindex
637
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):
638
413
if "struct" not in sys.modules:
640
415
if "fcntl" not in sys.modules:
642
def if_nametoindex(interface):
643
"Get an interface index the hard way, i.e. using fcntl()"
644
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
646
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
647
struct.pack("16s16x", interface))
649
interface_index = struct.unpack("I", ifreq[16:20])[0]
650
return interface_index
651
return if_nametoindex(interface)
654
def daemon(nochdir = False, noclose = False):
655
"""See daemon(3). Standard BSD Unix function.
656
This should really exist as os.daemon, but it doesn't (yet)."""
665
# Close all standard open file descriptors
666
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
667
if not stat.S_ISCHR(os.fstat(null).st_mode):
668
raise OSError(errno.ENODEV,
669
"/dev/null not a character device")
670
os.dup2(null, sys.stdin.fileno())
671
os.dup2(null, sys.stdout.fileno())
672
os.dup2(null, sys.stderr.fileno())
678
global main_loop_started
679
main_loop_started = False
681
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()
682
428
parser.add_option("-i", "--interface", type="string",
683
metavar="IF", help="Bind to interface IF")
684
parser.add_option("-a", "--address", type="string",
685
help="Address to listen for requests on")
686
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,
687
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")
688
451
parser.add_option("--check", action="store_true", default=False,
689
452
help="Run self-test")
690
parser.add_option("--debug", action="store_true",
691
help="Debug mode; run in foreground and log to"
693
parser.add_option("--priority", type="string", help="GnuTLS"
694
" priority string (see GnuTLS documentation)")
695
parser.add_option("--servicename", type="string", metavar="NAME",
696
help="Zeroconf service name")
697
parser.add_option("--configdir", type="string",
698
default="/etc/mandos", metavar="DIR",
699
help="Directory to search for configuration"
701
453
(options, args) = parser.parse_args()
703
455
if options.check:
705
457
doctest.testmod()
708
# Default values for config file for server-global settings
709
server_defaults = { "interface": "",
714
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
715
"servicename": "Mandos",
718
# Parse config file for server-global settings
719
server_config = ConfigParser.SafeConfigParser(server_defaults)
721
server_config.read(os.path.join(options.configdir, "mandos.conf"))
722
# Convert the SafeConfigParser object to a dict
723
server_settings = server_config.defaults()
724
# Use getboolean on the boolean config option
725
server_settings["debug"] = server_config.getboolean\
729
# Override the settings from the config file with command line
731
for option in ("interface", "address", "port", "debug",
732
"priority", "servicename", "configdir"):
733
value = getattr(options, option)
734
if value is not None:
735
server_settings[option] = value
737
# Now we have our good server settings in "server_settings"
739
debug = server_settings["debug"]
742
syslogger.setLevel(logging.WARNING)
743
console.setLevel(logging.WARNING)
745
if server_settings["servicename"] != "Mandos":
746
syslogger.setFormatter(logging.Formatter\
747
('Mandos (%s): %%(levelname)s:'
749
% server_settings["servicename"]))
751
# Parse config file with clients
752
client_defaults = { "timeout": "1h",
754
"checker": "fping -q -- %(host)s",
757
client_config = ConfigParser.SafeConfigParser(client_defaults)
758
client_config.read(os.path.join(server_settings["configdir"],
762
service = AvahiService(name = server_settings["servicename"],
763
type = "_mandos._tcp", );
764
if server_settings["interface"]:
765
service.interface = if_nametoindex\
766
(server_settings["interface"])
771
# 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
772
483
DBusGMainLoop(set_as_default=True )
773
484
main_loop = gobject.MainLoop()
774
485
bus = dbus.SystemBus()
775
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
776
avahi.DBUS_PATH_SERVER),
777
avahi.DBUS_INTERFACE_SERVER)
486
server = dbus.Interface(
487
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
488
avahi.DBUS_INTERFACE_SERVER )
778
489
# End of Avahi example code
781
492
def remove_from_clients(client):
782
493
clients.remove(client)
784
logger.critical(u"No clients left, exiting")
495
print "No clients left, exiting"
787
clients.update(Set(Client(name = section,
498
clients.update(Set(Client(name=section, options=options,
788
499
stop_hook = remove_from_clients,
790
= dict(client_config.items(section)))
500
**(dict(client_config\
791
502
for section in client_config.sections()))
793
logger.critical(u"No clients defined")
797
# Redirect stdin so all checkers get /dev/null
798
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
799
os.dup2(null, sys.stdin.fileno())
804
logger.removeHandler(console)
805
# Close all input and output, do double fork, etc.
808
pidfilename = "/var/run/mandos/mandos.pid"
811
pidfile = open(pidfilename, "w")
812
pidfile.write(str(pid) + "\n")
816
logger.error(u"Could not write %s file with PID %d",
817
pidfilename, os.getpid())
820
"Cleanup function; run on exit"
822
# From the Avahi example code
823
if not group is None:
826
# End of Avahi example code
829
client = clients.pop()
830
client.stop_hook = None
833
atexit.register(cleanup)
836
signal.signal(signal.SIGINT, signal.SIG_IGN)
837
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
838
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
840
503
for client in clients:
843
tcp_server = IPv6_TCPServer((server_settings["address"],
844
server_settings["port"]),
506
tcp_server = IPv6_TCPServer((None, options.port),
846
settings=server_settings,
848
# Find out what port we got
849
service.port = tcp_server.socket.getsockname()[1]
850
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
851
u" scope_id %d" % tcp_server.socket.getsockname())
853
#service.interface = tcp_server.socket.getsockname()[3]
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:],
856
# From the Avahi example code
857
server.connect_to_signal("StateChanged", server_state_changed)
859
server_state_changed(server.GetState())
860
except dbus.exceptions.DBusException, error:
861
logger.critical(u"DBusException: %s", error)
863
# End of Avahi example code
865
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
866
lambda *args, **kwargs:
867
tcp_server.handle_request\
868
(*args[2:], **kwargs) or True)
870
logger.debug(u"Starting main loop")
871
main_loop_started = True
873
except AvahiError, error:
874
logger.critical(u"AvahiError: %s" + unicode(error))
876
529
except KeyboardInterrupt:
880
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