239
104
max_renames: integer; maximum number of renames
240
105
rename_count: integer; counter so we only rename after collisions
241
106
a sensible number of times
242
group: D-Bus Entry Group
244
bus: dbus.SystemBus()
247
108
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
248
servicetype = None, port = None, TXT = None,
249
domain = "", host = "", max_renames = 32768,
250
protocol = avahi.PROTO_UNSPEC, bus = None):
109
type = None, port = None, TXT = None, domain = "",
110
host = "", max_renames = 32768):
251
111
self.interface = interface
253
self.type = servicetype
255
self.TXT = TXT if TXT is not None else []
256
119
self.domain = domain
258
121
self.rename_count = 0
259
self.max_renames = max_renames
260
self.protocol = protocol
261
self.group = None # our entry group
264
self.entry_group_state_changed_match = None
266
122
def rename(self):
267
123
"""Derived from the Avahi example code"""
268
124
if self.rename_count >= self.max_renames:
269
logger.critical("No suitable Zeroconf service name found"
270
" after %i retries, exiting.",
125
logger.critical(u"No suitable service name found after %i"
126
u" retries, exiting.", rename_count)
272
127
raise AvahiServiceError("Too many renames")
273
self.name = unicode(self.server
274
.GetAlternativeServiceName(self.name))
275
logger.info("Changing Zeroconf service name to %r ...",
128
name = server.GetAlternativeServiceName(name)
129
logger.error(u"Changing name to %r ...", name)
130
syslogger.setFormatter(logging.Formatter\
131
('Mandos (%s): %%(levelname)s:'
132
' %%(message)s' % name))
280
except dbus.exceptions.DBusException as error:
281
logger.critical("D-Bus Exception", exc_info=error)
284
135
self.rename_count += 1
286
136
def remove(self):
287
137
"""Derived from the Avahi example code"""
288
if self.entry_group_state_changed_match is not None:
289
self.entry_group_state_changed_match.remove()
290
self.entry_group_state_changed_match = None
291
if self.group is not None:
138
if group is not None:
295
141
"""Derived from the Avahi example code"""
297
if self.group is None:
298
self.group = dbus.Interface(
299
self.bus.get_object(avahi.DBUS_NAME,
300
self.server.EntryGroupNew()),
301
avahi.DBUS_INTERFACE_ENTRY_GROUP)
302
self.entry_group_state_changed_match = (
303
self.group.connect_to_signal(
304
'StateChanged', self.entry_group_state_changed))
305
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
306
self.name, self.type)
307
self.group.AddService(
310
dbus.UInt32(0), # flags
311
self.name, self.type,
312
self.domain, self.host,
313
dbus.UInt16(self.port),
314
avahi.string_array_to_txt_array(self.TXT))
317
def entry_group_state_changed(self, state, error):
318
"""Derived from the Avahi example code"""
319
logger.debug("Avahi entry group state change: %i", state)
321
if state == avahi.ENTRY_GROUP_ESTABLISHED:
322
logger.debug("Zeroconf service established.")
323
elif state == avahi.ENTRY_GROUP_COLLISION:
324
logger.info("Zeroconf service name collision.")
326
elif state == avahi.ENTRY_GROUP_FAILURE:
327
logger.critical("Avahi: Error in group state changed %s",
329
raise AvahiGroupError("State changed: {0!s}"
333
"""Derived from the Avahi example code"""
334
if self.group is not None:
337
except (dbus.exceptions.UnknownMethodException,
338
dbus.exceptions.DBusException):
343
def server_state_changed(self, state, error=None):
344
"""Derived from the Avahi example code"""
345
logger.debug("Avahi server state change: %i", state)
346
bad_states = { avahi.SERVER_INVALID:
347
"Zeroconf server invalid",
348
avahi.SERVER_REGISTERING: None,
349
avahi.SERVER_COLLISION:
350
"Zeroconf server name collision",
351
avahi.SERVER_FAILURE:
352
"Zeroconf server failure" }
353
if state in bad_states:
354
if bad_states[state] is not None:
356
logger.error(bad_states[state])
358
logger.error(bad_states[state] + ": %r", error)
360
elif state == avahi.SERVER_RUNNING:
364
logger.debug("Unknown state: %r", state)
366
logger.debug("Unknown state: %r: %r", state, error)
369
"""Derived from the Avahi example code"""
370
if self.server is None:
371
self.server = dbus.Interface(
372
self.bus.get_object(avahi.DBUS_NAME,
373
avahi.DBUS_PATH_SERVER,
374
follow_name_owner_changes=True),
375
avahi.DBUS_INTERFACE_SERVER)
376
self.server.connect_to_signal("StateChanged",
377
self.server_state_changed)
378
self.server_state_changed(self.server.GetState())
380
class AvahiServiceToSyslog(AvahiService):
382
"""Add the new name to the syslog messages"""
383
ret = AvahiService.rename(self)
384
syslogger.setFormatter(logging.Formatter
385
('Mandos ({0}) [%(process)d]:'
386
' %(levelname)s: %(message)s'
390
def timedelta_to_milliseconds(td):
391
"Convert a datetime.timedelta() to milliseconds"
392
return ((td.days * 24 * 60 * 60 * 1000)
393
+ (td.seconds * 1000)
394
+ (td.microseconds // 1000))
144
group = dbus.Interface\
145
(bus.get_object(avahi.DBUS_NAME,
146
server.EntryGroupNew()),
147
avahi.DBUS_INTERFACE_ENTRY_GROUP)
148
group.connect_to_signal('StateChanged',
149
entry_group_state_changed)
150
logger.debug(u"Adding service '%s' of type '%s' ...",
151
service.name, service.type)
153
self.interface, # interface
154
avahi.PROTO_INET6, # protocol
155
dbus.UInt32(0), # flags
156
self.name, self.type,
157
self.domain, self.host,
158
dbus.UInt16(self.port),
159
avahi.string_array_to_txt_array(self.TXT))
162
# From the Avahi example code:
163
group = None # our entry group
164
# End of Avahi example code
396
167
class Client(object):
397
168
"""A representation of a client host served by this server.
400
approved: bool(); 'None' if not yet approved/disapproved
401
approval_delay: datetime.timedelta(); Time to wait for approval
402
approval_duration: datetime.timedelta(); Duration of one approval
403
checker: subprocess.Popen(); a running checker process used
404
to see if the client lives.
405
'None' if no process is running.
406
checker_callback_tag: a gobject event source tag, or None
407
checker_command: string; External command which is run to check
408
if client lives. %() expansions are done at
170
name: string; from the config file, used in log messages
171
fingerprint: string (40 or 32 hexadecimal digits); used to
172
uniquely identify the client
173
secret: bytestring; sent verbatim (over TLS) to client
174
host: string; available for use by the checker command
175
created: datetime.datetime(); object creation, not client host
176
last_checked_ok: datetime.datetime() or None if not yet checked OK
177
timeout: datetime.timedelta(); How long from last_checked_ok
178
until this client is invalid
179
interval: datetime.timedelta(); How often to start a new checker
180
stop_hook: If set, called by stop() as stop_hook(self)
181
checker: subprocess.Popen(); a running checker process used
182
to see if the client lives.
183
'None' if no process is running.
184
checker_initiator_tag: a gobject event source tag, or None
185
stop_initiator_tag: - '' -
186
checker_callback_tag: - '' -
187
checker_command: string; External command which is run to check if
188
client lives. %() expansions are done at
409
189
runtime with vars(self) as dict, so that for
410
190
instance %(name)s can be used in the command.
411
checker_initiator_tag: a gobject event source tag, or None
412
created: datetime.datetime(); (UTC) object creation
413
client_structure: Object describing what attributes a client has
414
and is used for storing the client at exit
415
current_checker_command: string; current running checker_command
416
disable_initiator_tag: a gobject event source tag, or None
418
fingerprint: string (40 or 32 hexadecimal digits); used to
419
uniquely identify the client
420
host: string; available for use by the checker command
421
interval: datetime.timedelta(); How often to start a new checker
422
last_approval_request: datetime.datetime(); (UTC) or None
423
last_checked_ok: datetime.datetime(); (UTC) or None
424
last_checker_status: integer between 0 and 255 reflecting exit
425
status of last checker. -1 reflects crashed
426
checker, -2 means no checker completed yet.
427
last_enabled: datetime.datetime(); (UTC) or None
428
name: string; from the config file, used in log messages and
430
secret: bytestring; sent verbatim (over TLS) to client
431
timeout: datetime.timedelta(); How long from last_checked_ok
432
until this client is disabled
433
extended_timeout: extra long timeout when secret has been sent
434
runtime_expansions: Allowed attributes for runtime expansion.
435
expires: datetime.datetime(); time (UTC) when a client will be
192
_timeout: Real variable for 'timeout'
193
_interval: Real variable for 'interval'
194
_timeout_milliseconds: Used when calling gobject.timeout_add()
195
_interval_milliseconds: - '' -
439
runtime_expansions = ("approval_delay", "approval_duration",
440
"created", "enabled", "fingerprint",
441
"host", "interval", "last_checked_ok",
442
"last_enabled", "name", "timeout")
443
client_defaults = { "timeout": "5m",
444
"extended_timeout": "15m",
446
"checker": "fping -q -- %%(host)s",
448
"approval_delay": "0s",
449
"approval_duration": "1s",
450
"approved_by_default": "True",
454
def timeout_milliseconds(self):
455
"Return the 'timeout' attribute in milliseconds"
456
return timedelta_to_milliseconds(self.timeout)
458
def extended_timeout_milliseconds(self):
459
"Return the 'extended_timeout' attribute in milliseconds"
460
return timedelta_to_milliseconds(self.extended_timeout)
462
def interval_milliseconds(self):
463
"Return the 'interval' attribute in milliseconds"
464
return timedelta_to_milliseconds(self.interval)
466
def approval_delay_milliseconds(self):
467
return timedelta_to_milliseconds(self.approval_delay)
470
def config_parser(config):
471
"""Construct a new dict of client settings of this form:
472
{ client_name: {setting_name: value, ...}, ...}
473
with exceptions for any special settings as defined above.
474
NOTE: Must be a pure function. Must return the same result
475
value given the same arguments.
478
for client_name in config.sections():
479
section = dict(config.items(client_name))
480
client = settings[client_name] = {}
482
client["host"] = section["host"]
483
# Reformat values from string types to Python types
484
client["approved_by_default"] = config.getboolean(
485
client_name, "approved_by_default")
486
client["enabled"] = config.getboolean(client_name,
489
client["fingerprint"] = (section["fingerprint"].upper()
491
if "secret" in section:
492
client["secret"] = section["secret"].decode("base64")
493
elif "secfile" in section:
494
with open(os.path.expanduser(os.path.expandvars
495
(section["secfile"])),
497
client["secret"] = secfile.read()
499
raise TypeError("No secret or secfile for section {0}"
501
client["timeout"] = string_to_delta(section["timeout"])
502
client["extended_timeout"] = string_to_delta(
503
section["extended_timeout"])
504
client["interval"] = string_to_delta(section["interval"])
505
client["approval_delay"] = string_to_delta(
506
section["approval_delay"])
507
client["approval_duration"] = string_to_delta(
508
section["approval_duration"])
509
client["checker_command"] = section["checker"]
510
client["last_approval_request"] = None
511
client["last_checked_ok"] = None
512
client["last_checker_status"] = -2
516
def __init__(self, settings, name = None):
197
def _set_timeout(self, timeout):
198
"Setter function for 'timeout' attribute"
199
self._timeout = timeout
200
self._timeout_milliseconds = ((self.timeout.days
201
* 24 * 60 * 60 * 1000)
202
+ (self.timeout.seconds * 1000)
203
+ (self.timeout.microseconds
205
timeout = property(lambda self: self._timeout,
208
def _set_interval(self, interval):
209
"Setter function for 'interval' attribute"
210
self._interval = interval
211
self._interval_milliseconds = ((self.interval.days
212
* 24 * 60 * 60 * 1000)
213
+ (self.interval.seconds
215
+ (self.interval.microseconds
217
interval = property(lambda self: self._interval,
220
def __init__(self, name = None, stop_hook=None, config={}):
221
"""Note: the 'checker' key in 'config' sets the
222
'checker_command' attribute and *not* the 'checker'
518
# adding all client settings
519
for setting, value in settings.iteritems():
520
setattr(self, setting, value)
523
if not hasattr(self, "last_enabled"):
524
self.last_enabled = datetime.datetime.utcnow()
525
if not hasattr(self, "expires"):
526
self.expires = (datetime.datetime.utcnow()
529
self.last_enabled = None
532
logger.debug("Creating client %r", self.name)
225
logger.debug(u"Creating client %r", self.name)
533
226
# Uppercase and remove spaces from fingerprint for later
534
227
# comparison purposes with return value from the fingerprint()
536
logger.debug(" Fingerprint: %s", self.fingerprint)
537
self.created = settings.get("created",
538
datetime.datetime.utcnow())
540
# attributes specific for this server instance
229
self.fingerprint = config["fingerprint"].upper()\
231
logger.debug(u" Fingerprint: %s", self.fingerprint)
232
if "secret" in config:
233
self.secret = config["secret"].decode(u"base64")
234
elif "secfile" in config:
235
sf = open(config["secfile"])
236
self.secret = sf.read()
239
raise TypeError(u"No secret or secfile for client %s"
241
self.host = config.get("host", "")
242
self.created = datetime.datetime.now()
243
self.last_checked_ok = None
244
self.timeout = string_to_delta(config["timeout"])
245
self.interval = string_to_delta(config["interval"])
246
self.stop_hook = stop_hook
541
247
self.checker = None
542
248
self.checker_initiator_tag = None
543
self.disable_initiator_tag = None
249
self.stop_initiator_tag = None
544
250
self.checker_callback_tag = None
545
self.current_checker_command = None
547
self.approvals_pending = 0
548
self.changedstate = (multiprocessing_manager
549
.Condition(multiprocessing_manager
551
self.client_structure = [attr for attr in
552
self.__dict__.iterkeys()
553
if not attr.startswith("_")]
554
self.client_structure.append("client_structure")
556
for name, t in inspect.getmembers(type(self),
560
if not name.startswith("_"):
561
self.client_structure.append(name)
563
# Send notice to process children that client state has changed
564
def send_changedstate(self):
565
with self.changedstate:
566
self.changedstate.notify_all()
251
self.check_command = config["checker"]
569
253
"""Start this client's checker and timeout hooks"""
570
if getattr(self, "enabled", False):
573
self.send_changedstate()
574
self.expires = datetime.datetime.utcnow() + self.timeout
576
self.last_enabled = datetime.datetime.utcnow()
579
def disable(self, quiet=True):
580
"""Disable this client."""
581
if not getattr(self, "enabled", False):
254
# Schedule a new checker to be started an 'interval' from now,
255
# and every interval from then on.
256
self.checker_initiator_tag = gobject.timeout_add\
257
(self._interval_milliseconds,
259
# Also start a new checker *right now*.
261
# Schedule a stop() when 'timeout' has passed
262
self.stop_initiator_tag = gobject.timeout_add\
263
(self._timeout_milliseconds,
267
The possibility that a client might be restarted is left open,
268
but not currently used."""
269
# If this client doesn't have a secret, it is already stopped.
270
if hasattr(self, "secret") and self.secret:
271
logger.info(u"Stopping client %s", self.name)
584
self.send_changedstate()
586
logger.info("Disabling client %s", self.name)
587
if getattr(self, "disable_initiator_tag", False):
588
gobject.source_remove(self.disable_initiator_tag)
589
self.disable_initiator_tag = None
275
if getattr(self, "stop_initiator_tag", False):
276
gobject.source_remove(self.stop_initiator_tag)
277
self.stop_initiator_tag = None
591
278
if getattr(self, "checker_initiator_tag", False):
592
279
gobject.source_remove(self.checker_initiator_tag)
593
280
self.checker_initiator_tag = None
594
281
self.stop_checker()
596
284
# Do not run this again if called by a gobject.timeout_add
599
286
def __del__(self):
602
def init_checker(self):
603
# Schedule a new checker to be started an 'interval' from now,
604
# and every interval from then on.
605
self.checker_initiator_tag = (gobject.timeout_add
606
(self.interval_milliseconds(),
608
# Schedule a disable() when 'timeout' has passed
609
self.disable_initiator_tag = (gobject.timeout_add
610
(self.timeout_milliseconds(),
612
# Also start a new checker *right now*.
615
def checker_callback(self, pid, condition, command):
287
self.stop_hook = None
289
def checker_callback(self, pid, condition):
616
290
"""The checker has completed, so take appropriate actions."""
291
now = datetime.datetime.now()
617
292
self.checker_callback_tag = None
618
293
self.checker = None
619
if os.WIFEXITED(condition):
620
self.last_checker_status = os.WEXITSTATUS(condition)
621
if self.last_checker_status == 0:
622
logger.info("Checker for %(name)s succeeded",
626
logger.info("Checker for %(name)s failed",
629
self.last_checker_status = -1
630
logger.warning("Checker for %(name)s crashed?",
294
if os.WIFEXITED(condition) \
295
and (os.WEXITSTATUS(condition) == 0):
296
logger.info(u"Checker for %(name)s succeeded",
298
self.last_checked_ok = now
299
gobject.source_remove(self.stop_initiator_tag)
300
self.stop_initiator_tag = gobject.timeout_add\
301
(self._timeout_milliseconds,
303
elif not os.WIFEXITED(condition):
304
logger.warning(u"Checker for %(name)s crashed?",
633
def checked_ok(self):
634
"""Assert that the client has been seen, alive and well."""
635
self.last_checked_ok = datetime.datetime.utcnow()
636
self.last_checker_status = 0
639
def bump_timeout(self, timeout=None):
640
"""Bump up the timeout for this client."""
642
timeout = self.timeout
643
if self.disable_initiator_tag is not None:
644
gobject.source_remove(self.disable_initiator_tag)
645
if getattr(self, "enabled", False):
646
self.disable_initiator_tag = (gobject.timeout_add
647
(timedelta_to_milliseconds
648
(timeout), self.disable))
649
self.expires = datetime.datetime.utcnow() + timeout
651
def need_approval(self):
652
self.last_approval_request = datetime.datetime.utcnow()
307
logger.info(u"Checker for %(name)s failed",
654
309
def start_checker(self):
655
310
"""Start a new checker subprocess if one is not running.
657
311
If a checker already exists, leave it running and do
659
313
# The reason for not killing a running checker is that if we
733
354
self.checker_callback_tag = None
734
355
if getattr(self, "checker", None) is None:
736
logger.debug("Stopping checker for %(name)s", vars(self))
357
logger.debug(u"Stopping checker for %(name)s", vars(self))
738
self.checker.terminate()
359
os.kill(self.checker.pid, signal.SIGTERM)
740
361
#if self.checker.poll() is None:
741
# self.checker.kill()
742
except OSError as error:
362
# os.kill(self.checker.pid, signal.SIGKILL)
363
except OSError, error:
743
364
if error.errno != errno.ESRCH: # No such process
745
366
self.checker = None
748
def dbus_service_property(dbus_interface, signature="v",
749
access="readwrite", byte_arrays=False):
750
"""Decorators for marking methods of a DBusObjectWithProperties to
751
become properties on the D-Bus.
753
The decorated method will be called with no arguments by "Get"
754
and with one argument by "Set".
756
The parameters, where they are supported, are the same as
757
dbus.service.method, except there is only "signature", since the
758
type from Get() and the type sent to Set() is the same.
760
# Encoding deeply encoded byte arrays is not supported yet by the
761
# "Set" method, so we fail early here:
762
if byte_arrays and signature != "ay":
763
raise ValueError("Byte arrays not supported for non-'ay'"
764
" signature {0!r}".format(signature))
766
func._dbus_is_property = True
767
func._dbus_interface = dbus_interface
768
func._dbus_signature = signature
769
func._dbus_access = access
770
func._dbus_name = func.__name__
771
if func._dbus_name.endswith("_dbus_property"):
772
func._dbus_name = func._dbus_name[:-14]
773
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
778
def dbus_interface_annotations(dbus_interface):
779
"""Decorator for marking functions returning interface annotations
783
@dbus_interface_annotations("org.example.Interface")
784
def _foo(self): # Function name does not matter
785
return {"org.freedesktop.DBus.Deprecated": "true",
786
"org.freedesktop.DBus.Property.EmitsChangedSignal":
790
func._dbus_is_interface = True
791
func._dbus_interface = dbus_interface
792
func._dbus_name = dbus_interface
797
def dbus_annotations(annotations):
798
"""Decorator to annotate D-Bus methods, signals or properties
801
@dbus_service_property("org.example.Interface", signature="b",
803
@dbus_annotations({{"org.freedesktop.DBus.Deprecated": "true",
804
"org.freedesktop.DBus.Property."
805
"EmitsChangedSignal": "false"})
806
def Property_dbus_property(self):
807
return dbus.Boolean(False)
810
func._dbus_annotations = annotations
815
class DBusPropertyException(dbus.exceptions.DBusException):
816
"""A base class for D-Bus property-related exceptions
818
def __unicode__(self):
819
return unicode(str(self))
822
class DBusPropertyAccessException(DBusPropertyException):
823
"""A property's access permissions disallows an operation.
828
class DBusPropertyNotFound(DBusPropertyException):
829
"""An attempt was made to access a non-existing property.
834
class DBusObjectWithProperties(dbus.service.Object):
835
"""A D-Bus object with properties.
837
Classes inheriting from this can use the dbus_service_property
838
decorator to expose methods as D-Bus properties. It exposes the
839
standard Get(), Set(), and GetAll() methods on the D-Bus.
843
def _is_dbus_thing(thing):
844
"""Returns a function testing if an attribute is a D-Bus thing
846
If called like _is_dbus_thing("method") it returns a function
847
suitable for use as predicate to inspect.getmembers().
849
return lambda obj: getattr(obj, "_dbus_is_{0}".format(thing),
852
def _get_all_dbus_things(self, thing):
853
"""Returns a generator of (name, attribute) pairs
855
return ((getattr(athing.__get__(self), "_dbus_name",
857
athing.__get__(self))
858
for cls in self.__class__.__mro__
860
inspect.getmembers(cls,
861
self._is_dbus_thing(thing)))
863
def _get_dbus_property(self, interface_name, property_name):
864
"""Returns a bound method if one exists which is a D-Bus
865
property with the specified name and interface.
867
for cls in self.__class__.__mro__:
868
for name, value in (inspect.getmembers
870
self._is_dbus_thing("property"))):
871
if (value._dbus_name == property_name
872
and value._dbus_interface == interface_name):
873
return value.__get__(self)
876
raise DBusPropertyNotFound(self.dbus_object_path + ":"
877
+ interface_name + "."
880
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
882
def Get(self, interface_name, property_name):
883
"""Standard D-Bus property Get() method, see D-Bus standard.
885
prop = self._get_dbus_property(interface_name, property_name)
886
if prop._dbus_access == "write":
887
raise DBusPropertyAccessException(property_name)
889
if not hasattr(value, "variant_level"):
891
return type(value)(value, variant_level=value.variant_level+1)
893
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
894
def Set(self, interface_name, property_name, value):
895
"""Standard D-Bus property Set() method, see D-Bus standard.
897
prop = self._get_dbus_property(interface_name, property_name)
898
if prop._dbus_access == "read":
899
raise DBusPropertyAccessException(property_name)
900
if prop._dbus_get_args_options["byte_arrays"]:
901
# The byte_arrays option is not supported yet on
902
# signatures other than "ay".
903
if prop._dbus_signature != "ay":
905
value = dbus.ByteArray(b''.join(chr(byte)
909
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
910
out_signature="a{sv}")
911
def GetAll(self, interface_name):
912
"""Standard D-Bus property GetAll() method, see D-Bus
915
Note: Will not include properties with access="write".
918
for name, prop in self._get_all_dbus_things("property"):
920
and interface_name != prop._dbus_interface):
921
# Interface non-empty but did not match
923
# Ignore write-only properties
924
if prop._dbus_access == "write":
927
if not hasattr(value, "variant_level"):
928
properties[name] = value
930
properties[name] = type(value)(value, variant_level=
931
value.variant_level+1)
932
return dbus.Dictionary(properties, signature="sv")
934
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
936
path_keyword='object_path',
937
connection_keyword='connection')
938
def Introspect(self, object_path, connection):
939
"""Overloading of standard D-Bus method.
941
Inserts property tags and interface annotation tags.
943
xmlstring = dbus.service.Object.Introspect(self, object_path,
946
document = xml.dom.minidom.parseString(xmlstring)
947
def make_tag(document, name, prop):
948
e = document.createElement("property")
949
e.setAttribute("name", name)
950
e.setAttribute("type", prop._dbus_signature)
951
e.setAttribute("access", prop._dbus_access)
953
for if_tag in document.getElementsByTagName("interface"):
955
for tag in (make_tag(document, name, prop)
957
in self._get_all_dbus_things("property")
958
if prop._dbus_interface
959
== if_tag.getAttribute("name")):
960
if_tag.appendChild(tag)
961
# Add annotation tags
962
for typ in ("method", "signal", "property"):
963
for tag in if_tag.getElementsByTagName(typ):
965
for name, prop in (self.
966
_get_all_dbus_things(typ)):
967
if (name == tag.getAttribute("name")
968
and prop._dbus_interface
969
== if_tag.getAttribute("name")):
970
annots.update(getattr
974
for name, value in annots.iteritems():
975
ann_tag = document.createElement(
977
ann_tag.setAttribute("name", name)
978
ann_tag.setAttribute("value", value)
979
tag.appendChild(ann_tag)
980
# Add interface annotation tags
981
for annotation, value in dict(
983
*(annotations().iteritems()
984
for name, annotations in
985
self._get_all_dbus_things("interface")
986
if name == if_tag.getAttribute("name")
988
ann_tag = document.createElement("annotation")
989
ann_tag.setAttribute("name", annotation)
990
ann_tag.setAttribute("value", value)
991
if_tag.appendChild(ann_tag)
992
# Add the names to the return values for the
993
# "org.freedesktop.DBus.Properties" methods
994
if (if_tag.getAttribute("name")
995
== "org.freedesktop.DBus.Properties"):
996
for cn in if_tag.getElementsByTagName("method"):
997
if cn.getAttribute("name") == "Get":
998
for arg in cn.getElementsByTagName("arg"):
999
if (arg.getAttribute("direction")
1001
arg.setAttribute("name", "value")
1002
elif cn.getAttribute("name") == "GetAll":
1003
for arg in cn.getElementsByTagName("arg"):
1004
if (arg.getAttribute("direction")
1006
arg.setAttribute("name", "props")
1007
xmlstring = document.toxml("utf-8")
1009
except (AttributeError, xml.dom.DOMException,
1010
xml.parsers.expat.ExpatError) as error:
1011
logger.error("Failed to override Introspection method",
1016
def datetime_to_dbus (dt, variant_level=0):
1017
"""Convert a UTC datetime.datetime() to a D-Bus type."""
1019
return dbus.String("", variant_level = variant_level)
1020
return dbus.String(dt.isoformat(),
1021
variant_level=variant_level)
1024
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
1026
"""Applied to an empty subclass of a D-Bus object, this metaclass
1027
will add additional D-Bus attributes matching a certain pattern.
1029
def __new__(mcs, name, bases, attr):
1030
# Go through all the base classes which could have D-Bus
1031
# methods, signals, or properties in them
1032
old_interface_names = []
1033
for base in (b for b in bases
1034
if issubclass(b, dbus.service.Object)):
1035
# Go though all attributes of the base class
1036
for attrname, attribute in inspect.getmembers(base):
1037
# Ignore non-D-Bus attributes, and D-Bus attributes
1038
# with the wrong interface name
1039
if (not hasattr(attribute, "_dbus_interface")
1040
or not attribute._dbus_interface
1041
.startswith("se.recompile.Mandos")):
1043
# Create an alternate D-Bus interface name based on
1045
alt_interface = (attribute._dbus_interface
1046
.replace("se.recompile.Mandos",
1047
"se.bsnet.fukt.Mandos"))
1048
if alt_interface != attribute._dbus_interface:
1049
old_interface_names.append(alt_interface)
1050
# Is this a D-Bus signal?
1051
if getattr(attribute, "_dbus_is_signal", False):
1052
# Extract the original non-method function by
1054
nonmethod_func = (dict(
1055
zip(attribute.func_code.co_freevars,
1056
attribute.__closure__))["func"]
1058
# Create a new, but exactly alike, function
1059
# object, and decorate it to be a new D-Bus signal
1060
# with the alternate D-Bus interface name
1061
new_function = (dbus.service.signal
1063
attribute._dbus_signature)
1064
(types.FunctionType(
1065
nonmethod_func.func_code,
1066
nonmethod_func.func_globals,
1067
nonmethod_func.func_name,
1068
nonmethod_func.func_defaults,
1069
nonmethod_func.func_closure)))
1070
# Copy annotations, if any
1072
new_function._dbus_annotations = (
1073
dict(attribute._dbus_annotations))
1074
except AttributeError:
1076
# Define a creator of a function to call both the
1077
# old and new functions, so both the old and new
1078
# signals gets sent when the function is called
1079
def fixscope(func1, func2):
1080
"""This function is a scope container to pass
1081
func1 and func2 to the "call_both" function
1082
outside of its arguments"""
1083
def call_both(*args, **kwargs):
1084
"""This function will emit two D-Bus
1085
signals by calling func1 and func2"""
1086
func1(*args, **kwargs)
1087
func2(*args, **kwargs)
1089
# Create the "call_both" function and add it to
1091
attr[attrname] = fixscope(attribute,
1093
# Is this a D-Bus method?
1094
elif getattr(attribute, "_dbus_is_method", False):
1095
# Create a new, but exactly alike, function
1096
# object. Decorate it to be a new D-Bus method
1097
# with the alternate D-Bus interface name. Add it
1099
attr[attrname] = (dbus.service.method
1101
attribute._dbus_in_signature,
1102
attribute._dbus_out_signature)
1104
(attribute.func_code,
1105
attribute.func_globals,
1106
attribute.func_name,
1107
attribute.func_defaults,
1108
attribute.func_closure)))
1109
# Copy annotations, if any
1111
attr[attrname]._dbus_annotations = (
1112
dict(attribute._dbus_annotations))
1113
except AttributeError:
1115
# Is this a D-Bus property?
1116
elif getattr(attribute, "_dbus_is_property", False):
1117
# Create a new, but exactly alike, function
1118
# object, and decorate it to be a new D-Bus
1119
# property with the alternate D-Bus interface
1120
# name. Add it to the class.
1121
attr[attrname] = (dbus_service_property
1123
attribute._dbus_signature,
1124
attribute._dbus_access,
1126
._dbus_get_args_options
1129
(attribute.func_code,
1130
attribute.func_globals,
1131
attribute.func_name,
1132
attribute.func_defaults,
1133
attribute.func_closure)))
1134
# Copy annotations, if any
1136
attr[attrname]._dbus_annotations = (
1137
dict(attribute._dbus_annotations))
1138
except AttributeError:
1140
# Is this a D-Bus interface?
1141
elif getattr(attribute, "_dbus_is_interface", False):
1142
# Create a new, but exactly alike, function
1143
# object. Decorate it to be a new D-Bus interface
1144
# with the alternate D-Bus interface name. Add it
1146
attr[attrname] = (dbus_interface_annotations
1149
(attribute.func_code,
1150
attribute.func_globals,
1151
attribute.func_name,
1152
attribute.func_defaults,
1153
attribute.func_closure)))
1154
# Deprecate all old interfaces
1155
iname="_AlternateDBusNamesMetaclass_interface_annotation{0}"
1156
for old_interface_name in old_interface_names:
1157
@dbus_interface_annotations(old_interface_name)
1159
return { "org.freedesktop.DBus.Deprecated": "true" }
1160
# Find an unused name
1161
for aname in (iname.format(i) for i in itertools.count()):
1162
if aname not in attr:
1165
return type.__new__(mcs, name, bases, attr)
1168
class ClientDBus(Client, DBusObjectWithProperties):
1169
"""A Client class using D-Bus
1172
dbus_object_path: dbus.ObjectPath
1173
bus: dbus.SystemBus()
1176
runtime_expansions = (Client.runtime_expansions
1177
+ ("dbus_object_path",))
1179
# dbus.service.Object doesn't use super(), so we can't either.
1181
def __init__(self, bus = None, *args, **kwargs):
1183
Client.__init__(self, *args, **kwargs)
1184
# Only now, when this client is initialized, can it show up on
1186
client_object_name = unicode(self.name).translate(
1187
{ord("."): ord("_"),
1188
ord("-"): ord("_")})
1189
self.dbus_object_path = (dbus.ObjectPath
1190
("/clients/" + client_object_name))
1191
DBusObjectWithProperties.__init__(self, self.bus,
1192
self.dbus_object_path)
1194
def notifychangeproperty(transform_func,
1195
dbus_name, type_func=lambda x: x,
1197
""" Modify a variable so that it's a property which announces
1198
its changes to DBus.
1200
transform_fun: Function that takes a value and a variant_level
1201
and transforms it to a D-Bus type.
1202
dbus_name: D-Bus name of the variable
1203
type_func: Function that transform the value before sending it
1204
to the D-Bus. Default: no transform
1205
variant_level: D-Bus variant level. Default: 1
1207
attrname = "_{0}".format(dbus_name)
1208
def setter(self, value):
1209
if hasattr(self, "dbus_object_path"):
1210
if (not hasattr(self, attrname) or
1211
type_func(getattr(self, attrname, None))
1212
!= type_func(value)):
1213
dbus_value = transform_func(type_func(value),
1216
self.PropertyChanged(dbus.String(dbus_name),
1218
setattr(self, attrname, value)
1220
return property(lambda self: getattr(self, attrname), setter)
1222
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1223
approvals_pending = notifychangeproperty(dbus.Boolean,
1226
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1227
last_enabled = notifychangeproperty(datetime_to_dbus,
1229
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1230
type_func = lambda checker:
1231
checker is not None)
1232
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1234
last_checker_status = notifychangeproperty(dbus.Int16,
1235
"LastCheckerStatus")
1236
last_approval_request = notifychangeproperty(
1237
datetime_to_dbus, "LastApprovalRequest")
1238
approved_by_default = notifychangeproperty(dbus.Boolean,
1239
"ApprovedByDefault")
1240
approval_delay = notifychangeproperty(dbus.UInt64,
1243
timedelta_to_milliseconds)
1244
approval_duration = notifychangeproperty(
1245
dbus.UInt64, "ApprovalDuration",
1246
type_func = timedelta_to_milliseconds)
1247
host = notifychangeproperty(dbus.String, "Host")
1248
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1250
timedelta_to_milliseconds)
1251
extended_timeout = notifychangeproperty(
1252
dbus.UInt64, "ExtendedTimeout",
1253
type_func = timedelta_to_milliseconds)
1254
interval = notifychangeproperty(dbus.UInt64,
1257
timedelta_to_milliseconds)
1258
checker_command = notifychangeproperty(dbus.String, "Checker")
1260
del notifychangeproperty
1262
def __del__(self, *args, **kwargs):
1264
self.remove_from_connection()
1267
if hasattr(DBusObjectWithProperties, "__del__"):
1268
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1269
Client.__del__(self, *args, **kwargs)
1271
def checker_callback(self, pid, condition, command,
1273
self.checker_callback_tag = None
1275
if os.WIFEXITED(condition):
1276
exitstatus = os.WEXITSTATUS(condition)
1278
self.CheckerCompleted(dbus.Int16(exitstatus),
1279
dbus.Int64(condition),
1280
dbus.String(command))
1283
self.CheckerCompleted(dbus.Int16(-1),
1284
dbus.Int64(condition),
1285
dbus.String(command))
1287
return Client.checker_callback(self, pid, condition, command,
1290
def start_checker(self, *args, **kwargs):
1291
old_checker = self.checker
1292
if self.checker is not None:
1293
old_checker_pid = self.checker.pid
1295
old_checker_pid = None
1296
r = Client.start_checker(self, *args, **kwargs)
1297
# Only if new checker process was started
1298
if (self.checker is not None
1299
and old_checker_pid != self.checker.pid):
1301
self.CheckerStarted(self.current_checker_command)
1304
def _reset_approved(self):
1305
self.approved = None
1308
def approve(self, value=True):
1309
self.send_changedstate()
1310
self.approved = value
1311
gobject.timeout_add(timedelta_to_milliseconds
1312
(self.approval_duration),
1313
self._reset_approved)
1315
## D-Bus methods, signals & properties
1316
_interface = "se.recompile.Mandos.Client"
1320
@dbus_interface_annotations(_interface)
1322
return { "org.freedesktop.DBus.Property.EmitsChangedSignal":
1327
# CheckerCompleted - signal
1328
@dbus.service.signal(_interface, signature="nxs")
1329
def CheckerCompleted(self, exitcode, waitstatus, command):
1333
# CheckerStarted - signal
1334
@dbus.service.signal(_interface, signature="s")
1335
def CheckerStarted(self, command):
1339
# PropertyChanged - signal
1340
@dbus.service.signal(_interface, signature="sv")
1341
def PropertyChanged(self, property, value):
1345
# GotSecret - signal
1346
@dbus.service.signal(_interface)
1347
def GotSecret(self):
1349
Is sent after a successful transfer of secret from the Mandos
1350
server to mandos-client
1355
@dbus.service.signal(_interface, signature="s")
1356
def Rejected(self, reason):
1360
# NeedApproval - signal
1361
@dbus.service.signal(_interface, signature="tb")
1362
def NeedApproval(self, timeout, default):
1364
return self.need_approval()
1369
@dbus.service.method(_interface, in_signature="b")
1370
def Approve(self, value):
1373
# CheckedOK - method
1374
@dbus.service.method(_interface)
1375
def CheckedOK(self):
1379
@dbus.service.method(_interface)
1384
# StartChecker - method
1385
@dbus.service.method(_interface)
1386
def StartChecker(self):
1388
self.start_checker()
1391
@dbus.service.method(_interface)
1396
# StopChecker - method
1397
@dbus.service.method(_interface)
1398
def StopChecker(self):
1403
# ApprovalPending - property
1404
@dbus_service_property(_interface, signature="b", access="read")
1405
def ApprovalPending_dbus_property(self):
1406
return dbus.Boolean(bool(self.approvals_pending))
1408
# ApprovedByDefault - property
1409
@dbus_service_property(_interface, signature="b",
1411
def ApprovedByDefault_dbus_property(self, value=None):
1412
if value is None: # get
1413
return dbus.Boolean(self.approved_by_default)
1414
self.approved_by_default = bool(value)
1416
# ApprovalDelay - property
1417
@dbus_service_property(_interface, signature="t",
1419
def ApprovalDelay_dbus_property(self, value=None):
1420
if value is None: # get
1421
return dbus.UInt64(self.approval_delay_milliseconds())
1422
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1424
# ApprovalDuration - property
1425
@dbus_service_property(_interface, signature="t",
1427
def ApprovalDuration_dbus_property(self, value=None):
1428
if value is None: # get
1429
return dbus.UInt64(timedelta_to_milliseconds(
1430
self.approval_duration))
1431
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1434
@dbus_service_property(_interface, signature="s", access="read")
1435
def Name_dbus_property(self):
1436
return dbus.String(self.name)
1438
# Fingerprint - property
1439
@dbus_service_property(_interface, signature="s", access="read")
1440
def Fingerprint_dbus_property(self):
1441
return dbus.String(self.fingerprint)
1444
@dbus_service_property(_interface, signature="s",
1446
def Host_dbus_property(self, value=None):
1447
if value is None: # get
1448
return dbus.String(self.host)
1449
self.host = unicode(value)
1451
# Created - property
1452
@dbus_service_property(_interface, signature="s", access="read")
1453
def Created_dbus_property(self):
1454
return datetime_to_dbus(self.created)
1456
# LastEnabled - property
1457
@dbus_service_property(_interface, signature="s", access="read")
1458
def LastEnabled_dbus_property(self):
1459
return datetime_to_dbus(self.last_enabled)
1461
# Enabled - property
1462
@dbus_service_property(_interface, signature="b",
1464
def Enabled_dbus_property(self, value=None):
1465
if value is None: # get
1466
return dbus.Boolean(self.enabled)
1472
# LastCheckedOK - property
1473
@dbus_service_property(_interface, signature="s",
1475
def LastCheckedOK_dbus_property(self, value=None):
1476
if value is not None:
1479
return datetime_to_dbus(self.last_checked_ok)
1481
# LastCheckerStatus - property
1482
@dbus_service_property(_interface, signature="n",
1484
def LastCheckerStatus_dbus_property(self):
1485
return dbus.Int16(self.last_checker_status)
1487
# Expires - property
1488
@dbus_service_property(_interface, signature="s", access="read")
1489
def Expires_dbus_property(self):
1490
return datetime_to_dbus(self.expires)
1492
# LastApprovalRequest - property
1493
@dbus_service_property(_interface, signature="s", access="read")
1494
def LastApprovalRequest_dbus_property(self):
1495
return datetime_to_dbus(self.last_approval_request)
1497
# Timeout - property
1498
@dbus_service_property(_interface, signature="t",
1500
def Timeout_dbus_property(self, value=None):
1501
if value is None: # get
1502
return dbus.UInt64(self.timeout_milliseconds())
1503
self.timeout = datetime.timedelta(0, 0, 0, value)
1504
# Reschedule timeout
1506
now = datetime.datetime.utcnow()
1507
time_to_die = timedelta_to_milliseconds(
1508
(self.last_checked_ok + self.timeout) - now)
1509
if time_to_die <= 0:
1510
# The timeout has passed
1513
self.expires = (now +
1514
datetime.timedelta(milliseconds =
1516
if (getattr(self, "disable_initiator_tag", None)
1519
gobject.source_remove(self.disable_initiator_tag)
1520
self.disable_initiator_tag = (gobject.timeout_add
1524
# ExtendedTimeout - property
1525
@dbus_service_property(_interface, signature="t",
1527
def ExtendedTimeout_dbus_property(self, value=None):
1528
if value is None: # get
1529
return dbus.UInt64(self.extended_timeout_milliseconds())
1530
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1532
# Interval - property
1533
@dbus_service_property(_interface, signature="t",
1535
def Interval_dbus_property(self, value=None):
1536
if value is None: # get
1537
return dbus.UInt64(self.interval_milliseconds())
1538
self.interval = datetime.timedelta(0, 0, 0, value)
1539
if getattr(self, "checker_initiator_tag", None) is None:
1542
# Reschedule checker run
1543
gobject.source_remove(self.checker_initiator_tag)
1544
self.checker_initiator_tag = (gobject.timeout_add
1545
(value, self.start_checker))
1546
self.start_checker() # Start one now, too
1548
# Checker - property
1549
@dbus_service_property(_interface, signature="s",
1551
def Checker_dbus_property(self, value=None):
1552
if value is None: # get
1553
return dbus.String(self.checker_command)
1554
self.checker_command = unicode(value)
1556
# CheckerRunning - property
1557
@dbus_service_property(_interface, signature="b",
1559
def CheckerRunning_dbus_property(self, value=None):
1560
if value is None: # get
1561
return dbus.Boolean(self.checker is not None)
1563
self.start_checker()
1567
# ObjectPath - property
1568
@dbus_service_property(_interface, signature="o", access="read")
1569
def ObjectPath_dbus_property(self):
1570
return self.dbus_object_path # is already a dbus.ObjectPath
1573
@dbus_service_property(_interface, signature="ay",
1574
access="write", byte_arrays=True)
1575
def Secret_dbus_property(self, value):
1576
self.secret = str(value)
1581
class ProxyClient(object):
1582
def __init__(self, child_pipe, fpr, address):
1583
self._pipe = child_pipe
1584
self._pipe.send(('init', fpr, address))
1585
if not self._pipe.recv():
1588
def __getattribute__(self, name):
1590
return super(ProxyClient, self).__getattribute__(name)
1591
self._pipe.send(('getattr', name))
1592
data = self._pipe.recv()
1593
if data[0] == 'data':
1595
if data[0] == 'function':
1596
def func(*args, **kwargs):
1597
self._pipe.send(('funcall', name, args, kwargs))
1598
return self._pipe.recv()[1]
1601
def __setattr__(self, name, value):
1603
return super(ProxyClient, self).__setattr__(name, value)
1604
self._pipe.send(('setattr', name, value))
1607
class ClientDBusTransitional(ClientDBus):
1608
__metaclass__ = AlternateDBusNamesMetaclass
1611
class ClientHandler(socketserver.BaseRequestHandler, object):
1612
"""A class to handle client connections.
1614
Instantiated once for each connection to handle it.
367
def still_valid(self):
368
"""Has the timeout not yet passed for this client?"""
369
now = datetime.datetime.now()
370
if self.last_checked_ok is None:
371
return now < (self.created + self.timeout)
373
return now < (self.last_checked_ok + self.timeout)
376
def peer_certificate(session):
377
"Return the peer's OpenPGP certificate as a bytestring"
378
# If not an OpenPGP certificate...
379
if gnutls.library.functions.gnutls_certificate_type_get\
380
(session._c_object) \
381
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
382
# ...do the normal thing
383
return session.peer_certificate
384
list_size = ctypes.c_uint()
385
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
386
(session._c_object, ctypes.byref(list_size))
387
if list_size.value == 0:
390
return ctypes.string_at(cert.data, cert.size)
393
def fingerprint(openpgp):
394
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
395
# New GnuTLS "datum" with the OpenPGP public key
396
datum = gnutls.library.types.gnutls_datum_t\
397
(ctypes.cast(ctypes.c_char_p(openpgp),
398
ctypes.POINTER(ctypes.c_ubyte)),
399
ctypes.c_uint(len(openpgp)))
400
# New empty GnuTLS certificate
401
crt = gnutls.library.types.gnutls_openpgp_crt_t()
402
gnutls.library.functions.gnutls_openpgp_crt_init\
404
# Import the OpenPGP public key into the certificate
405
gnutls.library.functions.gnutls_openpgp_crt_import\
406
(crt, ctypes.byref(datum),
407
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
408
# New buffer for the fingerprint
409
buffer = ctypes.create_string_buffer(20)
410
buffer_length = ctypes.c_size_t()
411
# Get the fingerprint from the certificate into the buffer
412
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
413
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
414
# Deinit the certificate
415
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
416
# Convert the buffer to a Python bytestring
417
fpr = ctypes.string_at(buffer, buffer_length.value)
418
# Convert the bytestring to hexadecimal notation
419
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
423
class tcp_handler(SocketServer.BaseRequestHandler, object):
424
"""A TCP request handler class.
425
Instantiated by IPv6_TCPServer for each request to handle it.
1615
426
Note: This will run in its own forked process."""
1617
428
def handle(self):
1618
with contextlib.closing(self.server.child_pipe) as child_pipe:
1619
logger.info("TCP connection from: %s",
1620
unicode(self.client_address))
1621
logger.debug("Pipe FD: %d",
1622
self.server.child_pipe.fileno())
1624
session = (gnutls.connection
1625
.ClientSession(self.request,
1627
.X509Credentials()))
1629
# Note: gnutls.connection.X509Credentials is really a
1630
# generic GnuTLS certificate credentials object so long as
1631
# no X.509 keys are added to it. Therefore, we can use it
1632
# here despite using OpenPGP certificates.
1634
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1635
# "+AES-256-CBC", "+SHA1",
1636
# "+COMP-NULL", "+CTYPE-OPENPGP",
1638
# Use a fallback default, since this MUST be set.
1639
priority = self.server.gnutls_priority
1640
if priority is None:
1642
(gnutls.library.functions
1643
.gnutls_priority_set_direct(session._c_object,
1646
# Start communication using the Mandos protocol
1647
# Get protocol number
1648
line = self.request.makefile().readline()
1649
logger.debug("Protocol version: %r", line)
1651
if int(line.strip().split()[0]) > 1:
1653
except (ValueError, IndexError, RuntimeError) as error:
1654
logger.error("Unknown protocol version: %s", error)
1657
# Start GnuTLS connection
1660
except gnutls.errors.GNUTLSError as error:
1661
logger.warning("Handshake failed: %s", error)
1662
# Do not run session.bye() here: the session is not
1663
# established. Just abandon the request.
1665
logger.debug("Handshake succeeded")
1667
approval_required = False
1670
fpr = self.fingerprint(self.peer_certificate
1673
gnutls.errors.GNUTLSError) as error:
1674
logger.warning("Bad certificate: %s", error)
1676
logger.debug("Fingerprint: %s", fpr)
1679
client = ProxyClient(child_pipe, fpr,
1680
self.client_address)
1684
if client.approval_delay:
1685
delay = client.approval_delay
1686
client.approvals_pending += 1
1687
approval_required = True
1690
if not client.enabled:
1691
logger.info("Client %s is disabled",
1693
if self.server.use_dbus:
1695
client.Rejected("Disabled")
1698
if client.approved or not client.approval_delay:
1699
#We are approved or approval is disabled
1701
elif client.approved is None:
1702
logger.info("Client %s needs approval",
1704
if self.server.use_dbus:
1706
client.NeedApproval(
1707
client.approval_delay_milliseconds(),
1708
client.approved_by_default)
1710
logger.warning("Client %s was not approved",
1712
if self.server.use_dbus:
1714
client.Rejected("Denied")
1717
#wait until timeout or approved
1718
time = datetime.datetime.now()
1719
client.changedstate.acquire()
1720
(client.changedstate.wait
1721
(float(client.timedelta_to_milliseconds(delay)
1723
client.changedstate.release()
1724
time2 = datetime.datetime.now()
1725
if (time2 - time) >= delay:
1726
if not client.approved_by_default:
1727
logger.warning("Client %s timed out while"
1728
" waiting for approval",
1730
if self.server.use_dbus:
1732
client.Rejected("Approval timed out")
1737
delay -= time2 - time
1740
while sent_size < len(client.secret):
1742
sent = session.send(client.secret[sent_size:])
1743
except gnutls.errors.GNUTLSError as error:
1744
logger.warning("gnutls send failed",
1747
logger.debug("Sent: %d, remaining: %d",
1748
sent, len(client.secret)
1749
- (sent_size + sent))
1752
logger.info("Sending secret to %s", client.name)
1753
# bump the timeout using extended_timeout
1754
client.bump_timeout(client.extended_timeout)
1755
if self.server.use_dbus:
1760
if approval_required:
1761
client.approvals_pending -= 1
1764
except gnutls.errors.GNUTLSError as error:
1765
logger.warning("GnuTLS bye failed",
1769
def peer_certificate(session):
1770
"Return the peer's OpenPGP certificate as a bytestring"
1771
# If not an OpenPGP certificate...
1772
if (gnutls.library.functions
1773
.gnutls_certificate_type_get(session._c_object)
1774
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1775
# ...do the normal thing
1776
return session.peer_certificate
1777
list_size = ctypes.c_uint(1)
1778
cert_list = (gnutls.library.functions
1779
.gnutls_certificate_get_peers
1780
(session._c_object, ctypes.byref(list_size)))
1781
if not bool(cert_list) and list_size.value != 0:
1782
raise gnutls.errors.GNUTLSError("error getting peer"
1784
if list_size.value == 0:
1787
return ctypes.string_at(cert.data, cert.size)
1790
def fingerprint(openpgp):
1791
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1792
# New GnuTLS "datum" with the OpenPGP public key
1793
datum = (gnutls.library.types
1794
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1797
ctypes.c_uint(len(openpgp))))
1798
# New empty GnuTLS certificate
1799
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1800
(gnutls.library.functions
1801
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1802
# Import the OpenPGP public key into the certificate
1803
(gnutls.library.functions
1804
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1805
gnutls.library.constants
1806
.GNUTLS_OPENPGP_FMT_RAW))
1807
# Verify the self signature in the key
1808
crtverify = ctypes.c_uint()
1809
(gnutls.library.functions
1810
.gnutls_openpgp_crt_verify_self(crt, 0,
1811
ctypes.byref(crtverify)))
1812
if crtverify.value != 0:
1813
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1814
raise (gnutls.errors.CertificateSecurityError
1816
# New buffer for the fingerprint
1817
buf = ctypes.create_string_buffer(20)
1818
buf_len = ctypes.c_size_t()
1819
# Get the fingerprint from the certificate into the buffer
1820
(gnutls.library.functions
1821
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1822
ctypes.byref(buf_len)))
1823
# Deinit the certificate
1824
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1825
# Convert the buffer to a Python bytestring
1826
fpr = ctypes.string_at(buf, buf_len.value)
1827
# Convert the bytestring to hexadecimal notation
1828
hex_fpr = binascii.hexlify(fpr).upper()
1832
class MultiprocessingMixIn(object):
1833
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1834
def sub_process_main(self, request, address):
1836
self.finish_request(request, address)
1838
self.handle_error(request, address)
1839
self.close_request(request)
1841
def process_request(self, request, address):
1842
"""Start a new process to process the request."""
1843
proc = multiprocessing.Process(target = self.sub_process_main,
1850
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1851
""" adds a pipe to the MixIn """
1852
def process_request(self, request, client_address):
1853
"""Overrides and wraps the original process_request().
1855
This function creates a new pipe in self.pipe
1857
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1859
proc = MultiprocessingMixIn.process_request(self, request,
1861
self.child_pipe.close()
1862
self.add_pipe(parent_pipe, proc)
1864
def add_pipe(self, parent_pipe, proc):
1865
"""Dummy function; override as necessary"""
1866
raise NotImplementedError
1869
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1870
socketserver.TCPServer, object):
1871
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
429
logger.info(u"TCP connection from: %s",
430
unicode(self.client_address))
431
session = gnutls.connection.ClientSession\
432
(self.request, gnutls.connection.X509Credentials())
434
line = self.request.makefile().readline()
435
logger.debug(u"Protocol version: %r", line)
437
if int(line.strip().split()[0]) > 1:
439
except (ValueError, IndexError, RuntimeError), error:
440
logger.error(u"Unknown protocol version: %s", error)
443
# Note: gnutls.connection.X509Credentials is really a generic
444
# GnuTLS certificate credentials object so long as no X.509
445
# keys are added to it. Therefore, we can use it here despite
446
# using OpenPGP certificates.
448
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
449
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
451
priority = "NORMAL" # Fallback default, since this
453
if self.server.settings["priority"]:
454
priority = self.server.settings["priority"]
455
gnutls.library.functions.gnutls_priority_set_direct\
456
(session._c_object, priority, None);
460
except gnutls.errors.GNUTLSError, error:
461
logger.warning(u"Handshake failed: %s", error)
462
# Do not run session.bye() here: the session is not
463
# established. Just abandon the request.
466
fpr = fingerprint(peer_certificate(session))
467
except (TypeError, gnutls.errors.GNUTLSError), error:
468
logger.warning(u"Bad certificate: %s", error)
471
logger.debug(u"Fingerprint: %s", fpr)
473
for c in self.server.clients:
474
if c.fingerprint == fpr:
478
logger.warning(u"Client not found for fingerprint: %s",
482
# Have to check if client.still_valid(), since it is possible
483
# that the client timed out while establishing the GnuTLS
485
if not client.still_valid():
486
logger.warning(u"Client %(name)s is invalid",
491
while sent_size < len(client.secret):
492
sent = session.send(client.secret[sent_size:])
493
logger.debug(u"Sent: %d, remaining: %d",
494
sent, len(client.secret)
495
- (sent_size + sent))
500
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
501
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1874
enabled: Boolean; whether this server is activated yet
1875
interface: None or a network interface name (string)
1876
use_ipv6: Boolean; to use IPv6 or not
503
settings: Server settings
504
clients: Set() of Client objects
1878
def __init__(self, server_address, RequestHandlerClass,
1879
interface=None, use_ipv6=True):
1880
self.interface = interface
1882
self.address_family = socket.AF_INET6
1883
socketserver.TCPServer.__init__(self, server_address,
1884
RequestHandlerClass)
506
address_family = socket.AF_INET6
507
def __init__(self, *args, **kwargs):
508
if "settings" in kwargs:
509
self.settings = kwargs["settings"]
510
del kwargs["settings"]
511
if "clients" in kwargs:
512
self.clients = kwargs["clients"]
513
del kwargs["clients"]
514
return super(type(self), self).__init__(*args, **kwargs)
1885
515
def server_bind(self):
1886
516
"""This overrides the normal server_bind() function
1887
517
to bind to an interface if one was specified, and also NOT to
1888
518
bind to an address or port if they were not specified."""
1889
if self.interface is not None:
1890
if SO_BINDTODEVICE is None:
1891
logger.error("SO_BINDTODEVICE does not exist;"
1892
" cannot bind to interface %s",
1896
self.socket.setsockopt(socket.SOL_SOCKET,
1900
except socket.error as error:
1901
if error[0] == errno.EPERM:
1902
logger.error("No permission to"
1903
" bind to interface %s",
1905
elif error[0] == errno.ENOPROTOOPT:
1906
logger.error("SO_BINDTODEVICE not available;"
1907
" cannot bind to interface %s",
519
if self.settings["interface"]:
520
# 25 is from /usr/include/asm-i486/socket.h
521
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
523
self.socket.setsockopt(socket.SOL_SOCKET,
525
self.settings["interface"])
526
except socket.error, error:
527
if error[0] == errno.EPERM:
528
logger.error(u"No permission to"
529
u" bind to interface %s",
530
self.settings["interface"])
1911
533
# Only bind(2) the socket if we really need to.
1912
534
if self.server_address[0] or self.server_address[1]:
1913
535
if not self.server_address[0]:
1914
if self.address_family == socket.AF_INET6:
1915
any_address = "::" # in6addr_any
1917
any_address = socket.INADDR_ANY
1918
self.server_address = (any_address,
537
self.server_address = (in6addr_any,
1919
538
self.server_address[1])
1920
539
elif not self.server_address[1]:
1921
540
self.server_address = (self.server_address[0],
1923
# if self.interface:
542
# if self.settings["interface"]:
1924
543
# self.server_address = (self.server_address[0],
1927
546
# if_nametoindex
1929
return socketserver.TCPServer.server_bind(self)
1932
class MandosServer(IPv6_TCPServer):
1936
clients: set of Client objects
1937
gnutls_priority GnuTLS priority string
1938
use_dbus: Boolean; to emit D-Bus signals or not
1940
Assumes a gobject.MainLoop event loop.
1942
def __init__(self, server_address, RequestHandlerClass,
1943
interface=None, use_ipv6=True, clients=None,
1944
gnutls_priority=None, use_dbus=True):
1945
self.enabled = False
1946
self.clients = clients
1947
if self.clients is None:
1949
self.use_dbus = use_dbus
1950
self.gnutls_priority = gnutls_priority
1951
IPv6_TCPServer.__init__(self, server_address,
1952
RequestHandlerClass,
1953
interface = interface,
1954
use_ipv6 = use_ipv6)
1955
def server_activate(self):
1957
return socketserver.TCPServer.server_activate(self)
1962
def add_pipe(self, parent_pipe, proc):
1963
# Call "handle_ipc" for both data and EOF events
1964
gobject.io_add_watch(parent_pipe.fileno(),
1965
gobject.IO_IN | gobject.IO_HUP,
1966
functools.partial(self.handle_ipc,
1971
def handle_ipc(self, source, condition, parent_pipe=None,
1972
proc = None, client_object=None):
1974
gobject.IO_IN: "IN", # There is data to read.
1975
gobject.IO_OUT: "OUT", # Data can be written (without
1977
gobject.IO_PRI: "PRI", # There is urgent data to read.
1978
gobject.IO_ERR: "ERR", # Error condition.
1979
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1980
# broken, usually for pipes and
1983
conditions_string = ' | '.join(name
1985
condition_names.iteritems()
1986
if cond & condition)
1987
# error, or the other end of multiprocessing.Pipe has closed
1988
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1989
# Wait for other process to exit
1993
# Read a request from the child
1994
request = parent_pipe.recv()
1995
command = request[0]
1997
if command == 'init':
1999
address = request[2]
2001
for c in self.clients.itervalues():
2002
if c.fingerprint == fpr:
2006
logger.info("Client not found for fingerprint: %s, ad"
2007
"dress: %s", fpr, address)
2010
mandos_dbus_service.ClientNotFound(fpr,
2012
parent_pipe.send(False)
2015
gobject.io_add_watch(parent_pipe.fileno(),
2016
gobject.IO_IN | gobject.IO_HUP,
2017
functools.partial(self.handle_ipc,
2023
parent_pipe.send(True)
2024
# remove the old hook in favor of the new above hook on
2027
if command == 'funcall':
2028
funcname = request[1]
2032
parent_pipe.send(('data', getattr(client_object,
2036
if command == 'getattr':
2037
attrname = request[1]
2038
if callable(client_object.__getattribute__(attrname)):
2039
parent_pipe.send(('function',))
2041
parent_pipe.send(('data', client_object
2042
.__getattribute__(attrname)))
2044
if command == 'setattr':
2045
attrname = request[1]
2047
setattr(client_object, attrname, value)
549
return super(type(self), self).server_bind()
2052
552
def string_to_delta(interval):
2053
553
"""Parse a string and return a datetime.timedelta
2055
555
>>> string_to_delta('7d')
2056
556
datetime.timedelta(7)
2057
557
>>> string_to_delta('60s')
2171
694
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
2172
695
"servicename": "Mandos",
2177
"statedir": "/var/lib/mandos"
2180
698
# Parse config file for server-global settings
2181
server_config = configparser.SafeConfigParser(server_defaults)
699
server_config = ConfigParser.SafeConfigParser(server_defaults)
2182
700
del server_defaults
2183
server_config.read(os.path.join(options.configdir,
701
server_config.read(os.path.join(options.configdir, "mandos.conf"))
702
server_section = "server"
2185
703
# Convert the SafeConfigParser object to a dict
2186
server_settings = server_config.defaults()
2187
# Use the appropriate methods on the non-string config options
2188
for option in ("debug", "use_dbus", "use_ipv6"):
2189
server_settings[option] = server_config.getboolean("DEFAULT",
2191
if server_settings["port"]:
2192
server_settings["port"] = server_config.getint("DEFAULT",
704
server_settings = dict(server_config.items(server_section))
705
# Use getboolean on the boolean config option
706
server_settings["debug"] = server_config.getboolean\
707
(server_section, "debug")
2194
708
del server_config
2196
710
# Override the settings from the config file with command line
2197
711
# options, if set.
2198
712
for option in ("interface", "address", "port", "debug",
2199
"priority", "servicename", "configdir",
2200
"use_dbus", "use_ipv6", "debuglevel", "restore",
713
"priority", "servicename", "configdir"):
2202
714
value = getattr(options, option)
2203
715
if value is not None:
2204
716
server_settings[option] = value
2206
# Force all strings to be unicode
2207
for option in server_settings.keys():
2208
if type(server_settings[option]) is str:
2209
server_settings[option] = unicode(server_settings[option])
2210
718
# Now we have our good server settings in "server_settings"
2212
##################################################################
2215
720
debug = server_settings["debug"]
2216
debuglevel = server_settings["debuglevel"]
2217
use_dbus = server_settings["use_dbus"]
2218
use_ipv6 = server_settings["use_ipv6"]
2219
stored_state_path = os.path.join(server_settings["statedir"],
2223
initlogger(debug, logging.DEBUG)
2228
level = getattr(logging, debuglevel.upper())
2229
initlogger(debug, level)
723
syslogger.setLevel(logging.WARNING)
724
console.setLevel(logging.WARNING)
2231
726
if server_settings["servicename"] != "Mandos":
2232
syslogger.setFormatter(logging.Formatter
2233
('Mandos ({0}) [%(process)d]:'
2234
' %(levelname)s: %(message)s'
2235
.format(server_settings
727
syslogger.setFormatter(logging.Formatter\
728
('Mandos (%s): %%(levelname)s:'
730
% server_settings["servicename"]))
2238
732
# Parse config file with clients
2239
client_config = configparser.SafeConfigParser(Client
733
client_defaults = { "timeout": "1h",
735
"checker": "fping -q -- %%(host)s",
737
client_config = ConfigParser.SafeConfigParser(client_defaults)
2241
738
client_config.read(os.path.join(server_settings["configdir"],
2242
739
"clients.conf"))
2244
global mandos_dbus_service
2245
mandos_dbus_service = None
2247
tcp_server = MandosServer((server_settings["address"],
2248
server_settings["port"]),
2250
interface=(server_settings["interface"]
2254
server_settings["priority"],
2257
pidfilename = "/var/run/mandos.pid"
2259
pidfile = open(pidfilename, "w")
2260
except IOError as e:
2261
logger.error("Could not open file %r", pidfilename,
2264
for name in ("_mandos", "mandos", "nobody"):
2266
uid = pwd.getpwnam(name).pw_uid
2267
gid = pwd.getpwnam(name).pw_gid
2277
except OSError as error:
2278
if error[0] != errno.EPERM:
2282
# Enable all possible GnuTLS debugging
2284
# "Use a log level over 10 to enable all debugging options."
2286
gnutls.library.functions.gnutls_global_set_log_level(11)
2288
@gnutls.library.types.gnutls_log_func
2289
def debug_gnutls(level, string):
2290
logger.debug("GnuTLS: %s", string[:-1])
2292
(gnutls.library.functions
2293
.gnutls_global_set_log_function(debug_gnutls))
2295
# Redirect stdin so all checkers get /dev/null
2296
null = os.open(os.devnull, os.O_NOCTTY | os.O_RDWR)
2297
os.dup2(null, sys.stdin.fileno())
2301
# Need to fork before connecting to D-Bus
2303
# Close all input and output, do double fork, etc.
2306
gobject.threads_init()
742
service = AvahiService(name = server_settings["servicename"],
743
type = "_mandos._tcp", );
744
if server_settings["interface"]:
745
service.interface = if_nametoindex(server_settings["interface"])
2308
747
global main_loop
2309
750
# From the Avahi example code
2310
DBusGMainLoop(set_as_default=True)
751
DBusGMainLoop(set_as_default=True )
2311
752
main_loop = gobject.MainLoop()
2312
753
bus = dbus.SystemBus()
754
server = dbus.Interface(
755
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
756
avahi.DBUS_INTERFACE_SERVER )
2313
757
# End of Avahi example code
2316
bus_name = dbus.service.BusName("se.recompile.Mandos",
2317
bus, do_not_queue=True)
2318
old_bus_name = (dbus.service.BusName
2319
("se.bsnet.fukt.Mandos", bus,
2321
except dbus.exceptions.NameExistsException as e:
2322
logger.error("Disabling D-Bus:", exc_info=e)
2324
server_settings["use_dbus"] = False
2325
tcp_server.use_dbus = False
2326
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2327
service = AvahiServiceToSyslog(name =
2328
server_settings["servicename"],
2329
servicetype = "_mandos._tcp",
2330
protocol = protocol, bus = bus)
2331
if server_settings["interface"]:
2332
service.interface = (if_nametoindex
2333
(str(server_settings["interface"])))
2335
global multiprocessing_manager
2336
multiprocessing_manager = multiprocessing.Manager()
2338
client_class = Client
2340
client_class = functools.partial(ClientDBusTransitional,
2343
client_settings = Client.config_parser(client_config)
2344
old_client_settings = {}
2347
# Get client data and settings from last running state.
2348
if server_settings["restore"]:
2350
with open(stored_state_path, "rb") as stored_state:
2351
clients_data, old_client_settings = (pickle.load
2353
os.remove(stored_state_path)
2354
except IOError as e:
2355
if e.errno == errno.ENOENT:
2356
logger.warning("Could not load persistent state: {0}"
2357
.format(os.strerror(e.errno)))
2359
logger.critical("Could not load persistent state:",
2362
except EOFError as e:
2363
logger.warning("Could not load persistent state: "
2364
"EOFError:", exc_info=e)
2366
with PGPEngine() as pgp:
2367
for client_name, client in clients_data.iteritems():
2368
# Decide which value to use after restoring saved state.
2369
# We have three different values: Old config file,
2370
# new config file, and saved state.
2371
# New config value takes precedence if it differs from old
2372
# config value, otherwise use saved state.
2373
for name, value in client_settings[client_name].items():
2375
# For each value in new config, check if it
2376
# differs from the old config value (Except for
2377
# the "secret" attribute)
2378
if (name != "secret" and
2379
value != old_client_settings[client_name]
2381
client[name] = value
2385
# Clients who has passed its expire date can still be
2386
# enabled if its last checker was successful. Clients
2387
# whose checker succeeded before we stored its state is
2388
# assumed to have successfully run all checkers during
2390
if client["enabled"]:
2391
if datetime.datetime.utcnow() >= client["expires"]:
2392
if not client["last_checked_ok"]:
2394
"disabling client {0} - Client never "
2395
"performed a successful checker"
2396
.format(client_name))
2397
client["enabled"] = False
2398
elif client["last_checker_status"] != 0:
2400
"disabling client {0} - Client "
2401
"last checker failed with error code {1}"
2402
.format(client_name,
2403
client["last_checker_status"]))
2404
client["enabled"] = False
2406
client["expires"] = (datetime.datetime
2408
+ client["timeout"])
2409
logger.debug("Last checker succeeded,"
2410
" keeping {0} enabled"
2411
.format(client_name))
2413
client["secret"] = (
2414
pgp.decrypt(client["encrypted_secret"],
2415
client_settings[client_name]
2418
# If decryption fails, we use secret from new settings
2419
logger.debug("Failed to decrypt {0} old secret"
2420
.format(client_name))
2421
client["secret"] = (
2422
client_settings[client_name]["secret"])
2424
# Add/remove clients based on new changes made to config
2425
for client_name in (set(old_client_settings)
2426
- set(client_settings)):
2427
del clients_data[client_name]
2428
for client_name in (set(client_settings)
2429
- set(old_client_settings)):
2430
clients_data[client_name] = client_settings[client_name]
2432
# Create all client objects
2433
for client_name, client in clients_data.iteritems():
2434
tcp_server.clients[client_name] = client_class(
2435
name = client_name, settings = client)
2437
if not tcp_server.clients:
2438
logger.warning("No clients defined")
2444
pidfile.write(str(pid) + "\n".encode("utf-8"))
2447
logger.error("Could not write to file %r with PID %d",
2450
# "pidfile" was never created
760
def remove_from_clients(client):
761
clients.remove(client)
763
logger.critical(u"No clients left, exiting")
766
clients.update(Set(Client(name = section,
767
stop_hook = remove_from_clients,
769
= dict(client_config.items(section)))
770
for section in client_config.sections()))
772
logger.critical(u"No clients defined")
776
logger.removeHandler(console)
779
pidfilename = "/var/run/mandos/mandos.pid"
782
pidfile = open(pidfilename, "w")
783
pidfile.write(str(pid) + "\n")
787
logger.error(u"Could not write %s file with PID %d",
788
pidfilename, os.getpid())
791
"Cleanup function; run on exit"
793
# From the Avahi example code
794
if not group is None:
797
# End of Avahi example code
800
client = clients.pop()
801
client.stop_hook = None
804
atexit.register(cleanup)
2453
807
signal.signal(signal.SIGINT, signal.SIG_IGN)
2455
808
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2456
809
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2459
class MandosDBusService(DBusObjectWithProperties):
2460
"""A D-Bus proxy object"""
2462
dbus.service.Object.__init__(self, bus, "/")
2463
_interface = "se.recompile.Mandos"
2465
@dbus_interface_annotations(_interface)
2467
return { "org.freedesktop.DBus.Property"
2468
".EmitsChangedSignal":
2471
@dbus.service.signal(_interface, signature="o")
2472
def ClientAdded(self, objpath):
2476
@dbus.service.signal(_interface, signature="ss")
2477
def ClientNotFound(self, fingerprint, address):
2481
@dbus.service.signal(_interface, signature="os")
2482
def ClientRemoved(self, objpath, name):
2486
@dbus.service.method(_interface, out_signature="ao")
2487
def GetAllClients(self):
2489
return dbus.Array(c.dbus_object_path
2491
tcp_server.clients.itervalues())
2493
@dbus.service.method(_interface,
2494
out_signature="a{oa{sv}}")
2495
def GetAllClientsWithProperties(self):
2497
return dbus.Dictionary(
2498
((c.dbus_object_path, c.GetAll(""))
2499
for c in tcp_server.clients.itervalues()),
2502
@dbus.service.method(_interface, in_signature="o")
2503
def RemoveClient(self, object_path):
2505
for c in tcp_server.clients.itervalues():
2506
if c.dbus_object_path == object_path:
2507
del tcp_server.clients[c.name]
2508
c.remove_from_connection()
2509
# Don't signal anything except ClientRemoved
2510
c.disable(quiet=True)
2512
self.ClientRemoved(object_path, c.name)
2514
raise KeyError(object_path)
2518
class MandosDBusServiceTransitional(MandosDBusService):
2519
__metaclass__ = AlternateDBusNamesMetaclass
2520
mandos_dbus_service = MandosDBusServiceTransitional()
2523
"Cleanup function; run on exit"
2526
multiprocessing.active_children()
2527
if not (tcp_server.clients or client_settings):
2530
# Store client before exiting. Secrets are encrypted with key
2531
# based on what config file has. If config file is
2532
# removed/edited, old secret will thus be unrecovable.
2534
with PGPEngine() as pgp:
2535
for client in tcp_server.clients.itervalues():
2536
key = client_settings[client.name]["secret"]
2537
client.encrypted_secret = pgp.encrypt(client.secret,
2541
# A list of attributes that can not be pickled
2543
exclude = set(("bus", "changedstate", "secret",
2545
for name, typ in (inspect.getmembers
2546
(dbus.service.Object)):
2549
client_dict["encrypted_secret"] = (client
2551
for attr in client.client_structure:
2552
if attr not in exclude:
2553
client_dict[attr] = getattr(client, attr)
2555
clients[client.name] = client_dict
2556
del client_settings[client.name]["secret"]
2559
tempfd, tempname = tempfile.mkstemp(suffix=".pickle",
2562
(stored_state_path))
2563
with os.fdopen(tempfd, "wb") as stored_state:
2564
pickle.dump((clients, client_settings), stored_state)
2565
os.rename(tempname, stored_state_path)
2566
except (IOError, OSError) as e:
2572
if e.errno in (errno.ENOENT, errno.EACCES, errno.EEXIST):
2573
logger.warning("Could not save persistent state: {0}"
2574
.format(os.strerror(e.errno)))
2576
logger.warning("Could not save persistent state:",
2580
# Delete all clients, and settings from config
2581
while tcp_server.clients:
2582
name, client = tcp_server.clients.popitem()
2584
client.remove_from_connection()
2585
# Don't signal anything except ClientRemoved
2586
client.disable(quiet=True)
2589
mandos_dbus_service.ClientRemoved(client
2592
client_settings.clear()
2594
atexit.register(cleanup)
2596
for client in tcp_server.clients.itervalues():
2599
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2600
# Need to initiate checking of clients
2602
client.init_checker()
2605
tcp_server.server_activate()
811
for client in clients:
814
tcp_server = IPv6_TCPServer((server_settings["address"],
815
server_settings["port"]),
817
settings=server_settings,
2607
819
# Find out what port we got
2608
820
service.port = tcp_server.socket.getsockname()[1]
2610
logger.info("Now listening on address %r, port %d,"
2611
" flowinfo %d, scope_id %d",
2612
*tcp_server.socket.getsockname())
2614
logger.info("Now listening on address %r, port %d",
2615
*tcp_server.socket.getsockname())
821
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
822
u" scope_id %d" % tcp_server.socket.getsockname())
2617
824
#service.interface = tcp_server.socket.getsockname()[3]
2620
827
# From the Avahi example code
828
server.connect_to_signal("StateChanged", server_state_changed)
2623
except dbus.exceptions.DBusException as error:
2624
logger.critical("D-Bus Exception", exc_info=error)
830
server_state_changed(server.GetState())
831
except dbus.exceptions.DBusException, error:
832
logger.critical(u"DBusException: %s", error)
2627
834
# End of Avahi example code
2629
836
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2630
837
lambda *args, **kwargs:
2631
(tcp_server.handle_request
2632
(*args[2:], **kwargs) or True))
838
tcp_server.handle_request\
839
(*args[2:], **kwargs) or True)
2634
logger.debug("Starting main loop")
841
logger.debug(u"Starting main loop")
842
main_loop_started = True
2636
except AvahiError as error:
2637
logger.critical("Avahi Error", exc_info=error)
844
except AvahiError, error:
845
logger.critical(u"AvahiError: %s" + unicode(error))
2640
847
except KeyboardInterrupt:
2642
print("", file=sys.stderr)
2643
logger.debug("Server received KeyboardInterrupt")
2644
logger.debug("Server exiting")
2645
# Must run before the D-Bus bus name gets deregistered
2648
851
if __name__ == '__main__':