85
82
SO_BINDTODEVICE = None
89
stored_state_path = "/var/lib/mandos/clients.pickle"
91
logger = logging.getLogger()
87
#logger = logging.getLogger('mandos')
88
logger = logging.Logger('mandos')
92
89
syslogger = (logging.handlers.SysLogHandler
93
90
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
94
91
address = str("/dev/log")))
97
if_nametoindex = (ctypes.cdll.LoadLibrary
98
(ctypes.util.find_library("c"))
100
except (OSError, AttributeError):
101
def if_nametoindex(interface):
102
"Get an interface index the hard way, i.e. using fcntl()"
103
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
104
with contextlib.closing(socket.socket()) as s:
105
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
106
struct.pack(str("16s16x"),
108
interface_index = struct.unpack(str("I"),
110
return interface_index
113
def initlogger(level=logging.WARNING):
114
"""init logger and add loglevel"""
116
syslogger.setFormatter(logging.Formatter
117
('Mandos [%(process)d]: %(levelname)s:'
119
logger.addHandler(syslogger)
121
console = logging.StreamHandler()
122
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
126
logger.addHandler(console)
127
logger.setLevel(level)
92
syslogger.setFormatter(logging.Formatter
93
('Mandos [%(process)d]: %(levelname)s:'
95
logger.addHandler(syslogger)
97
console = logging.StreamHandler()
98
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
101
logger.addHandler(console)
130
103
class AvahiError(Exception):
131
104
def __init__(self, value, *args, **kwargs):
186
158
" after %i retries, exiting.",
187
159
self.rename_count)
188
160
raise AvahiServiceError("Too many renames")
189
self.name = unicode(self.server
190
.GetAlternativeServiceName(self.name))
161
self.name = unicode(self.server.GetAlternativeServiceName(self.name))
191
162
logger.info("Changing Zeroconf service name to %r ...",
164
syslogger.setFormatter(logging.Formatter
165
('Mandos (%s) [%%(process)d]:'
166
' %%(levelname)s: %%(message)s'
196
except dbus.exceptions.DBusException as error:
171
except dbus.exceptions.DBusException, error:
197
172
logger.critical("DBusException: %s", error)
200
175
self.rename_count += 1
201
176
def remove(self):
202
177
"""Derived from the Avahi example code"""
203
if self.entry_group_state_changed_match is not None:
204
self.entry_group_state_changed_match.remove()
205
self.entry_group_state_changed_match = None
206
178
if self.group is not None:
207
179
self.group.Reset()
209
181
"""Derived from the Avahi example code"""
211
182
if self.group is None:
212
183
self.group = dbus.Interface(
213
184
self.bus.get_object(avahi.DBUS_NAME,
214
185
self.server.EntryGroupNew()),
215
186
avahi.DBUS_INTERFACE_ENTRY_GROUP)
216
self.entry_group_state_changed_match = (
217
self.group.connect_to_signal(
218
'StateChanged', self.entry_group_state_changed))
187
self.group.connect_to_signal('StateChanged',
189
.entry_group_state_changed)
219
190
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
220
191
self.name, self.type)
221
192
self.group.AddService(
244
215
def cleanup(self):
245
216
"""Derived from the Avahi example code"""
246
217
if self.group is not None:
249
except (dbus.exceptions.UnknownMethodException,
250
dbus.exceptions.DBusException):
252
219
self.group = None
254
def server_state_changed(self, state, error=None):
220
def server_state_changed(self, state):
255
221
"""Derived from the Avahi example code"""
256
222
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)
223
if state == avahi.SERVER_COLLISION:
224
logger.error("Zeroconf server name collision")
271
226
elif state == avahi.SERVER_RUNNING:
275
logger.debug("Unknown state: %r", state)
277
logger.debug("Unknown state: %r: %r", state, error)
278
228
def activate(self):
279
229
"""Derived from the Avahi example code"""
280
230
if self.server is None:
281
231
self.server = dbus.Interface(
282
232
self.bus.get_object(avahi.DBUS_NAME,
283
avahi.DBUS_PATH_SERVER,
284
follow_name_owner_changes=True),
233
avahi.DBUS_PATH_SERVER),
285
234
avahi.DBUS_INTERFACE_SERVER)
286
235
self.server.connect_to_signal("StateChanged",
287
236
self.server_state_changed)
288
237
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
240
class Client(object):
307
241
"""A representation of a client host served by this server.
331
264
interval: datetime.timedelta(); How often to start a new checker
332
265
last_approval_request: datetime.datetime(); (UTC) or None
333
266
last_checked_ok: datetime.datetime(); (UTC) or None
334
last_checker_status: integer between 0 and 255 reflecting exit status
335
of last checker. -1 reflect crashed checker,
337
267
last_enabled: datetime.datetime(); (UTC)
338
268
name: string; from the config file, used in log messages and
339
269
D-Bus identifiers
340
270
secret: bytestring; sent verbatim (over TLS) to client
341
271
timeout: datetime.timedelta(); How long from last_checked_ok
342
272
until this client is disabled
343
extended_timeout: extra long timeout when password has been sent
344
273
runtime_expansions: Allowed attributes for runtime expansion.
345
expires: datetime.datetime(); time (UTC) when a client will be
349
276
runtime_expansions = ("approval_delay", "approval_duration",
351
278
"host", "interval", "last_checked_ok",
352
279
"last_enabled", "name", "timeout")
282
def _timedelta_to_milliseconds(td):
283
"Convert a datetime.timedelta() to milliseconds"
284
return ((td.days * 24 * 60 * 60 * 1000)
285
+ (td.seconds * 1000)
286
+ (td.microseconds // 1000))
354
288
def timeout_milliseconds(self):
355
289
"Return the 'timeout' attribute in milliseconds"
356
return _timedelta_to_milliseconds(self.timeout)
358
def extended_timeout_milliseconds(self):
359
"Return the 'extended_timeout' attribute in milliseconds"
360
return _timedelta_to_milliseconds(self.extended_timeout)
290
return self._timedelta_to_milliseconds(self.timeout)
362
292
def interval_milliseconds(self):
363
293
"Return the 'interval' attribute in milliseconds"
364
return _timedelta_to_milliseconds(self.interval)
294
return self._timedelta_to_milliseconds(self.interval)
366
296
def approval_delay_milliseconds(self):
367
return _timedelta_to_milliseconds(self.approval_delay)
297
return self._timedelta_to_milliseconds(self.approval_delay)
369
def __init__(self, name = None, config=None):
299
def __init__(self, name = None, disable_hook=None, config=None):
370
300
"""Note: the 'checker' key in 'config' sets the
371
301
'checker_command' attribute and *not* the 'checker'
393
323
self.host = config.get("host", "")
394
324
self.created = datetime.datetime.utcnow()
396
326
self.last_approval_request = None
397
self.last_enabled = datetime.datetime.utcnow()
327
self.last_enabled = None
398
328
self.last_checked_ok = None
399
self.last_checker_status = None
400
329
self.timeout = string_to_delta(config["timeout"])
401
self.extended_timeout = string_to_delta(config
402
["extended_timeout"])
403
330
self.interval = string_to_delta(config["interval"])
331
self.disable_hook = disable_hook
404
332
self.checker = None
405
333
self.checker_initiator_tag = None
406
334
self.disable_initiator_tag = None
407
self.expires = datetime.datetime.utcnow() + self.timeout
408
335
self.checker_callback_tag = None
409
336
self.checker_command = config["checker"]
410
337
self.current_checker_command = None
338
self.last_connect = None
411
339
self._approved = None
412
340
self.approved_by_default = config.get("approved_by_default",
416
344
config["approval_delay"])
417
345
self.approval_duration = string_to_delta(
418
346
config["approval_duration"])
419
self.changedstate = (multiprocessing_manager
420
.Condition(multiprocessing_manager
422
self.client_structure = [attr for attr in self.__dict__.iterkeys() if not attr.startswith("_")]
423
self.client_structure.append("client_structure")
426
for name, t in inspect.getmembers(type(self),
427
lambda obj: isinstance(obj, property)):
428
if not name.startswith("_"):
429
self.client_structure.append(name)
347
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
431
# Send notice to process children that client state has changed
432
349
def send_changedstate(self):
433
with self.changedstate:
434
self.changedstate.notify_all()
350
self.changedstate.acquire()
351
self.changedstate.notify_all()
352
self.changedstate.release()
436
354
def enable(self):
437
355
"""Start this client's checker and timeout hooks"""
438
356
if getattr(self, "enabled", False):
439
357
# Already enabled
441
359
self.send_changedstate()
442
self.expires = datetime.datetime.utcnow() + self.timeout
360
self.last_enabled = datetime.datetime.utcnow()
361
# Schedule a new checker to be started an 'interval' from now,
362
# and every interval from then on.
363
self.checker_initiator_tag = (gobject.timeout_add
364
(self.interval_milliseconds(),
366
# Schedule a disable() when 'timeout' has passed
367
self.disable_initiator_tag = (gobject.timeout_add
368
(self.timeout_milliseconds(),
443
370
self.enabled = True
444
self.last_enabled = datetime.datetime.utcnow()
371
# Also start a new checker *right now*.
447
374
def disable(self, quiet=True):
448
375
"""Disable this client."""
455
382
if getattr(self, "disable_initiator_tag", False):
456
383
gobject.source_remove(self.disable_initiator_tag)
457
384
self.disable_initiator_tag = None
459
385
if getattr(self, "checker_initiator_tag", False):
460
386
gobject.source_remove(self.checker_initiator_tag)
461
387
self.checker_initiator_tag = None
462
388
self.stop_checker()
389
if self.disable_hook:
390
self.disable_hook(self)
463
391
self.enabled = False
464
392
# Do not run this again if called by a gobject.timeout_add
467
395
def __del__(self):
396
self.disable_hook = None
470
def init_checker(self):
471
# Schedule a new checker to be started an 'interval' from now,
472
# and every interval from then on.
473
self.checker_initiator_tag = (gobject.timeout_add
474
(self.interval_milliseconds(),
476
# Schedule a disable() when 'timeout' has passed
477
self.disable_initiator_tag = (gobject.timeout_add
478
(self.timeout_milliseconds(),
480
# Also start a new checker *right now*.
484
399
def checker_callback(self, pid, condition, command):
485
400
"""The checker has completed, so take appropriate actions."""
486
401
self.checker_callback_tag = None
487
402
self.checker = None
488
403
if os.WIFEXITED(condition):
489
self.last_checker_status = os.WEXITSTATUS(condition)
490
if self.last_checker_status == 0:
404
exitstatus = os.WEXITSTATUS(condition)
491
406
logger.info("Checker for %(name)s succeeded",
493
408
self.checked_ok()
495
410
logger.info("Checker for %(name)s failed",
498
self.last_checker_status = -1
499
413
logger.warning("Checker for %(name)s crashed?",
502
def checked_ok(self, timeout=None):
416
def checked_ok(self):
503
417
"""Bump up the timeout for this client.
505
419
This should only be called when the client has been seen,
509
timeout = self.timeout
510
422
self.last_checked_ok = datetime.datetime.utcnow()
511
if self.disable_initiator_tag is not None:
512
gobject.source_remove(self.disable_initiator_tag)
513
if getattr(self, "enabled", False):
514
self.disable_initiator_tag = (gobject.timeout_add
515
(_timedelta_to_milliseconds
516
(timeout), self.disable))
517
self.expires = datetime.datetime.utcnow() + timeout
423
gobject.source_remove(self.disable_initiator_tag)
424
self.disable_initiator_tag = (gobject.timeout_add
425
(self.timeout_milliseconds(),
519
428
def need_approval(self):
520
429
self.last_approval_request = datetime.datetime.utcnow()
608
517
#if self.checker.poll() is None:
609
518
# os.kill(self.checker.pid, signal.SIGKILL)
610
except OSError as error:
519
except OSError, error:
611
520
if error.errno != errno.ESRCH: # No such process
613
522
self.checker = None
615
# Encrypts a client secret and stores it in a varible encrypted_secret
616
def encrypt_secret(self, key):
617
# Encryption-key need to be of a specific size, so we hash inputed key
618
hasheng = hashlib.sha256()
620
encryptionkey = hasheng.digest()
622
# Create validation hash so we know at decryption if it was sucessful
623
hasheng = hashlib.sha256()
624
hasheng.update(self.secret)
625
validationhash = hasheng.digest()
628
iv = os.urandom(Crypto.Cipher.AES.block_size)
629
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
630
Crypto.Cipher.AES.MODE_CFB, iv)
631
ciphertext = ciphereng.encrypt(validationhash+self.secret)
632
self.encrypted_secret = (ciphertext, iv)
634
# Decrypt a encrypted client secret
635
def decrypt_secret(self, key):
636
# Decryption-key need to be of a specific size, so we hash inputed key
637
hasheng = hashlib.sha256()
639
encryptionkey = hasheng.digest()
641
# Decrypt encrypted secret
642
ciphertext, iv = self.encrypted_secret
643
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
644
Crypto.Cipher.AES.MODE_CFB, iv)
645
plain = ciphereng.decrypt(ciphertext)
647
# Validate decrypted secret to know if it was succesful
648
hasheng = hashlib.sha256()
649
validationhash = plain[:hasheng.digest_size]
650
secret = plain[hasheng.digest_size:]
651
hasheng.update(secret)
653
# if validation fails, we use key as new secret. Otherwhise, we use
654
# the decrypted secret
655
if hasheng.digest() == validationhash:
659
del self.encrypted_secret
662
524
def dbus_service_property(dbus_interface, signature="v",
663
525
access="readwrite", byte_arrays=False):
664
526
"""Decorators for marking methods of a DBusObjectWithProperties to
723
585
def _get_all_dbus_properties(self):
724
586
"""Returns a generator of (name, attribute) pairs
726
return ((prop.__get__(self)._dbus_name, prop.__get__(self))
727
for cls in self.__class__.__mro__
588
return ((prop._dbus_name, prop)
728
589
for name, prop in
729
inspect.getmembers(cls, self._is_dbus_property))
590
inspect.getmembers(self, self._is_dbus_property))
731
592
def _get_dbus_property(self, interface_name, property_name):
732
593
"""Returns a bound method if one exists which is a D-Bus
733
594
property with the specified name and interface.
735
for cls in self.__class__.__mro__:
736
for name, value in (inspect.getmembers
737
(cls, self._is_dbus_property)):
738
if (value._dbus_name == property_name
739
and value._dbus_interface == interface_name):
740
return value.__get__(self)
596
for name in (property_name,
597
property_name + "_dbus_property"):
598
prop = getattr(self, name, None)
600
or not self._is_dbus_property(prop)
601
or prop._dbus_name != property_name
602
or (interface_name and prop._dbus_interface
603
and interface_name != prop._dbus_interface)):
742
606
# No such property
743
607
raise DBusPropertyNotFound(self.dbus_object_path + ":"
744
608
+ interface_name + "."
840
704
xmlstring = document.toxml("utf-8")
841
705
document.unlink()
842
706
except (AttributeError, xml.dom.DOMException,
843
xml.parsers.expat.ExpatError) as error:
707
xml.parsers.expat.ExpatError), error:
844
708
logger.error("Failed to override Introspection method",
849
def datetime_to_dbus (dt, variant_level=0):
850
"""Convert a UTC datetime.datetime() to a D-Bus type."""
852
return dbus.String("", variant_level = variant_level)
853
return dbus.String(dt.isoformat(),
854
variant_level=variant_level)
856
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
858
"""Applied to an empty subclass of a D-Bus object, this metaclass
859
will add additional D-Bus attributes matching a certain pattern.
861
def __new__(mcs, name, bases, attr):
862
# Go through all the base classes which could have D-Bus
863
# methods, signals, or properties in them
864
for base in (b for b in bases
865
if issubclass(b, dbus.service.Object)):
866
# Go though all attributes of the base class
867
for attrname, attribute in inspect.getmembers(base):
868
# Ignore non-D-Bus attributes, and D-Bus attributes
869
# with the wrong interface name
870
if (not hasattr(attribute, "_dbus_interface")
871
or not attribute._dbus_interface
872
.startswith("se.recompile.Mandos")):
874
# Create an alternate D-Bus interface name based on
876
alt_interface = (attribute._dbus_interface
877
.replace("se.recompile.Mandos",
878
"se.bsnet.fukt.Mandos"))
879
# Is this a D-Bus signal?
880
if getattr(attribute, "_dbus_is_signal", False):
881
# Extract the original non-method function by
883
nonmethod_func = (dict(
884
zip(attribute.func_code.co_freevars,
885
attribute.__closure__))["func"]
887
# Create a new, but exactly alike, function
888
# object, and decorate it to be a new D-Bus signal
889
# with the alternate D-Bus interface name
890
new_function = (dbus.service.signal
892
attribute._dbus_signature)
894
nonmethod_func.func_code,
895
nonmethod_func.func_globals,
896
nonmethod_func.func_name,
897
nonmethod_func.func_defaults,
898
nonmethod_func.func_closure)))
899
# Define a creator of a function to call both the
900
# old and new functions, so both the old and new
901
# signals gets sent when the function is called
902
def fixscope(func1, func2):
903
"""This function is a scope container to pass
904
func1 and func2 to the "call_both" function
905
outside of its arguments"""
906
def call_both(*args, **kwargs):
907
"""This function will emit two D-Bus
908
signals by calling func1 and func2"""
909
func1(*args, **kwargs)
910
func2(*args, **kwargs)
912
# Create the "call_both" function and add it to
914
attr[attrname] = fixscope(attribute,
916
# Is this a D-Bus method?
917
elif getattr(attribute, "_dbus_is_method", False):
918
# Create a new, but exactly alike, function
919
# object. Decorate it to be a new D-Bus method
920
# with the alternate D-Bus interface name. Add it
922
attr[attrname] = (dbus.service.method
924
attribute._dbus_in_signature,
925
attribute._dbus_out_signature)
927
(attribute.func_code,
928
attribute.func_globals,
930
attribute.func_defaults,
931
attribute.func_closure)))
932
# Is this a D-Bus property?
933
elif getattr(attribute, "_dbus_is_property", False):
934
# Create a new, but exactly alike, function
935
# object, and decorate it to be a new D-Bus
936
# property with the alternate D-Bus interface
937
# name. Add it to the class.
938
attr[attrname] = (dbus_service_property
940
attribute._dbus_signature,
941
attribute._dbus_access,
943
._dbus_get_args_options
946
(attribute.func_code,
947
attribute.func_globals,
949
attribute.func_defaults,
950
attribute.func_closure)))
951
return type.__new__(mcs, name, bases, attr)
953
713
class ClientDBus(Client, DBusObjectWithProperties):
954
714
"""A Client class using D-Bus
978
737
DBusObjectWithProperties.__init__(self, self.bus,
979
738
self.dbus_object_path)
981
def notifychangeproperty(transform_func,
982
dbus_name, type_func=lambda x: x,
984
""" Modify a variable so that it's a property which announces
740
def _get_approvals_pending(self):
741
return self._approvals_pending
742
def _set_approvals_pending(self, value):
743
old_value = self._approvals_pending
744
self._approvals_pending = value
746
if (hasattr(self, "dbus_object_path")
747
and bval is not bool(old_value)):
748
dbus_bool = dbus.Boolean(bval, variant_level=1)
749
self.PropertyChanged(dbus.String("ApprovalPending"),
987
transform_fun: Function that takes a value and a variant_level
988
and transforms it to a D-Bus type.
989
dbus_name: D-Bus name of the variable
990
type_func: Function that transform the value before sending it
991
to the D-Bus. Default: no transform
992
variant_level: D-Bus variant level. Default: 1
994
attrname = "_{0}".format(dbus_name)
995
def setter(self, value):
996
if hasattr(self, "dbus_object_path"):
997
if (not hasattr(self, attrname) or
998
type_func(getattr(self, attrname, None))
999
!= type_func(value)):
1000
dbus_value = transform_func(type_func(value),
1003
self.PropertyChanged(dbus.String(dbus_name),
1005
setattr(self, attrname, value)
1007
return property(lambda self: getattr(self, attrname), setter)
1010
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1011
approvals_pending = notifychangeproperty(dbus.Boolean,
1014
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1015
last_enabled = notifychangeproperty(datetime_to_dbus,
1017
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1018
type_func = lambda checker:
1019
checker is not None)
1020
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1022
last_approval_request = notifychangeproperty(
1023
datetime_to_dbus, "LastApprovalRequest")
1024
approved_by_default = notifychangeproperty(dbus.Boolean,
1025
"ApprovedByDefault")
1026
approval_delay = notifychangeproperty(dbus.UInt16,
1029
_timedelta_to_milliseconds)
1030
approval_duration = notifychangeproperty(
1031
dbus.UInt16, "ApprovalDuration",
1032
type_func = _timedelta_to_milliseconds)
1033
host = notifychangeproperty(dbus.String, "Host")
1034
timeout = notifychangeproperty(dbus.UInt16, "Timeout",
1036
_timedelta_to_milliseconds)
1037
extended_timeout = notifychangeproperty(
1038
dbus.UInt16, "ExtendedTimeout",
1039
type_func = _timedelta_to_milliseconds)
1040
interval = notifychangeproperty(dbus.UInt16,
1043
_timedelta_to_milliseconds)
1044
checker_command = notifychangeproperty(dbus.String, "Checker")
1046
del notifychangeproperty
752
approvals_pending = property(_get_approvals_pending,
753
_set_approvals_pending)
754
del _get_approvals_pending, _set_approvals_pending
757
def _datetime_to_dbus(dt, variant_level=0):
758
"""Convert a UTC datetime.datetime() to a D-Bus type."""
759
return dbus.String(dt.isoformat(),
760
variant_level=variant_level)
763
oldstate = getattr(self, "enabled", False)
764
r = Client.enable(self)
765
if oldstate != self.enabled:
767
self.PropertyChanged(dbus.String("Enabled"),
768
dbus.Boolean(True, variant_level=1))
769
self.PropertyChanged(
770
dbus.String("LastEnabled"),
771
self._datetime_to_dbus(self.last_enabled,
775
def disable(self, quiet = False):
776
oldstate = getattr(self, "enabled", False)
777
r = Client.disable(self, quiet=quiet)
778
if not quiet and oldstate != self.enabled:
780
self.PropertyChanged(dbus.String("Enabled"),
781
dbus.Boolean(False, variant_level=1))
1048
784
def __del__(self, *args, **kwargs):
1073
812
return Client.checker_callback(self, pid, condition, command,
1074
813
*args, **kwargs)
815
def checked_ok(self, *args, **kwargs):
816
r = Client.checked_ok(self, *args, **kwargs)
818
self.PropertyChanged(
819
dbus.String("LastCheckedOK"),
820
(self._datetime_to_dbus(self.last_checked_ok,
824
def need_approval(self, *args, **kwargs):
825
r = Client.need_approval(self, *args, **kwargs)
827
self.PropertyChanged(
828
dbus.String("LastApprovalRequest"),
829
(self._datetime_to_dbus(self.last_approval_request,
1076
833
def start_checker(self, *args, **kwargs):
1077
834
old_checker = self.checker
1078
835
if self.checker is not None:
1085
842
and old_checker_pid != self.checker.pid):
1086
843
# Emit D-Bus signal
1087
844
self.CheckerStarted(self.current_checker_command)
845
self.PropertyChanged(
846
dbus.String("CheckerRunning"),
847
dbus.Boolean(True, variant_level=1))
850
def stop_checker(self, *args, **kwargs):
851
old_checker = getattr(self, "checker", None)
852
r = Client.stop_checker(self, *args, **kwargs)
853
if (old_checker is not None
854
and getattr(self, "checker", None) is None):
855
self.PropertyChanged(dbus.String("CheckerRunning"),
856
dbus.Boolean(False, variant_level=1))
1090
859
def _reset_approved(self):
1091
860
self._approved = None
1208
972
if value is None: # get
1209
973
return dbus.UInt64(self.approval_delay_milliseconds())
1210
974
self.approval_delay = datetime.timedelta(0, 0, 0, value)
976
self.PropertyChanged(dbus.String("ApprovalDelay"),
977
dbus.UInt64(value, variant_level=1))
1212
979
# ApprovalDuration - property
1213
980
@dbus_service_property(_interface, signature="t",
1214
981
access="readwrite")
1215
982
def ApprovalDuration_dbus_property(self, value=None):
1216
983
if value is None: # get
1217
return dbus.UInt64(_timedelta_to_milliseconds(
984
return dbus.UInt64(self._timedelta_to_milliseconds(
1218
985
self.approval_duration))
1219
986
self.approval_duration = datetime.timedelta(0, 0, 0, value)
988
self.PropertyChanged(dbus.String("ApprovalDuration"),
989
dbus.UInt64(value, variant_level=1))
1221
991
# Name - property
1222
992
@dbus_service_property(_interface, signature="s", access="read")
1235
1005
if value is None: # get
1236
1006
return dbus.String(self.host)
1237
1007
self.host = value
1009
self.PropertyChanged(dbus.String("Host"),
1010
dbus.String(value, variant_level=1))
1239
1012
# Created - property
1240
1013
@dbus_service_property(_interface, signature="s", access="read")
1241
1014
def Created_dbus_property(self):
1242
return dbus.String(datetime_to_dbus(self.created))
1015
return dbus.String(self._datetime_to_dbus(self.created))
1244
1017
# LastEnabled - property
1245
1018
@dbus_service_property(_interface, signature="s", access="read")
1246
1019
def LastEnabled_dbus_property(self):
1247
return datetime_to_dbus(self.last_enabled)
1020
if self.last_enabled is None:
1021
return dbus.String("")
1022
return dbus.String(self._datetime_to_dbus(self.last_enabled))
1249
1024
# Enabled - property
1250
1025
@dbus_service_property(_interface, signature="b",
1283
1060
if value is None: # get
1284
1061
return dbus.UInt64(self.timeout_milliseconds())
1285
1062
self.timeout = datetime.timedelta(0, 0, 0, value)
1064
self.PropertyChanged(dbus.String("Timeout"),
1065
dbus.UInt64(value, variant_level=1))
1286
1066
if getattr(self, "disable_initiator_tag", None) is None:
1288
1068
# Reschedule timeout
1289
1069
gobject.source_remove(self.disable_initiator_tag)
1290
1070
self.disable_initiator_tag = None
1292
time_to_die = _timedelta_to_milliseconds((self
1071
time_to_die = (self.
1072
_timedelta_to_milliseconds((self
1297
1077
if time_to_die <= 0:
1298
1078
# The timeout has passed
1301
self.expires = (datetime.datetime.utcnow()
1302
+ datetime.timedelta(milliseconds =
1304
1081
self.disable_initiator_tag = (gobject.timeout_add
1305
1082
(time_to_die, self.disable))
1307
# ExtendedTimeout - property
1308
@dbus_service_property(_interface, signature="t",
1310
def ExtendedTimeout_dbus_property(self, value=None):
1311
if value is None: # get
1312
return dbus.UInt64(self.extended_timeout_milliseconds())
1313
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1315
1084
# Interval - property
1316
1085
@dbus_service_property(_interface, signature="t",
1317
1086
access="readwrite")
1431
1205
if int(line.strip().split()[0]) > 1:
1432
1206
raise RuntimeError
1433
except (ValueError, IndexError, RuntimeError) as error:
1207
except (ValueError, IndexError, RuntimeError), error:
1434
1208
logger.error("Unknown protocol version: %s", error)
1437
1211
# Start GnuTLS connection
1439
1213
session.handshake()
1440
except gnutls.errors.GNUTLSError as error:
1214
except gnutls.errors.GNUTLSError, error:
1441
1215
logger.warning("Handshake failed: %s", error)
1442
1216
# Do not run session.bye() here: the session is not
1443
1217
# established. Just abandon the request.
1445
1219
logger.debug("Handshake succeeded")
1447
1221
approval_required = False
1450
1224
fpr = self.fingerprint(self.peer_certificate
1453
gnutls.errors.GNUTLSError) as error:
1226
except (TypeError, gnutls.errors.GNUTLSError), error:
1454
1227
logger.warning("Bad certificate: %s", error)
1456
1229
logger.debug("Fingerprint: %s", fpr)
1457
if self.server.use_dbus:
1459
client.NewRequest(str(self.client_address))
1462
1232
client = ProxyClient(child_pipe, fpr,
1463
1233
self.client_address)
1636
1401
This function creates a new pipe in self.pipe
1638
1403
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1640
proc = MultiprocessingMixIn.process_request(self, request,
1405
super(MultiprocessingMixInWithPipe,
1406
self).process_request(request, client_address)
1642
1407
self.child_pipe.close()
1643
self.add_pipe(parent_pipe, proc)
1645
def add_pipe(self, parent_pipe, proc):
1408
self.add_pipe(parent_pipe)
1410
def add_pipe(self, parent_pipe):
1646
1411
"""Dummy function; override as necessary"""
1647
1412
raise NotImplementedError
1650
1414
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1651
1415
socketserver.TCPServer, object):
1652
1416
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
1736
1500
def server_activate(self):
1737
1501
if self.enabled:
1738
1502
return socketserver.TCPServer.server_activate(self)
1740
1503
def enable(self):
1741
1504
self.enabled = True
1743
def add_pipe(self, parent_pipe, proc):
1505
def add_pipe(self, parent_pipe):
1744
1506
# Call "handle_ipc" for both data and EOF events
1745
1507
gobject.io_add_watch(parent_pipe.fileno(),
1746
1508
gobject.IO_IN | gobject.IO_HUP,
1747
1509
functools.partial(self.handle_ipc,
1510
parent_pipe = parent_pipe))
1752
1512
def handle_ipc(self, source, condition, parent_pipe=None,
1753
proc = None, client_object=None):
1513
client_object=None):
1754
1514
condition_names = {
1755
1515
gobject.IO_IN: "IN", # There is data to read.
1756
1516
gobject.IO_OUT: "OUT", # Data can be written (without
1779
1537
fpr = request[1]
1780
1538
address = request[2]
1782
for c in self.clients.itervalues():
1540
for c in self.clients:
1783
1541
if c.fingerprint == fpr:
1787
logger.info("Client not found for fingerprint: %s, ad"
1788
"dress: %s", fpr, address)
1545
logger.warning("Client not found for fingerprint: %s, ad"
1546
"dress: %s", fpr, address)
1789
1547
if self.use_dbus:
1790
1548
# Emit D-Bus signal
1791
mandos_dbus_service.ClientNotFound(fpr,
1549
mandos_dbus_service.ClientNotFound(fpr, address[0])
1793
1550
parent_pipe.send(False)
1796
1553
gobject.io_add_watch(parent_pipe.fileno(),
1797
1554
gobject.IO_IN | gobject.IO_HUP,
1798
1555
functools.partial(self.handle_ipc,
1556
parent_pipe = parent_pipe,
1557
client_object = client))
1804
1558
parent_pipe.send(True)
1805
# remove the old hook in favor of the new above hook on
1559
# remove the old hook in favor of the new above hook on same fileno
1808
1561
if command == 'funcall':
1809
1562
funcname = request[1]
1810
1563
args = request[2]
1811
1564
kwargs = request[3]
1813
parent_pipe.send(('data', getattr(client_object,
1566
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1817
1568
if command == 'getattr':
1818
1569
attrname = request[1]
1819
1570
if callable(client_object.__getattribute__(attrname)):
1820
1571
parent_pipe.send(('function',))
1822
parent_pipe.send(('data', client_object
1823
.__getattribute__(attrname)))
1573
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1825
1575
if command == 'setattr':
1826
1576
attrname = request[1]
1827
1577
value = request[2]
1828
1578
setattr(client_object, attrname, value)
1863
1613
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1865
1615
raise ValueError("Unknown suffix %r" % suffix)
1866
except (ValueError, IndexError) as e:
1616
except (ValueError, IndexError), e:
1867
1617
raise ValueError(*(e.args))
1868
1618
timevalue += delta
1869
1619
return timevalue
1622
def if_nametoindex(interface):
1623
"""Call the C function if_nametoindex(), or equivalent
1625
Note: This function cannot accept a unicode string."""
1626
global if_nametoindex
1628
if_nametoindex = (ctypes.cdll.LoadLibrary
1629
(ctypes.util.find_library("c"))
1631
except (OSError, AttributeError):
1632
logger.warning("Doing if_nametoindex the hard way")
1633
def if_nametoindex(interface):
1634
"Get an interface index the hard way, i.e. using fcntl()"
1635
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1636
with contextlib.closing(socket.socket()) as s:
1637
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1638
struct.pack(str("16s16x"),
1640
interface_index = struct.unpack(str("I"),
1642
return interface_index
1643
return if_nametoindex(interface)
1872
1646
def daemon(nochdir = False, noclose = False):
1873
1647
"""See daemon(3). Standard BSD Unix function.
1899
1673
##################################################################
1900
1674
# Parsing of options, both command line and config file
1902
parser = argparse.ArgumentParser()
1903
parser.add_argument("-v", "--version", action="version",
1904
version = "%%(prog)s %s" % version,
1905
help="show version number and exit")
1906
parser.add_argument("-i", "--interface", metavar="IF",
1907
help="Bind to interface IF")
1908
parser.add_argument("-a", "--address",
1909
help="Address to listen for requests on")
1910
parser.add_argument("-p", "--port", type=int,
1911
help="Port number to receive requests on")
1912
parser.add_argument("--check", action="store_true",
1913
help="Run self-test")
1914
parser.add_argument("--debug", action="store_true",
1915
help="Debug mode; run in foreground and log"
1917
parser.add_argument("--debuglevel", metavar="LEVEL",
1918
help="Debug level for stdout output")
1919
parser.add_argument("--priority", help="GnuTLS"
1920
" priority string (see GnuTLS documentation)")
1921
parser.add_argument("--servicename",
1922
metavar="NAME", help="Zeroconf service name")
1923
parser.add_argument("--configdir",
1924
default="/etc/mandos", metavar="DIR",
1925
help="Directory to search for configuration"
1927
parser.add_argument("--no-dbus", action="store_false",
1928
dest="use_dbus", help="Do not provide D-Bus"
1929
" system bus interface")
1930
parser.add_argument("--no-ipv6", action="store_false",
1931
dest="use_ipv6", help="Do not use IPv6")
1932
parser.add_argument("--no-restore", action="store_false",
1933
dest="restore", help="Do not restore stored state",
1936
options = parser.parse_args()
1676
parser = optparse.OptionParser(version = "%%prog %s" % version)
1677
parser.add_option("-i", "--interface", type="string",
1678
metavar="IF", help="Bind to interface IF")
1679
parser.add_option("-a", "--address", type="string",
1680
help="Address to listen for requests on")
1681
parser.add_option("-p", "--port", type="int",
1682
help="Port number to receive requests on")
1683
parser.add_option("--check", action="store_true",
1684
help="Run self-test")
1685
parser.add_option("--debug", action="store_true",
1686
help="Debug mode; run in foreground and log to"
1688
parser.add_option("--debuglevel", type="string", metavar="LEVEL",
1689
help="Debug level for stdout output")
1690
parser.add_option("--priority", type="string", help="GnuTLS"
1691
" priority string (see GnuTLS documentation)")
1692
parser.add_option("--servicename", type="string",
1693
metavar="NAME", help="Zeroconf service name")
1694
parser.add_option("--configdir", type="string",
1695
default="/etc/mandos", metavar="DIR",
1696
help="Directory to search for configuration"
1698
parser.add_option("--no-dbus", action="store_false",
1699
dest="use_dbus", help="Do not provide D-Bus"
1700
" system bus interface")
1701
parser.add_option("--no-ipv6", action="store_false",
1702
dest="use_ipv6", help="Do not use IPv6")
1703
options = parser.parse_args()[0]
1938
1705
if options.check:
2096
1861
# End of Avahi example code
2099
bus_name = dbus.service.BusName("se.recompile.Mandos",
1864
bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
2100
1865
bus, do_not_queue=True)
2101
old_bus_name = (dbus.service.BusName
2102
("se.bsnet.fukt.Mandos", bus,
2104
except dbus.exceptions.NameExistsException as e:
1866
except dbus.exceptions.NameExistsException, e:
2105
1867
logger.error(unicode(e) + ", disabling D-Bus")
2106
1868
use_dbus = False
2107
1869
server_settings["use_dbus"] = False
2108
1870
tcp_server.use_dbus = False
2109
1871
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2110
service = AvahiServiceToSyslog(name =
2111
server_settings["servicename"],
2112
servicetype = "_mandos._tcp",
2113
protocol = protocol, bus = bus)
1872
service = AvahiService(name = server_settings["servicename"],
1873
servicetype = "_mandos._tcp",
1874
protocol = protocol, bus = bus)
2114
1875
if server_settings["interface"]:
2115
1876
service.interface = (if_nametoindex
2116
1877
(str(server_settings["interface"])))
2121
1882
client_class = Client
2123
client_class = functools.partial(ClientDBusTransitional,
2126
special_settings = {
2127
# Some settings need to be accessd by special methods;
2128
# booleans need .getboolean(), etc. Here is a list of them:
2129
"approved_by_default":
2131
client_config.getboolean(section, "approved_by_default"),
2133
# Construct a new dict of client settings of this form:
2134
# { client_name: {setting_name: value, ...}, ...}
2135
# with exceptions for any special settings as defined above
2136
client_settings = dict((clientname,
2138
(value if setting not in special_settings
2139
else special_settings[setting](clientname)))
2140
for setting, value in client_config.items(clientname)))
2141
for clientname in client_config.sections())
2143
old_client_settings = {}
2146
# Get client data and settings from last running state.
2147
if server_settings["restore"]:
2149
with open(stored_state_path, "rb") as stored_state:
2150
clients_data, old_client_settings = pickle.load(stored_state)
2151
os.remove(stored_state_path)
2152
except IOError as e:
2153
logger.warning("Could not load persistant state: {0}".format(e))
2154
if e.errno != errno.ENOENT:
2157
for client in clients_data:
2158
client_name = client["name"]
2160
# Decide which value to use after restoring saved state.
2161
# We have three different values: Old config file,
2162
# new config file, and saved state.
2163
# New config value takes precedence if it differs from old
2164
# config value, otherwise use saved state.
2165
for name, value in client_settings[client_name].items():
1884
client_class = functools.partial(ClientDBus, bus = bus)
1885
def client_config_items(config, section):
1886
special_settings = {
1887
"approved_by_default":
1888
lambda: config.getboolean(section,
1889
"approved_by_default"),
1891
for name, value in config.items(section):
2167
# For each value in new config, check if it differs
2168
# from the old config value (Except for the "secret"
2170
if name != "secret" and value != old_client_settings[client_name][name]:
2171
setattr(client, name, value)
1893
yield (name, special_settings[name]())
2172
1894
except KeyError:
2175
# Clients who has passed its expire date, can still be enabled if its
2176
# last checker was sucessful. Clients who checkers failed before we
2177
# stored it state is asumed to had failed checker during downtime.
2178
if client["enabled"] and client["last_checked_ok"]:
2179
if ((datetime.datetime.utcnow() - client["last_checked_ok"])
2180
> client["interval"]):
2181
if client["last_checker_status"] != 0:
2182
client["enabled"] = False
2184
client["expires"] = datetime.datetime.utcnow() + client["timeout"]
2186
client["changedstate"] = (multiprocessing_manager
2187
.Condition(multiprocessing_manager
2190
new_client = ClientDBusTransitional.__new__(ClientDBusTransitional)
2191
tcp_server.clients[client_name] = new_client
2192
new_client.bus = bus
2193
for name, value in client.iteritems():
2194
setattr(new_client, name, value)
2195
client_object_name = unicode(client_name).translate(
2196
{ord("."): ord("_"),
2197
ord("-"): ord("_")})
2198
new_client.dbus_object_path = (dbus.ObjectPath
2199
("/clients/" + client_object_name))
2200
DBusObjectWithProperties.__init__(new_client,
2202
new_client.dbus_object_path)
2204
tcp_server.clients[client_name] = Client.__new__(Client)
2205
for name, value in client.iteritems():
2206
setattr(tcp_server.clients[client_name], name, value)
2208
tcp_server.clients[client_name].decrypt_secret(
2209
client_settings[client_name]["secret"])
2211
# Create/remove clients based on new changes made to config
2212
for clientname in set(old_client_settings) - set(client_settings):
2213
del tcp_server.clients[clientname]
2214
for clientname in set(client_settings) - set(old_client_settings):
2215
tcp_server.clients[clientname] = (client_class(name = clientname,
1897
tcp_server.clients.update(set(
1898
client_class(name = section,
1899
config= dict(client_config_items(
1900
client_config, section)))
1901
for section in client_config.sections()))
2221
1902
if not tcp_server.clients:
2222
1903
logger.warning("No clients defined")
2297
class MandosDBusServiceTransitional(MandosDBusService):
2298
__metaclass__ = AlternateDBusNamesMetaclass
2299
mandos_dbus_service = MandosDBusServiceTransitional()
1977
mandos_dbus_service = MandosDBusService()
2302
1980
"Cleanup function; run on exit"
2303
1981
service.cleanup()
2305
multiprocessing.active_children()
2306
if not (tcp_server.clients or client_settings):
2309
# Store client before exiting. Secrets are encrypted with key based
2310
# on what config file has. If config file is removed/edited, old
2311
# secret will thus be unrecovable.
2313
for client in tcp_server.clients.itervalues():
2314
client.encrypt_secret(client_settings[client.name]["secret"])
2318
# A list of attributes that will not be stored when shuting down.
2319
exclude = set(("bus", "changedstate", "secret"))
2320
for name, typ in inspect.getmembers(dbus.service.Object):
2323
client_dict["encrypted_secret"] = client.encrypted_secret
2324
for attr in client.client_structure:
2325
if attr not in exclude:
2326
client_dict[attr] = getattr(client, attr)
2328
clients.append(client_dict)
2329
del client_settings[client.name]["secret"]
2332
with os.fdopen(os.open(stored_state_path, os.O_CREAT|os.O_WRONLY|os.O_TRUNC, 0600), "wb") as stored_state:
2333
pickle.dump((clients, client_settings), stored_state)
2334
except IOError as e:
2335
logger.warning("Could not save persistant state: {0}".format(e))
2336
if e.errno != errno.ENOENT:
2339
# Delete all clients, and settings from config
2340
1983
while tcp_server.clients:
2341
name, client = tcp_server.clients.popitem()
1984
client = tcp_server.clients.pop()
2343
1986
client.remove_from_connection()
1987
client.disable_hook = None
2344
1988
# Don't signal anything except ClientRemoved
2345
1989
client.disable(quiet=True)
2347
1991
# Emit D-Bus signal
2348
mandos_dbus_service.ClientRemoved(client
1992
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2351
client_settings.clear()
2353
1995
atexit.register(cleanup)
2355
for client in tcp_server.clients.itervalues():
1997
for client in tcp_server.clients:
2357
1999
# Emit D-Bus signal
2358
2000
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2359
# Need to initiate checking of clients
2361
client.init_checker()
2364
2003
tcp_server.enable()
2365
2004
tcp_server.server_activate()