125
106
max_renames: integer; maximum number of renames
126
107
rename_count: integer; counter so we only rename after collisions
127
108
a sensible number of times
128
group: D-Bus Entry Group
131
110
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
132
servicetype = None, port = None, TXT = None,
133
domain = u"", host = u"", max_renames = 32768,
134
protocol = avahi.PROTO_UNSPEC, bus = None):
111
type = None, port = None, TXT = None, domain = "",
112
host = "", max_renames = 32768):
135
113
self.interface = interface
137
self.type = servicetype
139
self.TXT = TXT if TXT is not None else []
140
121
self.domain = domain
142
123
self.rename_count = 0
143
124
self.max_renames = max_renames
144
self.protocol = protocol
145
self.group = None # our entry group
148
125
def rename(self):
149
126
"""Derived from the Avahi example code"""
150
127
if self.rename_count >= self.max_renames:
151
128
logger.critical(u"No suitable Zeroconf service name found"
152
129
u" after %i retries, exiting.",
154
raise AvahiServiceError(u"Too many renames")
155
self.name = self.server.GetAlternativeServiceName(self.name)
131
raise AvahiServiceError("Too many renames")
132
self.name = server.GetAlternativeServiceName(self.name)
156
133
logger.info(u"Changing Zeroconf service name to %r ...",
158
syslogger.setFormatter(logging.Formatter
159
(u'Mandos (%s) [%%(process)d]:'
160
u' %%(levelname)s: %%(message)s'
135
syslogger.setFormatter(logging.Formatter\
136
('Mandos (%s): %%(levelname)s:'
137
' %%(message)s' % self.name))
164
140
self.rename_count += 1
165
141
def remove(self):
166
142
"""Derived from the Avahi example code"""
167
if self.group is not None:
143
if group is not None:
170
146
"""Derived from the Avahi example code"""
171
if self.group is None:
172
self.group = dbus.Interface(
173
self.bus.get_object(avahi.DBUS_NAME,
174
self.server.EntryGroupNew()),
175
avahi.DBUS_INTERFACE_ENTRY_GROUP)
176
self.group.connect_to_signal('StateChanged',
177
self.entry_group_state_changed)
149
group = dbus.Interface\
150
(bus.get_object(avahi.DBUS_NAME,
151
server.EntryGroupNew()),
152
avahi.DBUS_INTERFACE_ENTRY_GROUP)
153
group.connect_to_signal('StateChanged',
154
entry_group_state_changed)
178
155
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
179
self.name, self.type)
180
self.group.AddService(
183
dbus.UInt32(0), # flags
184
self.name, self.type,
185
self.domain, self.host,
186
dbus.UInt16(self.port),
187
avahi.string_array_to_txt_array(self.TXT))
189
def entry_group_state_changed(self, state, error):
190
"""Derived from the Avahi example code"""
191
logger.debug(u"Avahi state change: %i", state)
193
if state == avahi.ENTRY_GROUP_ESTABLISHED:
194
logger.debug(u"Zeroconf service established.")
195
elif state == avahi.ENTRY_GROUP_COLLISION:
196
logger.warning(u"Zeroconf service name collision.")
198
elif state == avahi.ENTRY_GROUP_FAILURE:
199
logger.critical(u"Avahi: Error in group state changed %s",
201
raise AvahiGroupError(u"State changed: %s"
204
"""Derived from the Avahi example code"""
205
if self.group is not None:
208
def server_state_changed(self, state):
209
"""Derived from the Avahi example code"""
210
if state == avahi.SERVER_COLLISION:
211
logger.error(u"Zeroconf server name collision")
213
elif state == avahi.SERVER_RUNNING:
216
"""Derived from the Avahi example code"""
217
if self.server is None:
218
self.server = dbus.Interface(
219
self.bus.get_object(avahi.DBUS_NAME,
220
avahi.DBUS_PATH_SERVER),
221
avahi.DBUS_INTERFACE_SERVER)
222
self.server.connect_to_signal(u"StateChanged",
223
self.server_state_changed)
224
self.server_state_changed(self.server.GetState())
156
service.name, service.type)
158
self.interface, # interface
159
avahi.PROTO_INET6, # protocol
160
dbus.UInt32(0), # flags
161
self.name, self.type,
162
self.domain, self.host,
163
dbus.UInt16(self.port),
164
avahi.string_array_to_txt_array(self.TXT))
167
# From the Avahi example code:
168
group = None # our entry group
169
# End of Avahi example code
227
172
class Client(object):
228
173
"""A representation of a client host served by this server.
231
name: string; from the config file, used in log messages and
175
name: string; from the config file, used in log messages
233
176
fingerprint: string (40 or 32 hexadecimal digits); used to
234
177
uniquely identify the client
235
secret: bytestring; sent verbatim (over TLS) to client
236
host: string; available for use by the checker command
237
created: datetime.datetime(); (UTC) object creation
238
last_enabled: datetime.datetime(); (UTC)
240
last_checked_ok: datetime.datetime(); (UTC) or None
241
timeout: datetime.timedelta(); How long from last_checked_ok
242
until this client is invalid
243
interval: datetime.timedelta(); How often to start a new checker
244
disable_hook: If set, called by disable() as disable_hook(self)
245
checker: subprocess.Popen(); a running checker process used
246
to see if the client lives.
247
'None' if no process is running.
178
secret: bytestring; sent verbatim (over TLS) to client
179
host: string; available for use by the checker command
180
created: datetime.datetime(); object creation, not client host
181
last_checked_ok: datetime.datetime() or None if not yet checked OK
182
timeout: datetime.timedelta(); How long from last_checked_ok
183
until this client is invalid
184
interval: datetime.timedelta(); How often to start a new checker
185
stop_hook: If set, called by stop() as stop_hook(self)
186
checker: subprocess.Popen(); a running checker process used
187
to see if the client lives.
188
'None' if no process is running.
248
189
checker_initiator_tag: a gobject event source tag, or None
249
disable_initiator_tag: - '' -
190
stop_initiator_tag: - '' -
250
191
checker_callback_tag: - '' -
251
192
checker_command: string; External command which is run to check if
252
193
client lives. %() expansions are done at
253
194
runtime with vars(self) as dict, so that for
254
195
instance %(name)s can be used in the command.
255
current_checker_command: string; current running checker_command
197
_timeout: Real variable for 'timeout'
198
_interval: Real variable for 'interval'
199
_timeout_milliseconds: Used when calling gobject.timeout_add()
200
_interval_milliseconds: - '' -
259
def _datetime_to_milliseconds(dt):
260
"Convert a datetime.datetime() to milliseconds"
261
return ((dt.days * 24 * 60 * 60 * 1000)
262
+ (dt.seconds * 1000)
263
+ (dt.microseconds // 1000))
265
def timeout_milliseconds(self):
266
"Return the 'timeout' attribute in milliseconds"
267
return self._datetime_to_milliseconds(self.timeout)
269
def interval_milliseconds(self):
270
"Return the 'interval' attribute in milliseconds"
271
return self._datetime_to_milliseconds(self.interval)
273
def __init__(self, name = None, disable_hook=None, config=None):
202
def _set_timeout(self, timeout):
203
"Setter function for 'timeout' attribute"
204
self._timeout = timeout
205
self._timeout_milliseconds = ((self.timeout.days
206
* 24 * 60 * 60 * 1000)
207
+ (self.timeout.seconds * 1000)
208
+ (self.timeout.microseconds
210
timeout = property(lambda self: self._timeout,
213
def _set_interval(self, interval):
214
"Setter function for 'interval' attribute"
215
self._interval = interval
216
self._interval_milliseconds = ((self.interval.days
217
* 24 * 60 * 60 * 1000)
218
+ (self.interval.seconds
220
+ (self.interval.microseconds
222
interval = property(lambda self: self._interval,
225
def __init__(self, name = None, stop_hook=None, config={}):
274
226
"""Note: the 'checker' key in 'config' sets the
275
227
'checker_command' attribute and *not* the 'checker'
280
230
logger.debug(u"Creating client %r", self.name)
281
231
# Uppercase and remove spaces from fingerprint for later
282
232
# comparison purposes with return value from the fingerprint()
284
self.fingerprint = (config[u"fingerprint"].upper()
234
self.fingerprint = config["fingerprint"].upper()\
286
236
logger.debug(u" Fingerprint: %s", self.fingerprint)
287
if u"secret" in config:
288
self.secret = config[u"secret"].decode(u"base64")
289
elif u"secfile" in config:
290
with closing(open(os.path.expanduser
292
(config[u"secfile"])))) as secfile:
293
self.secret = secfile.read()
237
if "secret" in config:
238
self.secret = config["secret"].decode(u"base64")
239
elif "secfile" in config:
240
sf = open(config["secfile"])
241
self.secret = sf.read()
295
244
raise TypeError(u"No secret or secfile for client %s"
297
self.host = config.get(u"host", u"")
298
self.created = datetime.datetime.utcnow()
300
self.last_enabled = None
246
self.host = config.get("host", "")
247
self.created = datetime.datetime.now()
301
248
self.last_checked_ok = None
302
self.timeout = string_to_delta(config[u"timeout"])
303
self.interval = string_to_delta(config[u"interval"])
304
self.disable_hook = disable_hook
249
self.timeout = string_to_delta(config["timeout"])
250
self.interval = string_to_delta(config["interval"])
251
self.stop_hook = stop_hook
305
252
self.checker = None
306
253
self.checker_initiator_tag = None
307
self.disable_initiator_tag = None
254
self.stop_initiator_tag = None
308
255
self.checker_callback_tag = None
309
self.checker_command = config[u"checker"]
310
self.current_checker_command = None
311
self.last_connect = None
256
self.check_command = config["checker"]
314
258
"""Start this client's checker and timeout hooks"""
315
self.last_enabled = datetime.datetime.utcnow()
316
259
# Schedule a new checker to be started an 'interval' from now,
317
260
# and every interval from then on.
318
self.checker_initiator_tag = (gobject.timeout_add
319
(self.interval_milliseconds(),
261
self.checker_initiator_tag = gobject.timeout_add\
262
(self._interval_milliseconds,
321
264
# Also start a new checker *right now*.
322
265
self.start_checker()
323
# Schedule a disable() when 'timeout' has passed
324
self.disable_initiator_tag = (gobject.timeout_add
325
(self.timeout_milliseconds(),
330
"""Disable this client."""
331
if not getattr(self, "enabled", False):
266
# Schedule a stop() when 'timeout' has passed
267
self.stop_initiator_tag = gobject.timeout_add\
268
(self._timeout_milliseconds,
272
The possibility that a client might be restarted is left open,
273
but not currently used."""
274
# If this client doesn't have a secret, it is already stopped.
275
if hasattr(self, "secret") and self.secret:
276
logger.info(u"Stopping client %s", self.name)
333
logger.info(u"Disabling client %s", self.name)
334
if getattr(self, u"disable_initiator_tag", False):
335
gobject.source_remove(self.disable_initiator_tag)
336
self.disable_initiator_tag = None
337
if getattr(self, u"checker_initiator_tag", False):
280
if getattr(self, "stop_initiator_tag", False):
281
gobject.source_remove(self.stop_initiator_tag)
282
self.stop_initiator_tag = None
283
if getattr(self, "checker_initiator_tag", False):
338
284
gobject.source_remove(self.checker_initiator_tag)
339
285
self.checker_initiator_tag = None
340
286
self.stop_checker()
341
if self.disable_hook:
342
self.disable_hook(self)
344
289
# Do not run this again if called by a gobject.timeout_add
347
291
def __del__(self):
348
self.disable_hook = None
351
def checker_callback(self, pid, condition, command):
292
self.stop_hook = None
294
def checker_callback(self, pid, condition):
352
295
"""The checker has completed, so take appropriate actions."""
296
now = datetime.datetime.now()
353
297
self.checker_callback_tag = None
354
298
self.checker = None
355
if os.WIFEXITED(condition):
356
exitstatus = os.WEXITSTATUS(condition)
358
logger.info(u"Checker for %(name)s succeeded",
362
logger.info(u"Checker for %(name)s failed",
299
if os.WIFEXITED(condition) \
300
and (os.WEXITSTATUS(condition) == 0):
301
logger.info(u"Checker for %(name)s succeeded",
303
self.last_checked_ok = now
304
gobject.source_remove(self.stop_initiator_tag)
305
self.stop_initiator_tag = gobject.timeout_add\
306
(self._timeout_milliseconds,
308
elif not os.WIFEXITED(condition):
365
309
logger.warning(u"Checker for %(name)s crashed?",
368
def checked_ok(self):
369
"""Bump up the timeout for this client.
371
This should only be called when the client has been seen,
374
self.last_checked_ok = datetime.datetime.utcnow()
375
gobject.source_remove(self.disable_initiator_tag)
376
self.disable_initiator_tag = (gobject.timeout_add
377
(self.timeout_milliseconds(),
312
logger.info(u"Checker for %(name)s failed",
380
314
def start_checker(self):
381
315
"""Start a new checker subprocess if one is not running.
383
316
If a checker already exists, leave it running and do
385
318
# The reason for not killing a running checker is that if we
462
373
if error.errno != errno.ESRCH: # No such process
464
375
self.checker = None
466
376
def still_valid(self):
467
377
"""Has the timeout not yet passed for this client?"""
468
if not getattr(self, u"enabled", False):
470
now = datetime.datetime.utcnow()
378
now = datetime.datetime.now()
471
379
if self.last_checked_ok is None:
472
380
return now < (self.created + self.timeout)
474
382
return now < (self.last_checked_ok + self.timeout)
477
class ClientDBus(Client, dbus.service.Object):
478
"""A Client class using D-Bus
481
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
483
# dbus.service.Object doesn't use super(), so we can't either.
485
def __init__(self, bus = None, *args, **kwargs):
487
Client.__init__(self, *args, **kwargs)
488
# Only now, when this client is initialized, can it show up on
490
self.dbus_object_path = (dbus.ObjectPath
492
+ self.name.replace(u".", u"_")))
493
dbus.service.Object.__init__(self, self.bus,
494
self.dbus_object_path)
497
def _datetime_to_dbus(dt, variant_level=0):
498
"""Convert a UTC datetime.datetime() to a D-Bus type."""
499
return dbus.String(dt.isoformat(),
500
variant_level=variant_level)
503
oldstate = getattr(self, u"enabled", False)
504
r = Client.enable(self)
505
if oldstate != self.enabled:
507
self.PropertyChanged(dbus.String(u"enabled"),
508
dbus.Boolean(True, variant_level=1))
509
self.PropertyChanged(
510
dbus.String(u"last_enabled"),
511
self._datetime_to_dbus(self.last_enabled,
515
def disable(self, signal = True):
516
oldstate = getattr(self, u"enabled", False)
517
r = Client.disable(self)
518
if signal and oldstate != self.enabled:
520
self.PropertyChanged(dbus.String(u"enabled"),
521
dbus.Boolean(False, variant_level=1))
524
def __del__(self, *args, **kwargs):
526
self.remove_from_connection()
529
if hasattr(dbus.service.Object, u"__del__"):
530
dbus.service.Object.__del__(self, *args, **kwargs)
531
Client.__del__(self, *args, **kwargs)
533
def checker_callback(self, pid, condition, command,
535
self.checker_callback_tag = None
538
self.PropertyChanged(dbus.String(u"checker_running"),
539
dbus.Boolean(False, variant_level=1))
540
if os.WIFEXITED(condition):
541
exitstatus = os.WEXITSTATUS(condition)
543
self.CheckerCompleted(dbus.Int16(exitstatus),
544
dbus.Int64(condition),
545
dbus.String(command))
548
self.CheckerCompleted(dbus.Int16(-1),
549
dbus.Int64(condition),
550
dbus.String(command))
552
return Client.checker_callback(self, pid, condition, command,
555
def checked_ok(self, *args, **kwargs):
556
r = Client.checked_ok(self, *args, **kwargs)
558
self.PropertyChanged(
559
dbus.String(u"last_checked_ok"),
560
(self._datetime_to_dbus(self.last_checked_ok,
564
def start_checker(self, *args, **kwargs):
565
old_checker = self.checker
566
if self.checker is not None:
567
old_checker_pid = self.checker.pid
569
old_checker_pid = None
570
r = Client.start_checker(self, *args, **kwargs)
571
# Only if new checker process was started
572
if (self.checker is not None
573
and old_checker_pid != self.checker.pid):
575
self.CheckerStarted(self.current_checker_command)
576
self.PropertyChanged(
577
dbus.String(u"checker_running"),
578
dbus.Boolean(True, variant_level=1))
581
def stop_checker(self, *args, **kwargs):
582
old_checker = getattr(self, u"checker", None)
583
r = Client.stop_checker(self, *args, **kwargs)
584
if (old_checker is not None
585
and getattr(self, u"checker", None) is None):
586
self.PropertyChanged(dbus.String(u"checker_running"),
587
dbus.Boolean(False, variant_level=1))
590
## D-Bus methods & signals
591
_interface = u"se.bsnet.fukt.Mandos.Client"
594
@dbus.service.method(_interface)
596
return self.checked_ok()
598
# CheckerCompleted - signal
599
@dbus.service.signal(_interface, signature=u"nxs")
600
def CheckerCompleted(self, exitcode, waitstatus, command):
604
# CheckerStarted - signal
605
@dbus.service.signal(_interface, signature=u"s")
606
def CheckerStarted(self, command):
610
# GetAllProperties - method
611
@dbus.service.method(_interface, out_signature=u"a{sv}")
612
def GetAllProperties(self):
614
return dbus.Dictionary({
615
dbus.String(u"name"):
616
dbus.String(self.name, variant_level=1),
617
dbus.String(u"fingerprint"):
618
dbus.String(self.fingerprint, variant_level=1),
619
dbus.String(u"host"):
620
dbus.String(self.host, variant_level=1),
621
dbus.String(u"created"):
622
self._datetime_to_dbus(self.created,
624
dbus.String(u"last_enabled"):
625
(self._datetime_to_dbus(self.last_enabled,
627
if self.last_enabled is not None
628
else dbus.Boolean(False, variant_level=1)),
629
dbus.String(u"enabled"):
630
dbus.Boolean(self.enabled, variant_level=1),
631
dbus.String(u"last_checked_ok"):
632
(self._datetime_to_dbus(self.last_checked_ok,
634
if self.last_checked_ok is not None
635
else dbus.Boolean (False, variant_level=1)),
636
dbus.String(u"timeout"):
637
dbus.UInt64(self.timeout_milliseconds(),
639
dbus.String(u"interval"):
640
dbus.UInt64(self.interval_milliseconds(),
642
dbus.String(u"checker"):
643
dbus.String(self.checker_command,
645
dbus.String(u"checker_running"):
646
dbus.Boolean(self.checker is not None,
648
dbus.String(u"object_path"):
649
dbus.ObjectPath(self.dbus_object_path,
653
# IsStillValid - method
654
@dbus.service.method(_interface, out_signature=u"b")
655
def IsStillValid(self):
656
return self.still_valid()
658
# PropertyChanged - signal
659
@dbus.service.signal(_interface, signature=u"sv")
660
def PropertyChanged(self, property, value):
664
# ReceivedSecret - signal
665
@dbus.service.signal(_interface)
666
def ReceivedSecret(self):
671
@dbus.service.signal(_interface)
676
# SetChecker - method
677
@dbus.service.method(_interface, in_signature=u"s")
678
def SetChecker(self, checker):
679
"D-Bus setter method"
680
self.checker_command = checker
682
self.PropertyChanged(dbus.String(u"checker"),
683
dbus.String(self.checker_command,
687
@dbus.service.method(_interface, in_signature=u"s")
688
def SetHost(self, host):
689
"D-Bus setter method"
692
self.PropertyChanged(dbus.String(u"host"),
693
dbus.String(self.host, variant_level=1))
695
# SetInterval - method
696
@dbus.service.method(_interface, in_signature=u"t")
697
def SetInterval(self, milliseconds):
698
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
700
self.PropertyChanged(dbus.String(u"interval"),
701
(dbus.UInt64(self.interval_milliseconds(),
705
@dbus.service.method(_interface, in_signature=u"ay",
707
def SetSecret(self, secret):
708
"D-Bus setter method"
709
self.secret = str(secret)
711
# SetTimeout - method
712
@dbus.service.method(_interface, in_signature=u"t")
713
def SetTimeout(self, milliseconds):
714
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
716
self.PropertyChanged(dbus.String(u"timeout"),
717
(dbus.UInt64(self.timeout_milliseconds(),
721
@dbus.service.method(_interface)
726
# StartChecker - method
727
@dbus.service.method(_interface)
728
def StartChecker(self):
733
@dbus.service.method(_interface)
738
# StopChecker - method
739
@dbus.service.method(_interface)
740
def StopChecker(self):
746
class ClientHandler(socketserver.BaseRequestHandler, object):
747
"""A class to handle client connections.
749
Instantiated once for each connection to handle it.
385
def peer_certificate(session):
386
"Return the peer's OpenPGP certificate as a bytestring"
387
# If not an OpenPGP certificate...
388
if gnutls.library.functions.gnutls_certificate_type_get\
389
(session._c_object) \
390
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
391
# ...do the normal thing
392
return session.peer_certificate
393
list_size = ctypes.c_uint()
394
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
395
(session._c_object, ctypes.byref(list_size))
396
if list_size.value == 0:
399
return ctypes.string_at(cert.data, cert.size)
402
def fingerprint(openpgp):
403
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
404
# New GnuTLS "datum" with the OpenPGP public key
405
datum = gnutls.library.types.gnutls_datum_t\
406
(ctypes.cast(ctypes.c_char_p(openpgp),
407
ctypes.POINTER(ctypes.c_ubyte)),
408
ctypes.c_uint(len(openpgp)))
409
# New empty GnuTLS certificate
410
crt = gnutls.library.types.gnutls_openpgp_crt_t()
411
gnutls.library.functions.gnutls_openpgp_crt_init\
413
# Import the OpenPGP public key into the certificate
414
gnutls.library.functions.gnutls_openpgp_crt_import\
415
(crt, ctypes.byref(datum),
416
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
417
# Verify the self signature in the key
418
crtverify = ctypes.c_uint();
419
gnutls.library.functions.gnutls_openpgp_crt_verify_self\
420
(crt, 0, ctypes.byref(crtverify))
421
if crtverify.value != 0:
422
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
423
raise gnutls.errors.CertificateSecurityError("Verify failed")
424
# New buffer for the fingerprint
425
buffer = ctypes.create_string_buffer(20)
426
buffer_length = ctypes.c_size_t()
427
# Get the fingerprint from the certificate into the buffer
428
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
429
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
430
# Deinit the certificate
431
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
432
# Convert the buffer to a Python bytestring
433
fpr = ctypes.string_at(buffer, buffer_length.value)
434
# Convert the bytestring to hexadecimal notation
435
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
439
class tcp_handler(SocketServer.BaseRequestHandler, object):
440
"""A TCP request handler class.
441
Instantiated by IPv6_TCPServer for each request to handle it.
750
442
Note: This will run in its own forked process."""
752
444
def handle(self):
753
445
logger.info(u"TCP connection from: %s",
754
unicode(self.client_address))
755
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
756
# Open IPC pipe to parent process
757
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
758
session = (gnutls.connection
759
.ClientSession(self.request,
763
line = self.request.makefile().readline()
764
logger.debug(u"Protocol version: %r", line)
766
if int(line.strip().split()[0]) > 1:
768
except (ValueError, IndexError, RuntimeError), error:
769
logger.error(u"Unknown protocol version: %s", error)
772
# Note: gnutls.connection.X509Credentials is really a
773
# generic GnuTLS certificate credentials object so long as
774
# no X.509 keys are added to it. Therefore, we can use it
775
# here despite using OpenPGP certificates.
777
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
778
# u"+AES-256-CBC", u"+SHA1",
779
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
781
# Use a fallback default, since this MUST be set.
782
priority = self.server.gnutls_priority
785
(gnutls.library.functions
786
.gnutls_priority_set_direct(session._c_object,
791
except gnutls.errors.GNUTLSError, error:
792
logger.warning(u"Handshake failed: %s", error)
793
# Do not run session.bye() here: the session is not
794
# established. Just abandon the request.
796
logger.debug(u"Handshake succeeded")
798
fpr = self.fingerprint(self.peer_certificate(session))
799
except (TypeError, gnutls.errors.GNUTLSError), error:
800
logger.warning(u"Bad certificate: %s", error)
803
logger.debug(u"Fingerprint: %s", fpr)
805
for c in self.server.clients:
806
if c.fingerprint == fpr:
810
ipc.write(u"NOTFOUND %s\n" % fpr)
813
# Have to check if client.still_valid(), since it is
814
# possible that the client timed out while establishing
815
# the GnuTLS session.
816
if not client.still_valid():
817
ipc.write(u"INVALID %s\n" % client.name)
820
ipc.write(u"SENDING %s\n" % client.name)
822
while sent_size < len(client.secret):
823
sent = session.send(client.secret[sent_size:])
824
logger.debug(u"Sent: %d, remaining: %d",
825
sent, len(client.secret)
826
- (sent_size + sent))
831
def peer_certificate(session):
832
"Return the peer's OpenPGP certificate as a bytestring"
833
# If not an OpenPGP certificate...
834
if (gnutls.library.functions
835
.gnutls_certificate_type_get(session._c_object)
836
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
837
# ...do the normal thing
838
return session.peer_certificate
839
list_size = ctypes.c_uint(1)
840
cert_list = (gnutls.library.functions
841
.gnutls_certificate_get_peers
842
(session._c_object, ctypes.byref(list_size)))
843
if not bool(cert_list) and list_size.value != 0:
844
raise gnutls.errors.GNUTLSError(u"error getting peer"
846
if list_size.value == 0:
849
return ctypes.string_at(cert.data, cert.size)
852
def fingerprint(openpgp):
853
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
854
# New GnuTLS "datum" with the OpenPGP public key
855
datum = (gnutls.library.types
856
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
859
ctypes.c_uint(len(openpgp))))
860
# New empty GnuTLS certificate
861
crt = gnutls.library.types.gnutls_openpgp_crt_t()
862
(gnutls.library.functions
863
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
864
# Import the OpenPGP public key into the certificate
865
(gnutls.library.functions
866
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
867
gnutls.library.constants
868
.GNUTLS_OPENPGP_FMT_RAW))
869
# Verify the self signature in the key
870
crtverify = ctypes.c_uint()
871
(gnutls.library.functions
872
.gnutls_openpgp_crt_verify_self(crt, 0,
873
ctypes.byref(crtverify)))
874
if crtverify.value != 0:
875
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
876
raise (gnutls.errors.CertificateSecurityError
878
# New buffer for the fingerprint
879
buf = ctypes.create_string_buffer(20)
880
buf_len = ctypes.c_size_t()
881
# Get the fingerprint from the certificate into the buffer
882
(gnutls.library.functions
883
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
884
ctypes.byref(buf_len)))
885
# Deinit the certificate
886
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
887
# Convert the buffer to a Python bytestring
888
fpr = ctypes.string_at(buf, buf_len.value)
889
# Convert the bytestring to hexadecimal notation
890
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
894
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
895
"""Like socketserver.ForkingMixIn, but also pass a pipe.
897
Assumes a gobject.MainLoop event loop.
899
def process_request(self, request, client_address):
900
"""Overrides and wraps the original process_request().
902
This function creates a new pipe in self.pipe
904
self.pipe = os.pipe()
905
super(ForkingMixInWithPipe,
906
self).process_request(request, client_address)
907
os.close(self.pipe[1]) # close write end
908
# Call "handle_ipc" for both data and EOF events
909
gobject.io_add_watch(self.pipe[0],
910
gobject.IO_IN | gobject.IO_HUP,
912
def handle_ipc(source, condition):
913
"""Dummy function; override as necessary"""
918
class IPv6_TCPServer(ForkingMixInWithPipe,
919
socketserver.TCPServer, object):
920
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
446
unicode(self.client_address))
447
session = gnutls.connection.ClientSession\
448
(self.request, gnutls.connection.X509Credentials())
450
line = self.request.makefile().readline()
451
logger.debug(u"Protocol version: %r", line)
453
if int(line.strip().split()[0]) > 1:
455
except (ValueError, IndexError, RuntimeError), error:
456
logger.error(u"Unknown protocol version: %s", error)
459
# Note: gnutls.connection.X509Credentials is really a generic
460
# GnuTLS certificate credentials object so long as no X.509
461
# keys are added to it. Therefore, we can use it here despite
462
# using OpenPGP certificates.
464
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
465
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
467
priority = "NORMAL" # Fallback default, since this
469
if self.server.settings["priority"]:
470
priority = self.server.settings["priority"]
471
gnutls.library.functions.gnutls_priority_set_direct\
472
(session._c_object, priority, None);
476
except gnutls.errors.GNUTLSError, error:
477
logger.warning(u"Handshake failed: %s", error)
478
# Do not run session.bye() here: the session is not
479
# established. Just abandon the request.
482
fpr = fingerprint(peer_certificate(session))
483
except (TypeError, gnutls.errors.GNUTLSError), error:
484
logger.warning(u"Bad certificate: %s", error)
487
logger.debug(u"Fingerprint: %s", fpr)
489
for c in self.server.clients:
490
if c.fingerprint == fpr:
494
logger.warning(u"Client not found for fingerprint: %s",
498
# Have to check if client.still_valid(), since it is possible
499
# that the client timed out while establishing the GnuTLS
501
if not client.still_valid():
502
logger.warning(u"Client %(name)s is invalid",
507
while sent_size < len(client.secret):
508
sent = session.send(client.secret[sent_size:])
509
logger.debug(u"Sent: %d, remaining: %d",
510
sent, len(client.secret)
511
- (sent_size + sent))
516
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
517
"""IPv6 TCP server. Accepts 'None' as address and/or port.
519
settings: Server settings
520
clients: Set() of Client objects
923
521
enabled: Boolean; whether this server is activated yet
924
interface: None or a network interface name (string)
925
use_ipv6: Boolean; to use IPv6 or not
927
clients: set of Client objects
928
gnutls_priority GnuTLS priority string
929
use_dbus: Boolean; to emit D-Bus signals or not
931
def __init__(self, server_address, RequestHandlerClass,
932
interface=None, use_ipv6=True, clients=None,
933
gnutls_priority=None, use_dbus=True):
523
address_family = socket.AF_INET6
524
def __init__(self, *args, **kwargs):
525
if "settings" in kwargs:
526
self.settings = kwargs["settings"]
527
del kwargs["settings"]
528
if "clients" in kwargs:
529
self.clients = kwargs["clients"]
530
del kwargs["clients"]
934
531
self.enabled = False
935
self.interface = interface
937
self.address_family = socket.AF_INET6
938
self.clients = clients
939
self.use_dbus = use_dbus
940
self.gnutls_priority = gnutls_priority
941
socketserver.TCPServer.__init__(self, server_address,
532
return super(type(self), self).__init__(*args, **kwargs)
943
533
def server_bind(self):
944
534
"""This overrides the normal server_bind() function
945
535
to bind to an interface if one was specified, and also NOT to
946
536
bind to an address or port if they were not specified."""
947
if self.interface is not None:
537
if self.settings["interface"]:
538
# 25 is from /usr/include/asm-i486/socket.h
539
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
949
541
self.socket.setsockopt(socket.SOL_SOCKET,
951
str(self.interface + u'\0'))
543
self.settings["interface"])
952
544
except socket.error, error:
953
545
if error[0] == errno.EPERM:
954
546
logger.error(u"No permission to"
955
547
u" bind to interface %s",
548
self.settings["interface"])
959
551
# Only bind(2) the socket if we really need to.
960
552
if self.server_address[0] or self.server_address[1]:
961
553
if not self.server_address[0]:
962
if self.address_family == socket.AF_INET6:
963
any_address = u"::" # in6addr_any
965
any_address = socket.INADDR_ANY
966
self.server_address = (any_address,
555
self.server_address = (in6addr_any,
967
556
self.server_address[1])
968
557
elif not self.server_address[1]:
969
558
self.server_address = (self.server_address[0],
560
# if self.settings["interface"]:
972
561
# self.server_address = (self.server_address[0],
977
return socketserver.TCPServer.server_bind(self)
567
return super(type(self), self).server_bind()
978
568
def server_activate(self):
980
return socketserver.TCPServer.server_activate(self)
570
return super(type(self), self).server_activate()
981
571
def enable(self):
982
572
self.enabled = True
983
def handle_ipc(self, source, condition, file_objects={}):
985
gobject.IO_IN: u"IN", # There is data to read.
986
gobject.IO_OUT: u"OUT", # Data can be written (without
988
gobject.IO_PRI: u"PRI", # There is urgent data to read.
989
gobject.IO_ERR: u"ERR", # Error condition.
990
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
991
# broken, usually for pipes and
994
conditions_string = ' | '.join(name
996
condition_names.iteritems()
998
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1001
# Turn the pipe file descriptor into a Python file object
1002
if source not in file_objects:
1003
file_objects[source] = os.fdopen(source, u"r", 1)
1005
# Read a line from the file object
1006
cmdline = file_objects[source].readline()
1007
if not cmdline: # Empty line means end of file
1008
# close the IPC pipe
1009
file_objects[source].close()
1010
del file_objects[source]
1012
# Stop calling this function
1015
logger.debug(u"IPC command: %r", cmdline)
1017
# Parse and act on command
1018
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1020
if cmd == u"NOTFOUND":
1021
logger.warning(u"Client not found for fingerprint: %s",
1025
mandos_dbus_service.ClientNotFound(args)
1026
elif cmd == u"INVALID":
1027
for client in self.clients:
1028
if client.name == args:
1029
logger.warning(u"Client %s is invalid", args)
1035
logger.error(u"Unknown client %s is invalid", args)
1036
elif cmd == u"SENDING":
1037
for client in self.clients:
1038
if client.name == args:
1039
logger.info(u"Sending secret to %s", client.name)
1043
client.ReceivedSecret()
1046
logger.error(u"Sending secret to unknown client %s",
1049
logger.error(u"Unknown IPC command: %r", cmdline)
1051
# Keep calling this function
1055
575
def string_to_delta(interval):
1056
576
"""Parse a string and return a datetime.timedelta
1058
>>> string_to_delta(u'7d')
578
>>> string_to_delta('7d')
1059
579
datetime.timedelta(7)
1060
>>> string_to_delta(u'60s')
580
>>> string_to_delta('60s')
1061
581
datetime.timedelta(0, 60)
1062
>>> string_to_delta(u'60m')
582
>>> string_to_delta('60m')
1063
583
datetime.timedelta(0, 3600)
1064
>>> string_to_delta(u'24h')
584
>>> string_to_delta('24h')
1065
585
datetime.timedelta(1)
1066
586
>>> string_to_delta(u'1w')
1067
587
datetime.timedelta(7)
1068
>>> string_to_delta(u'5m 30s')
588
>>> string_to_delta('5m 30s')
1069
589
datetime.timedelta(0, 330)
1071
591
timevalue = datetime.timedelta(0)
1072
592
for s in interval.split():
1074
suffix = unicode(s[-1])
594
suffix=unicode(s[-1])
1076
596
if suffix == u"d":
1077
597
delta = datetime.timedelta(value)
1078
598
elif suffix == u"s":
1179
716
# Default values for config file for server-global settings
1180
server_defaults = { u"interface": u"",
1185
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1186
u"servicename": u"Mandos",
1187
u"use_dbus": u"True",
1188
u"use_ipv6": u"True",
717
server_defaults = { "interface": "",
722
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
723
"servicename": "Mandos",
1191
726
# Parse config file for server-global settings
1192
server_config = configparser.SafeConfigParser(server_defaults)
727
server_config = ConfigParser.SafeConfigParser(server_defaults)
1193
728
del server_defaults
1194
server_config.read(os.path.join(options.configdir,
729
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1196
730
# Convert the SafeConfigParser object to a dict
1197
731
server_settings = server_config.defaults()
1198
# Use the appropriate methods on the non-string config options
1199
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1200
server_settings[option] = server_config.getboolean(u"DEFAULT",
1202
if server_settings["port"]:
1203
server_settings["port"] = server_config.getint(u"DEFAULT",
732
# Use getboolean on the boolean config option
733
server_settings["debug"] = server_config.getboolean\
1205
735
del server_config
1207
737
# Override the settings from the config file with command line
1208
738
# options, if set.
1209
for option in (u"interface", u"address", u"port", u"debug",
1210
u"priority", u"servicename", u"configdir",
1211
u"use_dbus", u"use_ipv6"):
739
for option in ("interface", "address", "port", "debug",
740
"priority", "servicename", "configdir"):
1212
741
value = getattr(options, option)
1213
742
if value is not None:
1214
743
server_settings[option] = value
1216
# Force all strings to be unicode
1217
for option in server_settings.keys():
1218
if type(server_settings[option]) is str:
1219
server_settings[option] = unicode(server_settings[option])
1220
745
# Now we have our good server settings in "server_settings"
1222
##################################################################
1225
debug = server_settings[u"debug"]
1226
use_dbus = server_settings[u"use_dbus"]
1227
use_ipv6 = server_settings[u"use_ipv6"]
747
debug = server_settings["debug"]
1230
750
syslogger.setLevel(logging.WARNING)
1231
751
console.setLevel(logging.WARNING)
1233
if server_settings[u"servicename"] != u"Mandos":
1234
syslogger.setFormatter(logging.Formatter
1235
(u'Mandos (%s) [%%(process)d]:'
1236
u' %%(levelname)s: %%(message)s'
1237
% server_settings[u"servicename"]))
753
if server_settings["servicename"] != "Mandos":
754
syslogger.setFormatter(logging.Formatter\
755
('Mandos (%s): %%(levelname)s:'
757
% server_settings["servicename"]))
1239
759
# Parse config file with clients
1240
client_defaults = { u"timeout": u"1h",
1242
u"checker": u"fping -q -- %%(host)s",
760
client_defaults = { "timeout": "1h",
762
"checker": "fping -q -- %(host)s",
1245
client_config = configparser.SafeConfigParser(client_defaults)
1246
client_config.read(os.path.join(server_settings[u"configdir"],
1249
global mandos_dbus_service
1250
mandos_dbus_service = None
1253
tcp_server = IPv6_TCPServer((server_settings[u"address"],
1254
server_settings[u"port"]),
1257
server_settings[u"interface"],
1261
server_settings[u"priority"],
1263
pidfilename = u"/var/run/mandos.pid"
1265
pidfile = open(pidfilename, u"w")
1267
logger.error(u"Could not open file %r", pidfilename)
1270
uid = pwd.getpwnam(u"_mandos").pw_uid
1271
gid = pwd.getpwnam(u"_mandos").pw_gid
1274
uid = pwd.getpwnam(u"mandos").pw_uid
1275
gid = pwd.getpwnam(u"mandos").pw_gid
1278
uid = pwd.getpwnam(u"nobody").pw_uid
1279
gid = pwd.getpwnam(u"nobody").pw_gid
765
client_config = ConfigParser.SafeConfigParser(client_defaults)
766
client_config.read(os.path.join(server_settings["configdir"],
770
tcp_server = IPv6_TCPServer((server_settings["address"],
771
server_settings["port"]),
773
settings=server_settings,
775
pidfilename = "/var/run/mandos.pid"
777
pidfile = open(pidfilename, "w")
778
except IOError, error:
779
logger.error("Could not open file %r", pidfilename)
784
uid = pwd.getpwnam("mandos").pw_uid
787
uid = pwd.getpwnam("nobody").pw_uid
791
gid = pwd.getpwnam("mandos").pw_gid
794
gid = pwd.getpwnam("nogroup").pw_gid
1286
800
except OSError, error:
1287
801
if error[0] != errno.EPERM:
1290
# Enable all possible GnuTLS debugging
1292
# "Use a log level over 10 to enable all debugging options."
1294
gnutls.library.functions.gnutls_global_set_log_level(11)
1296
@gnutls.library.types.gnutls_log_func
1297
def debug_gnutls(level, string):
1298
logger.debug(u"GnuTLS: %s", string[:-1])
1300
(gnutls.library.functions
1301
.gnutls_global_set_log_function(debug_gnutls))
805
service = AvahiService(name = server_settings["servicename"],
806
type = "_mandos._tcp", );
807
if server_settings["interface"]:
808
service.interface = if_nametoindex\
809
(server_settings["interface"])
1303
811
global main_loop
1304
814
# From the Avahi example code
1305
815
DBusGMainLoop(set_as_default=True )
1306
816
main_loop = gobject.MainLoop()
1307
817
bus = dbus.SystemBus()
818
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
819
avahi.DBUS_PATH_SERVER),
820
avahi.DBUS_INTERFACE_SERVER)
1308
821
# End of Avahi example code
1310
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1311
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1312
service = AvahiService(name = server_settings[u"servicename"],
1313
servicetype = u"_mandos._tcp",
1314
protocol = protocol, bus = bus)
1315
if server_settings["interface"]:
1316
service.interface = (if_nametoindex
1317
(str(server_settings[u"interface"])))
1319
client_class = Client
1321
client_class = functools.partial(ClientDBus, bus = bus)
1323
client_class(name = section,
1324
config= dict(client_config.items(section)))
1325
for section in client_config.sections()))
823
def remove_from_clients(client):
824
clients.remove(client)
826
logger.critical(u"No clients left, exiting")
829
clients.update(Set(Client(name = section,
830
stop_hook = remove_from_clients,
832
= dict(client_config.items(section)))
833
for section in client_config.sections()))
1327
logger.warning(u"No clients defined")
835
logger.critical(u"No clients defined")
1330
839
# Redirect stdin so all checkers get /dev/null