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
132
raise AvahiServiceError("Too many renames")
163
self.name = unicode(self.server.GetAlternativeServiceName(self.name))
164
logger.info("Changing Zeroconf service name to %r ...",
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
class Client(dbus.service.Object):
275
174
"""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
281
checker: subprocess.Popen(); a running checker process used
282
to see if the client lives.
283
'None' if no process is running.
284
checker_callback_tag: a gobject event source tag, or None
285
checker_command: string; External command which is run to check
286
if client lives. %() expansions are done at
287
runtime with vars(self) as dict, so that for
288
instance %(name)s can be used in the command.
289
checker_initiator_tag: a gobject event source tag, or None
290
created: datetime.datetime(); (UTC) object creation
291
current_checker_command: string; current running checker_command
292
disable_hook: If set, called by disable() as disable_hook(self)
293
disable_initiator_tag: a gobject event source tag, or None
176
name: string; from the config file, used in log messages
295
177
fingerprint: string (40 or 32 hexadecimal digits); used to
296
178
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
179
secret: bytestring; sent verbatim (over TLS) to client
180
host: string; available for use by the checker command
181
created: datetime.datetime(); (UTC) object creation
182
started: datetime.datetime(); (UTC) last started
300
183
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
184
timeout: datetime.timedelta(); How long from last_checked_ok
185
until this client is invalid
186
interval: datetime.timedelta(); How often to start a new checker
187
stop_hook: If set, called by stop() as stop_hook(self)
188
checker: subprocess.Popen(); a running checker process used
189
to see if the client lives.
190
'None' if no process is running.
191
checker_initiator_tag: a gobject event source tag, or None
192
stop_initiator_tag: - '' -
193
checker_callback_tag: - '' -
194
checker_command: string; External command which is run to check if
195
client lives. %() expansions are done at
196
runtime with vars(self) as dict, so that for
197
instance %(name)s can be used in the command.
199
_timeout: Real variable for 'timeout'
200
_interval: Real variable for 'interval'
201
_timeout_milliseconds: Used when calling gobject.timeout_add()
202
_interval_milliseconds: - '' -
313
runtime_expansions = ("approval_delay", "approval_duration",
314
"created", "enabled", "fingerprint",
315
"host", "interval", "last_checked_ok",
316
"last_enabled", "name", "timeout")
318
def timeout_milliseconds(self):
319
"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)
326
def interval_milliseconds(self):
327
"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):
204
def _set_timeout(self, timeout):
205
"Setter function for the 'timeout' attribute"
206
self._timeout = timeout
207
self._timeout_milliseconds = ((self.timeout.days
208
* 24 * 60 * 60 * 1000)
209
+ (self.timeout.seconds * 1000)
210
+ (self.timeout.microseconds
213
self.TimeoutChanged(self._timeout_milliseconds)
214
timeout = property(lambda self: self._timeout, _set_timeout)
217
def _set_interval(self, interval):
218
"Setter function for the 'interval' attribute"
219
self._interval = interval
220
self._interval_milliseconds = ((self.interval.days
221
* 24 * 60 * 60 * 1000)
222
+ (self.interval.seconds
224
+ (self.interval.microseconds
227
self.IntervalChanged(self._interval_milliseconds)
228
interval = property(lambda self: self._interval, _set_interval)
231
def __init__(self, name = None, stop_hook=None, config=None):
334
232
"""Note: the 'checker' key in 'config' sets the
335
233
'checker_command' attribute and *not* the 'checker'
235
dbus.service.Object.__init__(self, bus,
237
% name.replace(".", "_"))
338
238
if config is None:
340
logger.debug("Creating client %r", self.name)
241
logger.debug(u"Creating client %r", self.name)
341
242
# Uppercase and remove spaces from fingerprint for later
342
243
# comparison purposes with return value from the fingerprint()
344
245
self.fingerprint = (config["fingerprint"].upper()
346
logger.debug(" Fingerprint: %s", self.fingerprint)
247
logger.debug(u" Fingerprint: %s", self.fingerprint)
347
248
if "secret" in config:
348
self.secret = config["secret"].decode("base64")
249
self.secret = config["secret"].decode(u"base64")
349
250
elif "secfile" in config:
350
with open(os.path.expanduser(os.path.expandvars
351
(config["secfile"])),
251
with closing(open(os.path.expanduser
253
(config["secfile"])))) as secfile:
353
254
self.secret = secfile.read()
355
raise TypeError("No secret or secfile for client %s"
256
raise TypeError(u"No secret or secfile for client %s"
357
258
self.host = config.get("host", "")
358
259
self.created = datetime.datetime.utcnow()
360
self.last_approval_request = None
361
self.last_enabled = None
362
261
self.last_checked_ok = None
363
262
self.timeout = string_to_delta(config["timeout"])
364
self.extended_timeout = string_to_delta(config["extended_timeout"])
365
263
self.interval = string_to_delta(config["interval"])
366
self.disable_hook = disable_hook
264
self.stop_hook = stop_hook
367
265
self.checker = None
368
266
self.checker_initiator_tag = None
369
self.disable_initiator_tag = None
267
self.stop_initiator_tag = None
371
268
self.checker_callback_tag = None
372
self.checker_command = config["checker"]
373
self.current_checker_command = None
374
self.last_connect = None
375
self._approved = None
376
self.approved_by_default = config.get("approved_by_default",
378
self.approvals_pending = 0
379
self.approval_delay = string_to_delta(
380
config["approval_delay"])
381
self.approval_duration = string_to_delta(
382
config["approval_duration"])
383
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
269
self.check_command = config["checker"]
385
def send_changedstate(self):
386
self.changedstate.acquire()
387
self.changedstate.notify_all()
388
self.changedstate.release()
391
272
"""Start this client's checker and timeout hooks"""
392
if getattr(self, "enabled", False):
395
self.send_changedstate()
273
self.started = datetime.datetime.utcnow()
396
274
# Schedule a new checker to be started an 'interval' from now,
397
275
# and every interval from then on.
398
276
self.checker_initiator_tag = (gobject.timeout_add
399
(self.interval_milliseconds(),
277
(self._interval_milliseconds,
400
278
self.start_checker))
401
# Schedule a disable() when 'timeout' has passed
402
self.expires = datetime.datetime.utcnow() + self.timeout
403
self.disable_initiator_tag = (gobject.timeout_add
404
(self.timeout_milliseconds(),
407
self.last_enabled = datetime.datetime.utcnow()
408
279
# Also start a new checker *right now*.
409
280
self.start_checker()
281
# Schedule a stop() when 'timeout' has passed
282
self.stop_initiator_tag = (gobject.timeout_add
283
(self._timeout_milliseconds,
286
self.StateChanged(True)
411
def disable(self, quiet=True):
412
"""Disable this client."""
413
if not getattr(self, "enabled", False):
289
"""Stop this client."""
290
if getattr(self, "started", None) is not None:
291
logger.info(u"Stopping client %s", self.name)
416
self.send_changedstate()
418
logger.info("Disabling client %s", self.name)
419
if getattr(self, "disable_initiator_tag", False):
420
gobject.source_remove(self.disable_initiator_tag)
421
self.disable_initiator_tag = None
294
if getattr(self, "stop_initiator_tag", False):
295
gobject.source_remove(self.stop_initiator_tag)
296
self.stop_initiator_tag = None
423
297
if getattr(self, "checker_initiator_tag", False):
424
298
gobject.source_remove(self.checker_initiator_tag)
425
299
self.checker_initiator_tag = None
426
300
self.stop_checker()
427
if self.disable_hook:
428
self.disable_hook(self)
305
self.StateChanged(False)
430
306
# Do not run this again if called by a gobject.timeout_add
433
309
def __del__(self):
434
self.disable_hook = None
310
self.stop_hook = None
437
def checker_callback(self, pid, condition, command):
313
def checker_callback(self, pid, condition):
438
314
"""The checker has completed, so take appropriate actions."""
439
315
self.checker_callback_tag = None
440
316
self.checker = None
441
if os.WIFEXITED(condition):
442
exitstatus = os.WEXITSTATUS(condition)
444
logger.info("Checker for %(name)s succeeded",
448
logger.info("Checker for %(name)s failed",
451
logger.warning("Checker for %(name)s crashed?",
317
if (os.WIFEXITED(condition)
318
and (os.WEXITSTATUS(condition) == 0)):
319
logger.info(u"Checker for %(name)s succeeded",
322
self.CheckerCompleted(True)
324
elif not os.WIFEXITED(condition):
325
logger.warning(u"Checker for %(name)s crashed?",
328
self.CheckerCompleted(False)
330
logger.info(u"Checker for %(name)s failed",
333
self.CheckerCompleted(False)
454
def checked_ok(self, timeout=None):
335
def bump_timeout(self):
455
336
"""Bump up the timeout for this client.
457
337
This should only be called when the client has been seen,
461
timeout = self.timeout
462
340
self.last_checked_ok = datetime.datetime.utcnow()
463
gobject.source_remove(self.disable_initiator_tag)
464
self.expires = datetime.datetime.utcnow() + timeout
465
self.disable_initiator_tag = (gobject.timeout_add
466
(_timedelta_to_milliseconds(timeout),
469
def need_approval(self):
470
self.last_approval_request = datetime.datetime.utcnow()
341
gobject.source_remove(self.stop_initiator_tag)
342
self.stop_initiator_tag = (gobject.timeout_add
343
(self._timeout_milliseconds,
472
346
def start_checker(self):
473
347
"""Start a new checker subprocess if one is not running.
475
348
If a checker already exists, leave it running and do
477
350
# The reason for not killing a running checker is that if we
551
398
self.checker_callback_tag = None
552
399
if getattr(self, "checker", None) is None:
554
logger.debug("Stopping checker for %(name)s", vars(self))
401
logger.debug(u"Stopping checker for %(name)s", vars(self))
556
403
os.kill(self.checker.pid, signal.SIGTERM)
558
405
#if self.checker.poll() is None:
559
406
# os.kill(self.checker.pid, signal.SIGKILL)
560
except OSError as error:
407
except OSError, error:
561
408
if error.errno != errno.ESRCH: # No such process
563
410
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"
412
def still_valid(self):
413
"""Has the timeout not yet passed for this client?"""
416
now = datetime.datetime.utcnow()
417
if self.last_checked_ok is None:
418
return now < (self.created + self.timeout)
420
return now < (self.last_checked_ok + self.timeout)
422
## D-Bus methods & signals
423
_interface = u"org.mandos_system.Mandos.Client"
425
def _datetime_to_dbus_struct(dt):
426
return dbus.Struct(dt.year, dt.month, dt.day, dt.hour,
427
dt.minute, dt.second, dt.microsecond,
430
# BumpTimeout - method
431
BumpTimeout = dbus.service.method(_interface)(bump_timeout)
432
BumpTimeout.__name__ = "BumpTimeout"
434
# IntervalChanged - signal
435
@dbus.service.signal(_interface, signature="t")
436
def IntervalChanged(self, t):
956
440
# CheckerCompleted - signal
957
@dbus.service.signal(_interface, signature="nxs")
958
def CheckerCompleted(self, exitcode, waitstatus, command):
441
@dbus.service.signal(_interface, signature="b")
442
def CheckerCompleted(self, success):
446
# CheckerIsRunning - method
447
@dbus.service.method(_interface, out_signature="b")
448
def CheckerIsRunning(self):
449
"D-Bus getter method"
450
return self.checker is not None
962
452
# CheckerStarted - signal
963
453
@dbus.service.signal(_interface, signature="s")
964
454
def CheckerStarted(self, command):
968
# PropertyChanged - signal
969
@dbus.service.signal(_interface, signature="sv")
970
def PropertyChanged(self, property, value):
975
@dbus.service.signal(_interface)
978
Is sent after a successful transfer of secret from the Mandos
979
server to mandos-client
984
@dbus.service.signal(_interface, signature="s")
985
def Rejected(self, reason):
989
# NeedApproval - signal
990
@dbus.service.signal(_interface, signature="tb")
991
def NeedApproval(self, timeout, default):
993
return self.need_approval()
998
@dbus.service.method(_interface, in_signature="b")
999
def Approve(self, value):
1002
# CheckedOK - method
1003
@dbus.service.method(_interface)
1004
def CheckedOK(self):
1008
@dbus.service.method(_interface)
458
# GetChecker - method
459
@dbus.service.method(_interface, out_signature="s")
460
def GetChecker(self):
461
"D-Bus getter method"
462
return self.checker_command
464
# GetCreated - method
465
@dbus.service.method(_interface, out_signature="(nyyyyyu)")
466
def GetCreated(self):
467
"D-Bus getter method"
468
return datetime_to_dbus_struct(self.created)
470
# GetFingerprint - method
471
@dbus.service.method(_interface, out_signature="s")
472
def GetFingerprint(self):
473
"D-Bus getter method"
474
return self.fingerprint
477
@dbus.service.method(_interface, out_signature="s")
479
"D-Bus getter method"
482
# GetInterval - method
483
@dbus.service.method(_interface, out_signature="t")
484
def GetInterval(self):
485
"D-Bus getter method"
486
return self._interval_milliseconds
489
@dbus.service.method(_interface, out_signature="s")
491
"D-Bus getter method"
494
# GetStarted - method
495
@dbus.service.method(_interface, out_signature="(nyyyyyu)")
496
def GetStarted(self):
497
"D-Bus getter method"
498
if self.started is not None:
499
return datetime_to_dbus_struct(self.started)
501
return dbus.Struct(0, 0, 0, 0, 0, 0, 0,
504
# GetTimeout - method
505
@dbus.service.method(_interface, out_signature="t")
506
def GetTimeout(self):
507
"D-Bus getter method"
508
return self._timeout_milliseconds
510
# SetChecker - method
511
@dbus.service.method(_interface, in_signature="s")
512
def SetChecker(self, checker):
513
"D-Bus setter method"
514
self.checker_command = checker
517
@dbus.service.method(_interface, in_signature="s")
518
def SetHost(self, host):
519
"D-Bus setter method"
522
# SetInterval - method
523
@dbus.service.method(_interface, in_signature="t")
524
def SetInterval(self, milliseconds):
525
self.interval = datetime.timdeelta(0, 0, 0, milliseconds)
527
# SetTimeout - method
528
@dbus.service.method(_interface, in_signature="t")
529
def SetTimeout(self, milliseconds):
530
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
533
@dbus.service.method(_interface, in_signature="ay",
535
def SetSecret(self, secret):
536
"D-Bus setter method"
537
self.secret = str(secret)
540
Start = dbus.service.method(_interface)(start)
541
Start.__name__ = "Start"
1013
543
# StartChecker - method
1014
@dbus.service.method(_interface)
1015
def StartChecker(self):
1017
self.start_checker()
1020
@dbus.service.method(_interface)
544
StartChecker = dbus.service.method(_interface)(start_checker)
545
StartChecker.__name__ = "StartChecker"
547
# StateChanged - signal
548
@dbus.service.signal(_interface, signature="b")
549
def StateChanged(self, started):
553
# StillValid - method
554
StillValid = (dbus.service.method(_interface, out_signature="b")
556
StillValid.__name__ = "StillValid"
559
Stop = dbus.service.method(_interface)(stop)
560
Stop.__name__ = "Stop"
1025
562
# 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)
563
StopChecker = dbus.service.method(_interface)(stop_checker)
564
StopChecker.__name__ = "StopChecker"
566
# TimeoutChanged - signal
567
@dbus.service.signal(_interface, signature="t")
568
def TimeoutChanged(self, t):
572
del _datetime_to_dbus_struct
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.
576
def peer_certificate(session):
577
"Return the peer's OpenPGP certificate as a bytestring"
578
# If not an OpenPGP certificate...
579
if (gnutls.library.functions
580
.gnutls_certificate_type_get(session._c_object)
581
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
582
# ...do the normal thing
583
return session.peer_certificate
584
list_size = ctypes.c_uint()
585
cert_list = (gnutls.library.functions
586
.gnutls_certificate_get_peers
587
(session._c_object, ctypes.byref(list_size)))
588
if list_size.value == 0:
591
return ctypes.string_at(cert.data, cert.size)
594
def fingerprint(openpgp):
595
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
596
# New GnuTLS "datum" with the OpenPGP public key
597
datum = (gnutls.library.types
598
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
601
ctypes.c_uint(len(openpgp))))
602
# New empty GnuTLS certificate
603
crt = gnutls.library.types.gnutls_openpgp_crt_t()
604
(gnutls.library.functions
605
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
606
# Import the OpenPGP public key into the certificate
607
(gnutls.library.functions
608
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
609
gnutls.library.constants
610
.GNUTLS_OPENPGP_FMT_RAW))
611
# Verify the self signature in the key
612
crtverify = ctypes.c_uint()
613
(gnutls.library.functions
614
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
615
if crtverify.value != 0:
616
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
617
raise gnutls.errors.CertificateSecurityError("Verify failed")
618
# New buffer for the fingerprint
619
buf = ctypes.create_string_buffer(20)
620
buf_len = ctypes.c_size_t()
621
# Get the fingerprint from the certificate into the buffer
622
(gnutls.library.functions
623
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
624
ctypes.byref(buf_len)))
625
# Deinit the certificate
626
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
627
# Convert the buffer to a Python bytestring
628
fpr = ctypes.string_at(buf, buf_len.value)
629
# Convert the bytestring to hexadecimal notation
630
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
634
class TCP_handler(SocketServer.BaseRequestHandler, object):
635
"""A TCP request handler class.
636
Instantiated by IPv6_TCPServer for each request to handle it.
1236
637
Note: This will run in its own forked process."""
1238
639
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
640
logger.info(u"TCP connection from: %s",
641
unicode(self.client_address))
642
session = (gnutls.connection
643
.ClientSession(self.request,
647
line = self.request.makefile().readline()
648
logger.debug(u"Protocol version: %r", line)
650
if int(line.strip().split()[0]) > 1:
652
except (ValueError, IndexError, RuntimeError), error:
653
logger.error(u"Unknown protocol version: %s", error)
656
# Note: gnutls.connection.X509Credentials is really a generic
657
# GnuTLS certificate credentials object so long as no X.509
658
# keys are added to it. Therefore, we can use it here despite
659
# using OpenPGP certificates.
661
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
662
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
664
# Use a fallback default, since this MUST be set.
665
priority = self.server.settings.get("priority", "NORMAL")
666
(gnutls.library.functions
667
.gnutls_priority_set_direct(session._c_object,
672
except gnutls.errors.GNUTLSError, error:
673
logger.warning(u"Handshake failed: %s", error)
674
# Do not run session.bye() here: the session is not
675
# established. Just abandon the request.
678
fpr = fingerprint(peer_certificate(session))
679
except (TypeError, gnutls.errors.GNUTLSError), error:
680
logger.warning(u"Bad certificate: %s", error)
683
logger.debug(u"Fingerprint: %s", fpr)
684
for c in self.server.clients:
685
if c.fingerprint == fpr:
689
logger.warning(u"Client not found for fingerprint: %s",
693
# Have to check if client.still_valid(), since it is possible
694
# that the client timed out while establishing the GnuTLS
696
if not client.still_valid():
697
logger.warning(u"Client %(name)s is invalid",
701
## This won't work here, since we're in a fork.
702
# client.bump_timeout()
704
while sent_size < len(client.secret):
705
sent = session.send(client.secret[sent_size:])
706
logger.debug(u"Sent: %d, remaining: %d",
707
sent, len(client.secret)
708
- (sent_size + sent))
713
class IPv6_TCPServer(SocketServer.ForkingMixIn,
714
SocketServer.TCPServer, object):
715
"""IPv6 TCP server. Accepts 'None' as address and/or port.
717
settings: Server settings
718
clients: Set() of Client objects
1489
719
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)
721
address_family = socket.AF_INET6
722
def __init__(self, *args, **kwargs):
723
if "settings" in kwargs:
724
self.settings = kwargs["settings"]
725
del kwargs["settings"]
726
if "clients" in kwargs:
727
self.clients = kwargs["clients"]
728
del kwargs["clients"]
730
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1500
731
def server_bind(self):
1501
732
"""This overrides the normal server_bind() function
1502
733
to bind to an interface if one was specified, and also NOT to
1503
734
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",
735
if self.settings["interface"]:
736
# 25 is from /usr/include/asm-i486/socket.h
737
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
739
self.socket.setsockopt(socket.SOL_SOCKET,
741
self.settings["interface"])
742
except socket.error, error:
743
if error[0] == errno.EPERM:
744
logger.error(u"No permission to"
745
u" bind to interface %s",
746
self.settings["interface"])
1526
749
# Only bind(2) the socket if we really need to.
1527
750
if self.server_address[0] or self.server_address[1]:
1528
751
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,
753
self.server_address = (in6addr_any,
1534
754
self.server_address[1])
1535
755
elif not self.server_address[1]:
1536
756
self.server_address = (self.server_address[0],
1538
# if self.interface:
758
# if self.settings["interface"]:
1539
759
# self.server_address = (self.server_address[0],
1542
762
# 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)
765
return super(IPv6_TCPServer, self).server_bind()
1570
766
def server_activate(self):
1571
767
if self.enabled:
1572
return socketserver.TCPServer.server_activate(self)
768
return super(IPv6_TCPServer, self).server_activate()
1573
769
def enable(self):
1574
770
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
773
def string_to_delta(interval):
1654
774
"""Parse a string and return a datetime.timedelta
1656
776
>>> string_to_delta('7d')
1657
777
datetime.timedelta(7)
1658
778
>>> string_to_delta('60s')