188
169
# End of Avahi example code
191
def _datetime_to_dbus(dt, variant_level=0):
192
"""Convert a UTC datetime.datetime() to a D-Bus type."""
193
return dbus.String(dt.isoformat(), variant_level=variant_level)
196
172
class Client(object):
197
173
"""A representation of a client host served by this server.
200
name: string; from the config file, used in log messages and
175
name: string; from the config file, used in log messages
202
176
fingerprint: string (40 or 32 hexadecimal digits); used to
203
177
uniquely identify the client
204
secret: bytestring; sent verbatim (over TLS) to client
205
host: string; available for use by the checker command
206
created: datetime.datetime(); (UTC) object creation
207
last_enabled: datetime.datetime(); (UTC)
209
last_checked_ok: datetime.datetime(); (UTC) or None
210
timeout: datetime.timedelta(); How long from last_checked_ok
211
until this client is invalid
212
interval: datetime.timedelta(); How often to start a new checker
213
disable_hook: If set, called by disable() as disable_hook(self)
214
checker: subprocess.Popen(); a running checker process used
215
to see if the client lives.
216
'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.
217
189
checker_initiator_tag: a gobject event source tag, or None
218
disable_initiator_tag: - '' -
190
stop_initiator_tag: - '' -
219
191
checker_callback_tag: - '' -
220
192
checker_command: string; External command which is run to check if
221
193
client lives. %() expansions are done at
222
194
runtime with vars(self) as dict, so that for
223
195
instance %(name)s can be used in the command.
224
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: - '' -
228
def _datetime_to_milliseconds(dt):
229
"Convert a datetime.datetime() to milliseconds"
230
return ((dt.days * 24 * 60 * 60 * 1000)
231
+ (dt.seconds * 1000)
232
+ (dt.microseconds // 1000))
234
def timeout_milliseconds(self):
235
"Return the 'timeout' attribute in milliseconds"
236
return self._datetime_to_milliseconds(self.timeout)
238
def interval_milliseconds(self):
239
"Return the 'interval' attribute in milliseconds"
240
return self._datetime_to_milliseconds(self.interval)
242
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={}):
243
226
"""Note: the 'checker' key in 'config' sets the
244
227
'checker_command' attribute and *not* the 'checker'
249
230
logger.debug(u"Creating client %r", self.name)
250
231
# Uppercase and remove spaces from fingerprint for later
251
232
# comparison purposes with return value from the fingerprint()
253
self.fingerprint = (config[u"fingerprint"].upper()
234
self.fingerprint = config["fingerprint"].upper()\
255
236
logger.debug(u" Fingerprint: %s", self.fingerprint)
256
if u"secret" in config:
257
self.secret = config[u"secret"].decode(u"base64")
258
elif u"secfile" in config:
259
with closing(open(os.path.expanduser
261
(config[u"secfile"])))) as secfile:
262
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()
264
244
raise TypeError(u"No secret or secfile for client %s"
266
self.host = config.get(u"host", u"")
267
self.created = datetime.datetime.utcnow()
269
self.last_enabled = None
246
self.host = config.get("host", "")
247
self.created = datetime.datetime.now()
270
248
self.last_checked_ok = None
271
self.timeout = string_to_delta(config[u"timeout"])
272
self.interval = string_to_delta(config[u"interval"])
273
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
274
252
self.checker = None
275
253
self.checker_initiator_tag = None
276
self.disable_initiator_tag = None
254
self.stop_initiator_tag = None
277
255
self.checker_callback_tag = None
278
self.checker_command = config[u"checker"]
279
self.current_checker_command = None
280
self.last_connect = None
256
self.check_command = config["checker"]
283
258
"""Start this client's checker and timeout hooks"""
284
self.last_enabled = datetime.datetime.utcnow()
285
259
# Schedule a new checker to be started an 'interval' from now,
286
260
# and every interval from then on.
287
self.checker_initiator_tag = (gobject.timeout_add
288
(self.interval_milliseconds(),
261
self.checker_initiator_tag = gobject.timeout_add\
262
(self._interval_milliseconds,
290
264
# Also start a new checker *right now*.
291
265
self.start_checker()
292
# Schedule a disable() when 'timeout' has passed
293
self.disable_initiator_tag = (gobject.timeout_add
294
(self.timeout_milliseconds(),
299
"""Disable this client."""
300
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)
302
logger.info(u"Disabling client %s", self.name)
303
if getattr(self, u"disable_initiator_tag", False):
304
gobject.source_remove(self.disable_initiator_tag)
305
self.disable_initiator_tag = None
306
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):
307
284
gobject.source_remove(self.checker_initiator_tag)
308
285
self.checker_initiator_tag = None
309
286
self.stop_checker()
310
if self.disable_hook:
311
self.disable_hook(self)
313
289
# Do not run this again if called by a gobject.timeout_add
316
291
def __del__(self):
317
self.disable_hook = None
320
def checker_callback(self, pid, condition, command):
292
self.stop_hook = None
294
def checker_callback(self, pid, condition):
321
295
"""The checker has completed, so take appropriate actions."""
296
now = datetime.datetime.now()
322
297
self.checker_callback_tag = None
323
298
self.checker = None
324
if os.WIFEXITED(condition):
325
exitstatus = os.WEXITSTATUS(condition)
327
logger.info(u"Checker for %(name)s succeeded",
331
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):
334
309
logger.warning(u"Checker for %(name)s crashed?",
337
def checked_ok(self):
338
"""Bump up the timeout for this client.
340
This should only be called when the client has been seen,
343
self.last_checked_ok = datetime.datetime.utcnow()
344
gobject.source_remove(self.disable_initiator_tag)
345
self.disable_initiator_tag = (gobject.timeout_add
346
(self.timeout_milliseconds(),
312
logger.info(u"Checker for %(name)s failed",
349
314
def start_checker(self):
350
315
"""Start a new checker subprocess if one is not running.
352
316
If a checker already exists, leave it running and do
354
318
# The reason for not killing a running checker is that if we
431
373
if error.errno != errno.ESRCH: # No such process
433
375
self.checker = None
435
376
def still_valid(self):
436
377
"""Has the timeout not yet passed for this client?"""
437
if not getattr(self, u"enabled", False):
439
now = datetime.datetime.utcnow()
378
now = datetime.datetime.now()
440
379
if self.last_checked_ok is None:
441
380
return now < (self.created + self.timeout)
443
382
return now < (self.last_checked_ok + self.timeout)
446
class ClientDBus(Client, dbus.service.Object):
447
"""A Client class using D-Bus
450
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
452
# dbus.service.Object doesn't use super(), so we can't either.
454
def __init__(self, *args, **kwargs):
455
Client.__init__(self, *args, **kwargs)
456
# Only now, when this client is initialized, can it show up on
458
self.dbus_object_path = (dbus.ObjectPath
460
+ self.name.replace(u".", u"_")))
461
dbus.service.Object.__init__(self, bus,
462
self.dbus_object_path)
464
oldstate = getattr(self, u"enabled", False)
465
r = Client.enable(self)
466
if oldstate != self.enabled:
468
self.PropertyChanged(dbus.String(u"enabled"),
469
dbus.Boolean(True, variant_level=1))
470
self.PropertyChanged(dbus.String(u"last_enabled"),
471
(_datetime_to_dbus(self.last_enabled,
475
def disable(self, signal = True):
476
oldstate = getattr(self, u"enabled", False)
477
r = Client.disable(self)
478
if signal and oldstate != self.enabled:
480
self.PropertyChanged(dbus.String(u"enabled"),
481
dbus.Boolean(False, variant_level=1))
484
def __del__(self, *args, **kwargs):
486
self.remove_from_connection()
489
if hasattr(dbus.service.Object, u"__del__"):
490
dbus.service.Object.__del__(self, *args, **kwargs)
491
Client.__del__(self, *args, **kwargs)
493
def checker_callback(self, pid, condition, command,
495
self.checker_callback_tag = None
498
self.PropertyChanged(dbus.String(u"checker_running"),
499
dbus.Boolean(False, variant_level=1))
500
if os.WIFEXITED(condition):
501
exitstatus = os.WEXITSTATUS(condition)
503
self.CheckerCompleted(dbus.Int16(exitstatus),
504
dbus.Int64(condition),
505
dbus.String(command))
508
self.CheckerCompleted(dbus.Int16(-1),
509
dbus.Int64(condition),
510
dbus.String(command))
512
return Client.checker_callback(self, pid, condition, command,
515
def checked_ok(self, *args, **kwargs):
516
r = Client.checked_ok(self, *args, **kwargs)
518
self.PropertyChanged(
519
dbus.String(u"last_checked_ok"),
520
(_datetime_to_dbus(self.last_checked_ok,
524
def start_checker(self, *args, **kwargs):
525
old_checker = self.checker
526
if self.checker is not None:
527
old_checker_pid = self.checker.pid
529
old_checker_pid = None
530
r = Client.start_checker(self, *args, **kwargs)
531
# Only if new checker process was started
532
if (self.checker is not None
533
and old_checker_pid != self.checker.pid):
535
self.CheckerStarted(self.current_checker_command)
536
self.PropertyChanged(
537
dbus.String(u"checker_running"),
538
dbus.Boolean(True, variant_level=1))
541
def stop_checker(self, *args, **kwargs):
542
old_checker = getattr(self, u"checker", None)
543
r = Client.stop_checker(self, *args, **kwargs)
544
if (old_checker is not None
545
and getattr(self, u"checker", None) is None):
546
self.PropertyChanged(dbus.String(u"checker_running"),
547
dbus.Boolean(False, variant_level=1))
550
## D-Bus methods & signals
551
_interface = u"se.bsnet.fukt.Mandos.Client"
554
@dbus.service.method(_interface)
556
return self.checked_ok()
558
# CheckerCompleted - signal
559
@dbus.service.signal(_interface, signature=u"nxs")
560
def CheckerCompleted(self, exitcode, waitstatus, command):
564
# CheckerStarted - signal
565
@dbus.service.signal(_interface, signature=u"s")
566
def CheckerStarted(self, command):
570
# GetAllProperties - method
571
@dbus.service.method(_interface, out_signature=u"a{sv}")
572
def GetAllProperties(self):
574
return dbus.Dictionary({
575
dbus.String(u"name"):
576
dbus.String(self.name, variant_level=1),
577
dbus.String(u"fingerprint"):
578
dbus.String(self.fingerprint, variant_level=1),
579
dbus.String(u"host"):
580
dbus.String(self.host, variant_level=1),
581
dbus.String(u"created"):
582
_datetime_to_dbus(self.created, variant_level=1),
583
dbus.String(u"last_enabled"):
584
(_datetime_to_dbus(self.last_enabled,
586
if self.last_enabled is not None
587
else dbus.Boolean(False, variant_level=1)),
588
dbus.String(u"enabled"):
589
dbus.Boolean(self.enabled, variant_level=1),
590
dbus.String(u"last_checked_ok"):
591
(_datetime_to_dbus(self.last_checked_ok,
593
if self.last_checked_ok is not None
594
else dbus.Boolean (False, variant_level=1)),
595
dbus.String(u"timeout"):
596
dbus.UInt64(self.timeout_milliseconds(),
598
dbus.String(u"interval"):
599
dbus.UInt64(self.interval_milliseconds(),
601
dbus.String(u"checker"):
602
dbus.String(self.checker_command,
604
dbus.String(u"checker_running"):
605
dbus.Boolean(self.checker is not None,
607
dbus.String(u"object_path"):
608
dbus.ObjectPath(self.dbus_object_path,
612
# IsStillValid - method
613
@dbus.service.method(_interface, out_signature=u"b")
614
def IsStillValid(self):
615
return self.still_valid()
617
# PropertyChanged - signal
618
@dbus.service.signal(_interface, signature=u"sv")
619
def PropertyChanged(self, property, value):
623
# ReceivedSecret - signal
624
@dbus.service.signal(_interface)
625
def ReceivedSecret(self):
630
@dbus.service.signal(_interface)
635
# SetChecker - method
636
@dbus.service.method(_interface, in_signature=u"s")
637
def SetChecker(self, checker):
638
"D-Bus setter method"
639
self.checker_command = checker
641
self.PropertyChanged(dbus.String(u"checker"),
642
dbus.String(self.checker_command,
646
@dbus.service.method(_interface, in_signature=u"s")
647
def SetHost(self, host):
648
"D-Bus setter method"
651
self.PropertyChanged(dbus.String(u"host"),
652
dbus.String(self.host, variant_level=1))
654
# SetInterval - method
655
@dbus.service.method(_interface, in_signature=u"t")
656
def SetInterval(self, milliseconds):
657
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
659
self.PropertyChanged(dbus.String(u"interval"),
660
(dbus.UInt64(self.interval_milliseconds(),
664
@dbus.service.method(_interface, in_signature=u"ay",
666
def SetSecret(self, secret):
667
"D-Bus setter method"
668
self.secret = str(secret)
670
# SetTimeout - method
671
@dbus.service.method(_interface, in_signature=u"t")
672
def SetTimeout(self, milliseconds):
673
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
675
self.PropertyChanged(dbus.String(u"timeout"),
676
(dbus.UInt64(self.timeout_milliseconds(),
680
@dbus.service.method(_interface)
685
# StartChecker - method
686
@dbus.service.method(_interface)
687
def StartChecker(self):
692
@dbus.service.method(_interface)
697
# StopChecker - method
698
@dbus.service.method(_interface)
699
def StopChecker(self):
705
class ClientHandler(SocketServer.BaseRequestHandler, object):
706
"""A class to handle client connections.
708
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.
709
442
Note: This will run in its own forked process."""
711
444
def handle(self):
712
445
logger.info(u"TCP connection from: %s",
713
unicode(self.client_address))
714
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
715
# Open IPC pipe to parent process
716
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
717
session = (gnutls.connection
718
.ClientSession(self.request,
722
line = self.request.makefile().readline()
723
logger.debug(u"Protocol version: %r", line)
725
if int(line.strip().split()[0]) > 1:
727
except (ValueError, IndexError, RuntimeError), error:
728
logger.error(u"Unknown protocol version: %s", error)
731
# Note: gnutls.connection.X509Credentials is really a
732
# generic GnuTLS certificate credentials object so long as
733
# no X.509 keys are added to it. Therefore, we can use it
734
# here despite using OpenPGP certificates.
736
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
737
# u"+AES-256-CBC", u"+SHA1",
738
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
740
# Use a fallback default, since this MUST be set.
741
priority = self.server.gnutls_priority
744
(gnutls.library.functions
745
.gnutls_priority_set_direct(session._c_object,
750
except gnutls.errors.GNUTLSError, error:
751
logger.warning(u"Handshake failed: %s", error)
752
# Do not run session.bye() here: the session is not
753
# established. Just abandon the request.
755
logger.debug(u"Handshake succeeded")
757
fpr = self.fingerprint(self.peer_certificate(session))
758
except (TypeError, gnutls.errors.GNUTLSError), error:
759
logger.warning(u"Bad certificate: %s", error)
762
logger.debug(u"Fingerprint: %s", fpr)
764
for c in self.server.clients:
765
if c.fingerprint == fpr:
769
ipc.write(u"NOTFOUND %s\n" % fpr)
772
# Have to check if client.still_valid(), since it is
773
# possible that the client timed out while establishing
774
# the GnuTLS session.
775
if not client.still_valid():
776
ipc.write(u"INVALID %s\n" % client.name)
779
ipc.write(u"SENDING %s\n" % client.name)
781
while sent_size < len(client.secret):
782
sent = session.send(client.secret[sent_size:])
783
logger.debug(u"Sent: %d, remaining: %d",
784
sent, len(client.secret)
785
- (sent_size + sent))
790
def peer_certificate(session):
791
"Return the peer's OpenPGP certificate as a bytestring"
792
# If not an OpenPGP certificate...
793
if (gnutls.library.functions
794
.gnutls_certificate_type_get(session._c_object)
795
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
796
# ...do the normal thing
797
return session.peer_certificate
798
list_size = ctypes.c_uint(1)
799
cert_list = (gnutls.library.functions
800
.gnutls_certificate_get_peers
801
(session._c_object, ctypes.byref(list_size)))
802
if not bool(cert_list) and list_size.value != 0:
803
raise gnutls.errors.GNUTLSError(u"error getting peer"
805
if list_size.value == 0:
808
return ctypes.string_at(cert.data, cert.size)
811
def fingerprint(openpgp):
812
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
813
# New GnuTLS "datum" with the OpenPGP public key
814
datum = (gnutls.library.types
815
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
818
ctypes.c_uint(len(openpgp))))
819
# New empty GnuTLS certificate
820
crt = gnutls.library.types.gnutls_openpgp_crt_t()
821
(gnutls.library.functions
822
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
823
# Import the OpenPGP public key into the certificate
824
(gnutls.library.functions
825
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
826
gnutls.library.constants
827
.GNUTLS_OPENPGP_FMT_RAW))
828
# Verify the self signature in the key
829
crtverify = ctypes.c_uint()
830
(gnutls.library.functions
831
.gnutls_openpgp_crt_verify_self(crt, 0,
832
ctypes.byref(crtverify)))
833
if crtverify.value != 0:
834
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
835
raise (gnutls.errors.CertificateSecurityError
837
# New buffer for the fingerprint
838
buf = ctypes.create_string_buffer(20)
839
buf_len = ctypes.c_size_t()
840
# Get the fingerprint from the certificate into the buffer
841
(gnutls.library.functions
842
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
843
ctypes.byref(buf_len)))
844
# Deinit the certificate
845
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
846
# Convert the buffer to a Python bytestring
847
fpr = ctypes.string_at(buf, buf_len.value)
848
# Convert the bytestring to hexadecimal notation
849
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
853
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
854
"""Like SocketServer.ForkingMixIn, but also pass a pipe.
856
Assumes a gobject.MainLoop event loop.
858
def process_request(self, request, client_address):
859
"""Overrides and wraps the original process_request().
861
This function creates a new pipe in self.pipe
863
self.pipe = os.pipe()
864
super(ForkingMixInWithPipe,
865
self).process_request(request, client_address)
866
os.close(self.pipe[1]) # close write end
867
# Call "handle_ipc" for both data and EOF events
868
gobject.io_add_watch(self.pipe[0],
869
gobject.IO_IN | gobject.IO_HUP,
871
def handle_ipc(source, condition):
872
"""Dummy function; override as necessary"""
877
class IPv6_TCPServer(ForkingMixInWithPipe,
878
SocketServer.TCPServer, object):
879
"""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
882
521
enabled: Boolean; whether this server is activated yet
883
interface: None or a network interface name (string)
884
use_ipv6: Boolean; to use IPv6 or not
886
clients: Set() of Client objects
887
gnutls_priority GnuTLS priority string
888
use_dbus: Boolean; to emit D-Bus signals or not
890
def __init__(self, server_address, RequestHandlerClass,
891
interface=None, use_ipv6=True, clients=None,
892
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"]
893
531
self.enabled = False
894
self.interface = interface
896
self.address_family = socket.AF_INET6
897
self.clients = clients
898
self.use_dbus = use_dbus
899
self.gnutls_priority = gnutls_priority
900
SocketServer.TCPServer.__init__(self, server_address,
532
return super(type(self), self).__init__(*args, **kwargs)
902
533
def server_bind(self):
903
534
"""This overrides the normal server_bind() function
904
535
to bind to an interface if one was specified, and also NOT to
905
536
bind to an address or port if they were not specified."""
906
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)
908
541
self.socket.setsockopt(socket.SOL_SOCKET,
910
str(self.interface + u'\0'))
543
self.settings["interface"])
911
544
except socket.error, error:
912
545
if error[0] == errno.EPERM:
913
546
logger.error(u"No permission to"
914
547
u" bind to interface %s",
548
self.settings["interface"])
918
551
# Only bind(2) the socket if we really need to.
919
552
if self.server_address[0] or self.server_address[1]:
920
553
if not self.server_address[0]:
921
if self.address_family == socket.AF_INET6:
922
any_address = u"::" # in6addr_any
924
any_address = socket.INADDR_ANY
925
self.server_address = (any_address,
555
self.server_address = (in6addr_any,
926
556
self.server_address[1])
927
557
elif not self.server_address[1]:
928
558
self.server_address = (self.server_address[0],
560
# if self.settings["interface"]:
931
561
# self.server_address = (self.server_address[0],
936
return SocketServer.TCPServer.server_bind(self)
567
return super(type(self), self).server_bind()
937
568
def server_activate(self):
939
return SocketServer.TCPServer.server_activate(self)
570
return super(type(self), self).server_activate()
940
571
def enable(self):
941
572
self.enabled = True
942
def handle_ipc(self, source, condition, file_objects={}):
944
gobject.IO_IN: u"IN", # There is data to read.
945
gobject.IO_OUT: u"OUT", # Data can be written (without
947
gobject.IO_PRI: u"PRI", # There is urgent data to read.
948
gobject.IO_ERR: u"ERR", # Error condition.
949
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
950
# broken, usually for pipes and
953
conditions_string = ' | '.join(name
955
condition_names.iteritems()
957
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
960
# Turn the pipe file descriptor into a Python file object
961
if source not in file_objects:
962
file_objects[source] = os.fdopen(source, u"r", 1)
964
# Read a line from the file object
965
cmdline = file_objects[source].readline()
966
if not cmdline: # Empty line means end of file
968
file_objects[source].close()
969
del file_objects[source]
971
# Stop calling this function
974
logger.debug(u"IPC command: %r", cmdline)
976
# Parse and act on command
977
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
979
if cmd == u"NOTFOUND":
980
logger.warning(u"Client not found for fingerprint: %s",
984
mandos_dbus_service.ClientNotFound(args)
985
elif cmd == u"INVALID":
986
for client in self.clients:
987
if client.name == args:
988
logger.warning(u"Client %s is invalid", args)
994
logger.error(u"Unknown client %s is invalid", args)
995
elif cmd == u"SENDING":
996
for client in self.clients:
997
if client.name == args:
998
logger.info(u"Sending secret to %s", client.name)
1002
client.ReceivedSecret()
1005
logger.error(u"Sending secret to unknown client %s",
1008
logger.error(u"Unknown IPC command: %r", cmdline)
1010
# Keep calling this function
1014
575
def string_to_delta(interval):
1015
576
"""Parse a string and return a datetime.timedelta
1017
>>> string_to_delta(u'7d')
578
>>> string_to_delta('7d')
1018
579
datetime.timedelta(7)
1019
>>> string_to_delta(u'60s')
580
>>> string_to_delta('60s')
1020
581
datetime.timedelta(0, 60)
1021
>>> string_to_delta(u'60m')
582
>>> string_to_delta('60m')
1022
583
datetime.timedelta(0, 3600)
1023
>>> string_to_delta(u'24h')
584
>>> string_to_delta('24h')
1024
585
datetime.timedelta(1)
1025
586
>>> string_to_delta(u'1w')
1026
587
datetime.timedelta(7)
1027
>>> string_to_delta(u'5m 30s')
588
>>> string_to_delta('5m 30s')
1028
589
datetime.timedelta(0, 330)
1030
591
timevalue = datetime.timedelta(0)
1031
592
for s in interval.split():
1033
suffix = unicode(s[-1])
594
suffix=unicode(s[-1])
1035
596
if suffix == u"d":
1036
597
delta = datetime.timedelta(value)
1037
598
elif suffix == u"s":
1161
716
# Default values for config file for server-global settings
1162
server_defaults = { u"interface": u"",
1167
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1168
u"servicename": u"Mandos",
1169
u"use_dbus": u"True",
1170
u"use_ipv6": u"True",
717
server_defaults = { "interface": "",
722
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
723
"servicename": "Mandos",
1173
726
# Parse config file for server-global settings
1174
727
server_config = ConfigParser.SafeConfigParser(server_defaults)
1175
728
del server_defaults
1176
server_config.read(os.path.join(options.configdir,
729
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1178
730
# Convert the SafeConfigParser object to a dict
1179
731
server_settings = server_config.defaults()
1180
# Use the appropriate methods on the non-string config options
1181
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1182
server_settings[option] = server_config.getboolean(u"DEFAULT",
1184
if server_settings["port"]:
1185
server_settings["port"] = server_config.getint(u"DEFAULT",
732
# Use getboolean on the boolean config option
733
server_settings["debug"] = server_config.getboolean\
1187
735
del server_config
1189
737
# Override the settings from the config file with command line
1190
738
# options, if set.
1191
for option in (u"interface", u"address", u"port", u"debug",
1192
u"priority", u"servicename", u"configdir",
1193
u"use_dbus", u"use_ipv6"):
739
for option in ("interface", "address", "port", "debug",
740
"priority", "servicename", "configdir"):
1194
741
value = getattr(options, option)
1195
742
if value is not None:
1196
743
server_settings[option] = value
1198
# Force all strings to be unicode
1199
for option in server_settings.keys():
1200
if type(server_settings[option]) is str:
1201
server_settings[option] = unicode(server_settings[option])
1202
745
# Now we have our good server settings in "server_settings"
1204
##################################################################
1207
debug = server_settings[u"debug"]
1208
use_dbus = server_settings[u"use_dbus"]
1209
use_ipv6 = server_settings[u"use_ipv6"]
747
debug = server_settings["debug"]
1212
750
syslogger.setLevel(logging.WARNING)
1213
751
console.setLevel(logging.WARNING)
1215
if server_settings[u"servicename"] != u"Mandos":
1216
syslogger.setFormatter(logging.Formatter
1217
(u'Mandos (%s) [%%(process)d]:'
1218
u' %%(levelname)s: %%(message)s'
1219
% server_settings[u"servicename"]))
753
if server_settings["servicename"] != "Mandos":
754
syslogger.setFormatter(logging.Formatter\
755
('Mandos (%s): %%(levelname)s:'
757
% server_settings["servicename"]))
1221
759
# Parse config file with clients
1222
client_defaults = { u"timeout": u"1h",
1224
u"checker": u"fping -q -- %%(host)s",
760
client_defaults = { "timeout": "1h",
762
"checker": "fping -q -- %(host)s",
1227
765
client_config = ConfigParser.SafeConfigParser(client_defaults)
1228
client_config.read(os.path.join(server_settings[u"configdir"],
1231
global mandos_dbus_service
1232
mandos_dbus_service = None
766
client_config.read(os.path.join(server_settings["configdir"],
1235
tcp_server = IPv6_TCPServer((server_settings[u"address"],
1236
server_settings[u"port"]),
1239
server_settings[u"interface"],
1243
server_settings[u"priority"],
1245
pidfilename = u"/var/run/mandos.pid"
770
tcp_server = IPv6_TCPServer((server_settings["address"],
771
server_settings["port"]),
773
settings=server_settings,
775
pidfilename = "/var/run/mandos.pid"
1247
pidfile = open(pidfilename, u"w")
1249
logger.error(u"Could not open file %r", pidfilename)
777
pidfile = open(pidfilename, "w")
778
except IOError, error:
779
logger.error("Could not open file %r", pidfilename)
1252
uid = pwd.getpwnam(u"_mandos").pw_uid
1253
gid = pwd.getpwnam(u"_mandos").pw_gid
1256
uid = pwd.getpwnam(u"mandos").pw_uid
1257
gid = pwd.getpwnam(u"mandos").pw_gid
1260
uid = pwd.getpwnam(u"nobody").pw_uid
1261
gid = pwd.getpwnam(u"nobody").pw_gid
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
1268
800
except OSError, error:
1269
801
if error[0] != errno.EPERM:
1272
# Enable all possible GnuTLS debugging
1274
# "Use a log level over 10 to enable all debugging options."
1276
gnutls.library.functions.gnutls_global_set_log_level(11)
1278
@gnutls.library.types.gnutls_log_func
1279
def debug_gnutls(level, string):
1280
logger.debug(u"GnuTLS: %s", string[:-1])
1282
(gnutls.library.functions
1283
.gnutls_global_set_log_function(debug_gnutls))
1286
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1287
service = AvahiService(name = server_settings[u"servicename"],
1288
servicetype = u"_mandos._tcp",
1289
protocol = protocol)
805
service = AvahiService(name = server_settings["servicename"],
806
type = "_mandos._tcp", );
1290
807
if server_settings["interface"]:
1291
service.interface = (if_nametoindex
1292
(str(server_settings[u"interface"])))
808
service.interface = if_nametoindex\
809
(server_settings["interface"])
1294
811
global main_loop