188
165
# 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
168
class Client(object):
197
169
"""A representation of a client host served by this server.
200
name: string; from the config file, used in log messages and
171
name: string; from the config file, used in log messages
202
172
fingerprint: string (40 or 32 hexadecimal digits); used to
203
173
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.
174
secret: bytestring; sent verbatim (over TLS) to client
175
host: string; available for use by the checker command
176
created: datetime.datetime(); object creation, not client host
177
last_checked_ok: datetime.datetime() or None if not yet checked OK
178
timeout: datetime.timedelta(); How long from last_checked_ok
179
until this client is invalid
180
interval: datetime.timedelta(); How often to start a new checker
181
stop_hook: If set, called by stop() as stop_hook(self)
182
checker: subprocess.Popen(); a running checker process used
183
to see if the client lives.
184
'None' if no process is running.
217
185
checker_initiator_tag: a gobject event source tag, or None
218
disable_initiator_tag: - '' -
186
stop_initiator_tag: - '' -
219
187
checker_callback_tag: - '' -
220
188
checker_command: string; External command which is run to check if
221
189
client lives. %() expansions are done at
222
190
runtime with vars(self) as dict, so that for
223
191
instance %(name)s can be used in the command.
224
current_checker_command: string; current running checker_command
193
_timeout: Real variable for 'timeout'
194
_interval: Real variable for 'interval'
195
_timeout_milliseconds: Used when calling gobject.timeout_add()
196
_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):
198
def _set_timeout(self, timeout):
199
"Setter function for 'timeout' attribute"
200
self._timeout = timeout
201
self._timeout_milliseconds = ((self.timeout.days
202
* 24 * 60 * 60 * 1000)
203
+ (self.timeout.seconds * 1000)
204
+ (self.timeout.microseconds
206
timeout = property(lambda self: self._timeout,
209
def _set_interval(self, interval):
210
"Setter function for 'interval' attribute"
211
self._interval = interval
212
self._interval_milliseconds = ((self.interval.days
213
* 24 * 60 * 60 * 1000)
214
+ (self.interval.seconds
216
+ (self.interval.microseconds
218
interval = property(lambda self: self._interval,
221
def __init__(self, name = None, stop_hook=None, config={}):
243
222
"""Note: the 'checker' key in 'config' sets the
244
223
'checker_command' attribute and *not* the 'checker'
249
226
logger.debug(u"Creating client %r", self.name)
250
227
# Uppercase and remove spaces from fingerprint for later
251
228
# comparison purposes with return value from the fingerprint()
253
self.fingerprint = (config[u"fingerprint"].upper()
230
self.fingerprint = config["fingerprint"].upper()\
255
232
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()
233
if "secret" in config:
234
self.secret = config["secret"].decode(u"base64")
235
elif "secfile" in config:
236
sf = open(config["secfile"])
237
self.secret = sf.read()
264
240
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
242
self.host = config.get("host", "")
243
self.created = datetime.datetime.now()
270
244
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
245
self.timeout = string_to_delta(config["timeout"])
246
self.interval = string_to_delta(config["interval"])
247
self.stop_hook = stop_hook
274
248
self.checker = None
275
249
self.checker_initiator_tag = None
276
self.disable_initiator_tag = None
250
self.stop_initiator_tag = None
277
251
self.checker_callback_tag = None
278
self.checker_command = config[u"checker"]
279
self.current_checker_command = None
280
self.last_connect = None
252
self.check_command = config["checker"]
283
254
"""Start this client's checker and timeout hooks"""
284
self.last_enabled = datetime.datetime.utcnow()
285
255
# Schedule a new checker to be started an 'interval' from now,
286
256
# and every interval from then on.
287
self.checker_initiator_tag = (gobject.timeout_add
288
(self.interval_milliseconds(),
257
self.checker_initiator_tag = gobject.timeout_add\
258
(self._interval_milliseconds,
290
260
# Also start a new checker *right now*.
291
261
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):
262
# Schedule a stop() when 'timeout' has passed
263
self.stop_initiator_tag = gobject.timeout_add\
264
(self._timeout_milliseconds,
268
The possibility that a client might be restarted is left open,
269
but not currently used."""
270
# If this client doesn't have a secret, it is already stopped.
271
if hasattr(self, "secret") and self.secret:
272
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):
276
if getattr(self, "stop_initiator_tag", False):
277
gobject.source_remove(self.stop_initiator_tag)
278
self.stop_initiator_tag = None
279
if getattr(self, "checker_initiator_tag", False):
307
280
gobject.source_remove(self.checker_initiator_tag)
308
281
self.checker_initiator_tag = None
309
282
self.stop_checker()
310
if self.disable_hook:
311
self.disable_hook(self)
313
285
# Do not run this again if called by a gobject.timeout_add
316
287
def __del__(self):
317
self.disable_hook = None
320
def checker_callback(self, pid, condition, command):
288
self.stop_hook = None
290
def checker_callback(self, pid, condition):
321
291
"""The checker has completed, so take appropriate actions."""
292
now = datetime.datetime.now()
322
293
self.checker_callback_tag = None
323
294
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",
295
if os.WIFEXITED(condition) \
296
and (os.WEXITSTATUS(condition) == 0):
297
logger.info(u"Checker for %(name)s succeeded",
299
self.last_checked_ok = now
300
gobject.source_remove(self.stop_initiator_tag)
301
self.stop_initiator_tag = gobject.timeout_add\
302
(self._timeout_milliseconds,
304
elif not os.WIFEXITED(condition):
334
305
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(),
308
logger.info(u"Checker for %(name)s failed",
349
310
def start_checker(self):
350
311
"""Start a new checker subprocess if one is not running.
352
312
If a checker already exists, leave it running and do
354
314
# The reason for not killing a running checker is that if we
431
369
if error.errno != errno.ESRCH: # No such process
433
371
self.checker = None
435
372
def still_valid(self):
436
373
"""Has the timeout not yet passed for this client?"""
437
if not getattr(self, u"enabled", False):
439
now = datetime.datetime.utcnow()
374
now = datetime.datetime.now()
440
375
if self.last_checked_ok is None:
441
376
return now < (self.created + self.timeout)
443
378
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.
381
def peer_certificate(session):
382
"Return the peer's OpenPGP certificate as a bytestring"
383
# If not an OpenPGP certificate...
384
if gnutls.library.functions.gnutls_certificate_type_get\
385
(session._c_object) \
386
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
387
# ...do the normal thing
388
return session.peer_certificate
389
list_size = ctypes.c_uint()
390
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
391
(session._c_object, ctypes.byref(list_size))
392
if list_size.value == 0:
395
return ctypes.string_at(cert.data, cert.size)
398
def fingerprint(openpgp):
399
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
400
# New GnuTLS "datum" with the OpenPGP public key
401
datum = gnutls.library.types.gnutls_datum_t\
402
(ctypes.cast(ctypes.c_char_p(openpgp),
403
ctypes.POINTER(ctypes.c_ubyte)),
404
ctypes.c_uint(len(openpgp)))
405
# New empty GnuTLS certificate
406
crt = gnutls.library.types.gnutls_openpgp_crt_t()
407
gnutls.library.functions.gnutls_openpgp_crt_init\
409
# Import the OpenPGP public key into the certificate
410
gnutls.library.functions.gnutls_openpgp_crt_import\
411
(crt, ctypes.byref(datum),
412
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
413
# New buffer for the fingerprint
414
buffer = ctypes.create_string_buffer(20)
415
buffer_length = ctypes.c_size_t()
416
# Get the fingerprint from the certificate into the buffer
417
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
418
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
419
# Deinit the certificate
420
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
421
# Convert the buffer to a Python bytestring
422
fpr = ctypes.string_at(buffer, buffer_length.value)
423
# Convert the bytestring to hexadecimal notation
424
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
428
class tcp_handler(SocketServer.BaseRequestHandler, object):
429
"""A TCP request handler class.
430
Instantiated by IPv6_TCPServer for each request to handle it.
709
431
Note: This will run in its own forked process."""
711
433
def handle(self):
712
434
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
435
unicode(self.client_address))
436
session = gnutls.connection.ClientSession\
437
(self.request, gnutls.connection.X509Credentials())
439
line = self.request.makefile().readline()
440
logger.debug(u"Protocol version: %r", line)
442
if int(line.strip().split()[0]) > 1:
444
except (ValueError, IndexError, RuntimeError), error:
445
logger.error(u"Unknown protocol version: %s", error)
448
# Note: gnutls.connection.X509Credentials is really a generic
449
# GnuTLS certificate credentials object so long as no X.509
450
# keys are added to it. Therefore, we can use it here despite
451
# using OpenPGP certificates.
453
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
454
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
456
priority = "NORMAL" # Fallback default, since this
458
if self.server.settings["priority"]:
459
priority = self.server.settings["priority"]
460
gnutls.library.functions.gnutls_priority_set_direct\
461
(session._c_object, priority, None);
465
except gnutls.errors.GNUTLSError, error:
466
logger.warning(u"Handshake failed: %s", error)
467
# Do not run session.bye() here: the session is not
468
# established. Just abandon the request.
471
fpr = fingerprint(peer_certificate(session))
472
except (TypeError, gnutls.errors.GNUTLSError), error:
473
logger.warning(u"Bad certificate: %s", error)
476
logger.debug(u"Fingerprint: %s", fpr)
478
for c in self.server.clients:
479
if c.fingerprint == fpr:
483
logger.warning(u"Client not found for fingerprint: %s",
487
# Have to check if client.still_valid(), since it is possible
488
# that the client timed out while establishing the GnuTLS
490
if not client.still_valid():
491
logger.warning(u"Client %(name)s is invalid",
496
while sent_size < len(client.secret):
497
sent = session.send(client.secret[sent_size:])
498
logger.debug(u"Sent: %d, remaining: %d",
499
sent, len(client.secret)
500
- (sent_size + sent))
505
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
506
"""IPv6 TCP server. Accepts 'None' as address and/or port.
882
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
508
settings: Server settings
886
509
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):
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,
511
address_family = socket.AF_INET6
512
def __init__(self, *args, **kwargs):
513
if "settings" in kwargs:
514
self.settings = kwargs["settings"]
515
del kwargs["settings"]
516
if "clients" in kwargs:
517
self.clients = kwargs["clients"]
518
del kwargs["clients"]
519
return super(type(self), self).__init__(*args, **kwargs)
902
520
def server_bind(self):
903
521
"""This overrides the normal server_bind() function
904
522
to bind to an interface if one was specified, and also NOT to
905
523
bind to an address or port if they were not specified."""
906
if self.interface is not None:
524
if self.settings["interface"]:
525
# 25 is from /usr/include/asm-i486/socket.h
526
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
908
528
self.socket.setsockopt(socket.SOL_SOCKET,
910
str(self.interface + u'\0'))
530
self.settings["interface"])
911
531
except socket.error, error:
912
532
if error[0] == errno.EPERM:
913
533
logger.error(u"No permission to"
914
534
u" bind to interface %s",
535
self.settings["interface"])
918
538
# Only bind(2) the socket if we really need to.
919
539
if self.server_address[0] or self.server_address[1]:
920
540
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,
542
self.server_address = (in6addr_any,
926
543
self.server_address[1])
927
544
elif not self.server_address[1]:
928
545
self.server_address = (self.server_address[0],
547
# if self.settings["interface"]:
931
548
# self.server_address = (self.server_address[0],
936
return SocketServer.TCPServer.server_bind(self)
937
def server_activate(self):
939
return SocketServer.TCPServer.server_activate(self)
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
554
return super(type(self), self).server_bind()
1014
557
def string_to_delta(interval):
1015
558
"""Parse a string and return a datetime.timedelta
1017
>>> string_to_delta(u'7d')
560
>>> string_to_delta('7d')
1018
561
datetime.timedelta(7)
1019
>>> string_to_delta(u'60s')
562
>>> string_to_delta('60s')
1020
563
datetime.timedelta(0, 60)
1021
>>> string_to_delta(u'60m')
564
>>> string_to_delta('60m')
1022
565
datetime.timedelta(0, 3600)
1023
>>> string_to_delta(u'24h')
566
>>> string_to_delta('24h')
1024
567
datetime.timedelta(1)
1025
568
>>> string_to_delta(u'1w')
1026
569
datetime.timedelta(7)
1027
>>> string_to_delta(u'5m 30s')
570
>>> string_to_delta('5m 30s')
1028
571
datetime.timedelta(0, 330)
1030
573
timevalue = datetime.timedelta(0)
1031
574
for s in interval.split():
1033
suffix = unicode(s[-1])
576
suffix=unicode(s[-1])
1035
578
if suffix == u"d":
1036
579
delta = datetime.timedelta(value)
1037
580
elif suffix == u"s":
1161
698
# 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",
699
server_defaults = { "interface": "",
704
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
705
"servicename": "Mandos",
1173
708
# Parse config file for server-global settings
1174
709
server_config = ConfigParser.SafeConfigParser(server_defaults)
1175
710
del server_defaults
1176
server_config.read(os.path.join(options.configdir,
711
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1178
712
# Convert the SafeConfigParser object to a dict
1179
713
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",
714
# Use getboolean on the boolean config option
715
server_settings["debug"] = server_config.getboolean\
1187
717
del server_config
1189
719
# Override the settings from the config file with command line
1190
720
# 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"):
721
for option in ("interface", "address", "port", "debug",
722
"priority", "servicename", "configdir"):
1194
723
value = getattr(options, option)
1195
724
if value is not None:
1196
725
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
727
# 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"]
729
debug = server_settings["debug"]
1212
732
syslogger.setLevel(logging.WARNING)
1213
733
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"]))
735
if server_settings["servicename"] != "Mandos":
736
syslogger.setFormatter(logging.Formatter\
737
('Mandos (%s): %%(levelname)s:'
739
% server_settings["servicename"]))
1221
741
# Parse config file with clients
1222
client_defaults = { u"timeout": u"1h",
1224
u"checker": u"fping -q -- %%(host)s",
742
client_defaults = { "timeout": "1h",
744
"checker": "fping -q -- %(host)s",
1227
747
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
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"
1247
pidfile = open(pidfilename, u"w")
1249
logger.error(u"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
1268
except OSError, error:
1269
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))
748
client_config.read(os.path.join(server_settings["configdir"],
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)
752
service = AvahiService(name = server_settings["servicename"],
753
type = "_mandos._tcp", );
1290
754
if server_settings["interface"]:
1291
service.interface = (if_nametoindex
1292
(str(server_settings[u"interface"])))
755
service.interface = if_nametoindex(server_settings["interface"])
1294
757
global main_loop