85
78
except ImportError:
86
79
SO_BINDTODEVICE = None
89
stored_state_file = "clients.pickle"
91
logger = logging.getLogger()
84
logger = logging.Logger(u'mandos')
92
85
syslogger = (logging.handlers.SysLogHandler
93
86
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
94
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)
130
class PGPError(Exception):
131
"""Exception if encryption/decryption fails"""
135
class PGPEngine(object):
136
"""A simple class for OpenPGP symmetric encryption & decryption"""
138
self.gnupg = GnuPGInterface.GnuPG()
139
self.tempdir = tempfile.mkdtemp(prefix="mandos-")
140
self.gnupg = GnuPGInterface.GnuPG()
141
self.gnupg.options.meta_interactive = False
142
self.gnupg.options.homedir = self.tempdir
143
self.gnupg.options.extra_args.extend(['--force-mdc',
149
def __exit__ (self, exc_type, exc_value, traceback):
157
if self.tempdir is not None:
158
# Delete contents of tempdir
159
for root, dirs, files in os.walk(self.tempdir,
161
for filename in files:
162
os.remove(os.path.join(root, filename))
164
os.rmdir(os.path.join(root, dirname))
166
os.rmdir(self.tempdir)
169
def password_encode(self, password):
170
# Passphrase can not be empty and can not contain newlines or
171
# NUL bytes. So we prefix it and hex encode it.
172
return b"mandos" + binascii.hexlify(password)
174
def encrypt(self, data, password):
175
self.gnupg.passphrase = self.password_encode(password)
176
with open(os.devnull) as devnull:
178
proc = self.gnupg.run(['--symmetric'],
179
create_fhs=['stdin', 'stdout'],
180
attach_fhs={'stderr': devnull})
181
with contextlib.closing(proc.handles['stdin']) as f:
183
with contextlib.closing(proc.handles['stdout']) as f:
184
ciphertext = f.read()
188
self.gnupg.passphrase = None
191
def decrypt(self, data, password):
192
self.gnupg.passphrase = self.password_encode(password)
193
with open(os.devnull) as devnull:
195
proc = self.gnupg.run(['--decrypt'],
196
create_fhs=['stdin', 'stdout'],
197
attach_fhs={'stderr': devnull})
198
with contextlib.closing(proc.handles['stdin'] ) as f:
200
with contextlib.closing(proc.handles['stdout']) as f:
201
decrypted_plaintext = f.read()
205
self.gnupg.passphrase = None
206
return decrypted_plaintext
87
address = "/dev/log"))
88
syslogger.setFormatter(logging.Formatter
89
(u'Mandos [%(process)d]: %(levelname)s:'
91
logger.addHandler(syslogger)
93
console = logging.StreamHandler()
94
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
97
logger.addHandler(console)
210
99
class AvahiError(Exception):
211
100
def __init__(self, value, *args, **kwargs):
309
191
self.group.Commit()
310
192
def entry_group_state_changed(self, state, error):
311
193
"""Derived from the Avahi example code"""
312
logger.debug("Avahi entry group state change: %i", state)
194
logger.debug(u"Avahi state change: %i", state)
314
196
if state == avahi.ENTRY_GROUP_ESTABLISHED:
315
logger.debug("Zeroconf service established.")
197
logger.debug(u"Zeroconf service established.")
316
198
elif state == avahi.ENTRY_GROUP_COLLISION:
317
logger.info("Zeroconf service name collision.")
199
logger.warning(u"Zeroconf service name collision.")
319
201
elif state == avahi.ENTRY_GROUP_FAILURE:
320
logger.critical("Avahi: Error in group state changed %s",
202
logger.critical(u"Avahi: Error in group state changed %s",
322
raise AvahiGroupError("State changed: %s"
204
raise AvahiGroupError(u"State changed: %s"
323
205
% unicode(error))
324
206
def cleanup(self):
325
207
"""Derived from the Avahi example code"""
326
208
if self.group is not None:
329
except (dbus.exceptions.UnknownMethodException,
330
dbus.exceptions.DBusException):
332
210
self.group = None
334
def server_state_changed(self, state, error=None):
211
def server_state_changed(self, state):
335
212
"""Derived from the Avahi example code"""
336
logger.debug("Avahi server state change: %i", state)
337
bad_states = { avahi.SERVER_INVALID:
338
"Zeroconf server invalid",
339
avahi.SERVER_REGISTERING: None,
340
avahi.SERVER_COLLISION:
341
"Zeroconf server name collision",
342
avahi.SERVER_FAILURE:
343
"Zeroconf server failure" }
344
if state in bad_states:
345
if bad_states[state] is not None:
347
logger.error(bad_states[state])
349
logger.error(bad_states[state] + ": %r", error)
213
if state == avahi.SERVER_COLLISION:
214
logger.error(u"Zeroconf server name collision")
351
216
elif state == avahi.SERVER_RUNNING:
355
logger.debug("Unknown state: %r", state)
357
logger.debug("Unknown state: %r: %r", state, error)
358
218
def activate(self):
359
219
"""Derived from the Avahi example code"""
360
220
if self.server is None:
361
221
self.server = dbus.Interface(
362
222
self.bus.get_object(avahi.DBUS_NAME,
363
avahi.DBUS_PATH_SERVER,
364
follow_name_owner_changes=True),
223
avahi.DBUS_PATH_SERVER),
365
224
avahi.DBUS_INTERFACE_SERVER)
366
self.server.connect_to_signal("StateChanged",
225
self.server.connect_to_signal(u"StateChanged",
367
226
self.server_state_changed)
368
227
self.server_state_changed(self.server.GetState())
370
class AvahiServiceToSyslog(AvahiService):
372
"""Add the new name to the syslog messages"""
373
ret = AvahiService.rename(self)
374
syslogger.setFormatter(logging.Formatter
375
('Mandos (%s) [%%(process)d]:'
376
' %%(levelname)s: %%(message)s'
380
def timedelta_to_milliseconds(td):
381
"Convert a datetime.timedelta() to milliseconds"
382
return ((td.days * 24 * 60 * 60 * 1000)
383
+ (td.seconds * 1000)
384
+ (td.microseconds // 1000))
386
230
class Client(object):
387
231
"""A representation of a client host served by this server.
390
approved: bool(); 'None' if not yet approved/disapproved
391
approval_delay: datetime.timedelta(); Time to wait for approval
392
approval_duration: datetime.timedelta(); Duration of one approval
234
name: string; from the config file, used in log messages and
236
fingerprint: string (40 or 32 hexadecimal digits); used to
237
uniquely identify the client
238
secret: bytestring; sent verbatim (over TLS) to client
239
host: string; available for use by the checker command
240
created: datetime.datetime(); (UTC) object creation
241
last_enabled: datetime.datetime(); (UTC)
243
last_checked_ok: datetime.datetime(); (UTC) or None
244
timeout: datetime.timedelta(); How long from last_checked_ok
245
until this client is invalid
246
interval: datetime.timedelta(); How often to start a new checker
247
disable_hook: If set, called by disable() as disable_hook(self)
393
248
checker: subprocess.Popen(); a running checker process used
394
249
to see if the client lives.
395
250
'None' if no process is running.
396
checker_callback_tag: a gobject event source tag, or None
397
checker_command: string; External command which is run to check
398
if client lives. %() expansions are done at
251
checker_initiator_tag: a gobject event source tag, or None
252
disable_initiator_tag: - '' -
253
checker_callback_tag: - '' -
254
checker_command: string; External command which is run to check if
255
client lives. %() expansions are done at
399
256
runtime with vars(self) as dict, so that for
400
257
instance %(name)s can be used in the command.
401
checker_initiator_tag: a gobject event source tag, or None
402
created: datetime.datetime(); (UTC) object creation
403
client_structure: Object describing what attributes a client has
404
and is used for storing the client at exit
405
258
current_checker_command: string; current running checker_command
406
disable_initiator_tag: a gobject event source tag, or None
408
fingerprint: string (40 or 32 hexadecimal digits); used to
409
uniquely identify the client
410
host: string; available for use by the checker command
411
interval: datetime.timedelta(); How often to start a new checker
412
last_approval_request: datetime.datetime(); (UTC) or None
413
last_checked_ok: datetime.datetime(); (UTC) or None
415
last_checker_status: integer between 0 and 255 reflecting exit
416
status of last checker. -1 reflects crashed
418
last_enabled: datetime.datetime(); (UTC) or None
419
name: string; from the config file, used in log messages and
421
secret: bytestring; sent verbatim (over TLS) to client
422
timeout: datetime.timedelta(); How long from last_checked_ok
423
until this client is disabled
424
extended_timeout: extra long timeout when password has been sent
425
runtime_expansions: Allowed attributes for runtime expansion.
426
expires: datetime.datetime(); time (UTC) when a client will be
430
runtime_expansions = ("approval_delay", "approval_duration",
431
"created", "enabled", "fingerprint",
432
"host", "interval", "last_checked_ok",
433
"last_enabled", "name", "timeout")
434
client_defaults = { "timeout": "5m",
435
"extended_timeout": "15m",
437
"checker": "fping -q -- %%(host)s",
439
"approval_delay": "0s",
440
"approval_duration": "1s",
441
"approved_by_default": "True",
262
def _timedelta_to_milliseconds(td):
263
"Convert a datetime.timedelta() to milliseconds"
264
return ((td.days * 24 * 60 * 60 * 1000)
265
+ (td.seconds * 1000)
266
+ (td.microseconds // 1000))
445
268
def timeout_milliseconds(self):
446
269
"Return the 'timeout' attribute in milliseconds"
447
return timedelta_to_milliseconds(self.timeout)
449
def extended_timeout_milliseconds(self):
450
"Return the 'extended_timeout' attribute in milliseconds"
451
return timedelta_to_milliseconds(self.extended_timeout)
270
return self._timedelta_to_milliseconds(self.timeout)
453
272
def interval_milliseconds(self):
454
273
"Return the 'interval' attribute in milliseconds"
455
return timedelta_to_milliseconds(self.interval)
274
return self._timedelta_to_milliseconds(self.interval)
457
def approval_delay_milliseconds(self):
458
return timedelta_to_milliseconds(self.approval_delay)
461
def config_parser(config):
462
""" Construct a new dict of client settings of this form:
463
{ client_name: {setting_name: value, ...}, ...}
464
with exceptions for any special settings as defined above"""
466
for client_name in config.sections():
467
section = dict(config.items(client_name))
468
client = settings[client_name] = {}
470
# Default copying each value from config to new dict
471
for setting, value in section.iteritems():
472
client[setting] = value
474
# Reformat values from string types to Python types
475
client["approved_by_default"] = config.getboolean(
476
client_name, "approved_by_default")
477
client["enabled"] = config.getboolean(client_name, "enabled")
479
client["fingerprint"] = (section["fingerprint"].upper()
481
if "secret" in section:
482
client["secret"] = section["secret"].decode("base64")
483
elif "secfile" in section:
484
with open(os.path.expanduser(os.path.expandvars
485
(section["secfile"])),
487
client["secret"] = secfile.read()
489
raise TypeError("No secret or secfile for section %s"
491
client["timeout"] = string_to_delta(section["timeout"])
492
client["extended_timeout"] = string_to_delta(
493
section["extended_timeout"])
494
client["interval"] = string_to_delta(section["interval"])
495
client["approval_delay"] = string_to_delta(
496
section["approval_delay"])
497
client["approval_duration"] = string_to_delta(
498
section["approval_duration"])
503
def __init__(self, config, name = None):
276
def __init__(self, name = None, disable_hook=None, config=None):
504
277
"""Note: the 'checker' key in 'config' sets the
505
278
'checker_command' attribute and *not* the 'checker'
508
logger.debug("Creating client %r", self.name)
283
logger.debug(u"Creating client %r", self.name)
509
284
# Uppercase and remove spaces from fingerprint for later
510
285
# comparison purposes with return value from the fingerprint()
512
self.fingerprint = config["fingerprint"]
513
logger.debug(" Fingerprint: %s", self.fingerprint)
514
self.secret = config["secret"]
515
self.host = config["host"]
287
self.fingerprint = (config[u"fingerprint"].upper()
289
logger.debug(u" Fingerprint: %s", self.fingerprint)
290
if u"secret" in config:
291
self.secret = config[u"secret"].decode(u"base64")
292
elif u"secfile" in config:
293
with closing(open(os.path.expanduser
295
(config[u"secfile"])),
297
self.secret = secfile.read()
299
raise TypeError(u"No secret or secfile for client %s"
301
self.host = config.get(u"host", u"")
516
302
self.created = datetime.datetime.utcnow()
517
self.enabled = config["enabled"]
518
self.last_approval_request = None
520
self.last_enabled = datetime.datetime.utcnow()
522
self.last_enabled = None
304
self.last_enabled = None
523
305
self.last_checked_ok = None
524
self.last_checker_status = None
525
self.timeout = config["timeout"]
526
self.extended_timeout = config["extended_timeout"]
527
self.interval = config["interval"]
306
self.timeout = string_to_delta(config[u"timeout"])
307
self.interval = string_to_delta(config[u"interval"])
308
self.disable_hook = disable_hook
528
309
self.checker = None
529
310
self.checker_initiator_tag = None
530
311
self.disable_initiator_tag = None
532
self.expires = datetime.datetime.utcnow() + self.timeout
535
312
self.checker_callback_tag = None
536
self.checker_command = config["checker"]
313
self.checker_command = config[u"checker"]
537
314
self.current_checker_command = None
539
self.approved_by_default = config["approved_by_default"]
540
self.approvals_pending = 0
541
self.approval_delay = config["approval_delay"]
542
self.approval_duration = config["approval_duration"]
543
self.changedstate = (multiprocessing_manager
544
.Condition(multiprocessing_manager
546
self.client_structure = [attr for attr in
547
self.__dict__.iterkeys()
548
if not attr.startswith("_")]
549
self.client_structure.append("client_structure")
551
for name, t in inspect.getmembers(type(self),
555
if not name.startswith("_"):
556
self.client_structure.append(name)
558
# Send notice to process children that client state has changed
559
def send_changedstate(self):
560
with self.changedstate:
561
self.changedstate.notify_all()
315
self.last_connect = None
563
317
def enable(self):
564
318
"""Start this client's checker and timeout hooks"""
565
if getattr(self, "enabled", False):
319
if getattr(self, u"enabled", False):
566
320
# Already enabled
568
self.send_changedstate()
569
self.expires = datetime.datetime.utcnow() + self.timeout
571
322
self.last_enabled = datetime.datetime.utcnow()
574
def disable(self, quiet=True):
575
"""Disable this client."""
576
if not getattr(self, "enabled", False):
579
self.send_changedstate()
581
logger.info("Disabling client %s", self.name)
582
if getattr(self, "disable_initiator_tag", False):
583
gobject.source_remove(self.disable_initiator_tag)
584
self.disable_initiator_tag = None
586
if getattr(self, "checker_initiator_tag", False):
587
gobject.source_remove(self.checker_initiator_tag)
588
self.checker_initiator_tag = None
591
# Do not run this again if called by a gobject.timeout_add
597
def init_checker(self):
598
323
# Schedule a new checker to be started an 'interval' from now,
599
324
# and every interval from then on.
600
325
self.checker_initiator_tag = (gobject.timeout_add
601
326
(self.interval_milliseconds(),
602
327
self.start_checker))
328
# Also start a new checker *right now*.
603
330
# Schedule a disable() when 'timeout' has passed
604
331
self.disable_initiator_tag = (gobject.timeout_add
605
332
(self.timeout_milliseconds(),
607
# Also start a new checker *right now*.
337
"""Disable this client."""
338
if not getattr(self, "enabled", False):
340
logger.info(u"Disabling client %s", self.name)
341
if getattr(self, u"disable_initiator_tag", False):
342
gobject.source_remove(self.disable_initiator_tag)
343
self.disable_initiator_tag = None
344
if getattr(self, u"checker_initiator_tag", False):
345
gobject.source_remove(self.checker_initiator_tag)
346
self.checker_initiator_tag = None
348
if self.disable_hook:
349
self.disable_hook(self)
351
# Do not run this again if called by a gobject.timeout_add
355
self.disable_hook = None
610
358
def checker_callback(self, pid, condition, command):
611
359
"""The checker has completed, so take appropriate actions."""
612
360
self.checker_callback_tag = None
613
361
self.checker = None
614
362
if os.WIFEXITED(condition):
615
self.last_checker_status = os.WEXITSTATUS(condition)
616
if self.last_checker_status == 0:
617
logger.info("Checker for %(name)s succeeded",
363
exitstatus = os.WEXITSTATUS(condition)
365
logger.info(u"Checker for %(name)s succeeded",
619
367
self.checked_ok()
621
logger.info("Checker for %(name)s failed",
369
logger.info(u"Checker for %(name)s failed",
624
self.last_checker_status = -1
625
logger.warning("Checker for %(name)s crashed?",
372
logger.warning(u"Checker for %(name)s crashed?",
628
def checked_ok(self, timeout=None):
375
def checked_ok(self):
629
376
"""Bump up the timeout for this client.
631
378
This should only be called when the client has been seen,
635
timeout = self.timeout
636
381
self.last_checked_ok = datetime.datetime.utcnow()
637
if self.disable_initiator_tag is not None:
638
gobject.source_remove(self.disable_initiator_tag)
639
if getattr(self, "enabled", False):
640
self.disable_initiator_tag = (gobject.timeout_add
641
(timedelta_to_milliseconds
642
(timeout), self.disable))
643
self.expires = datetime.datetime.utcnow() + timeout
645
def need_approval(self):
646
self.last_approval_request = datetime.datetime.utcnow()
382
gobject.source_remove(self.disable_initiator_tag)
383
self.disable_initiator_tag = (gobject.timeout_add
384
(self.timeout_milliseconds(),
648
387
def start_checker(self):
649
388
"""Start a new checker subprocess if one is not running.
800
542
def _is_dbus_property(obj):
801
return getattr(obj, "_dbus_is_property", False)
543
return getattr(obj, u"_dbus_is_property", False)
803
545
def _get_all_dbus_properties(self):
804
546
"""Returns a generator of (name, attribute) pairs
806
return ((prop.__get__(self)._dbus_name, prop.__get__(self))
807
for cls in self.__class__.__mro__
548
return ((prop._dbus_name, prop)
808
549
for name, prop in
809
inspect.getmembers(cls, self._is_dbus_property))
550
inspect.getmembers(self, self._is_dbus_property))
811
552
def _get_dbus_property(self, interface_name, property_name):
812
553
"""Returns a bound method if one exists which is a D-Bus
813
554
property with the specified name and interface.
815
for cls in self.__class__.__mro__:
816
for name, value in (inspect.getmembers
817
(cls, self._is_dbus_property)):
818
if (value._dbus_name == property_name
819
and value._dbus_interface == interface_name):
820
return value.__get__(self)
556
for name in (property_name,
557
property_name + u"_dbus_property"):
558
prop = getattr(self, name, None)
560
or not self._is_dbus_property(prop)
561
or prop._dbus_name != property_name
562
or (interface_name and prop._dbus_interface
563
and interface_name != prop._dbus_interface)):
822
566
# No such property
823
raise DBusPropertyNotFound(self.dbus_object_path + ":"
824
+ interface_name + "."
567
raise DBusPropertyNotFound(self.dbus_object_path + u":"
568
+ interface_name + u"."
827
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
571
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
829
573
def Get(self, interface_name, property_name):
830
574
"""Standard D-Bus property Get() method, see D-Bus standard.
832
576
prop = self._get_dbus_property(interface_name, property_name)
833
if prop._dbus_access == "write":
577
if prop._dbus_access == u"write":
834
578
raise DBusPropertyAccessException(property_name)
836
if not hasattr(value, "variant_level"):
580
if not hasattr(value, u"variant_level"):
838
582
return type(value)(value, variant_level=value.variant_level+1)
840
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
584
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
841
585
def Set(self, interface_name, property_name, value):
842
586
"""Standard D-Bus property Set() method, see D-Bus standard.
844
588
prop = self._get_dbus_property(interface_name, property_name)
845
if prop._dbus_access == "read":
589
if prop._dbus_access == u"read":
846
590
raise DBusPropertyAccessException(property_name)
847
if prop._dbus_get_args_options["byte_arrays"]:
848
# The byte_arrays option is not supported yet on
849
# signatures other than "ay".
850
if prop._dbus_signature != "ay":
591
if prop._dbus_get_args_options[u"byte_arrays"]:
852
592
value = dbus.ByteArray(''.join(unichr(byte)
853
593
for byte in value))
856
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
857
out_signature="a{sv}")
596
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
597
out_signature=u"a{sv}")
858
598
def GetAll(self, interface_name):
859
599
"""Standard D-Bus property GetAll() method, see D-Bus
862
602
Note: Will not include properties with access="write".
865
605
for name, prop in self._get_all_dbus_properties():
866
606
if (interface_name
867
607
and interface_name != prop._dbus_interface):
868
608
# Interface non-empty but did not match
870
610
# Ignore write-only properties
871
if prop._dbus_access == "write":
611
if prop._dbus_access == u"write":
874
if not hasattr(value, "variant_level"):
875
properties[name] = value
614
if not hasattr(value, u"variant_level"):
877
properties[name] = type(value)(value, variant_level=
878
value.variant_level+1)
879
return dbus.Dictionary(properties, signature="sv")
617
all[name] = type(value)(value, variant_level=
618
value.variant_level+1)
619
return dbus.Dictionary(all, signature=u"sv")
881
621
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
883
623
path_keyword='object_path',
884
624
connection_keyword='connection')
885
625
def Introspect(self, object_path, connection):
891
631
document = xml.dom.minidom.parseString(xmlstring)
892
632
def make_tag(document, name, prop):
893
e = document.createElement("property")
894
e.setAttribute("name", name)
895
e.setAttribute("type", prop._dbus_signature)
896
e.setAttribute("access", prop._dbus_access)
633
e = document.createElement(u"property")
634
e.setAttribute(u"name", name)
635
e.setAttribute(u"type", prop._dbus_signature)
636
e.setAttribute(u"access", prop._dbus_access)
898
for if_tag in document.getElementsByTagName("interface"):
638
for if_tag in document.getElementsByTagName(u"interface"):
899
639
for tag in (make_tag(document, name, prop)
901
641
in self._get_all_dbus_properties()
902
642
if prop._dbus_interface
903
== if_tag.getAttribute("name")):
643
== if_tag.getAttribute(u"name")):
904
644
if_tag.appendChild(tag)
905
645
# Add the names to the return values for the
906
646
# "org.freedesktop.DBus.Properties" methods
907
if (if_tag.getAttribute("name")
908
== "org.freedesktop.DBus.Properties"):
909
for cn in if_tag.getElementsByTagName("method"):
910
if cn.getAttribute("name") == "Get":
911
for arg in cn.getElementsByTagName("arg"):
912
if (arg.getAttribute("direction")
914
arg.setAttribute("name", "value")
915
elif cn.getAttribute("name") == "GetAll":
916
for arg in cn.getElementsByTagName("arg"):
917
if (arg.getAttribute("direction")
919
arg.setAttribute("name", "props")
920
xmlstring = document.toxml("utf-8")
647
if (if_tag.getAttribute(u"name")
648
== u"org.freedesktop.DBus.Properties"):
649
for cn in if_tag.getElementsByTagName(u"method"):
650
if cn.getAttribute(u"name") == u"Get":
651
for arg in cn.getElementsByTagName(u"arg"):
652
if (arg.getAttribute(u"direction")
654
arg.setAttribute(u"name", u"value")
655
elif cn.getAttribute(u"name") == u"GetAll":
656
for arg in cn.getElementsByTagName(u"arg"):
657
if (arg.getAttribute(u"direction")
659
arg.setAttribute(u"name", u"props")
660
xmlstring = document.toxml(u"utf-8")
921
661
document.unlink()
922
662
except (AttributeError, xml.dom.DOMException,
923
xml.parsers.expat.ExpatError) as error:
924
logger.error("Failed to override Introspection method",
663
xml.parsers.expat.ExpatError), error:
664
logger.error(u"Failed to override Introspection method",
929
def datetime_to_dbus (dt, variant_level=0):
930
"""Convert a UTC datetime.datetime() to a D-Bus type."""
932
return dbus.String("", variant_level = variant_level)
933
return dbus.String(dt.isoformat(),
934
variant_level=variant_level)
937
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
939
"""Applied to an empty subclass of a D-Bus object, this metaclass
940
will add additional D-Bus attributes matching a certain pattern.
942
def __new__(mcs, name, bases, attr):
943
# Go through all the base classes which could have D-Bus
944
# methods, signals, or properties in them
945
for base in (b for b in bases
946
if issubclass(b, dbus.service.Object)):
947
# Go though all attributes of the base class
948
for attrname, attribute in inspect.getmembers(base):
949
# Ignore non-D-Bus attributes, and D-Bus attributes
950
# with the wrong interface name
951
if (not hasattr(attribute, "_dbus_interface")
952
or not attribute._dbus_interface
953
.startswith("se.recompile.Mandos")):
955
# Create an alternate D-Bus interface name based on
957
alt_interface = (attribute._dbus_interface
958
.replace("se.recompile.Mandos",
959
"se.bsnet.fukt.Mandos"))
960
# Is this a D-Bus signal?
961
if getattr(attribute, "_dbus_is_signal", False):
962
# Extract the original non-method function by
964
nonmethod_func = (dict(
965
zip(attribute.func_code.co_freevars,
966
attribute.__closure__))["func"]
968
# Create a new, but exactly alike, function
969
# object, and decorate it to be a new D-Bus signal
970
# with the alternate D-Bus interface name
971
new_function = (dbus.service.signal
973
attribute._dbus_signature)
975
nonmethod_func.func_code,
976
nonmethod_func.func_globals,
977
nonmethod_func.func_name,
978
nonmethod_func.func_defaults,
979
nonmethod_func.func_closure)))
980
# Define a creator of a function to call both the
981
# old and new functions, so both the old and new
982
# signals gets sent when the function is called
983
def fixscope(func1, func2):
984
"""This function is a scope container to pass
985
func1 and func2 to the "call_both" function
986
outside of its arguments"""
987
def call_both(*args, **kwargs):
988
"""This function will emit two D-Bus
989
signals by calling func1 and func2"""
990
func1(*args, **kwargs)
991
func2(*args, **kwargs)
993
# Create the "call_both" function and add it to
995
attr[attrname] = fixscope(attribute,
997
# Is this a D-Bus method?
998
elif getattr(attribute, "_dbus_is_method", False):
999
# Create a new, but exactly alike, function
1000
# object. Decorate it to be a new D-Bus method
1001
# with the alternate D-Bus interface name. Add it
1003
attr[attrname] = (dbus.service.method
1005
attribute._dbus_in_signature,
1006
attribute._dbus_out_signature)
1008
(attribute.func_code,
1009
attribute.func_globals,
1010
attribute.func_name,
1011
attribute.func_defaults,
1012
attribute.func_closure)))
1013
# Is this a D-Bus property?
1014
elif getattr(attribute, "_dbus_is_property", False):
1015
# Create a new, but exactly alike, function
1016
# object, and decorate it to be a new D-Bus
1017
# property with the alternate D-Bus interface
1018
# name. Add it to the class.
1019
attr[attrname] = (dbus_service_property
1021
attribute._dbus_signature,
1022
attribute._dbus_access,
1024
._dbus_get_args_options
1027
(attribute.func_code,
1028
attribute.func_globals,
1029
attribute.func_name,
1030
attribute.func_defaults,
1031
attribute.func_closure)))
1032
return type.__new__(mcs, name, bases, attr)
1035
669
class ClientDBus(Client, DBusObjectWithProperties):
1036
670
"""A Client class using D-Bus
1039
673
dbus_object_path: dbus.ObjectPath
1040
674
bus: dbus.SystemBus()
1043
runtime_expansions = (Client.runtime_expansions
1044
+ ("dbus_object_path",))
1046
676
# dbus.service.Object doesn't use super(), so we can't either.
1048
678
def __init__(self, bus = None, *args, **kwargs):
1050
680
Client.__init__(self, *args, **kwargs)
1052
self._approvals_pending = 0
1053
681
# Only now, when this client is initialized, can it show up on
1055
client_object_name = unicode(self.name).translate(
1056
{ord("."): ord("_"),
1057
ord("-"): ord("_")})
1058
683
self.dbus_object_path = (dbus.ObjectPath
1059
("/clients/" + client_object_name))
685
+ self.name.replace(u".", u"_")))
1060
686
DBusObjectWithProperties.__init__(self, self.bus,
1061
687
self.dbus_object_path)
1063
def notifychangeproperty(transform_func,
1064
dbus_name, type_func=lambda x: x,
1066
""" Modify a variable so that it's a property which announces
1067
its changes to DBus.
1069
transform_fun: Function that takes a value and a variant_level
1070
and transforms it to a D-Bus type.
1071
dbus_name: D-Bus name of the variable
1072
type_func: Function that transform the value before sending it
1073
to the D-Bus. Default: no transform
1074
variant_level: D-Bus variant level. Default: 1
1076
attrname = "_{0}".format(dbus_name)
1077
def setter(self, value):
1078
if hasattr(self, "dbus_object_path"):
1079
if (not hasattr(self, attrname) or
1080
type_func(getattr(self, attrname, None))
1081
!= type_func(value)):
1082
dbus_value = transform_func(type_func(value),
1085
self.PropertyChanged(dbus.String(dbus_name),
1087
setattr(self, attrname, value)
1089
return property(lambda self: getattr(self, attrname), setter)
1092
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1093
approvals_pending = notifychangeproperty(dbus.Boolean,
1096
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1097
last_enabled = notifychangeproperty(datetime_to_dbus,
1099
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1100
type_func = lambda checker:
1101
checker is not None)
1102
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1104
last_approval_request = notifychangeproperty(
1105
datetime_to_dbus, "LastApprovalRequest")
1106
approved_by_default = notifychangeproperty(dbus.Boolean,
1107
"ApprovedByDefault")
1108
approval_delay = notifychangeproperty(dbus.UInt64,
1111
timedelta_to_milliseconds)
1112
approval_duration = notifychangeproperty(
1113
dbus.UInt64, "ApprovalDuration",
1114
type_func = timedelta_to_milliseconds)
1115
host = notifychangeproperty(dbus.String, "Host")
1116
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1118
timedelta_to_milliseconds)
1119
extended_timeout = notifychangeproperty(
1120
dbus.UInt64, "ExtendedTimeout",
1121
type_func = timedelta_to_milliseconds)
1122
interval = notifychangeproperty(dbus.UInt64,
1125
timedelta_to_milliseconds)
1126
checker_command = notifychangeproperty(dbus.String, "Checker")
1128
del notifychangeproperty
690
def _datetime_to_dbus(dt, variant_level=0):
691
"""Convert a UTC datetime.datetime() to a D-Bus type."""
692
return dbus.String(dt.isoformat(),
693
variant_level=variant_level)
696
oldstate = getattr(self, u"enabled", False)
697
r = Client.enable(self)
698
if oldstate != self.enabled:
700
self.PropertyChanged(dbus.String(u"enabled"),
701
dbus.Boolean(True, variant_level=1))
702
self.PropertyChanged(
703
dbus.String(u"last_enabled"),
704
self._datetime_to_dbus(self.last_enabled,
708
def disable(self, signal = True):
709
oldstate = getattr(self, u"enabled", False)
710
r = Client.disable(self)
711
if signal and oldstate != self.enabled:
713
self.PropertyChanged(dbus.String(u"enabled"),
714
dbus.Boolean(False, variant_level=1))
1130
717
def __del__(self, *args, **kwargs):
1132
719
self.remove_from_connection()
1133
720
except LookupError:
1135
if hasattr(DBusObjectWithProperties, "__del__"):
722
if hasattr(DBusObjectWithProperties, u"__del__"):
1136
723
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1137
724
Client.__del__(self, *args, **kwargs)
1268
841
def StopChecker(self):
1269
842
self.stop_checker()
1273
# ApprovalPending - property
1274
@dbus_service_property(_interface, signature="b", access="read")
1275
def ApprovalPending_dbus_property(self):
1276
return dbus.Boolean(bool(self.approvals_pending))
1278
# ApprovedByDefault - property
1279
@dbus_service_property(_interface, signature="b",
1281
def ApprovedByDefault_dbus_property(self, value=None):
1282
if value is None: # get
1283
return dbus.Boolean(self.approved_by_default)
1284
self.approved_by_default = bool(value)
1286
# ApprovalDelay - property
1287
@dbus_service_property(_interface, signature="t",
1289
def ApprovalDelay_dbus_property(self, value=None):
1290
if value is None: # get
1291
return dbus.UInt64(self.approval_delay_milliseconds())
1292
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1294
# ApprovalDuration - property
1295
@dbus_service_property(_interface, signature="t",
1297
def ApprovalDuration_dbus_property(self, value=None):
1298
if value is None: # get
1299
return dbus.UInt64(timedelta_to_milliseconds(
1300
self.approval_duration))
1301
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1304
@dbus_service_property(_interface, signature="s", access="read")
1305
def Name_dbus_property(self):
845
@dbus_service_property(_interface, signature=u"s", access=u"read")
846
def name_dbus_property(self):
1306
847
return dbus.String(self.name)
1308
# Fingerprint - property
1309
@dbus_service_property(_interface, signature="s", access="read")
1310
def Fingerprint_dbus_property(self):
849
# fingerprint - property
850
@dbus_service_property(_interface, signature=u"s", access=u"read")
851
def fingerprint_dbus_property(self):
1311
852
return dbus.String(self.fingerprint)
1314
@dbus_service_property(_interface, signature="s",
1316
def Host_dbus_property(self, value=None):
855
@dbus_service_property(_interface, signature=u"s",
857
def host_dbus_property(self, value=None):
1317
858
if value is None: # get
1318
859
return dbus.String(self.host)
1319
self.host = unicode(value)
1321
# Created - property
1322
@dbus_service_property(_interface, signature="s", access="read")
1323
def Created_dbus_property(self):
1324
return datetime_to_dbus(self.created)
1326
# LastEnabled - property
1327
@dbus_service_property(_interface, signature="s", access="read")
1328
def LastEnabled_dbus_property(self):
1329
return datetime_to_dbus(self.last_enabled)
1331
# Enabled - property
1332
@dbus_service_property(_interface, signature="b",
1334
def Enabled_dbus_property(self, value=None):
862
self.PropertyChanged(dbus.String(u"host"),
863
dbus.String(value, variant_level=1))
866
@dbus_service_property(_interface, signature=u"s", access=u"read")
867
def created_dbus_property(self):
868
return dbus.String(self._datetime_to_dbus(self.created))
870
# last_enabled - property
871
@dbus_service_property(_interface, signature=u"s", access=u"read")
872
def last_enabled_dbus_property(self):
873
if self.last_enabled is None:
874
return dbus.String(u"")
875
return dbus.String(self._datetime_to_dbus(self.last_enabled))
878
@dbus_service_property(_interface, signature=u"b",
880
def enabled_dbus_property(self, value=None):
1335
881
if value is None: # get
1336
882
return dbus.Boolean(self.enabled)
1342
# LastCheckedOK - property
1343
@dbus_service_property(_interface, signature="s",
1345
def LastCheckedOK_dbus_property(self, value=None):
888
# last_checked_ok - property
889
@dbus_service_property(_interface, signature=u"s",
891
def last_checked_ok_dbus_property(self, value=None):
1346
892
if value is not None:
1347
893
self.checked_ok()
1349
return datetime_to_dbus(self.last_checked_ok)
1351
# Expires - property
1352
@dbus_service_property(_interface, signature="s", access="read")
1353
def Expires_dbus_property(self):
1354
return datetime_to_dbus(self.expires)
1356
# LastApprovalRequest - property
1357
@dbus_service_property(_interface, signature="s", access="read")
1358
def LastApprovalRequest_dbus_property(self):
1359
return datetime_to_dbus(self.last_approval_request)
1361
# Timeout - property
1362
@dbus_service_property(_interface, signature="t",
1364
def Timeout_dbus_property(self, value=None):
895
if self.last_checked_ok is None:
896
return dbus.String(u"")
897
return dbus.String(self._datetime_to_dbus(self
901
@dbus_service_property(_interface, signature=u"t",
903
def timeout_dbus_property(self, value=None):
1365
904
if value is None: # get
1366
905
return dbus.UInt64(self.timeout_milliseconds())
1367
906
self.timeout = datetime.timedelta(0, 0, 0, value)
1368
if getattr(self, "disable_initiator_tag", None) is None:
908
self.PropertyChanged(dbus.String(u"timeout"),
909
dbus.UInt64(value, variant_level=1))
910
if getattr(self, u"disable_initiator_tag", None) is None:
1370
912
# Reschedule timeout
1371
913
gobject.source_remove(self.disable_initiator_tag)
1372
914
self.disable_initiator_tag = None
1374
time_to_die = timedelta_to_milliseconds((self
916
_timedelta_to_milliseconds((self
1379
921
if time_to_die <= 0:
1380
922
# The timeout has passed
1383
self.expires = (datetime.datetime.utcnow()
1384
+ datetime.timedelta(milliseconds =
1386
925
self.disable_initiator_tag = (gobject.timeout_add
1387
926
(time_to_die, self.disable))
1389
# ExtendedTimeout - property
1390
@dbus_service_property(_interface, signature="t",
1392
def ExtendedTimeout_dbus_property(self, value=None):
1393
if value is None: # get
1394
return dbus.UInt64(self.extended_timeout_milliseconds())
1395
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1397
# Interval - property
1398
@dbus_service_property(_interface, signature="t",
1400
def Interval_dbus_property(self, value=None):
928
# interval - property
929
@dbus_service_property(_interface, signature=u"t",
931
def interval_dbus_property(self, value=None):
1401
932
if value is None: # get
1402
933
return dbus.UInt64(self.interval_milliseconds())
1403
934
self.interval = datetime.timedelta(0, 0, 0, value)
1404
if getattr(self, "checker_initiator_tag", None) is None:
936
self.PropertyChanged(dbus.String(u"interval"),
937
dbus.UInt64(value, variant_level=1))
938
if getattr(self, u"checker_initiator_tag", None) is None:
1407
# Reschedule checker run
1408
gobject.source_remove(self.checker_initiator_tag)
1409
self.checker_initiator_tag = (gobject.timeout_add
1410
(value, self.start_checker))
1411
self.start_checker() # Start one now, too
1413
# Checker - property
1414
@dbus_service_property(_interface, signature="s",
1416
def Checker_dbus_property(self, value=None):
940
# Reschedule checker run
941
gobject.source_remove(self.checker_initiator_tag)
942
self.checker_initiator_tag = (gobject.timeout_add
943
(value, self.start_checker))
944
self.start_checker() # Start one now, too
947
@dbus_service_property(_interface, signature=u"s",
949
def checker_dbus_property(self, value=None):
1417
950
if value is None: # get
1418
951
return dbus.String(self.checker_command)
1419
self.checker_command = unicode(value)
952
self.checker_command = value
954
self.PropertyChanged(dbus.String(u"checker"),
955
dbus.String(self.checker_command,
1421
# CheckerRunning - property
1422
@dbus_service_property(_interface, signature="b",
1424
def CheckerRunning_dbus_property(self, value=None):
958
# checker_running - property
959
@dbus_service_property(_interface, signature=u"b",
961
def checker_running_dbus_property(self, value=None):
1425
962
if value is None: # get
1426
963
return dbus.Boolean(self.checker is not None)
1480
987
Note: This will run in its own forked process."""
1482
989
def handle(self):
1483
with contextlib.closing(self.server.child_pipe) as child_pipe:
1484
logger.info("TCP connection from: %s",
1485
unicode(self.client_address))
1486
logger.debug("Pipe FD: %d",
1487
self.server.child_pipe.fileno())
990
logger.info(u"TCP connection from: %s",
991
unicode(self.client_address))
992
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
993
# Open IPC pipe to parent process
994
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
1489
995
session = (gnutls.connection
1490
996
.ClientSession(self.request,
1491
997
gnutls.connection
1492
998
.X509Credentials()))
1000
line = self.request.makefile().readline()
1001
logger.debug(u"Protocol version: %r", line)
1003
if int(line.strip().split()[0]) > 1:
1005
except (ValueError, IndexError, RuntimeError), error:
1006
logger.error(u"Unknown protocol version: %s", error)
1494
1009
# Note: gnutls.connection.X509Credentials is really a
1495
1010
# generic GnuTLS certificate credentials object so long as
1496
1011
# no X.509 keys are added to it. Therefore, we can use it
1497
1012
# here despite using OpenPGP certificates.
1499
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1500
# "+AES-256-CBC", "+SHA1",
1501
# "+COMP-NULL", "+CTYPE-OPENPGP",
1014
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1015
# u"+AES-256-CBC", u"+SHA1",
1016
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1503
1018
# Use a fallback default, since this MUST be set.
1504
1019
priority = self.server.gnutls_priority
1505
1020
if priority is None:
1021
priority = u"NORMAL"
1507
1022
(gnutls.library.functions
1508
1023
.gnutls_priority_set_direct(session._c_object,
1509
1024
priority, None))
1511
# Start communication using the Mandos protocol
1512
# Get protocol number
1513
line = self.request.makefile().readline()
1514
logger.debug("Protocol version: %r", line)
1516
if int(line.strip().split()[0]) > 1:
1518
except (ValueError, IndexError, RuntimeError) as error:
1519
logger.error("Unknown protocol version: %s", error)
1522
# Start GnuTLS connection
1524
1027
session.handshake()
1525
except gnutls.errors.GNUTLSError as error:
1526
logger.warning("Handshake failed: %s", error)
1028
except gnutls.errors.GNUTLSError, error:
1029
logger.warning(u"Handshake failed: %s", error)
1527
1030
# Do not run session.bye() here: the session is not
1528
1031
# established. Just abandon the request.
1530
logger.debug("Handshake succeeded")
1532
approval_required = False
1033
logger.debug(u"Handshake succeeded")
1535
fpr = self.fingerprint(self.peer_certificate
1538
gnutls.errors.GNUTLSError) as error:
1539
logger.warning("Bad certificate: %s", error)
1541
logger.debug("Fingerprint: %s", fpr)
1544
client = ProxyClient(child_pipe, fpr,
1545
self.client_address)
1549
if self.server.use_dbus:
1551
client.NewRequest(str(self.client_address))
1553
if client.approval_delay:
1554
delay = client.approval_delay
1555
client.approvals_pending += 1
1556
approval_required = True
1559
if not client.enabled:
1560
logger.info("Client %s is disabled",
1562
if self.server.use_dbus:
1564
client.Rejected("Disabled")
1567
if client.approved or not client.approval_delay:
1568
#We are approved or approval is disabled
1570
elif client.approved is None:
1571
logger.info("Client %s needs approval",
1573
if self.server.use_dbus:
1575
client.NeedApproval(
1576
client.approval_delay_milliseconds(),
1577
client.approved_by_default)
1579
logger.warning("Client %s was not approved",
1581
if self.server.use_dbus:
1583
client.Rejected("Denied")
1586
#wait until timeout or approved
1587
time = datetime.datetime.now()
1588
client.changedstate.acquire()
1589
(client.changedstate.wait
1590
(float(client.timedelta_to_milliseconds(delay)
1592
client.changedstate.release()
1593
time2 = datetime.datetime.now()
1594
if (time2 - time) >= delay:
1595
if not client.approved_by_default:
1596
logger.warning("Client %s timed out while"
1597
" waiting for approval",
1599
if self.server.use_dbus:
1601
client.Rejected("Approval timed out")
1606
delay -= time2 - time
1609
while sent_size < len(client.secret):
1611
sent = session.send(client.secret[sent_size:])
1612
except gnutls.errors.GNUTLSError as error:
1613
logger.warning("gnutls send failed")
1615
logger.debug("Sent: %d, remaining: %d",
1616
sent, len(client.secret)
1617
- (sent_size + sent))
1620
logger.info("Sending secret to %s", client.name)
1621
# bump the timeout using extended_timeout
1622
client.checked_ok(client.extended_timeout)
1623
if self.server.use_dbus:
1035
fpr = self.fingerprint(self.peer_certificate(session))
1036
except (TypeError, gnutls.errors.GNUTLSError), error:
1037
logger.warning(u"Bad certificate: %s", error)
1040
logger.debug(u"Fingerprint: %s", fpr)
1628
if approval_required:
1629
client.approvals_pending -= 1
1632
except gnutls.errors.GNUTLSError as error:
1633
logger.warning("GnuTLS bye failed")
1042
for c in self.server.clients:
1043
if c.fingerprint == fpr:
1047
ipc.write(u"NOTFOUND %s %s\n"
1048
% (fpr, unicode(self.client_address)))
1051
# Have to check if client.still_valid(), since it is
1052
# possible that the client timed out while establishing
1053
# the GnuTLS session.
1054
if not client.still_valid():
1055
ipc.write(u"INVALID %s\n" % client.name)
1058
ipc.write(u"SENDING %s\n" % client.name)
1060
while sent_size < len(client.secret):
1061
sent = session.send(client.secret[sent_size:])
1062
logger.debug(u"Sent: %d, remaining: %d",
1063
sent, len(client.secret)
1064
- (sent_size + sent))
1636
1069
def peer_certificate(session):
1851
1256
for cond, name in
1852
1257
condition_names.iteritems()
1853
1258
if cond & condition)
1854
# error, or the other end of multiprocessing.Pipe has closed
1855
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1856
# Wait for other process to exit
1860
# Read a request from the child
1861
request = parent_pipe.recv()
1862
command = request[0]
1864
if command == 'init':
1866
address = request[2]
1868
for c in self.clients.itervalues():
1869
if c.fingerprint == fpr:
1873
logger.info("Client not found for fingerprint: %s, ad"
1874
"dress: %s", fpr, address)
1877
mandos_dbus_service.ClientNotFound(fpr,
1879
parent_pipe.send(False)
1882
gobject.io_add_watch(parent_pipe.fileno(),
1883
gobject.IO_IN | gobject.IO_HUP,
1884
functools.partial(self.handle_ipc,
1890
parent_pipe.send(True)
1891
# remove the old hook in favor of the new above hook on
1894
if command == 'funcall':
1895
funcname = request[1]
1899
parent_pipe.send(('data', getattr(client_object,
1903
if command == 'getattr':
1904
attrname = request[1]
1905
if callable(client_object.__getattribute__(attrname)):
1906
parent_pipe.send(('function',))
1908
parent_pipe.send(('data', client_object
1909
.__getattribute__(attrname)))
1911
if command == 'setattr':
1912
attrname = request[1]
1914
setattr(client_object, attrname, value)
1259
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1262
# Turn the pipe file descriptor into a Python file object
1263
if source not in file_objects:
1264
file_objects[source] = os.fdopen(source, u"r", 1)
1266
# Read a line from the file object
1267
cmdline = file_objects[source].readline()
1268
if not cmdline: # Empty line means end of file
1269
# close the IPC pipe
1270
file_objects[source].close()
1271
del file_objects[source]
1273
# Stop calling this function
1276
logger.debug(u"IPC command: %r", cmdline)
1278
# Parse and act on command
1279
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1281
if cmd == u"NOTFOUND":
1282
logger.warning(u"Client not found for fingerprint: %s",
1286
mandos_dbus_service.ClientNotFound(args)
1287
elif cmd == u"INVALID":
1288
for client in self.clients:
1289
if client.name == args:
1290
logger.warning(u"Client %s is invalid", args)
1296
logger.error(u"Unknown client %s is invalid", args)
1297
elif cmd == u"SENDING":
1298
for client in self.clients:
1299
if client.name == args:
1300
logger.info(u"Sending secret to %s", client.name)
1307
logger.error(u"Sending secret to unknown client %s",
1310
logger.error(u"Unknown IPC command: %r", cmdline)
1312
# Keep calling this function
1919
1316
def string_to_delta(interval):
1920
1317
"""Parse a string and return a datetime.timedelta
1922
>>> string_to_delta('7d')
1319
>>> string_to_delta(u'7d')
1923
1320
datetime.timedelta(7)
1924
>>> string_to_delta('60s')
1321
>>> string_to_delta(u'60s')
1925
1322
datetime.timedelta(0, 60)
1926
>>> string_to_delta('60m')
1323
>>> string_to_delta(u'60m')
1927
1324
datetime.timedelta(0, 3600)
1928
>>> string_to_delta('24h')
1325
>>> string_to_delta(u'24h')
1929
1326
datetime.timedelta(1)
1930
>>> string_to_delta('1w')
1327
>>> string_to_delta(u'1w')
1931
1328
datetime.timedelta(7)
1932
>>> string_to_delta('5m 30s')
1329
>>> string_to_delta(u'5m 30s')
1933
1330
datetime.timedelta(0, 330)
1935
1332
timevalue = datetime.timedelta(0)
1985
1406
##################################################################
1986
1407
# Parsing of options, both command line and config file
1988
parser = argparse.ArgumentParser()
1989
parser.add_argument("-v", "--version", action="version",
1990
version = "%%(prog)s %s" % version,
1991
help="show version number and exit")
1992
parser.add_argument("-i", "--interface", metavar="IF",
1993
help="Bind to interface IF")
1994
parser.add_argument("-a", "--address",
1995
help="Address to listen for requests on")
1996
parser.add_argument("-p", "--port", type=int,
1997
help="Port number to receive requests on")
1998
parser.add_argument("--check", action="store_true",
1999
help="Run self-test")
2000
parser.add_argument("--debug", action="store_true",
2001
help="Debug mode; run in foreground and log"
2003
parser.add_argument("--debuglevel", metavar="LEVEL",
2004
help="Debug level for stdout output")
2005
parser.add_argument("--priority", help="GnuTLS"
2006
" priority string (see GnuTLS documentation)")
2007
parser.add_argument("--servicename",
2008
metavar="NAME", help="Zeroconf service name")
2009
parser.add_argument("--configdir",
2010
default="/etc/mandos", metavar="DIR",
2011
help="Directory to search for configuration"
2013
parser.add_argument("--no-dbus", action="store_false",
2014
dest="use_dbus", help="Do not provide D-Bus"
2015
" system bus interface")
2016
parser.add_argument("--no-ipv6", action="store_false",
2017
dest="use_ipv6", help="Do not use IPv6")
2018
parser.add_argument("--no-restore", action="store_false",
2019
dest="restore", help="Do not restore stored"
2021
parser.add_argument("--statedir", metavar="DIR",
2022
help="Directory to save/restore state in")
2024
options = parser.parse_args()
1409
parser = optparse.OptionParser(version = "%%prog %s" % version)
1410
parser.add_option("-i", u"--interface", type=u"string",
1411
metavar="IF", help=u"Bind to interface IF")
1412
parser.add_option("-a", u"--address", type=u"string",
1413
help=u"Address to listen for requests on")
1414
parser.add_option("-p", u"--port", type=u"int",
1415
help=u"Port number to receive requests on")
1416
parser.add_option("--check", action=u"store_true",
1417
help=u"Run self-test")
1418
parser.add_option("--debug", action=u"store_true",
1419
help=u"Debug mode; run in foreground and log to"
1421
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1422
u" priority string (see GnuTLS documentation)")
1423
parser.add_option("--servicename", type=u"string",
1424
metavar=u"NAME", help=u"Zeroconf service name")
1425
parser.add_option("--configdir", type=u"string",
1426
default=u"/etc/mandos", metavar=u"DIR",
1427
help=u"Directory to search for configuration"
1429
parser.add_option("--no-dbus", action=u"store_false",
1430
dest=u"use_dbus", help=u"Do not provide D-Bus"
1431
u" system bus interface")
1432
parser.add_option("--no-ipv6", action=u"store_false",
1433
dest=u"use_ipv6", help=u"Do not use IPv6")
1434
options = parser.parse_args()[0]
2026
1436
if options.check:
2078
1484
##################################################################
2080
1486
# For convenience
2081
debug = server_settings["debug"]
2082
debuglevel = server_settings["debuglevel"]
2083
use_dbus = server_settings["use_dbus"]
2084
use_ipv6 = server_settings["use_ipv6"]
2085
stored_state_path = os.path.join(server_settings["statedir"],
2089
initlogger(logging.DEBUG)
2094
level = getattr(logging, debuglevel.upper())
2097
if server_settings["servicename"] != "Mandos":
1487
debug = server_settings[u"debug"]
1488
use_dbus = server_settings[u"use_dbus"]
1489
use_ipv6 = server_settings[u"use_ipv6"]
1492
syslogger.setLevel(logging.WARNING)
1493
console.setLevel(logging.WARNING)
1495
if server_settings[u"servicename"] != u"Mandos":
2098
1496
syslogger.setFormatter(logging.Formatter
2099
('Mandos (%s) [%%(process)d]:'
2100
' %%(levelname)s: %%(message)s'
2101
% server_settings["servicename"]))
1497
(u'Mandos (%s) [%%(process)d]:'
1498
u' %%(levelname)s: %%(message)s'
1499
% server_settings[u"servicename"]))
2103
1501
# Parse config file with clients
2104
client_config = configparser.SafeConfigParser(Client.client_defaults)
2105
client_config.read(os.path.join(server_settings["configdir"],
1502
client_defaults = { u"timeout": u"1h",
1504
u"checker": u"fping -q -- %%(host)s",
1507
client_config = configparser.SafeConfigParser(client_defaults)
1508
client_config.read(os.path.join(server_settings[u"configdir"],
2108
1511
global mandos_dbus_service
2109
1512
mandos_dbus_service = None
2111
tcp_server = MandosServer((server_settings["address"],
2112
server_settings["port"]),
1514
tcp_server = MandosServer((server_settings[u"address"],
1515
server_settings[u"port"]),
2114
interface=(server_settings["interface"]
1517
interface=server_settings[u"interface"],
2116
1518
use_ipv6=use_ipv6,
2117
1519
gnutls_priority=
2118
server_settings["priority"],
1520
server_settings[u"priority"],
2119
1521
use_dbus=use_dbus)
2121
pidfilename = "/var/run/mandos.pid"
2123
pidfile = open(pidfilename, "w")
2125
logger.error("Could not open file %r", pidfilename)
1522
pidfilename = u"/var/run/mandos.pid"
1524
pidfile = open(pidfilename, u"w")
1526
logger.error(u"Could not open file %r", pidfilename)
2128
uid = pwd.getpwnam("_mandos").pw_uid
2129
gid = pwd.getpwnam("_mandos").pw_gid
1529
uid = pwd.getpwnam(u"_mandos").pw_uid
1530
gid = pwd.getpwnam(u"_mandos").pw_gid
2130
1531
except KeyError:
2132
uid = pwd.getpwnam("mandos").pw_uid
2133
gid = pwd.getpwnam("mandos").pw_gid
1533
uid = pwd.getpwnam(u"mandos").pw_uid
1534
gid = pwd.getpwnam(u"mandos").pw_gid
2134
1535
except KeyError:
2136
uid = pwd.getpwnam("nobody").pw_uid
2137
gid = pwd.getpwnam("nobody").pw_gid
1537
uid = pwd.getpwnam(u"nobody").pw_uid
1538
gid = pwd.getpwnam(u"nobody").pw_gid
2138
1539
except KeyError:
2144
except OSError as error:
1545
except OSError, error:
2145
1546
if error[0] != errno.EPERM:
1549
# Enable all possible GnuTLS debugging
2149
# Enable all possible GnuTLS debugging
2151
1551
# "Use a log level over 10 to enable all debugging options."
2152
1552
# - GnuTLS manual
2153
1553
gnutls.library.functions.gnutls_global_set_log_level(11)
2155
1555
@gnutls.library.types.gnutls_log_func
2156
1556
def debug_gnutls(level, string):
2157
logger.debug("GnuTLS: %s", string[:-1])
1557
logger.debug(u"GnuTLS: %s", string[:-1])
2159
1559
(gnutls.library.functions
2160
1560
.gnutls_global_set_log_function(debug_gnutls))
2162
# Redirect stdin so all checkers get /dev/null
2163
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2164
os.dup2(null, sys.stdin.fileno())
2168
# No console logging
2169
logger.removeHandler(console)
2171
# Need to fork before connecting to D-Bus
2173
# Close all input and output, do double fork, etc.
2176
1562
global main_loop
2177
1563
# From the Avahi example code
2180
1566
bus = dbus.SystemBus()
2181
1567
# End of Avahi example code
2184
bus_name = dbus.service.BusName("se.recompile.Mandos",
2185
bus, do_not_queue=True)
2186
old_bus_name = (dbus.service.BusName
2187
("se.bsnet.fukt.Mandos", bus,
2189
except dbus.exceptions.NameExistsException as e:
2190
logger.error(unicode(e) + ", disabling D-Bus")
2192
server_settings["use_dbus"] = False
2193
tcp_server.use_dbus = False
1569
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
2194
1570
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2195
service = AvahiServiceToSyslog(name =
2196
server_settings["servicename"],
2197
servicetype = "_mandos._tcp",
2198
protocol = protocol, bus = bus)
1571
service = AvahiService(name = server_settings[u"servicename"],
1572
servicetype = u"_mandos._tcp",
1573
protocol = protocol, bus = bus)
2199
1574
if server_settings["interface"]:
2200
1575
service.interface = (if_nametoindex
2201
(str(server_settings["interface"])))
2203
global multiprocessing_manager
2204
multiprocessing_manager = multiprocessing.Manager()
1576
(str(server_settings[u"interface"])))
2206
1578
client_class = Client
2208
client_class = functools.partial(ClientDBusTransitional,
2211
client_settings = Client.config_parser(client_config)
2212
old_client_settings = {}
2215
# Get client data and settings from last running state.
2216
if server_settings["restore"]:
2218
with open(stored_state_path, "rb") as stored_state:
2219
clients_data, old_client_settings = (pickle.load
2221
os.remove(stored_state_path)
2222
except IOError as e:
2223
logger.warning("Could not load persistent state: {0}"
2225
if e.errno != errno.ENOENT:
2228
with PGPEngine() as pgp:
2229
for client in clients_data:
2230
client_name = client["name"]
2232
# Decide which value to use after restoring saved state.
2233
# We have three different values: Old config file,
2234
# new config file, and saved state.
2235
# New config value takes precedence if it differs from old
2236
# config value, otherwise use saved state.
2237
for name, value in client_settings[client_name].items():
2239
# For each value in new config, check if it
2240
# differs from the old config value (Except for
2241
# the "secret" attribute)
2242
if (name != "secret" and
2243
value != old_client_settings[client_name]
2245
client[name] = value
2249
# Clients who has passed its expire date can still be
2250
# enabled if its last checker was successful. Clients
2251
# whose checker failed before we stored its state is
2252
# assumed to have failed all checkers during downtime.
2253
if client["enabled"]:
2254
if datetime.datetime.utcnow() >= client["expires"]:
2255
if not client["last_checked_ok"]:
2257
"disabling client {0} - Client never "
2258
"performed a successfull checker"
2259
.format(client["name"]))
2260
client["enabled"] = False
2261
elif client["last_checker_status"] != 0:
2263
"disabling client {0} - Client "
2264
"last checker failed with error code {1}"
2265
.format(client["name"],
2266
client["last_checker_status"]))
2267
client["enabled"] = False
2269
client["expires"] = (datetime.datetime
2271
+ client["timeout"])
2273
client["changedstate"] = (multiprocessing_manager
2275
(multiprocessing_manager
2277
client["checker"] = None
2279
new_client = (ClientDBusTransitional.__new__
2280
(ClientDBusTransitional))
2281
tcp_server.clients[client_name] = new_client
2282
new_client.bus = bus
2283
for name, value in client.iteritems():
2284
setattr(new_client, name, value)
2285
client_object_name = unicode(client_name).translate(
2286
{ord("."): ord("_"),
2287
ord("-"): ord("_")})
2288
new_client.dbus_object_path = (dbus.ObjectPath
2290
+ client_object_name))
2291
DBusObjectWithProperties.__init__(new_client,
2296
tcp_server.clients[client_name] = (Client.__new__
2298
for name, value in client.iteritems():
2299
setattr(tcp_server.clients[client_name],
2303
tcp_server.clients[client_name].secret = (
2304
pgp.decrypt(tcp_server.clients[client_name]
2306
client_settings[client_name]
2309
# If decryption fails, we use secret from new settings
2310
logger.debug("Failed to decrypt {0} old secret"
2311
.format(client_name))
2312
tcp_server.clients[client_name].secret = (
2313
client_settings[client_name]["secret"])
2315
# Create/remove clients based on new changes made to config
2316
for clientname in set(old_client_settings) - set(client_settings):
2317
del tcp_server.clients[clientname]
2318
for clientname in set(client_settings) - set(old_client_settings):
2319
tcp_server.clients[clientname] = (client_class(name = clientname,
1580
client_class = functools.partial(ClientDBus, bus = bus)
1581
tcp_server.clients.update(set(
1582
client_class(name = section,
1583
config= dict(client_config.items(section)))
1584
for section in client_config.sections()))
2324
1585
if not tcp_server.clients:
2325
logger.warning("No clients defined")
1586
logger.warning(u"No clients defined")
1589
# Redirect stdin so all checkers get /dev/null
1590
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1591
os.dup2(null, sys.stdin.fileno())
1595
# No console logging
1596
logger.removeHandler(console)
1597
# Close all input and output, do double fork, etc.
1601
with closing(pidfile):
1603
pidfile.write(str(pid) + "\n")
1606
logger.error(u"Could not write to file %r with PID %d",
1609
# "pidfile" was never created
1614
"Cleanup function; run on exit"
1617
while tcp_server.clients:
1618
client = tcp_server.clients.pop()
1619
client.disable_hook = None
1622
atexit.register(cleanup)
2331
pidfile.write(str(pid) + "\n".encode("utf-8"))
2334
logger.error("Could not write to file %r with PID %d",
2337
# "pidfile" was never created
2340
1625
signal.signal(signal.SIGINT, signal.SIG_IGN)
2342
1626
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2343
1627
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2346
1630
class MandosDBusService(dbus.service.Object):
2347
1631
"""A D-Bus proxy object"""
2348
1632
def __init__(self):
2349
dbus.service.Object.__init__(self, bus, "/")
2350
_interface = "se.recompile.Mandos"
2352
@dbus.service.signal(_interface, signature="o")
2353
def ClientAdded(self, objpath):
2357
@dbus.service.signal(_interface, signature="ss")
2358
def ClientNotFound(self, fingerprint, address):
2362
@dbus.service.signal(_interface, signature="os")
1633
dbus.service.Object.__init__(self, bus, u"/")
1634
_interface = u"se.bsnet.fukt.Mandos"
1636
@dbus.service.signal(_interface, signature=u"oa{sv}")
1637
def ClientAdded(self, objpath, properties):
1641
@dbus.service.signal(_interface, signature=u"s")
1642
def ClientNotFound(self, fingerprint):
1646
@dbus.service.signal(_interface, signature=u"os")
2363
1647
def ClientRemoved(self, objpath, name):
2367
@dbus.service.method(_interface, out_signature="ao")
1651
@dbus.service.method(_interface, out_signature=u"ao")
2368
1652
def GetAllClients(self):
2370
1654
return dbus.Array(c.dbus_object_path
2372
tcp_server.clients.itervalues())
1655
for c in tcp_server.clients)
2374
1657
@dbus.service.method(_interface,
2375
out_signature="a{oa{sv}}")
1658
out_signature=u"a{oa{sv}}")
2376
1659
def GetAllClientsWithProperties(self):
2378
1661
return dbus.Dictionary(
2379
((c.dbus_object_path, c.GetAll(""))
2380
for c in tcp_server.clients.itervalues()),
1662
((c.dbus_object_path, c.GetAll(u""))
1663
for c in tcp_server.clients),
1664
signature=u"oa{sv}")
2383
@dbus.service.method(_interface, in_signature="o")
1666
@dbus.service.method(_interface, in_signature=u"o")
2384
1667
def RemoveClient(self, object_path):
2386
for c in tcp_server.clients.itervalues():
1669
for c in tcp_server.clients:
2387
1670
if c.dbus_object_path == object_path:
2388
del tcp_server.clients[c.name]
1671
tcp_server.clients.remove(c)
2389
1672
c.remove_from_connection()
2390
1673
# Don't signal anything except ClientRemoved
2391
c.disable(quiet=True)
1674
c.disable(signal=False)
2392
1675
# Emit D-Bus signal
2393
1676
self.ClientRemoved(object_path, c.name)
2395
raise KeyError(object_path)
2399
class MandosDBusServiceTransitional(MandosDBusService):
2400
__metaclass__ = AlternateDBusNamesMetaclass
2401
mandos_dbus_service = MandosDBusServiceTransitional()
2404
"Cleanup function; run on exit"
2407
multiprocessing.active_children()
2408
if not (tcp_server.clients or client_settings):
2411
# Store client before exiting. Secrets are encrypted with key
2412
# based on what config file has. If config file is
2413
# removed/edited, old secret will thus be unrecovable.
2415
with PGPEngine() as pgp:
2416
for client in tcp_server.clients.itervalues():
2417
key = client_settings[client.name]["secret"]
2418
client.encrypted_secret = pgp.encrypt(client.secret,
2422
# A list of attributes that will not be stored when
2424
exclude = set(("bus", "changedstate", "secret",
2426
for name, typ in (inspect.getmembers
2427
(dbus.service.Object)):
2430
client_dict["encrypted_secret"] = (client
2432
for attr in client.client_structure:
2433
if attr not in exclude:
2434
client_dict[attr] = getattr(client, attr)
2436
clients.append(client_dict)
2437
del client_settings[client.name]["secret"]
2440
with os.fdopen(os.open(stored_state_path,
2441
os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
2442
0600), "wb") as stored_state:
2443
pickle.dump((clients, client_settings), stored_state)
2444
except (IOError, OSError) as e:
2445
logger.warning("Could not save persistent state: {0}"
2447
if e.errno not in (errno.ENOENT, errno.EACCES):
2450
# Delete all clients, and settings from config
2451
while tcp_server.clients:
2452
name, client = tcp_server.clients.popitem()
2454
client.remove_from_connection()
2455
# Don't signal anything except ClientRemoved
2456
client.disable(quiet=True)
2459
mandos_dbus_service.ClientRemoved(client
2462
client_settings.clear()
2464
atexit.register(cleanup)
2466
for client in tcp_server.clients.itervalues():
1682
mandos_dbus_service = MandosDBusService()
1684
for client in tcp_server.clients:
2468
1686
# Emit D-Bus signal
2469
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2470
# Need to initiate checking of clients
2472
client.init_checker()
1687
mandos_dbus_service.ClientAdded(client.dbus_object_path,
2474
1691
tcp_server.enable()
2475
1692
tcp_server.server_activate()