149
126
self.rename_count = 0
150
127
self.max_renames = max_renames
151
128
self.protocol = protocol
152
self.group = None # our entry group
155
self.entry_group_state_changed_match = None
156
129
def rename(self):
157
130
"""Derived from the Avahi example code"""
158
131
if self.rename_count >= self.max_renames:
159
logger.critical("No suitable Zeroconf service name found"
160
" after %i retries, exiting.",
132
logger.critical(u"No suitable Zeroconf service name found"
133
u" after %i retries, exiting.",
161
134
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 ...",
135
raise AvahiServiceError(u"Too many renames")
136
self.name = server.GetAlternativeServiceName(self.name)
137
logger.info(u"Changing Zeroconf service name to %r ...",
166
139
syslogger.setFormatter(logging.Formatter
167
140
('Mandos (%s) [%%(process)d]:'
168
141
' %%(levelname)s: %%(message)s'
173
except dbus.exceptions.DBusException as error:
174
logger.critical("DBusException: %s", error)
177
145
self.rename_count += 1
178
146
def remove(self):
179
147
"""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:
148
if group is not None:
186
151
"""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):
154
group = dbus.Interface(bus.get_object
156
server.EntryGroupNew()),
157
avahi.DBUS_INTERFACE_ENTRY_GROUP)
158
group.connect_to_signal('StateChanged',
159
entry_group_state_changed)
160
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
161
service.name, service.type)
163
self.interface, # interface
164
self.protocol, # protocol
165
dbus.UInt32(0), # flags
166
self.name, self.type,
167
self.domain, self.host,
168
dbus.UInt16(self.port),
169
avahi.string_array_to_txt_array(self.TXT))
172
# From the Avahi example code:
173
group = None # our entry group
174
# End of Avahi example code
177
def _datetime_to_dbus(dt, variant_level=0):
178
"""Convert a UTC datetime.datetime() to a D-Bus type."""
179
return dbus.String(dt.isoformat(), variant_level=variant_level)
182
class Client(dbus.service.Object):
275
183
"""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
185
name: string; from the config file, used in log messages and
187
fingerprint: string (40 or 32 hexadecimal digits); used to
188
uniquely identify the client
189
secret: bytestring; sent verbatim (over TLS) to client
190
host: string; available for use by the checker command
191
created: datetime.datetime(); (UTC) object creation
192
last_enabled: datetime.datetime(); (UTC)
194
last_checked_ok: datetime.datetime(); (UTC) or None
195
timeout: datetime.timedelta(); How long from last_checked_ok
196
until this client is invalid
197
interval: datetime.timedelta(); How often to start a new checker
198
disable_hook: If set, called by disable() as disable_hook(self)
281
199
checker: subprocess.Popen(); a running checker process used
282
200
to see if the client lives.
283
201
'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
202
checker_initiator_tag: a gobject event source tag, or None
203
disable_initiator_tag: - '' -
204
checker_callback_tag: - '' -
205
checker_command: string; External command which is run to check if
206
client lives. %() expansions are done at
287
207
runtime with vars(self) as dict, so that for
288
208
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
209
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
210
use_dbus: bool(); Whether to provide D-Bus interface and signals
211
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
213
def timeout_milliseconds(self):
319
214
"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)
215
return ((self.timeout.days * 24 * 60 * 60 * 1000)
216
+ (self.timeout.seconds * 1000)
217
+ (self.timeout.microseconds // 1000))
326
219
def interval_milliseconds(self):
327
220
"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):
221
return ((self.interval.days * 24 * 60 * 60 * 1000)
222
+ (self.interval.seconds * 1000)
223
+ (self.interval.microseconds // 1000))
225
def __init__(self, name = None, disable_hook=None, config=None,
334
227
"""Note: the 'checker' key in 'config' sets the
335
228
'checker_command' attribute and *not* the 'checker'
338
231
if config is None:
340
logger.debug("Creating client %r", self.name)
233
logger.debug(u"Creating client %r", self.name)
234
self.use_dbus = False # During __init__
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
264
self.current_checker_command = None
374
265
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())
266
# Only now, when this client is initialized, can it show up on
268
self.use_dbus = use_dbus
270
self.dbus_object_path = (dbus.ObjectPath
272
+ self.name.replace(".", "_")))
273
dbus.service.Object.__init__(self, bus,
274
self.dbus_object_path)
385
def send_changedstate(self):
386
self.changedstate.acquire()
387
self.changedstate.notify_all()
388
self.changedstate.release()
390
276
def enable(self):
391
277
"""Start this client's checker and timeout hooks"""
392
if getattr(self, "enabled", False):
395
self.send_changedstate()
278
self.last_enabled = datetime.datetime.utcnow()
396
279
# Schedule a new checker to be started an 'interval' from now,
397
280
# and every interval from then on.
398
281
self.checker_initiator_tag = (gobject.timeout_add
399
282
(self.interval_milliseconds(),
400
283
self.start_checker))
284
# Also start a new checker *right now*.
401
286
# Schedule a disable() when 'timeout' has passed
402
self.expires = datetime.datetime.utcnow() + self.timeout
403
287
self.disable_initiator_tag = (gobject.timeout_add
404
288
(self.timeout_milliseconds(),
406
290
self.enabled = True
407
self.last_enabled = datetime.datetime.utcnow()
408
# Also start a new checker *right now*.
293
self.PropertyChanged(dbus.String(u"enabled"),
294
dbus.Boolean(True, variant_level=1))
295
self.PropertyChanged(dbus.String(u"last_enabled"),
296
(_datetime_to_dbus(self.last_enabled,
411
def disable(self, quiet=True):
412
300
"""Disable this client."""
413
301
if not getattr(self, "enabled", False):
416
self.send_changedstate()
418
logger.info("Disabling client %s", self.name)
303
logger.info(u"Disabling client %s", self.name)
419
304
if getattr(self, "disable_initiator_tag", False):
420
305
gobject.source_remove(self.disable_initiator_tag)
421
306
self.disable_initiator_tag = None
423
307
if getattr(self, "checker_initiator_tag", False):
424
308
gobject.source_remove(self.checker_initiator_tag)
425
309
self.checker_initiator_tag = None
551
447
self.checker_callback_tag = None
552
448
if getattr(self, "checker", None) is None:
554
logger.debug("Stopping checker for %(name)s", vars(self))
450
logger.debug(u"Stopping checker for %(name)s", vars(self))
556
452
os.kill(self.checker.pid, signal.SIGTERM)
558
454
#if self.checker.poll() is None:
559
455
# os.kill(self.checker.pid, signal.SIGKILL)
560
except OSError as error:
456
except OSError, error:
561
457
if error.errno != errno.ESRCH: # No such process
563
459
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"
461
self.PropertyChanged(dbus.String(u"checker_running"),
462
dbus.Boolean(False, variant_level=1))
464
def still_valid(self):
465
"""Has the timeout not yet passed for this client?"""
466
if not getattr(self, "enabled", False):
468
now = datetime.datetime.utcnow()
469
if self.last_checked_ok is None:
470
return now < (self.created + self.timeout)
472
return now < (self.last_checked_ok + self.timeout)
474
## D-Bus methods & signals
475
_interface = u"se.bsnet.fukt.Mandos.Client"
478
CheckedOK = dbus.service.method(_interface)(checked_ok)
479
CheckedOK.__name__ = "CheckedOK"
956
481
# CheckerCompleted - signal
957
482
@dbus.service.signal(_interface, signature="nxs")
1025
618
# 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)
619
StopChecker = dbus.service.method(_interface)(stop_checker)
620
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.
625
def peer_certificate(session):
626
"Return the peer's OpenPGP certificate as a bytestring"
627
# If not an OpenPGP certificate...
628
if (gnutls.library.functions
629
.gnutls_certificate_type_get(session._c_object)
630
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
631
# ...do the normal thing
632
return session.peer_certificate
633
list_size = ctypes.c_uint(1)
634
cert_list = (gnutls.library.functions
635
.gnutls_certificate_get_peers
636
(session._c_object, ctypes.byref(list_size)))
637
if not bool(cert_list) and list_size.value != 0:
638
raise gnutls.errors.GNUTLSError("error getting peer"
640
if list_size.value == 0:
643
return ctypes.string_at(cert.data, cert.size)
646
def fingerprint(openpgp):
647
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
648
# New GnuTLS "datum" with the OpenPGP public key
649
datum = (gnutls.library.types
650
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
653
ctypes.c_uint(len(openpgp))))
654
# New empty GnuTLS certificate
655
crt = gnutls.library.types.gnutls_openpgp_crt_t()
656
(gnutls.library.functions
657
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
658
# Import the OpenPGP public key into the certificate
659
(gnutls.library.functions
660
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
661
gnutls.library.constants
662
.GNUTLS_OPENPGP_FMT_RAW))
663
# Verify the self signature in the key
664
crtverify = ctypes.c_uint()
665
(gnutls.library.functions
666
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
667
if crtverify.value != 0:
668
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
669
raise gnutls.errors.CertificateSecurityError("Verify failed")
670
# New buffer for the fingerprint
671
buf = ctypes.create_string_buffer(20)
672
buf_len = ctypes.c_size_t()
673
# Get the fingerprint from the certificate into the buffer
674
(gnutls.library.functions
675
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
676
ctypes.byref(buf_len)))
677
# Deinit the certificate
678
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
679
# Convert the buffer to a Python bytestring
680
fpr = ctypes.string_at(buf, buf_len.value)
681
# Convert the bytestring to hexadecimal notation
682
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
686
class TCP_handler(SocketServer.BaseRequestHandler, object):
687
"""A TCP request handler class.
688
Instantiated by IPv6_TCPServer for each request to handle it.
1236
689
Note: This will run in its own forked process."""
1238
691
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())
692
logger.info(u"TCP connection from: %s",
693
unicode(self.client_address))
694
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
695
# Open IPC pipe to parent process
696
with closing(os.fdopen(self.server.pipe[1], "w", 1)) as ipc:
1245
697
session = (gnutls.connection
1246
698
.ClientSession(self.request,
1247
699
gnutls.connection
1248
700
.X509Credentials()))
702
line = self.request.makefile().readline()
703
logger.debug(u"Protocol version: %r", line)
705
if int(line.strip().split()[0]) > 1:
707
except (ValueError, IndexError, RuntimeError), error:
708
logger.error(u"Unknown protocol version: %s", error)
1250
711
# Note: gnutls.connection.X509Credentials is really a
1251
712
# generic GnuTLS certificate credentials object so long as
1252
713
# no X.509 keys are added to it. Therefore, we can use it
1253
714
# here despite using OpenPGP certificates.
1255
716
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1256
# "+AES-256-CBC", "+SHA1",
1257
# "+COMP-NULL", "+CTYPE-OPENPGP",
717
# "+AES-256-CBC", "+SHA1",
718
# "+COMP-NULL", "+CTYPE-OPENPGP",
1259
720
# Use a fallback default, since this MUST be set.
1260
priority = self.server.gnutls_priority
1261
if priority is None:
721
priority = self.server.settings.get("priority", "NORMAL")
1263
722
(gnutls.library.functions
1264
723
.gnutls_priority_set_direct(session._c_object,
1265
724
priority, None))
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
1280
727
session.handshake()
1281
except gnutls.errors.GNUTLSError as error:
1282
logger.warning("Handshake failed: %s", error)
728
except gnutls.errors.GNUTLSError, error:
729
logger.warning(u"Handshake failed: %s", error)
1283
730
# Do not run session.bye() here: the session is not
1284
731
# established. Just abandon the request.
1286
logger.debug("Handshake succeeded")
1288
approval_required = False
733
logger.debug(u"Handshake succeeded")
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 """
735
fpr = fingerprint(peer_certificate(session))
736
except (TypeError, gnutls.errors.GNUTLSError), error:
737
logger.warning(u"Bad certificate: %s", error)
740
logger.debug(u"Fingerprint: %s", fpr)
742
for c in self.server.clients:
743
if c.fingerprint == fpr:
747
logger.warning(u"Client not found for fingerprint: %s",
749
ipc.write("NOTFOUND %s\n" % fpr)
752
# Have to check if client.still_valid(), since it is
753
# possible that the client timed out while establishing
754
# the GnuTLS session.
755
if not client.still_valid():
756
logger.warning(u"Client %(name)s is invalid",
758
ipc.write("INVALID %s\n" % client.name)
761
ipc.write("SENDING %s\n" % client.name)
763
while sent_size < len(client.secret):
764
sent = session.send(client.secret[sent_size:])
765
logger.debug(u"Sent: %d, remaining: %d",
766
sent, len(client.secret)
767
- (sent_size + sent))
772
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
773
"""Like SocketServer.ForkingMixIn, but also pass a pipe.
774
Assumes a gobject.MainLoop event loop.
1467
776
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
777
"""This overrides and wraps the original process_request().
778
This function creates a new pipe in self.pipe
1472
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1474
super(MultiprocessingMixInWithPipe,
780
self.pipe = os.pipe()
781
super(ForkingMixInWithPipe,
1475
782
self).process_request(request, client_address)
1476
self.child_pipe.close()
1477
self.add_pipe(parent_pipe)
1479
def add_pipe(self, parent_pipe):
783
os.close(self.pipe[1]) # close write end
784
# Call "handle_ipc" for both data and EOF events
785
gobject.io_add_watch(self.pipe[0],
786
gobject.IO_IN | gobject.IO_HUP,
788
def handle_ipc(source, condition):
1480
789
"""Dummy function; override as necessary"""
1481
raise NotImplementedError
1484
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1485
socketserver.TCPServer, object):
794
class IPv6_TCPServer(ForkingMixInWithPipe,
795
SocketServer.TCPServer, object):
1486
796
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
798
settings: Server settings
799
clients: Set() of Client objects
1489
800
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)
802
address_family = socket.AF_INET6
803
def __init__(self, *args, **kwargs):
804
if "settings" in kwargs:
805
self.settings = kwargs["settings"]
806
del kwargs["settings"]
807
if "clients" in kwargs:
808
self.clients = kwargs["clients"]
809
del kwargs["clients"]
810
if "use_ipv6" in kwargs:
811
if not kwargs["use_ipv6"]:
812
self.address_family = socket.AF_INET
813
del kwargs["use_ipv6"]
815
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1500
816
def server_bind(self):
1501
817
"""This overrides the normal server_bind() function
1502
818
to bind to an interface if one was specified, and also NOT to
1503
819
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",
820
if self.settings["interface"]:
821
# 25 is from /usr/include/asm-i486/socket.h
822
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
824
self.socket.setsockopt(socket.SOL_SOCKET,
826
self.settings["interface"])
827
except socket.error, error:
828
if error[0] == errno.EPERM:
829
logger.error(u"No permission to"
830
u" bind to interface %s",
831
self.settings["interface"])
1526
834
# Only bind(2) the socket if we really need to.
1527
835
if self.server_address[0] or self.server_address[1]:
1528
836
if not self.server_address[0]:
1535
843
elif not self.server_address[1]:
1536
844
self.server_address = (self.server_address[0],
1538
# if self.interface:
846
# if self.settings["interface"]:
1539
847
# self.server_address = (self.server_address[0],
1542
850
# 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)
853
return super(IPv6_TCPServer, self).server_bind()
1570
854
def server_activate(self):
1571
855
if self.enabled:
1572
return socketserver.TCPServer.server_activate(self)
856
return super(IPv6_TCPServer, self).server_activate()
1573
857
def enable(self):
1574
858
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):
859
def handle_ipc(self, source, condition, file_objects={}):
1584
860
condition_names = {
1585
gobject.IO_IN: "IN", # There is data to read.
861
gobject.IO_IN: "IN", # There is data to read.
1586
862
gobject.IO_OUT: "OUT", # Data can be written (without
1588
864
gobject.IO_PRI: "PRI", # There is urgent data to read.
1589
865
gobject.IO_ERR: "ERR", # Error condition.
1590
866
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1591
# broken, usually for pipes and
867
# broken, usually for pipes and
1594
870
conditions_string = ' | '.join(name
1595
871
for cond, name in
1596
872
condition_names.iteritems()
1597
873
if cond & condition)
1598
# error or the other end of multiprocessing.Pipe has closed
1599
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
874
logger.debug("Handling IPC: FD = %d, condition = %s", source,
877
# Turn the pipe file descriptor into a Python file object
878
if source not in file_objects:
879
file_objects[source] = os.fdopen(source, "r", 1)
881
# Read a line from the file object
882
cmdline = file_objects[source].readline()
883
if not cmdline: # Empty line means end of file
885
file_objects[source].close()
886
del file_objects[source]
888
# Stop calling this function
1602
# Read a request from the child
1603
request = parent_pipe.recv()
1604
command = request[0]
891
logger.debug("IPC command: %r\n" % cmdline)
1606
if command == 'init':
1608
address = request[2]
1610
for c in self.clients:
1611
if c.fingerprint == fpr:
893
# Parse and act on command
894
cmd, args = cmdline.split(None, 1)
895
if cmd == "NOTFOUND":
896
if self.settings["use_dbus"]:
898
mandos_dbus_service.ClientNotFound(args)
899
elif cmd == "INVALID":
900
if self.settings["use_dbus"]:
901
for client in self.clients:
902
if client.name == args:
906
elif cmd == "SENDING":
907
for client in self.clients:
908
if client.name == args:
910
if self.settings["use_dbus"]:
912
client.ReceivedSecret()
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)
915
logger.error("Unknown IPC command: %r", cmdline)
917
# Keep calling this function