132
106
max_renames: integer; maximum number of renames
133
107
rename_count: integer; counter so we only rename after collisions
134
108
a sensible number of times
135
group: D-Bus Entry Group
137
bus: dbus.SystemBus()
139
110
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
140
servicetype = None, port = None, TXT = None,
141
domain = "", host = "", max_renames = 32768,
142
protocol = avahi.PROTO_UNSPEC, bus = None):
111
type = None, port = None, TXT = None, domain = "",
112
host = "", max_renames = 32768):
143
113
self.interface = interface
145
self.type = servicetype
147
self.TXT = TXT if TXT is not None else []
148
121
self.domain = domain
150
123
self.rename_count = 0
151
124
self.max_renames = max_renames
152
self.protocol = protocol
153
self.group = None # our entry group
156
self.entry_group_state_changed_match = None
157
125
def rename(self):
158
126
"""Derived from the Avahi example code"""
159
127
if self.rename_count >= self.max_renames:
160
logger.critical("No suitable Zeroconf service name found"
161
" after %i retries, exiting.",
128
logger.critical(u"No suitable Zeroconf service name found"
129
u" after %i retries, exiting.",
163
131
raise AvahiServiceError("Too many renames")
164
self.name = unicode(self.server
165
.GetAlternativeServiceName(self.name))
166
logger.info("Changing Zeroconf service name to %r ...",
132
self.name = server.GetAlternativeServiceName(self.name)
133
logger.info(u"Changing Zeroconf service name to %r ...",
135
syslogger.setFormatter(logging.Formatter\
136
('Mandos (%s): %%(levelname)s:'
137
' %%(message)s' % self.name))
171
except dbus.exceptions.DBusException as error:
172
logger.critical("DBusException: %s", error)
175
140
self.rename_count += 1
176
141
def remove(self):
177
142
"""Derived from the Avahi example code"""
178
if self.entry_group_state_changed_match is not None:
179
self.entry_group_state_changed_match.remove()
180
self.entry_group_state_changed_match = None
181
if self.group is not None:
143
if group is not None:
184
146
"""Derived from the Avahi example code"""
186
if self.group is None:
187
self.group = dbus.Interface(
188
self.bus.get_object(avahi.DBUS_NAME,
189
self.server.EntryGroupNew()),
190
avahi.DBUS_INTERFACE_ENTRY_GROUP)
191
self.entry_group_state_changed_match = (
192
self.group.connect_to_signal(
193
'StateChanged', self.entry_group_state_changed))
194
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
195
self.name, self.type)
196
self.group.AddService(
199
dbus.UInt32(0), # flags
200
self.name, self.type,
201
self.domain, self.host,
202
dbus.UInt16(self.port),
203
avahi.string_array_to_txt_array(self.TXT))
205
def entry_group_state_changed(self, state, error):
206
"""Derived from the Avahi example code"""
207
logger.debug("Avahi entry group state change: %i", state)
209
if state == avahi.ENTRY_GROUP_ESTABLISHED:
210
logger.debug("Zeroconf service established.")
211
elif state == avahi.ENTRY_GROUP_COLLISION:
212
logger.info("Zeroconf service name collision.")
214
elif state == avahi.ENTRY_GROUP_FAILURE:
215
logger.critical("Avahi: Error in group state changed %s",
217
raise AvahiGroupError("State changed: %s"
220
"""Derived from the Avahi example code"""
221
if self.group is not None:
224
except (dbus.exceptions.UnknownMethodException,
225
dbus.exceptions.DBusException) as e:
229
def server_state_changed(self, state, error=None):
230
"""Derived from the Avahi example code"""
231
logger.debug("Avahi server state change: %i", state)
232
bad_states = { avahi.SERVER_INVALID:
233
"Zeroconf server invalid",
234
avahi.SERVER_REGISTERING: None,
235
avahi.SERVER_COLLISION:
236
"Zeroconf server name collision",
237
avahi.SERVER_FAILURE:
238
"Zeroconf server failure" }
239
if state in bad_states:
240
if bad_states[state] is not None:
242
logger.error(bad_states[state])
244
logger.error(bad_states[state] + ": %r", error)
246
elif state == avahi.SERVER_RUNNING:
250
logger.debug("Unknown state: %r", state)
252
logger.debug("Unknown state: %r: %r", state, error)
254
"""Derived from the Avahi example code"""
255
if self.server is None:
256
self.server = dbus.Interface(
257
self.bus.get_object(avahi.DBUS_NAME,
258
avahi.DBUS_PATH_SERVER,
259
follow_name_owner_changes=True),
260
avahi.DBUS_INTERFACE_SERVER)
261
self.server.connect_to_signal("StateChanged",
262
self.server_state_changed)
263
self.server_state_changed(self.server.GetState())
265
class AvahiServiceToSyslog(AvahiService):
267
"""Add the new name to the syslog messages"""
268
ret = AvahiService.rename(self)
269
syslogger.setFormatter(logging.Formatter
270
('Mandos (%s) [%%(process)d]:'
271
' %%(levelname)s: %%(message)s'
275
def _timedelta_to_milliseconds(td):
276
"Convert a datetime.timedelta() to milliseconds"
277
return ((td.days * 24 * 60 * 60 * 1000)
278
+ (td.seconds * 1000)
279
+ (td.microseconds // 1000))
149
group = dbus.Interface\
150
(bus.get_object(avahi.DBUS_NAME,
151
server.EntryGroupNew()),
152
avahi.DBUS_INTERFACE_ENTRY_GROUP)
153
group.connect_to_signal('StateChanged',
154
entry_group_state_changed)
155
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
156
service.name, service.type)
158
self.interface, # interface
159
avahi.PROTO_INET6, # protocol
160
dbus.UInt32(0), # flags
161
self.name, self.type,
162
self.domain, self.host,
163
dbus.UInt16(self.port),
164
avahi.string_array_to_txt_array(self.TXT))
167
# From the Avahi example code:
168
group = None # our entry group
169
# End of Avahi example code
281
172
class Client(object):
282
173
"""A representation of a client host served by this server.
285
_approved: bool(); 'None' if not yet approved/disapproved
286
approval_delay: datetime.timedelta(); Time to wait for approval
287
approval_duration: datetime.timedelta(); Duration of one approval
288
checker: subprocess.Popen(); a running checker process used
289
to see if the client lives.
290
'None' if no process is running.
291
checker_callback_tag: a gobject event source tag, or None
292
checker_command: string; External command which is run to check
293
if client lives. %() expansions are done at
175
name: string; from the config file, used in log messages
176
fingerprint: string (40 or 32 hexadecimal digits); used to
177
uniquely identify the client
178
secret: bytestring; sent verbatim (over TLS) to client
179
host: string; available for use by the checker command
180
created: datetime.datetime(); object creation, not client host
181
last_checked_ok: datetime.datetime() or None if not yet checked OK
182
timeout: datetime.timedelta(); How long from last_checked_ok
183
until this client is invalid
184
interval: datetime.timedelta(); How often to start a new checker
185
stop_hook: If set, called by stop() as stop_hook(self)
186
checker: subprocess.Popen(); a running checker process used
187
to see if the client lives.
188
'None' if no process is running.
189
checker_initiator_tag: a gobject event source tag, or None
190
stop_initiator_tag: - '' -
191
checker_callback_tag: - '' -
192
checker_command: string; External command which is run to check if
193
client lives. %() expansions are done at
294
194
runtime with vars(self) as dict, so that for
295
195
instance %(name)s can be used in the command.
296
checker_initiator_tag: a gobject event source tag, or None
297
created: datetime.datetime(); (UTC) object creation
298
current_checker_command: string; current running checker_command
299
disable_hook: If set, called by disable() as disable_hook(self)
300
disable_initiator_tag: a gobject event source tag, or None
302
fingerprint: string (40 or 32 hexadecimal digits); used to
303
uniquely identify the client
304
host: string; available for use by the checker command
305
interval: datetime.timedelta(); How often to start a new checker
306
last_approval_request: datetime.datetime(); (UTC) or None
307
last_checked_ok: datetime.datetime(); (UTC) or None
308
last_enabled: datetime.datetime(); (UTC)
309
name: string; from the config file, used in log messages and
311
secret: bytestring; sent verbatim (over TLS) to client
312
timeout: datetime.timedelta(); How long from last_checked_ok
313
until this client is disabled
314
extended_timeout: extra long timeout when password has been sent
315
runtime_expansions: Allowed attributes for runtime expansion.
316
expires: datetime.datetime(); time (UTC) when a client will be
197
_timeout: Real variable for 'timeout'
198
_interval: Real variable for 'interval'
199
_timeout_milliseconds: Used when calling gobject.timeout_add()
200
_interval_milliseconds: - '' -
320
runtime_expansions = ("approval_delay", "approval_duration",
321
"created", "enabled", "fingerprint",
322
"host", "interval", "last_checked_ok",
323
"last_enabled", "name", "timeout")
325
def timeout_milliseconds(self):
326
"Return the 'timeout' attribute in milliseconds"
327
return _timedelta_to_milliseconds(self.timeout)
329
def extended_timeout_milliseconds(self):
330
"Return the 'extended_timeout' attribute in milliseconds"
331
return _timedelta_to_milliseconds(self.extended_timeout)
333
def interval_milliseconds(self):
334
"Return the 'interval' attribute in milliseconds"
335
return _timedelta_to_milliseconds(self.interval)
337
def approval_delay_milliseconds(self):
338
return _timedelta_to_milliseconds(self.approval_delay)
340
def __init__(self, name = None, disable_hook=None, config=None):
202
def _set_timeout(self, timeout):
203
"Setter function for 'timeout' attribute"
204
self._timeout = timeout
205
self._timeout_milliseconds = ((self.timeout.days
206
* 24 * 60 * 60 * 1000)
207
+ (self.timeout.seconds * 1000)
208
+ (self.timeout.microseconds
210
timeout = property(lambda self: self._timeout,
213
def _set_interval(self, interval):
214
"Setter function for 'interval' attribute"
215
self._interval = interval
216
self._interval_milliseconds = ((self.interval.days
217
* 24 * 60 * 60 * 1000)
218
+ (self.interval.seconds
220
+ (self.interval.microseconds
222
interval = property(lambda self: self._interval,
225
def __init__(self, name = None, stop_hook=None, config={}):
341
226
"""Note: the 'checker' key in 'config' sets the
342
227
'checker_command' attribute and *not* the 'checker'
347
logger.debug("Creating client %r", self.name)
230
logger.debug(u"Creating client %r", self.name)
348
231
# Uppercase and remove spaces from fingerprint for later
349
232
# comparison purposes with return value from the fingerprint()
351
self.fingerprint = (config["fingerprint"].upper()
353
logger.debug(" Fingerprint: %s", self.fingerprint)
234
self.fingerprint = config["fingerprint"].upper()\
236
logger.debug(u" Fingerprint: %s", self.fingerprint)
354
237
if "secret" in config:
355
self.secret = config["secret"].decode("base64")
238
self.secret = config["secret"].decode(u"base64")
356
239
elif "secfile" in config:
357
with open(os.path.expanduser(os.path.expandvars
358
(config["secfile"])),
360
self.secret = secfile.read()
240
sf = open(config["secfile"])
241
self.secret = sf.read()
362
raise TypeError("No secret or secfile for client %s"
244
raise TypeError(u"No secret or secfile for client %s"
364
246
self.host = config.get("host", "")
365
self.created = datetime.datetime.utcnow()
367
self.last_approval_request = None
368
self.last_enabled = None
247
self.created = datetime.datetime.now()
369
248
self.last_checked_ok = None
370
249
self.timeout = string_to_delta(config["timeout"])
371
self.extended_timeout = string_to_delta(config
372
["extended_timeout"])
373
250
self.interval = string_to_delta(config["interval"])
374
self.disable_hook = disable_hook
251
self.stop_hook = stop_hook
375
252
self.checker = None
376
253
self.checker_initiator_tag = None
377
self.disable_initiator_tag = None
254
self.stop_initiator_tag = None
379
255
self.checker_callback_tag = None
380
self.checker_command = config["checker"]
381
self.current_checker_command = None
382
self.last_connect = None
383
self._approved = None
384
self.approved_by_default = config.get("approved_by_default",
386
self.approvals_pending = 0
387
self.approval_delay = string_to_delta(
388
config["approval_delay"])
389
self.approval_duration = string_to_delta(
390
config["approval_duration"])
391
self.changedstate = (multiprocessing_manager
392
.Condition(multiprocessing_manager
395
def send_changedstate(self):
396
self.changedstate.acquire()
397
self.changedstate.notify_all()
398
self.changedstate.release()
256
self.check_command = config["checker"]
401
258
"""Start this client's checker and timeout hooks"""
402
if getattr(self, "enabled", False):
405
self.send_changedstate()
406
259
# Schedule a new checker to be started an 'interval' from now,
407
260
# and every interval from then on.
408
self.checker_initiator_tag = (gobject.timeout_add
409
(self.interval_milliseconds(),
411
# Schedule a disable() when 'timeout' has passed
412
self.expires = datetime.datetime.utcnow() + self.timeout
413
self.disable_initiator_tag = (gobject.timeout_add
414
(self.timeout_milliseconds(),
417
self.last_enabled = datetime.datetime.utcnow()
261
self.checker_initiator_tag = gobject.timeout_add\
262
(self._interval_milliseconds,
418
264
# Also start a new checker *right now*.
419
265
self.start_checker()
421
def disable(self, quiet=True):
422
"""Disable this client."""
423
if not getattr(self, "enabled", False):
266
# Schedule a stop() when 'timeout' has passed
267
self.stop_initiator_tag = gobject.timeout_add\
268
(self._timeout_milliseconds,
272
The possibility that a client might be restarted is left open,
273
but not currently used."""
274
# If this client doesn't have a secret, it is already stopped.
275
if hasattr(self, "secret") and self.secret:
276
logger.info(u"Stopping client %s", self.name)
426
self.send_changedstate()
428
logger.info("Disabling client %s", self.name)
429
if getattr(self, "disable_initiator_tag", False):
430
gobject.source_remove(self.disable_initiator_tag)
431
self.disable_initiator_tag = None
280
if getattr(self, "stop_initiator_tag", False):
281
gobject.source_remove(self.stop_initiator_tag)
282
self.stop_initiator_tag = None
433
283
if getattr(self, "checker_initiator_tag", False):
434
284
gobject.source_remove(self.checker_initiator_tag)
435
285
self.checker_initiator_tag = None
436
286
self.stop_checker()
437
if self.disable_hook:
438
self.disable_hook(self)
440
289
# Do not run this again if called by a gobject.timeout_add
443
291
def __del__(self):
444
self.disable_hook = None
447
def checker_callback(self, pid, condition, command):
292
self.stop_hook = None
294
def checker_callback(self, pid, condition):
448
295
"""The checker has completed, so take appropriate actions."""
296
now = datetime.datetime.now()
449
297
self.checker_callback_tag = None
450
298
self.checker = None
451
if os.WIFEXITED(condition):
452
exitstatus = os.WEXITSTATUS(condition)
454
logger.info("Checker for %(name)s succeeded",
458
logger.info("Checker for %(name)s failed",
461
logger.warning("Checker for %(name)s crashed?",
299
if os.WIFEXITED(condition) \
300
and (os.WEXITSTATUS(condition) == 0):
301
logger.info(u"Checker for %(name)s succeeded",
303
self.last_checked_ok = now
304
gobject.source_remove(self.stop_initiator_tag)
305
self.stop_initiator_tag = gobject.timeout_add\
306
(self._timeout_milliseconds,
308
elif not os.WIFEXITED(condition):
309
logger.warning(u"Checker for %(name)s crashed?",
464
def checked_ok(self, timeout=None):
465
"""Bump up the timeout for this client.
467
This should only be called when the client has been seen,
471
timeout = self.timeout
472
self.last_checked_ok = datetime.datetime.utcnow()
473
if self.disable_initiator_tag is not None:
474
gobject.source_remove(self.disable_initiator_tag)
475
if getattr(self, "enabled", False):
476
self.disable_initiator_tag = (gobject.timeout_add
477
(_timedelta_to_milliseconds
478
(timeout), self.disable))
479
self.expires = datetime.datetime.utcnow() + timeout
481
def need_approval(self):
482
self.last_approval_request = datetime.datetime.utcnow()
312
logger.info(u"Checker for %(name)s failed",
484
314
def start_checker(self):
485
315
"""Start a new checker subprocess if one is not running.
487
316
If a checker already exists, leave it running and do
489
318
# The reason for not killing a running checker is that if we
563
363
self.checker_callback_tag = None
564
364
if getattr(self, "checker", None) is None:
566
logger.debug("Stopping checker for %(name)s", vars(self))
366
logger.debug(u"Stopping checker for %(name)s", vars(self))
568
368
os.kill(self.checker.pid, signal.SIGTERM)
570
370
#if self.checker.poll() is None:
571
371
# os.kill(self.checker.pid, signal.SIGKILL)
572
except OSError as error:
372
except OSError, error:
573
373
if error.errno != errno.ESRCH: # No such process
575
375
self.checker = None
578
def dbus_service_property(dbus_interface, signature="v",
579
access="readwrite", byte_arrays=False):
580
"""Decorators for marking methods of a DBusObjectWithProperties to
581
become properties on the D-Bus.
583
The decorated method will be called with no arguments by "Get"
584
and with one argument by "Set".
586
The parameters, where they are supported, are the same as
587
dbus.service.method, except there is only "signature", since the
588
type from Get() and the type sent to Set() is the same.
590
# Encoding deeply encoded byte arrays is not supported yet by the
591
# "Set" method, so we fail early here:
592
if byte_arrays and signature != "ay":
593
raise ValueError("Byte arrays not supported for non-'ay'"
594
" signature %r" % signature)
596
func._dbus_is_property = True
597
func._dbus_interface = dbus_interface
598
func._dbus_signature = signature
599
func._dbus_access = access
600
func._dbus_name = func.__name__
601
if func._dbus_name.endswith("_dbus_property"):
602
func._dbus_name = func._dbus_name[:-14]
603
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
608
class DBusPropertyException(dbus.exceptions.DBusException):
609
"""A base class for D-Bus property-related exceptions
611
def __unicode__(self):
612
return unicode(str(self))
615
class DBusPropertyAccessException(DBusPropertyException):
616
"""A property's access permissions disallows an operation.
621
class DBusPropertyNotFound(DBusPropertyException):
622
"""An attempt was made to access a non-existing property.
627
class DBusObjectWithProperties(dbus.service.Object):
628
"""A D-Bus object with properties.
630
Classes inheriting from this can use the dbus_service_property
631
decorator to expose methods as D-Bus properties. It exposes the
632
standard Get(), Set(), and GetAll() methods on the D-Bus.
636
def _is_dbus_property(obj):
637
return getattr(obj, "_dbus_is_property", False)
639
def _get_all_dbus_properties(self):
640
"""Returns a generator of (name, attribute) pairs
642
return ((prop.__get__(self)._dbus_name, prop.__get__(self))
643
for cls in self.__class__.__mro__
645
inspect.getmembers(cls, self._is_dbus_property))
647
def _get_dbus_property(self, interface_name, property_name):
648
"""Returns a bound method if one exists which is a D-Bus
649
property with the specified name and interface.
651
for cls in self.__class__.__mro__:
652
for name, value in (inspect.getmembers
653
(cls, self._is_dbus_property)):
654
if (value._dbus_name == property_name
655
and value._dbus_interface == interface_name):
656
return value.__get__(self)
659
raise DBusPropertyNotFound(self.dbus_object_path + ":"
660
+ interface_name + "."
663
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
665
def Get(self, interface_name, property_name):
666
"""Standard D-Bus property Get() method, see D-Bus standard.
668
prop = self._get_dbus_property(interface_name, property_name)
669
if prop._dbus_access == "write":
670
raise DBusPropertyAccessException(property_name)
672
if not hasattr(value, "variant_level"):
674
return type(value)(value, variant_level=value.variant_level+1)
676
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
677
def Set(self, interface_name, property_name, value):
678
"""Standard D-Bus property Set() method, see D-Bus standard.
680
prop = self._get_dbus_property(interface_name, property_name)
681
if prop._dbus_access == "read":
682
raise DBusPropertyAccessException(property_name)
683
if prop._dbus_get_args_options["byte_arrays"]:
684
# The byte_arrays option is not supported yet on
685
# signatures other than "ay".
686
if prop._dbus_signature != "ay":
688
value = dbus.ByteArray(''.join(unichr(byte)
692
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
693
out_signature="a{sv}")
694
def GetAll(self, interface_name):
695
"""Standard D-Bus property GetAll() method, see D-Bus
698
Note: Will not include properties with access="write".
701
for name, prop in self._get_all_dbus_properties():
703
and interface_name != prop._dbus_interface):
704
# Interface non-empty but did not match
706
# Ignore write-only properties
707
if prop._dbus_access == "write":
710
if not hasattr(value, "variant_level"):
713
all[name] = type(value)(value, variant_level=
714
value.variant_level+1)
715
return dbus.Dictionary(all, signature="sv")
717
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
719
path_keyword='object_path',
720
connection_keyword='connection')
721
def Introspect(self, object_path, connection):
722
"""Standard D-Bus method, overloaded to insert property tags.
724
xmlstring = dbus.service.Object.Introspect(self, object_path,
727
document = xml.dom.minidom.parseString(xmlstring)
728
def make_tag(document, name, prop):
729
e = document.createElement("property")
730
e.setAttribute("name", name)
731
e.setAttribute("type", prop._dbus_signature)
732
e.setAttribute("access", prop._dbus_access)
734
for if_tag in document.getElementsByTagName("interface"):
735
for tag in (make_tag(document, name, prop)
737
in self._get_all_dbus_properties()
738
if prop._dbus_interface
739
== if_tag.getAttribute("name")):
740
if_tag.appendChild(tag)
741
# Add the names to the return values for the
742
# "org.freedesktop.DBus.Properties" methods
743
if (if_tag.getAttribute("name")
744
== "org.freedesktop.DBus.Properties"):
745
for cn in if_tag.getElementsByTagName("method"):
746
if cn.getAttribute("name") == "Get":
747
for arg in cn.getElementsByTagName("arg"):
748
if (arg.getAttribute("direction")
750
arg.setAttribute("name", "value")
751
elif cn.getAttribute("name") == "GetAll":
752
for arg in cn.getElementsByTagName("arg"):
753
if (arg.getAttribute("direction")
755
arg.setAttribute("name", "props")
756
xmlstring = document.toxml("utf-8")
758
except (AttributeError, xml.dom.DOMException,
759
xml.parsers.expat.ExpatError) as error:
760
logger.error("Failed to override Introspection method",
765
def datetime_to_dbus (dt, variant_level=0):
766
"""Convert a UTC datetime.datetime() to a D-Bus type."""
768
return dbus.String("", variant_level = variant_level)
769
return dbus.String(dt.isoformat(),
770
variant_level=variant_level)
772
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
774
"""Applied to an empty subclass of a D-Bus object, this metaclass
775
will add additional D-Bus attributes matching a certain pattern.
777
def __new__(mcs, name, bases, attr):
778
# Go through all the base classes which could have D-Bus
779
# methods, signals, or properties in them
780
for base in (b for b in bases
781
if issubclass(b, dbus.service.Object)):
782
# Go though all attributes of the base class
783
for attrname, attribute in inspect.getmembers(base):
784
# Ignore non-D-Bus attributes, and D-Bus attributes
785
# with the wrong interface name
786
if (not hasattr(attribute, "_dbus_interface")
787
or not attribute._dbus_interface
788
.startswith("se.recompile.Mandos")):
790
# Create an alternate D-Bus interface name based on
792
alt_interface = (attribute._dbus_interface
793
.replace("se.recompile.Mandos",
794
"se.bsnet.fukt.Mandos"))
795
# Is this a D-Bus signal?
796
if getattr(attribute, "_dbus_is_signal", False):
797
# Extract the original non-method function by
799
nonmethod_func = (dict(
800
zip(attribute.func_code.co_freevars,
801
attribute.__closure__))["func"]
803
# Create a new, but exactly alike, function
804
# object, and decorate it to be a new D-Bus signal
805
# with the alternate D-Bus interface name
806
new_function = (dbus.service.signal
808
attribute._dbus_signature)
810
nonmethod_func.func_code,
811
nonmethod_func.func_globals,
812
nonmethod_func.func_name,
813
nonmethod_func.func_defaults,
814
nonmethod_func.func_closure)))
815
# Define a creator of a function to call both the
816
# old and new functions, so both the old and new
817
# signals gets sent when the function is called
818
def fixscope(func1, func2):
819
"""This function is a scope container to pass
820
func1 and func2 to the "call_both" function
821
outside of its arguments"""
822
def call_both(*args, **kwargs):
823
"""This function will emit two D-Bus
824
signals by calling func1 and func2"""
825
func1(*args, **kwargs)
826
func2(*args, **kwargs)
828
# Create the "call_both" function and add it to
830
attr[attrname] = fixscope(attribute,
832
# Is this a D-Bus method?
833
elif getattr(attribute, "_dbus_is_method", False):
834
# Create a new, but exactly alike, function
835
# object. Decorate it to be a new D-Bus method
836
# with the alternate D-Bus interface name. Add it
838
attr[attrname] = (dbus.service.method
840
attribute._dbus_in_signature,
841
attribute._dbus_out_signature)
843
(attribute.func_code,
844
attribute.func_globals,
846
attribute.func_defaults,
847
attribute.func_closure)))
848
# Is this a D-Bus property?
849
elif getattr(attribute, "_dbus_is_property", False):
850
# Create a new, but exactly alike, function
851
# object, and decorate it to be a new D-Bus
852
# property with the alternate D-Bus interface
853
# name. Add it to the class.
854
attr[attrname] = (dbus_service_property
856
attribute._dbus_signature,
857
attribute._dbus_access,
859
._dbus_get_args_options
862
(attribute.func_code,
863
attribute.func_globals,
865
attribute.func_defaults,
866
attribute.func_closure)))
867
return type.__new__(mcs, name, bases, attr)
869
class ClientDBus(Client, DBusObjectWithProperties):
870
"""A Client class using D-Bus
873
dbus_object_path: dbus.ObjectPath
874
bus: dbus.SystemBus()
877
runtime_expansions = (Client.runtime_expansions
878
+ ("dbus_object_path",))
880
# dbus.service.Object doesn't use super(), so we can't either.
882
def __init__(self, bus = None, *args, **kwargs):
883
self._approvals_pending = 0
885
Client.__init__(self, *args, **kwargs)
886
# Only now, when this client is initialized, can it show up on
888
client_object_name = unicode(self.name).translate(
891
self.dbus_object_path = (dbus.ObjectPath
892
("/clients/" + client_object_name))
893
DBusObjectWithProperties.__init__(self, self.bus,
894
self.dbus_object_path)
896
def notifychangeproperty(transform_func,
897
dbus_name, type_func=lambda x: x,
899
""" Modify a variable so that it's a property which announces
902
transform_fun: Function that takes a value and a variant_level
903
and transforms it to a D-Bus type.
904
dbus_name: D-Bus name of the variable
905
type_func: Function that transform the value before sending it
906
to the D-Bus. Default: no transform
907
variant_level: D-Bus variant level. Default: 1
909
attrname = "_{0}".format(dbus_name)
910
def setter(self, value):
911
if hasattr(self, "dbus_object_path"):
912
if (not hasattr(self, attrname) or
913
type_func(getattr(self, attrname, None))
914
!= type_func(value)):
915
dbus_value = transform_func(type_func(value),
918
self.PropertyChanged(dbus.String(dbus_name),
920
setattr(self, attrname, value)
922
return property(lambda self: getattr(self, attrname), setter)
925
expires = notifychangeproperty(datetime_to_dbus, "Expires")
926
approvals_pending = notifychangeproperty(dbus.Boolean,
929
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
930
last_enabled = notifychangeproperty(datetime_to_dbus,
932
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
933
type_func = lambda checker:
935
last_checked_ok = notifychangeproperty(datetime_to_dbus,
937
last_approval_request = notifychangeproperty(
938
datetime_to_dbus, "LastApprovalRequest")
939
approved_by_default = notifychangeproperty(dbus.Boolean,
941
approval_delay = notifychangeproperty(dbus.UInt16,
944
_timedelta_to_milliseconds)
945
approval_duration = notifychangeproperty(
946
dbus.UInt16, "ApprovalDuration",
947
type_func = _timedelta_to_milliseconds)
948
host = notifychangeproperty(dbus.String, "Host")
949
timeout = notifychangeproperty(dbus.UInt16, "Timeout",
951
_timedelta_to_milliseconds)
952
extended_timeout = notifychangeproperty(
953
dbus.UInt16, "ExtendedTimeout",
954
type_func = _timedelta_to_milliseconds)
955
interval = notifychangeproperty(dbus.UInt16,
958
_timedelta_to_milliseconds)
959
checker_command = notifychangeproperty(dbus.String, "Checker")
961
del notifychangeproperty
963
def __del__(self, *args, **kwargs):
965
self.remove_from_connection()
968
if hasattr(DBusObjectWithProperties, "__del__"):
969
DBusObjectWithProperties.__del__(self, *args, **kwargs)
970
Client.__del__(self, *args, **kwargs)
972
def checker_callback(self, pid, condition, command,
974
self.checker_callback_tag = None
976
if os.WIFEXITED(condition):
977
exitstatus = os.WEXITSTATUS(condition)
979
self.CheckerCompleted(dbus.Int16(exitstatus),
980
dbus.Int64(condition),
981
dbus.String(command))
984
self.CheckerCompleted(dbus.Int16(-1),
985
dbus.Int64(condition),
986
dbus.String(command))
988
return Client.checker_callback(self, pid, condition, command,
991
def start_checker(self, *args, **kwargs):
992
old_checker = self.checker
993
if self.checker is not None:
994
old_checker_pid = self.checker.pid
996
old_checker_pid = None
997
r = Client.start_checker(self, *args, **kwargs)
998
# Only if new checker process was started
999
if (self.checker is not None
1000
and old_checker_pid != self.checker.pid):
1002
self.CheckerStarted(self.current_checker_command)
1005
def _reset_approved(self):
1006
self._approved = None
1009
def approve(self, value=True):
1010
self.send_changedstate()
1011
self._approved = value
1012
gobject.timeout_add(_timedelta_to_milliseconds
1013
(self.approval_duration),
1014
self._reset_approved)
1017
## D-Bus methods, signals & properties
1018
_interface = "se.recompile.Mandos.Client"
1022
# CheckerCompleted - signal
1023
@dbus.service.signal(_interface, signature="nxs")
1024
def CheckerCompleted(self, exitcode, waitstatus, command):
1028
# CheckerStarted - signal
1029
@dbus.service.signal(_interface, signature="s")
1030
def CheckerStarted(self, command):
1034
# PropertyChanged - signal
1035
@dbus.service.signal(_interface, signature="sv")
1036
def PropertyChanged(self, property, value):
1040
# GotSecret - signal
1041
@dbus.service.signal(_interface)
1042
def GotSecret(self):
1044
Is sent after a successful transfer of secret from the Mandos
1045
server to mandos-client
1050
@dbus.service.signal(_interface, signature="s")
1051
def Rejected(self, reason):
1055
# NeedApproval - signal
1056
@dbus.service.signal(_interface, signature="tb")
1057
def NeedApproval(self, timeout, default):
1059
return self.need_approval()
1064
@dbus.service.method(_interface, in_signature="b")
1065
def Approve(self, value):
1068
# CheckedOK - method
1069
@dbus.service.method(_interface)
1070
def CheckedOK(self):
1074
@dbus.service.method(_interface)
1079
# StartChecker - method
1080
@dbus.service.method(_interface)
1081
def StartChecker(self):
1083
self.start_checker()
1086
@dbus.service.method(_interface)
1091
# StopChecker - method
1092
@dbus.service.method(_interface)
1093
def StopChecker(self):
1098
# ApprovalPending - property
1099
@dbus_service_property(_interface, signature="b", access="read")
1100
def ApprovalPending_dbus_property(self):
1101
return dbus.Boolean(bool(self.approvals_pending))
1103
# ApprovedByDefault - property
1104
@dbus_service_property(_interface, signature="b",
1106
def ApprovedByDefault_dbus_property(self, value=None):
1107
if value is None: # get
1108
return dbus.Boolean(self.approved_by_default)
1109
self.approved_by_default = bool(value)
1111
# ApprovalDelay - property
1112
@dbus_service_property(_interface, signature="t",
1114
def ApprovalDelay_dbus_property(self, value=None):
1115
if value is None: # get
1116
return dbus.UInt64(self.approval_delay_milliseconds())
1117
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1119
# ApprovalDuration - property
1120
@dbus_service_property(_interface, signature="t",
1122
def ApprovalDuration_dbus_property(self, value=None):
1123
if value is None: # get
1124
return dbus.UInt64(_timedelta_to_milliseconds(
1125
self.approval_duration))
1126
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1129
@dbus_service_property(_interface, signature="s", access="read")
1130
def Name_dbus_property(self):
1131
return dbus.String(self.name)
1133
# Fingerprint - property
1134
@dbus_service_property(_interface, signature="s", access="read")
1135
def Fingerprint_dbus_property(self):
1136
return dbus.String(self.fingerprint)
1139
@dbus_service_property(_interface, signature="s",
1141
def Host_dbus_property(self, value=None):
1142
if value is None: # get
1143
return dbus.String(self.host)
1146
# Created - property
1147
@dbus_service_property(_interface, signature="s", access="read")
1148
def Created_dbus_property(self):
1149
return dbus.String(datetime_to_dbus(self.created))
1151
# LastEnabled - property
1152
@dbus_service_property(_interface, signature="s", access="read")
1153
def LastEnabled_dbus_property(self):
1154
return datetime_to_dbus(self.last_enabled)
1156
# Enabled - property
1157
@dbus_service_property(_interface, signature="b",
1159
def Enabled_dbus_property(self, value=None):
1160
if value is None: # get
1161
return dbus.Boolean(self.enabled)
1167
# LastCheckedOK - property
1168
@dbus_service_property(_interface, signature="s",
1170
def LastCheckedOK_dbus_property(self, value=None):
1171
if value is not None:
1174
return datetime_to_dbus(self.last_checked_ok)
1176
# Expires - property
1177
@dbus_service_property(_interface, signature="s", access="read")
1178
def Expires_dbus_property(self):
1179
return datetime_to_dbus(self.expires)
1181
# LastApprovalRequest - property
1182
@dbus_service_property(_interface, signature="s", access="read")
1183
def LastApprovalRequest_dbus_property(self):
1184
return datetime_to_dbus(self.last_approval_request)
1186
# Timeout - property
1187
@dbus_service_property(_interface, signature="t",
1189
def Timeout_dbus_property(self, value=None):
1190
if value is None: # get
1191
return dbus.UInt64(self.timeout_milliseconds())
1192
self.timeout = datetime.timedelta(0, 0, 0, value)
1193
if getattr(self, "disable_initiator_tag", None) is None:
1195
# Reschedule timeout
1196
gobject.source_remove(self.disable_initiator_tag)
1197
self.disable_initiator_tag = None
1199
time_to_die = _timedelta_to_milliseconds((self
1204
if time_to_die <= 0:
1205
# The timeout has passed
1208
self.expires = (datetime.datetime.utcnow()
1209
+ datetime.timedelta(milliseconds =
1211
self.disable_initiator_tag = (gobject.timeout_add
1212
(time_to_die, self.disable))
1214
# ExtendedTimeout - property
1215
@dbus_service_property(_interface, signature="t",
1217
def ExtendedTimeout_dbus_property(self, value=None):
1218
if value is None: # get
1219
return dbus.UInt64(self.extended_timeout_milliseconds())
1220
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1222
# Interval - property
1223
@dbus_service_property(_interface, signature="t",
1225
def Interval_dbus_property(self, value=None):
1226
if value is None: # get
1227
return dbus.UInt64(self.interval_milliseconds())
1228
self.interval = datetime.timedelta(0, 0, 0, value)
1229
if getattr(self, "checker_initiator_tag", None) is None:
1231
# Reschedule checker run
1232
gobject.source_remove(self.checker_initiator_tag)
1233
self.checker_initiator_tag = (gobject.timeout_add
1234
(value, self.start_checker))
1235
self.start_checker() # Start one now, too
1237
# Checker - property
1238
@dbus_service_property(_interface, signature="s",
1240
def Checker_dbus_property(self, value=None):
1241
if value is None: # get
1242
return dbus.String(self.checker_command)
1243
self.checker_command = value
1245
# CheckerRunning - property
1246
@dbus_service_property(_interface, signature="b",
1248
def CheckerRunning_dbus_property(self, value=None):
1249
if value is None: # get
1250
return dbus.Boolean(self.checker is not None)
1252
self.start_checker()
1256
# ObjectPath - property
1257
@dbus_service_property(_interface, signature="o", access="read")
1258
def ObjectPath_dbus_property(self):
1259
return self.dbus_object_path # is already a dbus.ObjectPath
1262
@dbus_service_property(_interface, signature="ay",
1263
access="write", byte_arrays=True)
1264
def Secret_dbus_property(self, value):
1265
self.secret = str(value)
1270
class ProxyClient(object):
1271
def __init__(self, child_pipe, fpr, address):
1272
self._pipe = child_pipe
1273
self._pipe.send(('init', fpr, address))
1274
if not self._pipe.recv():
1277
def __getattribute__(self, name):
1278
if(name == '_pipe'):
1279
return super(ProxyClient, self).__getattribute__(name)
1280
self._pipe.send(('getattr', name))
1281
data = self._pipe.recv()
1282
if data[0] == 'data':
1284
if data[0] == 'function':
1285
def func(*args, **kwargs):
1286
self._pipe.send(('funcall', name, args, kwargs))
1287
return self._pipe.recv()[1]
1290
def __setattr__(self, name, value):
1291
if(name == '_pipe'):
1292
return super(ProxyClient, self).__setattr__(name, value)
1293
self._pipe.send(('setattr', name, value))
1295
class ClientDBusTransitional(ClientDBus):
1296
__metaclass__ = AlternateDBusNamesMetaclass
1298
class ClientHandler(socketserver.BaseRequestHandler, object):
1299
"""A class to handle client connections.
1301
Instantiated once for each connection to handle it.
376
def still_valid(self):
377
"""Has the timeout not yet passed for this client?"""
378
now = datetime.datetime.now()
379
if self.last_checked_ok is None:
380
return now < (self.created + self.timeout)
382
return now < (self.last_checked_ok + self.timeout)
385
def peer_certificate(session):
386
"Return the peer's OpenPGP certificate as a bytestring"
387
# If not an OpenPGP certificate...
388
if gnutls.library.functions.gnutls_certificate_type_get\
389
(session._c_object) \
390
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
391
# ...do the normal thing
392
return session.peer_certificate
393
list_size = ctypes.c_uint()
394
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
395
(session._c_object, ctypes.byref(list_size))
396
if list_size.value == 0:
399
return ctypes.string_at(cert.data, cert.size)
402
def fingerprint(openpgp):
403
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
404
# New GnuTLS "datum" with the OpenPGP public key
405
datum = gnutls.library.types.gnutls_datum_t\
406
(ctypes.cast(ctypes.c_char_p(openpgp),
407
ctypes.POINTER(ctypes.c_ubyte)),
408
ctypes.c_uint(len(openpgp)))
409
# New empty GnuTLS certificate
410
crt = gnutls.library.types.gnutls_openpgp_crt_t()
411
gnutls.library.functions.gnutls_openpgp_crt_init\
413
# Import the OpenPGP public key into the certificate
414
gnutls.library.functions.gnutls_openpgp_crt_import\
415
(crt, ctypes.byref(datum),
416
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
417
# Verify the self signature in the key
418
crtverify = ctypes.c_uint();
419
gnutls.library.functions.gnutls_openpgp_crt_verify_self\
420
(crt, 0, ctypes.byref(crtverify))
421
if crtverify.value != 0:
422
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
423
raise gnutls.errors.CertificateSecurityError("Verify failed")
424
# New buffer for the fingerprint
425
buffer = ctypes.create_string_buffer(20)
426
buffer_length = ctypes.c_size_t()
427
# Get the fingerprint from the certificate into the buffer
428
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
429
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
430
# Deinit the certificate
431
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
432
# Convert the buffer to a Python bytestring
433
fpr = ctypes.string_at(buffer, buffer_length.value)
434
# Convert the bytestring to hexadecimal notation
435
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
439
class tcp_handler(SocketServer.BaseRequestHandler, object):
440
"""A TCP request handler class.
441
Instantiated by IPv6_TCPServer for each request to handle it.
1302
442
Note: This will run in its own forked process."""
1304
444
def handle(self):
1305
with contextlib.closing(self.server.child_pipe) as child_pipe:
1306
logger.info("TCP connection from: %s",
1307
unicode(self.client_address))
1308
logger.debug("Pipe FD: %d",
1309
self.server.child_pipe.fileno())
1311
session = (gnutls.connection
1312
.ClientSession(self.request,
1314
.X509Credentials()))
1316
# Note: gnutls.connection.X509Credentials is really a
1317
# generic GnuTLS certificate credentials object so long as
1318
# no X.509 keys are added to it. Therefore, we can use it
1319
# here despite using OpenPGP certificates.
1321
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1322
# "+AES-256-CBC", "+SHA1",
1323
# "+COMP-NULL", "+CTYPE-OPENPGP",
1325
# Use a fallback default, since this MUST be set.
1326
priority = self.server.gnutls_priority
1327
if priority is None:
1329
(gnutls.library.functions
1330
.gnutls_priority_set_direct(session._c_object,
1333
# Start communication using the Mandos protocol
1334
# Get protocol number
1335
line = self.request.makefile().readline()
1336
logger.debug("Protocol version: %r", line)
1338
if int(line.strip().split()[0]) > 1:
1340
except (ValueError, IndexError, RuntimeError) as error:
1341
logger.error("Unknown protocol version: %s", error)
1344
# Start GnuTLS connection
1347
except gnutls.errors.GNUTLSError as error:
1348
logger.warning("Handshake failed: %s", error)
1349
# Do not run session.bye() here: the session is not
1350
# established. Just abandon the request.
1352
logger.debug("Handshake succeeded")
1354
approval_required = False
1357
fpr = self.fingerprint(self.peer_certificate
1360
gnutls.errors.GNUTLSError) as error:
1361
logger.warning("Bad certificate: %s", error)
1363
logger.debug("Fingerprint: %s", fpr)
1366
client = ProxyClient(child_pipe, fpr,
1367
self.client_address)
1371
if client.approval_delay:
1372
delay = client.approval_delay
1373
client.approvals_pending += 1
1374
approval_required = True
1377
if not client.enabled:
1378
logger.info("Client %s is disabled",
1380
if self.server.use_dbus:
1382
client.Rejected("Disabled")
1385
if client._approved or not client.approval_delay:
1386
#We are approved or approval is disabled
1388
elif client._approved is None:
1389
logger.info("Client %s needs approval",
1391
if self.server.use_dbus:
1393
client.NeedApproval(
1394
client.approval_delay_milliseconds(),
1395
client.approved_by_default)
1397
logger.warning("Client %s was not approved",
1399
if self.server.use_dbus:
1401
client.Rejected("Denied")
1404
#wait until timeout or approved
1405
time = datetime.datetime.now()
1406
client.changedstate.acquire()
1407
(client.changedstate.wait
1408
(float(client._timedelta_to_milliseconds(delay)
1410
client.changedstate.release()
1411
time2 = datetime.datetime.now()
1412
if (time2 - time) >= delay:
1413
if not client.approved_by_default:
1414
logger.warning("Client %s timed out while"
1415
" waiting for approval",
1417
if self.server.use_dbus:
1419
client.Rejected("Approval timed out")
1424
delay -= time2 - time
1427
while sent_size < len(client.secret):
1429
sent = session.send(client.secret[sent_size:])
1430
except gnutls.errors.GNUTLSError as error:
1431
logger.warning("gnutls send failed")
1433
logger.debug("Sent: %d, remaining: %d",
1434
sent, len(client.secret)
1435
- (sent_size + sent))
1438
logger.info("Sending secret to %s", client.name)
1439
# bump the timeout using extended_timeout
1440
client.checked_ok(client.extended_timeout)
1441
if self.server.use_dbus:
1446
if approval_required:
1447
client.approvals_pending -= 1
1450
except gnutls.errors.GNUTLSError as error:
1451
logger.warning("GnuTLS bye failed")
1454
def peer_certificate(session):
1455
"Return the peer's OpenPGP certificate as a bytestring"
1456
# If not an OpenPGP certificate...
1457
if (gnutls.library.functions
1458
.gnutls_certificate_type_get(session._c_object)
1459
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1460
# ...do the normal thing
1461
return session.peer_certificate
1462
list_size = ctypes.c_uint(1)
1463
cert_list = (gnutls.library.functions
1464
.gnutls_certificate_get_peers
1465
(session._c_object, ctypes.byref(list_size)))
1466
if not bool(cert_list) and list_size.value != 0:
1467
raise gnutls.errors.GNUTLSError("error getting peer"
1469
if list_size.value == 0:
1472
return ctypes.string_at(cert.data, cert.size)
1475
def fingerprint(openpgp):
1476
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1477
# New GnuTLS "datum" with the OpenPGP public key
1478
datum = (gnutls.library.types
1479
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1482
ctypes.c_uint(len(openpgp))))
1483
# New empty GnuTLS certificate
1484
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1485
(gnutls.library.functions
1486
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1487
# Import the OpenPGP public key into the certificate
1488
(gnutls.library.functions
1489
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1490
gnutls.library.constants
1491
.GNUTLS_OPENPGP_FMT_RAW))
1492
# Verify the self signature in the key
1493
crtverify = ctypes.c_uint()
1494
(gnutls.library.functions
1495
.gnutls_openpgp_crt_verify_self(crt, 0,
1496
ctypes.byref(crtverify)))
1497
if crtverify.value != 0:
1498
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1499
raise (gnutls.errors.CertificateSecurityError
1501
# New buffer for the fingerprint
1502
buf = ctypes.create_string_buffer(20)
1503
buf_len = ctypes.c_size_t()
1504
# Get the fingerprint from the certificate into the buffer
1505
(gnutls.library.functions
1506
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1507
ctypes.byref(buf_len)))
1508
# Deinit the certificate
1509
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1510
# Convert the buffer to a Python bytestring
1511
fpr = ctypes.string_at(buf, buf_len.value)
1512
# Convert the bytestring to hexadecimal notation
1513
hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1517
class MultiprocessingMixIn(object):
1518
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1519
def sub_process_main(self, request, address):
1521
self.finish_request(request, address)
1523
self.handle_error(request, address)
1524
self.close_request(request)
1526
def process_request(self, request, address):
1527
"""Start a new process to process the request."""
1528
proc = multiprocessing.Process(target = self.sub_process_main,
1535
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1536
""" adds a pipe to the MixIn """
1537
def process_request(self, request, client_address):
1538
"""Overrides and wraps the original process_request().
1540
This function creates a new pipe in self.pipe
1542
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1544
proc = MultiprocessingMixIn.process_request(self, request,
1546
self.child_pipe.close()
1547
self.add_pipe(parent_pipe, proc)
1549
def add_pipe(self, parent_pipe, proc):
1550
"""Dummy function; override as necessary"""
1551
raise NotImplementedError
1554
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1555
socketserver.TCPServer, object):
1556
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
445
logger.info(u"TCP connection from: %s",
446
unicode(self.client_address))
447
session = gnutls.connection.ClientSession\
448
(self.request, gnutls.connection.X509Credentials())
450
line = self.request.makefile().readline()
451
logger.debug(u"Protocol version: %r", line)
453
if int(line.strip().split()[0]) > 1:
455
except (ValueError, IndexError, RuntimeError), error:
456
logger.error(u"Unknown protocol version: %s", error)
459
# Note: gnutls.connection.X509Credentials is really a generic
460
# GnuTLS certificate credentials object so long as no X.509
461
# keys are added to it. Therefore, we can use it here despite
462
# using OpenPGP certificates.
464
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
465
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
467
priority = "NORMAL" # Fallback default, since this
469
if self.server.settings["priority"]:
470
priority = self.server.settings["priority"]
471
gnutls.library.functions.gnutls_priority_set_direct\
472
(session._c_object, priority, None);
476
except gnutls.errors.GNUTLSError, error:
477
logger.warning(u"Handshake failed: %s", error)
478
# Do not run session.bye() here: the session is not
479
# established. Just abandon the request.
482
fpr = fingerprint(peer_certificate(session))
483
except (TypeError, gnutls.errors.GNUTLSError), error:
484
logger.warning(u"Bad certificate: %s", error)
487
logger.debug(u"Fingerprint: %s", fpr)
489
for c in self.server.clients:
490
if c.fingerprint == fpr:
494
logger.warning(u"Client not found for fingerprint: %s",
498
# Have to check if client.still_valid(), since it is possible
499
# that the client timed out while establishing the GnuTLS
501
if not client.still_valid():
502
logger.warning(u"Client %(name)s is invalid",
507
while sent_size < len(client.secret):
508
sent = session.send(client.secret[sent_size:])
509
logger.debug(u"Sent: %d, remaining: %d",
510
sent, len(client.secret)
511
- (sent_size + sent))
516
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
517
"""IPv6 TCP server. Accepts 'None' as address and/or port.
519
settings: Server settings
520
clients: Set() of Client objects
1559
521
enabled: Boolean; whether this server is activated yet
1560
interface: None or a network interface name (string)
1561
use_ipv6: Boolean; to use IPv6 or not
1563
def __init__(self, server_address, RequestHandlerClass,
1564
interface=None, use_ipv6=True):
1565
self.interface = interface
1567
self.address_family = socket.AF_INET6
1568
socketserver.TCPServer.__init__(self, server_address,
1569
RequestHandlerClass)
523
address_family = socket.AF_INET6
524
def __init__(self, *args, **kwargs):
525
if "settings" in kwargs:
526
self.settings = kwargs["settings"]
527
del kwargs["settings"]
528
if "clients" in kwargs:
529
self.clients = kwargs["clients"]
530
del kwargs["clients"]
532
return super(type(self), self).__init__(*args, **kwargs)
1570
533
def server_bind(self):
1571
534
"""This overrides the normal server_bind() function
1572
535
to bind to an interface if one was specified, and also NOT to
1573
536
bind to an address or port if they were not specified."""
1574
if self.interface is not None:
1575
if SO_BINDTODEVICE is None:
1576
logger.error("SO_BINDTODEVICE does not exist;"
1577
" cannot bind to interface %s",
1581
self.socket.setsockopt(socket.SOL_SOCKET,
1585
except socket.error as error:
1586
if error[0] == errno.EPERM:
1587
logger.error("No permission to"
1588
" bind to interface %s",
1590
elif error[0] == errno.ENOPROTOOPT:
1591
logger.error("SO_BINDTODEVICE not available;"
1592
" cannot bind to interface %s",
537
if self.settings["interface"]:
538
# 25 is from /usr/include/asm-i486/socket.h
539
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
541
self.socket.setsockopt(socket.SOL_SOCKET,
543
self.settings["interface"])
544
except socket.error, error:
545
if error[0] == errno.EPERM:
546
logger.error(u"No permission to"
547
u" bind to interface %s",
548
self.settings["interface"])
1596
551
# Only bind(2) the socket if we really need to.
1597
552
if self.server_address[0] or self.server_address[1]:
1598
553
if not self.server_address[0]:
1599
if self.address_family == socket.AF_INET6:
1600
any_address = "::" # in6addr_any
1602
any_address = socket.INADDR_ANY
1603
self.server_address = (any_address,
555
self.server_address = (in6addr_any,
1604
556
self.server_address[1])
1605
557
elif not self.server_address[1]:
1606
558
self.server_address = (self.server_address[0],
1608
# if self.interface:
560
# if self.settings["interface"]:
1609
561
# self.server_address = (self.server_address[0],
1612
564
# if_nametoindex
1614
return socketserver.TCPServer.server_bind(self)
1617
class MandosServer(IPv6_TCPServer):
1621
clients: set of Client objects
1622
gnutls_priority GnuTLS priority string
1623
use_dbus: Boolean; to emit D-Bus signals or not
1625
Assumes a gobject.MainLoop event loop.
1627
def __init__(self, server_address, RequestHandlerClass,
1628
interface=None, use_ipv6=True, clients=None,
1629
gnutls_priority=None, use_dbus=True):
1630
self.enabled = False
1631
self.clients = clients
1632
if self.clients is None:
1633
self.clients = set()
1634
self.use_dbus = use_dbus
1635
self.gnutls_priority = gnutls_priority
1636
IPv6_TCPServer.__init__(self, server_address,
1637
RequestHandlerClass,
1638
interface = interface,
1639
use_ipv6 = use_ipv6)
567
return super(type(self), self).server_bind()
1640
568
def server_activate(self):
1641
569
if self.enabled:
1642
return socketserver.TCPServer.server_activate(self)
570
return super(type(self), self).server_activate()
1644
571
def enable(self):
1645
572
self.enabled = True
1647
def add_pipe(self, parent_pipe, proc):
1648
# Call "handle_ipc" for both data and EOF events
1649
gobject.io_add_watch(parent_pipe.fileno(),
1650
gobject.IO_IN | gobject.IO_HUP,
1651
functools.partial(self.handle_ipc,
1656
def handle_ipc(self, source, condition, parent_pipe=None,
1657
proc = None, client_object=None):
1659
gobject.IO_IN: "IN", # There is data to read.
1660
gobject.IO_OUT: "OUT", # Data can be written (without
1662
gobject.IO_PRI: "PRI", # There is urgent data to read.
1663
gobject.IO_ERR: "ERR", # Error condition.
1664
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1665
# broken, usually for pipes and
1668
conditions_string = ' | '.join(name
1670
condition_names.iteritems()
1671
if cond & condition)
1672
# error, or the other end of multiprocessing.Pipe has closed
1673
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1674
# Wait for other process to exit
1678
# Read a request from the child
1679
request = parent_pipe.recv()
1680
command = request[0]
1682
if command == 'init':
1684
address = request[2]
1686
for c in self.clients:
1687
if c.fingerprint == fpr:
1691
logger.info("Client not found for fingerprint: %s, ad"
1692
"dress: %s", fpr, address)
1695
mandos_dbus_service.ClientNotFound(fpr,
1697
parent_pipe.send(False)
1700
gobject.io_add_watch(parent_pipe.fileno(),
1701
gobject.IO_IN | gobject.IO_HUP,
1702
functools.partial(self.handle_ipc,
1708
parent_pipe.send(True)
1709
# remove the old hook in favor of the new above hook on
1712
if command == 'funcall':
1713
funcname = request[1]
1717
parent_pipe.send(('data', getattr(client_object,
1721
if command == 'getattr':
1722
attrname = request[1]
1723
if callable(client_object.__getattribute__(attrname)):
1724
parent_pipe.send(('function',))
1726
parent_pipe.send(('data', client_object
1727
.__getattribute__(attrname)))
1729
if command == 'setattr':
1730
attrname = request[1]
1732
setattr(client_object, attrname, value)
1737
575
def string_to_delta(interval):
1738
576
"""Parse a string and return a datetime.timedelta
1740
578
>>> string_to_delta('7d')
1741
579
datetime.timedelta(7)
1742
580
>>> string_to_delta('60s')