51
19
from sets import Set
56
import logging.handlers
61
25
from dbus.mainloop.glib import DBusGMainLoop
65
logger = logging.Logger('mandos')
66
syslogger = logging.handlers.SysLogHandler\
67
(facility = logging.handlers.SysLogHandler.LOG_DAEMON)
68
syslogger.setFormatter(logging.Formatter\
69
('%(levelname)s: %(message)s'))
70
logger.addHandler(syslogger)
74
class AvahiError(Exception):
75
def __init__(self, value):
78
return repr(self.value)
80
class AvahiServiceError(AvahiError):
83
class AvahiGroupError(AvahiError):
87
class AvahiService(object):
88
"""An Avahi (Zeroconf) service.
90
interface: integer; avahi.IF_UNSPEC or an interface index.
91
Used to optionally bind to the specified interface.
92
name: string; Example: 'Mandos'
93
type: string; Example: '_mandos._tcp'.
94
See <http://www.dns-sd.org/ServiceTypes.html>
95
port: integer; what port to announce
96
TXT: list of strings; TXT record for the service
97
domain: string; Domain to publish on, default to .local if empty.
98
host: string; Host to publish records for, default is localhost
99
max_renames: integer; maximum number of renames
100
rename_count: integer; counter so we only rename after collisions
101
a sensible number of times
103
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
104
type = None, port = None, TXT = None, domain = "",
105
host = "", max_renames = 12):
106
self.interface = interface
116
self.rename_count = 0
118
"""Derived from the Avahi example code"""
119
if self.rename_count >= self.max_renames:
120
logger.critical(u"No suitable service name found after %i"
121
u" retries, exiting.", rename_count)
122
raise AvahiServiceError("Too many renames")
123
name = server.GetAlternativeServiceName(name)
124
logger.error(u"Changing name to %r ...", name)
127
self.rename_count += 1
129
"""Derived from the Avahi example code"""
130
if group is not None:
133
"""Derived from the Avahi example code"""
136
group = dbus.Interface\
137
(bus.get_object(avahi.DBUS_NAME,
138
server.EntryGroupNew()),
139
avahi.DBUS_INTERFACE_ENTRY_GROUP)
140
group.connect_to_signal('StateChanged',
141
entry_group_state_changed)
142
logger.debug(u"Adding service '%s' of type '%s' ...",
143
service.name, service.type)
145
self.interface, # interface
146
avahi.PROTO_INET6, # protocol
147
dbus.UInt32(0), # flags
148
self.name, self.type,
149
self.domain, self.host,
150
dbus.UInt16(self.port),
151
avahi.string_array_to_txt_array(self.TXT))
154
# From the Avahi example code:
155
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
156
41
# End of Avahi example code
159
44
class Client(object):
160
45
"""A representation of a client host served by this server.
162
name: string; from the config file, used in log messages
163
fingerprint: string (40 or 32 hexadecimal digits); used to
164
uniquely identify the client
165
secret: bytestring; sent verbatim (over TLS) to client
166
fqdn: string (FQDN); available for use by the checker command
167
created: datetime.datetime(); object creation, not client host
168
last_checked_ok: datetime.datetime() or None if not yet checked OK
169
timeout: datetime.timedelta(); How long from last_checked_ok
170
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
171
53
interval: datetime.timedelta(); How often to start a new checker
54
timeout_milliseconds: Used by gobject.timeout_add()
55
interval_milliseconds: - '' -
172
56
stop_hook: If set, called by stop() as stop_hook(self)
173
57
checker: subprocess.Popen(); a running checker process used
174
58
to see if the client lives.
175
'None' if no process is running.
59
Is None if no process is running.
176
60
checker_initiator_tag: a gobject event source tag, or None
177
61
stop_initiator_tag: - '' -
178
62
checker_callback_tag: - '' -
179
checker_command: string; External command which is run to check if
180
client lives. %() expansions are done at
181
runtime with vars(self) as dict, so that for
182
instance %(name)s can be used in the command.
184
_timeout: Real variable for 'timeout'
185
_interval: Real variable for 'interval'
186
_timeout_milliseconds: Used when calling gobject.timeout_add()
187
_interval_milliseconds: - '' -
189
def _set_timeout(self, timeout):
190
"Setter function for 'timeout' attribute"
191
self._timeout = timeout
192
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
193
93
* 24 * 60 * 60 * 1000)
194
+ (self.timeout.seconds * 1000)
195
+ (self.timeout.microseconds
94
+ (self.interval.seconds * 1000)
95
+ (self.interval.microseconds
197
timeout = property(lambda self: self._timeout,
200
def _set_interval(self, interval):
201
"Setter function for 'interval' attribute"
202
self._interval = interval
203
self._interval_milliseconds = ((self.interval.days
204
* 24 * 60 * 60 * 1000)
205
+ (self.interval.seconds
207
+ (self.interval.microseconds
209
interval = property(lambda self: self._interval,
212
def __init__(self, name = None, stop_hook=None, config={}):
213
"""Note: the 'checker' key in 'config' sets the
214
'checker_command' attribute and *not* the 'checker'
217
logger.debug(u"Creating client %r", self.name)
218
# Uppercase and remove spaces from fingerprint for later
219
# comparison purposes with return value from the fingerprint()
221
self.fingerprint = config["fingerprint"].upper()\
223
logger.debug(u" Fingerprint: %s", self.fingerprint)
224
if "secret" in config:
225
self.secret = config["secret"].decode(u"base64")
226
elif "secfile" in config:
227
sf = open(config["secfile"])
228
self.secret = sf.read()
231
raise TypeError(u"No secret or secfile for client %s"
233
self.fqdn = config.get("fqdn", "")
234
self.created = datetime.datetime.now()
235
self.last_checked_ok = None
236
self.timeout = string_to_delta(config["timeout"])
237
self.interval = string_to_delta(config["interval"])
238
97
self.stop_hook = stop_hook
239
98
self.checker = None
240
99
self.checker_initiator_tag = None
241
100
self.stop_initiator_tag = None
242
101
self.checker_callback_tag = None
243
self.check_command = config["checker"]
245
"""Start this client's checker and timeout hooks"""
103
"""Start this clients checker and timeout hooks"""
246
104
# Schedule a new checker to be started an 'interval' from now,
247
105
# and every interval from then on.
248
self.checker_initiator_tag = gobject.timeout_add\
249
(self._interval_milliseconds,
106
self.checker_initiator_tag = gobject.\
107
timeout_add(self.interval_milliseconds,
251
109
# Also start a new checker *right now*.
252
110
self.start_checker()
253
111
# Schedule a stop() when 'timeout' has passed
254
self.stop_initiator_tag = gobject.timeout_add\
255
(self._timeout_milliseconds,
112
self.stop_initiator_tag = gobject.\
113
timeout_add(self.timeout_milliseconds,
258
116
"""Stop this client.
259
The possibility that a client might be restarted is left open,
260
but not currently used."""
261
# If this client doesn't have a secret, it is already stopped.
263
logger.info(u"Stopping client %s", self.name)
267
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:
268
122
gobject.source_remove(self.stop_initiator_tag)
269
123
self.stop_initiator_tag = None
270
if getattr(self, "checker_initiator_tag", False):
124
if self.checker_initiator_tag:
271
125
gobject.source_remove(self.checker_initiator_tag)
272
126
self.checker_initiator_tag = None
273
127
self.stop_checker()
276
130
# Do not run this again if called by a gobject.timeout_add
278
132
def __del__(self):
279
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
281
143
def checker_callback(self, pid, condition):
282
144
"""The checker has completed, so take appropriate actions."""
283
145
now = datetime.datetime.now()
284
self.checker_callback_tag = None
286
146
if os.WIFEXITED(condition) \
287
147
and (os.WEXITSTATUS(condition) == 0):
288
logger.info(u"Checker for %(name)s succeeded",
290
self.last_checked_ok = now
148
#print "Checker for %(name)s succeeded" % vars(self)
291
150
gobject.source_remove(self.stop_initiator_tag)
292
self.stop_initiator_tag = gobject.timeout_add\
293
(self._timeout_milliseconds,
295
elif not os.WIFEXITED(condition):
296
logger.warning(u"Checker for %(name)s crashed?",
299
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
301
161
def start_checker(self):
302
162
"""Start a new checker subprocess if one is not running.
303
163
If a checker already exists, leave it running and do
305
# The reason for not killing a running checker is that if we
306
# did that, then if a checker (for some reason) started
307
# running slowly and taking more than 'interval' time, the
308
# client would inevitably timeout, since no checker would get
309
# a chance to run to completion. If we instead leave running
310
# checkers alone, the checker would have to take more time
311
# than 'timeout' for the client to be declared invalid, which
312
# is as it should be.
313
165
if self.checker is None:
315
# In case check_command has exactly one % operator
316
command = self.check_command % self.fqdn
318
# Escape attributes for the shell
319
escaped_attrs = dict((key, re.escape(str(val)))
321
vars(self).iteritems())
323
command = self.check_command % escaped_attrs
324
except TypeError, error:
325
logger.error(u'Could not format string "%s":'
326
u' %s', self.check_command, error)
327
return True # Try again later
329
logger.info(u"Starting checker %r for %s",
331
self.checker = subprocess.Popen(command,
334
self.checker_callback_tag = gobject.child_watch_add\
336
self.checker_callback)
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,
337
178
except subprocess.OSError, error:
338
logger.error(u"Failed to start subprocess: %s",
179
sys.stderr.write(u"Failed to start subprocess: %s\n"
340
181
# Re-run this periodically if run by gobject.timeout_add
342
183
def stop_checker(self):
343
184
"""Force the checker process, if any, to stop."""
344
if self.checker_callback_tag:
345
gobject.source_remove(self.checker_callback_tag)
346
self.checker_callback_tag = None
347
if getattr(self, "checker", None) is None:
185
if not hasattr(self, "checker") or self.checker is None:
349
logger.debug("Stopping checker for %(name)s", vars(self))
351
os.kill(self.checker.pid, signal.SIGTERM)
353
#if self.checker.poll() is None:
354
# os.kill(self.checker.pid, signal.SIGKILL)
355
except OSError, error:
356
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)
358
192
self.checker = None
359
def still_valid(self):
193
def still_valid(self, now=None):
360
194
"""Has the timeout not yet passed for this client?"""
361
now = datetime.datetime.now()
362
if self.last_checked_ok is None:
196
now = datetime.datetime.now()
197
if self.last_seen is None:
363
198
return now < (self.created + self.timeout)
365
return now < (self.last_checked_ok + self.timeout)
368
def peer_certificate(session):
369
"Return the peer's OpenPGP certificate as a bytestring"
370
# If not an OpenPGP certificate...
371
if gnutls.library.functions.gnutls_certificate_type_get\
372
(session._c_object) \
373
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
374
# ...do the normal thing
375
return session.peer_certificate
376
list_size = ctypes.c_uint()
377
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
378
(session._c_object, ctypes.byref(list_size))
379
if list_size.value == 0:
382
return ctypes.string_at(cert.data, cert.size)
385
def fingerprint(openpgp):
386
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
387
# New GnuTLS "datum" with the OpenPGP public key
388
datum = gnutls.library.types.gnutls_datum_t\
389
(ctypes.cast(ctypes.c_char_p(openpgp),
390
ctypes.POINTER(ctypes.c_ubyte)),
391
ctypes.c_uint(len(openpgp)))
392
# New empty GnuTLS certificate
393
crt = gnutls.library.types.gnutls_openpgp_crt_t()
394
gnutls.library.functions.gnutls_openpgp_crt_init\
396
# Import the OpenPGP public key into the certificate
397
gnutls.library.functions.gnutls_openpgp_crt_import\
398
(crt, ctypes.byref(datum),
399
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
400
# New buffer for the fingerprint
401
buffer = ctypes.create_string_buffer(20)
402
buffer_length = ctypes.c_size_t()
403
# Get the fingerprint from the certificate into the buffer
404
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
405
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
406
# Deinit the certificate
407
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
408
# Convert the buffer to a Python bytestring
409
fpr = ctypes.string_at(buffer, buffer_length.value)
410
# Convert the bytestring to hexadecimal notation
411
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
200
return now < (self.last_seen + self.timeout)
415
203
class tcp_handler(SocketServer.BaseRequestHandler, object):
416
204
"""A TCP request handler class.
417
205
Instantiated by IPv6_TCPServer for each request to handle it.
418
206
Note: This will run in its own forked process."""
420
207
def handle(self):
421
logger.info(u"TCP connection from: %s",
422
unicode(self.client_address))
423
session = gnutls.connection.ClientSession\
424
(self.request, gnutls.connection.X509Credentials())
426
line = self.request.makefile().readline()
427
logger.debug(u"Protocol version: %r", line)
429
if int(line.strip().split()[0]) > 1:
431
except (ValueError, IndexError, RuntimeError), error:
432
logger.error(u"Unknown protocol version: %s", error)
435
# Note: gnutls.connection.X509Credentials is really a generic
436
# GnuTLS certificate credentials object so long as no X.509
437
# keys are added to it. Therefore, we can use it here despite
438
# using OpenPGP certificates.
440
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
441
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
443
priority = "NORMAL" # Fallback default, since this
445
if self.server.settings["priority"]:
446
priority = self.server.settings["priority"]
447
gnutls.library.functions.gnutls_priority_set_direct\
448
(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,
451
216
session.handshake()
452
217
except gnutls.errors.GNUTLSError, error:
453
logger.warning(u"Handshake failed: %s", error)
218
#sys.stderr.write(u"Handshake failed: %s\n" % error)
454
219
# Do not run session.bye() here: the session is not
455
220
# established. Just abandon the request.
222
#if session.peer_certificate:
223
# print "DN:", session.peer_certificate.subject
458
fpr = fingerprint(peer_certificate(session))
459
except (TypeError, gnutls.errors.GNUTLSError), error:
460
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)
463
logger.debug(u"Fingerprint: %s", fpr)
465
for c in self.server.clients:
466
if c.fingerprint == fpr:
232
if c.dn == session.peer_certificate.subject:
470
logger.warning(u"Client not found for fingerprint: %s",
474
235
# Have to check if client.still_valid(), since it is possible
475
236
# that the client timed out while establishing the GnuTLS
477
if not client.still_valid():
478
logger.warning(u"Client %(name)s is invalid",
483
while sent_size < len(client.secret):
484
sent = session.send(client.secret[sent_size:])
485
logger.debug(u"Sent: %d, remaining: %d",
486
sent, len(client.secret)
487
- (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")
492
252
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
493
253
"""IPv6 TCP server. Accepts 'None' as address and/or port.
495
settings: Server settings
255
options: Command line options
496
256
clients: Set() of Client objects
257
credentials: GnuTLS X.509 credentials
498
259
address_family = socket.AF_INET6
499
260
def __init__(self, *args, **kwargs):
500
if "settings" in kwargs:
501
self.settings = kwargs["settings"]
502
del kwargs["settings"]
261
if "options" in kwargs:
262
self.options = kwargs["options"]
263
del kwargs["options"]
503
264
if "clients" in kwargs:
504
265
self.clients = kwargs["clients"]
505
266
del kwargs["clients"]
267
if "credentials" in kwargs:
268
self.credentials = kwargs["credentials"]
269
del kwargs["credentials"]
506
270
return super(type(self), self).__init__(*args, **kwargs)
507
271
def server_bind(self):
508
272
"""This overrides the normal server_bind() function
509
273
to bind to an interface if one was specified, and also NOT to
510
274
bind to an address or port if they were not specified."""
511
if self.settings["interface"]:
512
# 25 is from /usr/include/asm-i486/socket.h
513
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
515
280
self.socket.setsockopt(socket.SOL_SOCKET,
517
self.settings["interface"])
281
socket.SO_BINDTODEVICE,
282
self.options.interface)
518
283
except socket.error, error:
519
284
if error[0] == errno.EPERM:
520
logger.error(u"No permission to"
521
u" bind to interface %s",
522
self.settings["interface"])
285
sys.stderr.write(u"Warning: No permission to bind to interface %s\n"
286
% self.options.interface)
525
289
# Only bind(2) the socket if we really need to.
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:
571
369
def server_state_changed(state):
572
"""Derived from the Avahi example code"""
370
"""From the Avahi server example code"""
573
371
if state == avahi.SERVER_COLLISION:
574
logger.error(u"Server name collision")
372
print "WARNING: Server name collision"
576
374
elif state == avahi.SERVER_RUNNING:
580
378
def entry_group_state_changed(state, error):
581
"""Derived from the Avahi example code"""
582
logger.debug(u"state change: %i", state)
379
"""From the Avahi server example code"""
380
global serviceName, server, rename_count
382
# print "state change: %i" % state
584
384
if state == avahi.ENTRY_GROUP_ESTABLISHED:
585
logger.debug(u"Service established.")
386
# print "Service established."
586
387
elif state == avahi.ENTRY_GROUP_COLLISION:
587
logger.warning(u"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
589
399
elif state == avahi.ENTRY_GROUP_FAILURE:
590
logger.critical(u"Error in group state changed %s",
592
raise AvahiGroupError("State changed: %s", str(error))
400
print "Error in group state changed", error
594
405
def if_nametoindex(interface):
595
"""Call the C function if_nametoindex(), or equivalent"""
596
global if_nametoindex
406
"""Call the C function if_nametoindex()"""
598
if "ctypes.util" not in sys.modules:
600
if_nametoindex = ctypes.cdll.LoadLibrary\
601
(ctypes.util.find_library("c")).if_nametoindex
602
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):
603
413
if "struct" not in sys.modules:
605
415
if "fcntl" not in sys.modules:
607
def if_nametoindex(interface):
608
"Get an interface index the hard way, i.e. using fcntl()"
609
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
611
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
612
struct.pack("16s16x", interface))
614
interface_index = struct.unpack("I", ifreq[16:20])[0]
615
return interface_index
616
return if_nametoindex(interface)
619
def daemon(nochdir, noclose):
620
"""See daemon(3). Standard BSD Unix function.
621
This should really exist as os.daemon, but it doesn't (yet)."""
630
# Close all standard open file descriptors
631
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
632
if not stat.S_ISCHR(os.fstat(null).st_mode):
633
raise OSError(errno.ENODEV,
634
"/dev/null not a character device")
635
os.dup2(null, sys.stdin.fileno())
636
os.dup2(null, sys.stdout.fileno())
637
os.dup2(null, sys.stderr.fileno())
643
global main_loop_started
644
main_loop_started = False
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__':
646
427
parser = OptionParser()
647
428
parser.add_option("-i", "--interface", type="string",
648
metavar="IF", help="Bind to interface IF")
649
parser.add_option("-a", "--address", type="string",
650
help="Address to listen for requests on")
651
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,
652
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")
653
451
parser.add_option("--check", action="store_true", default=False,
654
452
help="Run self-test")
655
parser.add_option("--debug", action="store_true", default=False,
656
help="Debug mode; run in foreground and log to"
658
parser.add_option("--priority", type="string", help="GnuTLS"
659
" priority string (see GnuTLS documentation)")
660
parser.add_option("--servicename", type="string", metavar="NAME",
661
help="Zeroconf service name")
662
parser.add_option("--configdir", type="string",
663
default="/etc/mandos", metavar="DIR",
664
help="Directory to search for configuration"
666
453
(options, args) = parser.parse_args()
668
455
if options.check:
670
457
doctest.testmod()
673
# Default values for config file for server-global settings
674
server_defaults = { "interface": "",
679
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
680
"servicename": "Mandos",
683
# Parse config file for server-global settings
684
server_config = ConfigParser.SafeConfigParser(server_defaults)
686
server_config.read(os.path.join(options.configdir, "server.conf"))
687
server_section = "server"
688
# Convert the SafeConfigParser object to a dict
689
server_settings = dict(server_config.items(server_section))
690
# Use getboolean on the boolean config option
691
server_settings["debug"] = server_config.getboolean\
692
(server_section, "debug")
695
# Override the settings from the config file with command line
697
for option in ("interface", "address", "port", "debug",
698
"priority", "servicename", "configdir"):
699
value = getattr(options, option)
700
if value is not None:
701
server_settings[option] = value
703
# Now we have our good server settings in "server_settings"
705
# Parse config file with clients
706
client_defaults = { "timeout": "1h",
708
"checker": "fping -q -- %%(fqdn)s",
710
client_config = ConfigParser.SafeConfigParser(client_defaults)
711
client_config.read(os.path.join(server_settings["configdir"],
715
service = AvahiService(name = server_settings["servicename"],
716
type = "_mandos._tcp", );
717
if server_settings["interface"]:
718
service.interface = if_nametoindex(server_settings["interface"])
723
# 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
724
483
DBusGMainLoop(set_as_default=True )
725
484
main_loop = gobject.MainLoop()
726
485
bus = dbus.SystemBus()
729
488
avahi.DBUS_INTERFACE_SERVER )
730
489
# End of Avahi example code
732
debug = server_settings["debug"]
735
console = logging.StreamHandler()
736
# console.setLevel(logging.DEBUG)
737
console.setFormatter(logging.Formatter\
738
('%(levelname)s: %(message)s'))
739
logger.addHandler(console)
743
492
def remove_from_clients(client):
744
493
clients.remove(client)
746
logger.critical(u"No clients left, exiting")
495
print "No clients left, exiting"
749
clients.update(Set(Client(name = section,
498
clients.update(Set(Client(name=section, options=options,
750
499
stop_hook = remove_from_clients,
752
= dict(client_config.items(section)))
500
**(dict(client_config\
753
502
for section in client_config.sections()))
759
"Cleanup function; run on exit"
761
# From the Avahi example code
762
if not group is None:
765
# End of Avahi example code
768
client = clients.pop()
769
client.stop_hook = None
772
atexit.register(cleanup)
775
signal.signal(signal.SIGINT, signal.SIG_IGN)
776
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
777
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
779
503
for client in clients:
782
tcp_server = IPv6_TCPServer((server_settings["address"],
783
server_settings["port"]),
506
tcp_server = IPv6_TCPServer((None, options.port),
785
settings=server_settings,
787
# Find out what port we got
788
service.port = tcp_server.socket.getsockname()[1]
789
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
790
u" scope_id %d" % tcp_server.socket.getsockname())
792
#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:],
795
# From the Avahi example code
796
server.connect_to_signal("StateChanged", server_state_changed)
798
server_state_changed(server.GetState())
799
except dbus.exceptions.DBusException, error:
800
logger.critical(u"DBusException: %s", error)
802
# End of Avahi example code
804
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
805
lambda *args, **kwargs:
806
tcp_server.handle_request\
807
(*args[2:], **kwargs) or True)
809
logger.debug("Starting main loop")
810
main_loop_started = True
812
except AvahiError, error:
813
logger.critical(u"AvahiError: %s" + unicode(error))
815
529
except KeyboardInterrupt:
819
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