188
208
self.group.Commit()
189
209
def entry_group_state_changed(self, state, error):
190
210
"""Derived from the Avahi example code"""
191
logger.debug(u"Avahi state change: %i", state)
211
logger.debug("Avahi entry group state change: %i", state)
193
213
if state == avahi.ENTRY_GROUP_ESTABLISHED:
194
logger.debug(u"Zeroconf service established.")
214
logger.debug("Zeroconf service established.")
195
215
elif state == avahi.ENTRY_GROUP_COLLISION:
196
logger.warning(u"Zeroconf service name collision.")
216
logger.info("Zeroconf service name collision.")
198
218
elif state == avahi.ENTRY_GROUP_FAILURE:
199
logger.critical(u"Avahi: Error in group state changed %s",
219
logger.critical("Avahi: Error in group state changed %s",
201
raise AvahiGroupError(u"State changed: %s"
221
raise AvahiGroupError("State changed: %s"
202
222
% unicode(error))
203
223
def cleanup(self):
204
224
"""Derived from the Avahi example code"""
205
225
if self.group is not None:
228
except (dbus.exceptions.UnknownMethodException,
229
dbus.exceptions.DBusException) as e:
207
231
self.group = None
208
def server_state_changed(self, state):
233
def server_state_changed(self, state, error=None):
209
234
"""Derived from the Avahi example code"""
210
if state == avahi.SERVER_COLLISION:
211
logger.error(u"Zeroconf server name collision")
235
logger.debug("Avahi server state change: %i", state)
236
bad_states = { avahi.SERVER_INVALID:
237
"Zeroconf server invalid",
238
avahi.SERVER_REGISTERING: None,
239
avahi.SERVER_COLLISION:
240
"Zeroconf server name collision",
241
avahi.SERVER_FAILURE:
242
"Zeroconf server failure" }
243
if state in bad_states:
244
if bad_states[state] is not None:
246
logger.error(bad_states[state])
248
logger.error(bad_states[state] + ": %r", error)
213
250
elif state == avahi.SERVER_RUNNING:
254
logger.debug("Unknown state: %r", state)
256
logger.debug("Unknown state: %r: %r", state, error)
215
257
def activate(self):
216
258
"""Derived from the Avahi example code"""
217
259
if self.server is None:
218
260
self.server = dbus.Interface(
219
261
self.bus.get_object(avahi.DBUS_NAME,
220
avahi.DBUS_PATH_SERVER),
262
avahi.DBUS_PATH_SERVER,
263
follow_name_owner_changes=True),
221
264
avahi.DBUS_INTERFACE_SERVER)
222
self.server.connect_to_signal(u"StateChanged",
265
self.server.connect_to_signal("StateChanged",
223
266
self.server_state_changed)
224
267
self.server_state_changed(self.server.GetState())
269
class AvahiServiceToSyslog(AvahiService):
271
"""Add the new name to the syslog messages"""
272
ret = AvahiService.rename(self)
273
syslogger.setFormatter(logging.Formatter
274
('Mandos (%s) [%%(process)d]:'
275
' %%(levelname)s: %%(message)s'
279
def _timedelta_to_milliseconds(td):
280
"Convert a datetime.timedelta() to milliseconds"
281
return ((td.days * 24 * 60 * 60 * 1000)
282
+ (td.seconds * 1000)
283
+ (td.microseconds // 1000))
227
285
class Client(object):
228
286
"""A representation of a client host served by this server.
231
name: string; from the config file, used in log messages and
233
fingerprint: string (40 or 32 hexadecimal digits); used to
234
uniquely identify the client
235
secret: bytestring; sent verbatim (over TLS) to client
236
host: string; available for use by the checker command
237
created: datetime.datetime(); (UTC) object creation
238
last_enabled: datetime.datetime(); (UTC)
240
last_checked_ok: datetime.datetime(); (UTC) or None
241
timeout: datetime.timedelta(); How long from last_checked_ok
242
until this client is invalid
243
interval: datetime.timedelta(); How often to start a new checker
244
disable_hook: If set, called by disable() as disable_hook(self)
289
_approved: bool(); 'None' if not yet approved/disapproved
290
approval_delay: datetime.timedelta(); Time to wait for approval
291
approval_duration: datetime.timedelta(); Duration of one approval
245
292
checker: subprocess.Popen(); a running checker process used
246
293
to see if the client lives.
247
294
'None' if no process is running.
248
checker_initiator_tag: a gobject event source tag, or None
249
disable_initiator_tag: - '' -
250
checker_callback_tag: - '' -
251
checker_command: string; External command which is run to check if
252
client lives. %() expansions are done at
295
checker_callback_tag: a gobject event source tag, or None
296
checker_command: string; External command which is run to check
297
if client lives. %() expansions are done at
253
298
runtime with vars(self) as dict, so that for
254
299
instance %(name)s can be used in the command.
300
checker_initiator_tag: a gobject event source tag, or None
301
created: datetime.datetime(); (UTC) object creation
302
client_structure: Object describing what attributes a client has
303
and is used for storing the client at exit
255
304
current_checker_command: string; current running checker_command
305
disable_initiator_tag: a gobject event source tag, or None
307
fingerprint: string (40 or 32 hexadecimal digits); used to
308
uniquely identify the client
309
host: string; available for use by the checker command
310
interval: datetime.timedelta(); How often to start a new checker
311
last_approval_request: datetime.datetime(); (UTC) or None
312
last_checked_ok: datetime.datetime(); (UTC) or None
313
last_checker_status: integer between 0 and 255 reflecting exit
314
status of last checker. -1 reflect crashed
316
last_enabled: datetime.datetime(); (UTC)
317
name: string; from the config file, used in log messages and
319
secret: bytestring; sent verbatim (over TLS) to client
320
timeout: datetime.timedelta(); How long from last_checked_ok
321
until this client is disabled
322
extended_timeout: extra long timeout when password has been sent
323
runtime_expansions: Allowed attributes for runtime expansion.
324
expires: datetime.datetime(); time (UTC) when a client will be
259
def _datetime_to_milliseconds(dt):
260
"Convert a datetime.datetime() to milliseconds"
261
return ((dt.days * 24 * 60 * 60 * 1000)
262
+ (dt.seconds * 1000)
263
+ (dt.microseconds // 1000))
328
runtime_expansions = ("approval_delay", "approval_duration",
329
"created", "enabled", "fingerprint",
330
"host", "interval", "last_checked_ok",
331
"last_enabled", "name", "timeout")
265
333
def timeout_milliseconds(self):
266
334
"Return the 'timeout' attribute in milliseconds"
267
return self._datetime_to_milliseconds(self.timeout)
335
return _timedelta_to_milliseconds(self.timeout)
337
def extended_timeout_milliseconds(self):
338
"Return the 'extended_timeout' attribute in milliseconds"
339
return _timedelta_to_milliseconds(self.extended_timeout)
269
341
def interval_milliseconds(self):
270
342
"Return the 'interval' attribute in milliseconds"
271
return self._datetime_to_milliseconds(self.interval)
273
def __init__(self, name = None, disable_hook=None, config=None):
343
return _timedelta_to_milliseconds(self.interval)
345
def approval_delay_milliseconds(self):
346
return _timedelta_to_milliseconds(self.approval_delay)
348
def __init__(self, name = None, config=None):
274
349
"""Note: the 'checker' key in 'config' sets the
275
350
'checker_command' attribute and *not* the 'checker'
278
353
if config is None:
280
logger.debug(u"Creating client %r", self.name)
355
logger.debug("Creating client %r", self.name)
281
356
# Uppercase and remove spaces from fingerprint for later
282
357
# comparison purposes with return value from the fingerprint()
284
self.fingerprint = (config[u"fingerprint"].upper()
286
logger.debug(u" Fingerprint: %s", self.fingerprint)
287
if u"secret" in config:
288
self.secret = config[u"secret"].decode(u"base64")
289
elif u"secfile" in config:
290
with closing(open(os.path.expanduser
292
(config[u"secfile"])))) as secfile:
359
self.fingerprint = (config["fingerprint"].upper()
361
logger.debug(" Fingerprint: %s", self.fingerprint)
362
if "secret" in config:
363
self.secret = config["secret"].decode("base64")
364
elif "secfile" in config:
365
with open(os.path.expanduser(os.path.expandvars
366
(config["secfile"])),
293
368
self.secret = secfile.read()
295
raise TypeError(u"No secret or secfile for client %s"
370
raise TypeError("No secret or secfile for client %s"
297
self.host = config.get(u"host", u"")
372
self.host = config.get("host", "")
298
373
self.created = datetime.datetime.utcnow()
300
self.last_enabled = None
375
self.last_approval_request = None
376
self.last_enabled = datetime.datetime.utcnow()
301
377
self.last_checked_ok = None
302
self.timeout = string_to_delta(config[u"timeout"])
303
self.interval = string_to_delta(config[u"interval"])
304
self.disable_hook = disable_hook
378
self.last_checker_status = None
379
self.timeout = string_to_delta(config["timeout"])
380
self.extended_timeout = string_to_delta(config
381
["extended_timeout"])
382
self.interval = string_to_delta(config["interval"])
305
383
self.checker = None
306
384
self.checker_initiator_tag = None
307
385
self.disable_initiator_tag = None
386
self.expires = datetime.datetime.utcnow() + self.timeout
308
387
self.checker_callback_tag = None
309
self.checker_command = config[u"checker"]
388
self.checker_command = config["checker"]
310
389
self.current_checker_command = None
311
self.last_connect = None
390
self._approved = None
391
self.approved_by_default = config.get("approved_by_default",
393
self.approvals_pending = 0
394
self.approval_delay = string_to_delta(
395
config["approval_delay"])
396
self.approval_duration = string_to_delta(
397
config["approval_duration"])
398
self.changedstate = (multiprocessing_manager
399
.Condition(multiprocessing_manager
401
self.client_structure = [attr for attr
402
in self.__dict__.iterkeys()
403
if not attr.startswith("_")]
404
self.client_structure.append("client_structure")
407
for name, t in inspect.getmembers(type(self),
411
if not name.startswith("_"):
412
self.client_structure.append(name)
414
# Send notice to process children that client state has changed
415
def send_changedstate(self):
416
with self.changedstate:
417
self.changedstate.notify_all()
313
419
def enable(self):
314
420
"""Start this client's checker and timeout hooks"""
315
if getattr(self, u"enabled", False):
421
if getattr(self, "enabled", False):
316
422
# Already enabled
424
self.send_changedstate()
425
self.expires = datetime.datetime.utcnow() + self.timeout
318
427
self.last_enabled = datetime.datetime.utcnow()
430
def disable(self, quiet=True):
431
"""Disable this client."""
432
if not getattr(self, "enabled", False):
435
self.send_changedstate()
437
logger.info("Disabling client %s", self.name)
438
if getattr(self, "disable_initiator_tag", False):
439
gobject.source_remove(self.disable_initiator_tag)
440
self.disable_initiator_tag = None
442
if getattr(self, "checker_initiator_tag", False):
443
gobject.source_remove(self.checker_initiator_tag)
444
self.checker_initiator_tag = None
447
# Do not run this again if called by a gobject.timeout_add
453
def init_checker(self):
319
454
# Schedule a new checker to be started an 'interval' from now,
320
455
# and every interval from then on.
321
456
self.checker_initiator_tag = (gobject.timeout_add
322
457
(self.interval_milliseconds(),
323
458
self.start_checker))
324
# Also start a new checker *right now*.
326
459
# Schedule a disable() when 'timeout' has passed
327
460
self.disable_initiator_tag = (gobject.timeout_add
328
461
(self.timeout_milliseconds(),
333
"""Disable this client."""
334
if not getattr(self, "enabled", False):
336
logger.info(u"Disabling client %s", self.name)
337
if getattr(self, u"disable_initiator_tag", False):
338
gobject.source_remove(self.disable_initiator_tag)
339
self.disable_initiator_tag = None
340
if getattr(self, u"checker_initiator_tag", False):
341
gobject.source_remove(self.checker_initiator_tag)
342
self.checker_initiator_tag = None
344
if self.disable_hook:
345
self.disable_hook(self)
347
# Do not run this again if called by a gobject.timeout_add
351
self.disable_hook = None
463
# Also start a new checker *right now*.
354
467
def checker_callback(self, pid, condition, command):
355
468
"""The checker has completed, so take appropriate actions."""
356
469
self.checker_callback_tag = None
357
470
self.checker = None
358
471
if os.WIFEXITED(condition):
359
exitstatus = os.WEXITSTATUS(condition)
361
logger.info(u"Checker for %(name)s succeeded",
472
self.last_checker_status = os.WEXITSTATUS(condition)
473
if self.last_checker_status == 0:
474
logger.info("Checker for %(name)s succeeded",
363
476
self.checked_ok()
365
logger.info(u"Checker for %(name)s failed",
478
logger.info("Checker for %(name)s failed",
368
logger.warning(u"Checker for %(name)s crashed?",
481
self.last_checker_status = -1
482
logger.warning("Checker for %(name)s crashed?",
371
def checked_ok(self):
485
def checked_ok(self, timeout=None):
372
486
"""Bump up the timeout for this client.
374
488
This should only be called when the client has been seen,
492
timeout = self.timeout
377
493
self.last_checked_ok = datetime.datetime.utcnow()
378
gobject.source_remove(self.disable_initiator_tag)
379
self.disable_initiator_tag = (gobject.timeout_add
380
(self.timeout_milliseconds(),
494
if self.disable_initiator_tag is not None:
495
gobject.source_remove(self.disable_initiator_tag)
496
if getattr(self, "enabled", False):
497
self.disable_initiator_tag = (gobject.timeout_add
498
(_timedelta_to_milliseconds
499
(timeout), self.disable))
500
self.expires = datetime.datetime.utcnow() + timeout
502
def need_approval(self):
503
self.last_approval_request = datetime.datetime.utcnow()
383
505
def start_checker(self):
384
506
"""Start a new checker subprocess if one is not running.
453
582
if self.checker_callback_tag:
454
583
gobject.source_remove(self.checker_callback_tag)
455
584
self.checker_callback_tag = None
456
if getattr(self, u"checker", None) is None:
585
if getattr(self, "checker", None) is None:
458
logger.debug(u"Stopping checker for %(name)s", vars(self))
587
logger.debug("Stopping checker for %(name)s", vars(self))
460
589
os.kill(self.checker.pid, signal.SIGTERM)
462
591
#if self.checker.poll() is None:
463
592
# os.kill(self.checker.pid, signal.SIGKILL)
464
except OSError, error:
593
except OSError as error:
465
594
if error.errno != errno.ESRCH: # No such process
467
596
self.checker = None
469
def still_valid(self):
470
"""Has the timeout not yet passed for this client?"""
471
if not getattr(self, u"enabled", False):
473
now = datetime.datetime.utcnow()
474
if self.last_checked_ok is None:
475
return now < (self.created + self.timeout)
598
# Encrypts a client secret and stores it in a varible
600
def encrypt_secret(self, key):
601
# Encryption-key need to be of a specific size, so we hash
603
hasheng = hashlib.sha256()
605
encryptionkey = hasheng.digest()
607
# Create validation hash so we know at decryption if it was
609
hasheng = hashlib.sha256()
610
hasheng.update(self.secret)
611
validationhash = hasheng.digest()
614
iv = os.urandom(Crypto.Cipher.AES.block_size)
615
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
616
Crypto.Cipher.AES.MODE_CFB, iv)
617
ciphertext = ciphereng.encrypt(validationhash+self.secret)
618
self.encrypted_secret = (ciphertext, iv)
620
# Decrypt a encrypted client secret
621
def decrypt_secret(self, key):
622
# Decryption-key need to be of a specific size, so we hash
624
hasheng = hashlib.sha256()
626
encryptionkey = hasheng.digest()
628
# Decrypt encrypted secret
629
ciphertext, iv = self.encrypted_secret
630
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
631
Crypto.Cipher.AES.MODE_CFB, iv)
632
plain = ciphereng.decrypt(ciphertext)
634
# Validate decrypted secret to know if it was succesful
635
hasheng = hashlib.sha256()
636
validationhash = plain[:hasheng.digest_size]
637
secret = plain[hasheng.digest_size:]
638
hasheng.update(secret)
640
# If validation fails, we use key as new secret. Otherwise, we
641
# use the decrypted secret
642
if hasheng.digest() == validationhash:
477
return now < (self.last_checked_ok + self.timeout)
480
class ClientDBus(Client, dbus.service.Object):
646
del self.encrypted_secret
649
def dbus_service_property(dbus_interface, signature="v",
650
access="readwrite", byte_arrays=False):
651
"""Decorators for marking methods of a DBusObjectWithProperties to
652
become properties on the D-Bus.
654
The decorated method will be called with no arguments by "Get"
655
and with one argument by "Set".
657
The parameters, where they are supported, are the same as
658
dbus.service.method, except there is only "signature", since the
659
type from Get() and the type sent to Set() is the same.
661
# Encoding deeply encoded byte arrays is not supported yet by the
662
# "Set" method, so we fail early here:
663
if byte_arrays and signature != "ay":
664
raise ValueError("Byte arrays not supported for non-'ay'"
665
" signature %r" % signature)
667
func._dbus_is_property = True
668
func._dbus_interface = dbus_interface
669
func._dbus_signature = signature
670
func._dbus_access = access
671
func._dbus_name = func.__name__
672
if func._dbus_name.endswith("_dbus_property"):
673
func._dbus_name = func._dbus_name[:-14]
674
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
679
class DBusPropertyException(dbus.exceptions.DBusException):
680
"""A base class for D-Bus property-related exceptions
682
def __unicode__(self):
683
return unicode(str(self))
686
class DBusPropertyAccessException(DBusPropertyException):
687
"""A property's access permissions disallows an operation.
692
class DBusPropertyNotFound(DBusPropertyException):
693
"""An attempt was made to access a non-existing property.
698
class DBusObjectWithProperties(dbus.service.Object):
699
"""A D-Bus object with properties.
701
Classes inheriting from this can use the dbus_service_property
702
decorator to expose methods as D-Bus properties. It exposes the
703
standard Get(), Set(), and GetAll() methods on the D-Bus.
707
def _is_dbus_property(obj):
708
return getattr(obj, "_dbus_is_property", False)
710
def _get_all_dbus_properties(self):
711
"""Returns a generator of (name, attribute) pairs
713
return ((prop.__get__(self)._dbus_name, prop.__get__(self))
714
for cls in self.__class__.__mro__
716
inspect.getmembers(cls, self._is_dbus_property))
718
def _get_dbus_property(self, interface_name, property_name):
719
"""Returns a bound method if one exists which is a D-Bus
720
property with the specified name and interface.
722
for cls in self.__class__.__mro__:
723
for name, value in (inspect.getmembers
724
(cls, self._is_dbus_property)):
725
if (value._dbus_name == property_name
726
and value._dbus_interface == interface_name):
727
return value.__get__(self)
730
raise DBusPropertyNotFound(self.dbus_object_path + ":"
731
+ interface_name + "."
734
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
736
def Get(self, interface_name, property_name):
737
"""Standard D-Bus property Get() method, see D-Bus standard.
739
prop = self._get_dbus_property(interface_name, property_name)
740
if prop._dbus_access == "write":
741
raise DBusPropertyAccessException(property_name)
743
if not hasattr(value, "variant_level"):
745
return type(value)(value, variant_level=value.variant_level+1)
747
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
748
def Set(self, interface_name, property_name, value):
749
"""Standard D-Bus property Set() method, see D-Bus standard.
751
prop = self._get_dbus_property(interface_name, property_name)
752
if prop._dbus_access == "read":
753
raise DBusPropertyAccessException(property_name)
754
if prop._dbus_get_args_options["byte_arrays"]:
755
# The byte_arrays option is not supported yet on
756
# signatures other than "ay".
757
if prop._dbus_signature != "ay":
759
value = dbus.ByteArray(''.join(unichr(byte)
763
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
764
out_signature="a{sv}")
765
def GetAll(self, interface_name):
766
"""Standard D-Bus property GetAll() method, see D-Bus
769
Note: Will not include properties with access="write".
772
for name, prop in self._get_all_dbus_properties():
774
and interface_name != prop._dbus_interface):
775
# Interface non-empty but did not match
777
# Ignore write-only properties
778
if prop._dbus_access == "write":
781
if not hasattr(value, "variant_level"):
784
all[name] = type(value)(value, variant_level=
785
value.variant_level+1)
786
return dbus.Dictionary(all, signature="sv")
788
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
790
path_keyword='object_path',
791
connection_keyword='connection')
792
def Introspect(self, object_path, connection):
793
"""Standard D-Bus method, overloaded to insert property tags.
795
xmlstring = dbus.service.Object.Introspect(self, object_path,
798
document = xml.dom.minidom.parseString(xmlstring)
799
def make_tag(document, name, prop):
800
e = document.createElement("property")
801
e.setAttribute("name", name)
802
e.setAttribute("type", prop._dbus_signature)
803
e.setAttribute("access", prop._dbus_access)
805
for if_tag in document.getElementsByTagName("interface"):
806
for tag in (make_tag(document, name, prop)
808
in self._get_all_dbus_properties()
809
if prop._dbus_interface
810
== if_tag.getAttribute("name")):
811
if_tag.appendChild(tag)
812
# Add the names to the return values for the
813
# "org.freedesktop.DBus.Properties" methods
814
if (if_tag.getAttribute("name")
815
== "org.freedesktop.DBus.Properties"):
816
for cn in if_tag.getElementsByTagName("method"):
817
if cn.getAttribute("name") == "Get":
818
for arg in cn.getElementsByTagName("arg"):
819
if (arg.getAttribute("direction")
821
arg.setAttribute("name", "value")
822
elif cn.getAttribute("name") == "GetAll":
823
for arg in cn.getElementsByTagName("arg"):
824
if (arg.getAttribute("direction")
826
arg.setAttribute("name", "props")
827
xmlstring = document.toxml("utf-8")
829
except (AttributeError, xml.dom.DOMException,
830
xml.parsers.expat.ExpatError) as error:
831
logger.error("Failed to override Introspection method",
836
def datetime_to_dbus (dt, variant_level=0):
837
"""Convert a UTC datetime.datetime() to a D-Bus type."""
839
return dbus.String("", variant_level = variant_level)
840
return dbus.String(dt.isoformat(),
841
variant_level=variant_level)
843
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
845
"""Applied to an empty subclass of a D-Bus object, this metaclass
846
will add additional D-Bus attributes matching a certain pattern.
848
def __new__(mcs, name, bases, attr):
849
# Go through all the base classes which could have D-Bus
850
# methods, signals, or properties in them
851
for base in (b for b in bases
852
if issubclass(b, dbus.service.Object)):
853
# Go though all attributes of the base class
854
for attrname, attribute in inspect.getmembers(base):
855
# Ignore non-D-Bus attributes, and D-Bus attributes
856
# with the wrong interface name
857
if (not hasattr(attribute, "_dbus_interface")
858
or not attribute._dbus_interface
859
.startswith("se.recompile.Mandos")):
861
# Create an alternate D-Bus interface name based on
863
alt_interface = (attribute._dbus_interface
864
.replace("se.recompile.Mandos",
865
"se.bsnet.fukt.Mandos"))
866
# Is this a D-Bus signal?
867
if getattr(attribute, "_dbus_is_signal", False):
868
# Extract the original non-method function by
870
nonmethod_func = (dict(
871
zip(attribute.func_code.co_freevars,
872
attribute.__closure__))["func"]
874
# Create a new, but exactly alike, function
875
# object, and decorate it to be a new D-Bus signal
876
# with the alternate D-Bus interface name
877
new_function = (dbus.service.signal
879
attribute._dbus_signature)
881
nonmethod_func.func_code,
882
nonmethod_func.func_globals,
883
nonmethod_func.func_name,
884
nonmethod_func.func_defaults,
885
nonmethod_func.func_closure)))
886
# Define a creator of a function to call both the
887
# old and new functions, so both the old and new
888
# signals gets sent when the function is called
889
def fixscope(func1, func2):
890
"""This function is a scope container to pass
891
func1 and func2 to the "call_both" function
892
outside of its arguments"""
893
def call_both(*args, **kwargs):
894
"""This function will emit two D-Bus
895
signals by calling func1 and func2"""
896
func1(*args, **kwargs)
897
func2(*args, **kwargs)
899
# Create the "call_both" function and add it to
901
attr[attrname] = fixscope(attribute,
903
# Is this a D-Bus method?
904
elif getattr(attribute, "_dbus_is_method", False):
905
# Create a new, but exactly alike, function
906
# object. Decorate it to be a new D-Bus method
907
# with the alternate D-Bus interface name. Add it
909
attr[attrname] = (dbus.service.method
911
attribute._dbus_in_signature,
912
attribute._dbus_out_signature)
914
(attribute.func_code,
915
attribute.func_globals,
917
attribute.func_defaults,
918
attribute.func_closure)))
919
# Is this a D-Bus property?
920
elif getattr(attribute, "_dbus_is_property", False):
921
# Create a new, but exactly alike, function
922
# object, and decorate it to be a new D-Bus
923
# property with the alternate D-Bus interface
924
# name. Add it to the class.
925
attr[attrname] = (dbus_service_property
927
attribute._dbus_signature,
928
attribute._dbus_access,
930
._dbus_get_args_options
933
(attribute.func_code,
934
attribute.func_globals,
936
attribute.func_defaults,
937
attribute.func_closure)))
938
return type.__new__(mcs, name, bases, attr)
940
class ClientDBus(Client, DBusObjectWithProperties):
481
941
"""A Client class using D-Bus
484
944
dbus_object_path: dbus.ObjectPath
485
945
bus: dbus.SystemBus()
948
runtime_expansions = (Client.runtime_expansions
949
+ ("dbus_object_path",))
487
951
# dbus.service.Object doesn't use super(), so we can't either.
489
953
def __init__(self, bus = None, *args, **kwargs):
955
self._approvals_pending = 0
491
956
Client.__init__(self, *args, **kwargs)
959
def add_to_dbus(self):
492
960
# Only now, when this client is initialized, can it show up on
962
client_object_name = unicode(self.name).translate(
494
965
self.dbus_object_path = (dbus.ObjectPath
496
+ self.name.replace(u".", u"_")))
497
dbus.service.Object.__init__(self, self.bus,
498
self.dbus_object_path)
501
def _datetime_to_dbus(dt, variant_level=0):
502
"""Convert a UTC datetime.datetime() to a D-Bus type."""
503
return dbus.String(dt.isoformat(),
504
variant_level=variant_level)
507
oldstate = getattr(self, u"enabled", False)
508
r = Client.enable(self)
509
if oldstate != self.enabled:
511
self.PropertyChanged(dbus.String(u"enabled"),
512
dbus.Boolean(True, variant_level=1))
513
self.PropertyChanged(
514
dbus.String(u"last_enabled"),
515
self._datetime_to_dbus(self.last_enabled,
519
def disable(self, signal = True):
520
oldstate = getattr(self, u"enabled", False)
521
r = Client.disable(self)
522
if signal and oldstate != self.enabled:
524
self.PropertyChanged(dbus.String(u"enabled"),
525
dbus.Boolean(False, variant_level=1))
966
("/clients/" + client_object_name))
967
DBusObjectWithProperties.__init__(self, self.bus,
968
self.dbus_object_path)
970
def notifychangeproperty(transform_func,
971
dbus_name, type_func=lambda x: x,
973
""" Modify a variable so that it's a property which announces
976
transform_fun: Function that takes a value and a variant_level
977
and transforms it to a D-Bus type.
978
dbus_name: D-Bus name of the variable
979
type_func: Function that transform the value before sending it
980
to the D-Bus. Default: no transform
981
variant_level: D-Bus variant level. Default: 1
983
attrname = "_{0}".format(dbus_name)
984
def setter(self, value):
985
if hasattr(self, "dbus_object_path"):
986
if (not hasattr(self, attrname) or
987
type_func(getattr(self, attrname, None))
988
!= type_func(value)):
989
dbus_value = transform_func(type_func(value),
992
self.PropertyChanged(dbus.String(dbus_name),
994
setattr(self, attrname, value)
996
return property(lambda self: getattr(self, attrname), setter)
999
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1000
approvals_pending = notifychangeproperty(dbus.Boolean,
1003
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1004
last_enabled = notifychangeproperty(datetime_to_dbus,
1006
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1007
type_func = lambda checker:
1008
checker is not None)
1009
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1011
last_approval_request = notifychangeproperty(
1012
datetime_to_dbus, "LastApprovalRequest")
1013
approved_by_default = notifychangeproperty(dbus.Boolean,
1014
"ApprovedByDefault")
1015
approval_delay = notifychangeproperty(dbus.UInt16,
1018
_timedelta_to_milliseconds)
1019
approval_duration = notifychangeproperty(
1020
dbus.UInt16, "ApprovalDuration",
1021
type_func = _timedelta_to_milliseconds)
1022
host = notifychangeproperty(dbus.String, "Host")
1023
timeout = notifychangeproperty(dbus.UInt16, "Timeout",
1025
_timedelta_to_milliseconds)
1026
extended_timeout = notifychangeproperty(
1027
dbus.UInt16, "ExtendedTimeout",
1028
type_func = _timedelta_to_milliseconds)
1029
interval = notifychangeproperty(dbus.UInt16,
1032
_timedelta_to_milliseconds)
1033
checker_command = notifychangeproperty(dbus.String, "Checker")
1035
del notifychangeproperty
528
1037
def __del__(self, *args, **kwargs):
530
1039
self.remove_from_connection()
531
1040
except LookupError:
533
if hasattr(dbus.service.Object, u"__del__"):
534
dbus.service.Object.__del__(self, *args, **kwargs)
1042
if hasattr(DBusObjectWithProperties, "__del__"):
1043
DBusObjectWithProperties.__del__(self, *args, **kwargs)
535
1044
Client.__del__(self, *args, **kwargs)
537
1046
def checker_callback(self, pid, condition, command,
538
1047
*args, **kwargs):
539
1048
self.checker_callback_tag = None
540
1049
self.checker = None
542
self.PropertyChanged(dbus.String(u"checker_running"),
543
dbus.Boolean(False, variant_level=1))
544
1050
if os.WIFEXITED(condition):
545
1051
exitstatus = os.WEXITSTATUS(condition)
546
1052
# Emit D-Bus signal
577
1074
and old_checker_pid != self.checker.pid):
578
1075
# Emit D-Bus signal
579
1076
self.CheckerStarted(self.current_checker_command)
580
self.PropertyChanged(
581
dbus.String(u"checker_running"),
582
dbus.Boolean(True, variant_level=1))
585
def stop_checker(self, *args, **kwargs):
586
old_checker = getattr(self, u"checker", None)
587
r = Client.stop_checker(self, *args, **kwargs)
588
if (old_checker is not None
589
and getattr(self, u"checker", None) is None):
590
self.PropertyChanged(dbus.String(u"checker_running"),
591
dbus.Boolean(False, variant_level=1))
594
## D-Bus methods & signals
595
_interface = u"se.bsnet.fukt.Mandos.Client"
598
@dbus.service.method(_interface)
600
return self.checked_ok()
1079
def _reset_approved(self):
1080
self._approved = None
1083
def approve(self, value=True):
1084
self.send_changedstate()
1085
self._approved = value
1086
gobject.timeout_add(_timedelta_to_milliseconds
1087
(self.approval_duration),
1088
self._reset_approved)
1091
## D-Bus methods, signals & properties
1092
_interface = "se.recompile.Mandos.Client"
602
1096
# CheckerCompleted - signal
603
@dbus.service.signal(_interface, signature=u"nxs")
1097
@dbus.service.signal(_interface, signature="nxs")
604
1098
def CheckerCompleted(self, exitcode, waitstatus, command):
608
1102
# CheckerStarted - signal
609
@dbus.service.signal(_interface, signature=u"s")
1103
@dbus.service.signal(_interface, signature="s")
610
1104
def CheckerStarted(self, command):
614
# GetAllProperties - method
615
@dbus.service.method(_interface, out_signature=u"a{sv}")
616
def GetAllProperties(self):
618
return dbus.Dictionary({
619
dbus.String(u"name"):
620
dbus.String(self.name, variant_level=1),
621
dbus.String(u"fingerprint"):
622
dbus.String(self.fingerprint, variant_level=1),
623
dbus.String(u"host"):
624
dbus.String(self.host, variant_level=1),
625
dbus.String(u"created"):
626
self._datetime_to_dbus(self.created,
628
dbus.String(u"last_enabled"):
629
(self._datetime_to_dbus(self.last_enabled,
631
if self.last_enabled is not None
632
else dbus.Boolean(False, variant_level=1)),
633
dbus.String(u"enabled"):
634
dbus.Boolean(self.enabled, variant_level=1),
635
dbus.String(u"last_checked_ok"):
636
(self._datetime_to_dbus(self.last_checked_ok,
638
if self.last_checked_ok is not None
639
else dbus.Boolean (False, variant_level=1)),
640
dbus.String(u"timeout"):
641
dbus.UInt64(self.timeout_milliseconds(),
643
dbus.String(u"interval"):
644
dbus.UInt64(self.interval_milliseconds(),
646
dbus.String(u"checker"):
647
dbus.String(self.checker_command,
649
dbus.String(u"checker_running"):
650
dbus.Boolean(self.checker is not None,
652
dbus.String(u"object_path"):
653
dbus.ObjectPath(self.dbus_object_path,
657
# IsStillValid - method
658
@dbus.service.method(_interface, out_signature=u"b")
659
def IsStillValid(self):
660
return self.still_valid()
662
1108
# PropertyChanged - signal
663
@dbus.service.signal(_interface, signature=u"sv")
1109
@dbus.service.signal(_interface, signature="sv")
664
1110
def PropertyChanged(self, property, value):
668
# ReceivedSecret - signal
1114
# GotSecret - signal
669
1115
@dbus.service.signal(_interface)
670
def ReceivedSecret(self):
1116
def GotSecret(self):
1118
Is sent after a successful transfer of secret from the Mandos
1119
server to mandos-client
674
1123
# Rejected - signal
675
@dbus.service.signal(_interface)
1124
@dbus.service.signal(_interface, signature="s")
1125
def Rejected(self, reason):
680
# SetChecker - method
681
@dbus.service.method(_interface, in_signature=u"s")
682
def SetChecker(self, checker):
683
"D-Bus setter method"
684
self.checker_command = checker
686
self.PropertyChanged(dbus.String(u"checker"),
687
dbus.String(self.checker_command,
691
@dbus.service.method(_interface, in_signature=u"s")
692
def SetHost(self, host):
693
"D-Bus setter method"
696
self.PropertyChanged(dbus.String(u"host"),
697
dbus.String(self.host, variant_level=1))
699
# SetInterval - method
700
@dbus.service.method(_interface, in_signature=u"t")
701
def SetInterval(self, milliseconds):
702
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
704
self.PropertyChanged(dbus.String(u"interval"),
705
(dbus.UInt64(self.interval_milliseconds(),
709
@dbus.service.method(_interface, in_signature=u"ay",
711
def SetSecret(self, secret):
712
"D-Bus setter method"
713
self.secret = str(secret)
715
# SetTimeout - method
716
@dbus.service.method(_interface, in_signature=u"t")
717
def SetTimeout(self, milliseconds):
718
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
720
self.PropertyChanged(dbus.String(u"timeout"),
721
(dbus.UInt64(self.timeout_milliseconds(),
1129
# NeedApproval - signal
1130
@dbus.service.signal(_interface, signature="tb")
1131
def NeedApproval(self, timeout, default):
1133
return self.need_approval()
1138
@dbus.service.method(_interface, in_signature="b")
1139
def Approve(self, value):
1142
# CheckedOK - method
1143
@dbus.service.method(_interface)
1144
def CheckedOK(self):
724
1147
# Enable - method
725
1148
@dbus.service.method(_interface)
744
1167
def StopChecker(self):
745
1168
self.stop_checker()
1172
# ApprovalPending - property
1173
@dbus_service_property(_interface, signature="b", access="read")
1174
def ApprovalPending_dbus_property(self):
1175
return dbus.Boolean(bool(self.approvals_pending))
1177
# ApprovedByDefault - property
1178
@dbus_service_property(_interface, signature="b",
1180
def ApprovedByDefault_dbus_property(self, value=None):
1181
if value is None: # get
1182
return dbus.Boolean(self.approved_by_default)
1183
self.approved_by_default = bool(value)
1185
# ApprovalDelay - property
1186
@dbus_service_property(_interface, signature="t",
1188
def ApprovalDelay_dbus_property(self, value=None):
1189
if value is None: # get
1190
return dbus.UInt64(self.approval_delay_milliseconds())
1191
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1193
# ApprovalDuration - property
1194
@dbus_service_property(_interface, signature="t",
1196
def ApprovalDuration_dbus_property(self, value=None):
1197
if value is None: # get
1198
return dbus.UInt64(_timedelta_to_milliseconds(
1199
self.approval_duration))
1200
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1203
@dbus_service_property(_interface, signature="s", access="read")
1204
def Name_dbus_property(self):
1205
return dbus.String(self.name)
1207
# Fingerprint - property
1208
@dbus_service_property(_interface, signature="s", access="read")
1209
def Fingerprint_dbus_property(self):
1210
return dbus.String(self.fingerprint)
1213
@dbus_service_property(_interface, signature="s",
1215
def Host_dbus_property(self, value=None):
1216
if value is None: # get
1217
return dbus.String(self.host)
1220
# Created - property
1221
@dbus_service_property(_interface, signature="s", access="read")
1222
def Created_dbus_property(self):
1223
return dbus.String(datetime_to_dbus(self.created))
1225
# LastEnabled - property
1226
@dbus_service_property(_interface, signature="s", access="read")
1227
def LastEnabled_dbus_property(self):
1228
return datetime_to_dbus(self.last_enabled)
1230
# Enabled - property
1231
@dbus_service_property(_interface, signature="b",
1233
def Enabled_dbus_property(self, value=None):
1234
if value is None: # get
1235
return dbus.Boolean(self.enabled)
1241
# LastCheckedOK - property
1242
@dbus_service_property(_interface, signature="s",
1244
def LastCheckedOK_dbus_property(self, value=None):
1245
if value is not None:
1248
return datetime_to_dbus(self.last_checked_ok)
1250
# Expires - property
1251
@dbus_service_property(_interface, signature="s", access="read")
1252
def Expires_dbus_property(self):
1253
return datetime_to_dbus(self.expires)
1255
# LastApprovalRequest - property
1256
@dbus_service_property(_interface, signature="s", access="read")
1257
def LastApprovalRequest_dbus_property(self):
1258
return datetime_to_dbus(self.last_approval_request)
1260
# Timeout - property
1261
@dbus_service_property(_interface, signature="t",
1263
def Timeout_dbus_property(self, value=None):
1264
if value is None: # get
1265
return dbus.UInt64(self.timeout_milliseconds())
1266
self.timeout = datetime.timedelta(0, 0, 0, value)
1267
if getattr(self, "disable_initiator_tag", None) is None:
1269
# Reschedule timeout
1270
gobject.source_remove(self.disable_initiator_tag)
1271
self.disable_initiator_tag = None
1273
time_to_die = _timedelta_to_milliseconds((self
1278
if time_to_die <= 0:
1279
# The timeout has passed
1282
self.expires = (datetime.datetime.utcnow()
1283
+ datetime.timedelta(milliseconds =
1285
self.disable_initiator_tag = (gobject.timeout_add
1286
(time_to_die, self.disable))
1288
# ExtendedTimeout - property
1289
@dbus_service_property(_interface, signature="t",
1291
def ExtendedTimeout_dbus_property(self, value=None):
1292
if value is None: # get
1293
return dbus.UInt64(self.extended_timeout_milliseconds())
1294
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1296
# Interval - property
1297
@dbus_service_property(_interface, signature="t",
1299
def Interval_dbus_property(self, value=None):
1300
if value is None: # get
1301
return dbus.UInt64(self.interval_milliseconds())
1302
self.interval = datetime.timedelta(0, 0, 0, value)
1303
if getattr(self, "checker_initiator_tag", None) is None:
1305
# Reschedule checker run
1306
gobject.source_remove(self.checker_initiator_tag)
1307
self.checker_initiator_tag = (gobject.timeout_add
1308
(value, self.start_checker))
1309
self.start_checker() # Start one now, too
1311
# Checker - property
1312
@dbus_service_property(_interface, signature="s",
1314
def Checker_dbus_property(self, value=None):
1315
if value is None: # get
1316
return dbus.String(self.checker_command)
1317
self.checker_command = value
1319
# CheckerRunning - property
1320
@dbus_service_property(_interface, signature="b",
1322
def CheckerRunning_dbus_property(self, value=None):
1323
if value is None: # get
1324
return dbus.Boolean(self.checker is not None)
1326
self.start_checker()
1330
# ObjectPath - property
1331
@dbus_service_property(_interface, signature="o", access="read")
1332
def ObjectPath_dbus_property(self):
1333
return self.dbus_object_path # is already a dbus.ObjectPath
1336
@dbus_service_property(_interface, signature="ay",
1337
access="write", byte_arrays=True)
1338
def Secret_dbus_property(self, value):
1339
self.secret = str(value)
1344
class ProxyClient(object):
1345
def __init__(self, child_pipe, fpr, address):
1346
self._pipe = child_pipe
1347
self._pipe.send(('init', fpr, address))
1348
if not self._pipe.recv():
1351
def __getattribute__(self, name):
1352
if(name == '_pipe'):
1353
return super(ProxyClient, self).__getattribute__(name)
1354
self._pipe.send(('getattr', name))
1355
data = self._pipe.recv()
1356
if data[0] == 'data':
1358
if data[0] == 'function':
1359
def func(*args, **kwargs):
1360
self._pipe.send(('funcall', name, args, kwargs))
1361
return self._pipe.recv()[1]
1364
def __setattr__(self, name, value):
1365
if(name == '_pipe'):
1366
return super(ProxyClient, self).__setattr__(name, value)
1367
self._pipe.send(('setattr', name, value))
1369
class ClientDBusTransitional(ClientDBus):
1370
__metaclass__ = AlternateDBusNamesMetaclass
750
1372
class ClientHandler(socketserver.BaseRequestHandler, object):
751
1373
"""A class to handle client connections.
754
1376
Note: This will run in its own forked process."""
756
1378
def handle(self):
757
logger.info(u"TCP connection from: %s",
758
unicode(self.client_address))
759
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
760
# Open IPC pipe to parent process
761
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
1379
with contextlib.closing(self.server.child_pipe) as child_pipe:
1380
logger.info("TCP connection from: %s",
1381
unicode(self.client_address))
1382
logger.debug("Pipe FD: %d",
1383
self.server.child_pipe.fileno())
762
1385
session = (gnutls.connection
763
1386
.ClientSession(self.request,
764
1387
gnutls.connection
765
1388
.X509Credentials()))
767
line = self.request.makefile().readline()
768
logger.debug(u"Protocol version: %r", line)
770
if int(line.strip().split()[0]) > 1:
772
except (ValueError, IndexError, RuntimeError), error:
773
logger.error(u"Unknown protocol version: %s", error)
776
1390
# Note: gnutls.connection.X509Credentials is really a
777
1391
# generic GnuTLS certificate credentials object so long as
778
1392
# no X.509 keys are added to it. Therefore, we can use it
779
1393
# here despite using OpenPGP certificates.
781
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
782
# u"+AES-256-CBC", u"+SHA1",
783
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1395
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1396
# "+AES-256-CBC", "+SHA1",
1397
# "+COMP-NULL", "+CTYPE-OPENPGP",
785
1399
# Use a fallback default, since this MUST be set.
786
1400
priority = self.server.gnutls_priority
787
1401
if priority is None:
789
1403
(gnutls.library.functions
790
1404
.gnutls_priority_set_direct(session._c_object,
791
1405
priority, None))
1407
# Start communication using the Mandos protocol
1408
# Get protocol number
1409
line = self.request.makefile().readline()
1410
logger.debug("Protocol version: %r", line)
1412
if int(line.strip().split()[0]) > 1:
1414
except (ValueError, IndexError, RuntimeError) as error:
1415
logger.error("Unknown protocol version: %s", error)
1418
# Start GnuTLS connection
794
1420
session.handshake()
795
except gnutls.errors.GNUTLSError, error:
796
logger.warning(u"Handshake failed: %s", error)
1421
except gnutls.errors.GNUTLSError as error:
1422
logger.warning("Handshake failed: %s", error)
797
1423
# Do not run session.bye() here: the session is not
798
1424
# established. Just abandon the request.
800
logger.debug(u"Handshake succeeded")
1426
logger.debug("Handshake succeeded")
1428
approval_required = False
802
fpr = self.fingerprint(self.peer_certificate(session))
803
except (TypeError, gnutls.errors.GNUTLSError), error:
804
logger.warning(u"Bad certificate: %s", error)
807
logger.debug(u"Fingerprint: %s", fpr)
1431
fpr = self.fingerprint(self.peer_certificate
1434
gnutls.errors.GNUTLSError) as error:
1435
logger.warning("Bad certificate: %s", error)
1437
logger.debug("Fingerprint: %s", fpr)
1440
client = ProxyClient(child_pipe, fpr,
1441
self.client_address)
1445
if client.approval_delay:
1446
delay = client.approval_delay
1447
client.approvals_pending += 1
1448
approval_required = True
1451
if not client.enabled:
1452
logger.info("Client %s is disabled",
1454
if self.server.use_dbus:
1456
client.Rejected("Disabled")
1459
if client._approved or not client.approval_delay:
1460
#We are approved or approval is disabled
1462
elif client._approved is None:
1463
logger.info("Client %s needs approval",
1465
if self.server.use_dbus:
1467
client.NeedApproval(
1468
client.approval_delay_milliseconds(),
1469
client.approved_by_default)
1471
logger.warning("Client %s was not approved",
1473
if self.server.use_dbus:
1475
client.Rejected("Denied")
1478
#wait until timeout or approved
1479
time = datetime.datetime.now()
1480
client.changedstate.acquire()
1481
(client.changedstate.wait
1482
(float(client._timedelta_to_milliseconds(delay)
1484
client.changedstate.release()
1485
time2 = datetime.datetime.now()
1486
if (time2 - time) >= delay:
1487
if not client.approved_by_default:
1488
logger.warning("Client %s timed out while"
1489
" waiting for approval",
1491
if self.server.use_dbus:
1493
client.Rejected("Approval timed out")
1498
delay -= time2 - time
1501
while sent_size < len(client.secret):
1503
sent = session.send(client.secret[sent_size:])
1504
except gnutls.errors.GNUTLSError as error:
1505
logger.warning("gnutls send failed")
1507
logger.debug("Sent: %d, remaining: %d",
1508
sent, len(client.secret)
1509
- (sent_size + sent))
1512
logger.info("Sending secret to %s", client.name)
1513
# bump the timeout using extended_timeout
1514
client.checked_ok(client.extended_timeout)
1515
if self.server.use_dbus:
809
for c in self.server.clients:
810
if c.fingerprint == fpr:
814
ipc.write(u"NOTFOUND %s\n" % fpr)
817
# Have to check if client.still_valid(), since it is
818
# possible that the client timed out while establishing
819
# the GnuTLS session.
820
if not client.still_valid():
821
ipc.write(u"INVALID %s\n" % client.name)
824
ipc.write(u"SENDING %s\n" % client.name)
826
while sent_size < len(client.secret):
827
sent = session.send(client.secret[sent_size:])
828
logger.debug(u"Sent: %d, remaining: %d",
829
sent, len(client.secret)
830
- (sent_size + sent))
1520
if approval_required:
1521
client.approvals_pending -= 1
1524
except gnutls.errors.GNUTLSError as error:
1525
logger.warning("GnuTLS bye failed")
835
1528
def peer_certificate(session):
1174
######################################################################
1901
##################################################################
1175
1902
# Parsing of options, both command line and config file
1177
parser = optparse.OptionParser(version = "%%prog %s" % version)
1178
parser.add_option("-i", u"--interface", type=u"string",
1179
metavar="IF", help=u"Bind to interface IF")
1180
parser.add_option("-a", u"--address", type=u"string",
1181
help=u"Address to listen for requests on")
1182
parser.add_option("-p", u"--port", type=u"int",
1183
help=u"Port number to receive requests on")
1184
parser.add_option("--check", action=u"store_true",
1185
help=u"Run self-test")
1186
parser.add_option("--debug", action=u"store_true",
1187
help=u"Debug mode; run in foreground and log to"
1189
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1190
u" priority string (see GnuTLS documentation)")
1191
parser.add_option("--servicename", type=u"string",
1192
metavar=u"NAME", help=u"Zeroconf service name")
1193
parser.add_option("--configdir", type=u"string",
1194
default=u"/etc/mandos", metavar=u"DIR",
1195
help=u"Directory to search for configuration"
1197
parser.add_option("--no-dbus", action=u"store_false",
1198
dest=u"use_dbus", help=u"Do not provide D-Bus"
1199
u" system bus interface")
1200
parser.add_option("--no-ipv6", action=u"store_false",
1201
dest=u"use_ipv6", help=u"Do not use IPv6")
1202
options = parser.parse_args()[0]
1904
parser = argparse.ArgumentParser()
1905
parser.add_argument("-v", "--version", action="version",
1906
version = "%%(prog)s %s" % version,
1907
help="show version number and exit")
1908
parser.add_argument("-i", "--interface", metavar="IF",
1909
help="Bind to interface IF")
1910
parser.add_argument("-a", "--address",
1911
help="Address to listen for requests on")
1912
parser.add_argument("-p", "--port", type=int,
1913
help="Port number to receive requests on")
1914
parser.add_argument("--check", action="store_true",
1915
help="Run self-test")
1916
parser.add_argument("--debug", action="store_true",
1917
help="Debug mode; run in foreground and log"
1919
parser.add_argument("--debuglevel", metavar="LEVEL",
1920
help="Debug level for stdout output")
1921
parser.add_argument("--priority", help="GnuTLS"
1922
" priority string (see GnuTLS documentation)")
1923
parser.add_argument("--servicename",
1924
metavar="NAME", help="Zeroconf service name")
1925
parser.add_argument("--configdir",
1926
default="/etc/mandos", metavar="DIR",
1927
help="Directory to search for configuration"
1929
parser.add_argument("--no-dbus", action="store_false",
1930
dest="use_dbus", help="Do not provide D-Bus"
1931
" system bus interface")
1932
parser.add_argument("--no-ipv6", action="store_false",
1933
dest="use_ipv6", help="Do not use IPv6")
1934
parser.add_argument("--no-restore", action="store_false",
1936
help="Do not restore stored state",
1939
options = parser.parse_args()
1204
1941
if options.check:
1252
1990
##################################################################
1254
1992
# For convenience
1255
debug = server_settings[u"debug"]
1256
use_dbus = server_settings[u"use_dbus"]
1257
use_ipv6 = server_settings[u"use_ipv6"]
1260
syslogger.setLevel(logging.WARNING)
1261
console.setLevel(logging.WARNING)
1263
if server_settings[u"servicename"] != u"Mandos":
1993
debug = server_settings["debug"]
1994
debuglevel = server_settings["debuglevel"]
1995
use_dbus = server_settings["use_dbus"]
1996
use_ipv6 = server_settings["use_ipv6"]
1998
if server_settings["servicename"] != "Mandos":
1264
1999
syslogger.setFormatter(logging.Formatter
1265
(u'Mandos (%s) [%%(process)d]:'
1266
u' %%(levelname)s: %%(message)s'
1267
% server_settings[u"servicename"]))
2000
('Mandos (%s) [%%(process)d]:'
2001
' %%(levelname)s: %%(message)s'
2002
% server_settings["servicename"]))
1269
2004
# Parse config file with clients
1270
client_defaults = { u"timeout": u"1h",
1272
u"checker": u"fping -q -- %%(host)s",
2005
client_defaults = { "timeout": "5m",
2006
"extended_timeout": "15m",
2008
"checker": "fping -q -- %%(host)s",
2010
"approval_delay": "0s",
2011
"approval_duration": "1s",
1275
2013
client_config = configparser.SafeConfigParser(client_defaults)
1276
client_config.read(os.path.join(server_settings[u"configdir"],
2014
client_config.read(os.path.join(server_settings["configdir"],
1279
2017
global mandos_dbus_service
1280
2018
mandos_dbus_service = None
1282
tcp_server = MandosServer((server_settings[u"address"],
1283
server_settings[u"port"]),
2020
tcp_server = MandosServer((server_settings["address"],
2021
server_settings["port"]),
1285
interface=server_settings[u"interface"],
2023
interface=(server_settings["interface"]
1286
2025
use_ipv6=use_ipv6,
1287
2026
gnutls_priority=
1288
server_settings[u"priority"],
2027
server_settings["priority"],
1289
2028
use_dbus=use_dbus)
1290
pidfilename = u"/var/run/mandos.pid"
1292
pidfile = open(pidfilename, u"w")
1294
logger.error(u"Could not open file %r", pidfilename)
2030
pidfilename = "/var/run/mandos.pid"
2032
pidfile = open(pidfilename, "w")
2034
logger.error("Could not open file %r", pidfilename)
1297
uid = pwd.getpwnam(u"_mandos").pw_uid
1298
gid = pwd.getpwnam(u"_mandos").pw_gid
2037
uid = pwd.getpwnam("_mandos").pw_uid
2038
gid = pwd.getpwnam("_mandos").pw_gid
1299
2039
except KeyError:
1301
uid = pwd.getpwnam(u"mandos").pw_uid
1302
gid = pwd.getpwnam(u"mandos").pw_gid
2041
uid = pwd.getpwnam("mandos").pw_uid
2042
gid = pwd.getpwnam("mandos").pw_gid
1303
2043
except KeyError:
1305
uid = pwd.getpwnam(u"nobody").pw_uid
1306
gid = pwd.getpwnam(u"nobody").pw_gid
2045
uid = pwd.getpwnam("nobody").pw_uid
2046
gid = pwd.getpwnam("nobody").pw_gid
1307
2047
except KeyError:
1313
except OSError, error:
2053
except OSError as error:
1314
2054
if error[0] != errno.EPERM:
1317
# Enable all possible GnuTLS debugging
2057
if not debug and not debuglevel:
2058
logger.setLevel(logging.WARNING)
2060
level = getattr(logging, debuglevel.upper())
2061
logger.setLevel(level)
2064
logger.setLevel(logging.DEBUG)
2065
# Enable all possible GnuTLS debugging
1319
2067
# "Use a log level over 10 to enable all debugging options."
1320
2068
# - GnuTLS manual
1321
2069
gnutls.library.functions.gnutls_global_set_log_level(11)
1323
2071
@gnutls.library.types.gnutls_log_func
1324
2072
def debug_gnutls(level, string):
1325
logger.debug(u"GnuTLS: %s", string[:-1])
2073
logger.debug("GnuTLS: %s", string[:-1])
1327
2075
(gnutls.library.functions
1328
2076
.gnutls_global_set_log_function(debug_gnutls))
2078
# Redirect stdin so all checkers get /dev/null
2079
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2080
os.dup2(null, sys.stdin.fileno())
2084
# No console logging
2085
logger.removeHandler(console)
2087
# Need to fork before connecting to D-Bus
2089
# Close all input and output, do double fork, etc.
1330
2092
global main_loop
1331
2093
# From the Avahi example code
1334
2096
bus = dbus.SystemBus()
1335
2097
# End of Avahi example code
1337
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
2100
bus_name = dbus.service.BusName("se.recompile.Mandos",
2101
bus, do_not_queue=True)
2102
old_bus_name = (dbus.service.BusName
2103
("se.bsnet.fukt.Mandos", bus,
2105
except dbus.exceptions.NameExistsException as e:
2106
logger.error(unicode(e) + ", disabling D-Bus")
2108
server_settings["use_dbus"] = False
2109
tcp_server.use_dbus = False
1338
2110
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1339
service = AvahiService(name = server_settings[u"servicename"],
1340
servicetype = u"_mandos._tcp",
1341
protocol = protocol, bus = bus)
2111
service = AvahiServiceToSyslog(name =
2112
server_settings["servicename"],
2113
servicetype = "_mandos._tcp",
2114
protocol = protocol, bus = bus)
1342
2115
if server_settings["interface"]:
1343
2116
service.interface = (if_nametoindex
1344
(str(server_settings[u"interface"])))
2117
(str(server_settings["interface"])))
2119
global multiprocessing_manager
2120
multiprocessing_manager = multiprocessing.Manager()
1346
2122
client_class = Client
1348
client_class = functools.partial(ClientDBus, bus = bus)
1349
tcp_server.clients.update(set(
1350
client_class(name = section,
1351
config= dict(client_config.items(section)))
1352
for section in client_config.sections()))
2124
client_class = functools.partial(ClientDBusTransitional,
2127
special_settings = {
2128
# Some settings need to be accessd by special methods;
2129
# booleans need .getboolean(), etc. Here is a list of them:
2130
"approved_by_default":
2132
client_config.getboolean(section, "approved_by_default"),
2134
# Construct a new dict of client settings of this form:
2135
# { client_name: {setting_name: value, ...}, ...}
2136
# with exceptions for any special settings as defined above
2137
client_settings = dict((clientname,
2140
setting not in special_settings
2141
else special_settings[setting]
2144
in client_config.items(clientname)))
2145
for clientname in client_config.sections())
2147
old_client_settings = {}
2150
# Get client data and settings from last running state.
2151
if server_settings["restore"]:
2153
with open(stored_state_path, "rb") as stored_state:
2154
clients_data, old_client_settings = (
2155
pickle.load(stored_state))
2156
os.remove(stored_state_path)
2157
except IOError as e:
2158
logger.warning("Could not load persistant state: {0}"
2160
if e.errno != errno.ENOENT:
2163
for client in clients_data:
2164
client_name = client["name"]
2166
# Decide which value to use after restoring saved state.
2167
# We have three different values: Old config file,
2168
# new config file, and saved state.
2169
# New config value takes precedence if it differs from old
2170
# config value, otherwise use saved state.
2171
for name, value in client_settings[client_name].items():
2173
# For each value in new config, check if it differs
2174
# from the old config value (Except for the "secret"
2176
if (name != "secret" and
2177
value != old_client_settings[client_name][name]):
2178
setattr(client, name, value)
2182
# Clients who has passed its expire date, can still be enabled
2183
# if its last checker was sucessful. Clients who checkers
2184
# failed before we stored it state is asumed to had failed
2185
# checker during downtime.
2186
if client["enabled"] and client["last_checked_ok"]:
2187
if ((datetime.datetime.utcnow()
2188
- client["last_checked_ok"]) > client["interval"]):
2189
if client["last_checker_status"] != 0:
2190
client["enabled"] = False
2192
client["expires"] = (datetime.datetime.utcnow()
2193
+ client["timeout"])
2195
client["changedstate"] = (multiprocessing_manager
2196
.Condition(multiprocessing_manager
2199
new_client = ClientDBusTransitional.__new__(
2200
ClientDBusTransitional)
2201
tcp_server.clients[client_name] = new_client
2202
new_client.bus = bus
2203
for name, value in client.iteritems():
2204
setattr(new_client, name, value)
2205
new_client._approvals_pending = 0
2206
new_client.add_to_dbus()
2208
tcp_server.clients[client_name] = Client.__new__(Client)
2209
for name, value in client.iteritems():
2210
setattr(tcp_server.clients[client_name], name, value)
2212
tcp_server.clients[client_name].decrypt_secret(
2213
client_settings[client_name]["secret"])
2215
# Create/remove clients based on new changes made to config
2216
for clientname in set(old_client_settings) - set(client_settings):
2217
del tcp_server.clients[clientname]
2218
for clientname in set(client_settings) - set(old_client_settings):
2219
tcp_server.clients[clientname] = client_class(name
1353
2225
if not tcp_server.clients:
1354
logger.warning(u"No clients defined")
1357
# Redirect stdin so all checkers get /dev/null
1358
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1359
os.dup2(null, sys.stdin.fileno())
1363
# No console logging
1364
logger.removeHandler(console)
1365
# Close all input and output, do double fork, etc.
1369
with closing(pidfile):
1371
pidfile.write(str(pid) + "\n")
1374
logger.error(u"Could not write to file %r with PID %d",
1377
# "pidfile" was never created
1382
"Cleanup function; run on exit"
2226
logger.warning("No clients defined")
1385
while tcp_server.clients:
1386
client = tcp_server.clients.pop()
1387
client.disable_hook = None
1390
atexit.register(cleanup)
2232
pidfile.write(str(pid) + "\n".encode("utf-8"))
2235
logger.error("Could not write to file %r with PID %d",
2238
# "pidfile" was never created
1393
2242
signal.signal(signal.SIGINT, signal.SIG_IGN)
1394
2244
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1395
2245
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1398
2248
class MandosDBusService(dbus.service.Object):
1399
2249
"""A D-Bus proxy object"""
1400
2250
def __init__(self):
1401
dbus.service.Object.__init__(self, bus, u"/")
1402
_interface = u"se.bsnet.fukt.Mandos"
1404
@dbus.service.signal(_interface, signature=u"oa{sv}")
1405
def ClientAdded(self, objpath, properties):
1409
@dbus.service.signal(_interface, signature=u"s")
1410
def ClientNotFound(self, fingerprint):
1414
@dbus.service.signal(_interface, signature=u"os")
2251
dbus.service.Object.__init__(self, bus, "/")
2252
_interface = "se.recompile.Mandos"
2254
@dbus.service.signal(_interface, signature="o")
2255
def ClientAdded(self, objpath):
2259
@dbus.service.signal(_interface, signature="ss")
2260
def ClientNotFound(self, fingerprint, address):
2264
@dbus.service.signal(_interface, signature="os")
1415
2265
def ClientRemoved(self, objpath, name):
1419
@dbus.service.method(_interface, out_signature=u"ao")
2269
@dbus.service.method(_interface, out_signature="ao")
1420
2270
def GetAllClients(self):
1422
2272
return dbus.Array(c.dbus_object_path
1423
for c in tcp_server.clients)
2274
tcp_server.clients.itervalues())
1425
2276
@dbus.service.method(_interface,
1426
out_signature=u"a{oa{sv}}")
2277
out_signature="a{oa{sv}}")
1427
2278
def GetAllClientsWithProperties(self):
1429
2280
return dbus.Dictionary(
1430
((c.dbus_object_path, c.GetAllProperties())
1431
for c in tcp_server.clients),
1432
signature=u"oa{sv}")
2281
((c.dbus_object_path, c.GetAll(""))
2282
for c in tcp_server.clients.itervalues()),
1434
@dbus.service.method(_interface, in_signature=u"o")
2285
@dbus.service.method(_interface, in_signature="o")
1435
2286
def RemoveClient(self, object_path):
1437
for c in tcp_server.clients:
2288
for c in tcp_server.clients.itervalues():
1438
2289
if c.dbus_object_path == object_path:
1439
tcp_server.clients.remove(c)
2290
del tcp_server.clients[c.name]
1440
2291
c.remove_from_connection()
1441
2292
# Don't signal anything except ClientRemoved
1442
c.disable(signal=False)
2293
c.disable(quiet=True)
1443
2294
# Emit D-Bus signal
1444
2295
self.ClientRemoved(object_path, c.name)
2297
raise KeyError(object_path)
1450
mandos_dbus_service = MandosDBusService()
1452
for client in tcp_server.clients:
2301
class MandosDBusServiceTransitional(MandosDBusService):
2302
__metaclass__ = AlternateDBusNamesMetaclass
2303
mandos_dbus_service = MandosDBusServiceTransitional()
2306
"Cleanup function; run on exit"
2309
multiprocessing.active_children()
2310
if not (tcp_server.clients or client_settings):
2313
# Store client before exiting. Secrets are encrypted with key
2314
# based on what config file has. If config file is
2315
# removed/edited, old secret will thus be unrecovable.
2317
for client in tcp_server.clients.itervalues():
2318
client.encrypt_secret(
2319
client_settings[client.name]["secret"])
2323
# A list of attributes that will not be stored when
2325
exclude = set(("bus", "changedstate", "secret"))
2326
for name, typ in inspect.getmembers(dbus.service.Object):
2329
client_dict["encrypted_secret"] = client.encrypted_secret
2330
for attr in client.client_structure:
2331
if attr not in exclude:
2332
client_dict[attr] = getattr(client, attr)
2334
clients.append(client_dict)
2335
del client_settings[client.name]["secret"]
2338
with os.fdopen(os.open(stored_state_path,
2339
os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
2340
stat.S_IRUSR | stat.S_IWUSR),
2341
"wb") as stored_state:
2342
pickle.dump((clients, client_settings), stored_state)
2343
except IOError as e:
2344
logger.warning("Could not save persistant state: {0}"
2346
if e.errno != errno.ENOENT:
2349
# Delete all clients, and settings from config
2350
while tcp_server.clients:
2351
name, client = tcp_server.clients.popitem()
2353
client.remove_from_connection()
2354
# Don't signal anything except ClientRemoved
2355
client.disable(quiet=True)
2358
mandos_dbus_service.ClientRemoved(client
2361
client_settings.clear()
2363
atexit.register(cleanup)
2365
for client in tcp_server.clients.itervalues():
1454
2367
# Emit D-Bus signal
1455
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1456
client.GetAllProperties())
2368
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2369
# Need to initiate checking of clients
2371
client.init_checker()
1459
2374
tcp_server.enable()
1460
2375
tcp_server.server_activate()