131
104
max_renames: integer; maximum number of renames
132
105
rename_count: integer; counter so we only rename after collisions
133
106
a sensible number of times
134
group: D-Bus Entry Group
136
bus: dbus.SystemBus()
138
108
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
139
servicetype = None, port = None, TXT = None,
140
domain = "", host = "", max_renames = 32768,
141
protocol = avahi.PROTO_UNSPEC, bus = None):
109
type = None, port = None, TXT = None, domain = "",
110
host = "", max_renames = 32768):
142
111
self.interface = interface
144
self.type = servicetype
146
self.TXT = TXT if TXT is not None else []
147
119
self.domain = domain
149
121
self.rename_count = 0
150
122
self.max_renames = max_renames
151
self.protocol = protocol
152
self.group = None # our entry group
155
self.entry_group_state_changed_match = None
156
123
def rename(self):
157
124
"""Derived from the Avahi example code"""
158
125
if self.rename_count >= self.max_renames:
159
logger.critical("No suitable Zeroconf service name found"
160
" after %i retries, exiting.",
126
logger.critical(u"No suitable service name found after %i"
127
u" retries, exiting.", rename_count)
162
128
raise AvahiServiceError("Too many renames")
163
self.name = unicode(self.server.GetAlternativeServiceName(self.name))
164
logger.info("Changing Zeroconf service name to %r ...",
166
syslogger.setFormatter(logging.Formatter
167
('Mandos (%s) [%%(process)d]:'
168
' %%(levelname)s: %%(message)s'
129
self.name = server.GetAlternativeServiceName(self.name)
130
logger.info(u"Changing name to %r ...", str(self.name))
131
syslogger.setFormatter(logging.Formatter\
132
('Mandos (%s): %%(levelname)s:'
133
' %%(message)s' % self.name))
173
except dbus.exceptions.DBusException as error:
174
logger.critical("DBusException: %s", error)
177
136
self.rename_count += 1
178
137
def remove(self):
179
138
"""Derived from the Avahi example code"""
180
if self.entry_group_state_changed_match is not None:
181
self.entry_group_state_changed_match.remove()
182
self.entry_group_state_changed_match = None
183
if self.group is not None:
139
if group is not None:
186
142
"""Derived from the Avahi example code"""
188
if self.group is None:
189
self.group = dbus.Interface(
190
self.bus.get_object(avahi.DBUS_NAME,
191
self.server.EntryGroupNew()),
192
avahi.DBUS_INTERFACE_ENTRY_GROUP)
193
self.entry_group_state_changed_match = (
194
self.group.connect_to_signal(
195
'StateChanged', self .entry_group_state_changed))
196
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
197
self.name, self.type)
198
self.group.AddService(
201
dbus.UInt32(0), # flags
202
self.name, self.type,
203
self.domain, self.host,
204
dbus.UInt16(self.port),
205
avahi.string_array_to_txt_array(self.TXT))
207
def entry_group_state_changed(self, state, error):
208
"""Derived from the Avahi example code"""
209
logger.debug("Avahi entry group state change: %i", state)
211
if state == avahi.ENTRY_GROUP_ESTABLISHED:
212
logger.debug("Zeroconf service established.")
213
elif state == avahi.ENTRY_GROUP_COLLISION:
214
logger.info("Zeroconf service name collision.")
216
elif state == avahi.ENTRY_GROUP_FAILURE:
217
logger.critical("Avahi: Error in group state changed %s",
219
raise AvahiGroupError("State changed: %s"
222
"""Derived from the Avahi example code"""
223
if self.group is not None:
226
except (dbus.exceptions.UnknownMethodException,
227
dbus.exceptions.DBusException) as e:
231
def server_state_changed(self, state, error=None):
232
"""Derived from the Avahi example code"""
233
logger.debug("Avahi server state change: %i", state)
234
bad_states = { avahi.SERVER_INVALID:
235
"Zeroconf server invalid",
236
avahi.SERVER_REGISTERING: None,
237
avahi.SERVER_COLLISION:
238
"Zeroconf server name collision",
239
avahi.SERVER_FAILURE:
240
"Zeroconf server failure" }
241
if state in bad_states:
242
if bad_states[state] is not None:
244
logger.error(bad_states[state])
246
logger.error(bad_states[state] + ": %r", error)
248
elif state == avahi.SERVER_RUNNING:
252
logger.debug("Unknown state: %r", state)
254
logger.debug("Unknown state: %r: %r", state, error)
256
"""Derived from the Avahi example code"""
257
if self.server is None:
258
self.server = dbus.Interface(
259
self.bus.get_object(avahi.DBUS_NAME,
260
avahi.DBUS_PATH_SERVER,
261
follow_name_owner_changes=True),
262
avahi.DBUS_INTERFACE_SERVER)
263
self.server.connect_to_signal("StateChanged",
264
self.server_state_changed)
265
self.server_state_changed(self.server.GetState())
268
def _timedelta_to_milliseconds(td):
269
"Convert a datetime.timedelta() to milliseconds"
270
return ((td.days * 24 * 60 * 60 * 1000)
271
+ (td.seconds * 1000)
272
+ (td.microseconds // 1000))
145
group = dbus.Interface\
146
(bus.get_object(avahi.DBUS_NAME,
147
server.EntryGroupNew()),
148
avahi.DBUS_INTERFACE_ENTRY_GROUP)
149
group.connect_to_signal('StateChanged',
150
entry_group_state_changed)
151
logger.debug(u"Adding service '%s' of type '%s' ...",
152
service.name, service.type)
154
self.interface, # interface
155
avahi.PROTO_INET6, # protocol
156
dbus.UInt32(0), # flags
157
self.name, self.type,
158
self.domain, self.host,
159
dbus.UInt16(self.port),
160
avahi.string_array_to_txt_array(self.TXT))
163
# From the Avahi example code:
164
group = None # our entry group
165
# End of Avahi example code
274
168
class Client(object):
275
169
"""A representation of a client host served by this server.
278
_approved: bool(); 'None' if not yet approved/disapproved
279
approval_delay: datetime.timedelta(); Time to wait for approval
280
approval_duration: datetime.timedelta(); Duration of one approval
281
checker: subprocess.Popen(); a running checker process used
282
to see if the client lives.
283
'None' if no process is running.
284
checker_callback_tag: a gobject event source tag, or None
285
checker_command: string; External command which is run to check
286
if client lives. %() expansions are done at
171
name: string; from the config file, used in log messages
172
fingerprint: string (40 or 32 hexadecimal digits); used to
173
uniquely identify the client
174
secret: bytestring; sent verbatim (over TLS) to client
175
host: string; available for use by the checker command
176
created: datetime.datetime(); object creation, not client host
177
last_checked_ok: datetime.datetime() or None if not yet checked OK
178
timeout: datetime.timedelta(); How long from last_checked_ok
179
until this client is invalid
180
interval: datetime.timedelta(); How often to start a new checker
181
stop_hook: If set, called by stop() as stop_hook(self)
182
checker: subprocess.Popen(); a running checker process used
183
to see if the client lives.
184
'None' if no process is running.
185
checker_initiator_tag: a gobject event source tag, or None
186
stop_initiator_tag: - '' -
187
checker_callback_tag: - '' -
188
checker_command: string; External command which is run to check if
189
client lives. %() expansions are done at
287
190
runtime with vars(self) as dict, so that for
288
191
instance %(name)s can be used in the command.
289
checker_initiator_tag: a gobject event source tag, or None
290
created: datetime.datetime(); (UTC) object creation
291
current_checker_command: string; current running checker_command
292
disable_hook: If set, called by disable() as disable_hook(self)
293
disable_initiator_tag: a gobject event source tag, or None
295
fingerprint: string (40 or 32 hexadecimal digits); used to
296
uniquely identify the client
297
host: string; available for use by the checker command
298
interval: datetime.timedelta(); How often to start a new checker
299
last_approval_request: datetime.datetime(); (UTC) or None
300
last_checked_ok: datetime.datetime(); (UTC) or None
301
last_enabled: datetime.datetime(); (UTC)
302
name: string; from the config file, used in log messages and
304
secret: bytestring; sent verbatim (over TLS) to client
305
timeout: datetime.timedelta(); How long from last_checked_ok
306
until this client is disabled
307
extended_timeout: extra long timeout when password has been sent
308
runtime_expansions: Allowed attributes for runtime expansion.
309
expires: datetime.datetime(); time (UTC) when a client will be
193
_timeout: Real variable for 'timeout'
194
_interval: Real variable for 'interval'
195
_timeout_milliseconds: Used when calling gobject.timeout_add()
196
_interval_milliseconds: - '' -
313
runtime_expansions = ("approval_delay", "approval_duration",
314
"created", "enabled", "fingerprint",
315
"host", "interval", "last_checked_ok",
316
"last_enabled", "name", "timeout")
318
def timeout_milliseconds(self):
319
"Return the 'timeout' attribute in milliseconds"
320
return _timedelta_to_milliseconds(self.timeout)
322
def extended_timeout_milliseconds(self):
323
"Return the 'extended_timeout' attribute in milliseconds"
324
return _timedelta_to_milliseconds(self.extended_timeout)
326
def interval_milliseconds(self):
327
"Return the 'interval' attribute in milliseconds"
328
return _timedelta_to_milliseconds(self.interval)
330
def approval_delay_milliseconds(self):
331
return _timedelta_to_milliseconds(self.approval_delay)
333
def __init__(self, name = None, disable_hook=None, config=None):
198
def _set_timeout(self, timeout):
199
"Setter function for 'timeout' attribute"
200
self._timeout = timeout
201
self._timeout_milliseconds = ((self.timeout.days
202
* 24 * 60 * 60 * 1000)
203
+ (self.timeout.seconds * 1000)
204
+ (self.timeout.microseconds
206
timeout = property(lambda self: self._timeout,
209
def _set_interval(self, interval):
210
"Setter function for 'interval' attribute"
211
self._interval = interval
212
self._interval_milliseconds = ((self.interval.days
213
* 24 * 60 * 60 * 1000)
214
+ (self.interval.seconds
216
+ (self.interval.microseconds
218
interval = property(lambda self: self._interval,
221
def __init__(self, name = None, stop_hook=None, config={}):
334
222
"""Note: the 'checker' key in 'config' sets the
335
223
'checker_command' attribute and *not* the 'checker'
340
logger.debug("Creating client %r", self.name)
226
logger.debug(u"Creating client %r", self.name)
341
227
# Uppercase and remove spaces from fingerprint for later
342
228
# comparison purposes with return value from the fingerprint()
344
self.fingerprint = (config["fingerprint"].upper()
346
logger.debug(" Fingerprint: %s", self.fingerprint)
230
self.fingerprint = config["fingerprint"].upper()\
232
logger.debug(u" Fingerprint: %s", self.fingerprint)
347
233
if "secret" in config:
348
self.secret = config["secret"].decode("base64")
234
self.secret = config["secret"].decode(u"base64")
349
235
elif "secfile" in config:
350
with open(os.path.expanduser(os.path.expandvars
351
(config["secfile"])),
353
self.secret = secfile.read()
236
sf = open(config["secfile"])
237
self.secret = sf.read()
355
raise TypeError("No secret or secfile for client %s"
240
raise TypeError(u"No secret or secfile for client %s"
357
242
self.host = config.get("host", "")
358
self.created = datetime.datetime.utcnow()
360
self.last_approval_request = None
361
self.last_enabled = None
243
self.created = datetime.datetime.now()
362
244
self.last_checked_ok = None
363
245
self.timeout = string_to_delta(config["timeout"])
364
self.extended_timeout = string_to_delta(config["extended_timeout"])
365
246
self.interval = string_to_delta(config["interval"])
366
self.disable_hook = disable_hook
247
self.stop_hook = stop_hook
367
248
self.checker = None
368
249
self.checker_initiator_tag = None
369
self.disable_initiator_tag = None
250
self.stop_initiator_tag = None
371
251
self.checker_callback_tag = None
372
self.checker_command = config["checker"]
373
self.current_checker_command = None
374
self.last_connect = None
375
self._approved = None
376
self.approved_by_default = config.get("approved_by_default",
378
self.approvals_pending = 0
379
self.approval_delay = string_to_delta(
380
config["approval_delay"])
381
self.approval_duration = string_to_delta(
382
config["approval_duration"])
383
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
385
def send_changedstate(self):
386
self.changedstate.acquire()
387
self.changedstate.notify_all()
388
self.changedstate.release()
252
self.check_command = config["checker"]
391
254
"""Start this client's checker and timeout hooks"""
392
if getattr(self, "enabled", False):
395
self.send_changedstate()
396
255
# Schedule a new checker to be started an 'interval' from now,
397
256
# and every interval from then on.
398
self.checker_initiator_tag = (gobject.timeout_add
399
(self.interval_milliseconds(),
401
# Schedule a disable() when 'timeout' has passed
402
self.expires = datetime.datetime.utcnow() + self.timeout
403
self.disable_initiator_tag = (gobject.timeout_add
404
(self.timeout_milliseconds(),
407
self.last_enabled = datetime.datetime.utcnow()
257
self.checker_initiator_tag = gobject.timeout_add\
258
(self._interval_milliseconds,
408
260
# Also start a new checker *right now*.
409
261
self.start_checker()
411
def disable(self, quiet=True):
412
"""Disable this client."""
413
if not getattr(self, "enabled", False):
262
# Schedule a stop() when 'timeout' has passed
263
self.stop_initiator_tag = gobject.timeout_add\
264
(self._timeout_milliseconds,
268
The possibility that a client might be restarted is left open,
269
but not currently used."""
270
# If this client doesn't have a secret, it is already stopped.
271
if hasattr(self, "secret") and self.secret:
272
logger.info(u"Stopping client %s", self.name)
416
self.send_changedstate()
418
logger.info("Disabling client %s", self.name)
419
if getattr(self, "disable_initiator_tag", False):
420
gobject.source_remove(self.disable_initiator_tag)
421
self.disable_initiator_tag = None
276
if getattr(self, "stop_initiator_tag", False):
277
gobject.source_remove(self.stop_initiator_tag)
278
self.stop_initiator_tag = None
423
279
if getattr(self, "checker_initiator_tag", False):
424
280
gobject.source_remove(self.checker_initiator_tag)
425
281
self.checker_initiator_tag = None
426
282
self.stop_checker()
427
if self.disable_hook:
428
self.disable_hook(self)
430
285
# Do not run this again if called by a gobject.timeout_add
433
287
def __del__(self):
434
self.disable_hook = None
437
def checker_callback(self, pid, condition, command):
288
self.stop_hook = None
290
def checker_callback(self, pid, condition):
438
291
"""The checker has completed, so take appropriate actions."""
292
now = datetime.datetime.now()
439
293
self.checker_callback_tag = None
440
294
self.checker = None
441
if os.WIFEXITED(condition):
442
exitstatus = os.WEXITSTATUS(condition)
444
logger.info("Checker for %(name)s succeeded",
448
logger.info("Checker for %(name)s failed",
451
logger.warning("Checker for %(name)s crashed?",
295
if os.WIFEXITED(condition) \
296
and (os.WEXITSTATUS(condition) == 0):
297
logger.info(u"Checker for %(name)s succeeded",
299
self.last_checked_ok = now
300
gobject.source_remove(self.stop_initiator_tag)
301
self.stop_initiator_tag = gobject.timeout_add\
302
(self._timeout_milliseconds,
304
elif not os.WIFEXITED(condition):
305
logger.warning(u"Checker for %(name)s crashed?",
454
def checked_ok(self, timeout=None):
455
"""Bump up the timeout for this client.
457
This should only be called when the client has been seen,
461
timeout = self.timeout
462
self.last_checked_ok = datetime.datetime.utcnow()
463
gobject.source_remove(self.disable_initiator_tag)
464
self.expires = datetime.datetime.utcnow() + timeout
465
self.disable_initiator_tag = (gobject.timeout_add
466
(_timedelta_to_milliseconds(timeout),
469
def need_approval(self):
470
self.last_approval_request = datetime.datetime.utcnow()
308
logger.info(u"Checker for %(name)s failed",
472
310
def start_checker(self):
473
311
"""Start a new checker subprocess if one is not running.
475
312
If a checker already exists, leave it running and do
477
314
# The reason for not killing a running checker is that if we
551
355
self.checker_callback_tag = None
552
356
if getattr(self, "checker", None) is None:
554
logger.debug("Stopping checker for %(name)s", vars(self))
358
logger.debug(u"Stopping checker for %(name)s", vars(self))
556
360
os.kill(self.checker.pid, signal.SIGTERM)
558
362
#if self.checker.poll() is None:
559
363
# os.kill(self.checker.pid, signal.SIGKILL)
560
except OSError as error:
364
except OSError, error:
561
365
if error.errno != errno.ESRCH: # No such process
563
367
self.checker = None
566
def dbus_service_property(dbus_interface, signature="v",
567
access="readwrite", byte_arrays=False):
568
"""Decorators for marking methods of a DBusObjectWithProperties to
569
become properties on the D-Bus.
571
The decorated method will be called with no arguments by "Get"
572
and with one argument by "Set".
574
The parameters, where they are supported, are the same as
575
dbus.service.method, except there is only "signature", since the
576
type from Get() and the type sent to Set() is the same.
578
# Encoding deeply encoded byte arrays is not supported yet by the
579
# "Set" method, so we fail early here:
580
if byte_arrays and signature != "ay":
581
raise ValueError("Byte arrays not supported for non-'ay'"
582
" signature %r" % signature)
584
func._dbus_is_property = True
585
func._dbus_interface = dbus_interface
586
func._dbus_signature = signature
587
func._dbus_access = access
588
func._dbus_name = func.__name__
589
if func._dbus_name.endswith("_dbus_property"):
590
func._dbus_name = func._dbus_name[:-14]
591
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
596
class DBusPropertyException(dbus.exceptions.DBusException):
597
"""A base class for D-Bus property-related exceptions
599
def __unicode__(self):
600
return unicode(str(self))
603
class DBusPropertyAccessException(DBusPropertyException):
604
"""A property's access permissions disallows an operation.
609
class DBusPropertyNotFound(DBusPropertyException):
610
"""An attempt was made to access a non-existing property.
615
class DBusObjectWithProperties(dbus.service.Object):
616
"""A D-Bus object with properties.
618
Classes inheriting from this can use the dbus_service_property
619
decorator to expose methods as D-Bus properties. It exposes the
620
standard Get(), Set(), and GetAll() methods on the D-Bus.
624
def _is_dbus_property(obj):
625
return getattr(obj, "_dbus_is_property", False)
627
def _get_all_dbus_properties(self):
628
"""Returns a generator of (name, attribute) pairs
630
return ((prop.__get__(self)._dbus_name, prop.__get__(self))
631
for cls in self.__class__.__mro__
632
for name, prop in inspect.getmembers(cls, self._is_dbus_property))
634
def _get_dbus_property(self, interface_name, property_name):
635
"""Returns a bound method if one exists which is a D-Bus
636
property with the specified name and interface.
638
for cls in self.__class__.__mro__:
639
for name, value in inspect.getmembers(cls, self._is_dbus_property):
640
if value._dbus_name == property_name and value._dbus_interface == interface_name:
641
return value.__get__(self)
644
raise DBusPropertyNotFound(self.dbus_object_path + ":"
645
+ interface_name + "."
648
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
650
def Get(self, interface_name, property_name):
651
"""Standard D-Bus property Get() method, see D-Bus standard.
653
prop = self._get_dbus_property(interface_name, property_name)
654
if prop._dbus_access == "write":
655
raise DBusPropertyAccessException(property_name)
657
if not hasattr(value, "variant_level"):
659
return type(value)(value, variant_level=value.variant_level+1)
661
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
662
def Set(self, interface_name, property_name, value):
663
"""Standard D-Bus property Set() method, see D-Bus standard.
665
prop = self._get_dbus_property(interface_name, property_name)
666
if prop._dbus_access == "read":
667
raise DBusPropertyAccessException(property_name)
668
if prop._dbus_get_args_options["byte_arrays"]:
669
# The byte_arrays option is not supported yet on
670
# signatures other than "ay".
671
if prop._dbus_signature != "ay":
673
value = dbus.ByteArray(''.join(unichr(byte)
677
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
678
out_signature="a{sv}")
679
def GetAll(self, interface_name):
680
"""Standard D-Bus property GetAll() method, see D-Bus
683
Note: Will not include properties with access="write".
686
for name, prop in self._get_all_dbus_properties():
688
and interface_name != prop._dbus_interface):
689
# Interface non-empty but did not match
691
# Ignore write-only properties
692
if prop._dbus_access == "write":
695
if not hasattr(value, "variant_level"):
698
all[name] = type(value)(value, variant_level=
699
value.variant_level+1)
700
return dbus.Dictionary(all, signature="sv")
702
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
704
path_keyword='object_path',
705
connection_keyword='connection')
706
def Introspect(self, object_path, connection):
707
"""Standard D-Bus method, overloaded to insert property tags.
709
xmlstring = dbus.service.Object.Introspect(self, object_path,
712
document = xml.dom.minidom.parseString(xmlstring)
713
def make_tag(document, name, prop):
714
e = document.createElement("property")
715
e.setAttribute("name", name)
716
e.setAttribute("type", prop._dbus_signature)
717
e.setAttribute("access", prop._dbus_access)
719
for if_tag in document.getElementsByTagName("interface"):
720
for tag in (make_tag(document, name, prop)
722
in self._get_all_dbus_properties()
723
if prop._dbus_interface
724
== if_tag.getAttribute("name")):
725
if_tag.appendChild(tag)
726
# Add the names to the return values for the
727
# "org.freedesktop.DBus.Properties" methods
728
if (if_tag.getAttribute("name")
729
== "org.freedesktop.DBus.Properties"):
730
for cn in if_tag.getElementsByTagName("method"):
731
if cn.getAttribute("name") == "Get":
732
for arg in cn.getElementsByTagName("arg"):
733
if (arg.getAttribute("direction")
735
arg.setAttribute("name", "value")
736
elif cn.getAttribute("name") == "GetAll":
737
for arg in cn.getElementsByTagName("arg"):
738
if (arg.getAttribute("direction")
740
arg.setAttribute("name", "props")
741
xmlstring = document.toxml("utf-8")
743
except (AttributeError, xml.dom.DOMException,
744
xml.parsers.expat.ExpatError) as error:
745
logger.error("Failed to override Introspection method",
750
def datetime_to_dbus (dt, variant_level=0):
751
"""Convert a UTC datetime.datetime() to a D-Bus type."""
753
return dbus.String("", variant_level = variant_level)
754
return dbus.String(dt.isoformat(),
755
variant_level=variant_level)
757
class AlternateDBusNamesMetaclass(DBusObjectWithProperties.__metaclass__):
758
"""Applied to an empty subclass of a D-Bus object, this metaclass
759
will add additional D-Bus attributes matching a certain pattern.
761
def __new__(mcs, name, bases, attr):
762
# Go through all the base classes which could have D-Bus
763
# methods, signals, or properties in them
764
for base in (b for b in bases
765
if issubclass(b, dbus.service.Object)):
766
# Go though all attributes of the base class
767
for attrname, attribute in inspect.getmembers(base):
768
# Ignore non-D-Bus attributes, and D-Bus attributes
769
# with the wrong interface name
770
if (not hasattr(attribute, "_dbus_interface")
771
or not attribute._dbus_interface
772
.startswith("se.recompile.Mandos")):
774
# Create an alternate D-Bus interface name based on
776
alt_interface = (attribute._dbus_interface
777
.replace("se.recompile.Mandos",
778
"se.bsnet.fukt.Mandos"))
779
# Is this a D-Bus signal?
780
if getattr(attribute, "_dbus_is_signal", False):
781
# Extract the original non-method function by
783
nonmethod_func = (dict(
784
zip(attribute.func_code.co_freevars,
785
attribute.__closure__))["func"]
787
# Create a new, but exactly alike, function
788
# object, and decorate it to be a new D-Bus signal
789
# with the alternate D-Bus interface name
790
new_function = (dbus.service.signal
792
attribute._dbus_signature)
794
nonmethod_func.func_code,
795
nonmethod_func.func_globals,
796
nonmethod_func.func_name,
797
nonmethod_func.func_defaults,
798
nonmethod_func.func_closure)))
799
# Define a creator of a function to call both the
800
# old and new functions, so both the old and new
801
# signals gets sent when the function is called
802
def fixscope(func1, func2):
803
"""This function is a scope container to pass
804
func1 and func2 to the "call_both" function
805
outside of its arguments"""
806
def call_both(*args, **kwargs):
807
"""This function will emit two D-Bus
808
signals by calling func1 and func2"""
809
func1(*args, **kwargs)
810
func2(*args, **kwargs)
812
# Create the "call_both" function and add it to
814
attr[attrname] = fixscope(attribute,
816
# Is this a D-Bus method?
817
elif getattr(attribute, "_dbus_is_method", False):
818
# Create a new, but exactly alike, function
819
# object. Decorate it to be a new D-Bus method
820
# with the alternate D-Bus interface name. Add it
822
attr[attrname] = (dbus.service.method
824
attribute._dbus_in_signature,
825
attribute._dbus_out_signature)
827
(attribute.func_code,
828
attribute.func_globals,
830
attribute.func_defaults,
831
attribute.func_closure)))
832
# Is this a D-Bus property?
833
elif getattr(attribute, "_dbus_is_property", False):
834
# Create a new, but exactly alike, function
835
# object, and decorate it to be a new D-Bus
836
# property with the alternate D-Bus interface
837
# name. Add it to the class.
838
attr[attrname] = (dbus_service_property
840
attribute._dbus_signature,
841
attribute._dbus_access,
843
._dbus_get_args_options
846
(attribute.func_code,
847
attribute.func_globals,
849
attribute.func_defaults,
850
attribute.func_closure)))
851
return type.__new__(mcs, name, bases, attr)
853
class ClientDBus(Client, DBusObjectWithProperties):
854
"""A Client class using D-Bus
857
dbus_object_path: dbus.ObjectPath
858
bus: dbus.SystemBus()
861
runtime_expansions = (Client.runtime_expansions
862
+ ("dbus_object_path",))
864
# dbus.service.Object doesn't use super(), so we can't either.
866
def __init__(self, bus = None, *args, **kwargs):
867
self._approvals_pending = 0
869
Client.__init__(self, *args, **kwargs)
870
# Only now, when this client is initialized, can it show up on
872
client_object_name = unicode(self.name).translate(
875
self.dbus_object_path = (dbus.ObjectPath
876
("/clients/" + client_object_name))
877
DBusObjectWithProperties.__init__(self, self.bus,
878
self.dbus_object_path)
880
def notifychangeproperty(transform_func,
881
dbus_name, type_func=lambda x: x,
883
""" Modify a variable so that it's a property which announces
886
transform_fun: Function that takes a value and transforms it
888
dbus_name: D-Bus name of the variable
889
type_func: Function that transform the value before sending it
890
to the D-Bus. Default: no transform
891
variant_level: D-Bus variant level. Default: 1
894
def setter(self, value):
895
old_value = real_value[0]
896
real_value[0] = value
897
if hasattr(self, "dbus_object_path"):
898
if type_func(old_value) != type_func(real_value[0]):
899
dbus_value = transform_func(type_func(real_value[0]),
901
self.PropertyChanged(dbus.String(dbus_name),
904
return property(lambda self: real_value[0], setter)
907
expires = notifychangeproperty(datetime_to_dbus, "Expires")
908
approvals_pending = notifychangeproperty(dbus.Boolean,
911
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
912
last_enabled = notifychangeproperty(datetime_to_dbus,
914
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
915
type_func = lambda checker: checker is not None)
916
last_checked_ok = notifychangeproperty(datetime_to_dbus,
918
last_approval_request = notifychangeproperty(datetime_to_dbus,
919
"LastApprovalRequest")
920
approved_by_default = notifychangeproperty(dbus.Boolean,
922
approval_delay = notifychangeproperty(dbus.UInt16, "ApprovalDelay",
923
type_func = _timedelta_to_milliseconds)
924
approval_duration = notifychangeproperty(dbus.UInt16, "ApprovalDuration",
925
type_func = _timedelta_to_milliseconds)
926
host = notifychangeproperty(dbus.String, "Host")
927
timeout = notifychangeproperty(dbus.UInt16, "Timeout",
928
type_func = _timedelta_to_milliseconds)
929
extended_timeout = notifychangeproperty(dbus.UInt16, "ExtendedTimeout",
930
type_func = _timedelta_to_milliseconds)
931
interval = notifychangeproperty(dbus.UInt16, "Interval",
932
type_func = _timedelta_to_milliseconds)
933
checker_command = notifychangeproperty(dbus.String, "Checker")
935
del notifychangeproperty
937
def __del__(self, *args, **kwargs):
939
self.remove_from_connection()
942
if hasattr(DBusObjectWithProperties, "__del__"):
943
DBusObjectWithProperties.__del__(self, *args, **kwargs)
944
Client.__del__(self, *args, **kwargs)
946
def checker_callback(self, pid, condition, command,
948
self.checker_callback_tag = None
950
if os.WIFEXITED(condition):
951
exitstatus = os.WEXITSTATUS(condition)
953
self.CheckerCompleted(dbus.Int16(exitstatus),
954
dbus.Int64(condition),
955
dbus.String(command))
958
self.CheckerCompleted(dbus.Int16(-1),
959
dbus.Int64(condition),
960
dbus.String(command))
962
return Client.checker_callback(self, pid, condition, command,
965
def start_checker(self, *args, **kwargs):
966
old_checker = self.checker
967
if self.checker is not None:
968
old_checker_pid = self.checker.pid
970
old_checker_pid = None
971
r = Client.start_checker(self, *args, **kwargs)
972
# Only if new checker process was started
973
if (self.checker is not None
974
and old_checker_pid != self.checker.pid):
976
self.CheckerStarted(self.current_checker_command)
979
def _reset_approved(self):
980
self._approved = None
983
def approve(self, value=True):
984
self.send_changedstate()
985
self._approved = value
986
gobject.timeout_add(_timedelta_to_milliseconds
987
(self.approval_duration),
988
self._reset_approved)
991
## D-Bus methods, signals & properties
992
_interface = "se.recompile.Mandos.Client"
996
# CheckerCompleted - signal
997
@dbus.service.signal(_interface, signature="nxs")
998
def CheckerCompleted(self, exitcode, waitstatus, command):
1002
# CheckerStarted - signal
1003
@dbus.service.signal(_interface, signature="s")
1004
def CheckerStarted(self, command):
1008
# PropertyChanged - signal
1009
@dbus.service.signal(_interface, signature="sv")
1010
def PropertyChanged(self, property, value):
1014
# GotSecret - signal
1015
@dbus.service.signal(_interface)
1016
def GotSecret(self):
1018
Is sent after a successful transfer of secret from the Mandos
1019
server to mandos-client
1024
@dbus.service.signal(_interface, signature="s")
1025
def Rejected(self, reason):
1029
# NeedApproval - signal
1030
@dbus.service.signal(_interface, signature="tb")
1031
def NeedApproval(self, timeout, default):
1033
return self.need_approval()
1038
@dbus.service.method(_interface, in_signature="b")
1039
def Approve(self, value):
1042
# CheckedOK - method
1043
@dbus.service.method(_interface)
1044
def CheckedOK(self):
1048
@dbus.service.method(_interface)
1053
# StartChecker - method
1054
@dbus.service.method(_interface)
1055
def StartChecker(self):
1057
self.start_checker()
1060
@dbus.service.method(_interface)
1065
# StopChecker - method
1066
@dbus.service.method(_interface)
1067
def StopChecker(self):
1072
# ApprovalPending - property
1073
@dbus_service_property(_interface, signature="b", access="read")
1074
def ApprovalPending_dbus_property(self):
1075
return dbus.Boolean(bool(self.approvals_pending))
1077
# ApprovedByDefault - property
1078
@dbus_service_property(_interface, signature="b",
1080
def ApprovedByDefault_dbus_property(self, value=None):
1081
if value is None: # get
1082
return dbus.Boolean(self.approved_by_default)
1083
self.approved_by_default = bool(value)
1085
# ApprovalDelay - property
1086
@dbus_service_property(_interface, signature="t",
1088
def ApprovalDelay_dbus_property(self, value=None):
1089
if value is None: # get
1090
return dbus.UInt64(self.approval_delay_milliseconds())
1091
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1093
# ApprovalDuration - property
1094
@dbus_service_property(_interface, signature="t",
1096
def ApprovalDuration_dbus_property(self, value=None):
1097
if value is None: # get
1098
return dbus.UInt64(_timedelta_to_milliseconds(
1099
self.approval_duration))
1100
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1103
@dbus_service_property(_interface, signature="s", access="read")
1104
def Name_dbus_property(self):
1105
return dbus.String(self.name)
1107
# Fingerprint - property
1108
@dbus_service_property(_interface, signature="s", access="read")
1109
def Fingerprint_dbus_property(self):
1110
return dbus.String(self.fingerprint)
1113
@dbus_service_property(_interface, signature="s",
1115
def Host_dbus_property(self, value=None):
1116
if value is None: # get
1117
return dbus.String(self.host)
1120
# Created - property
1121
@dbus_service_property(_interface, signature="s", access="read")
1122
def Created_dbus_property(self):
1123
return dbus.String(datetime_to_dbus(self.created))
1125
# LastEnabled - property
1126
@dbus_service_property(_interface, signature="s", access="read")
1127
def LastEnabled_dbus_property(self):
1128
return datetime_to_dbus(self.last_enabled)
1130
# Enabled - property
1131
@dbus_service_property(_interface, signature="b",
1133
def Enabled_dbus_property(self, value=None):
1134
if value is None: # get
1135
return dbus.Boolean(self.enabled)
1141
# LastCheckedOK - property
1142
@dbus_service_property(_interface, signature="s",
1144
def LastCheckedOK_dbus_property(self, value=None):
1145
if value is not None:
1148
return datetime_to_dbus(self.last_checked_ok)
1150
# Expires - property
1151
@dbus_service_property(_interface, signature="s", access="read")
1152
def Expires_dbus_property(self):
1153
return datetime_to_dbus(self.expires)
1155
# LastApprovalRequest - property
1156
@dbus_service_property(_interface, signature="s", access="read")
1157
def LastApprovalRequest_dbus_property(self):
1158
return datetime_to_dbus(self.last_approval_request)
1160
# Timeout - property
1161
@dbus_service_property(_interface, signature="t",
1163
def Timeout_dbus_property(self, value=None):
1164
if value is None: # get
1165
return dbus.UInt64(self.timeout_milliseconds())
1166
self.timeout = datetime.timedelta(0, 0, 0, value)
1167
if getattr(self, "disable_initiator_tag", None) is None:
1169
# Reschedule timeout
1170
gobject.source_remove(self.disable_initiator_tag)
1171
self.disable_initiator_tag = None
1173
time_to_die = (self.
1174
_timedelta_to_milliseconds((self
1179
if time_to_die <= 0:
1180
# The timeout has passed
1183
self.expires = (datetime.datetime.utcnow()
1184
+ datetime.timedelta(milliseconds = time_to_die))
1185
self.disable_initiator_tag = (gobject.timeout_add
1186
(time_to_die, self.disable))
1188
# ExtendedTimeout - property
1189
@dbus_service_property(_interface, signature="t",
1191
def ExtendedTimeout_dbus_property(self, value=None):
1192
if value is None: # get
1193
return dbus.UInt64(self.extended_timeout_milliseconds())
1194
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1196
# Interval - property
1197
@dbus_service_property(_interface, signature="t",
1199
def Interval_dbus_property(self, value=None):
1200
if value is None: # get
1201
return dbus.UInt64(self.interval_milliseconds())
1202
self.interval = datetime.timedelta(0, 0, 0, value)
1203
if getattr(self, "checker_initiator_tag", None) is None:
1205
# Reschedule checker run
1206
gobject.source_remove(self.checker_initiator_tag)
1207
self.checker_initiator_tag = (gobject.timeout_add
1208
(value, self.start_checker))
1209
self.start_checker() # Start one now, too
1211
# Checker - property
1212
@dbus_service_property(_interface, signature="s",
1214
def Checker_dbus_property(self, value=None):
1215
if value is None: # get
1216
return dbus.String(self.checker_command)
1217
self.checker_command = value
1219
# CheckerRunning - property
1220
@dbus_service_property(_interface, signature="b",
1222
def CheckerRunning_dbus_property(self, value=None):
1223
if value is None: # get
1224
return dbus.Boolean(self.checker is not None)
1226
self.start_checker()
1230
# ObjectPath - property
1231
@dbus_service_property(_interface, signature="o", access="read")
1232
def ObjectPath_dbus_property(self):
1233
return self.dbus_object_path # is already a dbus.ObjectPath
1236
@dbus_service_property(_interface, signature="ay",
1237
access="write", byte_arrays=True)
1238
def Secret_dbus_property(self, value):
1239
self.secret = str(value)
1244
class ProxyClient(object):
1245
def __init__(self, child_pipe, fpr, address):
1246
self._pipe = child_pipe
1247
self._pipe.send(('init', fpr, address))
1248
if not self._pipe.recv():
1251
def __getattribute__(self, name):
1252
if(name == '_pipe'):
1253
return super(ProxyClient, self).__getattribute__(name)
1254
self._pipe.send(('getattr', name))
1255
data = self._pipe.recv()
1256
if data[0] == 'data':
1258
if data[0] == 'function':
1259
def func(*args, **kwargs):
1260
self._pipe.send(('funcall', name, args, kwargs))
1261
return self._pipe.recv()[1]
1264
def __setattr__(self, name, value):
1265
if(name == '_pipe'):
1266
return super(ProxyClient, self).__setattr__(name, value)
1267
self._pipe.send(('setattr', name, value))
1269
class ClientDBusTransitional(ClientDBus):
1270
__metaclass__ = AlternateDBusNamesMetaclass
1272
class ClientHandler(socketserver.BaseRequestHandler, object):
1273
"""A class to handle client connections.
1275
Instantiated once for each connection to handle it.
368
def still_valid(self):
369
"""Has the timeout not yet passed for this client?"""
370
now = datetime.datetime.now()
371
if self.last_checked_ok is None:
372
return now < (self.created + self.timeout)
374
return now < (self.last_checked_ok + self.timeout)
377
def peer_certificate(session):
378
"Return the peer's OpenPGP certificate as a bytestring"
379
# If not an OpenPGP certificate...
380
if gnutls.library.functions.gnutls_certificate_type_get\
381
(session._c_object) \
382
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
383
# ...do the normal thing
384
return session.peer_certificate
385
list_size = ctypes.c_uint()
386
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
387
(session._c_object, ctypes.byref(list_size))
388
if list_size.value == 0:
391
return ctypes.string_at(cert.data, cert.size)
394
def fingerprint(openpgp):
395
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
396
# New GnuTLS "datum" with the OpenPGP public key
397
datum = gnutls.library.types.gnutls_datum_t\
398
(ctypes.cast(ctypes.c_char_p(openpgp),
399
ctypes.POINTER(ctypes.c_ubyte)),
400
ctypes.c_uint(len(openpgp)))
401
# New empty GnuTLS certificate
402
crt = gnutls.library.types.gnutls_openpgp_crt_t()
403
gnutls.library.functions.gnutls_openpgp_crt_init\
405
# Import the OpenPGP public key into the certificate
406
gnutls.library.functions.gnutls_openpgp_crt_import\
407
(crt, ctypes.byref(datum),
408
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
409
# New buffer for the fingerprint
410
buffer = ctypes.create_string_buffer(20)
411
buffer_length = ctypes.c_size_t()
412
# Get the fingerprint from the certificate into the buffer
413
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
414
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
415
# Deinit the certificate
416
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
417
# Convert the buffer to a Python bytestring
418
fpr = ctypes.string_at(buffer, buffer_length.value)
419
# Convert the bytestring to hexadecimal notation
420
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
424
class tcp_handler(SocketServer.BaseRequestHandler, object):
425
"""A TCP request handler class.
426
Instantiated by IPv6_TCPServer for each request to handle it.
1276
427
Note: This will run in its own forked process."""
1278
429
def handle(self):
1279
with contextlib.closing(self.server.child_pipe) as child_pipe:
1280
logger.info("TCP connection from: %s",
1281
unicode(self.client_address))
1282
logger.debug("Pipe FD: %d",
1283
self.server.child_pipe.fileno())
1285
session = (gnutls.connection
1286
.ClientSession(self.request,
1288
.X509Credentials()))
1290
# Note: gnutls.connection.X509Credentials is really a
1291
# generic GnuTLS certificate credentials object so long as
1292
# no X.509 keys are added to it. Therefore, we can use it
1293
# here despite using OpenPGP certificates.
1295
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1296
# "+AES-256-CBC", "+SHA1",
1297
# "+COMP-NULL", "+CTYPE-OPENPGP",
1299
# Use a fallback default, since this MUST be set.
1300
priority = self.server.gnutls_priority
1301
if priority is None:
1303
(gnutls.library.functions
1304
.gnutls_priority_set_direct(session._c_object,
1307
# Start communication using the Mandos protocol
1308
# Get protocol number
1309
line = self.request.makefile().readline()
1310
logger.debug("Protocol version: %r", line)
1312
if int(line.strip().split()[0]) > 1:
1314
except (ValueError, IndexError, RuntimeError) as error:
1315
logger.error("Unknown protocol version: %s", error)
1318
# Start GnuTLS connection
1321
except gnutls.errors.GNUTLSError as error:
1322
logger.warning("Handshake failed: %s", error)
1323
# Do not run session.bye() here: the session is not
1324
# established. Just abandon the request.
1326
logger.debug("Handshake succeeded")
1328
approval_required = False
1331
fpr = self.fingerprint(self.peer_certificate
1334
gnutls.errors.GNUTLSError) as error:
1335
logger.warning("Bad certificate: %s", error)
1337
logger.debug("Fingerprint: %s", fpr)
1340
client = ProxyClient(child_pipe, fpr,
1341
self.client_address)
1345
if client.approval_delay:
1346
delay = client.approval_delay
1347
client.approvals_pending += 1
1348
approval_required = True
1351
if not client.enabled:
1352
logger.info("Client %s is disabled",
1354
if self.server.use_dbus:
1356
client.Rejected("Disabled")
1359
if client._approved or not client.approval_delay:
1360
#We are approved or approval is disabled
1362
elif client._approved is None:
1363
logger.info("Client %s needs approval",
1365
if self.server.use_dbus:
1367
client.NeedApproval(
1368
client.approval_delay_milliseconds(),
1369
client.approved_by_default)
1371
logger.warning("Client %s was not approved",
1373
if self.server.use_dbus:
1375
client.Rejected("Denied")
1378
#wait until timeout or approved
1379
#x = float(client._timedelta_to_milliseconds(delay))
1380
time = datetime.datetime.now()
1381
client.changedstate.acquire()
1382
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1383
client.changedstate.release()
1384
time2 = datetime.datetime.now()
1385
if (time2 - time) >= delay:
1386
if not client.approved_by_default:
1387
logger.warning("Client %s timed out while"
1388
" waiting for approval",
1390
if self.server.use_dbus:
1392
client.Rejected("Approval timed out")
1397
delay -= time2 - time
1400
while sent_size < len(client.secret):
1402
sent = session.send(client.secret[sent_size:])
1403
except gnutls.errors.GNUTLSError as error:
1404
logger.warning("gnutls send failed")
1406
logger.debug("Sent: %d, remaining: %d",
1407
sent, len(client.secret)
1408
- (sent_size + sent))
1411
logger.info("Sending secret to %s", client.name)
1412
# bump the timeout as if seen
1413
client.checked_ok(client.extended_timeout)
1414
if self.server.use_dbus:
1419
if approval_required:
1420
client.approvals_pending -= 1
1423
except gnutls.errors.GNUTLSError as error:
1424
logger.warning("GnuTLS bye failed")
1427
def peer_certificate(session):
1428
"Return the peer's OpenPGP certificate as a bytestring"
1429
# If not an OpenPGP certificate...
1430
if (gnutls.library.functions
1431
.gnutls_certificate_type_get(session._c_object)
1432
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1433
# ...do the normal thing
1434
return session.peer_certificate
1435
list_size = ctypes.c_uint(1)
1436
cert_list = (gnutls.library.functions
1437
.gnutls_certificate_get_peers
1438
(session._c_object, ctypes.byref(list_size)))
1439
if not bool(cert_list) and list_size.value != 0:
1440
raise gnutls.errors.GNUTLSError("error getting peer"
1442
if list_size.value == 0:
1445
return ctypes.string_at(cert.data, cert.size)
1448
def fingerprint(openpgp):
1449
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1450
# New GnuTLS "datum" with the OpenPGP public key
1451
datum = (gnutls.library.types
1452
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1455
ctypes.c_uint(len(openpgp))))
1456
# New empty GnuTLS certificate
1457
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1458
(gnutls.library.functions
1459
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1460
# Import the OpenPGP public key into the certificate
1461
(gnutls.library.functions
1462
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1463
gnutls.library.constants
1464
.GNUTLS_OPENPGP_FMT_RAW))
1465
# Verify the self signature in the key
1466
crtverify = ctypes.c_uint()
1467
(gnutls.library.functions
1468
.gnutls_openpgp_crt_verify_self(crt, 0,
1469
ctypes.byref(crtverify)))
1470
if crtverify.value != 0:
1471
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1472
raise (gnutls.errors.CertificateSecurityError
1474
# New buffer for the fingerprint
1475
buf = ctypes.create_string_buffer(20)
1476
buf_len = ctypes.c_size_t()
1477
# Get the fingerprint from the certificate into the buffer
1478
(gnutls.library.functions
1479
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1480
ctypes.byref(buf_len)))
1481
# Deinit the certificate
1482
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1483
# Convert the buffer to a Python bytestring
1484
fpr = ctypes.string_at(buf, buf_len.value)
1485
# Convert the bytestring to hexadecimal notation
1486
hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1490
class MultiprocessingMixIn(object):
1491
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1492
def sub_process_main(self, request, address):
1494
self.finish_request(request, address)
1496
self.handle_error(request, address)
1497
self.close_request(request)
1499
def process_request(self, request, address):
1500
"""Start a new process to process the request."""
1501
multiprocessing.Process(target = self.sub_process_main,
1502
args = (request, address)).start()
1505
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1506
""" adds a pipe to the MixIn """
1507
def process_request(self, request, client_address):
1508
"""Overrides and wraps the original process_request().
1510
This function creates a new pipe in self.pipe
1512
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1514
super(MultiprocessingMixInWithPipe,
1515
self).process_request(request, client_address)
1516
self.child_pipe.close()
1517
self.add_pipe(parent_pipe)
1519
def add_pipe(self, parent_pipe):
1520
"""Dummy function; override as necessary"""
1521
raise NotImplementedError
1524
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1525
socketserver.TCPServer, object):
1526
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
430
logger.info(u"TCP connection from: %s",
431
unicode(self.client_address))
432
session = gnutls.connection.ClientSession\
433
(self.request, gnutls.connection.X509Credentials())
435
line = self.request.makefile().readline()
436
logger.debug(u"Protocol version: %r", line)
438
if int(line.strip().split()[0]) > 1:
440
except (ValueError, IndexError, RuntimeError), error:
441
logger.error(u"Unknown protocol version: %s", error)
444
# Note: gnutls.connection.X509Credentials is really a generic
445
# GnuTLS certificate credentials object so long as no X.509
446
# keys are added to it. Therefore, we can use it here despite
447
# using OpenPGP certificates.
449
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
450
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
452
priority = "NORMAL" # Fallback default, since this
454
if self.server.settings["priority"]:
455
priority = self.server.settings["priority"]
456
gnutls.library.functions.gnutls_priority_set_direct\
457
(session._c_object, priority, None);
461
except gnutls.errors.GNUTLSError, error:
462
logger.warning(u"Handshake failed: %s", error)
463
# Do not run session.bye() here: the session is not
464
# established. Just abandon the request.
467
fpr = fingerprint(peer_certificate(session))
468
except (TypeError, gnutls.errors.GNUTLSError), error:
469
logger.warning(u"Bad certificate: %s", error)
472
logger.debug(u"Fingerprint: %s", fpr)
474
for c in self.server.clients:
475
if c.fingerprint == fpr:
479
logger.warning(u"Client not found for fingerprint: %s",
483
# Have to check if client.still_valid(), since it is possible
484
# that the client timed out while establishing the GnuTLS
486
if not client.still_valid():
487
logger.warning(u"Client %(name)s is invalid",
492
while sent_size < len(client.secret):
493
sent = session.send(client.secret[sent_size:])
494
logger.debug(u"Sent: %d, remaining: %d",
495
sent, len(client.secret)
496
- (sent_size + sent))
501
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
502
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1529
enabled: Boolean; whether this server is activated yet
1530
interface: None or a network interface name (string)
1531
use_ipv6: Boolean; to use IPv6 or not
504
settings: Server settings
505
clients: Set() of Client objects
1533
def __init__(self, server_address, RequestHandlerClass,
1534
interface=None, use_ipv6=True):
1535
self.interface = interface
1537
self.address_family = socket.AF_INET6
1538
socketserver.TCPServer.__init__(self, server_address,
1539
RequestHandlerClass)
507
address_family = socket.AF_INET6
508
def __init__(self, *args, **kwargs):
509
if "settings" in kwargs:
510
self.settings = kwargs["settings"]
511
del kwargs["settings"]
512
if "clients" in kwargs:
513
self.clients = kwargs["clients"]
514
del kwargs["clients"]
515
return super(type(self), self).__init__(*args, **kwargs)
1540
516
def server_bind(self):
1541
517
"""This overrides the normal server_bind() function
1542
518
to bind to an interface if one was specified, and also NOT to
1543
519
bind to an address or port if they were not specified."""
1544
if self.interface is not None:
1545
if SO_BINDTODEVICE is None:
1546
logger.error("SO_BINDTODEVICE does not exist;"
1547
" cannot bind to interface %s",
1551
self.socket.setsockopt(socket.SOL_SOCKET,
1555
except socket.error as error:
1556
if error[0] == errno.EPERM:
1557
logger.error("No permission to"
1558
" bind to interface %s",
1560
elif error[0] == errno.ENOPROTOOPT:
1561
logger.error("SO_BINDTODEVICE not available;"
1562
" cannot bind to interface %s",
520
if self.settings["interface"]:
521
# 25 is from /usr/include/asm-i486/socket.h
522
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
524
self.socket.setsockopt(socket.SOL_SOCKET,
526
self.settings["interface"])
527
except socket.error, error:
528
if error[0] == errno.EPERM:
529
logger.error(u"No permission to"
530
u" bind to interface %s",
531
self.settings["interface"])
1566
534
# Only bind(2) the socket if we really need to.
1567
535
if self.server_address[0] or self.server_address[1]:
1568
536
if not self.server_address[0]:
1569
if self.address_family == socket.AF_INET6:
1570
any_address = "::" # in6addr_any
1572
any_address = socket.INADDR_ANY
1573
self.server_address = (any_address,
538
self.server_address = (in6addr_any,
1574
539
self.server_address[1])
1575
540
elif not self.server_address[1]:
1576
541
self.server_address = (self.server_address[0],
1578
# if self.interface:
543
# if self.settings["interface"]:
1579
544
# self.server_address = (self.server_address[0],
1582
547
# if_nametoindex
1584
return socketserver.TCPServer.server_bind(self)
1587
class MandosServer(IPv6_TCPServer):
1591
clients: set of Client objects
1592
gnutls_priority GnuTLS priority string
1593
use_dbus: Boolean; to emit D-Bus signals or not
1595
Assumes a gobject.MainLoop event loop.
1597
def __init__(self, server_address, RequestHandlerClass,
1598
interface=None, use_ipv6=True, clients=None,
1599
gnutls_priority=None, use_dbus=True):
1600
self.enabled = False
1601
self.clients = clients
1602
if self.clients is None:
1603
self.clients = set()
1604
self.use_dbus = use_dbus
1605
self.gnutls_priority = gnutls_priority
1606
IPv6_TCPServer.__init__(self, server_address,
1607
RequestHandlerClass,
1608
interface = interface,
1609
use_ipv6 = use_ipv6)
1610
def server_activate(self):
1612
return socketserver.TCPServer.server_activate(self)
1615
def add_pipe(self, parent_pipe):
1616
# Call "handle_ipc" for both data and EOF events
1617
gobject.io_add_watch(parent_pipe.fileno(),
1618
gobject.IO_IN | gobject.IO_HUP,
1619
functools.partial(self.handle_ipc,
1620
parent_pipe = parent_pipe))
1622
def handle_ipc(self, source, condition, parent_pipe=None,
1623
client_object=None):
1625
gobject.IO_IN: "IN", # There is data to read.
1626
gobject.IO_OUT: "OUT", # Data can be written (without
1628
gobject.IO_PRI: "PRI", # There is urgent data to read.
1629
gobject.IO_ERR: "ERR", # Error condition.
1630
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1631
# broken, usually for pipes and
1634
conditions_string = ' | '.join(name
1636
condition_names.iteritems()
1637
if cond & condition)
1638
# error or the other end of multiprocessing.Pipe has closed
1639
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1642
# Read a request from the child
1643
request = parent_pipe.recv()
1644
command = request[0]
1646
if command == 'init':
1648
address = request[2]
1650
for c in self.clients:
1651
if c.fingerprint == fpr:
1655
logger.info("Client not found for fingerprint: %s, ad"
1656
"dress: %s", fpr, address)
1659
mandos_dbus_service.ClientNotFound(fpr, address[0])
1660
parent_pipe.send(False)
1663
gobject.io_add_watch(parent_pipe.fileno(),
1664
gobject.IO_IN | gobject.IO_HUP,
1665
functools.partial(self.handle_ipc,
1666
parent_pipe = parent_pipe,
1667
client_object = client))
1668
parent_pipe.send(True)
1669
# remove the old hook in favor of the new above hook on same fileno
1671
if command == 'funcall':
1672
funcname = request[1]
1676
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1678
if command == 'getattr':
1679
attrname = request[1]
1680
if callable(client_object.__getattribute__(attrname)):
1681
parent_pipe.send(('function',))
1683
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1685
if command == 'setattr':
1686
attrname = request[1]
1688
setattr(client_object, attrname, value)
550
return super(type(self), self).server_bind()
1693
553
def string_to_delta(interval):
1694
554
"""Parse a string and return a datetime.timedelta
1696
556
>>> string_to_delta('7d')
1697
557
datetime.timedelta(7)
1698
558
>>> string_to_delta('60s')
1829
695
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1830
696
"servicename": "Mandos",
1836
699
# Parse config file for server-global settings
1837
server_config = configparser.SafeConfigParser(server_defaults)
700
server_config = ConfigParser.SafeConfigParser(server_defaults)
1838
701
del server_defaults
1839
server_config.read(os.path.join(options.configdir,
702
server_config.read(os.path.join(options.configdir, "mandos.conf"))
703
server_section = "server"
1841
704
# Convert the SafeConfigParser object to a dict
1842
server_settings = server_config.defaults()
1843
# Use the appropriate methods on the non-string config options
1844
for option in ("debug", "use_dbus", "use_ipv6"):
1845
server_settings[option] = server_config.getboolean("DEFAULT",
1847
if server_settings["port"]:
1848
server_settings["port"] = server_config.getint("DEFAULT",
705
server_settings = dict(server_config.items(server_section))
706
# Use getboolean on the boolean config option
707
server_settings["debug"] = server_config.getboolean\
708
(server_section, "debug")
1850
709
del server_config
1852
711
# Override the settings from the config file with command line
1853
712
# options, if set.
1854
713
for option in ("interface", "address", "port", "debug",
1855
"priority", "servicename", "configdir",
1856
"use_dbus", "use_ipv6", "debuglevel"):
714
"priority", "servicename", "configdir"):
1857
715
value = getattr(options, option)
1858
716
if value is not None:
1859
717
server_settings[option] = value
1861
# Force all strings to be unicode
1862
for option in server_settings.keys():
1863
if type(server_settings[option]) is str:
1864
server_settings[option] = unicode(server_settings[option])
1865
719
# Now we have our good server settings in "server_settings"
1867
##################################################################
1870
721
debug = server_settings["debug"]
1871
debuglevel = server_settings["debuglevel"]
1872
use_dbus = server_settings["use_dbus"]
1873
use_ipv6 = server_settings["use_ipv6"]
724
syslogger.setLevel(logging.WARNING)
725
console.setLevel(logging.WARNING)
1875
727
if server_settings["servicename"] != "Mandos":
1876
syslogger.setFormatter(logging.Formatter
1877
('Mandos (%s) [%%(process)d]:'
1878
' %%(levelname)s: %%(message)s'
728
syslogger.setFormatter(logging.Formatter\
729
('Mandos (%s): %%(levelname)s:'
1879
731
% server_settings["servicename"]))
1881
733
# Parse config file with clients
1882
client_defaults = { "timeout": "5m",
1883
"extended_timeout": "15m",
734
client_defaults = { "timeout": "1h",
1885
736
"checker": "fping -q -- %%(host)s",
1887
"approval_delay": "0s",
1888
"approval_duration": "1s",
1890
client_config = configparser.SafeConfigParser(client_defaults)
738
client_config = ConfigParser.SafeConfigParser(client_defaults)
1891
739
client_config.read(os.path.join(server_settings["configdir"],
1892
740
"clients.conf"))
1894
global mandos_dbus_service
1895
mandos_dbus_service = None
1897
tcp_server = MandosServer((server_settings["address"],
1898
server_settings["port"]),
1900
interface=(server_settings["interface"]
1904
server_settings["priority"],
1907
pidfilename = "/var/run/mandos.pid"
1909
pidfile = open(pidfilename, "w")
1911
logger.error("Could not open file %r", pidfilename)
1914
uid = pwd.getpwnam("_mandos").pw_uid
1915
gid = pwd.getpwnam("_mandos").pw_gid
1918
uid = pwd.getpwnam("mandos").pw_uid
1919
gid = pwd.getpwnam("mandos").pw_gid
1922
uid = pwd.getpwnam("nobody").pw_uid
1923
gid = pwd.getpwnam("nobody").pw_gid
1930
except OSError as error:
1931
if error[0] != errno.EPERM:
1934
if not debug and not debuglevel:
1935
syslogger.setLevel(logging.WARNING)
1936
console.setLevel(logging.WARNING)
1938
level = getattr(logging, debuglevel.upper())
1939
syslogger.setLevel(level)
1940
console.setLevel(level)
1943
# Enable all possible GnuTLS debugging
1945
# "Use a log level over 10 to enable all debugging options."
1947
gnutls.library.functions.gnutls_global_set_log_level(11)
1949
@gnutls.library.types.gnutls_log_func
1950
def debug_gnutls(level, string):
1951
logger.debug("GnuTLS: %s", string[:-1])
1953
(gnutls.library.functions
1954
.gnutls_global_set_log_function(debug_gnutls))
1956
# Redirect stdin so all checkers get /dev/null
1957
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1958
os.dup2(null, sys.stdin.fileno())
1962
# No console logging
1963
logger.removeHandler(console)
1965
# Need to fork before connecting to D-Bus
1967
# Close all input and output, do double fork, etc.
743
service = AvahiService(name = server_settings["servicename"],
744
type = "_mandos._tcp", );
745
if server_settings["interface"]:
746
service.interface = if_nametoindex(server_settings["interface"])
1970
748
global main_loop
1971
751
# From the Avahi example code
1972
752
DBusGMainLoop(set_as_default=True )
1973
753
main_loop = gobject.MainLoop()
1974
754
bus = dbus.SystemBus()
755
server = dbus.Interface(
756
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
757
avahi.DBUS_INTERFACE_SERVER )
1975
758
# End of Avahi example code
1978
bus_name = dbus.service.BusName("se.recompile.Mandos",
1979
bus, do_not_queue=True)
1980
old_bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
1981
bus, do_not_queue=True)
1982
except dbus.exceptions.NameExistsException as e:
1983
logger.error(unicode(e) + ", disabling D-Bus")
1985
server_settings["use_dbus"] = False
1986
tcp_server.use_dbus = False
1987
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1988
service = AvahiService(name = server_settings["servicename"],
1989
servicetype = "_mandos._tcp",
1990
protocol = protocol, bus = bus)
1991
if server_settings["interface"]:
1992
service.interface = (if_nametoindex
1993
(str(server_settings["interface"])))
1995
global multiprocessing_manager
1996
multiprocessing_manager = multiprocessing.Manager()
1998
client_class = Client
2000
client_class = functools.partial(ClientDBusTransitional, bus = bus)
2001
def client_config_items(config, section):
2002
special_settings = {
2003
"approved_by_default":
2004
lambda: config.getboolean(section,
2005
"approved_by_default"),
2007
for name, value in config.items(section):
2009
yield (name, special_settings[name]())
2013
tcp_server.clients.update(set(
2014
client_class(name = section,
2015
config= dict(client_config_items(
2016
client_config, section)))
2017
for section in client_config.sections()))
2018
if not tcp_server.clients:
2019
logger.warning("No clients defined")
2025
pidfile.write(str(pid) + "\n".encode("utf-8"))
2028
logger.error("Could not write to file %r with PID %d",
2031
# "pidfile" was never created
761
def remove_from_clients(client):
762
clients.remove(client)
764
logger.critical(u"No clients left, exiting")
767
clients.update(Set(Client(name = section,
768
stop_hook = remove_from_clients,
770
= dict(client_config.items(section)))
771
for section in client_config.sections()))
773
logger.critical(u"No clients defined")
777
logger.removeHandler(console)
780
pidfilename = "/var/run/mandos/mandos.pid"
783
pidfile = open(pidfilename, "w")
784
pidfile.write(str(pid) + "\n")
788
logger.error(u"Could not write %s file with PID %d",
789
pidfilename, os.getpid())
792
"Cleanup function; run on exit"
794
# From the Avahi example code
795
if not group is None:
798
# End of Avahi example code
801
client = clients.pop()
802
client.stop_hook = None
805
atexit.register(cleanup)
2035
808
signal.signal(signal.SIGINT, signal.SIG_IGN)
2037
809
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2038
810
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2041
class MandosDBusService(dbus.service.Object):
2042
"""A D-Bus proxy object"""
2044
dbus.service.Object.__init__(self, bus, "/")
2045
_interface = "se.recompile.Mandos"
2047
@dbus.service.signal(_interface, signature="o")
2048
def ClientAdded(self, objpath):
2052
@dbus.service.signal(_interface, signature="ss")
2053
def ClientNotFound(self, fingerprint, address):
2057
@dbus.service.signal(_interface, signature="os")
2058
def ClientRemoved(self, objpath, name):
2062
@dbus.service.method(_interface, out_signature="ao")
2063
def GetAllClients(self):
2065
return dbus.Array(c.dbus_object_path
2066
for c in tcp_server.clients)
2068
@dbus.service.method(_interface,
2069
out_signature="a{oa{sv}}")
2070
def GetAllClientsWithProperties(self):
2072
return dbus.Dictionary(
2073
((c.dbus_object_path, c.GetAll(""))
2074
for c in tcp_server.clients),
2077
@dbus.service.method(_interface, in_signature="o")
2078
def RemoveClient(self, object_path):
2080
for c in tcp_server.clients:
2081
if c.dbus_object_path == object_path:
2082
tcp_server.clients.remove(c)
2083
c.remove_from_connection()
2084
# Don't signal anything except ClientRemoved
2085
c.disable(quiet=True)
2087
self.ClientRemoved(object_path, c.name)
2089
raise KeyError(object_path)
2093
class MandosDBusServiceTransitional(MandosDBusService):
2094
__metaclass__ = AlternateDBusNamesMetaclass
2095
mandos_dbus_service = MandosDBusServiceTransitional()
2098
"Cleanup function; run on exit"
2101
while tcp_server.clients:
2102
client = tcp_server.clients.pop()
2104
client.remove_from_connection()
2105
client.disable_hook = None
2106
# Don't signal anything except ClientRemoved
2107
client.disable(quiet=True)
2110
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2113
atexit.register(cleanup)
2115
for client in tcp_server.clients:
2118
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2122
tcp_server.server_activate()
812
for client in clients:
815
tcp_server = IPv6_TCPServer((server_settings["address"],
816
server_settings["port"]),
818
settings=server_settings,
2124
820
# Find out what port we got
2125
821
service.port = tcp_server.socket.getsockname()[1]
2127
logger.info("Now listening on address %r, port %d,"
2128
" flowinfo %d, scope_id %d"
2129
% tcp_server.socket.getsockname())
2131
logger.info("Now listening on address %r, port %d"
2132
% tcp_server.socket.getsockname())
822
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
823
u" scope_id %d" % tcp_server.socket.getsockname())
2134
825
#service.interface = tcp_server.socket.getsockname()[3]
2137
828
# From the Avahi example code
829
server.connect_to_signal("StateChanged", server_state_changed)
2140
except dbus.exceptions.DBusException as error:
2141
logger.critical("DBusException: %s", error)
831
server_state_changed(server.GetState())
832
except dbus.exceptions.DBusException, error:
833
logger.critical(u"DBusException: %s", error)
2144
835
# End of Avahi example code
2146
837
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2147
838
lambda *args, **kwargs:
2148
(tcp_server.handle_request
2149
(*args[2:], **kwargs) or True))
839
tcp_server.handle_request\
840
(*args[2:], **kwargs) or True)
2151
logger.debug("Starting main loop")
842
logger.debug(u"Starting main loop")
843
main_loop_started = True
2153
except AvahiError as error:
2154
logger.critical("AvahiError: %s", error)
845
except AvahiError, error:
846
logger.critical(u"AvahiError: %s" + unicode(error))
2157
848
except KeyboardInterrupt:
2159
print("", file=sys.stderr)
2160
logger.debug("Server received KeyboardInterrupt")
2161
logger.debug("Server exiting")
2162
# Must run before the D-Bus bus name gets deregistered
2166
852
if __name__ == '__main__':