150
124
self.rename_count = 0
151
125
self.max_renames = max_renames
152
self.protocol = protocol
153
self.group = None # our entry group
156
self.entry_group_state_changed_match = None
157
126
def rename(self):
158
127
"""Derived from the Avahi example code"""
159
128
if self.rename_count >= self.max_renames:
160
logger.critical("No suitable Zeroconf service name found"
161
" after %i retries, exiting.",
129
logger.critical(u"No suitable Zeroconf service name found"
130
u" after %i retries, exiting.",
162
131
self.rename_count)
163
raise AvahiServiceError("Too many renames")
164
self.name = unicode(self.server
165
.GetAlternativeServiceName(self.name))
166
logger.info("Changing Zeroconf service name to %r ...",
132
raise AvahiServiceError(u"Too many renames")
133
self.name = server.GetAlternativeServiceName(self.name)
134
logger.info(u"Changing Zeroconf service name to %r ...",
136
syslogger.setFormatter(logging.Formatter
137
('Mandos (%s): %%(levelname)s:'
138
' %%(message)s' % self.name))
171
except dbus.exceptions.DBusException as error:
172
logger.critical("DBusException: %s", error)
175
141
self.rename_count += 1
176
142
def remove(self):
177
143
"""Derived from the Avahi example code"""
178
if self.entry_group_state_changed_match is not None:
179
self.entry_group_state_changed_match.remove()
180
self.entry_group_state_changed_match = None
181
if self.group is not None:
144
if group is not None:
184
147
"""Derived from the Avahi example code"""
186
if self.group is None:
187
self.group = dbus.Interface(
188
self.bus.get_object(avahi.DBUS_NAME,
189
self.server.EntryGroupNew()),
190
avahi.DBUS_INTERFACE_ENTRY_GROUP)
191
self.entry_group_state_changed_match = (
192
self.group.connect_to_signal(
193
'StateChanged', self.entry_group_state_changed))
194
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
195
self.name, self.type)
196
self.group.AddService(
199
dbus.UInt32(0), # flags
200
self.name, self.type,
201
self.domain, self.host,
202
dbus.UInt16(self.port),
203
avahi.string_array_to_txt_array(self.TXT))
205
def entry_group_state_changed(self, state, error):
206
"""Derived from the Avahi example code"""
207
logger.debug("Avahi entry group state change: %i", state)
209
if state == avahi.ENTRY_GROUP_ESTABLISHED:
210
logger.debug("Zeroconf service established.")
211
elif state == avahi.ENTRY_GROUP_COLLISION:
212
logger.info("Zeroconf service name collision.")
214
elif state == avahi.ENTRY_GROUP_FAILURE:
215
logger.critical("Avahi: Error in group state changed %s",
217
raise AvahiGroupError("State changed: %s"
220
"""Derived from the Avahi example code"""
221
if self.group is not None:
224
except (dbus.exceptions.UnknownMethodException,
225
dbus.exceptions.DBusException) as e:
229
def server_state_changed(self, state, error=None):
230
"""Derived from the Avahi example code"""
231
logger.debug("Avahi server state change: %i", state)
232
bad_states = { avahi.SERVER_INVALID:
233
"Zeroconf server invalid",
234
avahi.SERVER_REGISTERING: None,
235
avahi.SERVER_COLLISION:
236
"Zeroconf server name collision",
237
avahi.SERVER_FAILURE:
238
"Zeroconf server failure" }
239
if state in bad_states:
240
if bad_states[state] is not None:
242
logger.error(bad_states[state])
244
logger.error(bad_states[state] + ": %r", error)
246
elif state == avahi.SERVER_RUNNING:
250
logger.debug("Unknown state: %r", state)
252
logger.debug("Unknown state: %r: %r", state, error)
254
"""Derived from the Avahi example code"""
255
if self.server is None:
256
self.server = dbus.Interface(
257
self.bus.get_object(avahi.DBUS_NAME,
258
avahi.DBUS_PATH_SERVER,
259
follow_name_owner_changes=True),
260
avahi.DBUS_INTERFACE_SERVER)
261
self.server.connect_to_signal("StateChanged",
262
self.server_state_changed)
263
self.server_state_changed(self.server.GetState())
265
class AvahiServiceToSyslog(AvahiService):
267
"""Add the new name to the syslog messages"""
268
ret = AvahiService.rename(self)
269
syslogger.setFormatter(logging.Formatter
270
('Mandos (%s) [%%(process)d]:'
271
' %%(levelname)s: %%(message)s'
275
def _timedelta_to_milliseconds(td):
276
"Convert a datetime.timedelta() to milliseconds"
277
return ((td.days * 24 * 60 * 60 * 1000)
278
+ (td.seconds * 1000)
279
+ (td.microseconds // 1000))
281
class Client(object):
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):
282
179
"""A representation of a client host served by this server.
285
_approved: bool(); 'None' if not yet approved/disapproved
286
approval_delay: datetime.timedelta(); Time to wait for approval
287
approval_duration: datetime.timedelta(); Duration of one approval
181
name: string; from the config file, used in log messages
182
fingerprint: string (40 or 32 hexadecimal digits); used to
183
uniquely identify the client
184
secret: bytestring; sent verbatim (over TLS) to client
185
host: string; available for use by the checker command
186
created: datetime.datetime(); (UTC) object creation
187
last_enabled: datetime.datetime(); (UTC)
189
last_checked_ok: datetime.datetime(); (UTC) or None
190
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)
288
194
checker: subprocess.Popen(); a running checker process used
289
195
to see if the client lives.
290
196
'None' if no process is running.
291
checker_callback_tag: a gobject event source tag, or None
292
checker_command: string; External command which is run to check
293
if client lives. %() expansions are done at
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
294
202
runtime with vars(self) as dict, so that for
295
203
instance %(name)s can be used in the command.
296
checker_initiator_tag: a gobject event source tag, or None
297
created: datetime.datetime(); (UTC) object creation
298
current_checker_command: string; current running checker_command
299
disable_hook: If set, called by disable() as disable_hook(self)
300
disable_initiator_tag: a gobject event source tag, or None
302
fingerprint: string (40 or 32 hexadecimal digits); used to
303
uniquely identify the client
304
host: string; available for use by the checker command
305
interval: datetime.timedelta(); How often to start a new checker
306
last_approval_request: datetime.datetime(); (UTC) or None
307
last_checked_ok: datetime.datetime(); (UTC) or None
308
last_enabled: datetime.datetime(); (UTC)
309
name: string; from the config file, used in log messages and
311
secret: bytestring; sent verbatim (over TLS) to client
312
timeout: datetime.timedelta(); How long from last_checked_ok
313
until this client is disabled
314
extended_timeout: extra long timeout when password has been sent
315
runtime_expansions: Allowed attributes for runtime expansion.
316
expires: datetime.datetime(); time (UTC) when a client will be
204
use_dbus: bool(); Whether to provide D-Bus interface and signals
205
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
320
runtime_expansions = ("approval_delay", "approval_duration",
321
"created", "enabled", "fingerprint",
322
"host", "interval", "last_checked_ok",
323
"last_enabled", "name", "timeout")
325
207
def timeout_milliseconds(self):
326
208
"Return the 'timeout' attribute in milliseconds"
327
return _timedelta_to_milliseconds(self.timeout)
329
def extended_timeout_milliseconds(self):
330
"Return the 'extended_timeout' attribute in milliseconds"
331
return _timedelta_to_milliseconds(self.extended_timeout)
209
return ((self.timeout.days * 24 * 60 * 60 * 1000)
210
+ (self.timeout.seconds * 1000)
211
+ (self.timeout.microseconds // 1000))
333
213
def interval_milliseconds(self):
334
214
"Return the 'interval' attribute in milliseconds"
335
return _timedelta_to_milliseconds(self.interval)
337
def approval_delay_milliseconds(self):
338
return _timedelta_to_milliseconds(self.approval_delay)
340
def __init__(self, name = None, disable_hook=None, config=None):
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,
341
221
"""Note: the 'checker' key in 'config' sets the
342
222
'checker_command' attribute and *not* the 'checker'
345
225
if config is None:
347
logger.debug("Creating client %r", self.name)
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)
348
235
# Uppercase and remove spaces from fingerprint for later
349
236
# comparison purposes with return value from the fingerprint()
351
238
self.fingerprint = (config["fingerprint"].upper()
353
logger.debug(" Fingerprint: %s", self.fingerprint)
240
logger.debug(u" Fingerprint: %s", self.fingerprint)
354
241
if "secret" in config:
355
self.secret = config["secret"].decode("base64")
242
self.secret = config["secret"].decode(u"base64")
356
243
elif "secfile" in config:
357
with open(os.path.expanduser(os.path.expandvars
358
(config["secfile"])),
244
with closing(open(os.path.expanduser
246
(config["secfile"])))) as secfile:
360
247
self.secret = secfile.read()
362
raise TypeError("No secret or secfile for client %s"
249
raise TypeError(u"No secret or secfile for client %s"
364
251
self.host = config.get("host", "")
365
252
self.created = datetime.datetime.utcnow()
366
253
self.enabled = False
367
self.last_approval_request = None
368
254
self.last_enabled = None
369
255
self.last_checked_ok = None
370
256
self.timeout = string_to_delta(config["timeout"])
371
self.extended_timeout = string_to_delta(config
372
["extended_timeout"])
373
257
self.interval = string_to_delta(config["interval"])
374
258
self.disable_hook = disable_hook
375
259
self.checker = None
376
260
self.checker_initiator_tag = None
377
261
self.disable_initiator_tag = None
379
262
self.checker_callback_tag = None
380
263
self.checker_command = config["checker"]
381
self.current_checker_command = None
382
self.last_connect = None
383
self._approved = None
384
self.approved_by_default = config.get("approved_by_default",
386
self.approvals_pending = 0
387
self.approval_delay = string_to_delta(
388
config["approval_delay"])
389
self.approval_duration = string_to_delta(
390
config["approval_duration"])
391
self.changedstate = (multiprocessing_manager
392
.Condition(multiprocessing_manager
395
def send_changedstate(self):
396
self.changedstate.acquire()
397
self.changedstate.notify_all()
398
self.changedstate.release()
400
265
def enable(self):
401
266
"""Start this client's checker and timeout hooks"""
402
if getattr(self, "enabled", False):
405
self.send_changedstate()
267
self.last_enabled = datetime.datetime.utcnow()
406
268
# Schedule a new checker to be started an 'interval' from now,
407
269
# and every interval from then on.
408
270
self.checker_initiator_tag = (gobject.timeout_add
409
271
(self.interval_milliseconds(),
410
272
self.start_checker))
273
# Also start a new checker *right now*.
411
275
# Schedule a disable() when 'timeout' has passed
412
self.expires = datetime.datetime.utcnow() + self.timeout
413
276
self.disable_initiator_tag = (gobject.timeout_add
414
277
(self.timeout_milliseconds(),
416
279
self.enabled = True
417
self.last_enabled = datetime.datetime.utcnow()
418
# Also start a new checker *right now*.
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,
421
def disable(self, quiet=True):
422
289
"""Disable this client."""
423
290
if not getattr(self, "enabled", False):
426
self.send_changedstate()
428
logger.info("Disabling client %s", self.name)
292
logger.info(u"Disabling client %s", self.name)
429
293
if getattr(self, "disable_initiator_tag", False):
430
294
gobject.source_remove(self.disable_initiator_tag)
431
295
self.disable_initiator_tag = None
433
296
if getattr(self, "checker_initiator_tag", False):
434
297
gobject.source_remove(self.checker_initiator_tag)
435
298
self.checker_initiator_tag = None
563
423
self.checker_callback_tag = None
564
424
if getattr(self, "checker", None) is None:
566
logger.debug("Stopping checker for %(name)s", vars(self))
426
logger.debug(u"Stopping checker for %(name)s", vars(self))
568
428
os.kill(self.checker.pid, signal.SIGTERM)
570
430
#if self.checker.poll() is None:
571
431
# os.kill(self.checker.pid, signal.SIGKILL)
572
except OSError as error:
432
except OSError, error:
573
433
if error.errno != errno.ESRCH: # No such process
575
435
self.checker = None
578
def dbus_service_property(dbus_interface, signature="v",
579
access="readwrite", byte_arrays=False):
580
"""Decorators for marking methods of a DBusObjectWithProperties to
581
become properties on the D-Bus.
583
The decorated method will be called with no arguments by "Get"
584
and with one argument by "Set".
586
The parameters, where they are supported, are the same as
587
dbus.service.method, except there is only "signature", since the
588
type from Get() and the type sent to Set() is the same.
590
# Encoding deeply encoded byte arrays is not supported yet by the
591
# "Set" method, so we fail early here:
592
if byte_arrays and signature != "ay":
593
raise ValueError("Byte arrays not supported for non-'ay'"
594
" signature %r" % signature)
596
func._dbus_is_property = True
597
func._dbus_interface = dbus_interface
598
func._dbus_signature = signature
599
func._dbus_access = access
600
func._dbus_name = func.__name__
601
if func._dbus_name.endswith("_dbus_property"):
602
func._dbus_name = func._dbus_name[:-14]
603
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
608
class DBusPropertyException(dbus.exceptions.DBusException):
609
"""A base class for D-Bus property-related exceptions
611
def __unicode__(self):
612
return unicode(str(self))
615
class DBusPropertyAccessException(DBusPropertyException):
616
"""A property's access permissions disallows an operation.
621
class DBusPropertyNotFound(DBusPropertyException):
622
"""An attempt was made to access a non-existing property.
627
class DBusObjectWithProperties(dbus.service.Object):
628
"""A D-Bus object with properties.
630
Classes inheriting from this can use the dbus_service_property
631
decorator to expose methods as D-Bus properties. It exposes the
632
standard Get(), Set(), and GetAll() methods on the D-Bus.
636
def _is_dbus_property(obj):
637
return getattr(obj, "_dbus_is_property", False)
639
def _get_all_dbus_properties(self):
640
"""Returns a generator of (name, attribute) pairs
642
return ((prop.__get__(self)._dbus_name, prop.__get__(self))
643
for cls in self.__class__.__mro__
645
inspect.getmembers(cls, self._is_dbus_property))
647
def _get_dbus_property(self, interface_name, property_name):
648
"""Returns a bound method if one exists which is a D-Bus
649
property with the specified name and interface.
651
for cls in self.__class__.__mro__:
652
for name, value in (inspect.getmembers
653
(cls, self._is_dbus_property)):
654
if (value._dbus_name == property_name
655
and value._dbus_interface == interface_name):
656
return value.__get__(self)
659
raise DBusPropertyNotFound(self.dbus_object_path + ":"
660
+ interface_name + "."
663
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
665
def Get(self, interface_name, property_name):
666
"""Standard D-Bus property Get() method, see D-Bus standard.
668
prop = self._get_dbus_property(interface_name, property_name)
669
if prop._dbus_access == "write":
670
raise DBusPropertyAccessException(property_name)
672
if not hasattr(value, "variant_level"):
674
return type(value)(value, variant_level=value.variant_level+1)
676
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
677
def Set(self, interface_name, property_name, value):
678
"""Standard D-Bus property Set() method, see D-Bus standard.
680
prop = self._get_dbus_property(interface_name, property_name)
681
if prop._dbus_access == "read":
682
raise DBusPropertyAccessException(property_name)
683
if prop._dbus_get_args_options["byte_arrays"]:
684
# The byte_arrays option is not supported yet on
685
# signatures other than "ay".
686
if prop._dbus_signature != "ay":
688
value = dbus.ByteArray(''.join(unichr(byte)
692
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
693
out_signature="a{sv}")
694
def GetAll(self, interface_name):
695
"""Standard D-Bus property GetAll() method, see D-Bus
698
Note: Will not include properties with access="write".
701
for name, prop in self._get_all_dbus_properties():
703
and interface_name != prop._dbus_interface):
704
# Interface non-empty but did not match
706
# Ignore write-only properties
707
if prop._dbus_access == "write":
710
if not hasattr(value, "variant_level"):
713
all[name] = type(value)(value, variant_level=
714
value.variant_level+1)
715
return dbus.Dictionary(all, signature="sv")
717
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
719
path_keyword='object_path',
720
connection_keyword='connection')
721
def Introspect(self, object_path, connection):
722
"""Standard D-Bus method, overloaded to insert property tags.
724
xmlstring = dbus.service.Object.Introspect(self, object_path,
727
document = xml.dom.minidom.parseString(xmlstring)
728
def make_tag(document, name, prop):
729
e = document.createElement("property")
730
e.setAttribute("name", name)
731
e.setAttribute("type", prop._dbus_signature)
732
e.setAttribute("access", prop._dbus_access)
734
for if_tag in document.getElementsByTagName("interface"):
735
for tag in (make_tag(document, name, prop)
737
in self._get_all_dbus_properties()
738
if prop._dbus_interface
739
== if_tag.getAttribute("name")):
740
if_tag.appendChild(tag)
741
# Add the names to the return values for the
742
# "org.freedesktop.DBus.Properties" methods
743
if (if_tag.getAttribute("name")
744
== "org.freedesktop.DBus.Properties"):
745
for cn in if_tag.getElementsByTagName("method"):
746
if cn.getAttribute("name") == "Get":
747
for arg in cn.getElementsByTagName("arg"):
748
if (arg.getAttribute("direction")
750
arg.setAttribute("name", "value")
751
elif cn.getAttribute("name") == "GetAll":
752
for arg in cn.getElementsByTagName("arg"):
753
if (arg.getAttribute("direction")
755
arg.setAttribute("name", "props")
756
xmlstring = document.toxml("utf-8")
758
except (AttributeError, xml.dom.DOMException,
759
xml.parsers.expat.ExpatError) as error:
760
logger.error("Failed to override Introspection method",
765
def datetime_to_dbus (dt, variant_level=0):
766
"""Convert a UTC datetime.datetime() to a D-Bus type."""
768
return dbus.String("", variant_level = variant_level)
769
return dbus.String(dt.isoformat(),
770
variant_level=variant_level)
772
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
774
"""Applied to an empty subclass of a D-Bus object, this metaclass
775
will add additional D-Bus attributes matching a certain pattern.
777
def __new__(mcs, name, bases, attr):
778
# Go through all the base classes which could have D-Bus
779
# methods, signals, or properties in them
780
for base in (b for b in bases
781
if issubclass(b, dbus.service.Object)):
782
# Go though all attributes of the base class
783
for attrname, attribute in inspect.getmembers(base):
784
# Ignore non-D-Bus attributes, and D-Bus attributes
785
# with the wrong interface name
786
if (not hasattr(attribute, "_dbus_interface")
787
or not attribute._dbus_interface
788
.startswith("se.recompile.Mandos")):
790
# Create an alternate D-Bus interface name based on
792
alt_interface = (attribute._dbus_interface
793
.replace("se.recompile.Mandos",
794
"se.bsnet.fukt.Mandos"))
795
# Is this a D-Bus signal?
796
if getattr(attribute, "_dbus_is_signal", False):
797
# Extract the original non-method function by
799
nonmethod_func = (dict(
800
zip(attribute.func_code.co_freevars,
801
attribute.__closure__))["func"]
803
# Create a new, but exactly alike, function
804
# object, and decorate it to be a new D-Bus signal
805
# with the alternate D-Bus interface name
806
new_function = (dbus.service.signal
808
attribute._dbus_signature)
810
nonmethod_func.func_code,
811
nonmethod_func.func_globals,
812
nonmethod_func.func_name,
813
nonmethod_func.func_defaults,
814
nonmethod_func.func_closure)))
815
# Define a creator of a function to call both the
816
# old and new functions, so both the old and new
817
# signals gets sent when the function is called
818
def fixscope(func1, func2):
819
"""This function is a scope container to pass
820
func1 and func2 to the "call_both" function
821
outside of its arguments"""
822
def call_both(*args, **kwargs):
823
"""This function will emit two D-Bus
824
signals by calling func1 and func2"""
825
func1(*args, **kwargs)
826
func2(*args, **kwargs)
828
# Create the "call_both" function and add it to
830
attr[attrname] = fixscope(attribute,
832
# Is this a D-Bus method?
833
elif getattr(attribute, "_dbus_is_method", False):
834
# Create a new, but exactly alike, function
835
# object. Decorate it to be a new D-Bus method
836
# with the alternate D-Bus interface name. Add it
838
attr[attrname] = (dbus.service.method
840
attribute._dbus_in_signature,
841
attribute._dbus_out_signature)
843
(attribute.func_code,
844
attribute.func_globals,
846
attribute.func_defaults,
847
attribute.func_closure)))
848
# Is this a D-Bus property?
849
elif getattr(attribute, "_dbus_is_property", False):
850
# Create a new, but exactly alike, function
851
# object, and decorate it to be a new D-Bus
852
# property with the alternate D-Bus interface
853
# name. Add it to the class.
854
attr[attrname] = (dbus_service_property
856
attribute._dbus_signature,
857
attribute._dbus_access,
859
._dbus_get_args_options
862
(attribute.func_code,
863
attribute.func_globals,
865
attribute.func_defaults,
866
attribute.func_closure)))
867
return type.__new__(mcs, name, bases, attr)
869
class ClientDBus(Client, DBusObjectWithProperties):
870
"""A Client class using D-Bus
873
dbus_object_path: dbus.ObjectPath
874
bus: dbus.SystemBus()
877
runtime_expansions = (Client.runtime_expansions
878
+ ("dbus_object_path",))
880
# dbus.service.Object doesn't use super(), so we can't either.
882
def __init__(self, bus = None, *args, **kwargs):
883
self._approvals_pending = 0
885
Client.__init__(self, *args, **kwargs)
886
# Only now, when this client is initialized, can it show up on
888
client_object_name = unicode(self.name).translate(
891
self.dbus_object_path = (dbus.ObjectPath
892
("/clients/" + client_object_name))
893
DBusObjectWithProperties.__init__(self, self.bus,
894
self.dbus_object_path)
896
def notifychangeproperty(transform_func,
897
dbus_name, type_func=lambda x: x,
899
""" Modify a variable so that it's a property which announces
902
transform_fun: Function that takes a value and a variant_level
903
and transforms it to a D-Bus type.
904
dbus_name: D-Bus name of the variable
905
type_func: Function that transform the value before sending it
906
to the D-Bus. Default: no transform
907
variant_level: D-Bus variant level. Default: 1
909
attrname = "_{0}".format(dbus_name)
910
def setter(self, value):
911
if hasattr(self, "dbus_object_path"):
912
if (not hasattr(self, attrname) or
913
type_func(getattr(self, attrname, None))
914
!= type_func(value)):
915
dbus_value = transform_func(type_func(value),
918
self.PropertyChanged(dbus.String(dbus_name),
920
setattr(self, attrname, value)
922
return property(lambda self: getattr(self, attrname), setter)
925
expires = notifychangeproperty(datetime_to_dbus, "Expires")
926
approvals_pending = notifychangeproperty(dbus.Boolean,
929
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
930
last_enabled = notifychangeproperty(datetime_to_dbus,
932
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
933
type_func = lambda checker:
935
last_checked_ok = notifychangeproperty(datetime_to_dbus,
937
last_approval_request = notifychangeproperty(
938
datetime_to_dbus, "LastApprovalRequest")
939
approved_by_default = notifychangeproperty(dbus.Boolean,
941
approval_delay = notifychangeproperty(dbus.UInt16,
944
_timedelta_to_milliseconds)
945
approval_duration = notifychangeproperty(
946
dbus.UInt16, "ApprovalDuration",
947
type_func = _timedelta_to_milliseconds)
948
host = notifychangeproperty(dbus.String, "Host")
949
timeout = notifychangeproperty(dbus.UInt16, "Timeout",
951
_timedelta_to_milliseconds)
952
extended_timeout = notifychangeproperty(
953
dbus.UInt16, "ExtendedTimeout",
954
type_func = _timedelta_to_milliseconds)
955
interval = notifychangeproperty(dbus.UInt16,
958
_timedelta_to_milliseconds)
959
checker_command = notifychangeproperty(dbus.String, "Checker")
961
del notifychangeproperty
963
def __del__(self, *args, **kwargs):
965
self.remove_from_connection()
968
if hasattr(DBusObjectWithProperties, "__del__"):
969
DBusObjectWithProperties.__del__(self, *args, **kwargs)
970
Client.__del__(self, *args, **kwargs)
972
def checker_callback(self, pid, condition, command,
974
self.checker_callback_tag = None
976
if os.WIFEXITED(condition):
977
exitstatus = os.WEXITSTATUS(condition)
979
self.CheckerCompleted(dbus.Int16(exitstatus),
980
dbus.Int64(condition),
981
dbus.String(command))
984
self.CheckerCompleted(dbus.Int16(-1),
985
dbus.Int64(condition),
986
dbus.String(command))
988
return Client.checker_callback(self, pid, condition, command,
991
def start_checker(self, *args, **kwargs):
992
old_checker = self.checker
993
if self.checker is not None:
994
old_checker_pid = self.checker.pid
996
old_checker_pid = None
997
r = Client.start_checker(self, *args, **kwargs)
998
# Only if new checker process was started
999
if (self.checker is not None
1000
and old_checker_pid != self.checker.pid):
1002
self.CheckerStarted(self.current_checker_command)
1005
def _reset_approved(self):
1006
self._approved = None
1009
def approve(self, value=True):
1010
self.send_changedstate()
1011
self._approved = value
1012
gobject.timeout_add(_timedelta_to_milliseconds
1013
(self.approval_duration),
1014
self._reset_approved)
1017
## D-Bus methods, signals & properties
1018
_interface = "se.recompile.Mandos.Client"
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"
1022
457
# CheckerCompleted - signal
1023
@dbus.service.signal(_interface, signature="nxs")
1024
def CheckerCompleted(self, exitcode, waitstatus, command):
458
@dbus.service.signal(_interface, signature="bqs")
459
def CheckerCompleted(self, success, condition, command):
1091
579
# StopChecker - method
1092
@dbus.service.method(_interface)
1093
def StopChecker(self):
1098
# ApprovalPending - property
1099
@dbus_service_property(_interface, signature="b", access="read")
1100
def ApprovalPending_dbus_property(self):
1101
return dbus.Boolean(bool(self.approvals_pending))
1103
# ApprovedByDefault - property
1104
@dbus_service_property(_interface, signature="b",
1106
def ApprovedByDefault_dbus_property(self, value=None):
1107
if value is None: # get
1108
return dbus.Boolean(self.approved_by_default)
1109
self.approved_by_default = bool(value)
1111
# ApprovalDelay - property
1112
@dbus_service_property(_interface, signature="t",
1114
def ApprovalDelay_dbus_property(self, value=None):
1115
if value is None: # get
1116
return dbus.UInt64(self.approval_delay_milliseconds())
1117
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1119
# ApprovalDuration - property
1120
@dbus_service_property(_interface, signature="t",
1122
def ApprovalDuration_dbus_property(self, value=None):
1123
if value is None: # get
1124
return dbus.UInt64(_timedelta_to_milliseconds(
1125
self.approval_duration))
1126
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1129
@dbus_service_property(_interface, signature="s", access="read")
1130
def Name_dbus_property(self):
1131
return dbus.String(self.name)
1133
# Fingerprint - property
1134
@dbus_service_property(_interface, signature="s", access="read")
1135
def Fingerprint_dbus_property(self):
1136
return dbus.String(self.fingerprint)
1139
@dbus_service_property(_interface, signature="s",
1141
def Host_dbus_property(self, value=None):
1142
if value is None: # get
1143
return dbus.String(self.host)
1146
# Created - property
1147
@dbus_service_property(_interface, signature="s", access="read")
1148
def Created_dbus_property(self):
1149
return dbus.String(datetime_to_dbus(self.created))
1151
# LastEnabled - property
1152
@dbus_service_property(_interface, signature="s", access="read")
1153
def LastEnabled_dbus_property(self):
1154
return datetime_to_dbus(self.last_enabled)
1156
# Enabled - property
1157
@dbus_service_property(_interface, signature="b",
1159
def Enabled_dbus_property(self, value=None):
1160
if value is None: # get
1161
return dbus.Boolean(self.enabled)
1167
# LastCheckedOK - property
1168
@dbus_service_property(_interface, signature="s",
1170
def LastCheckedOK_dbus_property(self, value=None):
1171
if value is not None:
1174
return datetime_to_dbus(self.last_checked_ok)
1176
# Expires - property
1177
@dbus_service_property(_interface, signature="s", access="read")
1178
def Expires_dbus_property(self):
1179
return datetime_to_dbus(self.expires)
1181
# LastApprovalRequest - property
1182
@dbus_service_property(_interface, signature="s", access="read")
1183
def LastApprovalRequest_dbus_property(self):
1184
return datetime_to_dbus(self.last_approval_request)
1186
# Timeout - property
1187
@dbus_service_property(_interface, signature="t",
1189
def Timeout_dbus_property(self, value=None):
1190
if value is None: # get
1191
return dbus.UInt64(self.timeout_milliseconds())
1192
self.timeout = datetime.timedelta(0, 0, 0, value)
1193
if getattr(self, "disable_initiator_tag", None) is None:
1195
# Reschedule timeout
1196
gobject.source_remove(self.disable_initiator_tag)
1197
self.disable_initiator_tag = None
1199
time_to_die = _timedelta_to_milliseconds((self
1204
if time_to_die <= 0:
1205
# The timeout has passed
1208
self.expires = (datetime.datetime.utcnow()
1209
+ datetime.timedelta(milliseconds =
1211
self.disable_initiator_tag = (gobject.timeout_add
1212
(time_to_die, self.disable))
1214
# ExtendedTimeout - property
1215
@dbus_service_property(_interface, signature="t",
1217
def ExtendedTimeout_dbus_property(self, value=None):
1218
if value is None: # get
1219
return dbus.UInt64(self.extended_timeout_milliseconds())
1220
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1222
# Interval - property
1223
@dbus_service_property(_interface, signature="t",
1225
def Interval_dbus_property(self, value=None):
1226
if value is None: # get
1227
return dbus.UInt64(self.interval_milliseconds())
1228
self.interval = datetime.timedelta(0, 0, 0, value)
1229
if getattr(self, "checker_initiator_tag", None) is None:
1231
# Reschedule checker run
1232
gobject.source_remove(self.checker_initiator_tag)
1233
self.checker_initiator_tag = (gobject.timeout_add
1234
(value, self.start_checker))
1235
self.start_checker() # Start one now, too
1237
# Checker - property
1238
@dbus_service_property(_interface, signature="s",
1240
def Checker_dbus_property(self, value=None):
1241
if value is None: # get
1242
return dbus.String(self.checker_command)
1243
self.checker_command = value
1245
# CheckerRunning - property
1246
@dbus_service_property(_interface, signature="b",
1248
def CheckerRunning_dbus_property(self, value=None):
1249
if value is None: # get
1250
return dbus.Boolean(self.checker is not None)
1252
self.start_checker()
1256
# ObjectPath - property
1257
@dbus_service_property(_interface, signature="o", access="read")
1258
def ObjectPath_dbus_property(self):
1259
return self.dbus_object_path # is already a dbus.ObjectPath
1262
@dbus_service_property(_interface, signature="ay",
1263
access="write", byte_arrays=True)
1264
def Secret_dbus_property(self, value):
1265
self.secret = str(value)
580
StopChecker = dbus.service.method(_interface)(stop_checker)
581
StopChecker.__name__ = "StopChecker"
1270
class ProxyClient(object):
1271
def __init__(self, child_pipe, fpr, address):
1272
self._pipe = child_pipe
1273
self._pipe.send(('init', fpr, address))
1274
if not self._pipe.recv():
1277
def __getattribute__(self, name):
1278
if(name == '_pipe'):
1279
return super(ProxyClient, self).__getattribute__(name)
1280
self._pipe.send(('getattr', name))
1281
data = self._pipe.recv()
1282
if data[0] == 'data':
1284
if data[0] == 'function':
1285
def func(*args, **kwargs):
1286
self._pipe.send(('funcall', name, args, kwargs))
1287
return self._pipe.recv()[1]
1290
def __setattr__(self, name, value):
1291
if(name == '_pipe'):
1292
return super(ProxyClient, self).__setattr__(name, value)
1293
self._pipe.send(('setattr', name, value))
1295
class ClientDBusTransitional(ClientDBus):
1296
__metaclass__ = AlternateDBusNamesMetaclass
1298
class ClientHandler(socketserver.BaseRequestHandler, object):
1299
"""A class to handle client connections.
1301
Instantiated once for each connection to handle it.
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.
1302
647
Note: This will run in its own forked process."""
1304
649
def handle(self):
1305
with contextlib.closing(self.server.child_pipe) as child_pipe:
1306
logger.info("TCP connection from: %s",
1307
unicode(self.client_address))
1308
logger.debug("Pipe FD: %d",
1309
self.server.child_pipe.fileno())
1311
session = (gnutls.connection
1312
.ClientSession(self.request,
1314
.X509Credentials()))
1316
# Note: gnutls.connection.X509Credentials is really a
1317
# generic GnuTLS certificate credentials object so long as
1318
# no X.509 keys are added to it. Therefore, we can use it
1319
# here despite using OpenPGP certificates.
1321
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1322
# "+AES-256-CBC", "+SHA1",
1323
# "+COMP-NULL", "+CTYPE-OPENPGP",
1325
# Use a fallback default, since this MUST be set.
1326
priority = self.server.gnutls_priority
1327
if priority is None:
1329
(gnutls.library.functions
1330
.gnutls_priority_set_direct(session._c_object,
1333
# Start communication using the Mandos protocol
1334
# Get protocol number
1335
line = self.request.makefile().readline()
1336
logger.debug("Protocol version: %r", line)
1338
if int(line.strip().split()[0]) > 1:
1340
except (ValueError, IndexError, RuntimeError) as error:
1341
logger.error("Unknown protocol version: %s", error)
1344
# Start GnuTLS connection
1347
except gnutls.errors.GNUTLSError as error:
1348
logger.warning("Handshake failed: %s", error)
1349
# Do not run session.bye() here: the session is not
1350
# established. Just abandon the request.
1352
logger.debug("Handshake succeeded")
1354
approval_required = False
1357
fpr = self.fingerprint(self.peer_certificate
1360
gnutls.errors.GNUTLSError) as error:
1361
logger.warning("Bad certificate: %s", error)
1363
logger.debug("Fingerprint: %s", fpr)
1366
client = ProxyClient(child_pipe, fpr,
1367
self.client_address)
1371
if client.approval_delay:
1372
delay = client.approval_delay
1373
client.approvals_pending += 1
1374
approval_required = True
1377
if not client.enabled:
1378
logger.info("Client %s is disabled",
1380
if self.server.use_dbus:
1382
client.Rejected("Disabled")
1385
if client._approved or not client.approval_delay:
1386
#We are approved or approval is disabled
1388
elif client._approved is None:
1389
logger.info("Client %s needs approval",
1391
if self.server.use_dbus:
1393
client.NeedApproval(
1394
client.approval_delay_milliseconds(),
1395
client.approved_by_default)
1397
logger.warning("Client %s was not approved",
1399
if self.server.use_dbus:
1401
client.Rejected("Denied")
1404
#wait until timeout or approved
1405
time = datetime.datetime.now()
1406
client.changedstate.acquire()
1407
(client.changedstate.wait
1408
(float(client._timedelta_to_milliseconds(delay)
1410
client.changedstate.release()
1411
time2 = datetime.datetime.now()
1412
if (time2 - time) >= delay:
1413
if not client.approved_by_default:
1414
logger.warning("Client %s timed out while"
1415
" waiting for approval",
1417
if self.server.use_dbus:
1419
client.Rejected("Approval timed out")
1424
delay -= time2 - time
1427
while sent_size < len(client.secret):
1429
sent = session.send(client.secret[sent_size:])
1430
except gnutls.errors.GNUTLSError as error:
1431
logger.warning("gnutls send failed")
1433
logger.debug("Sent: %d, remaining: %d",
1434
sent, len(client.secret)
1435
- (sent_size + sent))
1438
logger.info("Sending secret to %s", client.name)
1439
# bump the timeout using extended_timeout
1440
client.checked_ok(client.extended_timeout)
1441
if self.server.use_dbus:
1446
if approval_required:
1447
client.approvals_pending -= 1
1450
except gnutls.errors.GNUTLSError as error:
1451
logger.warning("GnuTLS bye failed")
1454
def peer_certificate(session):
1455
"Return the peer's OpenPGP certificate as a bytestring"
1456
# If not an OpenPGP certificate...
1457
if (gnutls.library.functions
1458
.gnutls_certificate_type_get(session._c_object)
1459
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1460
# ...do the normal thing
1461
return session.peer_certificate
1462
list_size = ctypes.c_uint(1)
1463
cert_list = (gnutls.library.functions
1464
.gnutls_certificate_get_peers
1465
(session._c_object, ctypes.byref(list_size)))
1466
if not bool(cert_list) and list_size.value != 0:
1467
raise gnutls.errors.GNUTLSError("error getting peer"
1469
if list_size.value == 0:
1472
return ctypes.string_at(cert.data, cert.size)
1475
def fingerprint(openpgp):
1476
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1477
# New GnuTLS "datum" with the OpenPGP public key
1478
datum = (gnutls.library.types
1479
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1482
ctypes.c_uint(len(openpgp))))
1483
# New empty GnuTLS certificate
1484
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1485
(gnutls.library.functions
1486
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1487
# Import the OpenPGP public key into the certificate
1488
(gnutls.library.functions
1489
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1490
gnutls.library.constants
1491
.GNUTLS_OPENPGP_FMT_RAW))
1492
# Verify the self signature in the key
1493
crtverify = ctypes.c_uint()
1494
(gnutls.library.functions
1495
.gnutls_openpgp_crt_verify_self(crt, 0,
1496
ctypes.byref(crtverify)))
1497
if crtverify.value != 0:
1498
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1499
raise (gnutls.errors.CertificateSecurityError
1501
# New buffer for the fingerprint
1502
buf = ctypes.create_string_buffer(20)
1503
buf_len = ctypes.c_size_t()
1504
# Get the fingerprint from the certificate into the buffer
1505
(gnutls.library.functions
1506
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1507
ctypes.byref(buf_len)))
1508
# Deinit the certificate
1509
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1510
# Convert the buffer to a Python bytestring
1511
fpr = ctypes.string_at(buf, buf_len.value)
1512
# Convert the bytestring to hexadecimal notation
1513
hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1517
class MultiprocessingMixIn(object):
1518
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1519
def sub_process_main(self, request, address):
1521
self.finish_request(request, address)
1523
self.handle_error(request, address)
1524
self.close_request(request)
1526
def process_request(self, request, address):
1527
"""Start a new process to process the request."""
1528
proc = multiprocessing.Process(target = self.sub_process_main,
1535
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1536
""" adds a pipe to the MixIn """
1537
def process_request(self, request, client_address):
1538
"""Overrides and wraps the original process_request().
1540
This function creates a new pipe in self.pipe
1542
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1544
proc = MultiprocessingMixIn.process_request(self, request,
1546
self.child_pipe.close()
1547
self.add_pipe(parent_pipe, proc)
1549
def add_pipe(self, parent_pipe, proc):
1550
"""Dummy function; override as necessary"""
1551
raise NotImplementedError
1554
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1555
socketserver.TCPServer, object):
1556
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
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.
727
settings: Server settings
728
clients: Set() of Client objects
1559
729
enabled: Boolean; whether this server is activated yet
1560
interface: None or a network interface name (string)
1561
use_ipv6: Boolean; to use IPv6 or not
1563
def __init__(self, server_address, RequestHandlerClass,
1564
interface=None, use_ipv6=True):
1565
self.interface = interface
1567
self.address_family = socket.AF_INET6
1568
socketserver.TCPServer.__init__(self, server_address,
1569
RequestHandlerClass)
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)
1570
741
def server_bind(self):
1571
742
"""This overrides the normal server_bind() function
1572
743
to bind to an interface if one was specified, and also NOT to
1573
744
bind to an address or port if they were not specified."""
1574
if self.interface is not None:
1575
if SO_BINDTODEVICE is None:
1576
logger.error("SO_BINDTODEVICE does not exist;"
1577
" cannot bind to interface %s",
1581
self.socket.setsockopt(socket.SOL_SOCKET,
1585
except socket.error as error:
1586
if error[0] == errno.EPERM:
1587
logger.error("No permission to"
1588
" bind to interface %s",
1590
elif error[0] == errno.ENOPROTOOPT:
1591
logger.error("SO_BINDTODEVICE not available;"
1592
" cannot bind to interface %s",
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"])
1596
759
# Only bind(2) the socket if we really need to.
1597
760
if self.server_address[0] or self.server_address[1]:
1598
761
if not self.server_address[0]:
1599
if self.address_family == socket.AF_INET6:
1600
any_address = "::" # in6addr_any
1602
any_address = socket.INADDR_ANY
1603
self.server_address = (any_address,
763
self.server_address = (in6addr_any,
1604
764
self.server_address[1])
1605
765
elif not self.server_address[1]:
1606
766
self.server_address = (self.server_address[0],
1608
# if self.interface:
768
# if self.settings["interface"]:
1609
769
# self.server_address = (self.server_address[0],
1612
772
# if_nametoindex
1614
return socketserver.TCPServer.server_bind(self)
1617
class MandosServer(IPv6_TCPServer):
1621
clients: set of Client objects
1622
gnutls_priority GnuTLS priority string
1623
use_dbus: Boolean; to emit D-Bus signals or not
1625
Assumes a gobject.MainLoop event loop.
1627
def __init__(self, server_address, RequestHandlerClass,
1628
interface=None, use_ipv6=True, clients=None,
1629
gnutls_priority=None, use_dbus=True):
1630
self.enabled = False
1631
self.clients = clients
1632
if self.clients is None:
1633
self.clients = set()
1634
self.use_dbus = use_dbus
1635
self.gnutls_priority = gnutls_priority
1636
IPv6_TCPServer.__init__(self, server_address,
1637
RequestHandlerClass,
1638
interface = interface,
1639
use_ipv6 = use_ipv6)
775
return super(IPv6_TCPServer, self).server_bind()
1640
776
def server_activate(self):
1641
777
if self.enabled:
1642
return socketserver.TCPServer.server_activate(self)
778
return super(IPv6_TCPServer, self).server_activate()
1644
779
def enable(self):
1645
780
self.enabled = True
1647
def add_pipe(self, parent_pipe, proc):
1648
# Call "handle_ipc" for both data and EOF events
1649
gobject.io_add_watch(parent_pipe.fileno(),
1650
gobject.IO_IN | gobject.IO_HUP,
1651
functools.partial(self.handle_ipc,
1656
def handle_ipc(self, source, condition, parent_pipe=None,
1657
proc = None, client_object=None):
1659
gobject.IO_IN: "IN", # There is data to read.
1660
gobject.IO_OUT: "OUT", # Data can be written (without
1662
gobject.IO_PRI: "PRI", # There is urgent data to read.
1663
gobject.IO_ERR: "ERR", # Error condition.
1664
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1665
# broken, usually for pipes and
1668
conditions_string = ' | '.join(name
1670
condition_names.iteritems()
1671
if cond & condition)
1672
# error, or the other end of multiprocessing.Pipe has closed
1673
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1674
# Wait for other process to exit
1678
# Read a request from the child
1679
request = parent_pipe.recv()
1680
command = request[0]
1682
if command == 'init':
1684
address = request[2]
1686
for c in self.clients:
1687
if c.fingerprint == fpr:
1691
logger.info("Client not found for fingerprint: %s, ad"
1692
"dress: %s", fpr, address)
1695
mandos_dbus_service.ClientNotFound(fpr,
1697
parent_pipe.send(False)
1700
gobject.io_add_watch(parent_pipe.fileno(),
1701
gobject.IO_IN | gobject.IO_HUP,
1702
functools.partial(self.handle_ipc,
1708
parent_pipe.send(True)
1709
# remove the old hook in favor of the new above hook on
1712
if command == 'funcall':
1713
funcname = request[1]
1717
parent_pipe.send(('data', getattr(client_object,
1721
if command == 'getattr':
1722
attrname = request[1]
1723
if callable(client_object.__getattribute__(attrname)):
1724
parent_pipe.send(('function',))
1726
parent_pipe.send(('data', client_object
1727
.__getattribute__(attrname)))
1729
if command == 'setattr':
1730
attrname = request[1]
1732
setattr(client_object, attrname, value)
1737
783
def string_to_delta(interval):
1738
784
"""Parse a string and return a datetime.timedelta
1740
786
>>> string_to_delta('7d')
1741
787
datetime.timedelta(7)
1742
788
>>> string_to_delta('60s')
1873
929
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1874
930
"servicename": "Mandos",
1875
931
"use_dbus": "True",
1880
934
# Parse config file for server-global settings
1881
server_config = configparser.SafeConfigParser(server_defaults)
935
server_config = ConfigParser.SafeConfigParser(server_defaults)
1882
936
del server_defaults
1883
server_config.read(os.path.join(options.configdir,
937
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1885
938
# Convert the SafeConfigParser object to a dict
1886
939
server_settings = server_config.defaults()
1887
# Use the appropriate methods on the non-string config options
1888
for option in ("debug", "use_dbus", "use_ipv6"):
1889
server_settings[option] = server_config.getboolean("DEFAULT",
1891
if server_settings["port"]:
1892
server_settings["port"] = server_config.getint("DEFAULT",
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"))
1894
945
del server_config
1896
947
# Override the settings from the config file with command line
1897
948
# options, if set.
1898
949
for option in ("interface", "address", "port", "debug",
1899
950
"priority", "servicename", "configdir",
1900
"use_dbus", "use_ipv6", "debuglevel"):
1901
952
value = getattr(options, option)
1902
953
if value is not None:
1903
954
server_settings[option] = value
1905
# Force all strings to be unicode
1906
for option in server_settings.keys():
1907
if type(server_settings[option]) is str:
1908
server_settings[option] = unicode(server_settings[option])
1909
956
# Now we have our good server settings in "server_settings"
1911
##################################################################
1913
958
# For convenience
1914
959
debug = server_settings["debug"]
1915
debuglevel = server_settings["debuglevel"]
1916
960
use_dbus = server_settings["use_dbus"]
1917
use_ipv6 = server_settings["use_ipv6"]
963
syslogger.setLevel(logging.WARNING)
964
console.setLevel(logging.WARNING)
1919
966
if server_settings["servicename"] != "Mandos":
1920
967
syslogger.setFormatter(logging.Formatter
1921
('Mandos (%s) [%%(process)d]:'
1922
' %%(levelname)s: %%(message)s'
968
('Mandos (%s): %%(levelname)s:'
1923
970
% server_settings["servicename"]))
1925
972
# Parse config file with clients
1926
client_defaults = { "timeout": "5m",
1927
"extended_timeout": "15m",
1929
"checker": "fping -q -- %%(host)s",
973
client_defaults = { "timeout": "1h",
975
"checker": "fping -q -- %(host)s",
1931
"approval_delay": "0s",
1932
"approval_duration": "1s",
1934
client_config = configparser.SafeConfigParser(client_defaults)
978
client_config = ConfigParser.SafeConfigParser(client_defaults)
1935
979
client_config.read(os.path.join(server_settings["configdir"],
1936
980
"clients.conf"))
1938
global mandos_dbus_service
1939
mandos_dbus_service = None
1941
tcp_server = MandosServer((server_settings["address"],
1942
server_settings["port"]),
1944
interface=(server_settings["interface"]
1948
server_settings["priority"],
1951
pidfilename = "/var/run/mandos.pid"
1953
pidfile = open(pidfilename, "w")
1955
logger.error("Could not open file %r", pidfilename)
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)
1958
995
uid = pwd.getpwnam("_mandos").pw_uid
1959
gid = pwd.getpwnam("_mandos").pw_gid
1960
996
except KeyError:
1962
998
uid = pwd.getpwnam("mandos").pw_uid
1963
gid = pwd.getpwnam("mandos").pw_gid
1964
999
except KeyError:
1966
1001
uid = pwd.getpwnam("nobody").pw_uid
1967
gid = pwd.getpwnam("nobody").pw_gid
1968
1002
except KeyError:
1005
gid = pwd.getpwnam("_mandos").pw_gid
1008
gid = pwd.getpwnam("mandos").pw_gid
1011
gid = pwd.getpwnam("nogroup").pw_gid
1974
except OSError as error:
1017
except OSError, error:
1975
1018
if error[0] != errno.EPERM:
1978
if not debug and not debuglevel:
1979
logger.setLevel(logging.WARNING)
1981
level = getattr(logging, debuglevel.upper())
1982
logger.setLevel(level)
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")
1985
logger.setLevel(logging.DEBUG)
1986
# Enable all possible GnuTLS debugging
1988
# "Use a log level over 10 to enable all debugging options."
1990
gnutls.library.functions.gnutls_global_set_log_level(11)
1992
@gnutls.library.types.gnutls_log_func
1993
def debug_gnutls(level, string):
1994
logger.debug("GnuTLS: %s", string[:-1])
1996
(gnutls.library.functions
1997
.gnutls_global_set_log_function(debug_gnutls))
1999
1052
# Redirect stdin so all checkers get /dev/null
2000
1053
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2001
1054
os.dup2(null, sys.stdin.fileno())
2005
1058
# No console logging
2006
1059
logger.removeHandler(console)
2008
# Need to fork before connecting to D-Bus
2010
1060
# Close all input and output, do double fork, etc.
2014
# From the Avahi example code
2015
DBusGMainLoop(set_as_default=True )
2016
main_loop = gobject.MainLoop()
2017
bus = dbus.SystemBus()
2018
# End of Avahi example code
2021
bus_name = dbus.service.BusName("se.recompile.Mandos",
2022
bus, do_not_queue=True)
2023
old_bus_name = (dbus.service.BusName
2024
("se.bsnet.fukt.Mandos", bus,
2026
except dbus.exceptions.NameExistsException as e:
2027
logger.error(unicode(e) + ", disabling D-Bus")
2029
server_settings["use_dbus"] = False
2030
tcp_server.use_dbus = False
2031
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2032
service = AvahiServiceToSyslog(name =
2033
server_settings["servicename"],
2034
servicetype = "_mandos._tcp",
2035
protocol = protocol, bus = bus)
2036
if server_settings["interface"]:
2037
service.interface = (if_nametoindex
2038
(str(server_settings["interface"])))
2040
global multiprocessing_manager
2041
multiprocessing_manager = multiprocessing.Manager()
2043
client_class = Client
2045
client_class = functools.partial(ClientDBusTransitional,
2047
def client_config_items(config, section):
2048
special_settings = {
2049
"approved_by_default":
2050
lambda: config.getboolean(section,
2051
"approved_by_default"),
2053
for name, value in config.items(section):
2055
yield (name, special_settings[name]())
2059
tcp_server.clients.update(set(
2060
client_class(name = section,
2061
config= dict(client_config_items(
2062
client_config, section)))
2063
for section in client_config.sections()))
2064
if not tcp_server.clients:
2065
logger.warning("No clients defined")
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
1086
client = clients.pop()
1087
client.disable_hook = None
1090
atexit.register(cleanup)
2071
pidfile.write(str(pid) + "\n".encode("utf-8"))
2074
logger.error("Could not write to file %r with PID %d",
2077
# "pidfile" was never created
2081
1093
signal.signal(signal.SIGINT, signal.SIG_IGN)
2083
1094
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2084
1095
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2087
class MandosDBusService(dbus.service.Object):
1098
class MandosServer(dbus.service.Object):
2088
1099
"""A D-Bus proxy object"""
2089
1100
def __init__(self):
2090
dbus.service.Object.__init__(self, bus, "/")
2091
_interface = "se.recompile.Mandos"
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):
2093
1110
@dbus.service.signal(_interface, signature="o")
2094
def ClientAdded(self, objpath):
2098
@dbus.service.signal(_interface, signature="ss")
2099
def ClientNotFound(self, fingerprint, address):
2103
@dbus.service.signal(_interface, signature="os")
2104
def ClientRemoved(self, objpath, name):
1111
def ClientRemoved(self, objpath):
2108
1115
@dbus.service.method(_interface, out_signature="ao")
2109
1116
def GetAllClients(self):
2111
return dbus.Array(c.dbus_object_path
2112
for c in tcp_server.clients)
2114
@dbus.service.method(_interface,
2115
out_signature="a{oa{sv}}")
1117
return dbus.Array(c.dbus_object_path for c in clients)
1119
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
2116
1120
def GetAllClientsWithProperties(self):
2118
1121
return dbus.Dictionary(
2119
((c.dbus_object_path, c.GetAll(""))
2120
for c in tcp_server.clients),
1122
((c.dbus_object_path, c.GetAllProperties())
2121
1124
signature="oa{sv}")
2123
1126
@dbus.service.method(_interface, in_signature="o")
2124
1127
def RemoveClient(self, object_path):
2126
for c in tcp_server.clients:
2127
1129
if c.dbus_object_path == object_path:
2128
tcp_server.clients.remove(c)
2129
c.remove_from_connection()
2130
1131
# Don't signal anything except ClientRemoved
2131
c.disable(quiet=True)
2132
1134
# Emit D-Bus signal
2133
self.ClientRemoved(object_path, c.name)
1135
self.ClientRemoved(object_path)
2135
raise KeyError(object_path)
1138
@dbus.service.method(_interface)
2139
class MandosDBusServiceTransitional(MandosDBusService):
2140
__metaclass__ = AlternateDBusNamesMetaclass
2141
mandos_dbus_service = MandosDBusServiceTransitional()
2144
"Cleanup function; run on exit"
2147
multiprocessing.active_children()
2148
while tcp_server.clients:
2149
client = tcp_server.clients.pop()
2151
client.remove_from_connection()
2152
client.disable_hook = None
2153
# Don't signal anything except ClientRemoved
2154
client.disable(quiet=True)
2157
mandos_dbus_service.ClientRemoved(client
2161
atexit.register(cleanup)
2163
for client in tcp_server.clients:
1144
mandos_server = MandosServer()
1146
for client in clients:
2165
1148
# Emit D-Bus signal
2166
mandos_dbus_service.ClientAdded(client.dbus_object_path)
1149
mandos_server.ClientAdded(client.dbus_object_path,
1150
client.GetAllProperties())
2167
1151
client.enable()
2169
1153
tcp_server.enable()