119
98
class AvahiService(object):
120
"""An Avahi (Zeroconf) service.
123
100
interface: integer; avahi.IF_UNSPEC or an interface index.
124
101
Used to optionally bind to the specified interface.
125
name: string; Example: 'Mandos'
126
type: string; Example: '_mandos._tcp'.
127
See <http://www.dns-sd.org/ServiceTypes.html>
128
port: integer; what port to announce
129
TXT: list of strings; TXT record for the service
130
domain: string; Domain to publish on, default to .local if empty.
131
host: string; Host to publish records for, default is localhost
132
max_renames: integer; maximum number of renames
133
rename_count: integer; counter so we only rename after collisions
134
a sensible number of times
135
group: D-Bus Entry Group
137
bus: dbus.SystemBus()
102
name = string; Example: "Mandos"
103
type = string; Example: "_mandos._tcp".
104
See <http://www.dns-sd.org/ServiceTypes.html>
105
port = integer; what port to announce
106
TXT = list of strings; TXT record for the service
107
domain = string; Domain to publish on, default to .local if empty.
108
host = string; Host to publish records for, default to localhost
110
max_renames = integer; maximum number of renames
111
rename_count = integer; counter so we only rename after collisions
112
a sensible number of times
139
114
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):
115
type = None, port = None, TXT = None, domain = "",
116
host = "", max_renames = 12):
117
"""An Avahi (Zeroconf) service. """
143
118
self.interface = interface
145
self.type = servicetype
147
self.TXT = TXT if TXT is not None else []
148
126
self.domain = domain
150
128
self.rename_count = 0
151
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
129
def rename(self):
158
130
"""Derived from the Avahi example code"""
159
131
if self.rename_count >= self.max_renames:
160
logger.critical("No suitable Zeroconf service name found"
161
" after %i retries, exiting.",
132
logger.critical(u"No suitable service name found after %i"
133
u" retries, exiting.", rename_count)
163
134
raise AvahiServiceError("Too many renames")
164
self.name = unicode(self.server
165
.GetAlternativeServiceName(self.name))
166
logger.info("Changing Zeroconf service name to %r ...",
135
name = server.GetAlternativeServiceName(name)
136
logger.error(u"Changing name to %r ...", name)
171
except dbus.exceptions.DBusException as error:
172
logger.critical("DBusException: %s", error)
175
139
self.rename_count += 1
176
140
def remove(self):
177
141
"""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:
142
if group is not None:
184
145
"""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))
148
group = dbus.Interface\
149
(bus.get_object(avahi.DBUS_NAME,
150
server.EntryGroupNew()),
151
avahi.DBUS_INTERFACE_ENTRY_GROUP)
152
group.connect_to_signal('StateChanged',
153
entry_group_state_changed)
154
logger.debug(u"Adding service '%s' of type '%s' ...",
155
service.name, service.type)
157
self.interface, # interface
158
avahi.PROTO_INET6, # protocol
159
dbus.UInt32(0), # flags
160
self.name, self.type,
161
self.domain, self.host,
162
dbus.UInt16(self.port),
163
avahi.string_array_to_txt_array(self.TXT))
166
# From the Avahi example code:
167
group = None # our entry group
168
# End of Avahi example code
281
171
class Client(object):
282
172
"""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
174
name: string; from the config file, used in log messages
175
fingerprint: string (40 or 32 hexadecimal digits); used to
176
uniquely identify the client
177
secret: bytestring; sent verbatim (over TLS) to client
178
fqdn: string (FQDN); available for use by the checker command
179
created: datetime.datetime(); object creation, not client host
180
last_checked_ok: datetime.datetime() or None if not yet checked OK
181
timeout: datetime.timedelta(); How long from last_checked_ok
182
until this client is invalid
183
interval: datetime.timedelta(); How often to start a new checker
184
stop_hook: If set, called by stop() as stop_hook(self)
185
checker: subprocess.Popen(); a running checker process used
186
to see if the client lives.
187
'None' if no process is running.
188
checker_initiator_tag: a gobject event source tag, or None
189
stop_initiator_tag: - '' -
190
checker_callback_tag: - '' -
191
checker_command: string; External command which is run to check if
192
client lives. %() expansions are done at
294
193
runtime with vars(self) as dict, so that for
295
194
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
196
_timeout: Real variable for 'timeout'
197
_interval: Real variable for 'interval'
198
_timeout_milliseconds: Used when calling gobject.timeout_add()
199
_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):
341
"""Note: the 'checker' key in 'config' sets the
342
'checker_command' attribute and *not* the 'checker'
201
def _set_timeout(self, timeout):
202
"Setter function for 'timeout' attribute"
203
self._timeout = timeout
204
self._timeout_milliseconds = ((self.timeout.days
205
* 24 * 60 * 60 * 1000)
206
+ (self.timeout.seconds * 1000)
207
+ (self.timeout.microseconds
209
timeout = property(lambda self: self._timeout,
212
def _set_interval(self, interval):
213
"Setter function for 'interval' attribute"
214
self._interval = interval
215
self._interval_milliseconds = ((self.interval.days
216
* 24 * 60 * 60 * 1000)
217
+ (self.interval.seconds
219
+ (self.interval.microseconds
221
interval = property(lambda self: self._interval,
224
def __init__(self, name = None, stop_hook=None, config={}):
225
"""Note: the 'checker' argument sets the 'checker_command'
226
attribute and not the 'checker' attribute.."""
347
logger.debug("Creating client %r", self.name)
348
# Uppercase and remove spaces from fingerprint for later
349
# comparison purposes with return value from the fingerprint()
351
self.fingerprint = (config["fingerprint"].upper()
353
logger.debug(" Fingerprint: %s", self.fingerprint)
228
logger.debug(u"Creating client %r", self.name)
229
# Uppercase and remove spaces from fingerprint
230
# for later comparison purposes with return value of
231
# the fingerprint() function
232
self.fingerprint = config["fingerprint"].upper()\
234
logger.debug(u" Fingerprint: %s", self.fingerprint)
354
235
if "secret" in config:
355
self.secret = config["secret"].decode("base64")
236
self.secret = config["secret"].decode(u"base64")
356
237
elif "secfile" in config:
357
with open(os.path.expanduser(os.path.expandvars
358
(config["secfile"])),
360
self.secret = secfile.read()
238
sf = open(config["secfile"])
239
self.secret = sf.read()
362
raise TypeError("No secret or secfile for client %s"
242
raise TypeError(u"No secret or secfile for client %s"
364
self.host = config.get("host", "")
365
self.created = datetime.datetime.utcnow()
367
self.last_approval_request = None
368
self.last_enabled = None
244
self.fqdn = config.get("fqdn", "")
245
self.created = datetime.datetime.now()
369
246
self.last_checked_ok = None
370
247
self.timeout = string_to_delta(config["timeout"])
371
self.extended_timeout = string_to_delta(config
372
["extended_timeout"])
373
248
self.interval = string_to_delta(config["interval"])
374
self.disable_hook = disable_hook
249
self.stop_hook = stop_hook
375
250
self.checker = None
376
251
self.checker_initiator_tag = None
377
self.disable_initiator_tag = None
252
self.stop_initiator_tag = None
379
253
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()
254
self.check_command = config["checker"]
401
256
"""Start this client's checker and timeout hooks"""
402
if getattr(self, "enabled", False):
405
self.send_changedstate()
406
257
# Schedule a new checker to be started an 'interval' from now,
407
258
# 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()
259
self.checker_initiator_tag = gobject.timeout_add\
260
(self._interval_milliseconds,
418
262
# Also start a new checker *right now*.
419
263
self.start_checker()
421
def disable(self, quiet=True):
422
"""Disable this client."""
423
if not getattr(self, "enabled", False):
264
# Schedule a stop() when 'timeout' has passed
265
self.stop_initiator_tag = gobject.timeout_add\
266
(self._timeout_milliseconds,
270
The possibility that a client might be restarted is left open,
271
but not currently used."""
272
# If this client doesn't have a secret, it is already stopped.
274
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
278
if getattr(self, "stop_initiator_tag", False):
279
gobject.source_remove(self.stop_initiator_tag)
280
self.stop_initiator_tag = None
433
281
if getattr(self, "checker_initiator_tag", False):
434
282
gobject.source_remove(self.checker_initiator_tag)
435
283
self.checker_initiator_tag = None
436
284
self.stop_checker()
437
if self.disable_hook:
438
self.disable_hook(self)
440
287
# Do not run this again if called by a gobject.timeout_add
443
289
def __del__(self):
444
self.disable_hook = None
447
def checker_callback(self, pid, condition, command):
290
self.stop_hook = None
292
def checker_callback(self, pid, condition):
448
293
"""The checker has completed, so take appropriate actions."""
294
now = datetime.datetime.now()
449
295
self.checker_callback_tag = None
450
296
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?",
297
if os.WIFEXITED(condition) \
298
and (os.WEXITSTATUS(condition) == 0):
299
logger.info(u"Checker for %(name)s succeeded",
301
self.last_checked_ok = now
302
gobject.source_remove(self.stop_initiator_tag)
303
self.stop_initiator_tag = gobject.timeout_add\
304
(self._timeout_milliseconds,
306
elif not os.WIFEXITED(condition):
307
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()
310
logger.info(u"Checker for %(name)s failed",
484
312
def start_checker(self):
485
313
"""Start a new checker subprocess if one is not running.
487
314
If a checker already exists, leave it running and do
489
316
# The reason for not killing a running checker is that if we
566
360
logger.debug("Stopping checker for %(name)s", vars(self))
568
362
os.kill(self.checker.pid, signal.SIGTERM)
570
364
#if self.checker.poll() is None:
571
365
# os.kill(self.checker.pid, signal.SIGKILL)
572
except OSError as error:
366
except OSError, error:
573
367
if error.errno != errno.ESRCH: # No such process
575
369
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.
370
def still_valid(self):
371
"""Has the timeout not yet passed for this client?"""
372
now = datetime.datetime.now()
373
if self.last_checked_ok is None:
374
return now < (self.created + self.timeout)
376
return now < (self.last_checked_ok + self.timeout)
379
def peer_certificate(session):
380
"Return the peer's OpenPGP certificate as a bytestring"
381
# If not an OpenPGP certificate...
382
if gnutls.library.functions.gnutls_certificate_type_get\
383
(session._c_object) \
384
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
385
# ...do the normal thing
386
return session.peer_certificate
387
list_size = ctypes.c_uint()
388
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
389
(session._c_object, ctypes.byref(list_size))
390
if list_size.value == 0:
393
return ctypes.string_at(cert.data, cert.size)
396
def fingerprint(openpgp):
397
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
398
# New empty GnuTLS certificate
399
crt = gnutls.library.types.gnutls_openpgp_crt_t()
400
gnutls.library.functions.gnutls_openpgp_crt_init\
402
# New GnuTLS "datum" with the OpenPGP public key
403
datum = gnutls.library.types.gnutls_datum_t\
404
(ctypes.cast(ctypes.c_char_p(openpgp),
405
ctypes.POINTER(ctypes.c_ubyte)),
406
ctypes.c_uint(len(openpgp)))
407
# Import the OpenPGP public key into the certificate
408
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
411
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
412
# New buffer for the fingerprint
413
buffer = ctypes.create_string_buffer(20)
414
buffer_length = ctypes.c_size_t()
415
# Get the fingerprint from the certificate into the buffer
416
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
417
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
418
# Deinit the certificate
419
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
420
# Convert the buffer to a Python bytestring
421
fpr = ctypes.string_at(buffer, buffer_length.value)
422
# Convert the bytestring to hexadecimal notation
423
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
427
class tcp_handler(SocketServer.BaseRequestHandler, object):
428
"""A TCP request handler class.
429
Instantiated by IPv6_TCPServer for each request to handle it.
1302
430
Note: This will run in its own forked process."""
1304
432
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
433
logger.info(u"TCP connection from: %s",
434
unicode(self.client_address))
435
session = gnutls.connection.ClientSession\
436
(self.request, gnutls.connection.X509Credentials())
438
line = self.request.makefile().readline()
439
logger.debug(u"Protocol version: %r", line)
441
if int(line.strip().split()[0]) > 1:
443
except (ValueError, IndexError, RuntimeError), error:
444
logger.error(u"Unknown protocol version: %s", error)
447
# Note: gnutls.connection.X509Credentials is really a generic
448
# GnuTLS certificate credentials object so long as no X.509
449
# keys are added to it. Therefore, we can use it here despite
450
# using OpenPGP certificates.
452
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
453
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
455
priority = "NORMAL" # Fallback default, since this
457
if self.server.settings["priority"]:
458
priority = self.server.settings["priority"]
459
gnutls.library.functions.gnutls_priority_set_direct\
460
(session._c_object, priority, None);
464
except gnutls.errors.GNUTLSError, error:
465
logger.warning(u"Handshake failed: %s", error)
466
# Do not run session.bye() here: the session is not
467
# established. Just abandon the request.
470
fpr = fingerprint(peer_certificate(session))
471
except (TypeError, gnutls.errors.GNUTLSError), error:
472
logger.warning(u"Bad certificate: %s", error)
475
logger.debug(u"Fingerprint: %s", fpr)
477
for c in self.server.clients:
478
if c.fingerprint == fpr:
482
logger.warning(u"Client not found for fingerprint: %s",
486
# Have to check if client.still_valid(), since it is possible
487
# that the client timed out while establishing the GnuTLS
489
if not client.still_valid():
490
logger.warning(u"Client %(name)s is invalid",
495
while sent_size < len(client.secret):
496
sent = session.send(client.secret[sent_size:])
497
logger.debug(u"Sent: %d, remaining: %d",
498
sent, len(client.secret)
499
- (sent_size + sent))
504
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
505
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1559
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
507
settings: Server settings
508
clients: Set() of Client objects
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)
510
address_family = socket.AF_INET6
511
def __init__(self, *args, **kwargs):
512
if "settings" in kwargs:
513
self.settings = kwargs["settings"]
514
del kwargs["settings"]
515
if "clients" in kwargs:
516
self.clients = kwargs["clients"]
517
del kwargs["clients"]
518
return super(type(self), self).__init__(*args, **kwargs)
1570
519
def server_bind(self):
1571
520
"""This overrides the normal server_bind() function
1572
521
to bind to an interface if one was specified, and also NOT to
1573
522
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",
523
if self.settings["interface"]:
524
# 25 is from /usr/include/asm-i486/socket.h
525
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
527
self.socket.setsockopt(socket.SOL_SOCKET,
529
self.settings["interface"])
530
except socket.error, error:
531
if error[0] == errno.EPERM:
532
logger.error(u"No permission to"
533
u" bind to interface %s",
534
self.settings["interface"])
1596
537
# Only bind(2) the socket if we really need to.
1597
538
if self.server_address[0] or self.server_address[1]:
1598
539
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,
541
self.server_address = (in6addr_any,
1604
542
self.server_address[1])
1605
elif not self.server_address[1]:
543
elif self.server_address[1] is None:
1606
544
self.server_address = (self.server_address[0],
1608
# if self.interface:
1609
# self.server_address = (self.server_address[0],
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)
1640
def server_activate(self):
1642
return socketserver.TCPServer.server_activate(self)
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)
546
return super(type(self), self).server_bind()
1737
549
def string_to_delta(interval):
1738
550
"""Parse a string and return a datetime.timedelta
1740
552
>>> string_to_delta('7d')
1741
553
datetime.timedelta(7)
1742
554
>>> string_to_delta('60s')
1873
689
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1874
690
"servicename": "Mandos",
1880
693
# Parse config file for server-global settings
1881
server_config = configparser.SafeConfigParser(server_defaults)
694
server_config = ConfigParser.SafeConfigParser(server_defaults)
1882
695
del server_defaults
1883
server_config.read(os.path.join(options.configdir,
696
server_config.read(os.path.join(options.configdir, "server.conf"))
697
server_section = "server"
1885
698
# Convert the SafeConfigParser object to a dict
1886
server_settings = server_config.defaults()
1887
# Use the appropriate methods on the non-string config options
1888
for option in ("debug", "use_dbus", "use_ipv6"):
1889
server_settings[option] = server_config.getboolean("DEFAULT",
1891
if server_settings["port"]:
1892
server_settings["port"] = server_config.getint("DEFAULT",
699
server_settings = dict(server_config.items(server_section))
700
# Use getboolean on the boolean config option
701
server_settings["debug"] = server_config.getboolean\
702
(server_section, "debug")
1894
703
del server_config
1896
705
# Override the settings from the config file with command line
1897
706
# options, if set.
1898
707
for option in ("interface", "address", "port", "debug",
1899
"priority", "servicename", "configdir",
1900
"use_dbus", "use_ipv6", "debuglevel"):
708
"priority", "servicename", "configdir"):
1901
709
value = getattr(options, option)
1902
710
if value is not None:
1903
711
server_settings[option] = value
1905
# Force all strings to be unicode
1906
for option in server_settings.keys():
1907
if type(server_settings[option]) is str:
1908
server_settings[option] = unicode(server_settings[option])
1909
713
# Now we have our good server settings in "server_settings"
1911
##################################################################
1914
debug = server_settings["debug"]
1915
debuglevel = server_settings["debuglevel"]
1916
use_dbus = server_settings["use_dbus"]
1917
use_ipv6 = server_settings["use_ipv6"]
1919
if server_settings["servicename"] != "Mandos":
1920
syslogger.setFormatter(logging.Formatter
1921
('Mandos (%s) [%%(process)d]:'
1922
' %%(levelname)s: %%(message)s'
1923
% server_settings["servicename"]))
1925
715
# Parse config file with clients
1926
client_defaults = { "timeout": "5m",
1927
"extended_timeout": "15m",
1929
"checker": "fping -q -- %%(host)s",
1931
"approval_delay": "0s",
1932
"approval_duration": "1s",
716
client_defaults = { "timeout": "1h",
718
"checker": "fping -q -- %%(fqdn)s",
1934
client_config = configparser.SafeConfigParser(client_defaults)
720
client_config = ConfigParser.SafeConfigParser(client_defaults)
1935
721
client_config.read(os.path.join(server_settings["configdir"],
1936
722
"clients.conf"))
1938
global mandos_dbus_service
1939
mandos_dbus_service = None
1941
tcp_server = MandosServer((server_settings["address"],
1942
server_settings["port"]),
1944
interface=(server_settings["interface"]
1948
server_settings["priority"],
1951
pidfilename = "/var/run/mandos.pid"
1953
pidfile = open(pidfilename, "w")
1955
logger.error("Could not open file %r", pidfilename)
1958
uid = pwd.getpwnam("_mandos").pw_uid
1959
gid = pwd.getpwnam("_mandos").pw_gid
1962
uid = pwd.getpwnam("mandos").pw_uid
1963
gid = pwd.getpwnam("mandos").pw_gid
1966
uid = pwd.getpwnam("nobody").pw_uid
1967
gid = pwd.getpwnam("nobody").pw_gid
1974
except OSError as error:
1975
if error[0] != errno.EPERM:
1978
if not debug and not debuglevel:
1979
logger.setLevel(logging.WARNING)
1981
level = getattr(logging, debuglevel.upper())
1982
logger.setLevel(level)
1985
logger.setLevel(logging.DEBUG)
1986
# Enable all possible GnuTLS debugging
1988
# "Use a log level over 10 to enable all debugging options."
1990
gnutls.library.functions.gnutls_global_set_log_level(11)
1992
@gnutls.library.types.gnutls_log_func
1993
def debug_gnutls(level, string):
1994
logger.debug("GnuTLS: %s", string[:-1])
1996
(gnutls.library.functions
1997
.gnutls_global_set_log_function(debug_gnutls))
1999
# Redirect stdin so all checkers get /dev/null
2000
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2001
os.dup2(null, sys.stdin.fileno())
2005
# No console logging
2006
logger.removeHandler(console)
2008
# Need to fork before connecting to D-Bus
2010
# Close all input and output, do double fork, etc.
725
service = AvahiService(name = server_settings["servicename"],
726
type = "_mandos._tcp", );
727
if server_settings["interface"]:
728
service.interface = if_nametoindex(server_settings["interface"])
2013
730
global main_loop
2014
733
# From the Avahi example code
2015
734
DBusGMainLoop(set_as_default=True )
2016
735
main_loop = gobject.MainLoop()
2017
736
bus = dbus.SystemBus()
737
server = dbus.Interface(
738
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
739
avahi.DBUS_INTERFACE_SERVER )
2018
740
# End of Avahi example code
2021
bus_name = dbus.service.BusName("se.recompile.Mandos",
2022
bus, do_not_queue=True)
2023
old_bus_name = (dbus.service.BusName
2024
("se.bsnet.fukt.Mandos", bus,
2026
except dbus.exceptions.NameExistsException as e:
2027
logger.error(unicode(e) + ", disabling D-Bus")
2029
server_settings["use_dbus"] = False
2030
tcp_server.use_dbus = False
2031
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2032
service = AvahiServiceToSyslog(name =
2033
server_settings["servicename"],
2034
servicetype = "_mandos._tcp",
2035
protocol = protocol, bus = bus)
2036
if server_settings["interface"]:
2037
service.interface = (if_nametoindex
2038
(str(server_settings["interface"])))
2040
global multiprocessing_manager
2041
multiprocessing_manager = multiprocessing.Manager()
2043
client_class = Client
2045
client_class = functools.partial(ClientDBusTransitional,
2047
def client_config_items(config, section):
2048
special_settings = {
2049
"approved_by_default":
2050
lambda: config.getboolean(section,
2051
"approved_by_default"),
2053
for name, value in config.items(section):
2055
yield (name, special_settings[name]())
2059
tcp_server.clients.update(set(
2060
client_class(name = section,
2061
config= dict(client_config_items(
2062
client_config, section)))
2063
for section in client_config.sections()))
2064
if not tcp_server.clients:
2065
logger.warning("No clients defined")
2071
pidfile.write(str(pid) + "\n".encode("utf-8"))
2074
logger.error("Could not write to file %r with PID %d",
2077
# "pidfile" was never created
742
debug = server_settings["debug"]
745
console = logging.StreamHandler()
746
# console.setLevel(logging.DEBUG)
747
console.setFormatter(logging.Formatter\
748
('%(levelname)s: %(message)s'))
749
logger.addHandler(console)
753
def remove_from_clients(client):
754
clients.remove(client)
756
logger.critical(u"No clients left, exiting")
759
clients.update(Set(Client(name = section,
760
stop_hook = remove_from_clients,
762
= dict(client_config.items(section)))
763
for section in client_config.sections()))
769
"Cleanup function; run on exit"
771
# From the Avahi example code
772
if not group is None:
775
# End of Avahi example code
778
client = clients.pop()
779
client.stop_hook = None
782
atexit.register(cleanup)
2081
785
signal.signal(signal.SIGINT, signal.SIG_IGN)
2083
786
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2084
787
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2087
class MandosDBusService(dbus.service.Object):
2088
"""A D-Bus proxy object"""
2090
dbus.service.Object.__init__(self, bus, "/")
2091
_interface = "se.recompile.Mandos"
2093
@dbus.service.signal(_interface, signature="o")
2094
def ClientAdded(self, objpath):
2098
@dbus.service.signal(_interface, signature="ss")
2099
def ClientNotFound(self, fingerprint, address):
2103
@dbus.service.signal(_interface, signature="os")
2104
def ClientRemoved(self, objpath, name):
2108
@dbus.service.method(_interface, out_signature="ao")
2109
def GetAllClients(self):
2111
return dbus.Array(c.dbus_object_path
2112
for c in tcp_server.clients)
2114
@dbus.service.method(_interface,
2115
out_signature="a{oa{sv}}")
2116
def GetAllClientsWithProperties(self):
2118
return dbus.Dictionary(
2119
((c.dbus_object_path, c.GetAll(""))
2120
for c in tcp_server.clients),
2123
@dbus.service.method(_interface, in_signature="o")
2124
def RemoveClient(self, object_path):
2126
for c in tcp_server.clients:
2127
if c.dbus_object_path == object_path:
2128
tcp_server.clients.remove(c)
2129
c.remove_from_connection()
2130
# Don't signal anything except ClientRemoved
2131
c.disable(quiet=True)
2133
self.ClientRemoved(object_path, c.name)
2135
raise KeyError(object_path)
2139
class MandosDBusServiceTransitional(MandosDBusService):
2140
__metaclass__ = AlternateDBusNamesMetaclass
2141
mandos_dbus_service = MandosDBusServiceTransitional()
2144
"Cleanup function; run on exit"
2147
multiprocessing.active_children()
2148
while tcp_server.clients:
2149
client = tcp_server.clients.pop()
2151
client.remove_from_connection()
2152
client.disable_hook = None
2153
# Don't signal anything except ClientRemoved
2154
client.disable(quiet=True)
2157
mandos_dbus_service.ClientRemoved(client
2161
atexit.register(cleanup)
2163
for client in tcp_server.clients:
2166
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2170
tcp_server.server_activate()
789
for client in clients:
792
tcp_server = IPv6_TCPServer((server_settings["address"],
793
server_settings["port"]),
795
settings=server_settings,
2172
797
# Find out what port we got
2173
798
service.port = tcp_server.socket.getsockname()[1]
2175
logger.info("Now listening on address %r, port %d,"
2176
" flowinfo %d, scope_id %d"
2177
% tcp_server.socket.getsockname())
2179
logger.info("Now listening on address %r, port %d"
2180
% tcp_server.socket.getsockname())
799
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
800
u" scope_id %d" % tcp_server.socket.getsockname())
2182
802
#service.interface = tcp_server.socket.getsockname()[3]
2185
805
# From the Avahi example code
806
server.connect_to_signal("StateChanged", server_state_changed)
2188
except dbus.exceptions.DBusException as error:
2189
logger.critical("DBusException: %s", error)
808
server_state_changed(server.GetState())
809
except dbus.exceptions.DBusException, error:
810
logger.critical(u"DBusException: %s", error)
2192
812
# End of Avahi example code
2194
814
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2195
815
lambda *args, **kwargs:
2196
(tcp_server.handle_request
2197
(*args[2:], **kwargs) or True))
816
tcp_server.handle_request\
817
(*args[2:], **kwargs) or True)
2199
819
logger.debug("Starting main loop")
820
main_loop_started = True
2201
except AvahiError as error:
2202
logger.critical("AvahiError: %s", error)
822
except AvahiError, error:
823
logger.critical(u"AvahiError: %s" + unicode(error))
2205
825
except KeyboardInterrupt:
2207
print("", file=sys.stderr)
2208
logger.debug("Server received KeyboardInterrupt")
2209
logger.debug("Server exiting")
2210
# Must run before the D-Bus bus name gets deregistered
2214
829
if __name__ == '__main__':