126
257
self.rename_count = 0
127
258
self.max_renames = max_renames
128
259
self.protocol = protocol
260
self.group = None # our entry group
263
self.entry_group_state_changed_match = None
129
264
def rename(self):
130
265
"""Derived from the Avahi example code"""
131
266
if self.rename_count >= self.max_renames:
132
logger.critical(u"No suitable Zeroconf service name found"
133
u" after %i retries, exiting.",
267
logger.critical("No suitable Zeroconf service name found"
268
" after %i retries, exiting.",
134
269
self.rename_count)
135
raise AvahiServiceError(u"Too many renames")
136
self.name = server.GetAlternativeServiceName(self.name)
137
logger.info(u"Changing Zeroconf service name to %r ...",
139
syslogger.setFormatter(logging.Formatter
140
('Mandos (%s) [%%(process)d]:'
141
' %%(levelname)s: %%(message)s'
270
raise AvahiServiceError("Too many renames")
271
self.name = unicode(self.server
272
.GetAlternativeServiceName(self.name))
273
logger.info("Changing Zeroconf service name to %r ...",
278
except dbus.exceptions.DBusException as error:
279
logger.critical("DBusException: %s", error)
145
282
self.rename_count += 1
146
283
def remove(self):
147
284
"""Derived from the Avahi example code"""
148
if group is not None:
285
if self.entry_group_state_changed_match is not None:
286
self.entry_group_state_changed_match.remove()
287
self.entry_group_state_changed_match = None
288
if self.group is not None:
151
291
"""Derived from the Avahi example code"""
154
group = dbus.Interface(bus.get_object
156
server.EntryGroupNew()),
157
avahi.DBUS_INTERFACE_ENTRY_GROUP)
158
group.connect_to_signal('StateChanged',
159
entry_group_state_changed)
160
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
161
service.name, service.type)
163
self.interface, # interface
164
self.protocol, # protocol
165
dbus.UInt32(0), # flags
166
self.name, self.type,
167
self.domain, self.host,
168
dbus.UInt16(self.port),
169
avahi.string_array_to_txt_array(self.TXT))
172
# From the Avahi example code:
173
group = None # our entry group
174
# End of Avahi example code
177
def _datetime_to_dbus(dt, variant_level=0):
178
"""Convert a UTC datetime.datetime() to a D-Bus type."""
179
return dbus.String(dt.isoformat(), variant_level=variant_level)
293
if self.group is None:
294
self.group = dbus.Interface(
295
self.bus.get_object(avahi.DBUS_NAME,
296
self.server.EntryGroupNew()),
297
avahi.DBUS_INTERFACE_ENTRY_GROUP)
298
self.entry_group_state_changed_match = (
299
self.group.connect_to_signal(
300
'StateChanged', self.entry_group_state_changed))
301
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
302
self.name, self.type)
303
self.group.AddService(
306
dbus.UInt32(0), # flags
307
self.name, self.type,
308
self.domain, self.host,
309
dbus.UInt16(self.port),
310
avahi.string_array_to_txt_array(self.TXT))
312
def entry_group_state_changed(self, state, error):
313
"""Derived from the Avahi example code"""
314
logger.debug("Avahi entry group state change: %i", state)
316
if state == avahi.ENTRY_GROUP_ESTABLISHED:
317
logger.debug("Zeroconf service established.")
318
elif state == avahi.ENTRY_GROUP_COLLISION:
319
logger.info("Zeroconf service name collision.")
321
elif state == avahi.ENTRY_GROUP_FAILURE:
322
logger.critical("Avahi: Error in group state changed %s",
324
raise AvahiGroupError("State changed: %s"
327
"""Derived from the Avahi example code"""
328
if self.group is not None:
331
except (dbus.exceptions.UnknownMethodException,
332
dbus.exceptions.DBusException):
336
def server_state_changed(self, state, error=None):
337
"""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)
353
elif state == avahi.SERVER_RUNNING:
357
logger.debug("Unknown state: %r", state)
359
logger.debug("Unknown state: %r: %r", state, error)
361
"""Derived from the Avahi example code"""
362
if self.server is None:
363
self.server = dbus.Interface(
364
self.bus.get_object(avahi.DBUS_NAME,
365
avahi.DBUS_PATH_SERVER,
366
follow_name_owner_changes=True),
367
avahi.DBUS_INTERFACE_SERVER)
368
self.server.connect_to_signal("StateChanged",
369
self.server_state_changed)
370
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))
182
388
class Client(object):
183
389
"""A representation of a client host served by this server.
185
name: string; from the config file, used in log messages and
187
fingerprint: string (40 or 32 hexadecimal digits); used to
188
uniquely identify the client
189
secret: bytestring; sent verbatim (over TLS) to client
190
host: string; available for use by the checker command
191
created: datetime.datetime(); (UTC) object creation
192
last_enabled: datetime.datetime(); (UTC)
194
last_checked_ok: datetime.datetime(); (UTC) or None
195
timeout: datetime.timedelta(); How long from last_checked_ok
196
until this client is invalid
197
interval: datetime.timedelta(); How often to start a new checker
198
disable_hook: If set, called by disable() as disable_hook(self)
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
199
395
checker: subprocess.Popen(); a running checker process used
200
396
to see if the client lives.
201
397
'None' if no process is running.
202
checker_initiator_tag: a gobject event source tag, or None
203
disable_initiator_tag: - '' -
204
checker_callback_tag: - '' -
205
checker_command: string; External command which is run to check if
206
client lives. %() expansions are done at
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
207
401
runtime with vars(self) as dict, so that for
208
402
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
209
407
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
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 password 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",
211
446
def timeout_milliseconds(self):
212
447
"Return the 'timeout' attribute in milliseconds"
213
return ((self.timeout.days * 24 * 60 * 60 * 1000)
214
+ (self.timeout.seconds * 1000)
215
+ (self.timeout.microseconds // 1000))
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)
217
454
def interval_milliseconds(self):
218
455
"Return the 'interval' attribute in milliseconds"
219
return ((self.interval.days * 24 * 60 * 60 * 1000)
220
+ (self.interval.seconds * 1000)
221
+ (self.interval.microseconds // 1000))
456
return timedelta_to_milliseconds(self.interval)
223
def __init__(self, name = None, disable_hook=None, config=None):
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"""
467
for client_name in config.sections():
468
section = dict(config.items(client_name))
469
client = settings[client_name] = {}
471
client["host"] = section["host"]
472
# Reformat values from string types to Python types
473
client["approved_by_default"] = config.getboolean(
474
client_name, "approved_by_default")
475
client["enabled"] = config.getboolean(client_name, "enabled")
477
client["fingerprint"] = (section["fingerprint"].upper()
479
if "secret" in section:
480
client["secret"] = section["secret"].decode("base64")
481
elif "secfile" in section:
482
with open(os.path.expanduser(os.path.expandvars
483
(section["secfile"])),
485
client["secret"] = secfile.read()
487
raise TypeError("No secret or secfile for section %s"
489
client["timeout"] = string_to_delta(section["timeout"])
490
client["extended_timeout"] = string_to_delta(
491
section["extended_timeout"])
492
client["interval"] = string_to_delta(section["interval"])
493
client["approval_delay"] = string_to_delta(
494
section["approval_delay"])
495
client["approval_duration"] = string_to_delta(
496
section["approval_duration"])
497
client["checker_command"] = section["checker"]
498
client["last_approval_request"] = None
499
client["last_checked_ok"] = None
500
client["last_checker_status"] = None
501
if client["enabled"]:
502
client["last_enabled"] = datetime.datetime.utcnow()
503
client["expires"] = (datetime.datetime.utcnow()
506
client["last_enabled"] = None
507
client["expires"] = None
512
def __init__(self, settings, name = None):
224
513
"""Note: the 'checker' key in 'config' sets the
225
514
'checker_command' attribute and *not* the 'checker'
230
logger.debug(u"Creating client %r", self.name)
517
# adding all client settings
518
for setting, value in settings.iteritems():
519
setattr(self, setting, value)
521
logger.debug("Creating client %r", self.name)
231
522
# Uppercase and remove spaces from fingerprint for later
232
523
# comparison purposes with return value from the fingerprint()
234
self.fingerprint = (config["fingerprint"].upper()
236
logger.debug(u" Fingerprint: %s", self.fingerprint)
237
if "secret" in config:
238
self.secret = config["secret"].decode(u"base64")
239
elif "secfile" in config:
240
with closing(open(os.path.expanduser
242
(config["secfile"])))) as secfile:
243
self.secret = secfile.read()
245
raise TypeError(u"No secret or secfile for client %s"
247
self.host = config.get("host", "")
248
self.created = datetime.datetime.utcnow()
250
self.last_enabled = None
251
self.last_checked_ok = None
252
self.timeout = string_to_delta(config["timeout"])
253
self.interval = string_to_delta(config["interval"])
254
self.disable_hook = disable_hook
525
logger.debug(" Fingerprint: %s", self.fingerprint)
526
self.created = settings.get("created", datetime.datetime.utcnow())
528
# attributes specific for this server instance
255
529
self.checker = None
256
530
self.checker_initiator_tag = None
257
531
self.disable_initiator_tag = None
258
532
self.checker_callback_tag = None
259
self.checker_command = config["checker"]
260
533
self.current_checker_command = None
261
self.last_connect = None
535
self.approvals_pending = 0
536
self.changedstate = (multiprocessing_manager
537
.Condition(multiprocessing_manager
539
self.client_structure = [attr for attr in
540
self.__dict__.iterkeys()
541
if not attr.startswith("_")]
542
self.client_structure.append("client_structure")
544
for name, t in inspect.getmembers(type(self),
548
if not name.startswith("_"):
549
self.client_structure.append(name)
551
# Send notice to process children that client state has changed
552
def send_changedstate(self):
553
with self.changedstate:
554
self.changedstate.notify_all()
263
556
def enable(self):
264
557
"""Start this client's checker and timeout hooks"""
558
if getattr(self, "enabled", False):
561
self.send_changedstate()
562
self.expires = datetime.datetime.utcnow() + self.timeout
265
564
self.last_enabled = datetime.datetime.utcnow()
567
def disable(self, quiet=True):
568
"""Disable this client."""
569
if not getattr(self, "enabled", False):
572
self.send_changedstate()
574
logger.info("Disabling client %s", self.name)
575
if getattr(self, "disable_initiator_tag", False):
576
gobject.source_remove(self.disable_initiator_tag)
577
self.disable_initiator_tag = None
579
if getattr(self, "checker_initiator_tag", False):
580
gobject.source_remove(self.checker_initiator_tag)
581
self.checker_initiator_tag = None
584
# Do not run this again if called by a gobject.timeout_add
590
def init_checker(self):
266
591
# Schedule a new checker to be started an 'interval' from now,
267
592
# and every interval from then on.
268
593
self.checker_initiator_tag = (gobject.timeout_add
269
594
(self.interval_milliseconds(),
270
595
self.start_checker))
271
# Also start a new checker *right now*.
273
596
# Schedule a disable() when 'timeout' has passed
274
597
self.disable_initiator_tag = (gobject.timeout_add
275
598
(self.timeout_milliseconds(),
280
"""Disable this client."""
281
if not getattr(self, "enabled", False):
283
logger.info(u"Disabling client %s", self.name)
284
if getattr(self, "disable_initiator_tag", False):
285
gobject.source_remove(self.disable_initiator_tag)
286
self.disable_initiator_tag = None
287
if getattr(self, "checker_initiator_tag", False):
288
gobject.source_remove(self.checker_initiator_tag)
289
self.checker_initiator_tag = None
291
if self.disable_hook:
292
self.disable_hook(self)
294
# Do not run this again if called by a gobject.timeout_add
298
self.disable_hook = None
600
# Also start a new checker *right now*.
301
603
def checker_callback(self, pid, condition, command):
302
604
"""The checker has completed, so take appropriate actions."""
303
605
self.checker_callback_tag = None
304
606
self.checker = None
305
607
if os.WIFEXITED(condition):
306
exitstatus = os.WEXITSTATUS(condition)
308
logger.info(u"Checker for %(name)s succeeded",
608
self.last_checker_status = os.WEXITSTATUS(condition)
609
if self.last_checker_status == 0:
610
logger.info("Checker for %(name)s succeeded",
310
612
self.checked_ok()
312
logger.info(u"Checker for %(name)s failed",
614
logger.info("Checker for %(name)s failed",
315
logger.warning(u"Checker for %(name)s crashed?",
617
self.last_checker_status = -1
618
logger.warning("Checker for %(name)s crashed?",
318
def checked_ok(self):
621
def checked_ok(self, timeout=None):
319
622
"""Bump up the timeout for this client.
320
624
This should only be called when the client has been seen,
628
timeout = self.timeout
323
629
self.last_checked_ok = datetime.datetime.utcnow()
324
gobject.source_remove(self.disable_initiator_tag)
325
self.disable_initiator_tag = (gobject.timeout_add
326
(self.timeout_milliseconds(),
630
if self.disable_initiator_tag is not None:
631
gobject.source_remove(self.disable_initiator_tag)
632
if getattr(self, "enabled", False):
633
self.disable_initiator_tag = (gobject.timeout_add
634
(timedelta_to_milliseconds
635
(timeout), self.disable))
636
self.expires = datetime.datetime.utcnow() + timeout
638
def need_approval(self):
639
self.last_approval_request = datetime.datetime.utcnow()
329
641
def start_checker(self):
330
642
"""Start a new checker subprocess if one is not running.
331
644
If a checker already exists, leave it running and do
333
646
# The reason for not killing a running checker is that if we
397
720
self.checker_callback_tag = None
398
721
if getattr(self, "checker", None) is None:
400
logger.debug(u"Stopping checker for %(name)s", vars(self))
723
logger.debug("Stopping checker for %(name)s", vars(self))
402
725
os.kill(self.checker.pid, signal.SIGTERM)
404
727
#if self.checker.poll() is None:
405
728
# os.kill(self.checker.pid, signal.SIGKILL)
406
except OSError, error:
729
except OSError as error:
407
730
if error.errno != errno.ESRCH: # No such process
409
732
self.checker = None
411
def still_valid(self):
412
"""Has the timeout not yet passed for this client?"""
413
if not getattr(self, "enabled", False):
415
now = datetime.datetime.utcnow()
416
if self.last_checked_ok is None:
417
return now < (self.created + self.timeout)
419
return now < (self.last_checked_ok + self.timeout)
422
class ClientDBus(Client, dbus.service.Object):
735
def dbus_service_property(dbus_interface, signature="v",
736
access="readwrite", byte_arrays=False):
737
"""Decorators for marking methods of a DBusObjectWithProperties to
738
become properties on the D-Bus.
740
The decorated method will be called with no arguments by "Get"
741
and with one argument by "Set".
743
The parameters, where they are supported, are the same as
744
dbus.service.method, except there is only "signature", since the
745
type from Get() and the type sent to Set() is the same.
747
# Encoding deeply encoded byte arrays is not supported yet by the
748
# "Set" method, so we fail early here:
749
if byte_arrays and signature != "ay":
750
raise ValueError("Byte arrays not supported for non-'ay'"
751
" signature %r" % signature)
753
func._dbus_is_property = True
754
func._dbus_interface = dbus_interface
755
func._dbus_signature = signature
756
func._dbus_access = access
757
func._dbus_name = func.__name__
758
if func._dbus_name.endswith("_dbus_property"):
759
func._dbus_name = func._dbus_name[:-14]
760
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
765
class DBusPropertyException(dbus.exceptions.DBusException):
766
"""A base class for D-Bus property-related exceptions
768
def __unicode__(self):
769
return unicode(str(self))
772
class DBusPropertyAccessException(DBusPropertyException):
773
"""A property's access permissions disallows an operation.
778
class DBusPropertyNotFound(DBusPropertyException):
779
"""An attempt was made to access a non-existing property.
784
class DBusObjectWithProperties(dbus.service.Object):
785
"""A D-Bus object with properties.
787
Classes inheriting from this can use the dbus_service_property
788
decorator to expose methods as D-Bus properties. It exposes the
789
standard Get(), Set(), and GetAll() methods on the D-Bus.
793
def _is_dbus_property(obj):
794
return getattr(obj, "_dbus_is_property", False)
796
def _get_all_dbus_properties(self):
797
"""Returns a generator of (name, attribute) pairs
799
return ((prop.__get__(self)._dbus_name, prop.__get__(self))
800
for cls in self.__class__.__mro__
802
inspect.getmembers(cls, self._is_dbus_property))
804
def _get_dbus_property(self, interface_name, property_name):
805
"""Returns a bound method if one exists which is a D-Bus
806
property with the specified name and interface.
808
for cls in self.__class__.__mro__:
809
for name, value in (inspect.getmembers
810
(cls, self._is_dbus_property)):
811
if (value._dbus_name == property_name
812
and value._dbus_interface == interface_name):
813
return value.__get__(self)
816
raise DBusPropertyNotFound(self.dbus_object_path + ":"
817
+ interface_name + "."
820
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
822
def Get(self, interface_name, property_name):
823
"""Standard D-Bus property Get() method, see D-Bus standard.
825
prop = self._get_dbus_property(interface_name, property_name)
826
if prop._dbus_access == "write":
827
raise DBusPropertyAccessException(property_name)
829
if not hasattr(value, "variant_level"):
831
return type(value)(value, variant_level=value.variant_level+1)
833
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
834
def Set(self, interface_name, property_name, value):
835
"""Standard D-Bus property Set() method, see D-Bus standard.
837
prop = self._get_dbus_property(interface_name, property_name)
838
if prop._dbus_access == "read":
839
raise DBusPropertyAccessException(property_name)
840
if prop._dbus_get_args_options["byte_arrays"]:
841
# The byte_arrays option is not supported yet on
842
# signatures other than "ay".
843
if prop._dbus_signature != "ay":
845
value = dbus.ByteArray(''.join(unichr(byte)
849
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
850
out_signature="a{sv}")
851
def GetAll(self, interface_name):
852
"""Standard D-Bus property GetAll() method, see D-Bus
855
Note: Will not include properties with access="write".
858
for name, prop in self._get_all_dbus_properties():
860
and interface_name != prop._dbus_interface):
861
# Interface non-empty but did not match
863
# Ignore write-only properties
864
if prop._dbus_access == "write":
867
if not hasattr(value, "variant_level"):
868
properties[name] = value
870
properties[name] = type(value)(value, variant_level=
871
value.variant_level+1)
872
return dbus.Dictionary(properties, signature="sv")
874
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
876
path_keyword='object_path',
877
connection_keyword='connection')
878
def Introspect(self, object_path, connection):
879
"""Standard D-Bus method, overloaded to insert property tags.
881
xmlstring = dbus.service.Object.Introspect(self, object_path,
884
document = xml.dom.minidom.parseString(xmlstring)
885
def make_tag(document, name, prop):
886
e = document.createElement("property")
887
e.setAttribute("name", name)
888
e.setAttribute("type", prop._dbus_signature)
889
e.setAttribute("access", prop._dbus_access)
891
for if_tag in document.getElementsByTagName("interface"):
892
for tag in (make_tag(document, name, prop)
894
in self._get_all_dbus_properties()
895
if prop._dbus_interface
896
== if_tag.getAttribute("name")):
897
if_tag.appendChild(tag)
898
# Add the names to the return values for the
899
# "org.freedesktop.DBus.Properties" methods
900
if (if_tag.getAttribute("name")
901
== "org.freedesktop.DBus.Properties"):
902
for cn in if_tag.getElementsByTagName("method"):
903
if cn.getAttribute("name") == "Get":
904
for arg in cn.getElementsByTagName("arg"):
905
if (arg.getAttribute("direction")
907
arg.setAttribute("name", "value")
908
elif cn.getAttribute("name") == "GetAll":
909
for arg in cn.getElementsByTagName("arg"):
910
if (arg.getAttribute("direction")
912
arg.setAttribute("name", "props")
913
xmlstring = document.toxml("utf-8")
915
except (AttributeError, xml.dom.DOMException,
916
xml.parsers.expat.ExpatError) as error:
917
logger.error("Failed to override Introspection method",
922
def datetime_to_dbus (dt, variant_level=0):
923
"""Convert a UTC datetime.datetime() to a D-Bus type."""
925
return dbus.String("", variant_level = variant_level)
926
return dbus.String(dt.isoformat(),
927
variant_level=variant_level)
930
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
932
"""Applied to an empty subclass of a D-Bus object, this metaclass
933
will add additional D-Bus attributes matching a certain pattern.
935
def __new__(mcs, name, bases, attr):
936
# Go through all the base classes which could have D-Bus
937
# methods, signals, or properties in them
938
for base in (b for b in bases
939
if issubclass(b, dbus.service.Object)):
940
# Go though all attributes of the base class
941
for attrname, attribute in inspect.getmembers(base):
942
# Ignore non-D-Bus attributes, and D-Bus attributes
943
# with the wrong interface name
944
if (not hasattr(attribute, "_dbus_interface")
945
or not attribute._dbus_interface
946
.startswith("se.recompile.Mandos")):
948
# Create an alternate D-Bus interface name based on
950
alt_interface = (attribute._dbus_interface
951
.replace("se.recompile.Mandos",
952
"se.bsnet.fukt.Mandos"))
953
# Is this a D-Bus signal?
954
if getattr(attribute, "_dbus_is_signal", False):
955
# Extract the original non-method function by
957
nonmethod_func = (dict(
958
zip(attribute.func_code.co_freevars,
959
attribute.__closure__))["func"]
961
# Create a new, but exactly alike, function
962
# object, and decorate it to be a new D-Bus signal
963
# with the alternate D-Bus interface name
964
new_function = (dbus.service.signal
966
attribute._dbus_signature)
968
nonmethod_func.func_code,
969
nonmethod_func.func_globals,
970
nonmethod_func.func_name,
971
nonmethod_func.func_defaults,
972
nonmethod_func.func_closure)))
973
# Define a creator of a function to call both the
974
# old and new functions, so both the old and new
975
# signals gets sent when the function is called
976
def fixscope(func1, func2):
977
"""This function is a scope container to pass
978
func1 and func2 to the "call_both" function
979
outside of its arguments"""
980
def call_both(*args, **kwargs):
981
"""This function will emit two D-Bus
982
signals by calling func1 and func2"""
983
func1(*args, **kwargs)
984
func2(*args, **kwargs)
986
# Create the "call_both" function and add it to
988
attr[attrname] = fixscope(attribute,
990
# Is this a D-Bus method?
991
elif getattr(attribute, "_dbus_is_method", False):
992
# Create a new, but exactly alike, function
993
# object. Decorate it to be a new D-Bus method
994
# with the alternate D-Bus interface name. Add it
996
attr[attrname] = (dbus.service.method
998
attribute._dbus_in_signature,
999
attribute._dbus_out_signature)
1001
(attribute.func_code,
1002
attribute.func_globals,
1003
attribute.func_name,
1004
attribute.func_defaults,
1005
attribute.func_closure)))
1006
# Is this a D-Bus property?
1007
elif getattr(attribute, "_dbus_is_property", False):
1008
# Create a new, but exactly alike, function
1009
# object, and decorate it to be a new D-Bus
1010
# property with the alternate D-Bus interface
1011
# name. Add it to the class.
1012
attr[attrname] = (dbus_service_property
1014
attribute._dbus_signature,
1015
attribute._dbus_access,
1017
._dbus_get_args_options
1020
(attribute.func_code,
1021
attribute.func_globals,
1022
attribute.func_name,
1023
attribute.func_defaults,
1024
attribute.func_closure)))
1025
return type.__new__(mcs, name, bases, attr)
1028
class ClientDBus(Client, DBusObjectWithProperties):
423
1029
"""A Client class using D-Bus
425
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
1032
dbus_object_path: dbus.ObjectPath
1033
bus: dbus.SystemBus()
1036
runtime_expansions = (Client.runtime_expansions
1037
+ ("dbus_object_path",))
427
1039
# dbus.service.Object doesn't use super(), so we can't either.
429
def __init__(self, *args, **kwargs):
1041
def __init__(self, bus = None, *args, **kwargs):
430
1043
Client.__init__(self, *args, **kwargs)
1044
self._approvals_pending = 0
1046
self._approvals_pending = 0
431
1047
# Only now, when this client is initialized, can it show up on
1049
client_object_name = unicode(self.name).translate(
1050
{ord("."): ord("_"),
1051
ord("-"): ord("_")})
433
1052
self.dbus_object_path = (dbus.ObjectPath
435
+ self.name.replace(".", "_")))
436
dbus.service.Object.__init__(self, bus,
437
self.dbus_object_path)
439
oldstate = getattr(self, "enabled", False)
440
r = Client.enable(self)
441
if oldstate != self.enabled:
443
self.PropertyChanged(dbus.String(u"enabled"),
444
dbus.Boolean(True, variant_level=1))
445
self.PropertyChanged(dbus.String(u"last_enabled"),
446
(_datetime_to_dbus(self.last_enabled,
450
def disable(self, signal = True):
451
oldstate = getattr(self, "enabled", False)
452
r = Client.disable(self)
453
if signal and oldstate != self.enabled:
455
self.PropertyChanged(dbus.String(u"enabled"),
456
dbus.Boolean(False, variant_level=1))
1053
("/clients/" + client_object_name))
1054
DBusObjectWithProperties.__init__(self, self.bus,
1055
self.dbus_object_path)
1057
def notifychangeproperty(transform_func,
1058
dbus_name, type_func=lambda x: x,
1060
""" Modify a variable so that it's a property which announces
1061
its changes to DBus.
1063
transform_fun: Function that takes a value and a variant_level
1064
and transforms it to a D-Bus type.
1065
dbus_name: D-Bus name of the variable
1066
type_func: Function that transform the value before sending it
1067
to the D-Bus. Default: no transform
1068
variant_level: D-Bus variant level. Default: 1
1070
attrname = "_{0}".format(dbus_name)
1071
def setter(self, value):
1072
if hasattr(self, "dbus_object_path"):
1073
if (not hasattr(self, attrname) or
1074
type_func(getattr(self, attrname, None))
1075
!= type_func(value)):
1076
dbus_value = transform_func(type_func(value),
1079
self.PropertyChanged(dbus.String(dbus_name),
1081
setattr(self, attrname, value)
1083
return property(lambda self: getattr(self, attrname), setter)
1086
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1087
approvals_pending = notifychangeproperty(dbus.Boolean,
1090
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1091
last_enabled = notifychangeproperty(datetime_to_dbus,
1093
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1094
type_func = lambda checker:
1095
checker is not None)
1096
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1098
last_approval_request = notifychangeproperty(
1099
datetime_to_dbus, "LastApprovalRequest")
1100
approved_by_default = notifychangeproperty(dbus.Boolean,
1101
"ApprovedByDefault")
1102
approval_delay = notifychangeproperty(dbus.UInt64,
1105
timedelta_to_milliseconds)
1106
approval_duration = notifychangeproperty(
1107
dbus.UInt64, "ApprovalDuration",
1108
type_func = timedelta_to_milliseconds)
1109
host = notifychangeproperty(dbus.String, "Host")
1110
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1112
timedelta_to_milliseconds)
1113
extended_timeout = notifychangeproperty(
1114
dbus.UInt64, "ExtendedTimeout",
1115
type_func = timedelta_to_milliseconds)
1116
interval = notifychangeproperty(dbus.UInt64,
1119
timedelta_to_milliseconds)
1120
checker_command = notifychangeproperty(dbus.String, "Checker")
1122
del notifychangeproperty
459
1124
def __del__(self, *args, **kwargs):
461
1126
self.remove_from_connection()
462
except org.freedesktop.DBus.Python.LookupError:
464
dbus.service.Object.__del__(self, *args, **kwargs)
1129
if hasattr(DBusObjectWithProperties, "__del__"):
1130
DBusObjectWithProperties.__del__(self, *args, **kwargs)
465
1131
Client.__del__(self, *args, **kwargs)
467
1133
def checker_callback(self, pid, condition, command,
468
1134
*args, **kwargs):
469
1135
self.checker_callback_tag = None
470
1136
self.checker = None
472
self.PropertyChanged(dbus.String(u"checker_running"),
473
dbus.Boolean(False, variant_level=1))
474
1137
if os.WIFEXITED(condition):
475
1138
exitstatus = os.WEXITSTATUS(condition)
476
1139
# Emit D-Bus signal
543
# GetAllProperties - method
544
@dbus.service.method(_interface, out_signature="a{sv}")
545
def GetAllProperties(self):
547
return dbus.Dictionary({
549
dbus.String(self.name, variant_level=1),
550
dbus.String("fingerprint"):
551
dbus.String(self.fingerprint, variant_level=1),
553
dbus.String(self.host, variant_level=1),
554
dbus.String("created"):
555
_datetime_to_dbus(self.created, variant_level=1),
556
dbus.String("last_enabled"):
557
(_datetime_to_dbus(self.last_enabled,
559
if self.last_enabled is not None
560
else dbus.Boolean(False, variant_level=1)),
561
dbus.String("enabled"):
562
dbus.Boolean(self.enabled, variant_level=1),
563
dbus.String("last_checked_ok"):
564
(_datetime_to_dbus(self.last_checked_ok,
566
if self.last_checked_ok is not None
567
else dbus.Boolean (False, variant_level=1)),
568
dbus.String("timeout"):
569
dbus.UInt64(self.timeout_milliseconds(),
571
dbus.String("interval"):
572
dbus.UInt64(self.interval_milliseconds(),
574
dbus.String("checker"):
575
dbus.String(self.checker_command,
577
dbus.String("checker_running"):
578
dbus.Boolean(self.checker is not None,
580
dbus.String("object_path"):
581
dbus.ObjectPath(self.dbus_object_path,
585
# IsStillValid - method
586
@dbus.service.method(_interface, out_signature="b")
587
def IsStillValid(self):
588
return self.still_valid()
590
1195
# PropertyChanged - signal
591
1196
@dbus.service.signal(_interface, signature="sv")
592
1197
def PropertyChanged(self, property, value):
596
# ReceivedSecret - signal
1201
# GotSecret - signal
597
1202
@dbus.service.signal(_interface)
598
def ReceivedSecret(self):
1203
def GotSecret(self):
1205
Is sent after a successful transfer of secret from the Mandos
1206
server to mandos-client
602
1210
# Rejected - signal
603
@dbus.service.signal(_interface)
608
# SetChecker - method
609
@dbus.service.method(_interface, in_signature="s")
610
def SetChecker(self, checker):
611
"D-Bus setter method"
612
self.checker_command = checker
614
self.PropertyChanged(dbus.String(u"checker"),
615
dbus.String(self.checker_command,
619
@dbus.service.method(_interface, in_signature="s")
620
def SetHost(self, host):
621
"D-Bus setter method"
624
self.PropertyChanged(dbus.String(u"host"),
625
dbus.String(self.host, variant_level=1))
627
# SetInterval - method
628
@dbus.service.method(_interface, in_signature="t")
629
def SetInterval(self, milliseconds):
630
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
632
self.PropertyChanged(dbus.String(u"interval"),
633
(dbus.UInt64(self.interval_milliseconds(),
637
@dbus.service.method(_interface, in_signature="ay",
639
def SetSecret(self, secret):
640
"D-Bus setter method"
641
self.secret = str(secret)
643
# SetTimeout - method
644
@dbus.service.method(_interface, in_signature="t")
645
def SetTimeout(self, milliseconds):
646
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
648
self.PropertyChanged(dbus.String(u"timeout"),
649
(dbus.UInt64(self.timeout_milliseconds(),
1211
@dbus.service.signal(_interface, signature="s")
1212
def Rejected(self, reason):
1216
# NeedApproval - signal
1217
@dbus.service.signal(_interface, signature="tb")
1218
def NeedApproval(self, timeout, default):
1220
return self.need_approval()
1222
# NeRwequest - signal
1223
@dbus.service.signal(_interface, signature="s")
1224
def NewRequest(self, ip):
1226
Is sent after a client request a password.
1233
@dbus.service.method(_interface, in_signature="b")
1234
def Approve(self, value):
1237
# CheckedOK - method
1238
@dbus.service.method(_interface)
1239
def CheckedOK(self):
652
1242
# Enable - method
653
Enable = dbus.service.method(_interface)(enable)
654
Enable.__name__ = "Enable"
1243
@dbus.service.method(_interface)
656
1248
# StartChecker - method
657
1249
@dbus.service.method(_interface)
668
1260
# StopChecker - method
669
StopChecker = dbus.service.method(_interface)(stop_checker)
670
StopChecker.__name__ = "StopChecker"
1261
@dbus.service.method(_interface)
1262
def StopChecker(self):
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):
1300
return dbus.String(self.name)
1302
# Fingerprint - property
1303
@dbus_service_property(_interface, signature="s", access="read")
1304
def Fingerprint_dbus_property(self):
1305
return dbus.String(self.fingerprint)
1308
@dbus_service_property(_interface, signature="s",
1310
def Host_dbus_property(self, value=None):
1311
if value is None: # get
1312
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):
1329
if value is None: # get
1330
return dbus.Boolean(self.enabled)
1336
# LastCheckedOK - property
1337
@dbus_service_property(_interface, signature="s",
1339
def LastCheckedOK_dbus_property(self, value=None):
1340
if value is not None:
1343
return datetime_to_dbus(self.last_checked_ok)
1345
# Expires - property
1346
@dbus_service_property(_interface, signature="s", access="read")
1347
def Expires_dbus_property(self):
1348
return datetime_to_dbus(self.expires)
1350
# LastApprovalRequest - property
1351
@dbus_service_property(_interface, signature="s", access="read")
1352
def LastApprovalRequest_dbus_property(self):
1353
return datetime_to_dbus(self.last_approval_request)
1355
# Timeout - property
1356
@dbus_service_property(_interface, signature="t",
1358
def Timeout_dbus_property(self, value=None):
1359
if value is None: # get
1360
return dbus.UInt64(self.timeout_milliseconds())
1361
self.timeout = datetime.timedelta(0, 0, 0, value)
1362
if getattr(self, "disable_initiator_tag", None) is None:
1364
# Reschedule timeout
1365
gobject.source_remove(self.disable_initiator_tag)
1366
self.disable_initiator_tag = None
1368
time_to_die = timedelta_to_milliseconds((self
1373
if time_to_die <= 0:
1374
# The timeout has passed
1377
self.expires = (datetime.datetime.utcnow()
1378
+ datetime.timedelta(milliseconds =
1380
self.disable_initiator_tag = (gobject.timeout_add
1381
(time_to_die, self.disable))
1383
# ExtendedTimeout - property
1384
@dbus_service_property(_interface, signature="t",
1386
def ExtendedTimeout_dbus_property(self, value=None):
1387
if value is None: # get
1388
return dbus.UInt64(self.extended_timeout_milliseconds())
1389
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1391
# Interval - property
1392
@dbus_service_property(_interface, signature="t",
1394
def Interval_dbus_property(self, value=None):
1395
if value is None: # get
1396
return dbus.UInt64(self.interval_milliseconds())
1397
self.interval = datetime.timedelta(0, 0, 0, value)
1398
if getattr(self, "checker_initiator_tag", None) is None:
1401
# Reschedule checker run
1402
gobject.source_remove(self.checker_initiator_tag)
1403
self.checker_initiator_tag = (gobject.timeout_add
1404
(value, self.start_checker))
1405
self.start_checker() # Start one now, too
1407
# Checker - property
1408
@dbus_service_property(_interface, signature="s",
1410
def Checker_dbus_property(self, value=None):
1411
if value is None: # get
1412
return dbus.String(self.checker_command)
1413
self.checker_command = unicode(value)
1415
# CheckerRunning - property
1416
@dbus_service_property(_interface, signature="b",
1418
def CheckerRunning_dbus_property(self, value=None):
1419
if value is None: # get
1420
return dbus.Boolean(self.checker is not None)
1422
self.start_checker()
1426
# ObjectPath - property
1427
@dbus_service_property(_interface, signature="o", access="read")
1428
def ObjectPath_dbus_property(self):
1429
return self.dbus_object_path # is already a dbus.ObjectPath
1432
@dbus_service_property(_interface, signature="ay",
1433
access="write", byte_arrays=True)
1434
def Secret_dbus_property(self, value):
1435
self.secret = str(value)
675
def peer_certificate(session):
676
"Return the peer's OpenPGP certificate as a bytestring"
677
# If not an OpenPGP certificate...
678
if (gnutls.library.functions
679
.gnutls_certificate_type_get(session._c_object)
680
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
681
# ...do the normal thing
682
return session.peer_certificate
683
list_size = ctypes.c_uint(1)
684
cert_list = (gnutls.library.functions
685
.gnutls_certificate_get_peers
686
(session._c_object, ctypes.byref(list_size)))
687
if not bool(cert_list) and list_size.value != 0:
688
raise gnutls.errors.GNUTLSError("error getting peer"
690
if list_size.value == 0:
693
return ctypes.string_at(cert.data, cert.size)
696
def fingerprint(openpgp):
697
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
698
# New GnuTLS "datum" with the OpenPGP public key
699
datum = (gnutls.library.types
700
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
703
ctypes.c_uint(len(openpgp))))
704
# New empty GnuTLS certificate
705
crt = gnutls.library.types.gnutls_openpgp_crt_t()
706
(gnutls.library.functions
707
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
708
# Import the OpenPGP public key into the certificate
709
(gnutls.library.functions
710
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
711
gnutls.library.constants
712
.GNUTLS_OPENPGP_FMT_RAW))
713
# Verify the self signature in the key
714
crtverify = ctypes.c_uint()
715
(gnutls.library.functions
716
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
717
if crtverify.value != 0:
718
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
719
raise gnutls.errors.CertificateSecurityError("Verify failed")
720
# New buffer for the fingerprint
721
buf = ctypes.create_string_buffer(20)
722
buf_len = ctypes.c_size_t()
723
# Get the fingerprint from the certificate into the buffer
724
(gnutls.library.functions
725
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
726
ctypes.byref(buf_len)))
727
# Deinit the certificate
728
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
729
# Convert the buffer to a Python bytestring
730
fpr = ctypes.string_at(buf, buf_len.value)
731
# Convert the bytestring to hexadecimal notation
732
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
736
class TCP_handler(SocketServer.BaseRequestHandler, object):
737
"""A TCP request handler class.
738
Instantiated by IPv6_TCPServer for each request to handle it.
1440
class ProxyClient(object):
1441
def __init__(self, child_pipe, fpr, address):
1442
self._pipe = child_pipe
1443
self._pipe.send(('init', fpr, address))
1444
if not self._pipe.recv():
1447
def __getattribute__(self, name):
1449
return super(ProxyClient, self).__getattribute__(name)
1450
self._pipe.send(('getattr', name))
1451
data = self._pipe.recv()
1452
if data[0] == 'data':
1454
if data[0] == 'function':
1455
def func(*args, **kwargs):
1456
self._pipe.send(('funcall', name, args, kwargs))
1457
return self._pipe.recv()[1]
1460
def __setattr__(self, name, value):
1462
return super(ProxyClient, self).__setattr__(name, value)
1463
self._pipe.send(('setattr', name, value))
1466
class ClientDBusTransitional(ClientDBus):
1467
__metaclass__ = AlternateDBusNamesMetaclass
1470
class ClientHandler(socketserver.BaseRequestHandler, object):
1471
"""A class to handle client connections.
1473
Instantiated once for each connection to handle it.
739
1474
Note: This will run in its own forked process."""
741
1476
def handle(self):
742
logger.info(u"TCP connection from: %s",
743
unicode(self.client_address))
744
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
745
# Open IPC pipe to parent process
746
with closing(os.fdopen(self.server.pipe[1], "w", 1)) as ipc:
1477
with contextlib.closing(self.server.child_pipe) as child_pipe:
1478
logger.info("TCP connection from: %s",
1479
unicode(self.client_address))
1480
logger.debug("Pipe FD: %d",
1481
self.server.child_pipe.fileno())
747
1483
session = (gnutls.connection
748
1484
.ClientSession(self.request,
749
1485
gnutls.connection
750
1486
.X509Credentials()))
752
line = self.request.makefile().readline()
753
logger.debug(u"Protocol version: %r", line)
755
if int(line.strip().split()[0]) > 1:
757
except (ValueError, IndexError, RuntimeError), error:
758
logger.error(u"Unknown protocol version: %s", error)
761
1488
# Note: gnutls.connection.X509Credentials is really a
762
1489
# generic GnuTLS certificate credentials object so long as
763
1490
# no X.509 keys are added to it. Therefore, we can use it
764
1491
# here despite using OpenPGP certificates.
766
1493
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
767
# "+AES-256-CBC", "+SHA1",
768
# "+COMP-NULL", "+CTYPE-OPENPGP",
1494
# "+AES-256-CBC", "+SHA1",
1495
# "+COMP-NULL", "+CTYPE-OPENPGP",
770
1497
# Use a fallback default, since this MUST be set.
771
priority = self.server.settings.get("priority", "NORMAL")
1498
priority = self.server.gnutls_priority
1499
if priority is None:
772
1501
(gnutls.library.functions
773
1502
.gnutls_priority_set_direct(session._c_object,
774
1503
priority, None))
1505
# Start communication using the Mandos protocol
1506
# Get protocol number
1507
line = self.request.makefile().readline()
1508
logger.debug("Protocol version: %r", line)
1510
if int(line.strip().split()[0]) > 1:
1512
except (ValueError, IndexError, RuntimeError) as error:
1513
logger.error("Unknown protocol version: %s", error)
1516
# Start GnuTLS connection
777
1518
session.handshake()
778
except gnutls.errors.GNUTLSError, error:
779
logger.warning(u"Handshake failed: %s", error)
1519
except gnutls.errors.GNUTLSError as error:
1520
logger.warning("Handshake failed: %s", error)
780
1521
# Do not run session.bye() here: the session is not
781
1522
# established. Just abandon the request.
783
logger.debug(u"Handshake succeeded")
1524
logger.debug("Handshake succeeded")
1526
approval_required = False
785
fpr = fingerprint(peer_certificate(session))
786
except (TypeError, gnutls.errors.GNUTLSError), error:
787
logger.warning(u"Bad certificate: %s", error)
790
logger.debug(u"Fingerprint: %s", fpr)
1529
fpr = self.fingerprint(self.peer_certificate
1532
gnutls.errors.GNUTLSError) as error:
1533
logger.warning("Bad certificate: %s", error)
1535
logger.debug("Fingerprint: %s", fpr)
1538
client = ProxyClient(child_pipe, fpr,
1539
self.client_address)
1543
if self.server.use_dbus:
1545
client.NewRequest(str(self.client_address))
1547
if client.approval_delay:
1548
delay = client.approval_delay
1549
client.approvals_pending += 1
1550
approval_required = True
1553
if not client.enabled:
1554
logger.info("Client %s is disabled",
1556
if self.server.use_dbus:
1558
client.Rejected("Disabled")
1561
if client.approved or not client.approval_delay:
1562
#We are approved or approval is disabled
1564
elif client.approved is None:
1565
logger.info("Client %s needs approval",
1567
if self.server.use_dbus:
1569
client.NeedApproval(
1570
client.approval_delay_milliseconds(),
1571
client.approved_by_default)
1573
logger.warning("Client %s was not approved",
1575
if self.server.use_dbus:
1577
client.Rejected("Denied")
1580
#wait until timeout or approved
1581
time = datetime.datetime.now()
1582
client.changedstate.acquire()
1583
(client.changedstate.wait
1584
(float(client.timedelta_to_milliseconds(delay)
1586
client.changedstate.release()
1587
time2 = datetime.datetime.now()
1588
if (time2 - time) >= delay:
1589
if not client.approved_by_default:
1590
logger.warning("Client %s timed out while"
1591
" waiting for approval",
1593
if self.server.use_dbus:
1595
client.Rejected("Approval timed out")
1600
delay -= time2 - time
1603
while sent_size < len(client.secret):
1605
sent = session.send(client.secret[sent_size:])
1606
except gnutls.errors.GNUTLSError as error:
1607
logger.warning("gnutls send failed")
1609
logger.debug("Sent: %d, remaining: %d",
1610
sent, len(client.secret)
1611
- (sent_size + sent))
1614
logger.info("Sending secret to %s", client.name)
1615
# bump the timeout using extended_timeout
1616
client.checked_ok(client.extended_timeout)
1617
if self.server.use_dbus:
792
for c in self.server.clients:
793
if c.fingerprint == fpr:
797
logger.warning(u"Client not found for fingerprint: %s",
799
ipc.write("NOTFOUND %s\n" % fpr)
802
# Have to check if client.still_valid(), since it is
803
# possible that the client timed out while establishing
804
# the GnuTLS session.
805
if not client.still_valid():
806
logger.warning(u"Client %(name)s is invalid",
808
ipc.write("INVALID %s\n" % client.name)
811
ipc.write("SENDING %s\n" % client.name)
813
while sent_size < len(client.secret):
814
sent = session.send(client.secret[sent_size:])
815
logger.debug(u"Sent: %d, remaining: %d",
816
sent, len(client.secret)
817
- (sent_size + sent))
822
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
823
"""Like SocketServer.ForkingMixIn, but also pass a pipe.
824
Assumes a gobject.MainLoop event loop.
1622
if approval_required:
1623
client.approvals_pending -= 1
1626
except gnutls.errors.GNUTLSError as error:
1627
logger.warning("GnuTLS bye failed")
1630
def peer_certificate(session):
1631
"Return the peer's OpenPGP certificate as a bytestring"
1632
# If not an OpenPGP certificate...
1633
if (gnutls.library.functions
1634
.gnutls_certificate_type_get(session._c_object)
1635
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1636
# ...do the normal thing
1637
return session.peer_certificate
1638
list_size = ctypes.c_uint(1)
1639
cert_list = (gnutls.library.functions
1640
.gnutls_certificate_get_peers
1641
(session._c_object, ctypes.byref(list_size)))
1642
if not bool(cert_list) and list_size.value != 0:
1643
raise gnutls.errors.GNUTLSError("error getting peer"
1645
if list_size.value == 0:
1648
return ctypes.string_at(cert.data, cert.size)
1651
def fingerprint(openpgp):
1652
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1653
# New GnuTLS "datum" with the OpenPGP public key
1654
datum = (gnutls.library.types
1655
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1658
ctypes.c_uint(len(openpgp))))
1659
# New empty GnuTLS certificate
1660
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1661
(gnutls.library.functions
1662
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1663
# Import the OpenPGP public key into the certificate
1664
(gnutls.library.functions
1665
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1666
gnutls.library.constants
1667
.GNUTLS_OPENPGP_FMT_RAW))
1668
# Verify the self signature in the key
1669
crtverify = ctypes.c_uint()
1670
(gnutls.library.functions
1671
.gnutls_openpgp_crt_verify_self(crt, 0,
1672
ctypes.byref(crtverify)))
1673
if crtverify.value != 0:
1674
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1675
raise (gnutls.errors.CertificateSecurityError
1677
# New buffer for the fingerprint
1678
buf = ctypes.create_string_buffer(20)
1679
buf_len = ctypes.c_size_t()
1680
# Get the fingerprint from the certificate into the buffer
1681
(gnutls.library.functions
1682
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1683
ctypes.byref(buf_len)))
1684
# Deinit the certificate
1685
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1686
# Convert the buffer to a Python bytestring
1687
fpr = ctypes.string_at(buf, buf_len.value)
1688
# Convert the bytestring to hexadecimal notation
1689
hex_fpr = binascii.hexlify(fpr).upper()
1693
class MultiprocessingMixIn(object):
1694
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1695
def sub_process_main(self, request, address):
1697
self.finish_request(request, address)
1699
self.handle_error(request, address)
1700
self.close_request(request)
1702
def process_request(self, request, address):
1703
"""Start a new process to process the request."""
1704
proc = multiprocessing.Process(target = self.sub_process_main,
1711
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1712
""" adds a pipe to the MixIn """
826
1713
def process_request(self, request, client_address):
827
"""This overrides and wraps the original process_request().
828
This function creates a new pipe in self.pipe
1714
"""Overrides and wraps the original process_request().
1716
This function creates a new pipe in self.pipe
830
self.pipe = os.pipe()
831
super(ForkingMixInWithPipe,
832
self).process_request(request, client_address)
833
os.close(self.pipe[1]) # close write end
834
# Call "handle_ipc" for both data and EOF events
835
gobject.io_add_watch(self.pipe[0],
836
gobject.IO_IN | gobject.IO_HUP,
838
def handle_ipc(source, condition):
1718
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1720
proc = MultiprocessingMixIn.process_request(self, request,
1722
self.child_pipe.close()
1723
self.add_pipe(parent_pipe, proc)
1725
def add_pipe(self, parent_pipe, proc):
839
1726
"""Dummy function; override as necessary"""
844
class IPv6_TCPServer(ForkingMixInWithPipe,
845
SocketServer.TCPServer, object):
1727
raise NotImplementedError
1730
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1731
socketserver.TCPServer, object):
846
1732
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
848
settings: Server settings
849
clients: Set() of Client objects
850
1735
enabled: Boolean; whether this server is activated yet
1736
interface: None or a network interface name (string)
1737
use_ipv6: Boolean; to use IPv6 or not
852
address_family = socket.AF_INET6
853
def __init__(self, *args, **kwargs):
854
if "settings" in kwargs:
855
self.settings = kwargs["settings"]
856
del kwargs["settings"]
857
if "clients" in kwargs:
858
self.clients = kwargs["clients"]
859
del kwargs["clients"]
860
if "use_ipv6" in kwargs:
861
if not kwargs["use_ipv6"]:
862
self.address_family = socket.AF_INET
863
del kwargs["use_ipv6"]
865
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1739
def __init__(self, server_address, RequestHandlerClass,
1740
interface=None, use_ipv6=True):
1741
self.interface = interface
1743
self.address_family = socket.AF_INET6
1744
socketserver.TCPServer.__init__(self, server_address,
1745
RequestHandlerClass)
866
1746
def server_bind(self):
867
1747
"""This overrides the normal server_bind() function
868
1748
to bind to an interface if one was specified, and also NOT to
869
1749
bind to an address or port if they were not specified."""
870
if self.settings["interface"]:
871
# 25 is from /usr/include/asm-i486/socket.h
872
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
874
self.socket.setsockopt(socket.SOL_SOCKET,
876
self.settings["interface"])
877
except socket.error, error:
878
if error[0] == errno.EPERM:
879
logger.error(u"No permission to"
880
u" bind to interface %s",
881
self.settings["interface"])
1750
if self.interface is not None:
1751
if SO_BINDTODEVICE is None:
1752
logger.error("SO_BINDTODEVICE does not exist;"
1753
" cannot bind to interface %s",
1757
self.socket.setsockopt(socket.SOL_SOCKET,
1761
except socket.error as error:
1762
if error[0] == errno.EPERM:
1763
logger.error("No permission to"
1764
" bind to interface %s",
1766
elif error[0] == errno.ENOPROTOOPT:
1767
logger.error("SO_BINDTODEVICE not available;"
1768
" cannot bind to interface %s",
884
1772
# Only bind(2) the socket if we really need to.
885
1773
if self.server_address[0] or self.server_address[1]:
886
1774
if not self.server_address[0]:
893
1781
elif not self.server_address[1]:
894
1782
self.server_address = (self.server_address[0],
896
# if self.settings["interface"]:
1784
# if self.interface:
897
1785
# self.server_address = (self.server_address[0],
900
1788
# if_nametoindex
903
return super(IPv6_TCPServer, self).server_bind()
1790
return socketserver.TCPServer.server_bind(self)
1793
class MandosServer(IPv6_TCPServer):
1797
clients: set of Client objects
1798
gnutls_priority GnuTLS priority string
1799
use_dbus: Boolean; to emit D-Bus signals or not
1801
Assumes a gobject.MainLoop event loop.
1803
def __init__(self, server_address, RequestHandlerClass,
1804
interface=None, use_ipv6=True, clients=None,
1805
gnutls_priority=None, use_dbus=True):
1806
self.enabled = False
1807
self.clients = clients
1808
if self.clients is None:
1810
self.use_dbus = use_dbus
1811
self.gnutls_priority = gnutls_priority
1812
IPv6_TCPServer.__init__(self, server_address,
1813
RequestHandlerClass,
1814
interface = interface,
1815
use_ipv6 = use_ipv6)
904
1816
def server_activate(self):
905
1817
if self.enabled:
906
return super(IPv6_TCPServer, self).server_activate()
1818
return socketserver.TCPServer.server_activate(self)
907
1820
def enable(self):
908
1821
self.enabled = True
909
def handle_ipc(self, source, condition, file_objects={}):
1823
def add_pipe(self, parent_pipe, proc):
1824
# Call "handle_ipc" for both data and EOF events
1825
gobject.io_add_watch(parent_pipe.fileno(),
1826
gobject.IO_IN | gobject.IO_HUP,
1827
functools.partial(self.handle_ipc,
1832
def handle_ipc(self, source, condition, parent_pipe=None,
1833
proc = None, client_object=None):
910
1834
condition_names = {
911
gobject.IO_IN: "IN", # There is data to read.
1835
gobject.IO_IN: "IN", # There is data to read.
912
1836
gobject.IO_OUT: "OUT", # Data can be written (without
914
1838
gobject.IO_PRI: "PRI", # There is urgent data to read.
915
1839
gobject.IO_ERR: "ERR", # Error condition.
916
1840
gobject.IO_HUP: "HUP" # Hung up (the connection has been
917
# broken, usually for pipes and
1841
# broken, usually for pipes and
920
1844
conditions_string = ' | '.join(name
921
1845
for cond, name in
922
1846
condition_names.iteritems()
923
1847
if cond & condition)
924
logger.debug("Handling IPC: FD = %d, condition = %s", source,
927
# Turn the pipe file descriptor into a Python file object
928
if source not in file_objects:
929
file_objects[source] = os.fdopen(source, "r", 1)
931
# Read a line from the file object
932
cmdline = file_objects[source].readline()
933
if not cmdline: # Empty line means end of file
935
file_objects[source].close()
936
del file_objects[source]
1848
# error, or the other end of multiprocessing.Pipe has closed
1849
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1850
# Wait for other process to exit
1854
# Read a request from the child
1855
request = parent_pipe.recv()
1856
command = request[0]
1858
if command == 'init':
1860
address = request[2]
938
# Stop calling this function
941
logger.debug("IPC command: %r\n" % cmdline)
943
# Parse and act on command
944
cmd, args = cmdline.split(None, 1)
945
if cmd == "NOTFOUND":
946
if self.settings["use_dbus"]:
948
mandos_dbus_service.ClientNotFound(args)
949
elif cmd == "INVALID":
950
if self.settings["use_dbus"]:
951
for client in self.clients:
952
if client.name == args:
956
elif cmd == "SENDING":
957
for client in self.clients:
958
if client.name == args:
960
if self.settings["use_dbus"]:
962
client.ReceivedSecret()
1862
for c in self.clients.itervalues():
1863
if c.fingerprint == fpr:
965
logger.error("Unknown IPC command: %r", cmdline)
967
# Keep calling this function
1867
logger.info("Client not found for fingerprint: %s, ad"
1868
"dress: %s", fpr, address)
1871
mandos_dbus_service.ClientNotFound(fpr,
1873
parent_pipe.send(False)
1876
gobject.io_add_watch(parent_pipe.fileno(),
1877
gobject.IO_IN | gobject.IO_HUP,
1878
functools.partial(self.handle_ipc,
1884
parent_pipe.send(True)
1885
# remove the old hook in favor of the new above hook on
1888
if command == 'funcall':
1889
funcname = request[1]
1893
parent_pipe.send(('data', getattr(client_object,
1897
if command == 'getattr':
1898
attrname = request[1]
1899
if callable(client_object.__getattribute__(attrname)):
1900
parent_pipe.send(('function',))
1902
parent_pipe.send(('data', client_object
1903
.__getattribute__(attrname)))
1905
if command == 'setattr':
1906
attrname = request[1]
1908
setattr(client_object, attrname, value)
1231
2153
(gnutls.library.functions
1232
2154
.gnutls_global_set_log_function(debug_gnutls))
1235
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1236
service = AvahiService(name = server_settings["servicename"],
1237
servicetype = "_mandos._tcp",
1238
protocol = protocol)
1239
if server_settings["interface"]:
1240
service.interface = (if_nametoindex
1241
(server_settings["interface"]))
2156
# Redirect stdin so all checkers get /dev/null
2157
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2158
os.dup2(null, sys.stdin.fileno())
2162
# Need to fork before connecting to D-Bus
2164
# Close all input and output, do double fork, etc.
2167
gobject.threads_init()
1243
2169
global main_loop
1246
2170
# From the Avahi example code
1247
2171
DBusGMainLoop(set_as_default=True )
1248
2172
main_loop = gobject.MainLoop()
1249
2173
bus = dbus.SystemBus()
1250
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1251
avahi.DBUS_PATH_SERVER),
1252
avahi.DBUS_INTERFACE_SERVER)
1253
2174
# End of Avahi example code
1255
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
2177
bus_name = dbus.service.BusName("se.recompile.Mandos",
2178
bus, do_not_queue=True)
2179
old_bus_name = (dbus.service.BusName
2180
("se.bsnet.fukt.Mandos", bus,
2182
except dbus.exceptions.NameExistsException as e:
2183
logger.error(unicode(e) + ", disabling D-Bus")
2185
server_settings["use_dbus"] = False
2186
tcp_server.use_dbus = False
2187
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2188
service = AvahiServiceToSyslog(name =
2189
server_settings["servicename"],
2190
servicetype = "_mandos._tcp",
2191
protocol = protocol, bus = bus)
2192
if server_settings["interface"]:
2193
service.interface = (if_nametoindex
2194
(str(server_settings["interface"])))
2196
global multiprocessing_manager
2197
multiprocessing_manager = multiprocessing.Manager()
1257
2199
client_class = Client
1259
client_class = ClientDBus
1261
client_class(name = section,
1262
config= dict(client_config.items(section)))
1263
for section in client_config.sections()))
1265
logger.warning(u"No clients defined")
1268
# Redirect stdin so all checkers get /dev/null
1269
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1270
os.dup2(null, sys.stdin.fileno())
1274
# No console logging
1275
logger.removeHandler(console)
1276
# Close all input and output, do double fork, etc.
1280
with closing(pidfile):
1282
pidfile.write(str(pid) + "\n")
1285
logger.error(u"Could not write to file %r with PID %d",
1288
# "pidfile" was never created
1293
"Cleanup function; run on exit"
1295
# From the Avahi example code
1296
if not group is None:
1299
# End of Avahi example code
2201
client_class = functools.partial(ClientDBusTransitional,
2204
client_settings = Client.config_parser(client_config)
2205
old_client_settings = {}
2208
# Get client data and settings from last running state.
2209
if server_settings["restore"]:
2211
with open(stored_state_path, "rb") as stored_state:
2212
clients_data, old_client_settings = (pickle.load
2214
os.remove(stored_state_path)
2215
except IOError as e:
2216
logger.warning("Could not load persistent state: {0}"
2218
if e.errno != errno.ENOENT:
2220
except EOFError as e:
2221
logger.warning("Could not load persistent state: "
2222
"EOFError: {0}".format(e))
2224
with PGPEngine() as pgp:
2225
for client_name, client in clients_data.iteritems():
2226
# Decide which value to use after restoring saved state.
2227
# We have three different values: Old config file,
2228
# new config file, and saved state.
2229
# New config value takes precedence if it differs from old
2230
# config value, otherwise use saved state.
2231
for name, value in client_settings[client_name].items():
2233
# For each value in new config, check if it
2234
# differs from the old config value (Except for
2235
# the "secret" attribute)
2236
if (name != "secret" and
2237
value != old_client_settings[client_name]
2239
client[name] = value
2243
# Clients who has passed its expire date can still be
2244
# enabled if its last checker was successful. Clients
2245
# whose checker failed before we stored its state is
2246
# assumed to have failed all checkers during downtime.
2247
if client["enabled"]:
2248
if datetime.datetime.utcnow() >= client["expires"]:
2249
if not client["last_checked_ok"]:
2251
"disabling client {0} - Client never "
2252
"performed a successfull checker"
2253
.format(client["name"]))
2254
client["enabled"] = False
2255
elif client["last_checker_status"] != 0:
2257
"disabling client {0} - Client "
2258
"last checker failed with error code {1}"
2259
.format(client["name"],
2260
client["last_checker_status"]))
2261
client["enabled"] = False
2263
client["expires"] = (datetime.datetime
2265
+ client["timeout"])
2268
client["secret"] = (
2269
pgp.decrypt(client["encrypted_secret"],
2270
client_settings[client_name]
2273
# If decryption fails, we use secret from new settings
2274
logger.debug("Failed to decrypt {0} old secret"
2275
.format(client_name))
2276
client["secret"] = (
2277
client_settings[client_name]["secret"])
2280
# Add/remove clients based on new changes made to config
2281
for client_name in set(old_client_settings) - set(client_settings):
2282
del clients_data[client_name]
2283
for client_name in set(client_settings) - set(old_client_settings):
2284
clients_data[client_name] = client_settings[client_name]
2286
# Create clients all clients
2287
for client_name, client in clients_data.iteritems():
2288
tcp_server.clients[client_name] = client_class(
2289
name = client_name, settings = client)
2291
if not tcp_server.clients:
2292
logger.warning("No clients defined")
1302
client = clients.pop()
1303
client.disable_hook = None
1306
atexit.register(cleanup)
2298
pidfile.write(str(pid) + "\n".encode("utf-8"))
2301
logger.error("Could not write to file %r with PID %d",
2304
# "pidfile" was never created
1309
2307
signal.signal(signal.SIGINT, signal.SIG_IGN)
1310
2309
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1311
2310
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())