57
57
import logging.handlers
59
from contextlib import closing
65
62
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.
71
77
logger = logging.Logger('mandos')
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'))
78
syslogger = logging.handlers.SysLogHandler\
79
(facility = logging.handlers.SysLogHandler.LOG_DAEMON)
80
syslogger.setFormatter(logging.Formatter\
81
('%(levelname)s: %(message)s'))
77
82
logger.addHandler(syslogger)
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))
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
168
89
# From the Avahi example code:
169
group = None # our entry group
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
170
99
# End of Avahi example code
173
class Client(dbus.service.Object):
102
class Client(object):
174
103
"""A representation of a client host served by this server.
176
105
name: string; from the config file, used in log messages
177
106
fingerprint: string (40 or 32 hexadecimal digits); used to
178
107
uniquely identify the client
179
108
secret: bytestring; sent verbatim (over TLS) to client
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
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
186
114
interval: datetime.timedelta(); How often to start a new checker
187
115
stop_hook: If set, called by stop() as stop_hook(self)
188
116
checker: subprocess.Popen(); a running checker process used
189
117
to see if the client lives.
190
'None' if no process is running.
118
Is None if no process is running.
191
119
checker_initiator_tag: a gobject event source tag, or None
192
120
stop_initiator_tag: - '' -
193
121
checker_callback_tag: - '' -
194
122
checker_command: string; External command which is run to check if
195
client lives. %() expansions are done at
123
client lives. %()s expansions are done at
196
124
runtime with vars(self) as dict, so that for
197
125
instance %(name)s can be used in the command.
198
126
Private attibutes:
199
127
_timeout: Real variable for 'timeout'
200
128
_interval: Real variable for 'interval'
201
_timeout_milliseconds: Used when calling gobject.timeout_add()
129
_timeout_milliseconds: Used by gobject.timeout_add()
202
130
_interval_milliseconds: - '' -
204
132
def _set_timeout(self, timeout):
205
"Setter function for the 'timeout' attribute"
133
"Setter function for 'timeout' attribute"
206
134
self._timeout = timeout
207
135
self._timeout_milliseconds = ((self.timeout.days
208
136
* 24 * 60 * 60 * 1000)
209
137
+ (self.timeout.seconds * 1000)
210
138
+ (self.timeout.microseconds
213
self.TimeoutChanged(self._timeout_milliseconds)
214
timeout = property(lambda self: self._timeout, _set_timeout)
140
timeout = property(lambda self: self._timeout,
217
143
def _set_interval(self, interval):
218
"Setter function for the 'interval' attribute"
144
"Setter function for 'interval' attribute"
219
145
self._interval = interval
220
146
self._interval_milliseconds = ((self.interval.days
221
147
* 24 * 60 * 60 * 1000)
224
150
+ (self.interval.microseconds
227
self.IntervalChanged(self._interval_milliseconds)
228
interval = property(lambda self: self._interval, _set_interval)
152
interval = property(lambda self: self._interval,
229
154
del _set_interval
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(".", "_"))
155
def __init__(self, name=None, stop_hook=None, fingerprint=None,
156
secret=None, secfile=None, fqdn=None, timeout=None,
157
interval=-1, checker=None):
158
"""Note: the 'checker' argument sets the 'checker_command'
159
attribute and not the 'checker' attribute.."""
241
161
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()
162
# Uppercase and remove spaces from fingerprint
163
# for later comparison purposes with return value of
164
# the fingerprint() function
165
self.fingerprint = fingerprint.upper().replace(u" ", u"")
247
166
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()
168
self.secret = secret.decode(u"base64")
171
self.secret = sf.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"])
174
raise RuntimeError(u"No secret or secfile for client %s"
176
self.fqdn = fqdn # string
177
self.created = datetime.datetime.now()
178
self.last_seen = None
179
self.timeout = string_to_delta(timeout)
180
self.interval = string_to_delta(interval)
264
181
self.stop_hook = stop_hook
265
182
self.checker = None
266
183
self.checker_initiator_tag = None
267
184
self.stop_initiator_tag = None
268
185
self.checker_callback_tag = None
269
self.check_command = config["checker"]
186
self.check_command = checker
272
188
"""Start this client's checker and timeout hooks"""
273
self.started = datetime.datetime.utcnow()
274
189
# Schedule a new checker to be started an 'interval' from now,
275
190
# and every interval from then on.
276
self.checker_initiator_tag = (gobject.timeout_add
277
(self._interval_milliseconds,
191
self.checker_initiator_tag = gobject.timeout_add\
192
(self._interval_milliseconds,
279
194
# Also start a new checker *right now*.
280
195
self.start_checker()
281
196
# Schedule a stop() when 'timeout' has passed
282
self.stop_initiator_tag = (gobject.timeout_add
283
(self._timeout_milliseconds,
286
self.StateChanged(True)
197
self.stop_initiator_tag = gobject.timeout_add\
198
(self._timeout_milliseconds,
289
"""Stop this client."""
290
if getattr(self, "started", None) is not None:
291
logger.info(u"Stopping client %s", self.name)
202
The possibility that this client might be restarted is left
203
open, but not currently used."""
204
# If this client doesn't have a secret, it is already stopped.
206
logger.debug(u"Stopping client %s", self.name)
294
if getattr(self, "stop_initiator_tag", False):
210
if hasattr(self, "stop_initiator_tag") \
211
and self.stop_initiator_tag:
295
212
gobject.source_remove(self.stop_initiator_tag)
296
213
self.stop_initiator_tag = None
297
if getattr(self, "checker_initiator_tag", False):
214
if hasattr(self, "checker_initiator_tag") \
215
and self.checker_initiator_tag:
298
216
gobject.source_remove(self.checker_initiator_tag)
299
217
self.checker_initiator_tag = None
300
218
self.stop_checker()
301
219
if self.stop_hook:
302
220
self.stop_hook(self)
305
self.StateChanged(False)
306
221
# Do not run this again if called by a gobject.timeout_add
309
223
def __del__(self):
310
224
self.stop_hook = None
313
226
def checker_callback(self, pid, condition):
314
227
"""The checker has completed, so take appropriate actions."""
228
now = datetime.datetime.now()
315
229
self.checker_callback_tag = None
316
230
self.checker = None
317
if (os.WIFEXITED(condition)
318
and (os.WEXITSTATUS(condition) == 0)):
319
logger.info(u"Checker for %(name)s succeeded",
322
self.CheckerCompleted(True)
231
if os.WIFEXITED(condition) \
232
and (os.WEXITSTATUS(condition) == 0):
233
logger.debug(u"Checker for %(name)s succeeded",
236
gobject.source_remove(self.stop_initiator_tag)
237
self.stop_initiator_tag = gobject.timeout_add\
238
(self._timeout_milliseconds,
324
240
elif not os.WIFEXITED(condition):
325
241
logger.warning(u"Checker for %(name)s crashed?",
328
self.CheckerCompleted(False)
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,
244
logger.debug(u"Checker for %(name)s failed",
346
246
def start_checker(self):
347
247
"""Start a new checker subprocess if one is not running.
348
248
If a checker already exists, leave it running and do
357
257
# is as it should be.
358
258
if self.checker is None:
360
# In case check_command has exactly one % operator
361
command = self.check_command % self.host
260
command = self.check_command % self.fqdn
362
261
except TypeError:
363
# Escape attributes for the shell
364
262
escaped_attrs = dict((key, re.escape(str(val)))
366
264
vars(self).iteritems())
368
266
command = self.check_command % escaped_attrs
369
267
except TypeError, error:
370
logger.error(u'Could not format string "%s":'
371
u' %s', self.check_command, error)
268
logger.critical(u'Could not format string "%s":'
269
u' %s', self.check_command, error)
372
270
return True # Try again later
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:
272
logger.debug(u"Starting checker %r for %s",
274
self.checker = subprocess.\
276
close_fds=True, shell=True,
278
self.checker_callback_tag = gobject.child_watch_add\
280
self.checker_callback)
281
except subprocess.OSError, error:
389
282
logger.error(u"Failed to start subprocess: %s",
391
284
# Re-run this periodically if run by gobject.timeout_add
394
286
def stop_checker(self):
395
287
"""Force the checker process, if any, to stop."""
396
288
if self.checker_callback_tag:
397
289
gobject.source_remove(self.checker_callback_tag)
398
290
self.checker_callback_tag = None
399
if getattr(self, "checker", None) is None:
291
if not hasattr(self, "checker") or self.checker is None:
401
logger.debug(u"Stopping checker for %(name)s", vars(self))
293
logger.debug("Stopping checker for %(name)s", vars(self))
403
295
os.kill(self.checker.pid, signal.SIGTERM)
405
297
#if self.checker.poll() is None:
406
298
# os.kill(self.checker.pid, signal.SIGKILL)
407
299
except OSError, error:
408
if error.errno != errno.ESRCH: # No such process
300
if error.errno != errno.ESRCH:
410
302
self.checker = None
412
def still_valid(self):
303
def still_valid(self, now=None):
413
304
"""Has the timeout not yet passed for this client?"""
416
now = datetime.datetime.utcnow()
417
if self.last_checked_ok is None:
306
now = datetime.datetime.now()
307
if self.last_seen is None:
418
308
return now < (self.created + 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
310
return now < (self.last_seen + self.timeout)
576
313
def peer_certificate(session):
577
314
"Return the peer's OpenPGP certificate as a bytestring"
578
315
# If not an OpenPGP certificate...
579
if (gnutls.library.functions
580
.gnutls_certificate_type_get(session._c_object)
581
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
316
if gnutls.library.functions.gnutls_certificate_type_get\
317
(session._c_object) \
318
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
582
319
# ...do the normal thing
583
320
return session.peer_certificate
584
321
list_size = ctypes.c_uint()
585
cert_list = (gnutls.library.functions
586
.gnutls_certificate_get_peers
587
(session._c_object, ctypes.byref(list_size)))
322
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
323
(session._c_object, ctypes.byref(list_size))
588
324
if list_size.value == 0:
590
326
cert = cert_list[0]
594
330
def fingerprint(openpgp):
595
331
"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))))
602
332
# New empty GnuTLS certificate
603
333
crt = gnutls.library.types.gnutls_openpgp_crt_t()
604
(gnutls.library.functions
605
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
334
gnutls.library.functions.gnutls_openpgp_crt_init\
336
# New GnuTLS "datum" with the OpenPGP public key
337
datum = gnutls.library.types.gnutls_datum_t\
338
(ctypes.cast(ctypes.c_char_p(openpgp),
339
ctypes.POINTER(ctypes.c_ubyte)),
340
ctypes.c_uint(len(openpgp)))
606
341
# Import the OpenPGP public key into the certificate
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")
342
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
345
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
618
346
# New buffer for the fingerprint
619
buf = ctypes.create_string_buffer(20)
620
buf_len = ctypes.c_size_t()
347
buffer = ctypes.create_string_buffer(20)
348
buffer_length = ctypes.c_size_t()
621
349
# Get the fingerprint from the certificate into the buffer
622
(gnutls.library.functions
623
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
624
ctypes.byref(buf_len)))
350
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
351
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
625
352
# Deinit the certificate
626
353
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
627
354
# Convert the buffer to a Python bytestring
628
fpr = ctypes.string_at(buf, buf_len.value)
355
fpr = ctypes.string_at(buffer, buffer_length.value)
629
356
# Convert the bytestring to hexadecimal notation
630
357
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
634
class TCP_handler(SocketServer.BaseRequestHandler, object):
361
class tcp_handler(SocketServer.BaseRequestHandler, object):
635
362
"""A TCP request handler class.
636
363
Instantiated by IPv6_TCPServer for each request to handle it.
637
364
Note: This will run in its own forked process."""
639
366
def handle(self):
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.
367
logger.debug(u"TCP connection from: %s",
368
unicode(self.client_address))
369
session = gnutls.connection.ClientSession(self.request,
661
373
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
662
374
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
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,
377
if self.server.options.priority:
378
priority = self.server.options.priority
379
gnutls.library.functions.gnutls_priority_set_direct\
380
(session._c_object, priority, None);
671
383
session.handshake()
672
384
except gnutls.errors.GNUTLSError, error:
673
logger.warning(u"Handshake failed: %s", error)
385
logger.debug(u"Handshake failed: %s", error)
674
386
# Do not run session.bye() here: the session is not
675
387
# established. Just abandon the request.
678
390
fpr = fingerprint(peer_certificate(session))
679
391
except (TypeError, gnutls.errors.GNUTLSError), error:
680
logger.warning(u"Bad certificate: %s", error)
392
logger.debug(u"Bad certificate: %s", error)
683
395
logger.debug(u"Fingerprint: %s", fpr)
684
397
for c in self.server.clients:
685
398
if c.fingerprint == fpr:
689
logger.warning(u"Client not found for fingerprint: %s",
693
401
# Have to check if client.still_valid(), since it is possible
694
402
# that the client timed out while establishing the GnuTLS
696
if not client.still_valid():
697
logger.warning(u"Client %(name)s is invalid",
404
if (not client) or (not client.still_valid()):
406
logger.debug(u"Client %(name)s is invalid",
409
logger.debug(u"Client not found for fingerprint: %s",
701
## This won't work here, since we're in a fork.
702
# client.bump_timeout()
704
414
while sent_size < len(client.secret):
705
415
sent = session.send(client.secret[sent_size:])
713
class IPv6_TCPServer(SocketServer.ForkingMixIn,
714
SocketServer.TCPServer, object):
423
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
715
424
"""IPv6 TCP server. Accepts 'None' as address and/or port.
717
settings: Server settings
426
options: Command line options
718
427
clients: Set() of Client objects
719
enabled: Boolean; whether this server is activated yet
721
429
address_family = socket.AF_INET6
722
430
def __init__(self, *args, **kwargs):
723
if "settings" in kwargs:
724
self.settings = kwargs["settings"]
725
del kwargs["settings"]
431
if "options" in kwargs:
432
self.options = kwargs["options"]
433
del kwargs["options"]
726
434
if "clients" in kwargs:
727
435
self.clients = kwargs["clients"]
728
436
del kwargs["clients"]
730
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
437
return super(type(self), self).__init__(*args, **kwargs)
731
438
def server_bind(self):
732
439
"""This overrides the normal server_bind() function
733
440
to bind to an interface if one was specified, and also NOT to
734
441
bind to an address or port if they were not specified."""
735
if self.settings["interface"]:
736
# 25 is from /usr/include/asm-i486/socket.h
737
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
442
if self.options.interface:
443
if not hasattr(socket, "SO_BINDTODEVICE"):
444
# From /usr/include/asm-i486/socket.h
445
socket.SO_BINDTODEVICE = 25
739
447
self.socket.setsockopt(socket.SOL_SOCKET,
741
self.settings["interface"])
448
socket.SO_BINDTODEVICE,
449
self.options.interface)
742
450
except socket.error, error:
743
451
if error[0] == errno.EPERM:
744
logger.error(u"No permission to"
745
u" bind to interface %s",
746
self.settings["interface"])
452
logger.warning(u"No permission to"
453
u" bind to interface %s",
454
self.options.interface)
749
457
# Only bind(2) the socket if we really need to.
783
479
datetime.timedelta(1)
784
480
>>> string_to_delta(u'1w')
785
481
datetime.timedelta(7)
786
>>> string_to_delta('5m 30s')
787
datetime.timedelta(0, 330)
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):
484
suffix=unicode(interval[-1])
485
value=int(interval[:-1])
487
delta = datetime.timedelta(value)
489
delta = datetime.timedelta(0, value)
491
delta = datetime.timedelta(0, 0, 0, 0, value)
493
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
495
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
498
except (ValueError, IndexError):
504
"""Derived from the Avahi example code"""
505
global group, serviceName, serviceType, servicePort, serviceTXT, \
508
group = dbus.Interface(
509
bus.get_object( avahi.DBUS_NAME,
510
server.EntryGroupNew()),
511
avahi.DBUS_INTERFACE_ENTRY_GROUP)
512
group.connect_to_signal('StateChanged',
513
entry_group_state_changed)
514
logger.debug(u"Adding service '%s' of type '%s' ...",
515
serviceName, serviceType)
518
serviceInterface, # interface
519
avahi.PROTO_INET6, # protocol
520
dbus.UInt32(0), # flags
521
serviceName, serviceType,
523
dbus.UInt16(servicePort),
524
avahi.string_array_to_txt_array(serviceTXT))
528
def remove_service():
529
"""From the Avahi example code"""
532
if not group is None:
812
536
def server_state_changed(state):
813
537
"""Derived from the Avahi example code"""
814
538
if state == avahi.SERVER_COLLISION:
815
logger.error(u"Zeroconf server name collision")
539
logger.warning(u"Server name collision")
817
541
elif state == avahi.SERVER_RUNNING:
821
545
def entry_group_state_changed(state, error):
822
546
"""Derived from the Avahi example code"""
823
logger.debug(u"Avahi state change: %i", state)
547
global serviceName, server, rename_count
549
logger.debug(u"state change: %i", state)
825
551
if state == avahi.ENTRY_GROUP_ESTABLISHED:
826
logger.debug(u"Zeroconf service established.")
552
logger.debug(u"Service established.")
827
553
elif state == avahi.ENTRY_GROUP_COLLISION:
828
logger.warning(u"Zeroconf service name collision.")
555
rename_count = rename_count - 1
557
name = server.GetAlternativeServiceName(name)
558
logger.warning(u"Service name collision, "
559
u"changing name to '%s' ...", name)
564
logger.error(u"No suitable service name found after %i"
565
u" retries, exiting.", n_rename)
830
567
elif state == avahi.ENTRY_GROUP_FAILURE:
831
logger.critical(u"Avahi: Error in group state changed %s",
833
raise AvahiGroupError("State changed: %s", str(error))
568
logger.error(u"Error in group state changed %s",
835
573
def if_nametoindex(interface):
836
"""Call the C function if_nametoindex(), or equivalent"""
837
global if_nametoindex
574
"""Call the C function if_nametoindex()"""
839
if_nametoindex = (ctypes.cdll.LoadLibrary
840
(ctypes.util.find_library("c"))
576
libc = ctypes.cdll.LoadLibrary("libc.so.6")
577
return libc.if_nametoindex(interface)
842
578
except (OSError, AttributeError):
843
579
if "struct" not in sys.modules:
845
581
if "fcntl" not in sys.modules:
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):
583
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
585
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
586
struct.pack("16s16x", interface))
588
interface_index = struct.unpack("I", ifreq[16:20])[0]
589
return interface_index
592
def daemon(nochdir, noclose):
859
593
"""See daemon(3). Standard BSD Unix function.
860
594
This should really exist as os.daemon, but it doesn't (yet)."""
613
def killme(status = 0):
614
logger.debug("Stopping server with exit status %d", status)
616
if main_loop_started:
882
parser = OptionParser(version = "%%prog %s" % version)
625
global main_loop_started
626
main_loop_started = False
628
parser = OptionParser()
883
629
parser.add_option("-i", "--interface", type="string",
884
metavar="IF", help="Bind to interface IF")
885
parser.add_option("-a", "--address", type="string",
630
default=None, metavar="IF",
631
help="Bind to interface IF")
632
parser.add_option("-a", "--address", type="string", default=None,
886
633
help="Address to listen for requests on")
887
parser.add_option("-p", "--port", type="int",
634
parser.add_option("-p", "--port", type="int", default=None,
888
635
help="Port number to receive requests on")
889
636
parser.add_option("--check", action="store_true", default=False,
890
637
help="Run self-test")
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]
638
parser.add_option("--debug", action="store_true", default=False,
640
parser.add_option("--priority", type="string",
642
help="GnuTLS priority string"
643
" (see GnuTLS documentation)")
644
parser.add_option("--servicename", type="string",
645
default="Mandos", help="Zeroconf service name")
646
(options, args) = parser.parse_args()
904
648
if options.check:
906
650
doctest.testmod()
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"]))
654
defaults = { "timeout": "1h",
656
"checker": "fping -q -- %%(fqdn)s",
658
client_config = ConfigParser.SafeConfigParser(defaults)
659
#client_config.readfp(open("global.conf"), "global.conf")
660
client_config.read("mandos-clients.conf")
663
serviceName = options.servicename;
1004
665
global main_loop
1008
669
DBusGMainLoop(set_as_default=True )
1009
670
main_loop = gobject.MainLoop()
1010
671
bus = dbus.SystemBus()
1011
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1012
avahi.DBUS_PATH_SERVER),
1013
avahi.DBUS_INTERFACE_SERVER)
672
server = dbus.Interface(
673
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
674
avahi.DBUS_INTERFACE_SERVER )
1014
675
# End of Avahi example code
1015
bus_name = dbus.service.BusName(u"org.mandos-system.Mandos", bus)
677
debug = options.debug
680
console = logging.StreamHandler()
681
# console.setLevel(logging.DEBUG)
682
console.setFormatter(logging.Formatter\
683
('%(levelname)s: %(message)s'))
684
logger.addHandler(console)
1017
688
def remove_from_clients(client):
1018
689
clients.remove(client)
1020
logger.critical(u"No clients left, exiting")
691
logger.debug(u"No clients left, exiting")
1023
clients.update(Set(Client(name = section,
694
clients.update(Set(Client(name=section,
1024
695
stop_hook = remove_from_clients,
1026
= dict(client_config.items(section)))
696
**(dict(client_config\
1027
698
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
1058
704
"Cleanup function; run on exit"
1074
720
signal.signal(signal.SIGINT, signal.SIG_IGN)
1075
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1076
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
721
signal.signal(signal.SIGHUP, lambda signum, frame: killme())
722
signal.signal(signal.SIGTERM, lambda signum, frame: killme())
1078
724
for client in clients:
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")
727
tcp_server = IPv6_TCPServer((options.address, options.port),
731
# Find out what random port we got
733
servicePort = tcp_server.socket.getsockname()[1]
734
logger.debug(u"Now listening on port %d", servicePort)
736
if options.interface is not None:
737
global serviceInterface
738
serviceInterface = if_nametoindex(options.interface)
740
# From the Avahi example code
741
server.connect_to_signal("StateChanged", server_state_changed)
743
server_state_changed(server.GetState())
744
except dbus.exceptions.DBusException, error:
745
logger.critical(u"DBusException: %s", error)
747
# End of Avahi example code
749
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
750
lambda *args, **kwargs:
751
tcp_server.handle_request(*args[2:],
754
logger.debug("Starting main loop")
755
main_loop_started = True
1108
except AvahiError, error:
1109
logger.critical(u"AvahiError: %s" + unicode(error))
1111
757
except KeyboardInterrupt:
1115
763
if __name__ == '__main__':