229
199
self.group.Commit()
230
200
def entry_group_state_changed(self, state, error):
231
201
"""Derived from the Avahi example code"""
232
logger.debug("Avahi entry group state change: %i", state)
202
logger.debug(u"Avahi entry group state change: %i", state)
234
204
if state == avahi.ENTRY_GROUP_ESTABLISHED:
235
logger.debug("Zeroconf service established.")
205
logger.debug(u"Zeroconf service established.")
236
206
elif state == avahi.ENTRY_GROUP_COLLISION:
237
logger.info("Zeroconf service name collision.")
207
logger.warning(u"Zeroconf service name collision.")
239
209
elif state == avahi.ENTRY_GROUP_FAILURE:
240
logger.critical("Avahi: Error in group state changed %s",
210
logger.critical(u"Avahi: Error in group state changed %s",
242
raise AvahiGroupError("State changed: %s"
212
raise AvahiGroupError(u"State changed: %s"
243
213
% unicode(error))
244
214
def cleanup(self):
245
215
"""Derived from the Avahi example code"""
246
216
if self.group is not None:
249
except (dbus.exceptions.UnknownMethodException,
250
dbus.exceptions.DBusException):
252
218
self.group = None
254
def server_state_changed(self, state, error=None):
219
def server_state_changed(self, state):
255
220
"""Derived from the Avahi example code"""
256
logger.debug("Avahi server state change: %i", state)
257
bad_states = { avahi.SERVER_INVALID:
258
"Zeroconf server invalid",
259
avahi.SERVER_REGISTERING: None,
260
avahi.SERVER_COLLISION:
261
"Zeroconf server name collision",
262
avahi.SERVER_FAILURE:
263
"Zeroconf server failure" }
264
if state in bad_states:
265
if bad_states[state] is not None:
267
logger.error(bad_states[state])
269
logger.error(bad_states[state] + ": %r", error)
221
logger.debug(u"Avahi server state change: %i", state)
222
if state == avahi.SERVER_COLLISION:
223
logger.error(u"Zeroconf server name collision")
271
225
elif state == avahi.SERVER_RUNNING:
275
logger.debug("Unknown state: %r", state)
277
logger.debug("Unknown state: %r: %r", state, error)
278
227
def activate(self):
279
228
"""Derived from the Avahi example code"""
280
229
if self.server is None:
281
230
self.server = dbus.Interface(
282
231
self.bus.get_object(avahi.DBUS_NAME,
283
avahi.DBUS_PATH_SERVER,
284
follow_name_owner_changes=True),
232
avahi.DBUS_PATH_SERVER),
285
233
avahi.DBUS_INTERFACE_SERVER)
286
self.server.connect_to_signal("StateChanged",
234
self.server.connect_to_signal(u"StateChanged",
287
235
self.server_state_changed)
288
236
self.server_state_changed(self.server.GetState())
290
class AvahiServiceToSyslog(AvahiService):
292
"""Add the new name to the syslog messages"""
293
ret = AvahiService.rename(self)
294
syslogger.setFormatter(logging.Formatter
295
('Mandos (%s) [%%(process)d]:'
296
' %%(levelname)s: %%(message)s'
300
def _timedelta_to_milliseconds(td):
301
"Convert a datetime.timedelta() to milliseconds"
302
return ((td.days * 24 * 60 * 60 * 1000)
303
+ (td.seconds * 1000)
304
+ (td.microseconds // 1000))
306
239
class Client(object):
307
240
"""A representation of a client host served by this server.
310
_approved: bool(); 'None' if not yet approved/disapproved
311
approval_delay: datetime.timedelta(); Time to wait for approval
312
approval_duration: datetime.timedelta(); Duration of one approval
243
name: string; from the config file, used in log messages and
245
fingerprint: string (40 or 32 hexadecimal digits); used to
246
uniquely identify the client
247
secret: bytestring; sent verbatim (over TLS) to client
248
host: string; available for use by the checker command
249
created: datetime.datetime(); (UTC) object creation
250
last_enabled: datetime.datetime(); (UTC)
252
last_checked_ok: datetime.datetime(); (UTC) or None
253
timeout: datetime.timedelta(); How long from last_checked_ok
254
until this client is disabled
255
interval: datetime.timedelta(); How often to start a new checker
256
disable_hook: If set, called by disable() as disable_hook(self)
313
257
checker: subprocess.Popen(); a running checker process used
314
258
to see if the client lives.
315
259
'None' if no process is running.
316
checker_callback_tag: a gobject event source tag, or None
317
checker_command: string; External command which is run to check
318
if client lives. %() expansions are done at
260
checker_initiator_tag: a gobject event source tag, or None
261
disable_initiator_tag: - '' -
262
checker_callback_tag: - '' -
263
checker_command: string; External command which is run to check if
264
client lives. %() expansions are done at
319
265
runtime with vars(self) as dict, so that for
320
266
instance %(name)s can be used in the command.
321
checker_initiator_tag: a gobject event source tag, or None
322
created: datetime.datetime(); (UTC) object creation
323
client_structure: Object describing what attributes a client has
324
and is used for storing the client at exit
325
267
current_checker_command: string; current running checker_command
326
disable_initiator_tag: a gobject event source tag, or None
328
fingerprint: string (40 or 32 hexadecimal digits); used to
329
uniquely identify the client
330
host: string; available for use by the checker command
331
interval: datetime.timedelta(); How often to start a new checker
332
last_approval_request: datetime.datetime(); (UTC) or None
333
last_checked_ok: datetime.datetime(); (UTC) or None
335
last_checker_status: integer between 0 and 255 reflecting exit
336
status of last checker. -1 reflects crashed
338
last_enabled: datetime.datetime(); (UTC)
339
name: string; from the config file, used in log messages and
341
secret: bytestring; sent verbatim (over TLS) to client
342
timeout: datetime.timedelta(); How long from last_checked_ok
343
until this client is disabled
344
extended_timeout: extra long timeout when password has been sent
345
runtime_expansions: Allowed attributes for runtime expansion.
346
expires: datetime.datetime(); time (UTC) when a client will be
268
approved_delay: datetime.timedelta(); Time to wait for approval
269
_approved: bool(); 'None' if not yet approved/disapproved
270
approved_duration: datetime.timedelta(); Duration of one approval
350
runtime_expansions = ("approval_delay", "approval_duration",
351
"created", "enabled", "fingerprint",
352
"host", "interval", "last_checked_ok",
353
"last_enabled", "name", "timeout")
274
def _timedelta_to_milliseconds(td):
275
"Convert a datetime.timedelta() to milliseconds"
276
return ((td.days * 24 * 60 * 60 * 1000)
277
+ (td.seconds * 1000)
278
+ (td.microseconds // 1000))
355
280
def timeout_milliseconds(self):
356
281
"Return the 'timeout' attribute in milliseconds"
357
return _timedelta_to_milliseconds(self.timeout)
359
def extended_timeout_milliseconds(self):
360
"Return the 'extended_timeout' attribute in milliseconds"
361
return _timedelta_to_milliseconds(self.extended_timeout)
282
return self._timedelta_to_milliseconds(self.timeout)
363
284
def interval_milliseconds(self):
364
285
"Return the 'interval' attribute in milliseconds"
365
return _timedelta_to_milliseconds(self.interval)
367
def approval_delay_milliseconds(self):
368
return _timedelta_to_milliseconds(self.approval_delay)
370
def __init__(self, name = None, config=None):
286
return self._timedelta_to_milliseconds(self.interval)
288
def approved_delay_milliseconds(self):
289
return self._timedelta_to_milliseconds(self.approved_delay)
291
def __init__(self, name = None, disable_hook=None, config=None):
371
292
"""Note: the 'checker' key in 'config' sets the
372
293
'checker_command' attribute and *not* the 'checker'
375
296
if config is None:
377
logger.debug("Creating client %r", self.name)
298
logger.debug(u"Creating client %r", self.name)
378
299
# Uppercase and remove spaces from fingerprint for later
379
300
# comparison purposes with return value from the fingerprint()
381
self.fingerprint = (config["fingerprint"].upper()
383
logger.debug(" Fingerprint: %s", self.fingerprint)
384
if "secret" in config:
385
self.secret = config["secret"].decode("base64")
386
elif "secfile" in config:
302
self.fingerprint = (config[u"fingerprint"].upper()
304
logger.debug(u" Fingerprint: %s", self.fingerprint)
305
if u"secret" in config:
306
self.secret = config[u"secret"].decode(u"base64")
307
elif u"secfile" in config:
387
308
with open(os.path.expanduser(os.path.expandvars
388
(config["secfile"])),
309
(config[u"secfile"])),
389
310
"rb") as secfile:
390
311
self.secret = secfile.read()
392
raise TypeError("No secret or secfile for client %s"
313
raise TypeError(u"No secret or secfile for client %s"
394
self.host = config.get("host", "")
315
self.host = config.get(u"host", u"")
395
316
self.created = datetime.datetime.utcnow()
397
self.last_approval_request = None
398
self.last_enabled = datetime.datetime.utcnow()
318
self.last_enabled = None
399
319
self.last_checked_ok = None
400
self.last_checker_status = None
401
self.timeout = string_to_delta(config["timeout"])
402
self.extended_timeout = string_to_delta(config
403
["extended_timeout"])
404
self.interval = string_to_delta(config["interval"])
320
self.timeout = string_to_delta(config[u"timeout"])
321
self.interval = string_to_delta(config[u"interval"])
322
self.disable_hook = disable_hook
405
323
self.checker = None
406
324
self.checker_initiator_tag = None
407
325
self.disable_initiator_tag = None
408
self.expires = datetime.datetime.utcnow() + self.timeout
409
326
self.checker_callback_tag = None
410
self.checker_command = config["checker"]
327
self.checker_command = config[u"checker"]
411
328
self.current_checker_command = None
329
self.last_connect = None
412
330
self._approved = None
413
self.approved_by_default = config.get("approved_by_default",
331
self.approved_by_default = config.get(u"approved_by_default",
415
333
self.approvals_pending = 0
416
self.approval_delay = string_to_delta(
417
config["approval_delay"])
418
self.approval_duration = string_to_delta(
419
config["approval_duration"])
420
self.changedstate = (multiprocessing_manager
421
.Condition(multiprocessing_manager
423
self.client_structure = [attr for attr in
424
self.__dict__.iterkeys()
425
if not attr.startswith("_")]
426
self.client_structure.append("client_structure")
428
for name, t in inspect.getmembers(type(self),
432
if not name.startswith("_"):
433
self.client_structure.append(name)
334
self.approved_delay = string_to_delta(
335
config[u"approved_delay"])
336
self.approved_duration = string_to_delta(
337
config[u"approved_duration"])
338
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
435
# Send notice to process children that client state has changed
436
340
def send_changedstate(self):
437
with self.changedstate:
438
self.changedstate.notify_all()
341
self.changedstate.acquire()
342
self.changedstate.notify_all()
343
self.changedstate.release()
440
345
def enable(self):
441
346
"""Start this client's checker and timeout hooks"""
442
if getattr(self, "enabled", False):
347
if getattr(self, u"enabled", False):
443
348
# Already enabled
445
350
self.send_changedstate()
446
self.expires = datetime.datetime.utcnow() + self.timeout
351
self.last_enabled = datetime.datetime.utcnow()
352
# Schedule a new checker to be started an 'interval' from now,
353
# and every interval from then on.
354
self.checker_initiator_tag = (gobject.timeout_add
355
(self.interval_milliseconds(),
357
# Schedule a disable() when 'timeout' has passed
358
self.disable_initiator_tag = (gobject.timeout_add
359
(self.timeout_milliseconds(),
447
361
self.enabled = True
448
self.last_enabled = datetime.datetime.utcnow()
362
# Also start a new checker *right now*.
451
365
def disable(self, quiet=True):
452
366
"""Disable this client."""
456
370
self.send_changedstate()
458
logger.info("Disabling client %s", self.name)
459
if getattr(self, "disable_initiator_tag", False):
372
logger.info(u"Disabling client %s", self.name)
373
if getattr(self, u"disable_initiator_tag", False):
460
374
gobject.source_remove(self.disable_initiator_tag)
461
375
self.disable_initiator_tag = None
463
if getattr(self, "checker_initiator_tag", False):
376
if getattr(self, u"checker_initiator_tag", False):
464
377
gobject.source_remove(self.checker_initiator_tag)
465
378
self.checker_initiator_tag = None
466
379
self.stop_checker()
380
if self.disable_hook:
381
self.disable_hook(self)
467
382
self.enabled = False
468
383
# Do not run this again if called by a gobject.timeout_add
471
386
def __del__(self):
387
self.disable_hook = None
474
def init_checker(self):
475
# Schedule a new checker to be started an 'interval' from now,
476
# and every interval from then on.
477
self.checker_initiator_tag = (gobject.timeout_add
478
(self.interval_milliseconds(),
480
# Schedule a disable() when 'timeout' has passed
481
self.disable_initiator_tag = (gobject.timeout_add
482
(self.timeout_milliseconds(),
484
# Also start a new checker *right now*.
487
390
def checker_callback(self, pid, condition, command):
488
391
"""The checker has completed, so take appropriate actions."""
489
392
self.checker_callback_tag = None
490
393
self.checker = None
491
394
if os.WIFEXITED(condition):
492
self.last_checker_status = os.WEXITSTATUS(condition)
493
if self.last_checker_status == 0:
494
logger.info("Checker for %(name)s succeeded",
395
exitstatus = os.WEXITSTATUS(condition)
397
logger.info(u"Checker for %(name)s succeeded",
496
399
self.checked_ok()
498
logger.info("Checker for %(name)s failed",
401
logger.info(u"Checker for %(name)s failed",
501
self.last_checker_status = -1
502
logger.warning("Checker for %(name)s crashed?",
404
logger.warning(u"Checker for %(name)s crashed?",
505
def checked_ok(self, timeout=None):
407
def checked_ok(self):
506
408
"""Bump up the timeout for this client.
508
410
This should only be called when the client has been seen,
512
timeout = self.timeout
513
413
self.last_checked_ok = datetime.datetime.utcnow()
514
if self.disable_initiator_tag is not None:
515
gobject.source_remove(self.disable_initiator_tag)
516
if getattr(self, "enabled", False):
517
self.disable_initiator_tag = (gobject.timeout_add
518
(_timedelta_to_milliseconds
519
(timeout), self.disable))
520
self.expires = datetime.datetime.utcnow() + timeout
522
def need_approval(self):
523
self.last_approval_request = datetime.datetime.utcnow()
414
gobject.source_remove(self.disable_initiator_tag)
415
self.disable_initiator_tag = (gobject.timeout_add
416
(self.timeout_milliseconds(),
525
419
def start_checker(self):
526
420
"""Start a new checker subprocess if one is not running.
602
494
if self.checker_callback_tag:
603
495
gobject.source_remove(self.checker_callback_tag)
604
496
self.checker_callback_tag = None
605
if getattr(self, "checker", None) is None:
497
if getattr(self, u"checker", None) is None:
607
logger.debug("Stopping checker for %(name)s", vars(self))
499
logger.debug(u"Stopping checker for %(name)s", vars(self))
609
501
os.kill(self.checker.pid, signal.SIGTERM)
611
503
#if self.checker.poll() is None:
612
504
# os.kill(self.checker.pid, signal.SIGKILL)
613
except OSError as error:
505
except OSError, error:
614
506
if error.errno != errno.ESRCH: # No such process
616
508
self.checker = None
618
# Encrypts a client secret and stores it in a varible
620
def encrypt_secret(self, key):
621
# Encryption-key need to be of a specific size, so we hash
623
hasheng = hashlib.sha256()
625
encryptionkey = hasheng.digest()
627
# Create validation hash so we know at decryption if it was
629
hasheng = hashlib.sha256()
630
hasheng.update(self.secret)
631
validationhash = hasheng.digest()
634
iv = os.urandom(Crypto.Cipher.AES.block_size)
635
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
636
Crypto.Cipher.AES.MODE_CFB,
638
ciphertext = ciphereng.encrypt(validationhash+self.secret)
639
self.encrypted_secret = (ciphertext, iv)
641
# Decrypt a encrypted client secret
642
def decrypt_secret(self, key):
643
# Decryption-key need to be of a specific size, so we hash inputed key
644
hasheng = hashlib.sha256()
646
encryptionkey = hasheng.digest()
648
# Decrypt encrypted secret
649
ciphertext, iv = self.encrypted_secret
650
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
651
Crypto.Cipher.AES.MODE_CFB,
653
plain = ciphereng.decrypt(ciphertext)
655
# Validate decrypted secret to know if it was succesful
656
hasheng = hashlib.sha256()
657
validationhash = plain[:hasheng.digest_size]
658
secret = plain[hasheng.digest_size:]
659
hasheng.update(secret)
661
# if validation fails, we use key as new secret. Otherwhise,
662
# we use the decrypted secret
663
if hasheng.digest() == validationhash:
667
del self.encrypted_secret
670
def dbus_service_property(dbus_interface, signature="v",
671
access="readwrite", byte_arrays=False):
510
def dbus_service_property(dbus_interface, signature=u"v",
511
access=u"readwrite", byte_arrays=False):
672
512
"""Decorators for marking methods of a DBusObjectWithProperties to
673
513
become properties on the D-Bus.
728
568
def _is_dbus_property(obj):
729
return getattr(obj, "_dbus_is_property", False)
569
return getattr(obj, u"_dbus_is_property", False)
731
571
def _get_all_dbus_properties(self):
732
572
"""Returns a generator of (name, attribute) pairs
734
return ((prop.__get__(self)._dbus_name, prop.__get__(self))
735
for cls in self.__class__.__mro__
574
return ((prop._dbus_name, prop)
736
575
for name, prop in
737
inspect.getmembers(cls, self._is_dbus_property))
576
inspect.getmembers(self, self._is_dbus_property))
739
578
def _get_dbus_property(self, interface_name, property_name):
740
579
"""Returns a bound method if one exists which is a D-Bus
741
580
property with the specified name and interface.
743
for cls in self.__class__.__mro__:
744
for name, value in (inspect.getmembers
745
(cls, self._is_dbus_property)):
746
if (value._dbus_name == property_name
747
and value._dbus_interface == interface_name):
748
return value.__get__(self)
582
for name in (property_name,
583
property_name + u"_dbus_property"):
584
prop = getattr(self, name, None)
586
or not self._is_dbus_property(prop)
587
or prop._dbus_name != property_name
588
or (interface_name and prop._dbus_interface
589
and interface_name != prop._dbus_interface)):
750
592
# No such property
751
raise DBusPropertyNotFound(self.dbus_object_path + ":"
752
+ interface_name + "."
593
raise DBusPropertyNotFound(self.dbus_object_path + u":"
594
+ interface_name + u"."
755
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
597
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
757
599
def Get(self, interface_name, property_name):
758
600
"""Standard D-Bus property Get() method, see D-Bus standard.
760
602
prop = self._get_dbus_property(interface_name, property_name)
761
if prop._dbus_access == "write":
603
if prop._dbus_access == u"write":
762
604
raise DBusPropertyAccessException(property_name)
764
if not hasattr(value, "variant_level"):
606
if not hasattr(value, u"variant_level"):
766
608
return type(value)(value, variant_level=value.variant_level+1)
768
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
610
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
769
611
def Set(self, interface_name, property_name, value):
770
612
"""Standard D-Bus property Set() method, see D-Bus standard.
772
614
prop = self._get_dbus_property(interface_name, property_name)
773
if prop._dbus_access == "read":
615
if prop._dbus_access == u"read":
774
616
raise DBusPropertyAccessException(property_name)
775
if prop._dbus_get_args_options["byte_arrays"]:
617
if prop._dbus_get_args_options[u"byte_arrays"]:
776
618
# The byte_arrays option is not supported yet on
777
619
# signatures other than "ay".
778
if prop._dbus_signature != "ay":
620
if prop._dbus_signature != u"ay":
780
622
value = dbus.ByteArray(''.join(unichr(byte)
781
623
for byte in value))
784
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
785
out_signature="a{sv}")
626
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
627
out_signature=u"a{sv}")
786
628
def GetAll(self, interface_name):
787
629
"""Standard D-Bus property GetAll() method, see D-Bus
790
632
Note: Will not include properties with access="write".
793
635
for name, prop in self._get_all_dbus_properties():
794
636
if (interface_name
795
637
and interface_name != prop._dbus_interface):
796
638
# Interface non-empty but did not match
798
640
# Ignore write-only properties
799
if prop._dbus_access == "write":
641
if prop._dbus_access == u"write":
802
if not hasattr(value, "variant_level"):
803
properties[name] = value
644
if not hasattr(value, u"variant_level"):
805
properties[name] = type(value)(value, variant_level=
806
value.variant_level+1)
807
return dbus.Dictionary(properties, signature="sv")
647
all[name] = type(value)(value, variant_level=
648
value.variant_level+1)
649
return dbus.Dictionary(all, signature=u"sv")
809
651
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
811
653
path_keyword='object_path',
812
654
connection_keyword='connection')
813
655
def Introspect(self, object_path, connection):
819
661
document = xml.dom.minidom.parseString(xmlstring)
820
662
def make_tag(document, name, prop):
821
e = document.createElement("property")
822
e.setAttribute("name", name)
823
e.setAttribute("type", prop._dbus_signature)
824
e.setAttribute("access", prop._dbus_access)
663
e = document.createElement(u"property")
664
e.setAttribute(u"name", name)
665
e.setAttribute(u"type", prop._dbus_signature)
666
e.setAttribute(u"access", prop._dbus_access)
826
for if_tag in document.getElementsByTagName("interface"):
668
for if_tag in document.getElementsByTagName(u"interface"):
827
669
for tag in (make_tag(document, name, prop)
829
671
in self._get_all_dbus_properties()
830
672
if prop._dbus_interface
831
== if_tag.getAttribute("name")):
673
== if_tag.getAttribute(u"name")):
832
674
if_tag.appendChild(tag)
833
675
# Add the names to the return values for the
834
676
# "org.freedesktop.DBus.Properties" methods
835
if (if_tag.getAttribute("name")
836
== "org.freedesktop.DBus.Properties"):
837
for cn in if_tag.getElementsByTagName("method"):
838
if cn.getAttribute("name") == "Get":
839
for arg in cn.getElementsByTagName("arg"):
840
if (arg.getAttribute("direction")
842
arg.setAttribute("name", "value")
843
elif cn.getAttribute("name") == "GetAll":
844
for arg in cn.getElementsByTagName("arg"):
845
if (arg.getAttribute("direction")
847
arg.setAttribute("name", "props")
848
xmlstring = document.toxml("utf-8")
677
if (if_tag.getAttribute(u"name")
678
== u"org.freedesktop.DBus.Properties"):
679
for cn in if_tag.getElementsByTagName(u"method"):
680
if cn.getAttribute(u"name") == u"Get":
681
for arg in cn.getElementsByTagName(u"arg"):
682
if (arg.getAttribute(u"direction")
684
arg.setAttribute(u"name", u"value")
685
elif cn.getAttribute(u"name") == u"GetAll":
686
for arg in cn.getElementsByTagName(u"arg"):
687
if (arg.getAttribute(u"direction")
689
arg.setAttribute(u"name", u"props")
690
xmlstring = document.toxml(u"utf-8")
849
691
document.unlink()
850
692
except (AttributeError, xml.dom.DOMException,
851
xml.parsers.expat.ExpatError) as error:
852
logger.error("Failed to override Introspection method",
693
xml.parsers.expat.ExpatError), error:
694
logger.error(u"Failed to override Introspection method",
857
def datetime_to_dbus (dt, variant_level=0):
858
"""Convert a UTC datetime.datetime() to a D-Bus type."""
860
return dbus.String("", variant_level = variant_level)
861
return dbus.String(dt.isoformat(),
862
variant_level=variant_level)
865
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
867
"""Applied to an empty subclass of a D-Bus object, this metaclass
868
will add additional D-Bus attributes matching a certain pattern.
870
def __new__(mcs, name, bases, attr):
871
# Go through all the base classes which could have D-Bus
872
# methods, signals, or properties in them
873
for base in (b for b in bases
874
if issubclass(b, dbus.service.Object)):
875
# Go though all attributes of the base class
876
for attrname, attribute in inspect.getmembers(base):
877
# Ignore non-D-Bus attributes, and D-Bus attributes
878
# with the wrong interface name
879
if (not hasattr(attribute, "_dbus_interface")
880
or not attribute._dbus_interface
881
.startswith("se.recompile.Mandos")):
883
# Create an alternate D-Bus interface name based on
885
alt_interface = (attribute._dbus_interface
886
.replace("se.recompile.Mandos",
887
"se.bsnet.fukt.Mandos"))
888
# Is this a D-Bus signal?
889
if getattr(attribute, "_dbus_is_signal", False):
890
# Extract the original non-method function by
892
nonmethod_func = (dict(
893
zip(attribute.func_code.co_freevars,
894
attribute.__closure__))["func"]
896
# Create a new, but exactly alike, function
897
# object, and decorate it to be a new D-Bus signal
898
# with the alternate D-Bus interface name
899
new_function = (dbus.service.signal
901
attribute._dbus_signature)
903
nonmethod_func.func_code,
904
nonmethod_func.func_globals,
905
nonmethod_func.func_name,
906
nonmethod_func.func_defaults,
907
nonmethod_func.func_closure)))
908
# Define a creator of a function to call both the
909
# old and new functions, so both the old and new
910
# signals gets sent when the function is called
911
def fixscope(func1, func2):
912
"""This function is a scope container to pass
913
func1 and func2 to the "call_both" function
914
outside of its arguments"""
915
def call_both(*args, **kwargs):
916
"""This function will emit two D-Bus
917
signals by calling func1 and func2"""
918
func1(*args, **kwargs)
919
func2(*args, **kwargs)
921
# Create the "call_both" function and add it to
923
attr[attrname] = fixscope(attribute,
925
# Is this a D-Bus method?
926
elif getattr(attribute, "_dbus_is_method", False):
927
# Create a new, but exactly alike, function
928
# object. Decorate it to be a new D-Bus method
929
# with the alternate D-Bus interface name. Add it
931
attr[attrname] = (dbus.service.method
933
attribute._dbus_in_signature,
934
attribute._dbus_out_signature)
936
(attribute.func_code,
937
attribute.func_globals,
939
attribute.func_defaults,
940
attribute.func_closure)))
941
# Is this a D-Bus property?
942
elif getattr(attribute, "_dbus_is_property", False):
943
# Create a new, but exactly alike, function
944
# object, and decorate it to be a new D-Bus
945
# property with the alternate D-Bus interface
946
# name. Add it to the class.
947
attr[attrname] = (dbus_service_property
949
attribute._dbus_signature,
950
attribute._dbus_access,
952
._dbus_get_args_options
955
(attribute.func_code,
956
attribute.func_globals,
958
attribute.func_defaults,
959
attribute.func_closure)))
960
return type.__new__(mcs, name, bases, attr)
963
699
class ClientDBus(Client, DBusObjectWithProperties):
964
700
"""A Client class using D-Bus
967
703
dbus_object_path: dbus.ObjectPath
968
704
bus: dbus.SystemBus()
971
runtime_expansions = (Client.runtime_expansions
972
+ ("dbus_object_path",))
974
706
# dbus.service.Object doesn't use super(), so we can't either.
976
708
def __init__(self, bus = None, *args, **kwargs):
709
self._approvals_pending = 0
978
711
Client.__init__(self, *args, **kwargs)
980
self._approvals_pending = 0
981
712
# Only now, when this client is initialized, can it show up on
983
client_object_name = unicode(self.name).translate(
986
714
self.dbus_object_path = (dbus.ObjectPath
987
("/clients/" + client_object_name))
716
+ self.name.replace(u".", u"_")))
988
717
DBusObjectWithProperties.__init__(self, self.bus,
989
718
self.dbus_object_path)
991
def notifychangeproperty(transform_func,
992
dbus_name, type_func=lambda x: x,
994
""" Modify a variable so that it's a property which announces
997
transform_fun: Function that takes a value and a variant_level
998
and transforms it to a D-Bus type.
999
dbus_name: D-Bus name of the variable
1000
type_func: Function that transform the value before sending it
1001
to the D-Bus. Default: no transform
1002
variant_level: D-Bus variant level. Default: 1
1004
attrname = "_{0}".format(dbus_name)
1005
def setter(self, value):
1006
if hasattr(self, "dbus_object_path"):
1007
if (not hasattr(self, attrname) or
1008
type_func(getattr(self, attrname, None))
1009
!= type_func(value)):
1010
dbus_value = transform_func(type_func(value),
1013
self.PropertyChanged(dbus.String(dbus_name),
1015
setattr(self, attrname, value)
1017
return property(lambda self: getattr(self, attrname), setter)
1020
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1021
approvals_pending = notifychangeproperty(dbus.Boolean,
1024
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1025
last_enabled = notifychangeproperty(datetime_to_dbus,
1027
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1028
type_func = lambda checker:
1029
checker is not None)
1030
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1032
last_approval_request = notifychangeproperty(
1033
datetime_to_dbus, "LastApprovalRequest")
1034
approved_by_default = notifychangeproperty(dbus.Boolean,
1035
"ApprovedByDefault")
1036
approval_delay = notifychangeproperty(dbus.UInt16,
1039
_timedelta_to_milliseconds)
1040
approval_duration = notifychangeproperty(
1041
dbus.UInt16, "ApprovalDuration",
1042
type_func = _timedelta_to_milliseconds)
1043
host = notifychangeproperty(dbus.String, "Host")
1044
timeout = notifychangeproperty(dbus.UInt16, "Timeout",
1046
_timedelta_to_milliseconds)
1047
extended_timeout = notifychangeproperty(
1048
dbus.UInt16, "ExtendedTimeout",
1049
type_func = _timedelta_to_milliseconds)
1050
interval = notifychangeproperty(dbus.UInt16,
1053
_timedelta_to_milliseconds)
1054
checker_command = notifychangeproperty(dbus.String, "Checker")
1056
del notifychangeproperty
720
def _get_approvals_pending(self):
721
return self._approvals_pending
722
def _set_approvals_pending(self, value):
723
old_value = self._approvals_pending
724
self._approvals_pending = value
726
if (hasattr(self, "dbus_object_path")
727
and bval is not bool(old_value)):
728
dbus_bool = dbus.Boolean(bval, variant_level=1)
729
self.PropertyChanged(dbus.String(u"approved_pending"),
732
approvals_pending = property(_get_approvals_pending,
733
_set_approvals_pending)
734
del _get_approvals_pending, _set_approvals_pending
737
def _datetime_to_dbus(dt, variant_level=0):
738
"""Convert a UTC datetime.datetime() to a D-Bus type."""
739
return dbus.String(dt.isoformat(),
740
variant_level=variant_level)
743
oldstate = getattr(self, u"enabled", False)
744
r = Client.enable(self)
745
if oldstate != self.enabled:
747
self.PropertyChanged(dbus.String(u"enabled"),
748
dbus.Boolean(True, variant_level=1))
749
self.PropertyChanged(
750
dbus.String(u"last_enabled"),
751
self._datetime_to_dbus(self.last_enabled,
755
def disable(self, quiet = False):
756
oldstate = getattr(self, u"enabled", False)
757
r = Client.disable(self, quiet=quiet)
758
if not quiet and oldstate != self.enabled:
760
self.PropertyChanged(dbus.String(u"enabled"),
761
dbus.Boolean(False, variant_level=1))
1058
764
def __del__(self, *args, **kwargs):
1060
766
self.remove_from_connection()
1061
767
except LookupError:
1063
if hasattr(DBusObjectWithProperties, "__del__"):
769
if hasattr(DBusObjectWithProperties, u"__del__"):
1064
770
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1065
771
Client.__del__(self, *args, **kwargs)
1201
# ApprovalPending - property
1202
@dbus_service_property(_interface, signature="b", access="read")
1203
def ApprovalPending_dbus_property(self):
922
# approved_pending - property
923
@dbus_service_property(_interface, signature=u"b", access=u"read")
924
def approved_pending_dbus_property(self):
1204
925
return dbus.Boolean(bool(self.approvals_pending))
1206
# ApprovedByDefault - property
1207
@dbus_service_property(_interface, signature="b",
1209
def ApprovedByDefault_dbus_property(self, value=None):
1210
if value is None: # get
1211
return dbus.Boolean(self.approved_by_default)
1212
self.approved_by_default = bool(value)
1214
# ApprovalDelay - property
1215
@dbus_service_property(_interface, signature="t",
1217
def ApprovalDelay_dbus_property(self, value=None):
1218
if value is None: # get
1219
return dbus.UInt64(self.approval_delay_milliseconds())
1220
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1222
# ApprovalDuration - property
1223
@dbus_service_property(_interface, signature="t",
1225
def ApprovalDuration_dbus_property(self, value=None):
1226
if value is None: # get
1227
return dbus.UInt64(_timedelta_to_milliseconds(
1228
self.approval_duration))
1229
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1232
@dbus_service_property(_interface, signature="s", access="read")
1233
def Name_dbus_property(self):
927
# approved_by_default - property
928
@dbus_service_property(_interface, signature=u"b",
930
def approved_by_default_dbus_property(self):
931
return dbus.Boolean(self.approved_by_default)
933
# approved_delay - property
934
@dbus_service_property(_interface, signature=u"t",
936
def approved_delay_dbus_property(self):
937
return dbus.UInt64(self.approved_delay_milliseconds())
939
# approved_duration - property
940
@dbus_service_property(_interface, signature=u"t",
942
def approved_duration_dbus_property(self):
943
return dbus.UInt64(self._timedelta_to_milliseconds(
944
self.approved_duration))
947
@dbus_service_property(_interface, signature=u"s", access=u"read")
948
def name_dbus_property(self):
1234
949
return dbus.String(self.name)
1236
# Fingerprint - property
1237
@dbus_service_property(_interface, signature="s", access="read")
1238
def Fingerprint_dbus_property(self):
951
# fingerprint - property
952
@dbus_service_property(_interface, signature=u"s", access=u"read")
953
def fingerprint_dbus_property(self):
1239
954
return dbus.String(self.fingerprint)
1242
@dbus_service_property(_interface, signature="s",
1244
def Host_dbus_property(self, value=None):
957
@dbus_service_property(_interface, signature=u"s",
959
def host_dbus_property(self, value=None):
1245
960
if value is None: # get
1246
961
return dbus.String(self.host)
1247
962
self.host = value
1249
# Created - property
1250
@dbus_service_property(_interface, signature="s", access="read")
1251
def Created_dbus_property(self):
1252
return dbus.String(datetime_to_dbus(self.created))
1254
# LastEnabled - property
1255
@dbus_service_property(_interface, signature="s", access="read")
1256
def LastEnabled_dbus_property(self):
1257
return datetime_to_dbus(self.last_enabled)
1259
# Enabled - property
1260
@dbus_service_property(_interface, signature="b",
1262
def Enabled_dbus_property(self, value=None):
964
self.PropertyChanged(dbus.String(u"host"),
965
dbus.String(value, variant_level=1))
968
@dbus_service_property(_interface, signature=u"s", access=u"read")
969
def created_dbus_property(self):
970
return dbus.String(self._datetime_to_dbus(self.created))
972
# last_enabled - property
973
@dbus_service_property(_interface, signature=u"s", access=u"read")
974
def last_enabled_dbus_property(self):
975
if self.last_enabled is None:
976
return dbus.String(u"")
977
return dbus.String(self._datetime_to_dbus(self.last_enabled))
980
@dbus_service_property(_interface, signature=u"b",
982
def enabled_dbus_property(self, value=None):
1263
983
if value is None: # get
1264
984
return dbus.Boolean(self.enabled)
1270
# LastCheckedOK - property
1271
@dbus_service_property(_interface, signature="s",
1273
def LastCheckedOK_dbus_property(self, value=None):
990
# last_checked_ok - property
991
@dbus_service_property(_interface, signature=u"s",
993
def last_checked_ok_dbus_property(self, value=None):
1274
994
if value is not None:
1275
995
self.checked_ok()
1277
return datetime_to_dbus(self.last_checked_ok)
1279
# Expires - property
1280
@dbus_service_property(_interface, signature="s", access="read")
1281
def Expires_dbus_property(self):
1282
return datetime_to_dbus(self.expires)
1284
# LastApprovalRequest - property
1285
@dbus_service_property(_interface, signature="s", access="read")
1286
def LastApprovalRequest_dbus_property(self):
1287
return datetime_to_dbus(self.last_approval_request)
1289
# Timeout - property
1290
@dbus_service_property(_interface, signature="t",
1292
def Timeout_dbus_property(self, value=None):
997
if self.last_checked_ok is None:
998
return dbus.String(u"")
999
return dbus.String(self._datetime_to_dbus(self
1002
# timeout - property
1003
@dbus_service_property(_interface, signature=u"t",
1004
access=u"readwrite")
1005
def timeout_dbus_property(self, value=None):
1293
1006
if value is None: # get
1294
1007
return dbus.UInt64(self.timeout_milliseconds())
1295
1008
self.timeout = datetime.timedelta(0, 0, 0, value)
1296
if getattr(self, "disable_initiator_tag", None) is None:
1010
self.PropertyChanged(dbus.String(u"timeout"),
1011
dbus.UInt64(value, variant_level=1))
1012
if getattr(self, u"disable_initiator_tag", None) is None:
1298
1014
# Reschedule timeout
1299
1015
gobject.source_remove(self.disable_initiator_tag)
1300
1016
self.disable_initiator_tag = None
1302
time_to_die = _timedelta_to_milliseconds((self
1017
time_to_die = (self.
1018
_timedelta_to_milliseconds((self
1307
1023
if time_to_die <= 0:
1308
1024
# The timeout has passed
1311
self.expires = (datetime.datetime.utcnow()
1312
+ datetime.timedelta(milliseconds =
1314
1027
self.disable_initiator_tag = (gobject.timeout_add
1315
1028
(time_to_die, self.disable))
1317
# ExtendedTimeout - property
1318
@dbus_service_property(_interface, signature="t",
1320
def ExtendedTimeout_dbus_property(self, value=None):
1321
if value is None: # get
1322
return dbus.UInt64(self.extended_timeout_milliseconds())
1323
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1325
# Interval - property
1326
@dbus_service_property(_interface, signature="t",
1328
def Interval_dbus_property(self, value=None):
1030
# interval - property
1031
@dbus_service_property(_interface, signature=u"t",
1032
access=u"readwrite")
1033
def interval_dbus_property(self, value=None):
1329
1034
if value is None: # get
1330
1035
return dbus.UInt64(self.interval_milliseconds())
1331
1036
self.interval = datetime.timedelta(0, 0, 0, value)
1332
if getattr(self, "checker_initiator_tag", None) is None:
1038
self.PropertyChanged(dbus.String(u"interval"),
1039
dbus.UInt64(value, variant_level=1))
1040
if getattr(self, u"checker_initiator_tag", None) is None:
1334
1042
# Reschedule checker run
1335
1043
gobject.source_remove(self.checker_initiator_tag)
1336
1044
self.checker_initiator_tag = (gobject.timeout_add
1337
1045
(value, self.start_checker))
1338
1046
self.start_checker() # Start one now, too
1340
# Checker - property
1341
@dbus_service_property(_interface, signature="s",
1343
def Checker_dbus_property(self, value=None):
1048
# checker - property
1049
@dbus_service_property(_interface, signature=u"s",
1050
access=u"readwrite")
1051
def checker_dbus_property(self, value=None):
1344
1052
if value is None: # get
1345
1053
return dbus.String(self.checker_command)
1346
1054
self.checker_command = value
1056
self.PropertyChanged(dbus.String(u"checker"),
1057
dbus.String(self.checker_command,
1348
# CheckerRunning - property
1349
@dbus_service_property(_interface, signature="b",
1351
def CheckerRunning_dbus_property(self, value=None):
1060
# checker_running - property
1061
@dbus_service_property(_interface, signature=u"b",
1062
access=u"readwrite")
1063
def checker_running_dbus_property(self, value=None):
1352
1064
if value is None: # get
1353
1065
return dbus.Boolean(self.checker is not None)
1409
1117
def handle(self):
1410
1118
with contextlib.closing(self.server.child_pipe) as child_pipe:
1411
logger.info("TCP connection from: %s",
1119
logger.info(u"TCP connection from: %s",
1412
1120
unicode(self.client_address))
1413
logger.debug("Pipe FD: %d",
1121
logger.debug(u"Pipe FD: %d",
1414
1122
self.server.child_pipe.fileno())
1416
1124
session = (gnutls.connection
1417
1125
.ClientSession(self.request,
1418
1126
gnutls.connection
1419
1127
.X509Credentials()))
1421
1129
# Note: gnutls.connection.X509Credentials is really a
1422
1130
# generic GnuTLS certificate credentials object so long as
1423
1131
# no X.509 keys are added to it. Therefore, we can use it
1424
1132
# here despite using OpenPGP certificates.
1426
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1427
# "+AES-256-CBC", "+SHA1",
1428
# "+COMP-NULL", "+CTYPE-OPENPGP",
1134
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1135
# u"+AES-256-CBC", u"+SHA1",
1136
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1430
1138
# Use a fallback default, since this MUST be set.
1431
1139
priority = self.server.gnutls_priority
1432
1140
if priority is None:
1141
priority = u"NORMAL"
1434
1142
(gnutls.library.functions
1435
1143
.gnutls_priority_set_direct(session._c_object,
1436
1144
priority, None))
1438
1146
# Start communication using the Mandos protocol
1439
1147
# Get protocol number
1440
1148
line = self.request.makefile().readline()
1441
logger.debug("Protocol version: %r", line)
1149
logger.debug(u"Protocol version: %r", line)
1443
1151
if int(line.strip().split()[0]) > 1:
1444
1152
raise RuntimeError
1445
except (ValueError, IndexError, RuntimeError) as error:
1446
logger.error("Unknown protocol version: %s", error)
1153
except (ValueError, IndexError, RuntimeError), error:
1154
logger.error(u"Unknown protocol version: %s", error)
1449
1157
# Start GnuTLS connection
1451
1159
session.handshake()
1452
except gnutls.errors.GNUTLSError as error:
1453
logger.warning("Handshake failed: %s", error)
1160
except gnutls.errors.GNUTLSError, error:
1161
logger.warning(u"Handshake failed: %s", error)
1454
1162
# Do not run session.bye() here: the session is not
1455
1163
# established. Just abandon the request.
1457
logger.debug("Handshake succeeded")
1165
logger.debug(u"Handshake succeeded")
1459
1167
approval_required = False
1462
1170
fpr = self.fingerprint(self.peer_certificate
1465
gnutls.errors.GNUTLSError) as error:
1466
logger.warning("Bad certificate: %s", error)
1172
except (TypeError, gnutls.errors.GNUTLSError), error:
1173
logger.warning(u"Bad certificate: %s", error)
1468
logger.debug("Fingerprint: %s", fpr)
1469
if self.server.use_dbus:
1471
client.NewRequest(str(self.client_address))
1175
logger.debug(u"Fingerprint: %s", fpr)
1474
1178
client = ProxyClient(child_pipe, fpr,
1475
1179
self.client_address)
1476
1180
except KeyError:
1479
if client.approval_delay:
1480
delay = client.approval_delay
1183
if client.approved_delay:
1184
delay = client.approved_delay
1481
1185
client.approvals_pending += 1
1482
1186
approval_required = True
1485
1189
if not client.enabled:
1486
logger.info("Client %s is disabled",
1190
logger.warning(u"Client %s is disabled",
1488
1192
if self.server.use_dbus:
1489
1193
# Emit D-Bus signal
1490
client.Rejected("Disabled")
1194
client.Rejected("Disabled")
1493
if client._approved or not client.approval_delay:
1197
if client._approved or not client.approved_delay:
1494
1198
#We are approved or approval is disabled
1496
1200
elif client._approved is None:
1497
logger.info("Client %s needs approval",
1201
logger.info(u"Client %s need approval",
1499
1203
if self.server.use_dbus:
1500
1204
# Emit D-Bus signal
1501
1205
client.NeedApproval(
1502
client.approval_delay_milliseconds(),
1206
client.approved_delay_milliseconds(),
1503
1207
client.approved_by_default)
1505
logger.warning("Client %s was not approved",
1209
logger.warning(u"Client %s was not approved",
1507
1211
if self.server.use_dbus:
1508
1212
# Emit D-Bus signal
1509
client.Rejected("Denied")
1213
client.Rejected("Disapproved")
1512
1216
#wait until timeout or approved
1217
#x = float(client._timedelta_to_milliseconds(delay))
1513
1218
time = datetime.datetime.now()
1514
1219
client.changedstate.acquire()
1515
(client.changedstate.wait
1516
(float(client._timedelta_to_milliseconds(delay)
1220
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1518
1221
client.changedstate.release()
1519
1222
time2 = datetime.datetime.now()
1520
1223
if (time2 - time) >= delay:
1777
1471
for cond, name in
1778
1472
condition_names.iteritems()
1779
1473
if cond & condition)
1780
# error, or the other end of multiprocessing.Pipe has closed
1474
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1477
# error or the other end of multiprocessing.Pipe has closed
1781
1478
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1782
# Wait for other process to exit
1786
1481
# Read a request from the child
1787
1482
request = parent_pipe.recv()
1483
logger.debug(u"IPC request: %s", repr(request))
1788
1484
command = request[0]
1790
1486
if command == 'init':
1791
1487
fpr = request[1]
1792
1488
address = request[2]
1794
for c in self.clients.itervalues():
1490
for c in self.clients:
1795
1491
if c.fingerprint == fpr:
1799
logger.info("Client not found for fingerprint: %s, ad"
1800
"dress: %s", fpr, address)
1495
logger.warning(u"Client not found for fingerprint: %s, ad"
1496
u"dress: %s", fpr, address)
1801
1497
if self.use_dbus:
1802
1498
# Emit D-Bus signal
1803
mandos_dbus_service.ClientNotFound(fpr,
1499
mandos_dbus_service.ClientNotFound(fpr, address)
1805
1500
parent_pipe.send(False)
1808
1503
gobject.io_add_watch(parent_pipe.fileno(),
1809
1504
gobject.IO_IN | gobject.IO_HUP,
1810
1505
functools.partial(self.handle_ipc,
1506
parent_pipe = parent_pipe,
1507
client_object = client))
1816
1508
parent_pipe.send(True)
1817
# remove the old hook in favor of the new above hook on
1509
# remove the old hook in favor of the new above hook on same fileno
1820
1511
if command == 'funcall':
1821
1512
funcname = request[1]
1822
1513
args = request[2]
1823
1514
kwargs = request[3]
1825
parent_pipe.send(('data', getattr(client_object,
1516
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1829
1518
if command == 'getattr':
1830
1519
attrname = request[1]
1831
1520
if callable(client_object.__getattribute__(attrname)):
1832
1521
parent_pipe.send(('function',))
1834
parent_pipe.send(('data', client_object
1835
.__getattribute__(attrname)))
1523
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1837
1525
if command == 'setattr':
1838
1526
attrname = request[1]
1839
1527
value = request[2]
1840
1528
setattr(client_object, attrname, value)
1845
1533
def string_to_delta(interval):
1846
1534
"""Parse a string and return a datetime.timedelta
1848
>>> string_to_delta('7d')
1536
>>> string_to_delta(u'7d')
1849
1537
datetime.timedelta(7)
1850
>>> string_to_delta('60s')
1538
>>> string_to_delta(u'60s')
1851
1539
datetime.timedelta(0, 60)
1852
>>> string_to_delta('60m')
1540
>>> string_to_delta(u'60m')
1853
1541
datetime.timedelta(0, 3600)
1854
>>> string_to_delta('24h')
1542
>>> string_to_delta(u'24h')
1855
1543
datetime.timedelta(1)
1856
>>> string_to_delta('1w')
1544
>>> string_to_delta(u'1w')
1857
1545
datetime.timedelta(7)
1858
>>> string_to_delta('5m 30s')
1546
>>> string_to_delta(u'5m 30s')
1859
1547
datetime.timedelta(0, 330)
1861
1549
timevalue = datetime.timedelta(0)
1911
1623
##################################################################
1912
1624
# Parsing of options, both command line and config file
1914
parser = argparse.ArgumentParser()
1915
parser.add_argument("-v", "--version", action="version",
1916
version = "%%(prog)s %s" % version,
1917
help="show version number and exit")
1918
parser.add_argument("-i", "--interface", metavar="IF",
1919
help="Bind to interface IF")
1920
parser.add_argument("-a", "--address",
1921
help="Address to listen for requests on")
1922
parser.add_argument("-p", "--port", type=int,
1923
help="Port number to receive requests on")
1924
parser.add_argument("--check", action="store_true",
1925
help="Run self-test")
1926
parser.add_argument("--debug", action="store_true",
1927
help="Debug mode; run in foreground and log"
1929
parser.add_argument("--debuglevel", metavar="LEVEL",
1930
help="Debug level for stdout output")
1931
parser.add_argument("--priority", help="GnuTLS"
1932
" priority string (see GnuTLS documentation)")
1933
parser.add_argument("--servicename",
1934
metavar="NAME", help="Zeroconf service name")
1935
parser.add_argument("--configdir",
1936
default="/etc/mandos", metavar="DIR",
1937
help="Directory to search for configuration"
1939
parser.add_argument("--no-dbus", action="store_false",
1940
dest="use_dbus", help="Do not provide D-Bus"
1941
" system bus interface")
1942
parser.add_argument("--no-ipv6", action="store_false",
1943
dest="use_ipv6", help="Do not use IPv6")
1944
parser.add_argument("--no-restore", action="store_false",
1945
dest="restore", help="Do not restore stored"
1946
" state", default=True)
1948
options = parser.parse_args()
1626
parser = optparse.OptionParser(version = "%%prog %s" % version)
1627
parser.add_option("-i", u"--interface", type=u"string",
1628
metavar="IF", help=u"Bind to interface IF")
1629
parser.add_option("-a", u"--address", type=u"string",
1630
help=u"Address to listen for requests on")
1631
parser.add_option("-p", u"--port", type=u"int",
1632
help=u"Port number to receive requests on")
1633
parser.add_option("--check", action=u"store_true",
1634
help=u"Run self-test")
1635
parser.add_option("--debug", action=u"store_true",
1636
help=u"Debug mode; run in foreground and log to"
1638
parser.add_option("--debuglevel", type=u"string", metavar="Level",
1639
help=u"Debug level for stdout output")
1640
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1641
u" priority string (see GnuTLS documentation)")
1642
parser.add_option("--servicename", type=u"string",
1643
metavar=u"NAME", help=u"Zeroconf service name")
1644
parser.add_option("--configdir", type=u"string",
1645
default=u"/etc/mandos", metavar=u"DIR",
1646
help=u"Directory to search for configuration"
1648
parser.add_option("--no-dbus", action=u"store_false",
1649
dest=u"use_dbus", help=u"Do not provide D-Bus"
1650
u" system bus interface")
1651
parser.add_option("--no-ipv6", action=u"store_false",
1652
dest=u"use_ipv6", help=u"Do not use IPv6")
1653
options = parser.parse_args()[0]
1950
1655
if options.check:
1999
1704
##################################################################
2001
1706
# For convenience
2002
debug = server_settings["debug"]
2003
debuglevel = server_settings["debuglevel"]
2004
use_dbus = server_settings["use_dbus"]
2005
use_ipv6 = server_settings["use_ipv6"]
2008
initlogger(logging.DEBUG)
2013
level = getattr(logging, debuglevel.upper())
2016
if server_settings["servicename"] != "Mandos":
1707
debug = server_settings[u"debug"]
1708
debuglevel = server_settings[u"debuglevel"]
1709
use_dbus = server_settings[u"use_dbus"]
1710
use_ipv6 = server_settings[u"use_ipv6"]
1712
if server_settings[u"servicename"] != u"Mandos":
2017
1713
syslogger.setFormatter(logging.Formatter
2018
('Mandos (%s) [%%(process)d]:'
2019
' %%(levelname)s: %%(message)s'
2020
% server_settings["servicename"]))
1714
(u'Mandos (%s) [%%(process)d]:'
1715
u' %%(levelname)s: %%(message)s'
1716
% server_settings[u"servicename"]))
2022
1718
# Parse config file with clients
2023
client_defaults = { "timeout": "5m",
2024
"extended_timeout": "15m",
2026
"checker": "fping -q -- %%(host)s",
2028
"approval_delay": "0s",
2029
"approval_duration": "1s",
1719
client_defaults = { u"timeout": u"1h",
1721
u"checker": u"fping -q -- %%(host)s",
1723
u"approved_delay": u"0s",
1724
u"approved_duration": u"1s",
2031
1726
client_config = configparser.SafeConfigParser(client_defaults)
2032
client_config.read(os.path.join(server_settings["configdir"],
1727
client_config.read(os.path.join(server_settings[u"configdir"],
2035
1730
global mandos_dbus_service
2036
1731
mandos_dbus_service = None
2038
tcp_server = MandosServer((server_settings["address"],
2039
server_settings["port"]),
1733
tcp_server = MandosServer((server_settings[u"address"],
1734
server_settings[u"port"]),
2041
interface=(server_settings["interface"]
1736
interface=(server_settings[u"interface"]
2043
1738
use_ipv6=use_ipv6,
2044
1739
gnutls_priority=
2045
server_settings["priority"],
1740
server_settings[u"priority"],
2046
1741
use_dbus=use_dbus)
2048
pidfilename = "/var/run/mandos.pid"
2050
pidfile = open(pidfilename, "w")
2052
logger.error("Could not open file %r", pidfilename)
1742
pidfilename = u"/var/run/mandos.pid"
1744
pidfile = open(pidfilename, u"w")
1746
logger.error(u"Could not open file %r", pidfilename)
2055
uid = pwd.getpwnam("_mandos").pw_uid
2056
gid = pwd.getpwnam("_mandos").pw_gid
1749
uid = pwd.getpwnam(u"_mandos").pw_uid
1750
gid = pwd.getpwnam(u"_mandos").pw_gid
2057
1751
except KeyError:
2059
uid = pwd.getpwnam("mandos").pw_uid
2060
gid = pwd.getpwnam("mandos").pw_gid
1753
uid = pwd.getpwnam(u"mandos").pw_uid
1754
gid = pwd.getpwnam(u"mandos").pw_gid
2061
1755
except KeyError:
2063
uid = pwd.getpwnam("nobody").pw_uid
2064
gid = pwd.getpwnam("nobody").pw_gid
1757
uid = pwd.getpwnam(u"nobody").pw_uid
1758
gid = pwd.getpwnam(u"nobody").pw_gid
2065
1759
except KeyError:
2071
except OSError as error:
1765
except OSError, error:
2072
1766
if error[0] != errno.EPERM:
1769
# Enable all possible GnuTLS debugging
1772
if not debug and not debuglevel:
1773
syslogger.setLevel(logging.WARNING)
1774
console.setLevel(logging.WARNING)
1776
level = getattr(logging, debuglevel.upper())
1777
syslogger.setLevel(level)
1778
console.setLevel(level)
2076
# Enable all possible GnuTLS debugging
2078
1781
# "Use a log level over 10 to enable all debugging options."
2079
1782
# - GnuTLS manual
2080
1783
gnutls.library.functions.gnutls_global_set_log_level(11)
2082
1785
@gnutls.library.types.gnutls_log_func
2083
1786
def debug_gnutls(level, string):
2084
logger.debug("GnuTLS: %s", string[:-1])
1787
logger.debug(u"GnuTLS: %s", string[:-1])
2086
1789
(gnutls.library.functions
2087
1790
.gnutls_global_set_log_function(debug_gnutls))
2089
1792
# Redirect stdin so all checkers get /dev/null
2090
1793
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2091
1794
os.dup2(null, sys.stdin.fileno())
2108
1807
# End of Avahi example code
2111
bus_name = dbus.service.BusName("se.recompile.Mandos",
1810
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
2112
1811
bus, do_not_queue=True)
2113
old_bus_name = (dbus.service.BusName
2114
("se.bsnet.fukt.Mandos", bus,
2116
except dbus.exceptions.NameExistsException as e:
2117
logger.error(unicode(e) + ", disabling D-Bus")
1812
except dbus.exceptions.NameExistsException, e:
1813
logger.error(unicode(e) + u", disabling D-Bus")
2118
1814
use_dbus = False
2119
server_settings["use_dbus"] = False
1815
server_settings[u"use_dbus"] = False
2120
1816
tcp_server.use_dbus = False
2121
1817
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2122
service = AvahiServiceToSyslog(name =
2123
server_settings["servicename"],
2124
servicetype = "_mandos._tcp",
2125
protocol = protocol, bus = bus)
1818
service = AvahiService(name = server_settings[u"servicename"],
1819
servicetype = u"_mandos._tcp",
1820
protocol = protocol, bus = bus)
2126
1821
if server_settings["interface"]:
2127
1822
service.interface = (if_nametoindex
2128
(str(server_settings["interface"])))
1823
(str(server_settings[u"interface"])))
1826
# Close all input and output, do double fork, etc.
2130
1829
global multiprocessing_manager
2131
1830
multiprocessing_manager = multiprocessing.Manager()
2133
1832
client_class = Client
2135
client_class = functools.partial(ClientDBusTransitional,
2138
special_settings = {
2139
# Some settings need to be accessd by special methods;
2140
# booleans need .getboolean(), etc. Here is a list of them:
2141
"approved_by_default":
2143
client_config.getboolean(section, "approved_by_default"),
2145
# Construct a new dict of client settings of this form:
2146
# { client_name: {setting_name: value, ...}, ...}
2147
# with exceptions for any special settings as defined above
2148
client_settings = dict((clientname,
2151
if setting not in special_settings
2152
else special_settings[setting]
2154
for setting, value in
2155
client_config.items(clientname)))
2156
for clientname in client_config.sections())
2158
old_client_settings = {}
2161
# Get client data and settings from last running state.
2162
if server_settings["restore"]:
2164
with open(stored_state_path, "rb") as stored_state:
2165
clients_data, old_client_settings = (pickle.load
2167
os.remove(stored_state_path)
2168
except IOError as e:
2169
logger.warning("Could not load persistant state: {0}"
2171
if e.errno != errno.ENOENT:
2174
for client in clients_data:
2175
client_name = client["name"]
2177
# Decide which value to use after restoring saved state.
2178
# We have three different values: Old config file,
2179
# new config file, and saved state.
2180
# New config value takes precedence if it differs from old
2181
# config value, otherwise use saved state.
2182
for name, value in client_settings[client_name].items():
1834
client_class = functools.partial(ClientDBus, bus = bus)
1835
def client_config_items(config, section):
1836
special_settings = {
1837
"approved_by_default":
1838
lambda: config.getboolean(section,
1839
"approved_by_default"),
1841
for name, value in config.items(section):
2184
# For each value in new config, check if it differs
2185
# from the old config value (Except for the "secret"
2187
if (name != "secret" and
2188
value != old_client_settings[client_name][name]):
2189
setattr(client, name, value)
1843
yield (name, special_settings[name]())
2190
1844
except KeyError:
2193
# Clients who has passed its expire date, can still be enabled
2194
# if its last checker was sucessful. Clients who checkers
2195
# failed before we stored it state is asumed to had failed
2196
# checker during downtime.
2197
if client["enabled"] and client["last_checked_ok"]:
2198
if ((datetime.datetime.utcnow()
2199
- client["last_checked_ok"]) > client["interval"]):
2200
if client["last_checker_status"] != 0:
2201
client["enabled"] = False
2203
client["expires"] = (datetime.datetime.utcnow()
2204
+ client["timeout"])
2206
client["changedstate"] = (multiprocessing_manager
2207
.Condition(multiprocessing_manager
2210
new_client = (ClientDBusTransitional.__new__
2211
(ClientDBusTransitional))
2212
tcp_server.clients[client_name] = new_client
2213
new_client.bus = bus
2214
for name, value in client.iteritems():
2215
setattr(new_client, name, value)
2216
client_object_name = unicode(client_name).translate(
2217
{ord("."): ord("_"),
2218
ord("-"): ord("_")})
2219
new_client.dbus_object_path = (dbus.ObjectPath
2221
+ client_object_name))
2222
DBusObjectWithProperties.__init__(new_client,
2227
tcp_server.clients[client_name] = Client.__new__(Client)
2228
for name, value in client.iteritems():
2229
setattr(tcp_server.clients[client_name], name, value)
2231
tcp_server.clients[client_name].decrypt_secret(
2232
client_settings[client_name]["secret"])
2234
# Create/remove clients based on new changes made to config
2235
for clientname in set(old_client_settings) - set(client_settings):
2236
del tcp_server.clients[clientname]
2237
for clientname in set(client_settings) - set(old_client_settings):
2238
tcp_server.clients[clientname] = (client_class(name
1847
tcp_server.clients.update(set(
1848
client_class(name = section,
1849
config= dict(client_config_items(
1850
client_config, section)))
1851
for section in client_config.sections()))
2244
1852
if not tcp_server.clients:
2245
logger.warning("No clients defined")
1853
logger.warning(u"No clients defined")
1858
pidfile.write(str(pid) + "\n")
1861
logger.error(u"Could not write to file %r with PID %d",
1864
# "pidfile" was never created
2251
pidfile.write(str(pid) + "\n".encode("utf-8"))
2254
logger.error("Could not write to file %r with PID %d",
2257
# "pidfile" was never created
2261
1869
signal.signal(signal.SIGINT, signal.SIG_IGN)
2263
1870
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2264
1871
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2267
1874
class MandosDBusService(dbus.service.Object):
2268
1875
"""A D-Bus proxy object"""
2269
1876
def __init__(self):
2270
dbus.service.Object.__init__(self, bus, "/")
2271
_interface = "se.recompile.Mandos"
1877
dbus.service.Object.__init__(self, bus, u"/")
1878
_interface = u"se.bsnet.fukt.Mandos"
2273
@dbus.service.signal(_interface, signature="o")
1880
@dbus.service.signal(_interface, signature=u"o")
2274
1881
def ClientAdded(self, objpath):
2278
@dbus.service.signal(_interface, signature="ss")
1885
@dbus.service.signal(_interface, signature=u"ss")
2279
1886
def ClientNotFound(self, fingerprint, address):
2283
@dbus.service.signal(_interface, signature="os")
1890
@dbus.service.signal(_interface, signature=u"os")
2284
1891
def ClientRemoved(self, objpath, name):
2288
@dbus.service.method(_interface, out_signature="ao")
1895
@dbus.service.method(_interface, out_signature=u"ao")
2289
1896
def GetAllClients(self):
2291
1898
return dbus.Array(c.dbus_object_path
2293
tcp_server.clients.itervalues())
1899
for c in tcp_server.clients)
2295
1901
@dbus.service.method(_interface,
2296
out_signature="a{oa{sv}}")
1902
out_signature=u"a{oa{sv}}")
2297
1903
def GetAllClientsWithProperties(self):
2299
1905
return dbus.Dictionary(
2300
((c.dbus_object_path, c.GetAll(""))
2301
for c in tcp_server.clients.itervalues()),
1906
((c.dbus_object_path, c.GetAll(u""))
1907
for c in tcp_server.clients),
1908
signature=u"oa{sv}")
2304
@dbus.service.method(_interface, in_signature="o")
1910
@dbus.service.method(_interface, in_signature=u"o")
2305
1911
def RemoveClient(self, object_path):
2307
for c in tcp_server.clients.itervalues():
1913
for c in tcp_server.clients:
2308
1914
if c.dbus_object_path == object_path:
2309
del tcp_server.clients[c.name]
1915
tcp_server.clients.remove(c)
2310
1916
c.remove_from_connection()
2311
1917
# Don't signal anything except ClientRemoved
2312
1918
c.disable(quiet=True)
2320
class MandosDBusServiceTransitional(MandosDBusService):
2321
__metaclass__ = AlternateDBusNamesMetaclass
2322
mandos_dbus_service = MandosDBusServiceTransitional()
1926
mandos_dbus_service = MandosDBusService()
2325
1929
"Cleanup function; run on exit"
2326
1930
service.cleanup()
2328
multiprocessing.active_children()
2329
if not (tcp_server.clients or client_settings):
2332
# Store client before exiting. Secrets are encrypted with key
2333
# based on what config file has. If config file is
2334
# removed/edited, old secret will thus be unrecovable.
2336
for client in tcp_server.clients.itervalues():
2337
client.encrypt_secret(client_settings[client.name]
2342
# A list of attributes that will not be stored when
2344
exclude = set(("bus", "changedstate", "secret"))
2345
for name, typ in inspect.getmembers(dbus.service.Object):
2348
client_dict["encrypted_secret"] = client.encrypted_secret
2349
for attr in client.client_structure:
2350
if attr not in exclude:
2351
client_dict[attr] = getattr(client, attr)
2353
clients.append(client_dict)
2354
del client_settings[client.name]["secret"]
2357
with os.fdopen(os.open(stored_state_path,
2358
os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
2359
0600), "wb") as stored_state:
2360
pickle.dump((clients, client_settings), stored_state)
2361
except IOError as e:
2362
logger.warning("Could not save persistant state: {0}"
2364
if e.errno != errno.ENOENT:
2367
# Delete all clients, and settings from config
2368
1932
while tcp_server.clients:
2369
name, client = tcp_server.clients.popitem()
1933
client = tcp_server.clients.pop()
2371
1935
client.remove_from_connection()
1936
client.disable_hook = None
2372
1937
# Don't signal anything except ClientRemoved
2373
1938
client.disable(quiet=True)
2375
1940
# Emit D-Bus signal
2376
mandos_dbus_service.ClientRemoved(client
1941
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2379
client_settings.clear()
2381
1944
atexit.register(cleanup)
2383
for client in tcp_server.clients.itervalues():
1946
for client in tcp_server.clients:
2385
1948
# Emit D-Bus signal
2386
1949
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2387
# Need to initiate checking of clients
2389
client.init_checker()
2392
1952
tcp_server.enable()
2393
1953
tcp_server.server_activate()