85
80
except ImportError:
86
81
SO_BINDTODEVICE = None
89
stored_state_file = "clients.pickle"
91
logger = logging.getLogger()
86
logger = logging.Logger(u'mandos')
92
87
syslogger = (logging.handlers.SysLogHandler
93
88
(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(debug, level=logging.WARNING):
114
"""init logger and add loglevel"""
116
syslogger.setFormatter(logging.Formatter
117
('Mandos [%(process)d]: %(levelname)s:'
119
logger.addHandler(syslogger)
122
console = logging.StreamHandler()
123
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
127
logger.addHandler(console)
128
logger.setLevel(level)
131
class PGPError(Exception):
132
"""Exception if encryption/decryption fails"""
136
class PGPEngine(object):
137
"""A simple class for OpenPGP symmetric encryption & decryption"""
139
self.gnupg = GnuPGInterface.GnuPG()
140
self.tempdir = tempfile.mkdtemp(prefix="mandos-")
141
self.gnupg = GnuPGInterface.GnuPG()
142
self.gnupg.options.meta_interactive = False
143
self.gnupg.options.homedir = self.tempdir
144
self.gnupg.options.extra_args.extend(['--force-mdc',
151
def __exit__ (self, exc_type, exc_value, traceback):
159
if self.tempdir is not None:
160
# Delete contents of tempdir
161
for root, dirs, files in os.walk(self.tempdir,
163
for filename in files:
164
os.remove(os.path.join(root, filename))
166
os.rmdir(os.path.join(root, dirname))
168
os.rmdir(self.tempdir)
171
def password_encode(self, password):
172
# Passphrase can not be empty and can not contain newlines or
173
# NUL bytes. So we prefix it and hex encode it.
174
return b"mandos" + binascii.hexlify(password)
176
def encrypt(self, data, password):
177
self.gnupg.passphrase = self.password_encode(password)
178
with open(os.devnull) as devnull:
180
proc = self.gnupg.run(['--symmetric'],
181
create_fhs=['stdin', 'stdout'],
182
attach_fhs={'stderr': devnull})
183
with contextlib.closing(proc.handles['stdin']) as f:
185
with contextlib.closing(proc.handles['stdout']) as f:
186
ciphertext = f.read()
190
self.gnupg.passphrase = None
193
def decrypt(self, data, password):
194
self.gnupg.passphrase = self.password_encode(password)
195
with open(os.devnull) as devnull:
197
proc = self.gnupg.run(['--decrypt'],
198
create_fhs=['stdin', 'stdout'],
199
attach_fhs={'stderr': devnull})
200
with contextlib.closing(proc.handles['stdin'] ) as f:
202
with contextlib.closing(proc.handles['stdout']) as f:
203
decrypted_plaintext = f.read()
207
self.gnupg.passphrase = None
208
return decrypted_plaintext
89
address = "/dev/log"))
90
syslogger.setFormatter(logging.Formatter
91
(u'Mandos [%(process)d]: %(levelname)s:'
93
logger.addHandler(syslogger)
95
console = logging.StreamHandler()
96
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
99
logger.addHandler(console)
212
101
class AvahiError(Exception):
213
102
def __init__(self, value, *args, **kwargs):
311
193
self.group.Commit()
312
194
def entry_group_state_changed(self, state, error):
313
195
"""Derived from the Avahi example code"""
314
logger.debug("Avahi entry group state change: %i", state)
196
logger.debug(u"Avahi state change: %i", state)
316
198
if state == avahi.ENTRY_GROUP_ESTABLISHED:
317
logger.debug("Zeroconf service established.")
199
logger.debug(u"Zeroconf service established.")
318
200
elif state == avahi.ENTRY_GROUP_COLLISION:
319
logger.info("Zeroconf service name collision.")
201
logger.warning(u"Zeroconf service name collision.")
321
203
elif state == avahi.ENTRY_GROUP_FAILURE:
322
logger.critical("Avahi: Error in group state changed %s",
204
logger.critical(u"Avahi: Error in group state changed %s",
324
raise AvahiGroupError("State changed: %s"
206
raise AvahiGroupError(u"State changed: %s"
325
207
% unicode(error))
326
208
def cleanup(self):
327
209
"""Derived from the Avahi example code"""
328
210
if self.group is not None:
331
except (dbus.exceptions.UnknownMethodException,
332
dbus.exceptions.DBusException):
334
212
self.group = None
336
def server_state_changed(self, state, error=None):
213
def server_state_changed(self, state):
337
214
"""Derived from the Avahi example code"""
338
logger.debug("Avahi server state change: %i", state)
339
bad_states = { avahi.SERVER_INVALID:
340
"Zeroconf server invalid",
341
avahi.SERVER_REGISTERING: None,
342
avahi.SERVER_COLLISION:
343
"Zeroconf server name collision",
344
avahi.SERVER_FAILURE:
345
"Zeroconf server failure" }
346
if state in bad_states:
347
if bad_states[state] is not None:
349
logger.error(bad_states[state])
351
logger.error(bad_states[state] + ": %r", error)
215
if state == avahi.SERVER_COLLISION:
216
logger.error(u"Zeroconf server name collision")
353
218
elif state == avahi.SERVER_RUNNING:
357
logger.debug("Unknown state: %r", state)
359
logger.debug("Unknown state: %r: %r", state, error)
360
220
def activate(self):
361
221
"""Derived from the Avahi example code"""
362
222
if self.server is None:
363
223
self.server = dbus.Interface(
364
224
self.bus.get_object(avahi.DBUS_NAME,
365
avahi.DBUS_PATH_SERVER,
366
follow_name_owner_changes=True),
225
avahi.DBUS_PATH_SERVER),
367
226
avahi.DBUS_INTERFACE_SERVER)
368
self.server.connect_to_signal("StateChanged",
227
self.server.connect_to_signal(u"StateChanged",
369
228
self.server_state_changed)
370
229
self.server_state_changed(self.server.GetState())
372
class AvahiServiceToSyslog(AvahiService):
374
"""Add the new name to the syslog messages"""
375
ret = AvahiService.rename(self)
376
syslogger.setFormatter(logging.Formatter
377
('Mandos (%s) [%%(process)d]:'
378
' %%(levelname)s: %%(message)s'
382
def timedelta_to_milliseconds(td):
383
"Convert a datetime.timedelta() to milliseconds"
384
return ((td.days * 24 * 60 * 60 * 1000)
385
+ (td.seconds * 1000)
386
+ (td.microseconds // 1000))
388
232
class Client(object):
389
233
"""A representation of a client host served by this server.
392
approved: bool(); 'None' if not yet approved/disapproved
393
approval_delay: datetime.timedelta(); Time to wait for approval
394
approval_duration: datetime.timedelta(); Duration of one approval
236
name: string; from the config file, used in log messages and
238
fingerprint: string (40 or 32 hexadecimal digits); used to
239
uniquely identify the client
240
secret: bytestring; sent verbatim (over TLS) to client
241
host: string; available for use by the checker command
242
created: datetime.datetime(); (UTC) object creation
243
last_enabled: datetime.datetime(); (UTC)
245
last_checked_ok: datetime.datetime(); (UTC) or None
246
timeout: datetime.timedelta(); How long from last_checked_ok
247
until this client is disabled
248
interval: datetime.timedelta(); How often to start a new checker
249
disable_hook: If set, called by disable() as disable_hook(self)
395
250
checker: subprocess.Popen(); a running checker process used
396
251
to see if the client lives.
397
252
'None' if no process is running.
398
checker_callback_tag: a gobject event source tag, or None
399
checker_command: string; External command which is run to check
400
if client lives. %() expansions are done at
253
checker_initiator_tag: a gobject event source tag, or None
254
disable_initiator_tag: - '' -
255
checker_callback_tag: - '' -
256
checker_command: string; External command which is run to check if
257
client lives. %() expansions are done at
401
258
runtime with vars(self) as dict, so that for
402
259
instance %(name)s can be used in the command.
403
checker_initiator_tag: a gobject event source tag, or None
404
created: datetime.datetime(); (UTC) object creation
405
client_structure: Object describing what attributes a client has
406
and is used for storing the client at exit
407
260
current_checker_command: string; current running checker_command
408
disable_initiator_tag: a gobject event source tag, or None
410
fingerprint: string (40 or 32 hexadecimal digits); used to
411
uniquely identify the client
412
host: string; available for use by the checker command
413
interval: datetime.timedelta(); How often to start a new checker
414
last_approval_request: datetime.datetime(); (UTC) or None
415
last_checked_ok: datetime.datetime(); (UTC) or None
416
last_checker_status: integer between 0 and 255 reflecting exit
417
status of last checker. -1 reflects crashed
418
checker, -2 means no checker completed yet.
419
last_enabled: datetime.datetime(); (UTC) or None
420
name: string; from the config file, used in log messages and
422
secret: bytestring; sent verbatim (over TLS) to client
423
timeout: datetime.timedelta(); How long from last_checked_ok
424
until this client is disabled
425
extended_timeout: extra long timeout when secret has been sent
426
runtime_expansions: Allowed attributes for runtime expansion.
427
expires: datetime.datetime(); time (UTC) when a client will be
431
runtime_expansions = ("approval_delay", "approval_duration",
432
"created", "enabled", "fingerprint",
433
"host", "interval", "last_checked_ok",
434
"last_enabled", "name", "timeout")
435
client_defaults = { "timeout": "5m",
436
"extended_timeout": "15m",
438
"checker": "fping -q -- %%(host)s",
440
"approval_delay": "0s",
441
"approval_duration": "1s",
442
"approved_by_default": "True",
264
def _timedelta_to_milliseconds(td):
265
"Convert a datetime.timedelta() to milliseconds"
266
return ((td.days * 24 * 60 * 60 * 1000)
267
+ (td.seconds * 1000)
268
+ (td.microseconds // 1000))
446
270
def timeout_milliseconds(self):
447
271
"Return the 'timeout' attribute in milliseconds"
448
return timedelta_to_milliseconds(self.timeout)
450
def extended_timeout_milliseconds(self):
451
"Return the 'extended_timeout' attribute in milliseconds"
452
return timedelta_to_milliseconds(self.extended_timeout)
272
return self._timedelta_to_milliseconds(self.timeout)
454
274
def interval_milliseconds(self):
455
275
"Return the 'interval' attribute in milliseconds"
456
return timedelta_to_milliseconds(self.interval)
276
return self._timedelta_to_milliseconds(self.interval)
458
def approval_delay_milliseconds(self):
459
return timedelta_to_milliseconds(self.approval_delay)
462
def config_parser(config):
463
"""Construct a new dict of client settings of this form:
464
{ client_name: {setting_name: value, ...}, ...}
465
with exceptions for any special settings as defined above.
466
NOTE: Must be a pure function. Must return the same result
467
value given the same arguments.
470
for client_name in config.sections():
471
section = dict(config.items(client_name))
472
client = settings[client_name] = {}
474
client["host"] = section["host"]
475
# Reformat values from string types to Python types
476
client["approved_by_default"] = config.getboolean(
477
client_name, "approved_by_default")
478
client["enabled"] = config.getboolean(client_name,
481
client["fingerprint"] = (section["fingerprint"].upper()
483
if "secret" in section:
484
client["secret"] = section["secret"].decode("base64")
485
elif "secfile" in section:
486
with open(os.path.expanduser(os.path.expandvars
487
(section["secfile"])),
489
client["secret"] = secfile.read()
491
raise TypeError("No secret or secfile for section %s"
493
client["timeout"] = string_to_delta(section["timeout"])
494
client["extended_timeout"] = string_to_delta(
495
section["extended_timeout"])
496
client["interval"] = string_to_delta(section["interval"])
497
client["approval_delay"] = string_to_delta(
498
section["approval_delay"])
499
client["approval_duration"] = string_to_delta(
500
section["approval_duration"])
501
client["checker_command"] = section["checker"]
502
client["last_approval_request"] = None
503
client["last_checked_ok"] = None
504
client["last_checker_status"] = -2
509
def __init__(self, settings, name = None):
278
def __init__(self, name = None, disable_hook=None, config=None):
510
279
"""Note: the 'checker' key in 'config' sets the
511
280
'checker_command' attribute and *not* the 'checker'
514
# adding all client settings
515
for setting, value in settings.iteritems():
516
setattr(self, setting, value)
519
if not hasattr(self, "last_enabled"):
520
self.last_enabled = datetime.datetime.utcnow()
521
if not hasattr(self, "expires"):
522
self.expires = (datetime.datetime.utcnow()
525
self.last_enabled = None
528
logger.debug("Creating client %r", self.name)
285
logger.debug(u"Creating client %r", self.name)
529
286
# Uppercase and remove spaces from fingerprint for later
530
287
# comparison purposes with return value from the fingerprint()
532
logger.debug(" Fingerprint: %s", self.fingerprint)
533
self.created = settings.get("created",
534
datetime.datetime.utcnow())
536
# attributes specific for this server instance
289
self.fingerprint = (config[u"fingerprint"].upper()
291
logger.debug(u" Fingerprint: %s", self.fingerprint)
292
if u"secret" in config:
293
self.secret = config[u"secret"].decode(u"base64")
294
elif u"secfile" in config:
295
with open(os.path.expanduser(os.path.expandvars
296
(config[u"secfile"])),
298
self.secret = secfile.read()
300
raise TypeError(u"No secret or secfile for client %s"
302
self.host = config.get(u"host", u"")
303
self.created = datetime.datetime.utcnow()
305
self.last_enabled = None
306
self.last_checked_ok = None
307
self.timeout = string_to_delta(config[u"timeout"])
308
self.interval = string_to_delta(config[u"interval"])
309
self.disable_hook = disable_hook
537
310
self.checker = None
538
311
self.checker_initiator_tag = None
539
312
self.disable_initiator_tag = None
540
313
self.checker_callback_tag = None
314
self.checker_command = config[u"checker"]
541
315
self.current_checker_command = None
543
self.approvals_pending = 0
544
self.changedstate = (multiprocessing_manager
545
.Condition(multiprocessing_manager
547
self.client_structure = [attr for attr in
548
self.__dict__.iterkeys()
549
if not attr.startswith("_")]
550
self.client_structure.append("client_structure")
552
for name, t in inspect.getmembers(type(self),
556
if not name.startswith("_"):
557
self.client_structure.append(name)
559
# Send notice to process children that client state has changed
560
def send_changedstate(self):
561
with self.changedstate:
562
self.changedstate.notify_all()
316
self.last_connect = None
564
318
def enable(self):
565
319
"""Start this client's checker and timeout hooks"""
566
if getattr(self, "enabled", False):
320
if getattr(self, u"enabled", False):
567
321
# Already enabled
569
self.send_changedstate()
570
self.expires = datetime.datetime.utcnow() + self.timeout
572
323
self.last_enabled = datetime.datetime.utcnow()
575
def disable(self, quiet=True):
576
"""Disable this client."""
577
if not getattr(self, "enabled", False):
580
self.send_changedstate()
582
logger.info("Disabling client %s", self.name)
583
if getattr(self, "disable_initiator_tag", False):
584
gobject.source_remove(self.disable_initiator_tag)
585
self.disable_initiator_tag = None
587
if getattr(self, "checker_initiator_tag", False):
588
gobject.source_remove(self.checker_initiator_tag)
589
self.checker_initiator_tag = None
592
# Do not run this again if called by a gobject.timeout_add
598
def init_checker(self):
599
324
# Schedule a new checker to be started an 'interval' from now,
600
325
# and every interval from then on.
601
326
self.checker_initiator_tag = (gobject.timeout_add
605
330
self.disable_initiator_tag = (gobject.timeout_add
606
331
(self.timeout_milliseconds(),
608
334
# Also start a new checker *right now*.
609
335
self.start_checker()
337
def disable(self, quiet=True):
338
"""Disable this client."""
339
if not getattr(self, "enabled", False):
342
logger.info(u"Disabling client %s", self.name)
343
if getattr(self, u"disable_initiator_tag", False):
344
gobject.source_remove(self.disable_initiator_tag)
345
self.disable_initiator_tag = None
346
if getattr(self, u"checker_initiator_tag", False):
347
gobject.source_remove(self.checker_initiator_tag)
348
self.checker_initiator_tag = None
350
if self.disable_hook:
351
self.disable_hook(self)
353
# Do not run this again if called by a gobject.timeout_add
357
self.disable_hook = None
611
360
def checker_callback(self, pid, condition, command):
612
361
"""The checker has completed, so take appropriate actions."""
613
362
self.checker_callback_tag = None
614
363
self.checker = None
615
364
if os.WIFEXITED(condition):
616
self.last_checker_status = os.WEXITSTATUS(condition)
617
if self.last_checker_status == 0:
618
logger.info("Checker for %(name)s succeeded",
365
exitstatus = os.WEXITSTATUS(condition)
367
logger.info(u"Checker for %(name)s succeeded",
620
369
self.checked_ok()
622
logger.info("Checker for %(name)s failed",
371
logger.info(u"Checker for %(name)s failed",
625
self.last_checker_status = -1
626
logger.warning("Checker for %(name)s crashed?",
374
logger.warning(u"Checker for %(name)s crashed?",
629
377
def checked_ok(self):
630
"""Assert that the client has been seen, alive and well."""
378
"""Bump up the timeout for this client.
380
This should only be called when the client has been seen,
631
383
self.last_checked_ok = datetime.datetime.utcnow()
632
self.last_checker_status = 0
635
def bump_timeout(self, timeout=None):
636
"""Bump up the timeout for this client."""
638
timeout = self.timeout
639
if self.disable_initiator_tag is not None:
640
gobject.source_remove(self.disable_initiator_tag)
641
if getattr(self, "enabled", False):
642
self.disable_initiator_tag = (gobject.timeout_add
643
(timedelta_to_milliseconds
644
(timeout), self.disable))
645
self.expires = datetime.datetime.utcnow() + timeout
647
def need_approval(self):
648
self.last_approval_request = datetime.datetime.utcnow()
384
gobject.source_remove(self.disable_initiator_tag)
385
self.disable_initiator_tag = (gobject.timeout_add
386
(self.timeout_milliseconds(),
650
389
def start_checker(self):
651
390
"""Start a new checker subprocess if one is not running.
802
539
def _is_dbus_property(obj):
803
return getattr(obj, "_dbus_is_property", False)
540
return getattr(obj, u"_dbus_is_property", False)
805
542
def _get_all_dbus_properties(self):
806
543
"""Returns a generator of (name, attribute) pairs
808
return ((prop.__get__(self)._dbus_name, prop.__get__(self))
809
for cls in self.__class__.__mro__
545
return ((prop._dbus_name, prop)
810
546
for name, prop in
811
inspect.getmembers(cls, self._is_dbus_property))
547
inspect.getmembers(self, self._is_dbus_property))
813
549
def _get_dbus_property(self, interface_name, property_name):
814
550
"""Returns a bound method if one exists which is a D-Bus
815
551
property with the specified name and interface.
817
for cls in self.__class__.__mro__:
818
for name, value in (inspect.getmembers
819
(cls, self._is_dbus_property)):
820
if (value._dbus_name == property_name
821
and value._dbus_interface == interface_name):
822
return value.__get__(self)
553
for name in (property_name,
554
property_name + u"_dbus_property"):
555
prop = getattr(self, name, None)
557
or not self._is_dbus_property(prop)
558
or prop._dbus_name != property_name
559
or (interface_name and prop._dbus_interface
560
and interface_name != prop._dbus_interface)):
824
563
# No such property
825
raise DBusPropertyNotFound(self.dbus_object_path + ":"
826
+ interface_name + "."
564
raise DBusPropertyNotFound(self.dbus_object_path + u":"
565
+ interface_name + u"."
829
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
568
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
831
570
def Get(self, interface_name, property_name):
832
571
"""Standard D-Bus property Get() method, see D-Bus standard.
834
573
prop = self._get_dbus_property(interface_name, property_name)
835
if prop._dbus_access == "write":
574
if prop._dbus_access == u"write":
836
575
raise DBusPropertyAccessException(property_name)
838
if not hasattr(value, "variant_level"):
577
if not hasattr(value, u"variant_level"):
840
579
return type(value)(value, variant_level=value.variant_level+1)
842
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
581
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
843
582
def Set(self, interface_name, property_name, value):
844
583
"""Standard D-Bus property Set() method, see D-Bus standard.
846
585
prop = self._get_dbus_property(interface_name, property_name)
847
if prop._dbus_access == "read":
586
if prop._dbus_access == u"read":
848
587
raise DBusPropertyAccessException(property_name)
849
if prop._dbus_get_args_options["byte_arrays"]:
588
if prop._dbus_get_args_options[u"byte_arrays"]:
850
589
# The byte_arrays option is not supported yet on
851
590
# signatures other than "ay".
852
if prop._dbus_signature != "ay":
591
if prop._dbus_signature != u"ay":
854
value = dbus.ByteArray(b''.join(chr(byte)
593
value = dbus.ByteArray(''.join(unichr(byte)
858
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
859
out_signature="a{sv}")
597
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
598
out_signature=u"a{sv}")
860
599
def GetAll(self, interface_name):
861
600
"""Standard D-Bus property GetAll() method, see D-Bus
864
603
Note: Will not include properties with access="write".
867
606
for name, prop in self._get_all_dbus_properties():
868
607
if (interface_name
869
608
and interface_name != prop._dbus_interface):
870
609
# Interface non-empty but did not match
872
611
# Ignore write-only properties
873
if prop._dbus_access == "write":
612
if prop._dbus_access == u"write":
876
if not hasattr(value, "variant_level"):
877
properties[name] = value
615
if not hasattr(value, u"variant_level"):
879
properties[name] = type(value)(value, variant_level=
880
value.variant_level+1)
881
return dbus.Dictionary(properties, signature="sv")
618
all[name] = type(value)(value, variant_level=
619
value.variant_level+1)
620
return dbus.Dictionary(all, signature=u"sv")
883
622
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
885
624
path_keyword='object_path',
886
625
connection_keyword='connection')
887
626
def Introspect(self, object_path, connection):
893
632
document = xml.dom.minidom.parseString(xmlstring)
894
633
def make_tag(document, name, prop):
895
e = document.createElement("property")
896
e.setAttribute("name", name)
897
e.setAttribute("type", prop._dbus_signature)
898
e.setAttribute("access", prop._dbus_access)
634
e = document.createElement(u"property")
635
e.setAttribute(u"name", name)
636
e.setAttribute(u"type", prop._dbus_signature)
637
e.setAttribute(u"access", prop._dbus_access)
900
for if_tag in document.getElementsByTagName("interface"):
639
for if_tag in document.getElementsByTagName(u"interface"):
901
640
for tag in (make_tag(document, name, prop)
903
642
in self._get_all_dbus_properties()
904
643
if prop._dbus_interface
905
== if_tag.getAttribute("name")):
644
== if_tag.getAttribute(u"name")):
906
645
if_tag.appendChild(tag)
907
646
# Add the names to the return values for the
908
647
# "org.freedesktop.DBus.Properties" methods
909
if (if_tag.getAttribute("name")
910
== "org.freedesktop.DBus.Properties"):
911
for cn in if_tag.getElementsByTagName("method"):
912
if cn.getAttribute("name") == "Get":
913
for arg in cn.getElementsByTagName("arg"):
914
if (arg.getAttribute("direction")
916
arg.setAttribute("name", "value")
917
elif cn.getAttribute("name") == "GetAll":
918
for arg in cn.getElementsByTagName("arg"):
919
if (arg.getAttribute("direction")
921
arg.setAttribute("name", "props")
922
xmlstring = document.toxml("utf-8")
648
if (if_tag.getAttribute(u"name")
649
== u"org.freedesktop.DBus.Properties"):
650
for cn in if_tag.getElementsByTagName(u"method"):
651
if cn.getAttribute(u"name") == u"Get":
652
for arg in cn.getElementsByTagName(u"arg"):
653
if (arg.getAttribute(u"direction")
655
arg.setAttribute(u"name", u"value")
656
elif cn.getAttribute(u"name") == u"GetAll":
657
for arg in cn.getElementsByTagName(u"arg"):
658
if (arg.getAttribute(u"direction")
660
arg.setAttribute(u"name", u"props")
661
xmlstring = document.toxml(u"utf-8")
923
662
document.unlink()
924
663
except (AttributeError, xml.dom.DOMException,
925
xml.parsers.expat.ExpatError) as error:
926
logger.error("Failed to override Introspection method",
664
xml.parsers.expat.ExpatError), error:
665
logger.error(u"Failed to override Introspection method",
931
def datetime_to_dbus (dt, variant_level=0):
932
"""Convert a UTC datetime.datetime() to a D-Bus type."""
934
return dbus.String("", variant_level = variant_level)
935
return dbus.String(dt.isoformat(),
936
variant_level=variant_level)
939
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
941
"""Applied to an empty subclass of a D-Bus object, this metaclass
942
will add additional D-Bus attributes matching a certain pattern.
944
def __new__(mcs, name, bases, attr):
945
# Go through all the base classes which could have D-Bus
946
# methods, signals, or properties in them
947
for base in (b for b in bases
948
if issubclass(b, dbus.service.Object)):
949
# Go though all attributes of the base class
950
for attrname, attribute in inspect.getmembers(base):
951
# Ignore non-D-Bus attributes, and D-Bus attributes
952
# with the wrong interface name
953
if (not hasattr(attribute, "_dbus_interface")
954
or not attribute._dbus_interface
955
.startswith("se.recompile.Mandos")):
957
# Create an alternate D-Bus interface name based on
959
alt_interface = (attribute._dbus_interface
960
.replace("se.recompile.Mandos",
961
"se.bsnet.fukt.Mandos"))
962
# Is this a D-Bus signal?
963
if getattr(attribute, "_dbus_is_signal", False):
964
# Extract the original non-method function by
966
nonmethod_func = (dict(
967
zip(attribute.func_code.co_freevars,
968
attribute.__closure__))["func"]
970
# Create a new, but exactly alike, function
971
# object, and decorate it to be a new D-Bus signal
972
# with the alternate D-Bus interface name
973
new_function = (dbus.service.signal
975
attribute._dbus_signature)
977
nonmethod_func.func_code,
978
nonmethod_func.func_globals,
979
nonmethod_func.func_name,
980
nonmethod_func.func_defaults,
981
nonmethod_func.func_closure)))
982
# Define a creator of a function to call both the
983
# old and new functions, so both the old and new
984
# signals gets sent when the function is called
985
def fixscope(func1, func2):
986
"""This function is a scope container to pass
987
func1 and func2 to the "call_both" function
988
outside of its arguments"""
989
def call_both(*args, **kwargs):
990
"""This function will emit two D-Bus
991
signals by calling func1 and func2"""
992
func1(*args, **kwargs)
993
func2(*args, **kwargs)
995
# Create the "call_both" function and add it to
997
attr[attrname] = fixscope(attribute,
999
# Is this a D-Bus method?
1000
elif getattr(attribute, "_dbus_is_method", False):
1001
# Create a new, but exactly alike, function
1002
# object. Decorate it to be a new D-Bus method
1003
# with the alternate D-Bus interface name. Add it
1005
attr[attrname] = (dbus.service.method
1007
attribute._dbus_in_signature,
1008
attribute._dbus_out_signature)
1010
(attribute.func_code,
1011
attribute.func_globals,
1012
attribute.func_name,
1013
attribute.func_defaults,
1014
attribute.func_closure)))
1015
# Is this a D-Bus property?
1016
elif getattr(attribute, "_dbus_is_property", False):
1017
# Create a new, but exactly alike, function
1018
# object, and decorate it to be a new D-Bus
1019
# property with the alternate D-Bus interface
1020
# name. Add it to the class.
1021
attr[attrname] = (dbus_service_property
1023
attribute._dbus_signature,
1024
attribute._dbus_access,
1026
._dbus_get_args_options
1029
(attribute.func_code,
1030
attribute.func_globals,
1031
attribute.func_name,
1032
attribute.func_defaults,
1033
attribute.func_closure)))
1034
return type.__new__(mcs, name, bases, attr)
1037
670
class ClientDBus(Client, DBusObjectWithProperties):
1038
671
"""A Client class using D-Bus
1052
681
Client.__init__(self, *args, **kwargs)
1053
682
# 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
684
self.dbus_object_path = (dbus.ObjectPath
1059
("/clients/" + client_object_name))
686
+ self.name.replace(u".", u"_")))
1060
687
DBusObjectWithProperties.__init__(self, self.bus,
1061
688
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_checker_status = notifychangeproperty(dbus.Int16,
1105
"LastCheckerStatus")
1106
last_approval_request = notifychangeproperty(
1107
datetime_to_dbus, "LastApprovalRequest")
1108
approved_by_default = notifychangeproperty(dbus.Boolean,
1109
"ApprovedByDefault")
1110
approval_delay = notifychangeproperty(dbus.UInt64,
1113
timedelta_to_milliseconds)
1114
approval_duration = notifychangeproperty(
1115
dbus.UInt64, "ApprovalDuration",
1116
type_func = timedelta_to_milliseconds)
1117
host = notifychangeproperty(dbus.String, "Host")
1118
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1120
timedelta_to_milliseconds)
1121
extended_timeout = notifychangeproperty(
1122
dbus.UInt64, "ExtendedTimeout",
1123
type_func = timedelta_to_milliseconds)
1124
interval = notifychangeproperty(dbus.UInt64,
1127
timedelta_to_milliseconds)
1128
checker_command = notifychangeproperty(dbus.String, "Checker")
1130
del notifychangeproperty
691
def _datetime_to_dbus(dt, variant_level=0):
692
"""Convert a UTC datetime.datetime() to a D-Bus type."""
693
return dbus.String(dt.isoformat(),
694
variant_level=variant_level)
697
oldstate = getattr(self, u"enabled", False)
698
r = Client.enable(self)
699
if oldstate != self.enabled:
701
self.PropertyChanged(dbus.String(u"enabled"),
702
dbus.Boolean(True, variant_level=1))
703
self.PropertyChanged(
704
dbus.String(u"last_enabled"),
705
self._datetime_to_dbus(self.last_enabled,
709
def disable(self, quiet = False):
710
oldstate = getattr(self, u"enabled", False)
711
r = Client.disable(self, quiet=quiet)
712
if not quiet and oldstate != self.enabled:
714
self.PropertyChanged(dbus.String(u"enabled"),
715
dbus.Boolean(False, variant_level=1))
1132
718
def __del__(self, *args, **kwargs):
1134
720
self.remove_from_connection()
1135
721
except LookupError:
1137
if hasattr(DBusObjectWithProperties, "__del__"):
723
if hasattr(DBusObjectWithProperties, u"__del__"):
1138
724
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1139
725
Client.__del__(self, *args, **kwargs)
1267
# ApprovalPending - property
1268
@dbus_service_property(_interface, signature="b", access="read")
1269
def ApprovalPending_dbus_property(self):
1270
return dbus.Boolean(bool(self.approvals_pending))
1272
# ApprovedByDefault - property
1273
@dbus_service_property(_interface, signature="b",
1275
def ApprovedByDefault_dbus_property(self, value=None):
1276
if value is None: # get
1277
return dbus.Boolean(self.approved_by_default)
1278
self.approved_by_default = bool(value)
1280
# ApprovalDelay - property
1281
@dbus_service_property(_interface, signature="t",
1283
def ApprovalDelay_dbus_property(self, value=None):
1284
if value is None: # get
1285
return dbus.UInt64(self.approval_delay_milliseconds())
1286
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1288
# ApprovalDuration - property
1289
@dbus_service_property(_interface, signature="t",
1291
def ApprovalDuration_dbus_property(self, value=None):
1292
if value is None: # get
1293
return dbus.UInt64(timedelta_to_milliseconds(
1294
self.approval_duration))
1295
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1298
@dbus_service_property(_interface, signature="s", access="read")
1299
def Name_dbus_property(self):
852
@dbus_service_property(_interface, signature=u"s", access=u"read")
853
def name_dbus_property(self):
1300
854
return dbus.String(self.name)
1302
# Fingerprint - property
1303
@dbus_service_property(_interface, signature="s", access="read")
1304
def Fingerprint_dbus_property(self):
856
# fingerprint - property
857
@dbus_service_property(_interface, signature=u"s", access=u"read")
858
def fingerprint_dbus_property(self):
1305
859
return dbus.String(self.fingerprint)
1308
@dbus_service_property(_interface, signature="s",
1310
def Host_dbus_property(self, value=None):
862
@dbus_service_property(_interface, signature=u"s",
864
def host_dbus_property(self, value=None):
1311
865
if value is None: # get
1312
866
return dbus.String(self.host)
1313
self.host = unicode(value)
1315
# Created - property
1316
@dbus_service_property(_interface, signature="s", access="read")
1317
def Created_dbus_property(self):
1318
return datetime_to_dbus(self.created)
1320
# LastEnabled - property
1321
@dbus_service_property(_interface, signature="s", access="read")
1322
def LastEnabled_dbus_property(self):
1323
return datetime_to_dbus(self.last_enabled)
1325
# Enabled - property
1326
@dbus_service_property(_interface, signature="b",
1328
def Enabled_dbus_property(self, value=None):
869
self.PropertyChanged(dbus.String(u"host"),
870
dbus.String(value, variant_level=1))
873
@dbus_service_property(_interface, signature=u"s", access=u"read")
874
def created_dbus_property(self):
875
return dbus.String(self._datetime_to_dbus(self.created))
877
# last_enabled - property
878
@dbus_service_property(_interface, signature=u"s", access=u"read")
879
def last_enabled_dbus_property(self):
880
if self.last_enabled is None:
881
return dbus.String(u"")
882
return dbus.String(self._datetime_to_dbus(self.last_enabled))
885
@dbus_service_property(_interface, signature=u"b",
887
def enabled_dbus_property(self, value=None):
1329
888
if value is None: # get
1330
889
return dbus.Boolean(self.enabled)
1336
# LastCheckedOK - property
1337
@dbus_service_property(_interface, signature="s",
1339
def LastCheckedOK_dbus_property(self, value=None):
895
# last_checked_ok - property
896
@dbus_service_property(_interface, signature=u"s",
898
def last_checked_ok_dbus_property(self, value=None):
1340
899
if value is not None:
1341
900
self.checked_ok()
1343
return datetime_to_dbus(self.last_checked_ok)
1345
# LastCheckerStatus - property
1346
@dbus_service_property(_interface, signature="n",
1348
def LastCheckerStatus_dbus_property(self):
1349
return dbus.Int16(self.last_checker_status)
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):
902
if self.last_checked_ok is None:
903
return dbus.String(u"")
904
return dbus.String(self._datetime_to_dbus(self
908
@dbus_service_property(_interface, signature=u"t",
910
def timeout_dbus_property(self, value=None):
1365
911
if value is None: # get
1366
912
return dbus.UInt64(self.timeout_milliseconds())
1367
913
self.timeout = datetime.timedelta(0, 0, 0, value)
915
self.PropertyChanged(dbus.String(u"timeout"),
916
dbus.UInt64(value, variant_level=1))
917
if getattr(self, u"disable_initiator_tag", None) is None:
1368
919
# Reschedule timeout
1370
now = datetime.datetime.utcnow()
1371
time_to_die = timedelta_to_milliseconds(
1372
(self.last_checked_ok + self.timeout) - now)
1373
if time_to_die <= 0:
1374
# The timeout has passed
1377
self.expires = (now +
1378
datetime.timedelta(milliseconds =
1380
if (getattr(self, "disable_initiator_tag", None)
1383
gobject.source_remove(self.disable_initiator_tag)
1384
self.disable_initiator_tag = (gobject.timeout_add
1388
# ExtendedTimeout - property
1389
@dbus_service_property(_interface, signature="t",
1391
def ExtendedTimeout_dbus_property(self, value=None):
1392
if value is None: # get
1393
return dbus.UInt64(self.extended_timeout_milliseconds())
1394
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1396
# Interval - property
1397
@dbus_service_property(_interface, signature="t",
1399
def Interval_dbus_property(self, value=None):
920
gobject.source_remove(self.disable_initiator_tag)
921
self.disable_initiator_tag = None
923
_timedelta_to_milliseconds((self
929
# The timeout has passed
932
self.disable_initiator_tag = (gobject.timeout_add
933
(time_to_die, self.disable))
935
# interval - property
936
@dbus_service_property(_interface, signature=u"t",
938
def interval_dbus_property(self, value=None):
1400
939
if value is None: # get
1401
940
return dbus.UInt64(self.interval_milliseconds())
1402
941
self.interval = datetime.timedelta(0, 0, 0, value)
1403
if getattr(self, "checker_initiator_tag", None) is None:
943
self.PropertyChanged(dbus.String(u"interval"),
944
dbus.UInt64(value, variant_level=1))
945
if getattr(self, u"checker_initiator_tag", None) is None:
1406
# Reschedule checker run
1407
gobject.source_remove(self.checker_initiator_tag)
1408
self.checker_initiator_tag = (gobject.timeout_add
1409
(value, self.start_checker))
1410
self.start_checker() # Start one now, too
1412
# Checker - property
1413
@dbus_service_property(_interface, signature="s",
1415
def Checker_dbus_property(self, value=None):
947
# Reschedule checker run
948
gobject.source_remove(self.checker_initiator_tag)
949
self.checker_initiator_tag = (gobject.timeout_add
950
(value, self.start_checker))
951
self.start_checker() # Start one now, too
954
@dbus_service_property(_interface, signature=u"s",
956
def checker_dbus_property(self, value=None):
1416
957
if value is None: # get
1417
958
return dbus.String(self.checker_command)
1418
self.checker_command = unicode(value)
959
self.checker_command = value
961
self.PropertyChanged(dbus.String(u"checker"),
962
dbus.String(self.checker_command,
1420
# CheckerRunning - property
1421
@dbus_service_property(_interface, signature="b",
1423
def CheckerRunning_dbus_property(self, value=None):
965
# checker_running - property
966
@dbus_service_property(_interface, signature=u"b",
968
def checker_running_dbus_property(self, value=None):
1424
969
if value is None: # get
1425
970
return dbus.Boolean(self.checker is not None)
1510
1027
# Start communication using the Mandos protocol
1511
1028
# Get protocol number
1512
1029
line = self.request.makefile().readline()
1513
logger.debug("Protocol version: %r", line)
1030
logger.debug(u"Protocol version: %r", line)
1515
1032
if int(line.strip().split()[0]) > 1:
1516
1033
raise RuntimeError
1517
except (ValueError, IndexError, RuntimeError) as error:
1518
logger.error("Unknown protocol version: %s", error)
1034
except (ValueError, IndexError, RuntimeError), error:
1035
logger.error(u"Unknown protocol version: %s", error)
1521
1038
# Start GnuTLS connection
1523
1040
session.handshake()
1524
except gnutls.errors.GNUTLSError as error:
1525
logger.warning("Handshake failed: %s", error)
1041
except gnutls.errors.GNUTLSError, error:
1042
logger.warning(u"Handshake failed: %s", error)
1526
1043
# Do not run session.bye() here: the session is not
1527
1044
# established. Just abandon the request.
1529
logger.debug("Handshake succeeded")
1531
approval_required = False
1046
logger.debug(u"Handshake succeeded")
1534
1049
fpr = self.fingerprint(self.peer_certificate
1537
gnutls.errors.GNUTLSError) as error:
1538
logger.warning("Bad certificate: %s", error)
1540
logger.debug("Fingerprint: %s", fpr)
1543
client = ProxyClient(child_pipe, fpr,
1544
self.client_address)
1548
if client.approval_delay:
1549
delay = client.approval_delay
1550
client.approvals_pending += 1
1551
approval_required = True
1554
if not client.enabled:
1555
logger.info("Client %s is disabled",
1557
if self.server.use_dbus:
1559
client.Rejected("Disabled")
1562
if client.approved or not client.approval_delay:
1563
#We are approved or approval is disabled
1051
except (TypeError, gnutls.errors.GNUTLSError), error:
1052
logger.warning(u"Bad certificate: %s", error)
1054
logger.debug(u"Fingerprint: %s", fpr)
1056
for c in self.server.clients:
1057
if c.fingerprint == fpr:
1565
elif client.approved is None:
1566
logger.info("Client %s needs approval",
1568
if self.server.use_dbus:
1570
client.NeedApproval(
1571
client.approval_delay_milliseconds(),
1572
client.approved_by_default)
1574
logger.warning("Client %s was not approved",
1576
if self.server.use_dbus:
1578
client.Rejected("Denied")
1581
#wait until timeout or approved
1582
time = datetime.datetime.now()
1583
client.changedstate.acquire()
1584
(client.changedstate.wait
1585
(float(client.timedelta_to_milliseconds(delay)
1587
client.changedstate.release()
1588
time2 = datetime.datetime.now()
1589
if (time2 - time) >= delay:
1590
if not client.approved_by_default:
1591
logger.warning("Client %s timed out while"
1592
" waiting for approval",
1594
if self.server.use_dbus:
1596
client.Rejected("Approval timed out")
1601
delay -= time2 - time
1061
ipc.write(u"NOTFOUND %s %s\n"
1062
% (fpr, unicode(self.client_address)))
1065
class ClientProxy(object):
1066
"""Client proxy object. Not for calling methods."""
1067
def __init__(self, client):
1068
self.client = client
1069
def __getattr__(self, name):
1070
if name.startswith("ipc_"):
1072
ipc.write("%s %s\n" % (name[4:].upper(),
1075
if not hasattr(self.client, name):
1076
raise AttributeError
1077
ipc.write(u"GETATTR %s %s\n"
1078
% (name, self.client.fingerprint))
1079
return pickle.load(ipc_return)
1080
clientproxy = ClientProxy(client)
1081
# Have to check if client.enabled, since it is
1082
# possible that the client was disabled since the
1083
# GnuTLS session was established.
1084
if not clientproxy.enabled:
1085
clientproxy.ipc_disabled()
1088
clientproxy.ipc_sending()
1604
1090
while sent_size < len(client.secret):
1606
sent = session.send(client.secret[sent_size:])
1607
except gnutls.errors.GNUTLSError as error:
1608
logger.warning("gnutls send failed")
1610
logger.debug("Sent: %d, remaining: %d",
1091
sent = session.send(client.secret[sent_size:])
1092
logger.debug(u"Sent: %d, remaining: %d",
1611
1093
sent, len(client.secret)
1612
1094
- (sent_size + sent))
1613
1095
sent_size += sent
1615
logger.info("Sending secret to %s", client.name)
1616
# bump the timeout using extended_timeout
1617
client.bump_timeout(client.extended_timeout)
1618
if self.server.use_dbus:
1623
if approval_required:
1624
client.approvals_pending -= 1
1627
except gnutls.errors.GNUTLSError as error:
1628
logger.warning("GnuTLS bye failed")
1631
1100
def peer_certificate(session):
1687
1156
# Convert the buffer to a Python bytestring
1688
1157
fpr = ctypes.string_at(buf, buf_len.value)
1689
1158
# Convert the bytestring to hexadecimal notation
1690
hex_fpr = binascii.hexlify(fpr).upper()
1159
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1694
class MultiprocessingMixIn(object):
1695
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1696
def sub_process_main(self, request, address):
1698
self.finish_request(request, address)
1700
self.handle_error(request, address)
1701
self.close_request(request)
1703
def process_request(self, request, address):
1704
"""Start a new process to process the request."""
1705
proc = multiprocessing.Process(target = self.sub_process_main,
1712
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1713
""" adds a pipe to the MixIn """
1163
class ForkingMixInWithPipes(socketserver.ForkingMixIn, object):
1164
"""Like socketserver.ForkingMixIn, but also pass a pipe pair."""
1714
1165
def process_request(self, request, client_address):
1715
1166
"""Overrides and wraps the original process_request().
1717
1168
This function creates a new pipe in self.pipe
1719
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1721
proc = MultiprocessingMixIn.process_request(self, request,
1723
self.child_pipe.close()
1724
self.add_pipe(parent_pipe, proc)
1726
def add_pipe(self, parent_pipe, proc):
1170
# Child writes to child_pipe
1171
self.child_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1172
# Parent writes to parent_pipe
1173
self.parent_pipe = map(os.fdopen, os.pipe(), u"rw", (1, 0))
1174
super(ForkingMixInWithPipes,
1175
self).process_request(request, client_address)
1176
# Close unused ends for parent
1177
self.parent_pipe[0].close() # close read end
1178
self.child_pipe[1].close() # close write end
1179
self.add_pipe_fds(self.child_pipe[0], self.parent_pipe[1])
1180
def add_pipe_fds(self, child_pipe_fd, parent_pipe_fd):
1727
1181
"""Dummy function; override as necessary"""
1728
raise NotImplementedError
1731
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1182
child_pipe_fd.close()
1183
parent_pipe_fd.close()
1186
class IPv6_TCPServer(ForkingMixInWithPipes,
1732
1187
socketserver.TCPServer, object):
1733
1188
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
1846
1296
for cond, name in
1847
1297
condition_names.iteritems()
1848
1298
if cond & condition)
1849
# error, or the other end of multiprocessing.Pipe has closed
1850
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1851
# Wait for other process to exit
1855
# Read a request from the child
1856
request = parent_pipe.recv()
1857
command = request[0]
1859
if command == 'init':
1861
address = request[2]
1863
for c in self.clients.itervalues():
1864
if c.fingerprint == fpr:
1868
logger.info("Client not found for fingerprint: %s, ad"
1869
"dress: %s", fpr, address)
1872
mandos_dbus_service.ClientNotFound(fpr,
1874
parent_pipe.send(False)
1877
gobject.io_add_watch(parent_pipe.fileno(),
1878
gobject.IO_IN | gobject.IO_HUP,
1879
functools.partial(self.handle_ipc,
1885
parent_pipe.send(True)
1886
# remove the old hook in favor of the new above hook on
1889
if command == 'funcall':
1890
funcname = request[1]
1894
parent_pipe.send(('data', getattr(client_object,
1898
if command == 'getattr':
1899
attrname = request[1]
1900
if callable(client_object.__getattribute__(attrname)):
1901
parent_pipe.send(('function',))
1903
parent_pipe.send(('data', client_object
1904
.__getattribute__(attrname)))
1906
if command == 'setattr':
1907
attrname = request[1]
1909
setattr(client_object, attrname, value)
1299
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1302
# Read a line from the file object
1303
cmdline = sender.readline()
1304
if not cmdline: # Empty line means end of file
1305
# close the IPC pipes
1309
# Stop calling this function
1312
logger.debug(u"IPC command: %r", cmdline)
1314
# Parse and act on command
1315
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1317
if cmd == u"NOTFOUND":
1318
fpr, address = args.split(None, 1)
1319
logger.warning(u"Client not found for fingerprint: %s, ad"
1320
u"dress: %s", fpr, address)
1323
mandos_dbus_service.ClientNotFound(fpr, address)
1324
elif cmd == u"DISABLED":
1325
for client in self.clients:
1326
if client.name == args:
1327
logger.warning(u"Client %s is disabled", args)
1333
logger.error(u"Unknown client %s is disabled", args)
1334
elif cmd == u"SENDING":
1335
for client in self.clients:
1336
if client.name == args:
1337
logger.info(u"Sending secret to %s", client.name)
1344
logger.error(u"Sending secret to unknown client %s",
1346
elif cmd == u"GETATTR":
1347
attr_name, fpr = args.split(None, 1)
1348
for client in self.clients:
1349
if client.fingerprint == fpr:
1350
attr_value = getattr(client, attr_name, None)
1351
logger.debug("IPC reply: %r", attr_value)
1352
pickle.dump(attr_value, reply)
1355
logger.error(u"Client %s on address %s requesting "
1356
u"attribute %s not found", fpr, address,
1358
pickle.dump(None, reply)
1360
logger.error(u"Unknown IPC command: %r", cmdline)
1362
# Keep calling this function
1914
1366
def string_to_delta(interval):
1915
1367
"""Parse a string and return a datetime.timedelta
1917
>>> string_to_delta('7d')
1369
>>> string_to_delta(u'7d')
1918
1370
datetime.timedelta(7)
1919
>>> string_to_delta('60s')
1371
>>> string_to_delta(u'60s')
1920
1372
datetime.timedelta(0, 60)
1921
>>> string_to_delta('60m')
1373
>>> string_to_delta(u'60m')
1922
1374
datetime.timedelta(0, 3600)
1923
>>> string_to_delta('24h')
1375
>>> string_to_delta(u'24h')
1924
1376
datetime.timedelta(1)
1925
>>> string_to_delta('1w')
1377
>>> string_to_delta(u'1w')
1926
1378
datetime.timedelta(7)
1927
>>> string_to_delta('5m 30s')
1379
>>> string_to_delta(u'5m 30s')
1928
1380
datetime.timedelta(0, 330)
1930
1382
timevalue = datetime.timedelta(0)
1980
1456
##################################################################
1981
1457
# Parsing of options, both command line and config file
1983
parser = argparse.ArgumentParser()
1984
parser.add_argument("-v", "--version", action="version",
1985
version = "%%(prog)s %s" % version,
1986
help="show version number and exit")
1987
parser.add_argument("-i", "--interface", metavar="IF",
1988
help="Bind to interface IF")
1989
parser.add_argument("-a", "--address",
1990
help="Address to listen for requests on")
1991
parser.add_argument("-p", "--port", type=int,
1992
help="Port number to receive requests on")
1993
parser.add_argument("--check", action="store_true",
1994
help="Run self-test")
1995
parser.add_argument("--debug", action="store_true",
1996
help="Debug mode; run in foreground and log"
1998
parser.add_argument("--debuglevel", metavar="LEVEL",
1999
help="Debug level for stdout output")
2000
parser.add_argument("--priority", help="GnuTLS"
2001
" priority string (see GnuTLS documentation)")
2002
parser.add_argument("--servicename",
2003
metavar="NAME", help="Zeroconf service name")
2004
parser.add_argument("--configdir",
2005
default="/etc/mandos", metavar="DIR",
2006
help="Directory to search for configuration"
2008
parser.add_argument("--no-dbus", action="store_false",
2009
dest="use_dbus", help="Do not provide D-Bus"
2010
" system bus interface")
2011
parser.add_argument("--no-ipv6", action="store_false",
2012
dest="use_ipv6", help="Do not use IPv6")
2013
parser.add_argument("--no-restore", action="store_false",
2014
dest="restore", help="Do not restore stored"
2016
parser.add_argument("--statedir", metavar="DIR",
2017
help="Directory to save/restore state in")
2019
options = parser.parse_args()
1459
parser = optparse.OptionParser(version = "%%prog %s" % version)
1460
parser.add_option("-i", u"--interface", type=u"string",
1461
metavar="IF", help=u"Bind to interface IF")
1462
parser.add_option("-a", u"--address", type=u"string",
1463
help=u"Address to listen for requests on")
1464
parser.add_option("-p", u"--port", type=u"int",
1465
help=u"Port number to receive requests on")
1466
parser.add_option("--check", action=u"store_true",
1467
help=u"Run self-test")
1468
parser.add_option("--debug", action=u"store_true",
1469
help=u"Debug mode; run in foreground and log to"
1471
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1472
u" priority string (see GnuTLS documentation)")
1473
parser.add_option("--servicename", type=u"string",
1474
metavar=u"NAME", help=u"Zeroconf service name")
1475
parser.add_option("--configdir", type=u"string",
1476
default=u"/etc/mandos", metavar=u"DIR",
1477
help=u"Directory to search for configuration"
1479
parser.add_option("--no-dbus", action=u"store_false",
1480
dest=u"use_dbus", help=u"Do not provide D-Bus"
1481
u" system bus interface")
1482
parser.add_option("--no-ipv6", action=u"store_false",
1483
dest=u"use_ipv6", help=u"Do not use IPv6")
1484
options = parser.parse_args()[0]
2021
1486
if options.check:
2073
1534
##################################################################
2075
1536
# For convenience
2076
debug = server_settings["debug"]
2077
debuglevel = server_settings["debuglevel"]
2078
use_dbus = server_settings["use_dbus"]
2079
use_ipv6 = server_settings["use_ipv6"]
2080
stored_state_path = os.path.join(server_settings["statedir"],
2084
initlogger(debug, logging.DEBUG)
2089
level = getattr(logging, debuglevel.upper())
2090
initlogger(debug, level)
2092
if server_settings["servicename"] != "Mandos":
1537
debug = server_settings[u"debug"]
1538
use_dbus = server_settings[u"use_dbus"]
1539
use_ipv6 = server_settings[u"use_ipv6"]
1542
syslogger.setLevel(logging.WARNING)
1543
console.setLevel(logging.WARNING)
1545
if server_settings[u"servicename"] != u"Mandos":
2093
1546
syslogger.setFormatter(logging.Formatter
2094
('Mandos (%s) [%%(process)d]:'
2095
' %%(levelname)s: %%(message)s'
2096
% server_settings["servicename"]))
1547
(u'Mandos (%s) [%%(process)d]:'
1548
u' %%(levelname)s: %%(message)s'
1549
% server_settings[u"servicename"]))
2098
1551
# Parse config file with clients
2099
client_config = configparser.SafeConfigParser(Client
2101
client_config.read(os.path.join(server_settings["configdir"],
1552
client_defaults = { u"timeout": u"1h",
1554
u"checker": u"fping -q -- %%(host)s",
1557
client_config = configparser.SafeConfigParser(client_defaults)
1558
client_config.read(os.path.join(server_settings[u"configdir"],
2104
1561
global mandos_dbus_service
2105
1562
mandos_dbus_service = None
2107
tcp_server = MandosServer((server_settings["address"],
2108
server_settings["port"]),
1564
tcp_server = MandosServer((server_settings[u"address"],
1565
server_settings[u"port"]),
2110
interface=(server_settings["interface"]
1567
interface=server_settings[u"interface"],
2112
1568
use_ipv6=use_ipv6,
2113
1569
gnutls_priority=
2114
server_settings["priority"],
1570
server_settings[u"priority"],
2115
1571
use_dbus=use_dbus)
2117
pidfilename = "/var/run/mandos.pid"
2119
pidfile = open(pidfilename, "w")
2121
logger.error("Could not open file %r", pidfilename)
1572
pidfilename = u"/var/run/mandos.pid"
1574
pidfile = open(pidfilename, u"w")
1576
logger.error(u"Could not open file %r", pidfilename)
2124
uid = pwd.getpwnam("_mandos").pw_uid
2125
gid = pwd.getpwnam("_mandos").pw_gid
1579
uid = pwd.getpwnam(u"_mandos").pw_uid
1580
gid = pwd.getpwnam(u"_mandos").pw_gid
2126
1581
except KeyError:
2128
uid = pwd.getpwnam("mandos").pw_uid
2129
gid = pwd.getpwnam("mandos").pw_gid
1583
uid = pwd.getpwnam(u"mandos").pw_uid
1584
gid = pwd.getpwnam(u"mandos").pw_gid
2130
1585
except KeyError:
2132
uid = pwd.getpwnam("nobody").pw_uid
2133
gid = pwd.getpwnam("nobody").pw_gid
1587
uid = pwd.getpwnam(u"nobody").pw_uid
1588
gid = pwd.getpwnam(u"nobody").pw_gid
2134
1589
except KeyError:
2140
except OSError as error:
1595
except OSError, error:
2141
1596
if error[0] != errno.EPERM:
1599
# Enable all possible GnuTLS debugging
2145
# Enable all possible GnuTLS debugging
2147
1601
# "Use a log level over 10 to enable all debugging options."
2148
1602
# - GnuTLS manual
2149
1603
gnutls.library.functions.gnutls_global_set_log_level(11)
2151
1605
@gnutls.library.types.gnutls_log_func
2152
1606
def debug_gnutls(level, string):
2153
logger.debug("GnuTLS: %s", string[:-1])
1607
logger.debug(u"GnuTLS: %s", string[:-1])
2155
1609
(gnutls.library.functions
2156
1610
.gnutls_global_set_log_function(debug_gnutls))
2158
# Redirect stdin so all checkers get /dev/null
2159
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2160
os.dup2(null, sys.stdin.fileno())
2164
# Need to fork before connecting to D-Bus
2166
# Close all input and output, do double fork, etc.
2169
gobject.threads_init()
2171
1612
global main_loop
2172
1613
# From the Avahi example code
2176
1617
# End of Avahi example code
2179
bus_name = dbus.service.BusName("se.recompile.Mandos",
1620
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
2180
1621
bus, do_not_queue=True)
2181
old_bus_name = (dbus.service.BusName
2182
("se.bsnet.fukt.Mandos", bus,
2184
except dbus.exceptions.NameExistsException as e:
2185
logger.error(unicode(e) + ", disabling D-Bus")
1622
except dbus.exceptions.NameExistsException, e:
1623
logger.error(unicode(e) + u", disabling D-Bus")
2186
1624
use_dbus = False
2187
server_settings["use_dbus"] = False
1625
server_settings[u"use_dbus"] = False
2188
1626
tcp_server.use_dbus = False
2189
1627
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2190
service = AvahiServiceToSyslog(name =
2191
server_settings["servicename"],
2192
servicetype = "_mandos._tcp",
2193
protocol = protocol, bus = bus)
1628
service = AvahiService(name = server_settings[u"servicename"],
1629
servicetype = u"_mandos._tcp",
1630
protocol = protocol, bus = bus)
2194
1631
if server_settings["interface"]:
2195
1632
service.interface = (if_nametoindex
2196
(str(server_settings["interface"])))
2198
global multiprocessing_manager
2199
multiprocessing_manager = multiprocessing.Manager()
1633
(str(server_settings[u"interface"])))
2201
1635
client_class = Client
2203
client_class = functools.partial(ClientDBusTransitional,
2206
client_settings = Client.config_parser(client_config)
2207
old_client_settings = {}
2210
# Get client data and settings from last running state.
2211
if server_settings["restore"]:
2213
with open(stored_state_path, "rb") as stored_state:
2214
clients_data, old_client_settings = (pickle.load
2216
os.remove(stored_state_path)
2217
except IOError as e:
2218
logger.warning("Could not load persistent state: {0}"
2220
if e.errno != errno.ENOENT:
2222
except EOFError as e:
2223
logger.warning("Could not load persistent state: "
2224
"EOFError: {0}".format(e))
2226
with PGPEngine() as pgp:
2227
for client_name, client in clients_data.iteritems():
2228
# Decide which value to use after restoring saved state.
2229
# We have three different values: Old config file,
2230
# new config file, and saved state.
2231
# New config value takes precedence if it differs from old
2232
# config value, otherwise use saved state.
2233
for name, value in client_settings[client_name].items():
2235
# For each value in new config, check if it
2236
# differs from the old config value (Except for
2237
# the "secret" attribute)
2238
if (name != "secret" and
2239
value != old_client_settings[client_name]
2241
client[name] = value
2245
# Clients who has passed its expire date can still be
2246
# enabled if its last checker was successful. Clients
2247
# whose checker succeeded before we stored its state is
2248
# assumed to have successfully run all checkers during
2250
if client["enabled"]:
2251
if datetime.datetime.utcnow() >= client["expires"]:
2252
if not client["last_checked_ok"]:
2254
"disabling client {0} - Client never "
2255
"performed a successful checker"
2256
.format(client_name))
2257
client["enabled"] = False
2258
elif client["last_checker_status"] != 0:
2260
"disabling client {0} - Client "
2261
"last checker failed with error code {1}"
2262
.format(client_name,
2263
client["last_checker_status"]))
2264
client["enabled"] = False
2266
client["expires"] = (datetime.datetime
2268
+ client["timeout"])
2269
logger.debug("Last checker succeeded,"
2270
" keeping {0} enabled"
2271
.format(client_name))
2273
client["secret"] = (
2274
pgp.decrypt(client["encrypted_secret"],
2275
client_settings[client_name]
2278
# If decryption fails, we use secret from new settings
2279
logger.debug("Failed to decrypt {0} old secret"
2280
.format(client_name))
2281
client["secret"] = (
2282
client_settings[client_name]["secret"])
2285
# Add/remove clients based on new changes made to config
2286
for client_name in (set(old_client_settings)
2287
- set(client_settings)):
2288
del clients_data[client_name]
2289
for client_name in (set(client_settings)
2290
- set(old_client_settings)):
2291
clients_data[client_name] = client_settings[client_name]
2293
# Create all client objects
2294
for client_name, client in clients_data.iteritems():
2295
tcp_server.clients[client_name] = client_class(
2296
name = client_name, settings = client)
1637
client_class = functools.partial(ClientDBus, bus = bus)
1638
tcp_server.clients.update(set(
1639
client_class(name = section,
1640
config= dict(client_config.items(section)))
1641
for section in client_config.sections()))
2298
1642
if not tcp_server.clients:
2299
logger.warning("No clients defined")
1643
logger.warning(u"No clients defined")
1646
# Redirect stdin so all checkers get /dev/null
1647
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1648
os.dup2(null, sys.stdin.fileno())
1652
# No console logging
1653
logger.removeHandler(console)
1654
# Close all input and output, do double fork, etc.
1660
pidfile.write(str(pid) + "\n")
1663
logger.error(u"Could not write to file %r with PID %d",
1666
# "pidfile" was never created
2305
pidfile.write(str(pid) + "\n".encode("utf-8"))
2308
logger.error("Could not write to file %r with PID %d",
2311
# "pidfile" was never created
2314
1671
signal.signal(signal.SIGINT, signal.SIG_IGN)
2316
1672
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2317
1673
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2373
class MandosDBusServiceTransitional(MandosDBusService):
2374
__metaclass__ = AlternateDBusNamesMetaclass
2375
mandos_dbus_service = MandosDBusServiceTransitional()
1728
mandos_dbus_service = MandosDBusService()
2378
1731
"Cleanup function; run on exit"
2379
1732
service.cleanup()
2381
multiprocessing.active_children()
2382
if not (tcp_server.clients or client_settings):
2385
# Store client before exiting. Secrets are encrypted with key
2386
# based on what config file has. If config file is
2387
# removed/edited, old secret will thus be unrecovable.
2389
with PGPEngine() as pgp:
2390
for client in tcp_server.clients.itervalues():
2391
key = client_settings[client.name]["secret"]
2392
client.encrypted_secret = pgp.encrypt(client.secret,
2396
# A list of attributes that can not be pickled
2398
exclude = set(("bus", "changedstate", "secret",
2400
for name, typ in (inspect.getmembers
2401
(dbus.service.Object)):
2404
client_dict["encrypted_secret"] = (client
2406
for attr in client.client_structure:
2407
if attr not in exclude:
2408
client_dict[attr] = getattr(client, attr)
2410
clients[client.name] = client_dict
2411
del client_settings[client.name]["secret"]
2414
tempfd, tempname = tempfile.mkstemp(suffix=".pickle",
2417
(stored_state_path))
2418
with os.fdopen(tempfd, "wb") as stored_state:
2419
pickle.dump((clients, client_settings), stored_state)
2420
os.rename(tempname, stored_state_path)
2421
except (IOError, OSError) as e:
2422
logger.warning("Could not save persistent state: {0}"
2429
if e.errno not in set((errno.ENOENT, errno.EACCES,
2433
# Delete all clients, and settings from config
2434
1734
while tcp_server.clients:
2435
name, client = tcp_server.clients.popitem()
1735
client = tcp_server.clients.pop()
2437
1737
client.remove_from_connection()
1738
client.disable_hook = None
2438
1739
# Don't signal anything except ClientRemoved
2439
1740
client.disable(quiet=True)
2441
1742
# Emit D-Bus signal
2442
mandos_dbus_service.ClientRemoved(client
1743
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2445
client_settings.clear()
2447
1746
atexit.register(cleanup)
2449
for client in tcp_server.clients.itervalues():
1748
for client in tcp_server.clients:
2451
1750
# Emit D-Bus signal
2452
1751
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2453
# Need to initiate checking of clients
2455
client.init_checker()
2457
1754
tcp_server.enable()
2458
1755
tcp_server.server_activate()