51
19
from sets import Set
56
import logging.handlers
61
25
from dbus.mainloop.glib import DBusGMainLoop
66
logger = logging.Logger('mandos')
67
syslogger = logging.handlers.SysLogHandler\
68
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
70
syslogger.setFormatter(logging.Formatter\
71
('Mandos: %(levelname)s: %(message)s'))
72
logger.addHandler(syslogger)
75
class AvahiError(Exception):
76
def __init__(self, value):
79
return repr(self.value)
81
class AvahiServiceError(AvahiError):
84
class AvahiGroupError(AvahiError):
88
class AvahiService(object):
89
"""An Avahi (Zeroconf) service.
91
interface: integer; avahi.IF_UNSPEC or an interface index.
92
Used to optionally bind to the specified interface.
93
name: string; Example: 'Mandos'
94
type: string; Example: '_mandos._tcp'.
95
See <http://www.dns-sd.org/ServiceTypes.html>
96
port: integer; what port to announce
97
TXT: list of strings; TXT record for the service
98
domain: string; Domain to publish on, default to .local if empty.
99
host: string; Host to publish records for, default is localhost
100
max_renames: integer; maximum number of renames
101
rename_count: integer; counter so we only rename after collisions
102
a sensible number of times
104
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
105
type = None, port = None, TXT = None, domain = "",
106
host = "", max_renames = 32768):
107
self.interface = interface
117
self.rename_count = 0
119
"""Derived from the Avahi example code"""
120
if self.rename_count >= self.max_renames:
121
logger.critical(u"No suitable service name found after %i"
122
u" retries, exiting.", rename_count)
123
raise AvahiServiceError("Too many renames")
124
name = server.GetAlternativeServiceName(name)
125
logger.error(u"Changing name to %r ...", name)
126
syslogger.setFormatter(logging.Formatter\
127
('Mandos (%s): %%(levelname)s:'
128
' %%(message)s' % name))
131
self.rename_count += 1
133
"""Derived from the Avahi example code"""
134
if group is not None:
137
"""Derived from the Avahi example code"""
140
group = dbus.Interface\
141
(bus.get_object(avahi.DBUS_NAME,
142
server.EntryGroupNew()),
143
avahi.DBUS_INTERFACE_ENTRY_GROUP)
144
group.connect_to_signal('StateChanged',
145
entry_group_state_changed)
146
logger.debug(u"Adding service '%s' of type '%s' ...",
147
service.name, service.type)
149
self.interface, # interface
150
avahi.PROTO_INET6, # protocol
151
dbus.UInt32(0), # flags
152
self.name, self.type,
153
self.domain, self.host,
154
dbus.UInt16(self.port),
155
avahi.string_array_to_txt_array(self.TXT))
158
# From the Avahi example code:
159
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
160
41
# End of Avahi example code
163
44
class Client(object):
164
45
"""A representation of a client host served by this server.
166
name: string; from the config file, used in log messages
167
fingerprint: string (40 or 32 hexadecimal digits); used to
168
uniquely identify the client
169
secret: bytestring; sent verbatim (over TLS) to client
170
host: string; available for use by the checker command
171
created: datetime.datetime(); object creation, not client host
172
last_checked_ok: datetime.datetime() or None if not yet checked OK
173
timeout: datetime.timedelta(); How long from last_checked_ok
174
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
175
53
interval: datetime.timedelta(); How often to start a new checker
54
timeout_milliseconds: Used by gobject.timeout_add()
55
interval_milliseconds: - '' -
176
56
stop_hook: If set, called by stop() as stop_hook(self)
177
57
checker: subprocess.Popen(); a running checker process used
178
58
to see if the client lives.
179
'None' if no process is running.
59
Is None if no process is running.
180
60
checker_initiator_tag: a gobject event source tag, or None
181
61
stop_initiator_tag: - '' -
182
62
checker_callback_tag: - '' -
183
checker_command: string; External command which is run to check if
184
client lives. %() expansions are done at
185
runtime with vars(self) as dict, so that for
186
instance %(name)s can be used in the command.
188
_timeout: Real variable for 'timeout'
189
_interval: Real variable for 'interval'
190
_timeout_milliseconds: Used when calling gobject.timeout_add()
191
_interval_milliseconds: - '' -
193
def _set_timeout(self, timeout):
194
"Setter function for 'timeout' attribute"
195
self._timeout = timeout
196
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
197
93
* 24 * 60 * 60 * 1000)
198
+ (self.timeout.seconds * 1000)
199
+ (self.timeout.microseconds
94
+ (self.interval.seconds * 1000)
95
+ (self.interval.microseconds
201
timeout = property(lambda self: self._timeout,
204
def _set_interval(self, interval):
205
"Setter function for 'interval' attribute"
206
self._interval = interval
207
self._interval_milliseconds = ((self.interval.days
208
* 24 * 60 * 60 * 1000)
209
+ (self.interval.seconds
211
+ (self.interval.microseconds
213
interval = property(lambda self: self._interval,
216
def __init__(self, name = None, stop_hook=None, config={}):
217
"""Note: the 'checker' key in 'config' sets the
218
'checker_command' attribute and *not* the 'checker'
221
logger.debug(u"Creating client %r", self.name)
222
# Uppercase and remove spaces from fingerprint for later
223
# comparison purposes with return value from the fingerprint()
225
self.fingerprint = config["fingerprint"].upper()\
227
logger.debug(u" Fingerprint: %s", self.fingerprint)
228
if "secret" in config:
229
self.secret = config["secret"].decode(u"base64")
230
elif "secfile" in config:
231
sf = open(config["secfile"])
232
self.secret = sf.read()
235
raise TypeError(u"No secret or secfile for client %s"
237
self.host = config.get("host", "")
238
self.created = datetime.datetime.now()
239
self.last_checked_ok = None
240
self.timeout = string_to_delta(config["timeout"])
241
self.interval = string_to_delta(config["interval"])
242
97
self.stop_hook = stop_hook
243
98
self.checker = None
244
99
self.checker_initiator_tag = None
245
100
self.stop_initiator_tag = None
246
101
self.checker_callback_tag = None
247
self.check_command = config["checker"]
249
"""Start this client's checker and timeout hooks"""
103
"""Start this clients checker and timeout hooks"""
250
104
# Schedule a new checker to be started an 'interval' from now,
251
105
# and every interval from then on.
252
self.checker_initiator_tag = gobject.timeout_add\
253
(self._interval_milliseconds,
106
self.checker_initiator_tag = gobject.\
107
timeout_add(self.interval_milliseconds,
255
109
# Also start a new checker *right now*.
256
110
self.start_checker()
257
111
# Schedule a stop() when 'timeout' has passed
258
self.stop_initiator_tag = gobject.timeout_add\
259
(self._timeout_milliseconds,
112
self.stop_initiator_tag = gobject.\
113
timeout_add(self.timeout_milliseconds,
262
116
"""Stop this client.
263
The possibility that a client might be restarted is left open,
264
but not currently used."""
265
# If this client doesn't have a secret, it is already stopped.
266
if hasattr(self, "secret") and self.secret:
267
logger.info(u"Stopping client %s", self.name)
271
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:
272
122
gobject.source_remove(self.stop_initiator_tag)
273
123
self.stop_initiator_tag = None
274
if getattr(self, "checker_initiator_tag", False):
124
if self.checker_initiator_tag:
275
125
gobject.source_remove(self.checker_initiator_tag)
276
126
self.checker_initiator_tag = None
277
127
self.stop_checker()
280
130
# Do not run this again if called by a gobject.timeout_add
282
132
def __del__(self):
283
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
285
143
def checker_callback(self, pid, condition):
286
144
"""The checker has completed, so take appropriate actions."""
287
145
now = datetime.datetime.now()
288
self.checker_callback_tag = None
290
146
if os.WIFEXITED(condition) \
291
147
and (os.WEXITSTATUS(condition) == 0):
292
logger.info(u"Checker for %(name)s succeeded",
294
self.last_checked_ok = now
148
#print "Checker for %(name)s succeeded" % vars(self)
295
150
gobject.source_remove(self.stop_initiator_tag)
296
self.stop_initiator_tag = gobject.timeout_add\
297
(self._timeout_milliseconds,
299
elif not os.WIFEXITED(condition):
300
logger.warning(u"Checker for %(name)s crashed?",
303
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
305
161
def start_checker(self):
306
162
"""Start a new checker subprocess if one is not running.
307
163
If a checker already exists, leave it running and do
309
# The reason for not killing a running checker is that if we
310
# did that, then if a checker (for some reason) started
311
# running slowly and taking more than 'interval' time, the
312
# client would inevitably timeout, since no checker would get
313
# a chance to run to completion. If we instead leave running
314
# checkers alone, the checker would have to take more time
315
# than 'timeout' for the client to be declared invalid, which
316
# is as it should be.
317
165
if self.checker is None:
319
# In case check_command has exactly one % operator
320
command = self.check_command % self.host
322
# Escape attributes for the shell
323
escaped_attrs = dict((key, re.escape(str(val)))
325
vars(self).iteritems())
327
command = self.check_command % escaped_attrs
328
except TypeError, error:
329
logger.error(u'Could not format string "%s":'
330
u' %s', self.check_command, error)
331
return True # Try again later
333
logger.info(u"Starting checker %r for %s",
335
self.checker = subprocess.Popen(command,
338
self.checker_callback_tag = gobject.child_watch_add\
340
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,
341
178
except subprocess.OSError, error:
342
logger.error(u"Failed to start subprocess: %s",
179
sys.stderr.write(u"Failed to start subprocess: %s\n"
344
181
# Re-run this periodically if run by gobject.timeout_add
346
183
def stop_checker(self):
347
184
"""Force the checker process, if any, to stop."""
348
if self.checker_callback_tag:
349
gobject.source_remove(self.checker_callback_tag)
350
self.checker_callback_tag = None
351
if getattr(self, "checker", None) is None:
185
if not hasattr(self, "checker") or self.checker is None:
353
logger.debug(u"Stopping checker for %(name)s", vars(self))
355
os.kill(self.checker.pid, signal.SIGTERM)
357
#if self.checker.poll() is None:
358
# os.kill(self.checker.pid, signal.SIGKILL)
359
except OSError, error:
360
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)
362
192
self.checker = None
363
def still_valid(self):
193
def still_valid(self, now=None):
364
194
"""Has the timeout not yet passed for this client?"""
365
now = datetime.datetime.now()
366
if self.last_checked_ok is None:
196
now = datetime.datetime.now()
197
if self.last_seen is None:
367
198
return now < (self.created + self.timeout)
369
return now < (self.last_checked_ok + self.timeout)
372
def peer_certificate(session):
373
"Return the peer's OpenPGP certificate as a bytestring"
374
# If not an OpenPGP certificate...
375
if gnutls.library.functions.gnutls_certificate_type_get\
376
(session._c_object) \
377
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
378
# ...do the normal thing
379
return session.peer_certificate
380
list_size = ctypes.c_uint()
381
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
382
(session._c_object, ctypes.byref(list_size))
383
if list_size.value == 0:
386
return ctypes.string_at(cert.data, cert.size)
389
def fingerprint(openpgp):
390
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
391
# New GnuTLS "datum" with the OpenPGP public key
392
datum = gnutls.library.types.gnutls_datum_t\
393
(ctypes.cast(ctypes.c_char_p(openpgp),
394
ctypes.POINTER(ctypes.c_ubyte)),
395
ctypes.c_uint(len(openpgp)))
396
# New empty GnuTLS certificate
397
crt = gnutls.library.types.gnutls_openpgp_crt_t()
398
gnutls.library.functions.gnutls_openpgp_crt_init\
400
# Import the OpenPGP public key into the certificate
401
gnutls.library.functions.gnutls_openpgp_crt_import\
402
(crt, ctypes.byref(datum),
403
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
404
# New buffer for the fingerprint
405
buffer = ctypes.create_string_buffer(20)
406
buffer_length = ctypes.c_size_t()
407
# Get the fingerprint from the certificate into the buffer
408
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
409
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
410
# Deinit the certificate
411
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
412
# Convert the buffer to a Python bytestring
413
fpr = ctypes.string_at(buffer, buffer_length.value)
414
# Convert the bytestring to hexadecimal notation
415
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
200
return now < (self.last_seen + self.timeout)
419
203
class tcp_handler(SocketServer.BaseRequestHandler, object):
420
204
"""A TCP request handler class.
421
205
Instantiated by IPv6_TCPServer for each request to handle it.
422
206
Note: This will run in its own forked process."""
424
207
def handle(self):
425
logger.info(u"TCP connection from: %s",
426
unicode(self.client_address))
427
session = gnutls.connection.ClientSession\
428
(self.request, gnutls.connection.X509Credentials())
430
line = self.request.makefile().readline()
431
logger.debug(u"Protocol version: %r", line)
433
if int(line.strip().split()[0]) > 1:
435
except (ValueError, IndexError, RuntimeError), error:
436
logger.error(u"Unknown protocol version: %s", error)
439
# Note: gnutls.connection.X509Credentials is really a generic
440
# GnuTLS certificate credentials object so long as no X.509
441
# keys are added to it. Therefore, we can use it here despite
442
# using OpenPGP certificates.
444
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
445
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
447
priority = "NORMAL" # Fallback default, since this
449
if self.server.settings["priority"]:
450
priority = self.server.settings["priority"]
451
gnutls.library.functions.gnutls_priority_set_direct\
452
(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,
455
216
session.handshake()
456
217
except gnutls.errors.GNUTLSError, error:
457
logger.warning(u"Handshake failed: %s", error)
218
#sys.stderr.write(u"Handshake failed: %s\n" % error)
458
219
# Do not run session.bye() here: the session is not
459
220
# established. Just abandon the request.
222
#if session.peer_certificate:
223
# print "DN:", session.peer_certificate.subject
462
fpr = fingerprint(peer_certificate(session))
463
except (TypeError, gnutls.errors.GNUTLSError), error:
464
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)
467
logger.debug(u"Fingerprint: %s", fpr)
469
for c in self.server.clients:
470
if c.fingerprint == fpr:
232
if c.dn == session.peer_certificate.subject:
474
logger.warning(u"Client not found for fingerprint: %s",
478
235
# Have to check if client.still_valid(), since it is possible
479
236
# that the client timed out while establishing the GnuTLS
481
if not client.still_valid():
482
logger.warning(u"Client %(name)s is invalid",
487
while sent_size < len(client.secret):
488
sent = session.send(client.secret[sent_size:])
489
logger.debug(u"Sent: %d, remaining: %d",
490
sent, len(client.secret)
491
- (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")
496
252
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
497
253
"""IPv6 TCP server. Accepts 'None' as address and/or port.
499
settings: Server settings
255
options: Command line options
500
256
clients: Set() of Client objects
257
credentials: GnuTLS X.509 credentials
502
259
address_family = socket.AF_INET6
503
260
def __init__(self, *args, **kwargs):
504
if "settings" in kwargs:
505
self.settings = kwargs["settings"]
506
del kwargs["settings"]
261
if "options" in kwargs:
262
self.options = kwargs["options"]
263
del kwargs["options"]
507
264
if "clients" in kwargs:
508
265
self.clients = kwargs["clients"]
509
266
del kwargs["clients"]
267
if "credentials" in kwargs:
268
self.credentials = kwargs["credentials"]
269
del kwargs["credentials"]
510
270
return super(type(self), self).__init__(*args, **kwargs)
511
271
def server_bind(self):
512
272
"""This overrides the normal server_bind() function
513
273
to bind to an interface if one was specified, and also NOT to
514
274
bind to an address or port if they were not specified."""
515
if self.settings["interface"]:
516
# 25 is from /usr/include/asm-i486/socket.h
517
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
519
280
self.socket.setsockopt(socket.SOL_SOCKET,
521
self.settings["interface"])
281
socket.SO_BINDTODEVICE,
282
self.options.interface)
522
283
except socket.error, error:
523
284
if error[0] == errno.EPERM:
524
logger.error(u"No permission to"
525
u" bind to interface %s",
526
self.settings["interface"])
285
sys.stderr.write(u"Warning: No permission to bind to interface %s\n"
286
% self.options.interface)
529
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:
582
369
def server_state_changed(state):
583
"""Derived from the Avahi example code"""
370
"""From the Avahi server example code"""
584
371
if state == avahi.SERVER_COLLISION:
585
logger.error(u"Server name collision")
372
print "WARNING: Server name collision"
587
374
elif state == avahi.SERVER_RUNNING:
591
378
def entry_group_state_changed(state, error):
592
"""Derived from the Avahi example code"""
593
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
595
384
if state == avahi.ENTRY_GROUP_ESTABLISHED:
596
logger.debug(u"Service established.")
386
# print "Service established."
597
387
elif state == avahi.ENTRY_GROUP_COLLISION:
598
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
600
399
elif state == avahi.ENTRY_GROUP_FAILURE:
601
logger.critical(u"Error in group state changed %s",
603
raise AvahiGroupError("State changed: %s", str(error))
400
print "Error in group state changed", error
605
405
def if_nametoindex(interface):
606
"""Call the C function if_nametoindex(), or equivalent"""
607
global if_nametoindex
406
"""Call the C function if_nametoindex()"""
609
if "ctypes.util" not in sys.modules:
611
if_nametoindex = ctypes.cdll.LoadLibrary\
612
(ctypes.util.find_library("c")).if_nametoindex
613
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):
614
413
if "struct" not in sys.modules:
616
415
if "fcntl" not in sys.modules:
618
def if_nametoindex(interface):
619
"Get an interface index the hard way, i.e. using fcntl()"
620
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
622
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
623
struct.pack("16s16x", interface))
625
interface_index = struct.unpack("I", ifreq[16:20])[0]
626
return interface_index
627
return if_nametoindex(interface)
630
def daemon(nochdir = False, noclose = False):
631
"""See daemon(3). Standard BSD Unix function.
632
This should really exist as os.daemon, but it doesn't (yet)."""
641
# Close all standard open file descriptors
642
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
643
if not stat.S_ISCHR(os.fstat(null).st_mode):
644
raise OSError(errno.ENODEV,
645
"/dev/null not a character device")
646
os.dup2(null, sys.stdin.fileno())
647
os.dup2(null, sys.stdout.fileno())
648
os.dup2(null, sys.stderr.fileno())
654
global main_loop_started
655
main_loop_started = False
657
parser = OptionParser(version = "Mandos server %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()
658
428
parser.add_option("-i", "--interface", type="string",
659
metavar="IF", help="Bind to interface IF")
660
parser.add_option("-a", "--address", type="string",
661
help="Address to listen for requests on")
662
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,
663
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")
664
451
parser.add_option("--check", action="store_true", default=False,
665
452
help="Run self-test")
666
parser.add_option("--debug", action="store_true",
667
help="Debug mode; run in foreground and log to"
669
parser.add_option("--priority", type="string", help="GnuTLS"
670
" priority string (see GnuTLS documentation)")
671
parser.add_option("--servicename", type="string", metavar="NAME",
672
help="Zeroconf service name")
673
parser.add_option("--configdir", type="string",
674
default="/etc/mandos", metavar="DIR",
675
help="Directory to search for configuration"
677
453
(options, args) = parser.parse_args()
679
455
if options.check:
681
457
doctest.testmod()
684
# Default values for config file for server-global settings
685
server_defaults = { "interface": "",
690
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
691
"servicename": "Mandos",
694
# Parse config file for server-global settings
695
server_config = ConfigParser.SafeConfigParser(server_defaults)
697
server_config.read(os.path.join(options.configdir, "mandos.conf"))
698
server_section = "server"
699
# Convert the SafeConfigParser object to a dict
700
server_settings = dict(server_config.items(server_section))
701
# Use getboolean on the boolean config option
702
server_settings["debug"] = server_config.getboolean\
703
(server_section, "debug")
706
# Override the settings from the config file with command line
708
for option in ("interface", "address", "port", "debug",
709
"priority", "servicename", "configdir"):
710
value = getattr(options, option)
711
if value is not None:
712
server_settings[option] = value
714
# Now we have our good server settings in "server_settings"
716
debug = server_settings["debug"]
719
syslogger.setLevel(logging.WARNING)
721
if server_settings["servicename"] != "Mandos":
722
syslogger.setFormatter(logging.Formatter\
723
('Mandos (%s): %%(levelname)s:'
725
% server_settings["servicename"]))
727
# Parse config file with clients
728
client_defaults = { "timeout": "1h",
730
"checker": "fping -q -- %%(host)s",
732
client_config = ConfigParser.SafeConfigParser(client_defaults)
733
client_config.read(os.path.join(server_settings["configdir"],
737
service = AvahiService(name = server_settings["servicename"],
738
type = "_mandos._tcp", );
739
if server_settings["interface"]:
740
service.interface = if_nametoindex(server_settings["interface"])
745
# 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
746
483
DBusGMainLoop(set_as_default=True )
747
484
main_loop = gobject.MainLoop()
748
485
bus = dbus.SystemBus()
751
488
avahi.DBUS_INTERFACE_SERVER )
752
489
# End of Avahi example code
755
console = logging.StreamHandler()
756
# console.setLevel(logging.DEBUG)
757
console.setFormatter(logging.Formatter\
758
('%(levelname)s: %(message)s'))
759
logger.addHandler(console)
763
492
def remove_from_clients(client):
764
493
clients.remove(client)
766
logger.critical(u"No clients left, exiting")
495
print "No clients left, exiting"
769
clients.update(Set(Client(name = section,
498
clients.update(Set(Client(name=section, options=options,
770
499
stop_hook = remove_from_clients,
772
= dict(client_config.items(section)))
500
**(dict(client_config\
773
502
for section in client_config.sections()))
775
logger.critical(u"No clients defined")
781
pidfilename = "/var/run/mandos/mandos.pid"
784
pidfile = open(pidfilename, "w")
785
pidfile.write(str(pid) + "\n")
789
logger.error(u"Could not write %s file with PID %d",
790
pidfilename, os.getpid())
793
"Cleanup function; run on exit"
795
# From the Avahi example code
796
if not group is None:
799
# End of Avahi example code
802
client = clients.pop()
803
client.stop_hook = None
806
atexit.register(cleanup)
809
signal.signal(signal.SIGINT, signal.SIG_IGN)
810
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
811
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
813
503
for client in clients:
816
tcp_server = IPv6_TCPServer((server_settings["address"],
817
server_settings["port"]),
506
tcp_server = IPv6_TCPServer((None, options.port),
819
settings=server_settings,
821
# Find out what port we got
822
service.port = tcp_server.socket.getsockname()[1]
823
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
824
u" scope_id %d" % tcp_server.socket.getsockname())
826
#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:],
829
# From the Avahi example code
830
server.connect_to_signal("StateChanged", server_state_changed)
832
server_state_changed(server.GetState())
833
except dbus.exceptions.DBusException, error:
834
logger.critical(u"DBusException: %s", error)
836
# End of Avahi example code
838
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
839
lambda *args, **kwargs:
840
tcp_server.handle_request\
841
(*args[2:], **kwargs) or True)
843
logger.debug(u"Starting main loop")
844
main_loop_started = True
846
except AvahiError, error:
847
logger.critical(u"AvahiError: %s" + unicode(error))
849
529
except KeyboardInterrupt:
853
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