57
57
import logging.handlers
59
from contextlib import closing
62
65
from dbus.mainloop.glib import DBusGMainLoop
65
# Brief description of the operation of this program:
67
# This server announces itself as a Zeroconf service. Connecting
68
# clients use the TLS protocol, with the unusual quirk that this
69
# server program acts as a TLS "client" while the connecting clients
70
# acts as a TLS "server". The clients (acting as a TLS "server") must
71
# supply an OpenPGP certificate, and the fingerprint of this
72
# certificate is used by this server to look up (in a list read from a
73
# file at start time) which binary blob to give the client. No other
74
# authentication or authorization is done by this server.
77
71
logger = logging.Logger('mandos')
78
syslogger = logging.handlers.SysLogHandler\
79
(facility = logging.handlers.SysLogHandler.LOG_DAEMON)
80
syslogger.setFormatter(logging.Formatter\
81
('%(levelname)s: %(message)s'))
72
syslogger = (logging.handlers.SysLogHandler
73
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
74
address = "/dev/log"))
75
syslogger.setFormatter(logging.Formatter
76
('Mandos: %(levelname)s: %(message)s'))
82
77
logger.addHandler(syslogger)
85
# This variable is used to optionally bind to a specified interface.
86
# It is a global variable to fit in with the other variables from the
88
serviceInterface = avahi.IF_UNSPEC
79
console = logging.StreamHandler()
80
console.setFormatter(logging.Formatter('%(name)s: %(levelname)s:'
82
logger.addHandler(console)
84
class AvahiError(Exception):
85
def __init__(self, value):
87
super(AvahiError, self).__init__()
89
return repr(self.value)
91
class AvahiServiceError(AvahiError):
94
class AvahiGroupError(AvahiError):
98
class AvahiService(object):
99
"""An Avahi (Zeroconf) service.
101
interface: integer; avahi.IF_UNSPEC or an interface index.
102
Used to optionally bind to the specified interface.
103
name: string; Example: 'Mandos'
104
type: string; Example: '_mandos._tcp'.
105
See <http://www.dns-sd.org/ServiceTypes.html>
106
port: integer; what port to announce
107
TXT: list of strings; TXT record for the service
108
domain: string; Domain to publish on, default to .local if empty.
109
host: string; Host to publish records for, default is localhost
110
max_renames: integer; maximum number of renames
111
rename_count: integer; counter so we only rename after collisions
112
a sensible number of times
114
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
115
servicetype = None, port = None, TXT = None,
116
domain = "", host = "", max_renames = 32768):
117
self.interface = interface
119
self.type = servicetype
121
self.TXT = TXT if TXT is not None else []
124
self.rename_count = 0
125
self.max_renames = max_renames
127
"""Derived from the Avahi example code"""
128
if self.rename_count >= self.max_renames:
129
logger.critical(u"No suitable Zeroconf service name found"
130
u" after %i retries, exiting.",
132
raise AvahiServiceError("Too many renames")
133
self.name = server.GetAlternativeServiceName(self.name)
134
logger.info(u"Changing Zeroconf service name to %r ...",
136
syslogger.setFormatter(logging.Formatter
137
('Mandos (%s): %%(levelname)s:'
138
' %%(message)s' % self.name))
141
self.rename_count += 1
143
"""Derived from the Avahi example code"""
144
if group is not None:
147
"""Derived from the Avahi example code"""
150
group = dbus.Interface(bus.get_object
152
server.EntryGroupNew()),
153
avahi.DBUS_INTERFACE_ENTRY_GROUP)
154
group.connect_to_signal('StateChanged',
155
entry_group_state_changed)
156
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
157
service.name, service.type)
159
self.interface, # interface
160
avahi.PROTO_INET6, # protocol
161
dbus.UInt32(0), # flags
162
self.name, self.type,
163
self.domain, self.host,
164
dbus.UInt16(self.port),
165
avahi.string_array_to_txt_array(self.TXT))
89
168
# From the Avahi example code:
90
serviceName = "Mandos"
91
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
92
servicePort = None # Not known at startup
93
serviceTXT = [] # TXT record for the service
94
domain = "" # Domain to publish on, default to .local
95
host = "" # Host to publish records for, default to localhost
96
group = None #our entry group
97
rename_count = 12 # Counter so we only rename after collisions a
98
# sensible number of times
169
group = None # our entry group
99
170
# End of Avahi example code
102
class Client(object):
173
class Client(dbus.service.Object):
103
174
"""A representation of a client host served by this server.
105
176
name: string; from the config file, used in log messages
106
177
fingerprint: string (40 or 32 hexadecimal digits); used to
107
178
uniquely identify the client
108
179
secret: bytestring; sent verbatim (over TLS) to client
109
fqdn: string (FQDN); available for use by the checker command
110
created: datetime.datetime()
111
last_seen: datetime.datetime() or None if not yet seen
112
timeout: datetime.timedelta(); How long from last_seen until
113
this client is invalid
180
host: string; available for use by the checker command
181
created: datetime.datetime(); (UTC) object creation
182
started: datetime.datetime(); (UTC) last started
183
last_checked_ok: datetime.datetime(); (UTC) or None
184
timeout: datetime.timedelta(); How long from last_checked_ok
185
until this client is invalid
114
186
interval: datetime.timedelta(); How often to start a new checker
115
187
stop_hook: If set, called by stop() as stop_hook(self)
116
188
checker: subprocess.Popen(); a running checker process used
117
189
to see if the client lives.
118
Is None if no process is running.
190
'None' if no process is running.
119
191
checker_initiator_tag: a gobject event source tag, or None
120
192
stop_initiator_tag: - '' -
121
193
checker_callback_tag: - '' -
122
194
checker_command: string; External command which is run to check if
123
client lives. %()s expansions are done at
195
client lives. %() expansions are done at
124
196
runtime with vars(self) as dict, so that for
125
197
instance %(name)s can be used in the command.
126
198
Private attibutes:
127
199
_timeout: Real variable for 'timeout'
128
200
_interval: Real variable for 'interval'
129
_timeout_milliseconds: Used by gobject.timeout_add()
201
_timeout_milliseconds: Used when calling gobject.timeout_add()
130
202
_interval_milliseconds: - '' -
132
204
def _set_timeout(self, timeout):
133
"Setter function for 'timeout' attribute"
205
"Setter function for the 'timeout' attribute"
134
206
self._timeout = timeout
135
207
self._timeout_milliseconds = ((self.timeout.days
136
208
* 24 * 60 * 60 * 1000)
137
209
+ (self.timeout.seconds * 1000)
138
210
+ (self.timeout.microseconds
140
timeout = property(lambda self: self._timeout,
213
self.TimeoutChanged(self._timeout_milliseconds)
214
timeout = property(lambda self: self._timeout, _set_timeout)
143
217
def _set_interval(self, interval):
144
"Setter function for 'interval' attribute"
218
"Setter function for the 'interval' attribute"
145
219
self._interval = interval
146
220
self._interval_milliseconds = ((self.interval.days
147
221
* 24 * 60 * 60 * 1000)
150
224
+ (self.interval.microseconds
152
interval = property(lambda self: self._interval,
227
self.IntervalChanged(self._interval_milliseconds)
228
interval = property(lambda self: self._interval, _set_interval)
154
229
del _set_interval
155
def __init__(self, name=None, options=None, stop_hook=None,
156
fingerprint=None, secret=None, secfile=None,
157
fqdn=None, timeout=None, interval=-1, checker=None):
158
"""Note: the 'checker' argument sets the 'checker_command'
159
attribute and not the 'checker' attribute.."""
231
def __init__(self, name = None, stop_hook=None, config=None):
232
"""Note: the 'checker' key in 'config' sets the
233
'checker_command' attribute and *not* the 'checker'
235
dbus.service.Object.__init__(self, bus,
237
% name.replace(".", "_"))
161
# Uppercase and remove spaces from fingerprint
162
# for later comparison purposes with return value of
163
# the fingerprint() function
164
self.fingerprint = fingerprint.upper().replace(u" ", u"")
166
self.secret = secret.decode(u"base64")
169
self.secret = sf.read()
172
raise RuntimeError(u"No secret or secfile for client %s"
174
self.fqdn = fqdn # string
175
self.created = datetime.datetime.now()
176
self.last_seen = None
178
self.timeout = options.timeout
180
self.timeout = string_to_delta(timeout)
182
self.interval = options.interval
184
self.interval = string_to_delta(interval)
241
logger.debug(u"Creating client %r", self.name)
242
# Uppercase and remove spaces from fingerprint for later
243
# comparison purposes with return value from the fingerprint()
245
self.fingerprint = (config["fingerprint"].upper()
247
logger.debug(u" Fingerprint: %s", self.fingerprint)
248
if "secret" in config:
249
self.secret = config["secret"].decode(u"base64")
250
elif "secfile" in config:
251
with closing(open(os.path.expanduser
253
(config["secfile"])))) as secfile:
254
self.secret = secfile.read()
256
raise TypeError(u"No secret or secfile for client %s"
258
self.host = config.get("host", "")
259
self.created = datetime.datetime.utcnow()
261
self.last_checked_ok = None
262
self.timeout = string_to_delta(config["timeout"])
263
self.interval = string_to_delta(config["interval"])
185
264
self.stop_hook = stop_hook
186
265
self.checker = None
187
266
self.checker_initiator_tag = None
188
267
self.stop_initiator_tag = None
189
268
self.checker_callback_tag = None
190
self.check_command = checker
269
self.check_command = config["checker"]
192
272
"""Start this client's checker and timeout hooks"""
273
self.started = datetime.datetime.utcnow()
193
274
# Schedule a new checker to be started an 'interval' from now,
194
275
# and every interval from then on.
195
self.checker_initiator_tag = gobject.timeout_add\
196
(self._interval_milliseconds,
276
self.checker_initiator_tag = (gobject.timeout_add
277
(self._interval_milliseconds,
198
279
# Also start a new checker *right now*.
199
280
self.start_checker()
200
281
# Schedule a stop() when 'timeout' has passed
201
self.stop_initiator_tag = gobject.timeout_add\
202
(self._timeout_milliseconds,
282
self.stop_initiator_tag = (gobject.timeout_add
283
(self._timeout_milliseconds,
286
self.StateChanged(True)
206
The possibility that this client might be restarted is left
207
open, but not currently used."""
208
# If this client doesn't have a secret, it is already stopped.
210
logger.debug(u"Stopping client %s", self.name)
289
"""Stop this client."""
290
if getattr(self, "started", None) is not None:
291
logger.info(u"Stopping client %s", self.name)
214
if hasattr(self, "stop_initiator_tag") \
215
and self.stop_initiator_tag:
294
if getattr(self, "stop_initiator_tag", False):
216
295
gobject.source_remove(self.stop_initiator_tag)
217
296
self.stop_initiator_tag = None
218
if hasattr(self, "checker_initiator_tag") \
219
and self.checker_initiator_tag:
297
if getattr(self, "checker_initiator_tag", False):
220
298
gobject.source_remove(self.checker_initiator_tag)
221
299
self.checker_initiator_tag = None
222
300
self.stop_checker()
223
301
if self.stop_hook:
224
302
self.stop_hook(self)
305
self.StateChanged(False)
225
306
# Do not run this again if called by a gobject.timeout_add
227
309
def __del__(self):
228
310
self.stop_hook = None
230
313
def checker_callback(self, pid, condition):
231
314
"""The checker has completed, so take appropriate actions."""
232
now = datetime.datetime.now()
233
315
self.checker_callback_tag = None
234
316
self.checker = None
235
if os.WIFEXITED(condition) \
236
and (os.WEXITSTATUS(condition) == 0):
237
logger.debug(u"Checker for %(name)s succeeded",
240
gobject.source_remove(self.stop_initiator_tag)
241
self.stop_initiator_tag = gobject.timeout_add\
242
(self._timeout_milliseconds,
317
if (os.WIFEXITED(condition)
318
and (os.WEXITSTATUS(condition) == 0)):
319
logger.info(u"Checker for %(name)s succeeded",
322
self.CheckerCompleted(True)
244
324
elif not os.WIFEXITED(condition):
245
325
logger.warning(u"Checker for %(name)s crashed?",
328
self.CheckerCompleted(False)
248
logger.debug(u"Checker for %(name)s failed",
330
logger.info(u"Checker for %(name)s failed",
333
self.CheckerCompleted(False)
335
def bump_timeout(self):
336
"""Bump up the timeout for this client.
337
This should only be called when the client has been seen,
340
self.last_checked_ok = datetime.datetime.utcnow()
341
gobject.source_remove(self.stop_initiator_tag)
342
self.stop_initiator_tag = (gobject.timeout_add
343
(self._timeout_milliseconds,
250
346
def start_checker(self):
251
347
"""Start a new checker subprocess if one is not running.
252
348
If a checker already exists, leave it running and do
261
357
# is as it should be.
262
358
if self.checker is None:
264
command = self.check_command % self.fqdn
360
# In case check_command has exactly one % operator
361
command = self.check_command % self.host
265
362
except TypeError:
363
# Escape attributes for the shell
266
364
escaped_attrs = dict((key, re.escape(str(val)))
268
366
vars(self).iteritems())
270
368
command = self.check_command % escaped_attrs
271
369
except TypeError, error:
272
logger.critical(u'Could not format string "%s":'
273
u' %s', self.check_command, error)
370
logger.error(u'Could not format string "%s":'
371
u' %s', self.check_command, error)
274
372
return True # Try again later
276
logger.debug(u"Starting checker %r for %s",
278
self.checker = subprocess.\
280
close_fds=True, shell=True,
282
self.checker_callback_tag = gobject.child_watch_add\
284
self.checker_callback)
285
except subprocess.OSError, error:
374
logger.info(u"Starting checker %r for %s",
376
# We don't need to redirect stdout and stderr, since
377
# in normal mode, that is already done by daemon(),
378
# and in debug mode we don't want to. (Stdin is
379
# always replaced by /dev/null.)
380
self.checker = subprocess.Popen(command,
383
self.checker_callback_tag = (gobject.child_watch_add
385
self.checker_callback))
387
self.CheckerStarted(command)
388
except OSError, error:
286
389
logger.error(u"Failed to start subprocess: %s",
288
391
# Re-run this periodically if run by gobject.timeout_add
290
394
def stop_checker(self):
291
395
"""Force the checker process, if any, to stop."""
292
396
if self.checker_callback_tag:
293
397
gobject.source_remove(self.checker_callback_tag)
294
398
self.checker_callback_tag = None
295
if not hasattr(self, "checker") or self.checker is None:
399
if getattr(self, "checker", None) is None:
297
logger.debug("Stopping checker for %(name)s", vars(self))
401
logger.debug(u"Stopping checker for %(name)s", vars(self))
299
403
os.kill(self.checker.pid, signal.SIGTERM)
301
405
#if self.checker.poll() is None:
302
406
# os.kill(self.checker.pid, signal.SIGKILL)
303
407
except OSError, error:
304
if error.errno != errno.ESRCH:
408
if error.errno != errno.ESRCH: # No such process
306
410
self.checker = None
307
def still_valid(self, now=None):
412
def still_valid(self):
308
413
"""Has the timeout not yet passed for this client?"""
310
now = datetime.datetime.now()
311
if self.last_seen is None:
416
now = datetime.datetime.utcnow()
417
if self.last_checked_ok is None:
312
418
return now < (self.created + self.timeout)
314
return now < (self.last_seen + self.timeout)
420
return now < (self.last_checked_ok + self.timeout)
422
## D-Bus methods & signals
423
_interface = u"org.mandos_system.Mandos.Client"
425
def _datetime_to_dbus_struct(dt):
426
return dbus.Struct(dt.year, dt.month, dt.day, dt.hour,
427
dt.minute, dt.second, dt.microsecond,
430
# BumpTimeout - method
431
BumpTimeout = dbus.service.method(_interface)(bump_timeout)
432
BumpTimeout.__name__ = "BumpTimeout"
434
# IntervalChanged - signal
435
@dbus.service.signal(_interface, signature="t")
436
def IntervalChanged(self, t):
440
# CheckerCompleted - signal
441
@dbus.service.signal(_interface, signature="b")
442
def CheckerCompleted(self, success):
446
# CheckerIsRunning - method
447
@dbus.service.method(_interface, out_signature="b")
448
def CheckerIsRunning(self):
449
"D-Bus getter method"
450
return self.checker is not None
452
# CheckerStarted - signal
453
@dbus.service.signal(_interface, signature="s")
454
def CheckerStarted(self, command):
458
# GetChecker - method
459
@dbus.service.method(_interface, out_signature="s")
460
def GetChecker(self):
461
"D-Bus getter method"
462
return self.checker_command
464
# GetCreated - method
465
@dbus.service.method(_interface, out_signature="(nyyyyyu)")
466
def GetCreated(self):
467
"D-Bus getter method"
468
return datetime_to_dbus_struct(self.created)
470
# GetFingerprint - method
471
@dbus.service.method(_interface, out_signature="s")
472
def GetFingerprint(self):
473
"D-Bus getter method"
474
return self.fingerprint
477
@dbus.service.method(_interface, out_signature="s")
479
"D-Bus getter method"
482
# GetInterval - method
483
@dbus.service.method(_interface, out_signature="t")
484
def GetInterval(self):
485
"D-Bus getter method"
486
return self._interval_milliseconds
489
@dbus.service.method(_interface, out_signature="s")
491
"D-Bus getter method"
494
# GetStarted - method
495
@dbus.service.method(_interface, out_signature="(nyyyyyu)")
496
def GetStarted(self):
497
"D-Bus getter method"
498
if self.started is not None:
499
return datetime_to_dbus_struct(self.started)
501
return dbus.Struct(0, 0, 0, 0, 0, 0, 0,
504
# GetTimeout - method
505
@dbus.service.method(_interface, out_signature="t")
506
def GetTimeout(self):
507
"D-Bus getter method"
508
return self._timeout_milliseconds
510
# SetChecker - method
511
@dbus.service.method(_interface, in_signature="s")
512
def SetChecker(self, checker):
513
"D-Bus setter method"
514
self.checker_command = checker
517
@dbus.service.method(_interface, in_signature="s")
518
def SetHost(self, host):
519
"D-Bus setter method"
522
# SetInterval - method
523
@dbus.service.method(_interface, in_signature="t")
524
def SetInterval(self, milliseconds):
525
self.interval = datetime.timdeelta(0, 0, 0, milliseconds)
527
# SetTimeout - method
528
@dbus.service.method(_interface, in_signature="t")
529
def SetTimeout(self, milliseconds):
530
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
533
@dbus.service.method(_interface, in_signature="ay",
535
def SetSecret(self, secret):
536
"D-Bus setter method"
537
self.secret = str(secret)
540
Start = dbus.service.method(_interface)(start)
541
Start.__name__ = "Start"
543
# StartChecker - method
544
StartChecker = dbus.service.method(_interface)(start_checker)
545
StartChecker.__name__ = "StartChecker"
547
# StateChanged - signal
548
@dbus.service.signal(_interface, signature="b")
549
def StateChanged(self, started):
553
# StillValid - method
554
StillValid = (dbus.service.method(_interface, out_signature="b")
556
StillValid.__name__ = "StillValid"
559
Stop = dbus.service.method(_interface)(stop)
560
Stop.__name__ = "Stop"
562
# StopChecker - method
563
StopChecker = dbus.service.method(_interface)(stop_checker)
564
StopChecker.__name__ = "StopChecker"
566
# TimeoutChanged - signal
567
@dbus.service.signal(_interface, signature="t")
568
def TimeoutChanged(self, t):
572
del _datetime_to_dbus_struct
317
576
def peer_certificate(session):
318
577
"Return the peer's OpenPGP certificate as a bytestring"
319
578
# If not an OpenPGP certificate...
320
if gnutls.library.functions.gnutls_certificate_type_get\
321
(session._c_object) \
322
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
579
if (gnutls.library.functions
580
.gnutls_certificate_type_get(session._c_object)
581
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
323
582
# ...do the normal thing
324
583
return session.peer_certificate
325
584
list_size = ctypes.c_uint()
326
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
327
(session._c_object, ctypes.byref(list_size))
585
cert_list = (gnutls.library.functions
586
.gnutls_certificate_get_peers
587
(session._c_object, ctypes.byref(list_size)))
328
588
if list_size.value == 0:
330
590
cert = cert_list[0]
334
594
def fingerprint(openpgp):
335
595
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
596
# New GnuTLS "datum" with the OpenPGP public key
597
datum = (gnutls.library.types
598
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
601
ctypes.c_uint(len(openpgp))))
336
602
# New empty GnuTLS certificate
337
603
crt = gnutls.library.types.gnutls_openpgp_crt_t()
338
gnutls.library.functions.gnutls_openpgp_crt_init\
340
# New GnuTLS "datum" with the OpenPGP public key
341
datum = gnutls.library.types.gnutls_datum_t\
342
(ctypes.cast(ctypes.c_char_p(openpgp),
343
ctypes.POINTER(ctypes.c_ubyte)),
344
ctypes.c_uint(len(openpgp)))
604
(gnutls.library.functions
605
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
345
606
# Import the OpenPGP public key into the certificate
346
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
349
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
607
(gnutls.library.functions
608
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
609
gnutls.library.constants
610
.GNUTLS_OPENPGP_FMT_RAW))
611
# Verify the self signature in the key
612
crtverify = ctypes.c_uint()
613
(gnutls.library.functions
614
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
615
if crtverify.value != 0:
616
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
617
raise gnutls.errors.CertificateSecurityError("Verify failed")
350
618
# New buffer for the fingerprint
351
buffer = ctypes.create_string_buffer(20)
352
buffer_length = ctypes.c_size_t()
619
buf = ctypes.create_string_buffer(20)
620
buf_len = ctypes.c_size_t()
353
621
# Get the fingerprint from the certificate into the buffer
354
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
355
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
622
(gnutls.library.functions
623
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
624
ctypes.byref(buf_len)))
356
625
# Deinit the certificate
357
626
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
358
627
# Convert the buffer to a Python bytestring
359
fpr = ctypes.string_at(buffer, buffer_length.value)
628
fpr = ctypes.string_at(buf, buf_len.value)
360
629
# Convert the bytestring to hexadecimal notation
361
630
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
365
class tcp_handler(SocketServer.BaseRequestHandler, object):
634
class TCP_handler(SocketServer.BaseRequestHandler, object):
366
635
"""A TCP request handler class.
367
636
Instantiated by IPv6_TCPServer for each request to handle it.
368
637
Note: This will run in its own forked process."""
370
639
def handle(self):
371
logger.debug(u"TCP connection from: %s",
372
unicode(self.client_address))
373
session = gnutls.connection.ClientSession(self.request,
640
logger.info(u"TCP connection from: %s",
641
unicode(self.client_address))
642
session = (gnutls.connection
643
.ClientSession(self.request,
647
line = self.request.makefile().readline()
648
logger.debug(u"Protocol version: %r", line)
650
if int(line.strip().split()[0]) > 1:
652
except (ValueError, IndexError, RuntimeError), error:
653
logger.error(u"Unknown protocol version: %s", error)
656
# Note: gnutls.connection.X509Credentials is really a generic
657
# GnuTLS certificate credentials object so long as no X.509
658
# keys are added to it. Therefore, we can use it here despite
659
# using OpenPGP certificates.
377
661
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
378
662
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
380
priority = "SECURE256"
382
gnutls.library.functions.gnutls_priority_set_direct\
383
(session._c_object, priority, None);
664
# Use a fallback default, since this MUST be set.
665
priority = self.server.settings.get("priority", "NORMAL")
666
(gnutls.library.functions
667
.gnutls_priority_set_direct(session._c_object,
386
671
session.handshake()
387
672
except gnutls.errors.GNUTLSError, error:
388
logger.debug(u"Handshake failed: %s", error)
673
logger.warning(u"Handshake failed: %s", error)
389
674
# Do not run session.bye() here: the session is not
390
675
# established. Just abandon the request.
393
678
fpr = fingerprint(peer_certificate(session))
394
679
except (TypeError, gnutls.errors.GNUTLSError), error:
395
logger.debug(u"Bad certificate: %s", error)
680
logger.warning(u"Bad certificate: %s", error)
398
683
logger.debug(u"Fingerprint: %s", fpr)
400
684
for c in self.server.clients:
401
685
if c.fingerprint == fpr:
689
logger.warning(u"Client not found for fingerprint: %s",
404
693
# Have to check if client.still_valid(), since it is possible
405
694
# that the client timed out while establishing the GnuTLS
407
if (not client) or (not client.still_valid()):
409
logger.debug(u"Client %(name)s is invalid",
412
logger.debug(u"Client not found for fingerprint: %s",
696
if not client.still_valid():
697
logger.warning(u"Client %(name)s is invalid",
701
## This won't work here, since we're in a fork.
702
# client.bump_timeout()
417
704
while sent_size < len(client.secret):
418
705
sent = session.send(client.secret[sent_size:])
426
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
713
class IPv6_TCPServer(SocketServer.ForkingMixIn,
714
SocketServer.TCPServer, object):
427
715
"""IPv6 TCP server. Accepts 'None' as address and/or port.
429
options: Command line options
717
settings: Server settings
430
718
clients: Set() of Client objects
719
enabled: Boolean; whether this server is activated yet
432
721
address_family = socket.AF_INET6
433
722
def __init__(self, *args, **kwargs):
434
if "options" in kwargs:
435
self.options = kwargs["options"]
436
del kwargs["options"]
723
if "settings" in kwargs:
724
self.settings = kwargs["settings"]
725
del kwargs["settings"]
437
726
if "clients" in kwargs:
438
727
self.clients = kwargs["clients"]
439
728
del kwargs["clients"]
440
return super(type(self), self).__init__(*args, **kwargs)
730
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
441
731
def server_bind(self):
442
732
"""This overrides the normal server_bind() function
443
733
to bind to an interface if one was specified, and also NOT to
444
734
bind to an address or port if they were not specified."""
445
if self.options.interface:
446
if not hasattr(socket, "SO_BINDTODEVICE"):
447
# From /usr/include/asm-i486/socket.h
448
socket.SO_BINDTODEVICE = 25
735
if self.settings["interface"]:
736
# 25 is from /usr/include/asm-i486/socket.h
737
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
450
739
self.socket.setsockopt(socket.SOL_SOCKET,
451
socket.SO_BINDTODEVICE,
452
self.options.interface)
741
self.settings["interface"])
453
742
except socket.error, error:
454
743
if error[0] == errno.EPERM:
455
logger.warning(u"No permission to"
456
u" bind to interface %s",
457
self.options.interface)
744
logger.error(u"No permission to"
745
u" bind to interface %s",
746
self.settings["interface"])
460
749
# Only bind(2) the socket if we really need to.
482
783
datetime.timedelta(1)
483
784
>>> string_to_delta(u'1w')
484
785
datetime.timedelta(7)
786
>>> string_to_delta('5m 30s')
787
datetime.timedelta(0, 330)
487
suffix=unicode(interval[-1])
488
value=int(interval[:-1])
490
delta = datetime.timedelta(value)
492
delta = datetime.timedelta(0, value)
494
delta = datetime.timedelta(0, 0, 0, 0, value)
496
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
498
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
789
timevalue = datetime.timedelta(0)
790
for s in interval.split():
792
suffix = unicode(s[-1])
795
delta = datetime.timedelta(value)
797
delta = datetime.timedelta(0, value)
799
delta = datetime.timedelta(0, 0, 0, 0, value)
801
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
803
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
806
except (ValueError, IndexError):
501
except (ValueError, IndexError):
507
"""Derived from the Avahi example code"""
508
global group, serviceName, serviceType, servicePort, serviceTXT, \
511
group = dbus.Interface(
512
bus.get_object( avahi.DBUS_NAME,
513
server.EntryGroupNew()),
514
avahi.DBUS_INTERFACE_ENTRY_GROUP)
515
group.connect_to_signal('StateChanged',
516
entry_group_state_changed)
517
logger.debug(u"Adding service '%s' of type '%s' ...",
518
serviceName, serviceType)
521
serviceInterface, # interface
522
avahi.PROTO_INET6, # protocol
523
dbus.UInt32(0), # flags
524
serviceName, serviceType,
526
dbus.UInt16(servicePort),
527
avahi.string_array_to_txt_array(serviceTXT))
531
def remove_service():
532
"""From the Avahi example code"""
535
if not group is None:
539
812
def server_state_changed(state):
540
813
"""Derived from the Avahi example code"""
541
814
if state == avahi.SERVER_COLLISION:
542
logger.warning(u"Server name collision")
815
logger.error(u"Zeroconf server name collision")
544
817
elif state == avahi.SERVER_RUNNING:
548
821
def entry_group_state_changed(state, error):
549
822
"""Derived from the Avahi example code"""
550
global serviceName, server, rename_count
552
logger.debug(u"state change: %i", state)
823
logger.debug(u"Avahi state change: %i", state)
554
825
if state == avahi.ENTRY_GROUP_ESTABLISHED:
555
logger.debug(u"Service established.")
826
logger.debug(u"Zeroconf service established.")
556
827
elif state == avahi.ENTRY_GROUP_COLLISION:
558
rename_count = rename_count - 1
560
name = server.GetAlternativeServiceName(name)
561
logger.warning(u"Service name collision, "
562
u"changing name to '%s' ...", name)
567
logger.error(u"No suitable service name found after %i"
568
u" retries, exiting.", n_rename)
828
logger.warning(u"Zeroconf service name collision.")
570
830
elif state == avahi.ENTRY_GROUP_FAILURE:
571
logger.error(u"Error in group state changed %s",
831
logger.critical(u"Avahi: Error in group state changed %s",
833
raise AvahiGroupError("State changed: %s", str(error))
576
835
def if_nametoindex(interface):
577
"""Call the C function if_nametoindex()"""
836
"""Call the C function if_nametoindex(), or equivalent"""
837
global if_nametoindex
579
libc = ctypes.cdll.LoadLibrary("libc.so.6")
580
return libc.if_nametoindex(interface)
839
if_nametoindex = (ctypes.cdll.LoadLibrary
840
(ctypes.util.find_library("c"))
581
842
except (OSError, AttributeError):
582
843
if "struct" not in sys.modules:
584
845
if "fcntl" not in sys.modules:
586
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
588
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
589
struct.pack("16s16x", interface))
591
interface_index = struct.unpack("I", ifreq[16:20])[0]
592
return interface_index
595
def daemon(nochdir, noclose):
847
def if_nametoindex(interface):
848
"Get an interface index the hard way, i.e. using fcntl()"
849
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
850
with closing(socket.socket()) as s:
851
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
852
struct.pack("16s16x", interface))
853
interface_index = struct.unpack("I", ifreq[16:20])[0]
854
return interface_index
855
return if_nametoindex(interface)
858
def daemon(nochdir = False, noclose = False):
596
859
"""See daemon(3). Standard BSD Unix function.
597
860
This should really exist as os.daemon, but it doesn't (yet)."""
616
def killme(status = 0):
617
logger.debug("Stopping server with exit status %d", status)
619
if main_loop_started:
628
global main_loop_started
629
main_loop_started = False
631
parser = OptionParser()
882
parser = OptionParser(version = "%%prog %s" % version)
632
883
parser.add_option("-i", "--interface", type="string",
633
default=None, metavar="IF",
634
help="Bind to interface IF")
635
parser.add_option("-a", "--address", type="string", default=None,
884
metavar="IF", help="Bind to interface IF")
885
parser.add_option("-a", "--address", type="string",
636
886
help="Address to listen for requests on")
637
parser.add_option("-p", "--port", type="int", default=None,
887
parser.add_option("-p", "--port", type="int",
638
888
help="Port number to receive requests on")
639
parser.add_option("--timeout", type="string", # Parsed later
641
help="Amount of downtime allowed for clients")
642
parser.add_option("--interval", type="string", # Parsed later
644
help="How often to check that a client is up")
645
889
parser.add_option("--check", action="store_true", default=False,
646
890
help="Run self-test")
647
parser.add_option("--debug", action="store_true", default=False,
649
(options, args) = parser.parse_args()
891
parser.add_option("--debug", action="store_true",
892
help="Debug mode; run in foreground and log to"
894
parser.add_option("--priority", type="string", help="GnuTLS"
895
" priority string (see GnuTLS documentation)")
896
parser.add_option("--servicename", type="string", metavar="NAME",
897
help="Zeroconf service name")
898
parser.add_option("--configdir", type="string",
899
default="/etc/mandos", metavar="DIR",
900
help="Directory to search for configuration"
902
options = parser.parse_args()[0]
651
904
if options.check:
653
906
doctest.testmod()
656
# Parse the time arguments
658
options.timeout = string_to_delta(options.timeout)
660
parser.error("option --timeout: Unparseable time")
662
options.interval = string_to_delta(options.interval)
664
parser.error("option --interval: Unparseable time")
667
defaults = { "checker": "fping -q -- %%(fqdn)s" }
668
client_config = ConfigParser.SafeConfigParser(defaults)
669
#client_config.readfp(open("global.conf"), "global.conf")
670
client_config.read("mandos-clients.conf")
909
# Default values for config file for server-global settings
910
server_defaults = { "interface": "",
915
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
916
"servicename": "Mandos",
919
# Parse config file for server-global settings
920
server_config = ConfigParser.SafeConfigParser(server_defaults)
922
server_config.read(os.path.join(options.configdir, "mandos.conf"))
923
# Convert the SafeConfigParser object to a dict
924
server_settings = server_config.defaults()
925
# Use getboolean on the boolean config option
926
server_settings["debug"] = (server_config.getboolean
927
("DEFAULT", "debug"))
930
# Override the settings from the config file with command line
932
for option in ("interface", "address", "port", "debug",
933
"priority", "servicename", "configdir"):
934
value = getattr(options, option)
935
if value is not None:
936
server_settings[option] = value
938
# Now we have our good server settings in "server_settings"
940
debug = server_settings["debug"]
943
syslogger.setLevel(logging.WARNING)
944
console.setLevel(logging.WARNING)
946
if server_settings["servicename"] != "Mandos":
947
syslogger.setFormatter(logging.Formatter
948
('Mandos (%s): %%(levelname)s:'
950
% server_settings["servicename"]))
952
# Parse config file with clients
953
client_defaults = { "timeout": "1h",
955
"checker": "fping -q -- %(host)s",
958
client_config = ConfigParser.SafeConfigParser(client_defaults)
959
client_config.read(os.path.join(server_settings["configdir"],
963
tcp_server = IPv6_TCPServer((server_settings["address"],
964
server_settings["port"]),
966
settings=server_settings,
968
pidfilename = "/var/run/mandos.pid"
970
pidfile = open(pidfilename, "w")
971
except IOError, error:
972
logger.error("Could not open file %r", pidfilename)
977
uid = pwd.getpwnam("mandos").pw_uid
980
uid = pwd.getpwnam("nobody").pw_uid
984
gid = pwd.getpwnam("mandos").pw_gid
987
gid = pwd.getpwnam("nogroup").pw_gid
993
except OSError, error:
994
if error[0] != errno.EPERM:
998
service = AvahiService(name = server_settings["servicename"],
999
servicetype = "_mandos._tcp", )
1000
if server_settings["interface"]:
1001
service.interface = (if_nametoindex
1002
(server_settings["interface"]))
672
1004
global main_loop
676
1008
DBusGMainLoop(set_as_default=True )
677
1009
main_loop = gobject.MainLoop()
678
1010
bus = dbus.SystemBus()
679
server = dbus.Interface(
680
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
681
avahi.DBUS_INTERFACE_SERVER )
1011
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1012
avahi.DBUS_PATH_SERVER),
1013
avahi.DBUS_INTERFACE_SERVER)
682
1014
# End of Avahi example code
684
debug = options.debug
687
console = logging.StreamHandler()
688
# console.setLevel(logging.DEBUG)
689
console.setFormatter(logging.Formatter\
690
('%(levelname)s: %(message)s'))
691
logger.addHandler(console)
1015
bus_name = dbus.service.BusName(u"org.mandos-system.Mandos", bus)
695
1017
def remove_from_clients(client):
696
1018
clients.remove(client)
698
logger.debug(u"No clients left, exiting")
1020
logger.critical(u"No clients left, exiting")
701
clients.update(Set(Client(name=section, options=options,
1023
clients.update(Set(Client(name = section,
702
1024
stop_hook = remove_from_clients,
703
**(dict(client_config\
1026
= dict(client_config.items(section)))
705
1027
for section in client_config.sections()))
1029
logger.critical(u"No clients defined")
1033
# Redirect stdin so all checkers get /dev/null
1034
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1035
os.dup2(null, sys.stdin.fileno())
1039
# No console logging
1040
logger.removeHandler(console)
1041
# Close all input and output, do double fork, etc.
1046
pidfile.write(str(pid) + "\n")
1050
logger.error(u"Could not write to file %r with PID %d",
1053
# "pidfile" was never created
711
1058
"Cleanup function; run on exit"
727
1074
signal.signal(signal.SIGINT, signal.SIG_IGN)
728
signal.signal(signal.SIGHUP, lambda signum, frame: killme())
729
signal.signal(signal.SIGTERM, lambda signum, frame: killme())
1075
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1076
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
731
1078
for client in clients:
734
tcp_server = IPv6_TCPServer((options.address, options.port),
738
# Find out what random port we got
740
servicePort = tcp_server.socket.getsockname()[1]
741
logger.debug(u"Now listening on port %d", servicePort)
743
if options.interface is not None:
744
global serviceInterface
745
serviceInterface = if_nametoindex(options.interface)
747
# From the Avahi example code
748
server.connect_to_signal("StateChanged", server_state_changed)
750
server_state_changed(server.GetState())
751
except dbus.exceptions.DBusException, error:
752
logger.critical(u"DBusException: %s", error)
754
# End of Avahi example code
756
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
757
lambda *args, **kwargs:
758
tcp_server.handle_request(*args[2:],
761
logger.debug("Starting main loop")
762
main_loop_started = True
1082
tcp_server.server_activate()
1084
# Find out what port we got
1085
service.port = tcp_server.socket.getsockname()[1]
1086
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
1087
u" scope_id %d" % tcp_server.socket.getsockname())
1089
#service.interface = tcp_server.socket.getsockname()[3]
1092
# From the Avahi example code
1093
server.connect_to_signal("StateChanged", server_state_changed)
1095
server_state_changed(server.GetState())
1096
except dbus.exceptions.DBusException, error:
1097
logger.critical(u"DBusException: %s", error)
1099
# End of Avahi example code
1101
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
1102
lambda *args, **kwargs:
1103
(tcp_server.handle_request
1104
(*args[2:], **kwargs) or True))
1106
logger.debug(u"Starting main loop")
1108
except AvahiError, error:
1109
logger.critical(u"AvahiError: %s" + unicode(error))
764
1111
except KeyboardInterrupt:
770
1115
if __name__ == '__main__':