149
124
self.rename_count = 0
150
125
self.max_renames = max_renames
151
self.protocol = protocol
152
self.group = None # our entry group
155
self.entry_group_state_changed_match = None
156
126
def rename(self):
157
127
"""Derived from the Avahi example code"""
158
128
if self.rename_count >= self.max_renames:
159
logger.critical("No suitable Zeroconf service name found"
160
" after %i retries, exiting.",
129
logger.critical(u"No suitable Zeroconf service name found"
130
u" after %i retries, exiting.",
161
131
self.rename_count)
162
raise AvahiServiceError("Too many renames")
163
self.name = unicode(self.server.GetAlternativeServiceName(self.name))
164
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 ...",
166
136
syslogger.setFormatter(logging.Formatter
167
('Mandos (%s) [%%(process)d]:'
168
' %%(levelname)s: %%(message)s'
137
('Mandos (%s): %%(levelname)s:'
138
' %%(message)s' % self.name))
173
except dbus.exceptions.DBusException as error:
174
logger.critical("DBusException: %s", error)
177
141
self.rename_count += 1
178
142
def remove(self):
179
143
"""Derived from the Avahi example code"""
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:
144
if group is not None:
186
147
"""Derived from the Avahi example code"""
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):
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):
275
179
"""A representation of a client host served by this server.
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
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)
281
194
checker: subprocess.Popen(); a running checker process used
282
195
to see if the client lives.
283
196
'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
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
287
202
runtime with vars(self) as dict, so that for
288
203
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
295
fingerprint: string (40 or 32 hexadecimal digits); used to
296
uniquely identify the client
297
host: string; available for use by the checker command
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
301
last_enabled: datetime.datetime(); (UTC)
302
name: string; from the config file, used in log messages and
304
secret: bytestring; sent verbatim (over TLS) to client
305
timeout: datetime.timedelta(); How long from last_checked_ok
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
204
use_dbus: bool(); Whether to provide D-Bus interface and signals
205
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
313
runtime_expansions = ("approval_delay", "approval_duration",
314
"created", "enabled", "fingerprint",
315
"host", "interval", "last_checked_ok",
316
"last_enabled", "name", "timeout")
318
207
def timeout_milliseconds(self):
319
208
"Return the 'timeout' attribute in milliseconds"
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)
209
return ((self.timeout.days * 24 * 60 * 60 * 1000)
210
+ (self.timeout.seconds * 1000)
211
+ (self.timeout.microseconds // 1000))
326
213
def interval_milliseconds(self):
327
214
"Return the 'interval' attribute in milliseconds"
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):
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,
334
221
"""Note: the 'checker' key in 'config' sets the
335
222
'checker_command' attribute and *not* the 'checker'
338
225
if config is None:
340
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)
341
235
# Uppercase and remove spaces from fingerprint for later
342
236
# comparison purposes with return value from the fingerprint()
344
238
self.fingerprint = (config["fingerprint"].upper()
346
logger.debug(" Fingerprint: %s", self.fingerprint)
240
logger.debug(u" Fingerprint: %s", self.fingerprint)
347
241
if "secret" in config:
348
self.secret = config["secret"].decode("base64")
242
self.secret = config["secret"].decode(u"base64")
349
243
elif "secfile" in config:
350
with open(os.path.expanduser(os.path.expandvars
351
(config["secfile"])),
244
with closing(open(os.path.expanduser
246
(config["secfile"])))) as secfile:
353
247
self.secret = secfile.read()
355
raise TypeError("No secret or secfile for client %s"
249
raise TypeError(u"No secret or secfile for client %s"
357
251
self.host = config.get("host", "")
358
252
self.created = datetime.datetime.utcnow()
359
253
self.enabled = False
360
self.last_approval_request = None
361
254
self.last_enabled = None
362
255
self.last_checked_ok = None
363
256
self.timeout = string_to_delta(config["timeout"])
364
self.extended_timeout = string_to_delta(config["extended_timeout"])
365
257
self.interval = string_to_delta(config["interval"])
366
258
self.disable_hook = disable_hook
367
259
self.checker = None
368
260
self.checker_initiator_tag = None
369
261
self.disable_initiator_tag = None
371
262
self.checker_callback_tag = None
372
263
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()
390
265
def enable(self):
391
266
"""Start this client's checker and timeout hooks"""
392
if getattr(self, "enabled", False):
395
self.send_changedstate()
267
self.last_enabled = datetime.datetime.utcnow()
396
268
# Schedule a new checker to be started an 'interval' from now,
397
269
# and every interval from then on.
398
270
self.checker_initiator_tag = (gobject.timeout_add
399
271
(self.interval_milliseconds(),
400
272
self.start_checker))
273
# Also start a new checker *right now*.
401
275
# Schedule a disable() when 'timeout' has passed
402
self.expires = datetime.datetime.utcnow() + self.timeout
403
276
self.disable_initiator_tag = (gobject.timeout_add
404
277
(self.timeout_milliseconds(),
406
279
self.enabled = True
407
self.last_enabled = datetime.datetime.utcnow()
408
# 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,
411
def disable(self, quiet=True):
412
289
"""Disable this client."""
413
290
if not getattr(self, "enabled", False):
416
self.send_changedstate()
418
logger.info("Disabling client %s", self.name)
292
logger.info(u"Disabling client %s", self.name)
419
293
if getattr(self, "disable_initiator_tag", False):
420
294
gobject.source_remove(self.disable_initiator_tag)
421
295
self.disable_initiator_tag = None
423
296
if getattr(self, "checker_initiator_tag", False):
424
297
gobject.source_remove(self.checker_initiator_tag)
425
298
self.checker_initiator_tag = None
551
423
self.checker_callback_tag = None
552
424
if getattr(self, "checker", None) is None:
554
logger.debug("Stopping checker for %(name)s", vars(self))
426
logger.debug(u"Stopping checker for %(name)s", vars(self))
556
428
os.kill(self.checker.pid, signal.SIGTERM)
558
430
#if self.checker.poll() is None:
559
431
# os.kill(self.checker.pid, signal.SIGKILL)
560
except OSError as error:
432
except OSError, error:
561
433
if error.errno != errno.ESRCH: # No such process
563
435
self.checker = None
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 + "."
649
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
651
def Get(self, interface_name, property_name):
652
"""Standard D-Bus property Get() method, see D-Bus standard.
654
prop = self._get_dbus_property(interface_name, property_name)
655
if prop._dbus_access == "write":
656
raise DBusPropertyAccessException(property_name)
658
if not hasattr(value, "variant_level"):
660
return type(value)(value, variant_level=value.variant_level+1)
662
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
663
def Set(self, interface_name, property_name, value):
664
"""Standard D-Bus property Set() method, see D-Bus standard.
666
prop = self._get_dbus_property(interface_name, property_name)
667
if prop._dbus_access == "read":
668
raise DBusPropertyAccessException(property_name)
669
if prop._dbus_get_args_options["byte_arrays"]:
670
# The byte_arrays option is not supported yet on
671
# signatures other than "ay".
672
if prop._dbus_signature != "ay":
674
value = dbus.ByteArray(''.join(unichr(byte)
678
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
679
out_signature="a{sv}")
680
def GetAll(self, interface_name):
681
"""Standard D-Bus property GetAll() method, see D-Bus
684
Note: Will not include properties with access="write".
687
for name, prop in self._get_all_dbus_properties():
689
and interface_name != prop._dbus_interface):
690
# Interface non-empty but did not match
692
# Ignore write-only properties
693
if prop._dbus_access == "write":
696
if not hasattr(value, "variant_level"):
699
all[name] = type(value)(value, variant_level=
700
value.variant_level+1)
701
return dbus.Dictionary(all, signature="sv")
703
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
705
path_keyword='object_path',
706
connection_keyword='connection')
707
def Introspect(self, object_path, connection):
708
"""Standard D-Bus method, overloaded to insert property tags.
710
xmlstring = dbus.service.Object.Introspect(self, object_path,
713
document = xml.dom.minidom.parseString(xmlstring)
714
def make_tag(document, name, prop):
715
e = document.createElement("property")
716
e.setAttribute("name", name)
717
e.setAttribute("type", prop._dbus_signature)
718
e.setAttribute("access", prop._dbus_access)
720
for if_tag in document.getElementsByTagName("interface"):
721
for tag in (make_tag(document, name, prop)
723
in self._get_all_dbus_properties()
724
if prop._dbus_interface
725
== if_tag.getAttribute("name")):
726
if_tag.appendChild(tag)
727
# Add the names to the return values for the
728
# "org.freedesktop.DBus.Properties" methods
729
if (if_tag.getAttribute("name")
730
== "org.freedesktop.DBus.Properties"):
731
for cn in if_tag.getElementsByTagName("method"):
732
if cn.getAttribute("name") == "Get":
733
for arg in cn.getElementsByTagName("arg"):
734
if (arg.getAttribute("direction")
736
arg.setAttribute("name", "value")
737
elif cn.getAttribute("name") == "GetAll":
738
for arg in cn.getElementsByTagName("arg"):
739
if (arg.getAttribute("direction")
741
arg.setAttribute("name", "props")
742
xmlstring = document.toxml("utf-8")
744
except (AttributeError, xml.dom.DOMException,
745
xml.parsers.expat.ExpatError) as error:
746
logger.error("Failed to override Introspection method",
751
def datetime_to_dbus (dt, variant_level=0):
752
"""Convert a UTC datetime.datetime() to a D-Bus type."""
754
return dbus.String("", variant_level = variant_level)
755
return dbus.String(dt.isoformat(),
756
variant_level=variant_level)
758
class transitional_dbus_metaclass(DBusObjectWithProperties.__metaclass__):
759
def __new__(mcs, name, bases, attr):
760
for attrname, old_dbusobj in inspect.getmembers(bases[0]):
761
new_interface = getattr(old_dbusobj, "_dbus_interface", "").replace("se.bsnet.fukt.", "se.recompile.")
762
if (getattr(old_dbusobj, "_dbus_is_signal", False)
763
and old_dbusobj._dbus_interface.startswith("se.bsnet.fukt.Mandos")):
764
unwrappedfunc = dict(zip(old_dbusobj.func_code.co_freevars,
765
old_dbusobj.__closure__))["func"].cell_contents
766
newfunc = types.FunctionType(unwrappedfunc.func_code,
767
unwrappedfunc.func_globals,
768
unwrappedfunc.func_name,
769
unwrappedfunc.func_defaults,
770
unwrappedfunc.func_closure)
771
new_dbusfunc = dbus.service.signal(
772
new_interface, old_dbusobj._dbus_signature)(newfunc)
773
attr["_transitional_" + attrname] = new_dbusfunc
775
def fixscope(func1, func2):
776
def newcall(*args, **kwargs):
777
func1(*args, **kwargs)
778
func2(*args, **kwargs)
781
attr[attrname] = fixscope(old_dbusobj, new_dbusfunc)
783
elif (getattr(old_dbusobj, "_dbus_is_method", False)
784
and old_dbusobj._dbus_interface.startswith("se.bsnet.fukt.Mandos")):
785
new_dbusfunc = (dbus.service.method
787
old_dbusobj._dbus_in_signature,
788
old_dbusobj._dbus_out_signature)
790
(old_dbusobj.func_code,
791
old_dbusobj.func_globals,
792
old_dbusobj.func_name,
793
old_dbusobj.func_defaults,
794
old_dbusobj.func_closure)))
796
attr[attrname] = new_dbusfunc
797
elif (getattr(old_dbusobj, "_dbus_is_property", False)
798
and old_dbusobj._dbus_interface.startswith("se.bsnet.fukt.Mandos")):
799
new_dbusfunc = (dbus_service_property
801
old_dbusobj._dbus_signature,
802
old_dbusobj._dbus_access,
803
old_dbusobj._dbus_get_args_options["byte_arrays"])
805
(old_dbusobj.func_code,
806
old_dbusobj.func_globals,
807
old_dbusobj.func_name,
808
old_dbusobj.func_defaults,
809
old_dbusobj.func_closure)))
811
attr[attrname] = new_dbusfunc
812
return type.__new__(mcs, name, bases, attr)
814
class ClientDBus(Client, DBusObjectWithProperties):
815
"""A Client class using D-Bus
818
dbus_object_path: dbus.ObjectPath
819
bus: dbus.SystemBus()
822
runtime_expansions = (Client.runtime_expansions
823
+ ("dbus_object_path",))
825
# dbus.service.Object doesn't use super(), so we can't either.
827
def __init__(self, bus = None, *args, **kwargs):
828
self._approvals_pending = 0
830
Client.__init__(self, *args, **kwargs)
831
# Only now, when this client is initialized, can it show up on
833
client_object_name = unicode(self.name).translate(
836
self.dbus_object_path = (dbus.ObjectPath
837
("/clients/" + client_object_name))
838
DBusObjectWithProperties.__init__(self, self.bus,
839
self.dbus_object_path)
841
def notifychangeproperty(transform_func,
842
dbus_name, type_func=lambda x: x,
844
""" Modify a variable so that its a property that announce its
846
transform_fun: Function that takes a value and transform it to
848
dbus_name: DBus name of the variable
849
type_func: Function that transform the value before sending it
851
variant_level: DBus variant level. default: 1
854
def setter(self, value):
855
old_value = real_value[0]
856
real_value[0] = value
857
if hasattr(self, "dbus_object_path"):
858
if type_func(old_value) != type_func(real_value[0]):
859
dbus_value = transform_func(type_func(real_value[0]),
861
self.PropertyChanged(dbus.String(dbus_name),
864
return property(lambda self: real_value[0], setter)
867
expires = notifychangeproperty(datetime_to_dbus, "Expires")
868
approvals_pending = notifychangeproperty(dbus.Boolean,
871
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
872
last_enabled = notifychangeproperty(datetime_to_dbus,
874
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
875
type_func = lambda checker: checker is not None)
876
last_checked_ok = notifychangeproperty(datetime_to_dbus,
878
last_approval_request = notifychangeproperty(datetime_to_dbus,
879
"LastApprovalRequest")
880
approved_by_default = notifychangeproperty(dbus.Boolean,
882
approval_delay = notifychangeproperty(dbus.UInt16, "ApprovalDelay",
883
type_func = _timedelta_to_milliseconds)
884
approval_duration = notifychangeproperty(dbus.UInt16, "ApprovalDuration",
885
type_func = _timedelta_to_milliseconds)
886
host = notifychangeproperty(dbus.String, "Host")
887
timeout = notifychangeproperty(dbus.UInt16, "Timeout",
888
type_func = _timedelta_to_milliseconds)
889
extended_timeout = notifychangeproperty(dbus.UInt16, "ExtendedTimeout",
890
type_func = _timedelta_to_milliseconds)
891
interval = notifychangeproperty(dbus.UInt16, "Interval",
892
type_func = _timedelta_to_milliseconds)
893
checker_command = notifychangeproperty(dbus.String, "Checker")
895
del notifychangeproperty
897
def __del__(self, *args, **kwargs):
899
self.remove_from_connection()
902
if hasattr(DBusObjectWithProperties, "__del__"):
903
DBusObjectWithProperties.__del__(self, *args, **kwargs)
904
Client.__del__(self, *args, **kwargs)
906
def checker_callback(self, pid, condition, command,
908
self.checker_callback_tag = None
910
if os.WIFEXITED(condition):
911
exitstatus = os.WEXITSTATUS(condition)
913
self.CheckerCompleted(dbus.Int16(exitstatus),
914
dbus.Int64(condition),
915
dbus.String(command))
918
self.CheckerCompleted(dbus.Int16(-1),
919
dbus.Int64(condition),
920
dbus.String(command))
922
return Client.checker_callback(self, pid, condition, command,
925
def start_checker(self, *args, **kwargs):
926
old_checker = self.checker
927
if self.checker is not None:
928
old_checker_pid = self.checker.pid
930
old_checker_pid = None
931
r = Client.start_checker(self, *args, **kwargs)
932
# Only if new checker process was started
933
if (self.checker is not None
934
and old_checker_pid != self.checker.pid):
936
self.CheckerStarted(self.current_checker_command)
939
def _reset_approved(self):
940
self._approved = None
943
def approve(self, value=True):
944
self.send_changedstate()
945
self._approved = value
946
gobject.timeout_add(_timedelta_to_milliseconds
947
(self.approval_duration),
948
self._reset_approved)
951
## D-Bus methods, signals & properties
952
_interface = "se.bsnet.fukt.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"
956
457
# CheckerCompleted - signal
957
@dbus.service.signal(_interface, signature="nxs")
958
def CheckerCompleted(self, exitcode, waitstatus, command):
458
@dbus.service.signal(_interface, signature="bqs")
459
def CheckerCompleted(self, success, condition, command):
1025
579
# StopChecker - method
1026
@dbus.service.method(_interface)
1027
def StopChecker(self):
1032
# ApprovalPending - property
1033
@dbus_service_property(_interface, signature="b", access="read")
1034
def ApprovalPending_dbus_property(self):
1035
return dbus.Boolean(bool(self.approvals_pending))
1037
# ApprovedByDefault - property
1038
@dbus_service_property(_interface, signature="b",
1040
def ApprovedByDefault_dbus_property(self, value=None):
1041
if value is None: # get
1042
return dbus.Boolean(self.approved_by_default)
1043
self.approved_by_default = bool(value)
1045
# ApprovalDelay - property
1046
@dbus_service_property(_interface, signature="t",
1048
def ApprovalDelay_dbus_property(self, value=None):
1049
if value is None: # get
1050
return dbus.UInt64(self.approval_delay_milliseconds())
1051
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1053
# ApprovalDuration - property
1054
@dbus_service_property(_interface, signature="t",
1056
def ApprovalDuration_dbus_property(self, value=None):
1057
if value is None: # get
1058
return dbus.UInt64(_timedelta_to_milliseconds(
1059
self.approval_duration))
1060
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1063
@dbus_service_property(_interface, signature="s", access="read")
1064
def Name_dbus_property(self):
1065
return dbus.String(self.name)
1067
# Fingerprint - property
1068
@dbus_service_property(_interface, signature="s", access="read")
1069
def Fingerprint_dbus_property(self):
1070
return dbus.String(self.fingerprint)
1073
@dbus_service_property(_interface, signature="s",
1075
def Host_dbus_property(self, value=None):
1076
if value is None: # get
1077
return dbus.String(self.host)
1080
# Created - property
1081
@dbus_service_property(_interface, signature="s", access="read")
1082
def Created_dbus_property(self):
1083
return dbus.String(datetime_to_dbus(self.created))
1085
# LastEnabled - property
1086
@dbus_service_property(_interface, signature="s", access="read")
1087
def LastEnabled_dbus_property(self):
1088
return datetime_to_dbus(self.last_enabled)
1090
# Enabled - property
1091
@dbus_service_property(_interface, signature="b",
1093
def Enabled_dbus_property(self, value=None):
1094
if value is None: # get
1095
return dbus.Boolean(self.enabled)
1101
# LastCheckedOK - property
1102
@dbus_service_property(_interface, signature="s",
1104
def LastCheckedOK_dbus_property(self, value=None):
1105
if value is not None:
1108
return datetime_to_dbus(self.last_checked_ok)
1110
# Expires - property
1111
@dbus_service_property(_interface, signature="s", access="read")
1112
def Expires_dbus_property(self):
1113
return datetime_to_dbus(self.expires)
1115
# LastApprovalRequest - property
1116
@dbus_service_property(_interface, signature="s", access="read")
1117
def LastApprovalRequest_dbus_property(self):
1118
return datetime_to_dbus(self.last_approval_request)
1120
# Timeout - property
1121
@dbus_service_property(_interface, signature="t",
1123
def Timeout_dbus_property(self, value=None):
1124
if value is None: # get
1125
return dbus.UInt64(self.timeout_milliseconds())
1126
self.timeout = datetime.timedelta(0, 0, 0, value)
1127
if getattr(self, "disable_initiator_tag", None) is None:
1129
# Reschedule timeout
1130
gobject.source_remove(self.disable_initiator_tag)
1131
self.disable_initiator_tag = None
1133
time_to_die = (self.
1134
_timedelta_to_milliseconds((self
1139
if time_to_die <= 0:
1140
# The timeout has passed
1143
self.expires = (datetime.datetime.utcnow()
1144
+ datetime.timedelta(milliseconds = time_to_die))
1145
self.disable_initiator_tag = (gobject.timeout_add
1146
(time_to_die, self.disable))
1148
# ExtendedTimeout - property
1149
@dbus_service_property(_interface, signature="t",
1151
def ExtendedTimeout_dbus_property(self, value=None):
1152
if value is None: # get
1153
return dbus.UInt64(self.extended_timeout_milliseconds())
1154
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1156
# Interval - property
1157
@dbus_service_property(_interface, signature="t",
1159
def Interval_dbus_property(self, value=None):
1160
if value is None: # get
1161
return dbus.UInt64(self.interval_milliseconds())
1162
self.interval = datetime.timedelta(0, 0, 0, value)
1163
if getattr(self, "checker_initiator_tag", None) is None:
1165
# Reschedule checker run
1166
gobject.source_remove(self.checker_initiator_tag)
1167
self.checker_initiator_tag = (gobject.timeout_add
1168
(value, self.start_checker))
1169
self.start_checker() # Start one now, too
1171
# Checker - property
1172
@dbus_service_property(_interface, signature="s",
1174
def Checker_dbus_property(self, value=None):
1175
if value is None: # get
1176
return dbus.String(self.checker_command)
1177
self.checker_command = value
1179
# CheckerRunning - property
1180
@dbus_service_property(_interface, signature="b",
1182
def CheckerRunning_dbus_property(self, value=None):
1183
if value is None: # get
1184
return dbus.Boolean(self.checker is not None)
1186
self.start_checker()
1190
# ObjectPath - property
1191
@dbus_service_property(_interface, signature="o", access="read")
1192
def ObjectPath_dbus_property(self):
1193
return self.dbus_object_path # is already a dbus.ObjectPath
1196
@dbus_service_property(_interface, signature="ay",
1197
access="write", byte_arrays=True)
1198
def Secret_dbus_property(self, value):
1199
self.secret = str(value)
580
StopChecker = dbus.service.method(_interface)(stop_checker)
581
StopChecker.__name__ = "StopChecker"
1204
class ProxyClient(object):
1205
def __init__(self, child_pipe, fpr, address):
1206
self._pipe = child_pipe
1207
self._pipe.send(('init', fpr, address))
1208
if not self._pipe.recv():
1211
def __getattribute__(self, name):
1212
if(name == '_pipe'):
1213
return super(ProxyClient, self).__getattribute__(name)
1214
self._pipe.send(('getattr', name))
1215
data = self._pipe.recv()
1216
if data[0] == 'data':
1218
if data[0] == 'function':
1219
def func(*args, **kwargs):
1220
self._pipe.send(('funcall', name, args, kwargs))
1221
return self._pipe.recv()[1]
1224
def __setattr__(self, name, value):
1225
if(name == '_pipe'):
1226
return super(ProxyClient, self).__setattr__(name, value)
1227
self._pipe.send(('setattr', name, value))
1229
class ClientDBusTransitional(ClientDBus):
1230
__metaclass__ = transitional_dbus_metaclass
1232
class ClientHandler(socketserver.BaseRequestHandler, object):
1233
"""A class to handle client connections.
1235
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.
1236
647
Note: This will run in its own forked process."""
1238
649
def handle(self):
1239
with contextlib.closing(self.server.child_pipe) as child_pipe:
1240
logger.info("TCP connection from: %s",
1241
unicode(self.client_address))
1242
logger.debug("Pipe FD: %d",
1243
self.server.child_pipe.fileno())
1245
session = (gnutls.connection
1246
.ClientSession(self.request,
1248
.X509Credentials()))
1250
# Note: gnutls.connection.X509Credentials is really a
1251
# generic GnuTLS certificate credentials object so long as
1252
# no X.509 keys are added to it. Therefore, we can use it
1253
# here despite using OpenPGP certificates.
1255
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1256
# "+AES-256-CBC", "+SHA1",
1257
# "+COMP-NULL", "+CTYPE-OPENPGP",
1259
# Use a fallback default, since this MUST be set.
1260
priority = self.server.gnutls_priority
1261
if priority is None:
1263
(gnutls.library.functions
1264
.gnutls_priority_set_direct(session._c_object,
1267
# Start communication using the Mandos protocol
1268
# Get protocol number
1269
line = self.request.makefile().readline()
1270
logger.debug("Protocol version: %r", line)
1272
if int(line.strip().split()[0]) > 1:
1274
except (ValueError, IndexError, RuntimeError) as error:
1275
logger.error("Unknown protocol version: %s", error)
1278
# Start GnuTLS connection
1281
except gnutls.errors.GNUTLSError as error:
1282
logger.warning("Handshake failed: %s", error)
1283
# Do not run session.bye() here: the session is not
1284
# established. Just abandon the request.
1286
logger.debug("Handshake succeeded")
1288
approval_required = False
1291
fpr = self.fingerprint(self.peer_certificate
1294
gnutls.errors.GNUTLSError) as error:
1295
logger.warning("Bad certificate: %s", error)
1297
logger.debug("Fingerprint: %s", fpr)
1300
client = ProxyClient(child_pipe, fpr,
1301
self.client_address)
1305
if client.approval_delay:
1306
delay = client.approval_delay
1307
client.approvals_pending += 1
1308
approval_required = True
1311
if not client.enabled:
1312
logger.info("Client %s is disabled",
1314
if self.server.use_dbus:
1316
client.Rejected("Disabled")
1319
if client._approved or not client.approval_delay:
1320
#We are approved or approval is disabled
1322
elif client._approved is None:
1323
logger.info("Client %s needs approval",
1325
if self.server.use_dbus:
1327
client.NeedApproval(
1328
client.approval_delay_milliseconds(),
1329
client.approved_by_default)
1331
logger.warning("Client %s was not approved",
1333
if self.server.use_dbus:
1335
client.Rejected("Denied")
1338
#wait until timeout or approved
1339
#x = float(client._timedelta_to_milliseconds(delay))
1340
time = datetime.datetime.now()
1341
client.changedstate.acquire()
1342
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1343
client.changedstate.release()
1344
time2 = datetime.datetime.now()
1345
if (time2 - time) >= delay:
1346
if not client.approved_by_default:
1347
logger.warning("Client %s timed out while"
1348
" waiting for approval",
1350
if self.server.use_dbus:
1352
client.Rejected("Approval timed out")
1357
delay -= time2 - time
1360
while sent_size < len(client.secret):
1362
sent = session.send(client.secret[sent_size:])
1363
except gnutls.errors.GNUTLSError as error:
1364
logger.warning("gnutls send failed")
1366
logger.debug("Sent: %d, remaining: %d",
1367
sent, len(client.secret)
1368
- (sent_size + sent))
1371
logger.info("Sending secret to %s", client.name)
1372
# bump the timeout as if seen
1373
client.checked_ok(client.extended_timeout)
1374
if self.server.use_dbus:
1379
if approval_required:
1380
client.approvals_pending -= 1
1383
except gnutls.errors.GNUTLSError as error:
1384
logger.warning("GnuTLS bye failed")
1387
def peer_certificate(session):
1388
"Return the peer's OpenPGP certificate as a bytestring"
1389
# If not an OpenPGP certificate...
1390
if (gnutls.library.functions
1391
.gnutls_certificate_type_get(session._c_object)
1392
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1393
# ...do the normal thing
1394
return session.peer_certificate
1395
list_size = ctypes.c_uint(1)
1396
cert_list = (gnutls.library.functions
1397
.gnutls_certificate_get_peers
1398
(session._c_object, ctypes.byref(list_size)))
1399
if not bool(cert_list) and list_size.value != 0:
1400
raise gnutls.errors.GNUTLSError("error getting peer"
1402
if list_size.value == 0:
1405
return ctypes.string_at(cert.data, cert.size)
1408
def fingerprint(openpgp):
1409
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1410
# New GnuTLS "datum" with the OpenPGP public key
1411
datum = (gnutls.library.types
1412
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1415
ctypes.c_uint(len(openpgp))))
1416
# New empty GnuTLS certificate
1417
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1418
(gnutls.library.functions
1419
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1420
# Import the OpenPGP public key into the certificate
1421
(gnutls.library.functions
1422
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1423
gnutls.library.constants
1424
.GNUTLS_OPENPGP_FMT_RAW))
1425
# Verify the self signature in the key
1426
crtverify = ctypes.c_uint()
1427
(gnutls.library.functions
1428
.gnutls_openpgp_crt_verify_self(crt, 0,
1429
ctypes.byref(crtverify)))
1430
if crtverify.value != 0:
1431
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1432
raise (gnutls.errors.CertificateSecurityError
1434
# New buffer for the fingerprint
1435
buf = ctypes.create_string_buffer(20)
1436
buf_len = ctypes.c_size_t()
1437
# Get the fingerprint from the certificate into the buffer
1438
(gnutls.library.functions
1439
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1440
ctypes.byref(buf_len)))
1441
# Deinit the certificate
1442
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1443
# Convert the buffer to a Python bytestring
1444
fpr = ctypes.string_at(buf, buf_len.value)
1445
# Convert the bytestring to hexadecimal notation
1446
hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1450
class MultiprocessingMixIn(object):
1451
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1452
def sub_process_main(self, request, address):
1454
self.finish_request(request, address)
1456
self.handle_error(request, address)
1457
self.close_request(request)
1459
def process_request(self, request, address):
1460
"""Start a new process to process the request."""
1461
multiprocessing.Process(target = self.sub_process_main,
1462
args = (request, address)).start()
1465
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1466
""" adds a pipe to the MixIn """
1467
def process_request(self, request, client_address):
1468
"""Overrides and wraps the original process_request().
1470
This function creates a new pipe in self.pipe
1472
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1474
super(MultiprocessingMixInWithPipe,
1475
self).process_request(request, client_address)
1476
self.child_pipe.close()
1477
self.add_pipe(parent_pipe)
1479
def add_pipe(self, parent_pipe):
1480
"""Dummy function; override as necessary"""
1481
raise NotImplementedError
1484
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1485
socketserver.TCPServer, object):
1486
"""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
1489
729
enabled: Boolean; whether this server is activated yet
1490
interface: None or a network interface name (string)
1491
use_ipv6: Boolean; to use IPv6 or not
1493
def __init__(self, server_address, RequestHandlerClass,
1494
interface=None, use_ipv6=True):
1495
self.interface = interface
1497
self.address_family = socket.AF_INET6
1498
socketserver.TCPServer.__init__(self, server_address,
1499
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)
1500
741
def server_bind(self):
1501
742
"""This overrides the normal server_bind() function
1502
743
to bind to an interface if one was specified, and also NOT to
1503
744
bind to an address or port if they were not specified."""
1504
if self.interface is not None:
1505
if SO_BINDTODEVICE is None:
1506
logger.error("SO_BINDTODEVICE does not exist;"
1507
" cannot bind to interface %s",
1511
self.socket.setsockopt(socket.SOL_SOCKET,
1515
except socket.error as error:
1516
if error[0] == errno.EPERM:
1517
logger.error("No permission to"
1518
" bind to interface %s",
1520
elif error[0] == errno.ENOPROTOOPT:
1521
logger.error("SO_BINDTODEVICE not available;"
1522
" 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"])
1526
759
# Only bind(2) the socket if we really need to.
1527
760
if self.server_address[0] or self.server_address[1]:
1528
761
if not self.server_address[0]:
1529
if self.address_family == socket.AF_INET6:
1530
any_address = "::" # in6addr_any
1532
any_address = socket.INADDR_ANY
1533
self.server_address = (any_address,
763
self.server_address = (in6addr_any,
1534
764
self.server_address[1])
1535
765
elif not self.server_address[1]:
1536
766
self.server_address = (self.server_address[0],
1538
# if self.interface:
768
# if self.settings["interface"]:
1539
769
# self.server_address = (self.server_address[0],
1542
772
# if_nametoindex
1544
return socketserver.TCPServer.server_bind(self)
1547
class MandosServer(IPv6_TCPServer):
1551
clients: set of Client objects
1552
gnutls_priority GnuTLS priority string
1553
use_dbus: Boolean; to emit D-Bus signals or not
1555
Assumes a gobject.MainLoop event loop.
1557
def __init__(self, server_address, RequestHandlerClass,
1558
interface=None, use_ipv6=True, clients=None,
1559
gnutls_priority=None, use_dbus=True):
1560
self.enabled = False
1561
self.clients = clients
1562
if self.clients is None:
1563
self.clients = set()
1564
self.use_dbus = use_dbus
1565
self.gnutls_priority = gnutls_priority
1566
IPv6_TCPServer.__init__(self, server_address,
1567
RequestHandlerClass,
1568
interface = interface,
1569
use_ipv6 = use_ipv6)
775
return super(IPv6_TCPServer, self).server_bind()
1570
776
def server_activate(self):
1571
777
if self.enabled:
1572
return socketserver.TCPServer.server_activate(self)
778
return super(IPv6_TCPServer, self).server_activate()
1573
779
def enable(self):
1574
780
self.enabled = True
1575
def add_pipe(self, parent_pipe):
1576
# Call "handle_ipc" for both data and EOF events
1577
gobject.io_add_watch(parent_pipe.fileno(),
1578
gobject.IO_IN | gobject.IO_HUP,
1579
functools.partial(self.handle_ipc,
1580
parent_pipe = parent_pipe))
1582
def handle_ipc(self, source, condition, parent_pipe=None,
1583
client_object=None):
1585
gobject.IO_IN: "IN", # There is data to read.
1586
gobject.IO_OUT: "OUT", # Data can be written (without
1588
gobject.IO_PRI: "PRI", # There is urgent data to read.
1589
gobject.IO_ERR: "ERR", # Error condition.
1590
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1591
# broken, usually for pipes and
1594
conditions_string = ' | '.join(name
1596
condition_names.iteritems()
1597
if cond & condition)
1598
# error or the other end of multiprocessing.Pipe has closed
1599
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1602
# Read a request from the child
1603
request = parent_pipe.recv()
1604
command = request[0]
1606
if command == 'init':
1608
address = request[2]
1610
for c in self.clients:
1611
if c.fingerprint == fpr:
1615
logger.info("Client not found for fingerprint: %s, ad"
1616
"dress: %s", fpr, address)
1619
mandos_dbus_service.ClientNotFound(fpr, address[0])
1620
parent_pipe.send(False)
1623
gobject.io_add_watch(parent_pipe.fileno(),
1624
gobject.IO_IN | gobject.IO_HUP,
1625
functools.partial(self.handle_ipc,
1626
parent_pipe = parent_pipe,
1627
client_object = client))
1628
parent_pipe.send(True)
1629
# remove the old hook in favor of the new above hook on same fileno
1631
if command == 'funcall':
1632
funcname = request[1]
1636
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1638
if command == 'getattr':
1639
attrname = request[1]
1640
if callable(client_object.__getattribute__(attrname)):
1641
parent_pipe.send(('function',))
1643
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1645
if command == 'setattr':
1646
attrname = request[1]
1648
setattr(client_object, attrname, value)
1653
783
def string_to_delta(interval):
1654
784
"""Parse a string and return a datetime.timedelta
1656
786
>>> string_to_delta('7d')
1657
787
datetime.timedelta(7)
1658
788
>>> string_to_delta('60s')
1922
1052
# No console logging
1923
1053
logger.removeHandler(console)
1925
# Need to fork before connecting to D-Bus
1927
1054
# Close all input and output, do double fork, etc.
1931
# From the Avahi example code
1932
DBusGMainLoop(set_as_default=True )
1933
main_loop = gobject.MainLoop()
1934
bus = dbus.SystemBus()
1935
# End of Avahi example code
1938
bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
1939
bus, do_not_queue=True)
1940
bus_name2 = dbus.service.BusName("se.recompile.Mandos",
1941
bus, do_not_queue=True)
1942
except dbus.exceptions.NameExistsException as e:
1943
logger.error(unicode(e) + ", disabling D-Bus")
1945
server_settings["use_dbus"] = False
1946
tcp_server.use_dbus = False
1947
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1948
service = AvahiService(name = server_settings["servicename"],
1949
servicetype = "_mandos._tcp",
1950
protocol = protocol, bus = bus)
1951
if server_settings["interface"]:
1952
service.interface = (if_nametoindex
1953
(str(server_settings["interface"])))
1955
global multiprocessing_manager
1956
multiprocessing_manager = multiprocessing.Manager()
1958
client_class = Client
1960
client_class = functools.partial(ClientDBusTransitional, bus = bus)
1961
def client_config_items(config, section):
1962
special_settings = {
1963
"approved_by_default":
1964
lambda: config.getboolean(section,
1965
"approved_by_default"),
1967
for name, value in config.items(section):
1969
yield (name, special_settings[name]())
1973
tcp_server.clients.update(set(
1974
client_class(name = section,
1975
config= dict(client_config_items(
1976
client_config, section)))
1977
for section in client_config.sections()))
1978
if not tcp_server.clients:
1979
logger.warning("No clients defined")
1059
pidfile.write(str(pid) + "\n")
1063
logger.error(u"Could not write to file %r with PID %d",
1066
# "pidfile" was never created
1071
"Cleanup function; run on exit"
1073
# From the Avahi example code
1074
if not group is None:
1077
# End of Avahi example code
1080
client = clients.pop()
1081
client.disable_hook = None
1084
atexit.register(cleanup)
1985
pidfile.write(str(pid) + "\n".encode("utf-8"))
1988
logger.error("Could not write to file %r with PID %d",
1991
# "pidfile" was never created
1995
1087
signal.signal(signal.SIGINT, signal.SIG_IGN)
1997
1088
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1998
1089
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2001
class MandosDBusService(dbus.service.Object):
1092
class MandosServer(dbus.service.Object):
2002
1093
"""A D-Bus proxy object"""
2003
1094
def __init__(self):
2004
dbus.service.Object.__init__(self, bus, "/")
2005
_interface = "se.bsnet.fukt.Mandos"
1095
dbus.service.Object.__init__(self, bus,
1097
_interface = u"org.mandos_system.Mandos"
1099
@dbus.service.signal(_interface, signature="oa{sv}")
1100
def ClientAdded(self, objpath, properties):
2007
1104
@dbus.service.signal(_interface, signature="o")
2008
def ClientAdded(self, objpath):
2012
@dbus.service.signal(_interface, signature="ss")
2013
def ClientNotFound(self, fingerprint, address):
2017
@dbus.service.signal(_interface, signature="os")
2018
def ClientRemoved(self, objpath, name):
1105
def ClientRemoved(self, objpath):
2022
1109
@dbus.service.method(_interface, out_signature="ao")
2023
1110
def GetAllClients(self):
2025
return dbus.Array(c.dbus_object_path
2026
for c in tcp_server.clients)
2028
@dbus.service.method(_interface,
2029
out_signature="a{oa{sv}}")
1111
return dbus.Array(c.dbus_object_path for c in clients)
1113
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
2030
1114
def GetAllClientsWithProperties(self):
2032
1115
return dbus.Dictionary(
2033
((c.dbus_object_path, c.GetAll(""))
2034
for c in tcp_server.clients),
1116
((c.dbus_object_path, c.GetAllProperties())
2035
1118
signature="oa{sv}")
2037
1120
@dbus.service.method(_interface, in_signature="o")
2038
1121
def RemoveClient(self, object_path):
2040
for c in tcp_server.clients:
2041
1123
if c.dbus_object_path == object_path:
2042
tcp_server.clients.remove(c)
2043
c.remove_from_connection()
2044
1125
# Don't signal anything except ClientRemoved
2045
c.disable(quiet=True)
2046
1128
# Emit D-Bus signal
2047
self.ClientRemoved(object_path, c.name)
1129
self.ClientRemoved(object_path)
2049
raise KeyError(object_path)
1132
@dbus.service.method(_interface)
2053
class MandosDBusServiceTransitional(MandosDBusService):
2054
__metaclass__ = transitional_dbus_metaclass
2055
mandos_dbus_service = MandosDBusServiceTransitional()
2058
"Cleanup function; run on exit"
2061
while tcp_server.clients:
2062
client = tcp_server.clients.pop()
2064
client.remove_from_connection()
2065
client.disable_hook = None
2066
# Don't signal anything except ClientRemoved
2067
client.disable(quiet=True)
2070
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2073
atexit.register(cleanup)
2075
for client in tcp_server.clients:
1138
mandos_server = MandosServer()
1140
for client in clients:
2077
1142
# Emit D-Bus signal
2078
mandos_dbus_service.ClientAdded(client.dbus_object_path)
1143
mandos_server.ClientAdded(client.dbus_object_path,
1144
client.GetAllProperties())
2079
1145
client.enable()
2081
1147
tcp_server.enable()