124
149
self.rename_count = 0
125
150
self.max_renames = max_renames
151
self.protocol = protocol
152
self.group = None # our entry group
155
self.entry_group_state_changed_match = None
126
156
def rename(self):
127
157
"""Derived from the Avahi example code"""
128
158
if self.rename_count >= self.max_renames:
129
logger.critical(u"No suitable Zeroconf service name found"
130
u" after %i retries, exiting.",
159
logger.critical("No suitable Zeroconf service name found"
160
" after %i retries, exiting.",
131
161
self.rename_count)
132
raise AvahiServiceError(u"Too many renames")
133
self.name = server.GetAlternativeServiceName(self.name)
134
logger.info(u"Changing Zeroconf service name to %r ...",
162
raise AvahiServiceError("Too many renames")
163
self.name = unicode(self.server.GetAlternativeServiceName(self.name))
164
logger.info("Changing Zeroconf service name to %r ...",
136
166
syslogger.setFormatter(logging.Formatter
137
('Mandos (%s): %%(levelname)s:'
138
' %%(message)s' % self.name))
167
('Mandos (%s) [%%(process)d]:'
168
' %%(levelname)s: %%(message)s'
173
except dbus.exceptions.DBusException as error:
174
logger.critical("DBusException: %s", error)
141
177
self.rename_count += 1
142
178
def remove(self):
143
179
"""Derived from the Avahi example code"""
144
if group is not None:
180
if self.entry_group_state_changed_match is not None:
181
self.entry_group_state_changed_match.remove()
182
self.entry_group_state_changed_match = None
183
if self.group is not None:
147
186
"""Derived from the Avahi example code"""
150
group = dbus.Interface(bus.get_object
152
server.EntryGroupNew()),
153
avahi.DBUS_INTERFACE_ENTRY_GROUP)
154
group.connect_to_signal('StateChanged',
155
entry_group_state_changed)
156
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
157
service.name, service.type)
159
self.interface, # interface
160
avahi.PROTO_INET6, # protocol
161
dbus.UInt32(0), # flags
162
self.name, self.type,
163
self.domain, self.host,
164
dbus.UInt16(self.port),
165
avahi.string_array_to_txt_array(self.TXT))
168
# From the Avahi example code:
169
group = None # our entry group
170
# End of Avahi example code
173
def _datetime_to_dbus(dt, variant_level=0):
174
"""Convert a UTC datetime.datetime() to a D-Bus type."""
175
return dbus.String(dt.isoformat(), variant_level=variant_level)
178
class Client(dbus.service.Object):
188
if self.group is None:
189
self.group = dbus.Interface(
190
self.bus.get_object(avahi.DBUS_NAME,
191
self.server.EntryGroupNew()),
192
avahi.DBUS_INTERFACE_ENTRY_GROUP)
193
self.entry_group_state_changed_match = (
194
self.group.connect_to_signal(
195
'StateChanged', self .entry_group_state_changed))
196
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
197
self.name, self.type)
198
self.group.AddService(
201
dbus.UInt32(0), # flags
202
self.name, self.type,
203
self.domain, self.host,
204
dbus.UInt16(self.port),
205
avahi.string_array_to_txt_array(self.TXT))
207
def entry_group_state_changed(self, state, error):
208
"""Derived from the Avahi example code"""
209
logger.debug("Avahi entry group state change: %i", state)
211
if state == avahi.ENTRY_GROUP_ESTABLISHED:
212
logger.debug("Zeroconf service established.")
213
elif state == avahi.ENTRY_GROUP_COLLISION:
214
logger.info("Zeroconf service name collision.")
216
elif state == avahi.ENTRY_GROUP_FAILURE:
217
logger.critical("Avahi: Error in group state changed %s",
219
raise AvahiGroupError("State changed: %s"
222
"""Derived from the Avahi example code"""
223
if self.group is not None:
226
except (dbus.exceptions.UnknownMethodException,
227
dbus.exceptions.DBusException) as e:
231
def server_state_changed(self, state, error=None):
232
"""Derived from the Avahi example code"""
233
logger.debug("Avahi server state change: %i", state)
234
bad_states = { avahi.SERVER_INVALID:
235
"Zeroconf server invalid",
236
avahi.SERVER_REGISTERING: None,
237
avahi.SERVER_COLLISION:
238
"Zeroconf server name collision",
239
avahi.SERVER_FAILURE:
240
"Zeroconf server failure" }
241
if state in bad_states:
242
if bad_states[state] is not None:
244
logger.error(bad_states[state])
246
logger.error(bad_states[state] + ": %r", error)
248
elif state == avahi.SERVER_RUNNING:
252
logger.debug("Unknown state: %r", state)
254
logger.debug("Unknown state: %r: %r", state, error)
256
"""Derived from the Avahi example code"""
257
if self.server is None:
258
self.server = dbus.Interface(
259
self.bus.get_object(avahi.DBUS_NAME,
260
avahi.DBUS_PATH_SERVER,
261
follow_name_owner_changes=True),
262
avahi.DBUS_INTERFACE_SERVER)
263
self.server.connect_to_signal("StateChanged",
264
self.server_state_changed)
265
self.server_state_changed(self.server.GetState())
268
def _timedelta_to_milliseconds(td):
269
"Convert a datetime.timedelta() to milliseconds"
270
return ((td.days * 24 * 60 * 60 * 1000)
271
+ (td.seconds * 1000)
272
+ (td.microseconds // 1000))
274
class Client(object):
179
275
"""A representation of a client host served by this server.
181
name: string; from the config file, used in log messages
278
_approved: bool(); 'None' if not yet approved/disapproved
279
approval_delay: datetime.timedelta(); Time to wait for approval
280
approval_duration: datetime.timedelta(); Duration of one approval
281
checker: subprocess.Popen(); a running checker process used
282
to see if the client lives.
283
'None' if no process is running.
284
checker_callback_tag: a gobject event source tag, or None
285
checker_command: string; External command which is run to check
286
if client lives. %() expansions are done at
287
runtime with vars(self) as dict, so that for
288
instance %(name)s can be used in the command.
289
checker_initiator_tag: a gobject event source tag, or None
290
created: datetime.datetime(); (UTC) object creation
291
current_checker_command: string; current running checker_command
292
disable_hook: If set, called by disable() as disable_hook(self)
293
disable_initiator_tag: a gobject event source tag, or None
182
295
fingerprint: string (40 or 32 hexadecimal digits); used to
183
296
uniquely identify the client
184
secret: bytestring; sent verbatim (over TLS) to client
185
297
host: string; available for use by the checker command
186
created: datetime.datetime(); (UTC) object creation
298
interval: datetime.timedelta(); How often to start a new checker
299
last_approval_request: datetime.datetime(); (UTC) or None
300
last_checked_ok: datetime.datetime(); (UTC) or None
187
301
last_enabled: datetime.datetime(); (UTC)
189
last_checked_ok: datetime.datetime(); (UTC) or None
302
name: string; from the config file, used in log messages and
304
secret: bytestring; sent verbatim (over TLS) to client
190
305
timeout: datetime.timedelta(); How long from last_checked_ok
191
until this client is invalid
192
interval: datetime.timedelta(); How often to start a new checker
193
disable_hook: If set, called by disable() as disable_hook(self)
194
checker: subprocess.Popen(); a running checker process used
195
to see if the client lives.
196
'None' if no process is running.
197
checker_initiator_tag: a gobject event source tag, or None
198
disable_initiator_tag: - '' -
199
checker_callback_tag: - '' -
200
checker_command: string; External command which is run to check if
201
client lives. %() expansions are done at
202
runtime with vars(self) as dict, so that for
203
instance %(name)s can be used in the command.
204
use_dbus: bool(); Whether to provide D-Bus interface and signals
205
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
306
until this client is disabled
307
extended_timeout: extra long timeout when password has been sent
308
runtime_expansions: Allowed attributes for runtime expansion.
309
expires: datetime.datetime(); time (UTC) when a client will be
313
runtime_expansions = ("approval_delay", "approval_duration",
314
"created", "enabled", "fingerprint",
315
"host", "interval", "last_checked_ok",
316
"last_enabled", "name", "timeout")
207
318
def timeout_milliseconds(self):
208
319
"Return the 'timeout' attribute in milliseconds"
209
return ((self.timeout.days * 24 * 60 * 60 * 1000)
210
+ (self.timeout.seconds * 1000)
211
+ (self.timeout.microseconds // 1000))
320
return _timedelta_to_milliseconds(self.timeout)
322
def extended_timeout_milliseconds(self):
323
"Return the 'extended_timeout' attribute in milliseconds"
324
return _timedelta_to_milliseconds(self.extended_timeout)
213
326
def interval_milliseconds(self):
214
327
"Return the 'interval' attribute in milliseconds"
215
return ((self.interval.days * 24 * 60 * 60 * 1000)
216
+ (self.interval.seconds * 1000)
217
+ (self.interval.microseconds // 1000))
219
def __init__(self, name = None, disable_hook=None, config=None,
328
return _timedelta_to_milliseconds(self.interval)
330
def approval_delay_milliseconds(self):
331
return _timedelta_to_milliseconds(self.approval_delay)
333
def __init__(self, name = None, disable_hook=None, config=None):
221
334
"""Note: the 'checker' key in 'config' sets the
222
335
'checker_command' attribute and *not* the 'checker'
225
338
if config is None:
227
logger.debug(u"Creating client %r", self.name)
228
self.use_dbus = use_dbus
230
self.dbus_object_path = (dbus.ObjectPath
232
+ self.name.replace(".", "_")))
233
dbus.service.Object.__init__(self, bus,
234
self.dbus_object_path)
340
logger.debug("Creating client %r", self.name)
235
341
# Uppercase and remove spaces from fingerprint for later
236
342
# comparison purposes with return value from the fingerprint()
238
344
self.fingerprint = (config["fingerprint"].upper()
240
logger.debug(u" Fingerprint: %s", self.fingerprint)
346
logger.debug(" Fingerprint: %s", self.fingerprint)
241
347
if "secret" in config:
242
self.secret = config["secret"].decode(u"base64")
348
self.secret = config["secret"].decode("base64")
243
349
elif "secfile" in config:
244
with closing(open(os.path.expanduser
246
(config["secfile"])))) as secfile:
350
with open(os.path.expanduser(os.path.expandvars
351
(config["secfile"])),
247
353
self.secret = secfile.read()
249
raise TypeError(u"No secret or secfile for client %s"
355
raise TypeError("No secret or secfile for client %s"
251
357
self.host = config.get("host", "")
252
358
self.created = datetime.datetime.utcnow()
253
359
self.enabled = False
360
self.last_approval_request = None
254
361
self.last_enabled = None
255
362
self.last_checked_ok = None
256
363
self.timeout = string_to_delta(config["timeout"])
364
self.extended_timeout = string_to_delta(config["extended_timeout"])
257
365
self.interval = string_to_delta(config["interval"])
258
366
self.disable_hook = disable_hook
259
367
self.checker = None
260
368
self.checker_initiator_tag = None
261
369
self.disable_initiator_tag = None
262
371
self.checker_callback_tag = None
263
372
self.checker_command = config["checker"]
373
self.current_checker_command = None
374
self.last_connect = None
375
self._approved = None
376
self.approved_by_default = config.get("approved_by_default",
378
self.approvals_pending = 0
379
self.approval_delay = string_to_delta(
380
config["approval_delay"])
381
self.approval_duration = string_to_delta(
382
config["approval_duration"])
383
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
385
def send_changedstate(self):
386
self.changedstate.acquire()
387
self.changedstate.notify_all()
388
self.changedstate.release()
265
390
def enable(self):
266
391
"""Start this client's checker and timeout hooks"""
267
self.last_enabled = datetime.datetime.utcnow()
392
if getattr(self, "enabled", False):
395
self.send_changedstate()
268
396
# Schedule a new checker to be started an 'interval' from now,
269
397
# and every interval from then on.
270
398
self.checker_initiator_tag = (gobject.timeout_add
271
399
(self.interval_milliseconds(),
272
400
self.start_checker))
273
# Also start a new checker *right now*.
275
401
# Schedule a disable() when 'timeout' has passed
402
self.expires = datetime.datetime.utcnow() + self.timeout
276
403
self.disable_initiator_tag = (gobject.timeout_add
277
404
(self.timeout_milliseconds(),
279
406
self.enabled = True
282
self.PropertyChanged(dbus.String(u"enabled"),
283
dbus.Boolean(True, variant_level=1))
284
self.PropertyChanged(dbus.String(u"last_enabled"),
285
(_datetime_to_dbus(self.last_enabled,
407
self.last_enabled = datetime.datetime.utcnow()
408
# Also start a new checker *right now*.
411
def disable(self, quiet=True):
289
412
"""Disable this client."""
290
413
if not getattr(self, "enabled", False):
292
logger.info(u"Disabling client %s", self.name)
416
self.send_changedstate()
418
logger.info("Disabling client %s", self.name)
293
419
if getattr(self, "disable_initiator_tag", False):
294
420
gobject.source_remove(self.disable_initiator_tag)
295
421
self.disable_initiator_tag = None
296
423
if getattr(self, "checker_initiator_tag", False):
297
424
gobject.source_remove(self.checker_initiator_tag)
298
425
self.checker_initiator_tag = None
423
551
self.checker_callback_tag = None
424
552
if getattr(self, "checker", None) is None:
426
logger.debug(u"Stopping checker for %(name)s", vars(self))
554
logger.debug("Stopping checker for %(name)s", vars(self))
428
556
os.kill(self.checker.pid, signal.SIGTERM)
430
558
#if self.checker.poll() is None:
431
559
# os.kill(self.checker.pid, signal.SIGKILL)
432
except OSError, error:
560
except OSError as error:
433
561
if error.errno != errno.ESRCH: # No such process
435
563
self.checker = None
437
self.PropertyChanged(dbus.String(u"checker_running"),
438
dbus.Boolean(False, variant_level=1))
440
def still_valid(self):
441
"""Has the timeout not yet passed for this client?"""
442
if not getattr(self, "enabled", False):
444
now = datetime.datetime.utcnow()
445
if self.last_checked_ok is None:
446
return now < (self.created + self.timeout)
448
return now < (self.last_checked_ok + self.timeout)
450
## D-Bus methods & signals
451
_interface = u"org.mandos_system.Mandos.Client"
453
# BumpTimeout - method
454
BumpTimeout = dbus.service.method(_interface)(bump_timeout)
455
BumpTimeout.__name__ = "BumpTimeout"
566
def dbus_service_property(dbus_interface, signature="v",
567
access="readwrite", byte_arrays=False):
568
"""Decorators for marking methods of a DBusObjectWithProperties to
569
become properties on the D-Bus.
571
The decorated method will be called with no arguments by "Get"
572
and with one argument by "Set".
574
The parameters, where they are supported, are the same as
575
dbus.service.method, except there is only "signature", since the
576
type from Get() and the type sent to Set() is the same.
578
# Encoding deeply encoded byte arrays is not supported yet by the
579
# "Set" method, so we fail early here:
580
if byte_arrays and signature != "ay":
581
raise ValueError("Byte arrays not supported for non-'ay'"
582
" signature %r" % signature)
584
func._dbus_is_property = True
585
func._dbus_interface = dbus_interface
586
func._dbus_signature = signature
587
func._dbus_access = access
588
func._dbus_name = func.__name__
589
if func._dbus_name.endswith("_dbus_property"):
590
func._dbus_name = func._dbus_name[:-14]
591
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
596
class DBusPropertyException(dbus.exceptions.DBusException):
597
"""A base class for D-Bus property-related exceptions
599
def __unicode__(self):
600
return unicode(str(self))
603
class DBusPropertyAccessException(DBusPropertyException):
604
"""A property's access permissions disallows an operation.
609
class DBusPropertyNotFound(DBusPropertyException):
610
"""An attempt was made to access a non-existing property.
615
class DBusObjectWithProperties(dbus.service.Object):
616
"""A D-Bus object with properties.
618
Classes inheriting from this can use the dbus_service_property
619
decorator to expose methods as D-Bus properties. It exposes the
620
standard Get(), Set(), and GetAll() methods on the D-Bus.
624
def _is_dbus_property(obj):
625
return getattr(obj, "_dbus_is_property", False)
627
def _get_all_dbus_properties(self):
628
"""Returns a generator of (name, attribute) pairs
630
return ((prop.__get__(self)._dbus_name, prop.__get__(self))
631
for cls in self.__class__.__mro__
632
for name, prop in inspect.getmembers(cls, self._is_dbus_property))
634
def _get_dbus_property(self, interface_name, property_name):
635
"""Returns a bound method if one exists which is a D-Bus
636
property with the specified name and interface.
638
for cls in self.__class__.__mro__:
639
for name, value in inspect.getmembers(cls, self._is_dbus_property):
640
if value._dbus_name == property_name and value._dbus_interface == interface_name:
641
return value.__get__(self)
644
raise DBusPropertyNotFound(self.dbus_object_path + ":"
645
+ interface_name + "."
648
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
650
def Get(self, interface_name, property_name):
651
"""Standard D-Bus property Get() method, see D-Bus standard.
653
prop = self._get_dbus_property(interface_name, property_name)
654
if prop._dbus_access == "write":
655
raise DBusPropertyAccessException(property_name)
657
if not hasattr(value, "variant_level"):
659
return type(value)(value, variant_level=value.variant_level+1)
661
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
662
def Set(self, interface_name, property_name, value):
663
"""Standard D-Bus property Set() method, see D-Bus standard.
665
prop = self._get_dbus_property(interface_name, property_name)
666
if prop._dbus_access == "read":
667
raise DBusPropertyAccessException(property_name)
668
if prop._dbus_get_args_options["byte_arrays"]:
669
# The byte_arrays option is not supported yet on
670
# signatures other than "ay".
671
if prop._dbus_signature != "ay":
673
value = dbus.ByteArray(''.join(unichr(byte)
677
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
678
out_signature="a{sv}")
679
def GetAll(self, interface_name):
680
"""Standard D-Bus property GetAll() method, see D-Bus
683
Note: Will not include properties with access="write".
686
for name, prop in self._get_all_dbus_properties():
688
and interface_name != prop._dbus_interface):
689
# Interface non-empty but did not match
691
# Ignore write-only properties
692
if prop._dbus_access == "write":
695
if not hasattr(value, "variant_level"):
698
all[name] = type(value)(value, variant_level=
699
value.variant_level+1)
700
return dbus.Dictionary(all, signature="sv")
702
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
704
path_keyword='object_path',
705
connection_keyword='connection')
706
def Introspect(self, object_path, connection):
707
"""Standard D-Bus method, overloaded to insert property tags.
709
xmlstring = dbus.service.Object.Introspect(self, object_path,
712
document = xml.dom.minidom.parseString(xmlstring)
713
def make_tag(document, name, prop):
714
e = document.createElement("property")
715
e.setAttribute("name", name)
716
e.setAttribute("type", prop._dbus_signature)
717
e.setAttribute("access", prop._dbus_access)
719
for if_tag in document.getElementsByTagName("interface"):
720
for tag in (make_tag(document, name, prop)
722
in self._get_all_dbus_properties()
723
if prop._dbus_interface
724
== if_tag.getAttribute("name")):
725
if_tag.appendChild(tag)
726
# Add the names to the return values for the
727
# "org.freedesktop.DBus.Properties" methods
728
if (if_tag.getAttribute("name")
729
== "org.freedesktop.DBus.Properties"):
730
for cn in if_tag.getElementsByTagName("method"):
731
if cn.getAttribute("name") == "Get":
732
for arg in cn.getElementsByTagName("arg"):
733
if (arg.getAttribute("direction")
735
arg.setAttribute("name", "value")
736
elif cn.getAttribute("name") == "GetAll":
737
for arg in cn.getElementsByTagName("arg"):
738
if (arg.getAttribute("direction")
740
arg.setAttribute("name", "props")
741
xmlstring = document.toxml("utf-8")
743
except (AttributeError, xml.dom.DOMException,
744
xml.parsers.expat.ExpatError) as error:
745
logger.error("Failed to override Introspection method",
750
def datetime_to_dbus (dt, variant_level=0):
751
"""Convert a UTC datetime.datetime() to a D-Bus type."""
753
return dbus.String("", variant_level = variant_level)
754
return dbus.String(dt.isoformat(),
755
variant_level=variant_level)
757
class AlternateDBusNamesMetaclass(DBusObjectWithProperties.__metaclass__):
758
"""Applied to an empty subclass of a D-Bus object, this metaclass
759
will add additional D-Bus attributes matching a certain pattern.
761
def __new__(mcs, name, bases, attr):
762
# Go through all the base classes which could have D-Bus
763
# methods, signals, or properties in them
764
for base in (b for b in bases
765
if issubclass(b, dbus.service.Object)):
766
# Go though all attributes of the base class
767
for attrname, attribute in inspect.getmembers(base):
768
# Ignore non-D-Bus attributes, and D-Bus attributes
769
# with the wrong interface name
770
if (not hasattr(attribute, "_dbus_interface")
771
or not attribute._dbus_interface
772
.startswith("se.recompile.Mandos")):
774
# Create an alternate D-Bus interface name based on
776
alt_interface = (attribute._dbus_interface
777
.replace("se.recompile.Mandos",
778
"se.bsnet.fukt.Mandos"))
779
# Is this a D-Bus signal?
780
if getattr(attribute, "_dbus_is_signal", False):
781
# Extract the original non-method function by
783
nonmethod_func = (dict(
784
zip(attribute.func_code.co_freevars,
785
attribute.__closure__))["func"]
787
# Create a new, but exactly alike, function
788
# object, and decorate it to be a new D-Bus signal
789
# with the alternate D-Bus interface name
790
new_function = (dbus.service.signal
792
attribute._dbus_signature)
794
nonmethod_func.func_code,
795
nonmethod_func.func_globals,
796
nonmethod_func.func_name,
797
nonmethod_func.func_defaults,
798
nonmethod_func.func_closure)))
799
# Define a creator of a function to call both the
800
# old and new functions, so both the old and new
801
# signals gets sent when the function is called
802
def fixscope(func1, func2):
803
"""This function is a scope container to pass
804
func1 and func2 to the "call_both" function
805
outside of its arguments"""
806
def call_both(*args, **kwargs):
807
"""This function will emit two D-Bus
808
signals by calling func1 and func2"""
809
func1(*args, **kwargs)
810
func2(*args, **kwargs)
812
# Create the "call_both" function and add it to
814
attr[attrname] = fixscope(attribute,
816
# Is this a D-Bus method?
817
elif getattr(attribute, "_dbus_is_method", False):
818
# Create a new, but exactly alike, function
819
# object. Decorate it to be a new D-Bus method
820
# with the alternate D-Bus interface name. Add it
822
attr[attrname] = (dbus.service.method
824
attribute._dbus_in_signature,
825
attribute._dbus_out_signature)
827
(attribute.func_code,
828
attribute.func_globals,
830
attribute.func_defaults,
831
attribute.func_closure)))
832
# Is this a D-Bus property?
833
elif getattr(attribute, "_dbus_is_property", False):
834
# Create a new, but exactly alike, function
835
# object, and decorate it to be a new D-Bus
836
# property with the alternate D-Bus interface
837
# name. Add it to the class.
838
attr[attrname] = (dbus_service_property
840
attribute._dbus_signature,
841
attribute._dbus_access,
843
._dbus_get_args_options
846
(attribute.func_code,
847
attribute.func_globals,
849
attribute.func_defaults,
850
attribute.func_closure)))
851
return type.__new__(mcs, name, bases, attr)
853
class ClientDBus(Client, DBusObjectWithProperties):
854
"""A Client class using D-Bus
857
dbus_object_path: dbus.ObjectPath
858
bus: dbus.SystemBus()
861
runtime_expansions = (Client.runtime_expansions
862
+ ("dbus_object_path",))
864
# dbus.service.Object doesn't use super(), so we can't either.
866
def __init__(self, bus = None, *args, **kwargs):
867
self._approvals_pending = 0
869
Client.__init__(self, *args, **kwargs)
870
# Only now, when this client is initialized, can it show up on
872
client_object_name = unicode(self.name).translate(
875
self.dbus_object_path = (dbus.ObjectPath
876
("/clients/" + client_object_name))
877
DBusObjectWithProperties.__init__(self, self.bus,
878
self.dbus_object_path)
880
def notifychangeproperty(transform_func,
881
dbus_name, type_func=lambda x: x,
883
""" Modify a variable so that it's a property which announces
886
transform_fun: Function that takes a value and transforms it
888
dbus_name: D-Bus name of the variable
889
type_func: Function that transform the value before sending it
890
to the D-Bus. Default: no transform
891
variant_level: D-Bus variant level. Default: 1
894
def setter(self, value):
895
old_value = real_value[0]
896
real_value[0] = value
897
if hasattr(self, "dbus_object_path"):
898
if type_func(old_value) != type_func(real_value[0]):
899
dbus_value = transform_func(type_func(real_value[0]),
901
self.PropertyChanged(dbus.String(dbus_name),
904
return property(lambda self: real_value[0], setter)
907
expires = notifychangeproperty(datetime_to_dbus, "Expires")
908
approvals_pending = notifychangeproperty(dbus.Boolean,
911
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
912
last_enabled = notifychangeproperty(datetime_to_dbus,
914
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
915
type_func = lambda checker: checker is not None)
916
last_checked_ok = notifychangeproperty(datetime_to_dbus,
918
last_approval_request = notifychangeproperty(datetime_to_dbus,
919
"LastApprovalRequest")
920
approved_by_default = notifychangeproperty(dbus.Boolean,
922
approval_delay = notifychangeproperty(dbus.UInt16, "ApprovalDelay",
923
type_func = _timedelta_to_milliseconds)
924
approval_duration = notifychangeproperty(dbus.UInt16, "ApprovalDuration",
925
type_func = _timedelta_to_milliseconds)
926
host = notifychangeproperty(dbus.String, "Host")
927
timeout = notifychangeproperty(dbus.UInt16, "Timeout",
928
type_func = _timedelta_to_milliseconds)
929
extended_timeout = notifychangeproperty(dbus.UInt16, "ExtendedTimeout",
930
type_func = _timedelta_to_milliseconds)
931
interval = notifychangeproperty(dbus.UInt16, "Interval",
932
type_func = _timedelta_to_milliseconds)
933
checker_command = notifychangeproperty(dbus.String, "Checker")
935
del notifychangeproperty
937
def __del__(self, *args, **kwargs):
939
self.remove_from_connection()
942
if hasattr(DBusObjectWithProperties, "__del__"):
943
DBusObjectWithProperties.__del__(self, *args, **kwargs)
944
Client.__del__(self, *args, **kwargs)
946
def checker_callback(self, pid, condition, command,
948
self.checker_callback_tag = None
950
if os.WIFEXITED(condition):
951
exitstatus = os.WEXITSTATUS(condition)
953
self.CheckerCompleted(dbus.Int16(exitstatus),
954
dbus.Int64(condition),
955
dbus.String(command))
958
self.CheckerCompleted(dbus.Int16(-1),
959
dbus.Int64(condition),
960
dbus.String(command))
962
return Client.checker_callback(self, pid, condition, command,
965
def start_checker(self, *args, **kwargs):
966
old_checker = self.checker
967
if self.checker is not None:
968
old_checker_pid = self.checker.pid
970
old_checker_pid = None
971
r = Client.start_checker(self, *args, **kwargs)
972
# Only if new checker process was started
973
if (self.checker is not None
974
and old_checker_pid != self.checker.pid):
976
self.CheckerStarted(self.current_checker_command)
979
def _reset_approved(self):
980
self._approved = None
983
def approve(self, value=True):
984
self.send_changedstate()
985
self._approved = value
986
gobject.timeout_add(_timedelta_to_milliseconds
987
(self.approval_duration),
988
self._reset_approved)
991
## D-Bus methods, signals & properties
992
_interface = "se.recompile.Mandos.Client"
457
996
# CheckerCompleted - signal
458
@dbus.service.signal(_interface, signature="bqs")
459
def CheckerCompleted(self, success, condition, command):
997
@dbus.service.signal(_interface, signature="nxs")
998
def CheckerCompleted(self, exitcode, waitstatus, command):
579
1065
# StopChecker - method
580
StopChecker = dbus.service.method(_interface)(stop_checker)
581
StopChecker.__name__ = "StopChecker"
1066
@dbus.service.method(_interface)
1067
def StopChecker(self):
1072
# ApprovalPending - property
1073
@dbus_service_property(_interface, signature="b", access="read")
1074
def ApprovalPending_dbus_property(self):
1075
return dbus.Boolean(bool(self.approvals_pending))
1077
# ApprovedByDefault - property
1078
@dbus_service_property(_interface, signature="b",
1080
def ApprovedByDefault_dbus_property(self, value=None):
1081
if value is None: # get
1082
return dbus.Boolean(self.approved_by_default)
1083
self.approved_by_default = bool(value)
1085
# ApprovalDelay - property
1086
@dbus_service_property(_interface, signature="t",
1088
def ApprovalDelay_dbus_property(self, value=None):
1089
if value is None: # get
1090
return dbus.UInt64(self.approval_delay_milliseconds())
1091
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1093
# ApprovalDuration - property
1094
@dbus_service_property(_interface, signature="t",
1096
def ApprovalDuration_dbus_property(self, value=None):
1097
if value is None: # get
1098
return dbus.UInt64(_timedelta_to_milliseconds(
1099
self.approval_duration))
1100
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1103
@dbus_service_property(_interface, signature="s", access="read")
1104
def Name_dbus_property(self):
1105
return dbus.String(self.name)
1107
# Fingerprint - property
1108
@dbus_service_property(_interface, signature="s", access="read")
1109
def Fingerprint_dbus_property(self):
1110
return dbus.String(self.fingerprint)
1113
@dbus_service_property(_interface, signature="s",
1115
def Host_dbus_property(self, value=None):
1116
if value is None: # get
1117
return dbus.String(self.host)
1120
# Created - property
1121
@dbus_service_property(_interface, signature="s", access="read")
1122
def Created_dbus_property(self):
1123
return dbus.String(datetime_to_dbus(self.created))
1125
# LastEnabled - property
1126
@dbus_service_property(_interface, signature="s", access="read")
1127
def LastEnabled_dbus_property(self):
1128
return datetime_to_dbus(self.last_enabled)
1130
# Enabled - property
1131
@dbus_service_property(_interface, signature="b",
1133
def Enabled_dbus_property(self, value=None):
1134
if value is None: # get
1135
return dbus.Boolean(self.enabled)
1141
# LastCheckedOK - property
1142
@dbus_service_property(_interface, signature="s",
1144
def LastCheckedOK_dbus_property(self, value=None):
1145
if value is not None:
1148
return datetime_to_dbus(self.last_checked_ok)
1150
# Expires - property
1151
@dbus_service_property(_interface, signature="s", access="read")
1152
def Expires_dbus_property(self):
1153
return datetime_to_dbus(self.expires)
1155
# LastApprovalRequest - property
1156
@dbus_service_property(_interface, signature="s", access="read")
1157
def LastApprovalRequest_dbus_property(self):
1158
return datetime_to_dbus(self.last_approval_request)
1160
# Timeout - property
1161
@dbus_service_property(_interface, signature="t",
1163
def Timeout_dbus_property(self, value=None):
1164
if value is None: # get
1165
return dbus.UInt64(self.timeout_milliseconds())
1166
self.timeout = datetime.timedelta(0, 0, 0, value)
1167
if getattr(self, "disable_initiator_tag", None) is None:
1169
# Reschedule timeout
1170
gobject.source_remove(self.disable_initiator_tag)
1171
self.disable_initiator_tag = None
1173
time_to_die = (self.
1174
_timedelta_to_milliseconds((self
1179
if time_to_die <= 0:
1180
# The timeout has passed
1183
self.expires = (datetime.datetime.utcnow()
1184
+ datetime.timedelta(milliseconds = time_to_die))
1185
self.disable_initiator_tag = (gobject.timeout_add
1186
(time_to_die, self.disable))
1188
# ExtendedTimeout - property
1189
@dbus_service_property(_interface, signature="t",
1191
def ExtendedTimeout_dbus_property(self, value=None):
1192
if value is None: # get
1193
return dbus.UInt64(self.extended_timeout_milliseconds())
1194
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1196
# Interval - property
1197
@dbus_service_property(_interface, signature="t",
1199
def Interval_dbus_property(self, value=None):
1200
if value is None: # get
1201
return dbus.UInt64(self.interval_milliseconds())
1202
self.interval = datetime.timedelta(0, 0, 0, value)
1203
if getattr(self, "checker_initiator_tag", None) is None:
1205
# Reschedule checker run
1206
gobject.source_remove(self.checker_initiator_tag)
1207
self.checker_initiator_tag = (gobject.timeout_add
1208
(value, self.start_checker))
1209
self.start_checker() # Start one now, too
1211
# Checker - property
1212
@dbus_service_property(_interface, signature="s",
1214
def Checker_dbus_property(self, value=None):
1215
if value is None: # get
1216
return dbus.String(self.checker_command)
1217
self.checker_command = value
1219
# CheckerRunning - property
1220
@dbus_service_property(_interface, signature="b",
1222
def CheckerRunning_dbus_property(self, value=None):
1223
if value is None: # get
1224
return dbus.Boolean(self.checker is not None)
1226
self.start_checker()
1230
# ObjectPath - property
1231
@dbus_service_property(_interface, signature="o", access="read")
1232
def ObjectPath_dbus_property(self):
1233
return self.dbus_object_path # is already a dbus.ObjectPath
1236
@dbus_service_property(_interface, signature="ay",
1237
access="write", byte_arrays=True)
1238
def Secret_dbus_property(self, value):
1239
self.secret = str(value)
586
def peer_certificate(session):
587
"Return the peer's OpenPGP certificate as a bytestring"
588
# If not an OpenPGP certificate...
589
if (gnutls.library.functions
590
.gnutls_certificate_type_get(session._c_object)
591
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
592
# ...do the normal thing
593
return session.peer_certificate
594
list_size = ctypes.c_uint()
595
cert_list = (gnutls.library.functions
596
.gnutls_certificate_get_peers
597
(session._c_object, ctypes.byref(list_size)))
598
if list_size.value == 0:
601
return ctypes.string_at(cert.data, cert.size)
604
def fingerprint(openpgp):
605
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
606
# New GnuTLS "datum" with the OpenPGP public key
607
datum = (gnutls.library.types
608
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
611
ctypes.c_uint(len(openpgp))))
612
# New empty GnuTLS certificate
613
crt = gnutls.library.types.gnutls_openpgp_crt_t()
614
(gnutls.library.functions
615
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
616
# Import the OpenPGP public key into the certificate
617
(gnutls.library.functions
618
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
619
gnutls.library.constants
620
.GNUTLS_OPENPGP_FMT_RAW))
621
# Verify the self signature in the key
622
crtverify = ctypes.c_uint()
623
(gnutls.library.functions
624
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
625
if crtverify.value != 0:
626
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
627
raise gnutls.errors.CertificateSecurityError("Verify failed")
628
# New buffer for the fingerprint
629
buf = ctypes.create_string_buffer(20)
630
buf_len = ctypes.c_size_t()
631
# Get the fingerprint from the certificate into the buffer
632
(gnutls.library.functions
633
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
634
ctypes.byref(buf_len)))
635
# Deinit the certificate
636
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
637
# Convert the buffer to a Python bytestring
638
fpr = ctypes.string_at(buf, buf_len.value)
639
# Convert the bytestring to hexadecimal notation
640
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
644
class TCP_handler(SocketServer.BaseRequestHandler, object):
645
"""A TCP request handler class.
646
Instantiated by IPv6_TCPServer for each request to handle it.
1244
class ProxyClient(object):
1245
def __init__(self, child_pipe, fpr, address):
1246
self._pipe = child_pipe
1247
self._pipe.send(('init', fpr, address))
1248
if not self._pipe.recv():
1251
def __getattribute__(self, name):
1252
if(name == '_pipe'):
1253
return super(ProxyClient, self).__getattribute__(name)
1254
self._pipe.send(('getattr', name))
1255
data = self._pipe.recv()
1256
if data[0] == 'data':
1258
if data[0] == 'function':
1259
def func(*args, **kwargs):
1260
self._pipe.send(('funcall', name, args, kwargs))
1261
return self._pipe.recv()[1]
1264
def __setattr__(self, name, value):
1265
if(name == '_pipe'):
1266
return super(ProxyClient, self).__setattr__(name, value)
1267
self._pipe.send(('setattr', name, value))
1269
class ClientDBusTransitional(ClientDBus):
1270
__metaclass__ = AlternateDBusNamesMetaclass
1272
class ClientHandler(socketserver.BaseRequestHandler, object):
1273
"""A class to handle client connections.
1275
Instantiated once for each connection to handle it.
647
1276
Note: This will run in its own forked process."""
649
1278
def handle(self):
650
logger.info(u"TCP connection from: %s",
651
unicode(self.client_address))
652
session = (gnutls.connection
653
.ClientSession(self.request,
657
line = self.request.makefile().readline()
658
logger.debug(u"Protocol version: %r", line)
660
if int(line.strip().split()[0]) > 1:
662
except (ValueError, IndexError, RuntimeError), error:
663
logger.error(u"Unknown protocol version: %s", error)
666
# Note: gnutls.connection.X509Credentials is really a generic
667
# GnuTLS certificate credentials object so long as no X.509
668
# keys are added to it. Therefore, we can use it here despite
669
# using OpenPGP certificates.
671
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
672
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
674
# Use a fallback default, since this MUST be set.
675
priority = self.server.settings.get("priority", "NORMAL")
676
(gnutls.library.functions
677
.gnutls_priority_set_direct(session._c_object,
682
except gnutls.errors.GNUTLSError, error:
683
logger.warning(u"Handshake failed: %s", error)
684
# Do not run session.bye() here: the session is not
685
# established. Just abandon the request.
688
fpr = fingerprint(peer_certificate(session))
689
except (TypeError, gnutls.errors.GNUTLSError), error:
690
logger.warning(u"Bad certificate: %s", error)
693
logger.debug(u"Fingerprint: %s", fpr)
694
for c in self.server.clients:
695
if c.fingerprint == fpr:
699
logger.warning(u"Client not found for fingerprint: %s",
703
# Have to check if client.still_valid(), since it is possible
704
# that the client timed out while establishing the GnuTLS
706
if not client.still_valid():
707
logger.warning(u"Client %(name)s is invalid",
711
## This won't work here, since we're in a fork.
712
# client.bump_timeout()
714
while sent_size < len(client.secret):
715
sent = session.send(client.secret[sent_size:])
716
logger.debug(u"Sent: %d, remaining: %d",
717
sent, len(client.secret)
718
- (sent_size + sent))
723
class IPv6_TCPServer(SocketServer.ForkingMixIn,
724
SocketServer.TCPServer, object):
725
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1279
with contextlib.closing(self.server.child_pipe) as child_pipe:
1280
logger.info("TCP connection from: %s",
1281
unicode(self.client_address))
1282
logger.debug("Pipe FD: %d",
1283
self.server.child_pipe.fileno())
1285
session = (gnutls.connection
1286
.ClientSession(self.request,
1288
.X509Credentials()))
1290
# Note: gnutls.connection.X509Credentials is really a
1291
# generic GnuTLS certificate credentials object so long as
1292
# no X.509 keys are added to it. Therefore, we can use it
1293
# here despite using OpenPGP certificates.
1295
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1296
# "+AES-256-CBC", "+SHA1",
1297
# "+COMP-NULL", "+CTYPE-OPENPGP",
1299
# Use a fallback default, since this MUST be set.
1300
priority = self.server.gnutls_priority
1301
if priority is None:
1303
(gnutls.library.functions
1304
.gnutls_priority_set_direct(session._c_object,
1307
# Start communication using the Mandos protocol
1308
# Get protocol number
1309
line = self.request.makefile().readline()
1310
logger.debug("Protocol version: %r", line)
1312
if int(line.strip().split()[0]) > 1:
1314
except (ValueError, IndexError, RuntimeError) as error:
1315
logger.error("Unknown protocol version: %s", error)
1318
# Start GnuTLS connection
1321
except gnutls.errors.GNUTLSError as error:
1322
logger.warning("Handshake failed: %s", error)
1323
# Do not run session.bye() here: the session is not
1324
# established. Just abandon the request.
1326
logger.debug("Handshake succeeded")
1328
approval_required = False
1331
fpr = self.fingerprint(self.peer_certificate
1334
gnutls.errors.GNUTLSError) as error:
1335
logger.warning("Bad certificate: %s", error)
1337
logger.debug("Fingerprint: %s", fpr)
1340
client = ProxyClient(child_pipe, fpr,
1341
self.client_address)
1345
if client.approval_delay:
1346
delay = client.approval_delay
1347
client.approvals_pending += 1
1348
approval_required = True
1351
if not client.enabled:
1352
logger.info("Client %s is disabled",
1354
if self.server.use_dbus:
1356
client.Rejected("Disabled")
1359
if client._approved or not client.approval_delay:
1360
#We are approved or approval is disabled
1362
elif client._approved is None:
1363
logger.info("Client %s needs approval",
1365
if self.server.use_dbus:
1367
client.NeedApproval(
1368
client.approval_delay_milliseconds(),
1369
client.approved_by_default)
1371
logger.warning("Client %s was not approved",
1373
if self.server.use_dbus:
1375
client.Rejected("Denied")
1378
#wait until timeout or approved
1379
#x = float(client._timedelta_to_milliseconds(delay))
1380
time = datetime.datetime.now()
1381
client.changedstate.acquire()
1382
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1383
client.changedstate.release()
1384
time2 = datetime.datetime.now()
1385
if (time2 - time) >= delay:
1386
if not client.approved_by_default:
1387
logger.warning("Client %s timed out while"
1388
" waiting for approval",
1390
if self.server.use_dbus:
1392
client.Rejected("Approval timed out")
1397
delay -= time2 - time
1400
while sent_size < len(client.secret):
1402
sent = session.send(client.secret[sent_size:])
1403
except gnutls.errors.GNUTLSError as error:
1404
logger.warning("gnutls send failed")
1406
logger.debug("Sent: %d, remaining: %d",
1407
sent, len(client.secret)
1408
- (sent_size + sent))
1411
logger.info("Sending secret to %s", client.name)
1412
# bump the timeout as if seen
1413
client.checked_ok(client.extended_timeout)
1414
if self.server.use_dbus:
1419
if approval_required:
1420
client.approvals_pending -= 1
1423
except gnutls.errors.GNUTLSError as error:
1424
logger.warning("GnuTLS bye failed")
1427
def peer_certificate(session):
1428
"Return the peer's OpenPGP certificate as a bytestring"
1429
# If not an OpenPGP certificate...
1430
if (gnutls.library.functions
1431
.gnutls_certificate_type_get(session._c_object)
1432
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1433
# ...do the normal thing
1434
return session.peer_certificate
1435
list_size = ctypes.c_uint(1)
1436
cert_list = (gnutls.library.functions
1437
.gnutls_certificate_get_peers
1438
(session._c_object, ctypes.byref(list_size)))
1439
if not bool(cert_list) and list_size.value != 0:
1440
raise gnutls.errors.GNUTLSError("error getting peer"
1442
if list_size.value == 0:
1445
return ctypes.string_at(cert.data, cert.size)
1448
def fingerprint(openpgp):
1449
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1450
# New GnuTLS "datum" with the OpenPGP public key
1451
datum = (gnutls.library.types
1452
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1455
ctypes.c_uint(len(openpgp))))
1456
# New empty GnuTLS certificate
1457
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1458
(gnutls.library.functions
1459
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1460
# Import the OpenPGP public key into the certificate
1461
(gnutls.library.functions
1462
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1463
gnutls.library.constants
1464
.GNUTLS_OPENPGP_FMT_RAW))
1465
# Verify the self signature in the key
1466
crtverify = ctypes.c_uint()
1467
(gnutls.library.functions
1468
.gnutls_openpgp_crt_verify_self(crt, 0,
1469
ctypes.byref(crtverify)))
1470
if crtverify.value != 0:
1471
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1472
raise (gnutls.errors.CertificateSecurityError
1474
# New buffer for the fingerprint
1475
buf = ctypes.create_string_buffer(20)
1476
buf_len = ctypes.c_size_t()
1477
# Get the fingerprint from the certificate into the buffer
1478
(gnutls.library.functions
1479
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1480
ctypes.byref(buf_len)))
1481
# Deinit the certificate
1482
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1483
# Convert the buffer to a Python bytestring
1484
fpr = ctypes.string_at(buf, buf_len.value)
1485
# Convert the bytestring to hexadecimal notation
1486
hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1490
class MultiprocessingMixIn(object):
1491
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1492
def sub_process_main(self, request, address):
1494
self.finish_request(request, address)
1496
self.handle_error(request, address)
1497
self.close_request(request)
1499
def process_request(self, request, address):
1500
"""Start a new process to process the request."""
1501
multiprocessing.Process(target = self.sub_process_main,
1502
args = (request, address)).start()
1505
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1506
""" adds a pipe to the MixIn """
1507
def process_request(self, request, client_address):
1508
"""Overrides and wraps the original process_request().
1510
This function creates a new pipe in self.pipe
1512
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1514
super(MultiprocessingMixInWithPipe,
1515
self).process_request(request, client_address)
1516
self.child_pipe.close()
1517
self.add_pipe(parent_pipe)
1519
def add_pipe(self, parent_pipe):
1520
"""Dummy function; override as necessary"""
1521
raise NotImplementedError
1524
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1525
socketserver.TCPServer, object):
1526
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
727
settings: Server settings
728
clients: Set() of Client objects
729
1529
enabled: Boolean; whether this server is activated yet
1530
interface: None or a network interface name (string)
1531
use_ipv6: Boolean; to use IPv6 or not
731
address_family = socket.AF_INET6
732
def __init__(self, *args, **kwargs):
733
if "settings" in kwargs:
734
self.settings = kwargs["settings"]
735
del kwargs["settings"]
736
if "clients" in kwargs:
737
self.clients = kwargs["clients"]
738
del kwargs["clients"]
740
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1533
def __init__(self, server_address, RequestHandlerClass,
1534
interface=None, use_ipv6=True):
1535
self.interface = interface
1537
self.address_family = socket.AF_INET6
1538
socketserver.TCPServer.__init__(self, server_address,
1539
RequestHandlerClass)
741
1540
def server_bind(self):
742
1541
"""This overrides the normal server_bind() function
743
1542
to bind to an interface if one was specified, and also NOT to
744
1543
bind to an address or port if they were not specified."""
745
if self.settings["interface"]:
746
# 25 is from /usr/include/asm-i486/socket.h
747
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
749
self.socket.setsockopt(socket.SOL_SOCKET,
751
self.settings["interface"])
752
except socket.error, error:
753
if error[0] == errno.EPERM:
754
logger.error(u"No permission to"
755
u" bind to interface %s",
756
self.settings["interface"])
1544
if self.interface is not None:
1545
if SO_BINDTODEVICE is None:
1546
logger.error("SO_BINDTODEVICE does not exist;"
1547
" cannot bind to interface %s",
1551
self.socket.setsockopt(socket.SOL_SOCKET,
1555
except socket.error as error:
1556
if error[0] == errno.EPERM:
1557
logger.error("No permission to"
1558
" bind to interface %s",
1560
elif error[0] == errno.ENOPROTOOPT:
1561
logger.error("SO_BINDTODEVICE not available;"
1562
" cannot bind to interface %s",
759
1566
# Only bind(2) the socket if we really need to.
760
1567
if self.server_address[0] or self.server_address[1]:
761
1568
if not self.server_address[0]:
763
self.server_address = (in6addr_any,
1569
if self.address_family == socket.AF_INET6:
1570
any_address = "::" # in6addr_any
1572
any_address = socket.INADDR_ANY
1573
self.server_address = (any_address,
764
1574
self.server_address[1])
765
1575
elif not self.server_address[1]:
766
1576
self.server_address = (self.server_address[0],
768
# if self.settings["interface"]:
1578
# if self.interface:
769
1579
# self.server_address = (self.server_address[0],
772
1582
# if_nametoindex
775
return super(IPv6_TCPServer, self).server_bind()
1584
return socketserver.TCPServer.server_bind(self)
1587
class MandosServer(IPv6_TCPServer):
1591
clients: set of Client objects
1592
gnutls_priority GnuTLS priority string
1593
use_dbus: Boolean; to emit D-Bus signals or not
1595
Assumes a gobject.MainLoop event loop.
1597
def __init__(self, server_address, RequestHandlerClass,
1598
interface=None, use_ipv6=True, clients=None,
1599
gnutls_priority=None, use_dbus=True):
1600
self.enabled = False
1601
self.clients = clients
1602
if self.clients is None:
1603
self.clients = set()
1604
self.use_dbus = use_dbus
1605
self.gnutls_priority = gnutls_priority
1606
IPv6_TCPServer.__init__(self, server_address,
1607
RequestHandlerClass,
1608
interface = interface,
1609
use_ipv6 = use_ipv6)
776
1610
def server_activate(self):
777
1611
if self.enabled:
778
return super(IPv6_TCPServer, self).server_activate()
1612
return socketserver.TCPServer.server_activate(self)
779
1613
def enable(self):
780
1614
self.enabled = True
1615
def add_pipe(self, parent_pipe):
1616
# Call "handle_ipc" for both data and EOF events
1617
gobject.io_add_watch(parent_pipe.fileno(),
1618
gobject.IO_IN | gobject.IO_HUP,
1619
functools.partial(self.handle_ipc,
1620
parent_pipe = parent_pipe))
1622
def handle_ipc(self, source, condition, parent_pipe=None,
1623
client_object=None):
1625
gobject.IO_IN: "IN", # There is data to read.
1626
gobject.IO_OUT: "OUT", # Data can be written (without
1628
gobject.IO_PRI: "PRI", # There is urgent data to read.
1629
gobject.IO_ERR: "ERR", # Error condition.
1630
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1631
# broken, usually for pipes and
1634
conditions_string = ' | '.join(name
1636
condition_names.iteritems()
1637
if cond & condition)
1638
# error or the other end of multiprocessing.Pipe has closed
1639
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1642
# Read a request from the child
1643
request = parent_pipe.recv()
1644
command = request[0]
1646
if command == 'init':
1648
address = request[2]
1650
for c in self.clients:
1651
if c.fingerprint == fpr:
1655
logger.info("Client not found for fingerprint: %s, ad"
1656
"dress: %s", fpr, address)
1659
mandos_dbus_service.ClientNotFound(fpr, address[0])
1660
parent_pipe.send(False)
1663
gobject.io_add_watch(parent_pipe.fileno(),
1664
gobject.IO_IN | gobject.IO_HUP,
1665
functools.partial(self.handle_ipc,
1666
parent_pipe = parent_pipe,
1667
client_object = client))
1668
parent_pipe.send(True)
1669
# remove the old hook in favor of the new above hook on same fileno
1671
if command == 'funcall':
1672
funcname = request[1]
1676
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1678
if command == 'getattr':
1679
attrname = request[1]
1680
if callable(client_object.__getattribute__(attrname)):
1681
parent_pipe.send(('function',))
1683
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1685
if command == 'setattr':
1686
attrname = request[1]
1688
setattr(client_object, attrname, value)
783
1693
def string_to_delta(interval):
784
1694
"""Parse a string and return a datetime.timedelta
786
1696
>>> string_to_delta('7d')
787
1697
datetime.timedelta(7)
788
1698
>>> string_to_delta('60s')
929
1829
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
930
1830
"servicename": "Mandos",
931
1831
"use_dbus": "True",
934
1836
# Parse config file for server-global settings
935
server_config = ConfigParser.SafeConfigParser(server_defaults)
1837
server_config = configparser.SafeConfigParser(server_defaults)
936
1838
del server_defaults
937
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1839
server_config.read(os.path.join(options.configdir,
938
1841
# Convert the SafeConfigParser object to a dict
939
1842
server_settings = server_config.defaults()
940
# Use getboolean on the boolean config options
941
server_settings["debug"] = (server_config.getboolean
942
("DEFAULT", "debug"))
943
server_settings["use_dbus"] = (server_config.getboolean
944
("DEFAULT", "use_dbus"))
1843
# Use the appropriate methods on the non-string config options
1844
for option in ("debug", "use_dbus", "use_ipv6"):
1845
server_settings[option] = server_config.getboolean("DEFAULT",
1847
if server_settings["port"]:
1848
server_settings["port"] = server_config.getint("DEFAULT",
945
1850
del server_config
947
1852
# Override the settings from the config file with command line
948
1853
# options, if set.
949
1854
for option in ("interface", "address", "port", "debug",
950
1855
"priority", "servicename", "configdir",
1856
"use_dbus", "use_ipv6", "debuglevel"):
952
1857
value = getattr(options, option)
953
1858
if value is not None:
954
1859
server_settings[option] = value
1861
# Force all strings to be unicode
1862
for option in server_settings.keys():
1863
if type(server_settings[option]) is str:
1864
server_settings[option] = unicode(server_settings[option])
956
1865
# Now we have our good server settings in "server_settings"
1867
##################################################################
958
1869
# For convenience
959
1870
debug = server_settings["debug"]
1871
debuglevel = server_settings["debuglevel"]
960
1872
use_dbus = server_settings["use_dbus"]
963
syslogger.setLevel(logging.WARNING)
964
console.setLevel(logging.WARNING)
1873
use_ipv6 = server_settings["use_ipv6"]
966
1875
if server_settings["servicename"] != "Mandos":
967
1876
syslogger.setFormatter(logging.Formatter
968
('Mandos (%s): %%(levelname)s:'
1877
('Mandos (%s) [%%(process)d]:'
1878
' %%(levelname)s: %%(message)s'
970
1879
% server_settings["servicename"]))
972
1881
# Parse config file with clients
973
client_defaults = { "timeout": "1h",
975
"checker": "fping -q -- %(host)s",
1882
client_defaults = { "timeout": "5m",
1883
"extended_timeout": "15m",
1885
"checker": "fping -q -- %%(host)s",
1887
"approval_delay": "0s",
1888
"approval_duration": "1s",
978
client_config = ConfigParser.SafeConfigParser(client_defaults)
1890
client_config = configparser.SafeConfigParser(client_defaults)
979
1891
client_config.read(os.path.join(server_settings["configdir"],
980
1892
"clients.conf"))
983
tcp_server = IPv6_TCPServer((server_settings["address"],
984
server_settings["port"]),
986
settings=server_settings,
988
pidfilename = "/var/run/mandos.pid"
990
pidfile = open(pidfilename, "w")
991
except IOError, error:
992
logger.error("Could not open file %r", pidfilename)
1894
global mandos_dbus_service
1895
mandos_dbus_service = None
1897
tcp_server = MandosServer((server_settings["address"],
1898
server_settings["port"]),
1900
interface=(server_settings["interface"]
1904
server_settings["priority"],
1907
pidfilename = "/var/run/mandos.pid"
1909
pidfile = open(pidfilename, "w")
1911
logger.error("Could not open file %r", pidfilename)
995
1914
uid = pwd.getpwnam("_mandos").pw_uid
1915
gid = pwd.getpwnam("_mandos").pw_gid
996
1916
except KeyError:
998
1918
uid = pwd.getpwnam("mandos").pw_uid
1919
gid = pwd.getpwnam("mandos").pw_gid
999
1920
except KeyError:
1001
1922
uid = pwd.getpwnam("nobody").pw_uid
1923
gid = pwd.getpwnam("nobody").pw_gid
1002
1924
except KeyError:
1005
gid = pwd.getpwnam("_mandos").pw_gid
1008
gid = pwd.getpwnam("mandos").pw_gid
1011
gid = pwd.getpwnam("nogroup").pw_gid
1017
except OSError, error:
1930
except OSError as error:
1018
1931
if error[0] != errno.EPERM:
1022
service = AvahiService(name = server_settings["servicename"],
1023
servicetype = "_mandos._tcp", )
1024
if server_settings["interface"]:
1025
service.interface = (if_nametoindex
1026
(server_settings["interface"]))
1031
# From the Avahi example code
1032
DBusGMainLoop(set_as_default=True )
1033
main_loop = gobject.MainLoop()
1034
bus = dbus.SystemBus()
1035
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1036
avahi.DBUS_PATH_SERVER),
1037
avahi.DBUS_INTERFACE_SERVER)
1038
# End of Avahi example code
1040
bus_name = dbus.service.BusName(u"org.mandos-system.Mandos",
1043
clients.update(Set(Client(name = section,
1045
= dict(client_config.items(section)),
1046
use_dbus = use_dbus)
1047
for section in client_config.sections()))
1049
logger.warning(u"No clients defined")
1934
if not debug and not debuglevel:
1935
syslogger.setLevel(logging.WARNING)
1936
console.setLevel(logging.WARNING)
1938
level = getattr(logging, debuglevel.upper())
1939
syslogger.setLevel(level)
1940
console.setLevel(level)
1943
# Enable all possible GnuTLS debugging
1945
# "Use a log level over 10 to enable all debugging options."
1947
gnutls.library.functions.gnutls_global_set_log_level(11)
1949
@gnutls.library.types.gnutls_log_func
1950
def debug_gnutls(level, string):
1951
logger.debug("GnuTLS: %s", string[:-1])
1953
(gnutls.library.functions
1954
.gnutls_global_set_log_function(debug_gnutls))
1052
1956
# Redirect stdin so all checkers get /dev/null
1053
1957
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1054
1958
os.dup2(null, sys.stdin.fileno())
1058
1962
# No console logging
1059
1963
logger.removeHandler(console)
1965
# Need to fork before connecting to D-Bus
1060
1967
# Close all input and output, do double fork, etc.
1065
pidfile.write(str(pid) + "\n")
1069
logger.error(u"Could not write to file %r with PID %d",
1072
# "pidfile" was never created
1077
"Cleanup function; run on exit"
1079
# From the Avahi example code
1080
if not group is None:
1083
# End of Avahi example code
1971
# From the Avahi example code
1972
DBusGMainLoop(set_as_default=True )
1973
main_loop = gobject.MainLoop()
1974
bus = dbus.SystemBus()
1975
# End of Avahi example code
1978
bus_name = dbus.service.BusName("se.recompile.Mandos",
1979
bus, do_not_queue=True)
1980
old_bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
1981
bus, do_not_queue=True)
1982
except dbus.exceptions.NameExistsException as e:
1983
logger.error(unicode(e) + ", disabling D-Bus")
1985
server_settings["use_dbus"] = False
1986
tcp_server.use_dbus = False
1987
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1988
service = AvahiService(name = server_settings["servicename"],
1989
servicetype = "_mandos._tcp",
1990
protocol = protocol, bus = bus)
1991
if server_settings["interface"]:
1992
service.interface = (if_nametoindex
1993
(str(server_settings["interface"])))
1995
global multiprocessing_manager
1996
multiprocessing_manager = multiprocessing.Manager()
1998
client_class = Client
2000
client_class = functools.partial(ClientDBusTransitional, bus = bus)
2001
def client_config_items(config, section):
2002
special_settings = {
2003
"approved_by_default":
2004
lambda: config.getboolean(section,
2005
"approved_by_default"),
2007
for name, value in config.items(section):
2009
yield (name, special_settings[name]())
2013
tcp_server.clients.update(set(
2014
client_class(name = section,
2015
config= dict(client_config_items(
2016
client_config, section)))
2017
for section in client_config.sections()))
2018
if not tcp_server.clients:
2019
logger.warning("No clients defined")
1086
client = clients.pop()
1087
client.disable_hook = None
1090
atexit.register(cleanup)
2025
pidfile.write(str(pid) + "\n".encode("utf-8"))
2028
logger.error("Could not write to file %r with PID %d",
2031
# "pidfile" was never created
1093
2035
signal.signal(signal.SIGINT, signal.SIG_IGN)
1094
2037
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1095
2038
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1098
class MandosServer(dbus.service.Object):
2041
class MandosDBusService(dbus.service.Object):
1099
2042
"""A D-Bus proxy object"""
1100
2043
def __init__(self):
1101
dbus.service.Object.__init__(self, bus,
1103
_interface = u"org.mandos_system.Mandos"
1105
@dbus.service.signal(_interface, signature="oa{sv}")
1106
def ClientAdded(self, objpath, properties):
2044
dbus.service.Object.__init__(self, bus, "/")
2045
_interface = "se.recompile.Mandos"
1110
2047
@dbus.service.signal(_interface, signature="o")
1111
def ClientRemoved(self, objpath):
2048
def ClientAdded(self, objpath):
2052
@dbus.service.signal(_interface, signature="ss")
2053
def ClientNotFound(self, fingerprint, address):
2057
@dbus.service.signal(_interface, signature="os")
2058
def ClientRemoved(self, objpath, name):
1115
2062
@dbus.service.method(_interface, out_signature="ao")
1116
2063
def GetAllClients(self):
1117
return dbus.Array(c.dbus_object_path for c in clients)
1119
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
2065
return dbus.Array(c.dbus_object_path
2066
for c in tcp_server.clients)
2068
@dbus.service.method(_interface,
2069
out_signature="a{oa{sv}}")
1120
2070
def GetAllClientsWithProperties(self):
1121
2072
return dbus.Dictionary(
1122
((c.dbus_object_path, c.GetAllProperties())
2073
((c.dbus_object_path, c.GetAll(""))
2074
for c in tcp_server.clients),
1124
2075
signature="oa{sv}")
1126
2077
@dbus.service.method(_interface, in_signature="o")
1127
2078
def RemoveClient(self, object_path):
2080
for c in tcp_server.clients:
1129
2081
if c.dbus_object_path == object_path:
2082
tcp_server.clients.remove(c)
2083
c.remove_from_connection()
1131
2084
# Don't signal anything except ClientRemoved
2085
c.disable(quiet=True)
1134
2086
# Emit D-Bus signal
1135
self.ClientRemoved(object_path)
2087
self.ClientRemoved(object_path, c.name)
1138
@dbus.service.method(_interface)
2089
raise KeyError(object_path)
1144
mandos_server = MandosServer()
1146
for client in clients:
2093
class MandosDBusServiceTransitional(MandosDBusService):
2094
__metaclass__ = AlternateDBusNamesMetaclass
2095
mandos_dbus_service = MandosDBusServiceTransitional()
2098
"Cleanup function; run on exit"
2101
while tcp_server.clients:
2102
client = tcp_server.clients.pop()
2104
client.remove_from_connection()
2105
client.disable_hook = None
2106
# Don't signal anything except ClientRemoved
2107
client.disable(quiet=True)
2110
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2113
atexit.register(cleanup)
2115
for client in tcp_server.clients:
1148
2117
# Emit D-Bus signal
1149
mandos_server.ClientAdded(client.dbus_object_path,
1150
client.GetAllProperties())
2118
mandos_dbus_service.ClientAdded(client.dbus_object_path)
1151
2119
client.enable()
1153
2121
tcp_server.enable()