144
124
self.rename_count = 0
145
125
self.max_renames = max_renames
146
self.protocol = protocol
147
self.group = None # our entry group
150
126
def rename(self):
151
127
"""Derived from the Avahi example code"""
152
128
if self.rename_count >= self.max_renames:
153
129
logger.critical(u"No suitable Zeroconf service name found"
154
130
u" after %i retries, exiting.",
155
131
self.rename_count)
156
raise AvahiServiceError(u"Too many renames")
157
self.name = self.server.GetAlternativeServiceName(self.name)
132
raise AvahiServiceError("Too many renames")
133
self.name = server.GetAlternativeServiceName(self.name)
158
134
logger.info(u"Changing Zeroconf service name to %r ...",
160
136
syslogger.setFormatter(logging.Formatter
161
(u'Mandos (%s) [%%(process)d]:'
162
u' %%(levelname)s: %%(message)s'
137
('Mandos (%s): %%(levelname)s:'
138
' %%(message)s' % self.name))
166
141
self.rename_count += 1
167
142
def remove(self):
168
143
"""Derived from the Avahi example code"""
169
if self.group is not None:
144
if group is not None:
172
147
"""Derived from the Avahi example code"""
173
if self.group is None:
174
self.group = dbus.Interface(
175
self.bus.get_object(avahi.DBUS_NAME,
176
self.server.EntryGroupNew()),
177
avahi.DBUS_INTERFACE_ENTRY_GROUP)
178
self.group.connect_to_signal('StateChanged',
180
.entry_group_state_changed)
150
group = dbus.Interface(bus.get_object
152
server.EntryGroupNew()),
153
avahi.DBUS_INTERFACE_ENTRY_GROUP)
154
group.connect_to_signal('StateChanged',
155
entry_group_state_changed)
181
156
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
182
self.name, self.type)
183
self.group.AddService(
186
dbus.UInt32(0), # flags
187
self.name, self.type,
188
self.domain, self.host,
189
dbus.UInt16(self.port),
190
avahi.string_array_to_txt_array(self.TXT))
192
def entry_group_state_changed(self, state, error):
193
"""Derived from the Avahi example code"""
194
logger.debug(u"Avahi state change: %i", state)
196
if state == avahi.ENTRY_GROUP_ESTABLISHED:
197
logger.debug(u"Zeroconf service established.")
198
elif state == avahi.ENTRY_GROUP_COLLISION:
199
logger.warning(u"Zeroconf service name collision.")
201
elif state == avahi.ENTRY_GROUP_FAILURE:
202
logger.critical(u"Avahi: Error in group state changed %s",
204
raise AvahiGroupError(u"State changed: %s"
207
"""Derived from the Avahi example code"""
208
if self.group is not None:
211
def server_state_changed(self, state):
212
"""Derived from the Avahi example code"""
213
if state == avahi.SERVER_COLLISION:
214
logger.error(u"Zeroconf server name collision")
216
elif state == avahi.SERVER_RUNNING:
219
"""Derived from the Avahi example code"""
220
if self.server is None:
221
self.server = dbus.Interface(
222
self.bus.get_object(avahi.DBUS_NAME,
223
avahi.DBUS_PATH_SERVER),
224
avahi.DBUS_INTERFACE_SERVER)
225
self.server.connect_to_signal(u"StateChanged",
226
self.server_state_changed)
227
self.server_state_changed(self.server.GetState())
230
class Client(object):
157
service.name, service.type)
159
self.interface, # interface
160
avahi.PROTO_INET6, # protocol
161
dbus.UInt32(0), # flags
162
self.name, self.type,
163
self.domain, self.host,
164
dbus.UInt16(self.port),
165
avahi.string_array_to_txt_array(self.TXT))
168
# From the Avahi example code:
169
group = None # our entry group
170
# End of Avahi example code
173
class Client(dbus.service.Object):
231
174
"""A representation of a client host served by this server.
234
name: string; from the config file, used in log messages and
176
name: string; from the config file, used in log messages
236
177
fingerprint: string (40 or 32 hexadecimal digits); used to
237
178
uniquely identify the client
238
secret: bytestring; sent verbatim (over TLS) to client
239
host: string; available for use by the checker command
240
created: datetime.datetime(); (UTC) object creation
241
last_enabled: datetime.datetime(); (UTC)
179
secret: bytestring; sent verbatim (over TLS) to client
180
host: string; available for use by the checker command
181
created: datetime.datetime(); (UTC) object creation
182
started: datetime.datetime(); (UTC) last started
243
183
last_checked_ok: datetime.datetime(); (UTC) or None
244
timeout: datetime.timedelta(); How long from last_checked_ok
245
until this client is invalid
246
interval: datetime.timedelta(); How often to start a new checker
247
disable_hook: If set, called by disable() as disable_hook(self)
248
checker: subprocess.Popen(); a running checker process used
249
to see if the client lives.
250
'None' if no process is running.
184
timeout: datetime.timedelta(); How long from last_checked_ok
185
until this client is invalid
186
interval: datetime.timedelta(); How often to start a new checker
187
stop_hook: If set, called by stop() as stop_hook(self)
188
checker: subprocess.Popen(); a running checker process used
189
to see if the client lives.
190
'None' if no process is running.
251
191
checker_initiator_tag: a gobject event source tag, or None
252
disable_initiator_tag: - '' -
192
stop_initiator_tag: - '' -
253
193
checker_callback_tag: - '' -
254
194
checker_command: string; External command which is run to check if
255
195
client lives. %() expansions are done at
256
196
runtime with vars(self) as dict, so that for
257
197
instance %(name)s can be used in the command.
258
current_checker_command: string; current running checker_command
199
_timeout: Real variable for 'timeout'
200
_interval: Real variable for 'interval'
201
_timeout_milliseconds: Used when calling gobject.timeout_add()
202
_interval_milliseconds: - '' -
262
def _timedelta_to_milliseconds(td):
263
"Convert a datetime.timedelta() to milliseconds"
264
return ((td.days * 24 * 60 * 60 * 1000)
265
+ (td.seconds * 1000)
266
+ (td.microseconds // 1000))
268
def timeout_milliseconds(self):
269
"Return the 'timeout' attribute in milliseconds"
270
return self._timedelta_to_milliseconds(self.timeout)
272
def interval_milliseconds(self):
273
"Return the 'interval' attribute in milliseconds"
274
return self._timedelta_to_milliseconds(self.interval)
276
def __init__(self, name = None, disable_hook=None, config=None):
204
def _set_timeout(self, timeout):
205
"Setter function for the 'timeout' attribute"
206
self._timeout = timeout
207
self._timeout_milliseconds = ((self.timeout.days
208
* 24 * 60 * 60 * 1000)
209
+ (self.timeout.seconds * 1000)
210
+ (self.timeout.microseconds
213
self.TimeoutChanged(self._timeout_milliseconds)
214
timeout = property(lambda self: self._timeout, _set_timeout)
217
def _set_interval(self, interval):
218
"Setter function for the 'interval' attribute"
219
self._interval = interval
220
self._interval_milliseconds = ((self.interval.days
221
* 24 * 60 * 60 * 1000)
222
+ (self.interval.seconds
224
+ (self.interval.microseconds
227
self.IntervalChanged(self._interval_milliseconds)
228
interval = property(lambda self: self._interval, _set_interval)
231
def __init__(self, name = None, stop_hook=None, config=None):
277
232
"""Note: the 'checker' key in 'config' sets the
278
233
'checker_command' attribute and *not* the 'checker'
235
dbus.service.Object.__init__(self, bus,
237
% name.replace(".", "_"))
281
238
if config is None:
283
241
logger.debug(u"Creating client %r", self.name)
284
242
# Uppercase and remove spaces from fingerprint for later
285
243
# comparison purposes with return value from the fingerprint()
287
self.fingerprint = (config[u"fingerprint"].upper()
245
self.fingerprint = (config["fingerprint"].upper()
288
246
.replace(u" ", u""))
289
247
logger.debug(u" Fingerprint: %s", self.fingerprint)
290
if u"secret" in config:
291
self.secret = config[u"secret"].decode(u"base64")
292
elif u"secfile" in config:
248
if "secret" in config:
249
self.secret = config["secret"].decode(u"base64")
250
elif "secfile" in config:
293
251
with closing(open(os.path.expanduser
294
252
(os.path.expandvars
295
(config[u"secfile"])),
253
(config["secfile"])))) as secfile:
297
254
self.secret = secfile.read()
299
256
raise TypeError(u"No secret or secfile for client %s"
301
self.host = config.get(u"host", u"")
258
self.host = config.get("host", "")
302
259
self.created = datetime.datetime.utcnow()
304
self.last_enabled = None
305
261
self.last_checked_ok = None
306
self.timeout = string_to_delta(config[u"timeout"])
307
self.interval = string_to_delta(config[u"interval"])
308
self.disable_hook = disable_hook
262
self.timeout = string_to_delta(config["timeout"])
263
self.interval = string_to_delta(config["interval"])
264
self.stop_hook = stop_hook
309
265
self.checker = None
310
266
self.checker_initiator_tag = None
311
self.disable_initiator_tag = None
267
self.stop_initiator_tag = None
312
268
self.checker_callback_tag = None
313
self.checker_command = config[u"checker"]
314
self.current_checker_command = None
315
self.last_connect = None
269
self.check_command = config["checker"]
318
272
"""Start this client's checker and timeout hooks"""
319
if getattr(self, u"enabled", False):
322
self.last_enabled = datetime.datetime.utcnow()
273
self.started = datetime.datetime.utcnow()
323
274
# Schedule a new checker to be started an 'interval' from now,
324
275
# and every interval from then on.
325
276
self.checker_initiator_tag = (gobject.timeout_add
326
(self.interval_milliseconds(),
277
(self._interval_milliseconds,
327
278
self.start_checker))
328
279
# Also start a new checker *right now*.
329
280
self.start_checker()
330
# Schedule a disable() when 'timeout' has passed
331
self.disable_initiator_tag = (gobject.timeout_add
332
(self.timeout_milliseconds(),
281
# Schedule a stop() when 'timeout' has passed
282
self.stop_initiator_tag = (gobject.timeout_add
283
(self._timeout_milliseconds,
286
self.StateChanged(True)
337
"""Disable this client."""
338
if not getattr(self, "enabled", False):
289
"""Stop this client."""
290
if getattr(self, "started", None) is not None:
291
logger.info(u"Stopping client %s", self.name)
340
logger.info(u"Disabling client %s", self.name)
341
if getattr(self, u"disable_initiator_tag", False):
342
gobject.source_remove(self.disable_initiator_tag)
343
self.disable_initiator_tag = None
344
if getattr(self, u"checker_initiator_tag", False):
294
if getattr(self, "stop_initiator_tag", False):
295
gobject.source_remove(self.stop_initiator_tag)
296
self.stop_initiator_tag = None
297
if getattr(self, "checker_initiator_tag", False):
345
298
gobject.source_remove(self.checker_initiator_tag)
346
299
self.checker_initiator_tag = None
347
300
self.stop_checker()
348
if self.disable_hook:
349
self.disable_hook(self)
305
self.StateChanged(False)
351
306
# Do not run this again if called by a gobject.timeout_add
354
309
def __del__(self):
355
self.disable_hook = None
310
self.stop_hook = None
358
def checker_callback(self, pid, condition, command):
313
def checker_callback(self, pid, condition):
359
314
"""The checker has completed, so take appropriate actions."""
360
315
self.checker_callback_tag = None
361
316
self.checker = None
362
if os.WIFEXITED(condition):
363
exitstatus = os.WEXITSTATUS(condition)
365
logger.info(u"Checker for %(name)s succeeded",
369
logger.info(u"Checker for %(name)s failed",
317
if (os.WIFEXITED(condition)
318
and (os.WEXITSTATUS(condition) == 0)):
319
logger.info(u"Checker for %(name)s succeeded",
322
self.CheckerCompleted(True)
324
elif not os.WIFEXITED(condition):
372
325
logger.warning(u"Checker for %(name)s crashed?",
328
self.CheckerCompleted(False)
330
logger.info(u"Checker for %(name)s failed",
333
self.CheckerCompleted(False)
375
def checked_ok(self):
335
def bump_timeout(self):
376
336
"""Bump up the timeout for this client.
378
337
This should only be called when the client has been seen,
381
340
self.last_checked_ok = datetime.datetime.utcnow()
382
gobject.source_remove(self.disable_initiator_tag)
383
self.disable_initiator_tag = (gobject.timeout_add
384
(self.timeout_milliseconds(),
341
gobject.source_remove(self.stop_initiator_tag)
342
self.stop_initiator_tag = (gobject.timeout_add
343
(self._timeout_milliseconds,
387
346
def start_checker(self):
388
347
"""Start a new checker subprocess if one is not running.
390
348
If a checker already exists, leave it running and do
392
350
# The reason for not killing a running checker is that if we
478
412
def still_valid(self):
479
413
"""Has the timeout not yet passed for this client?"""
480
if not getattr(self, u"enabled", False):
482
416
now = datetime.datetime.utcnow()
483
417
if self.last_checked_ok is None:
484
418
return now < (self.created + self.timeout)
486
420
return now < (self.last_checked_ok + self.timeout)
489
def dbus_service_property(dbus_interface, signature=u"v",
490
access=u"readwrite", byte_arrays=False):
491
"""Decorators for marking methods of a DBusObjectWithProperties to
492
become properties on the D-Bus.
494
The decorated method will be called with no arguments by "Get"
495
and with one argument by "Set".
497
The parameters, where they are supported, are the same as
498
dbus.service.method, except there is only "signature", since the
499
type from Get() and the type sent to Set() is the same.
502
func._dbus_is_property = True
503
func._dbus_interface = dbus_interface
504
func._dbus_signature = signature
505
func._dbus_access = access
506
func._dbus_name = func.__name__
507
if func._dbus_name.endswith(u"_dbus_property"):
508
func._dbus_name = func._dbus_name[:-14]
509
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
514
class DBusPropertyException(dbus.exceptions.DBusException):
515
"""A base class for D-Bus property-related exceptions
517
def __unicode__(self):
518
return unicode(str(self))
521
class DBusPropertyAccessException(DBusPropertyException):
522
"""A property's access permissions disallows an operation.
527
class DBusPropertyNotFound(DBusPropertyException):
528
"""An attempt was made to access a non-existing property.
533
class DBusObjectWithProperties(dbus.service.Object):
534
"""A D-Bus object with properties.
536
Classes inheriting from this can use the dbus_service_property
537
decorator to expose methods as D-Bus properties. It exposes the
538
standard Get(), Set(), and GetAll() methods on the D-Bus.
542
def _is_dbus_property(obj):
543
return getattr(obj, u"_dbus_is_property", False)
545
def _get_all_dbus_properties(self):
546
"""Returns a generator of (name, attribute) pairs
548
return ((prop._dbus_name, prop)
550
inspect.getmembers(self, self._is_dbus_property))
552
def _get_dbus_property(self, interface_name, property_name):
553
"""Returns a bound method if one exists which is a D-Bus
554
property with the specified name and interface.
556
for name in (property_name,
557
property_name + u"_dbus_property"):
558
prop = getattr(self, name, None)
560
or not self._is_dbus_property(prop)
561
or prop._dbus_name != property_name
562
or (interface_name and prop._dbus_interface
563
and interface_name != prop._dbus_interface)):
567
raise DBusPropertyNotFound(self.dbus_object_path + u":"
568
+ interface_name + u"."
571
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
573
def Get(self, interface_name, property_name):
574
"""Standard D-Bus property Get() method, see D-Bus standard.
576
prop = self._get_dbus_property(interface_name, property_name)
577
if prop._dbus_access == u"write":
578
raise DBusPropertyAccessException(property_name)
580
if not hasattr(value, u"variant_level"):
582
return type(value)(value, variant_level=value.variant_level+1)
584
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
585
def Set(self, interface_name, property_name, value):
586
"""Standard D-Bus property Set() method, see D-Bus standard.
588
prop = self._get_dbus_property(interface_name, property_name)
589
if prop._dbus_access == u"read":
590
raise DBusPropertyAccessException(property_name)
591
if prop._dbus_get_args_options[u"byte_arrays"]:
592
value = dbus.ByteArray(''.join(unichr(byte)
596
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
597
out_signature=u"a{sv}")
598
def GetAll(self, interface_name):
599
"""Standard D-Bus property GetAll() method, see D-Bus
602
Note: Will not include properties with access="write".
605
for name, prop in self._get_all_dbus_properties():
607
and interface_name != prop._dbus_interface):
608
# Interface non-empty but did not match
610
# Ignore write-only properties
611
if prop._dbus_access == u"write":
614
if not hasattr(value, u"variant_level"):
617
all[name] = type(value)(value, variant_level=
618
value.variant_level+1)
619
return dbus.Dictionary(all, signature=u"sv")
621
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
623
path_keyword='object_path',
624
connection_keyword='connection')
625
def Introspect(self, object_path, connection):
626
"""Standard D-Bus method, overloaded to insert property tags.
628
xmlstring = dbus.service.Object.Introspect(self, object_path,
631
document = xml.dom.minidom.parseString(xmlstring)
632
def make_tag(document, name, prop):
633
e = document.createElement(u"property")
634
e.setAttribute(u"name", name)
635
e.setAttribute(u"type", prop._dbus_signature)
636
e.setAttribute(u"access", prop._dbus_access)
638
for if_tag in document.getElementsByTagName(u"interface"):
639
for tag in (make_tag(document, name, prop)
641
in self._get_all_dbus_properties()
642
if prop._dbus_interface
643
== if_tag.getAttribute(u"name")):
644
if_tag.appendChild(tag)
645
# Add the names to the return values for the
646
# "org.freedesktop.DBus.Properties" methods
647
if (if_tag.getAttribute(u"name")
648
== u"org.freedesktop.DBus.Properties"):
649
for cn in if_tag.getElementsByTagName(u"method"):
650
if cn.getAttribute(u"name") == u"Get":
651
for arg in cn.getElementsByTagName(u"arg"):
652
if (arg.getAttribute(u"direction")
654
arg.setAttribute(u"name", u"value")
655
elif cn.getAttribute(u"name") == u"GetAll":
656
for arg in cn.getElementsByTagName(u"arg"):
657
if (arg.getAttribute(u"direction")
659
arg.setAttribute(u"name", u"props")
660
xmlstring = document.toxml(u"utf-8")
662
except (AttributeError, xml.dom.DOMException,
663
xml.parsers.expat.ExpatError), error:
664
logger.error(u"Failed to override Introspection method",
669
class ClientDBus(Client, DBusObjectWithProperties):
670
"""A Client class using D-Bus
673
dbus_object_path: dbus.ObjectPath
674
bus: dbus.SystemBus()
676
# dbus.service.Object doesn't use super(), so we can't either.
678
def __init__(self, bus = None, *args, **kwargs):
680
Client.__init__(self, *args, **kwargs)
681
# Only now, when this client is initialized, can it show up on
683
self.dbus_object_path = (dbus.ObjectPath
685
+ self.name.replace(u".", u"_")))
686
DBusObjectWithProperties.__init__(self, self.bus,
687
self.dbus_object_path)
690
def _datetime_to_dbus(dt, variant_level=0):
691
"""Convert a UTC datetime.datetime() to a D-Bus type."""
692
return dbus.String(dt.isoformat(),
693
variant_level=variant_level)
696
oldstate = getattr(self, u"enabled", False)
697
r = Client.enable(self)
698
if oldstate != self.enabled:
700
self.PropertyChanged(dbus.String(u"enabled"),
701
dbus.Boolean(True, variant_level=1))
702
self.PropertyChanged(
703
dbus.String(u"last_enabled"),
704
self._datetime_to_dbus(self.last_enabled,
708
def disable(self, signal = True):
709
oldstate = getattr(self, u"enabled", False)
710
r = Client.disable(self)
711
if signal and oldstate != self.enabled:
713
self.PropertyChanged(dbus.String(u"enabled"),
714
dbus.Boolean(False, variant_level=1))
717
def __del__(self, *args, **kwargs):
719
self.remove_from_connection()
722
if hasattr(DBusObjectWithProperties, u"__del__"):
723
DBusObjectWithProperties.__del__(self, *args, **kwargs)
724
Client.__del__(self, *args, **kwargs)
726
def checker_callback(self, pid, condition, command,
728
self.checker_callback_tag = None
731
self.PropertyChanged(dbus.String(u"checker_running"),
732
dbus.Boolean(False, variant_level=1))
733
if os.WIFEXITED(condition):
734
exitstatus = os.WEXITSTATUS(condition)
736
self.CheckerCompleted(dbus.Int16(exitstatus),
737
dbus.Int64(condition),
738
dbus.String(command))
741
self.CheckerCompleted(dbus.Int16(-1),
742
dbus.Int64(condition),
743
dbus.String(command))
745
return Client.checker_callback(self, pid, condition, command,
748
def checked_ok(self, *args, **kwargs):
749
r = Client.checked_ok(self, *args, **kwargs)
751
self.PropertyChanged(
752
dbus.String(u"last_checked_ok"),
753
(self._datetime_to_dbus(self.last_checked_ok,
757
def start_checker(self, *args, **kwargs):
758
old_checker = self.checker
759
if self.checker is not None:
760
old_checker_pid = self.checker.pid
762
old_checker_pid = None
763
r = Client.start_checker(self, *args, **kwargs)
764
# Only if new checker process was started
765
if (self.checker is not None
766
and old_checker_pid != self.checker.pid):
768
self.CheckerStarted(self.current_checker_command)
769
self.PropertyChanged(
770
dbus.String(u"checker_running"),
771
dbus.Boolean(True, variant_level=1))
774
def stop_checker(self, *args, **kwargs):
775
old_checker = getattr(self, u"checker", None)
776
r = Client.stop_checker(self, *args, **kwargs)
777
if (old_checker is not None
778
and getattr(self, u"checker", None) is None):
779
self.PropertyChanged(dbus.String(u"checker_running"),
780
dbus.Boolean(False, variant_level=1))
783
422
## D-Bus methods & signals
784
_interface = u"se.bsnet.fukt.Mandos.Client"
787
@dbus.service.method(_interface)
789
return self.checked_ok()
423
_interface = u"org.mandos_system.Mandos.Client"
425
def _datetime_to_dbus_struct(dt):
426
return dbus.Struct(dt.year, dt.month, dt.day, dt.hour,
427
dt.minute, dt.second, dt.microsecond,
430
# BumpTimeout - method
431
BumpTimeout = dbus.service.method(_interface)(bump_timeout)
432
BumpTimeout.__name__ = "BumpTimeout"
434
# IntervalChanged - signal
435
@dbus.service.signal(_interface, signature="t")
436
def IntervalChanged(self, t):
791
440
# CheckerCompleted - signal
792
@dbus.service.signal(_interface, signature=u"nxs")
793
def CheckerCompleted(self, exitcode, waitstatus, command):
441
@dbus.service.signal(_interface, signature="b")
442
def CheckerCompleted(self, success):
446
# CheckerIsRunning - method
447
@dbus.service.method(_interface, out_signature="b")
448
def CheckerIsRunning(self):
449
"D-Bus getter method"
450
return self.checker is not None
797
452
# CheckerStarted - signal
798
@dbus.service.signal(_interface, signature=u"s")
453
@dbus.service.signal(_interface, signature="s")
799
454
def CheckerStarted(self, command):
803
# PropertyChanged - signal
804
@dbus.service.signal(_interface, signature=u"sv")
805
def PropertyChanged(self, property, value):
810
@dbus.service.signal(_interface)
816
@dbus.service.signal(_interface)
822
@dbus.service.method(_interface)
458
# GetChecker - method
459
@dbus.service.method(_interface, out_signature="s")
460
def GetChecker(self):
461
"D-Bus getter method"
462
return self.checker_command
464
# GetCreated - method
465
@dbus.service.method(_interface, out_signature="(nyyyyyu)")
466
def GetCreated(self):
467
"D-Bus getter method"
468
return datetime_to_dbus_struct(self.created)
470
# GetFingerprint - method
471
@dbus.service.method(_interface, out_signature="s")
472
def GetFingerprint(self):
473
"D-Bus getter method"
474
return self.fingerprint
477
@dbus.service.method(_interface, out_signature="s")
479
"D-Bus getter method"
482
# GetInterval - method
483
@dbus.service.method(_interface, out_signature="t")
484
def GetInterval(self):
485
"D-Bus getter method"
486
return self._interval_milliseconds
489
@dbus.service.method(_interface, out_signature="s")
491
"D-Bus getter method"
494
# GetStarted - method
495
@dbus.service.method(_interface, out_signature="(nyyyyyu)")
496
def GetStarted(self):
497
"D-Bus getter method"
498
if self.started is not None:
499
return datetime_to_dbus_struct(self.started)
501
return dbus.Struct(0, 0, 0, 0, 0, 0, 0,
504
# GetTimeout - method
505
@dbus.service.method(_interface, out_signature="t")
506
def GetTimeout(self):
507
"D-Bus getter method"
508
return self._timeout_milliseconds
510
# SetChecker - method
511
@dbus.service.method(_interface, in_signature="s")
512
def SetChecker(self, checker):
513
"D-Bus setter method"
514
self.checker_command = checker
517
@dbus.service.method(_interface, in_signature="s")
518
def SetHost(self, host):
519
"D-Bus setter method"
522
# SetInterval - method
523
@dbus.service.method(_interface, in_signature="t")
524
def SetInterval(self, milliseconds):
525
self.interval = datetime.timdeelta(0, 0, 0, milliseconds)
527
# SetTimeout - method
528
@dbus.service.method(_interface, in_signature="t")
529
def SetTimeout(self, milliseconds):
530
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
533
@dbus.service.method(_interface, in_signature="ay",
535
def SetSecret(self, secret):
536
"D-Bus setter method"
537
self.secret = str(secret)
540
Start = dbus.service.method(_interface)(start)
541
Start.__name__ = "Start"
827
543
# StartChecker - method
828
@dbus.service.method(_interface)
829
def StartChecker(self):
834
@dbus.service.method(_interface)
544
StartChecker = dbus.service.method(_interface)(start_checker)
545
StartChecker.__name__ = "StartChecker"
547
# StateChanged - signal
548
@dbus.service.signal(_interface, signature="b")
549
def StateChanged(self, started):
553
# StillValid - method
554
StillValid = (dbus.service.method(_interface, out_signature="b")
556
StillValid.__name__ = "StillValid"
559
Stop = dbus.service.method(_interface)(stop)
560
Stop.__name__ = "Stop"
839
562
# StopChecker - method
840
@dbus.service.method(_interface)
841
def StopChecker(self):
845
@dbus_service_property(_interface, signature=u"s", access=u"read")
846
def name_dbus_property(self):
847
return dbus.String(self.name)
849
# fingerprint - property
850
@dbus_service_property(_interface, signature=u"s", access=u"read")
851
def fingerprint_dbus_property(self):
852
return dbus.String(self.fingerprint)
855
@dbus_service_property(_interface, signature=u"s",
857
def host_dbus_property(self, value=None):
858
if value is None: # get
859
return dbus.String(self.host)
862
self.PropertyChanged(dbus.String(u"host"),
863
dbus.String(value, variant_level=1))
866
@dbus_service_property(_interface, signature=u"s", access=u"read")
867
def created_dbus_property(self):
868
return dbus.String(self._datetime_to_dbus(self.created))
870
# last_enabled - property
871
@dbus_service_property(_interface, signature=u"s", access=u"read")
872
def last_enabled_dbus_property(self):
873
if self.last_enabled is None:
874
return dbus.String(u"")
875
return dbus.String(self._datetime_to_dbus(self.last_enabled))
878
@dbus_service_property(_interface, signature=u"b",
880
def enabled_dbus_property(self, value=None):
881
if value is None: # get
882
return dbus.Boolean(self.enabled)
888
# last_checked_ok - property
889
@dbus_service_property(_interface, signature=u"s",
891
def last_checked_ok_dbus_property(self, value=None):
892
if value is not None:
895
if self.last_checked_ok is None:
896
return dbus.String(u"")
897
return dbus.String(self._datetime_to_dbus(self
901
@dbus_service_property(_interface, signature=u"t",
903
def timeout_dbus_property(self, value=None):
904
if value is None: # get
905
return dbus.UInt64(self.timeout_milliseconds())
906
self.timeout = datetime.timedelta(0, 0, 0, value)
908
self.PropertyChanged(dbus.String(u"timeout"),
909
dbus.UInt64(value, variant_level=1))
910
if getattr(self, u"disable_initiator_tag", None) is None:
913
gobject.source_remove(self.disable_initiator_tag)
914
self.disable_initiator_tag = None
916
_timedelta_to_milliseconds((self
922
# The timeout has passed
925
self.disable_initiator_tag = (gobject.timeout_add
926
(time_to_die, self.disable))
928
# interval - property
929
@dbus_service_property(_interface, signature=u"t",
931
def interval_dbus_property(self, value=None):
932
if value is None: # get
933
return dbus.UInt64(self.interval_milliseconds())
934
self.interval = datetime.timedelta(0, 0, 0, value)
936
self.PropertyChanged(dbus.String(u"interval"),
937
dbus.UInt64(value, variant_level=1))
938
if getattr(self, u"checker_initiator_tag", None) is None:
940
# Reschedule checker run
941
gobject.source_remove(self.checker_initiator_tag)
942
self.checker_initiator_tag = (gobject.timeout_add
943
(value, self.start_checker))
944
self.start_checker() # Start one now, too
947
@dbus_service_property(_interface, signature=u"s",
949
def checker_dbus_property(self, value=None):
950
if value is None: # get
951
return dbus.String(self.checker_command)
952
self.checker_command = value
954
self.PropertyChanged(dbus.String(u"checker"),
955
dbus.String(self.checker_command,
958
# checker_running - property
959
@dbus_service_property(_interface, signature=u"b",
961
def checker_running_dbus_property(self, value=None):
962
if value is None: # get
963
return dbus.Boolean(self.checker is not None)
969
# object_path - property
970
@dbus_service_property(_interface, signature=u"o", access=u"read")
971
def object_path_dbus_property(self):
972
return self.dbus_object_path # is already a dbus.ObjectPath
975
@dbus_service_property(_interface, signature=u"ay",
976
access=u"write", byte_arrays=True)
977
def secret_dbus_property(self, value):
978
self.secret = str(value)
563
StopChecker = dbus.service.method(_interface)(stop_checker)
564
StopChecker.__name__ = "StopChecker"
566
# TimeoutChanged - signal
567
@dbus.service.signal(_interface, signature="t")
568
def TimeoutChanged(self, t):
572
del _datetime_to_dbus_struct
983
class ClientHandler(socketserver.BaseRequestHandler, object):
984
"""A class to handle client connections.
986
Instantiated once for each connection to handle it.
576
def peer_certificate(session):
577
"Return the peer's OpenPGP certificate as a bytestring"
578
# If not an OpenPGP certificate...
579
if (gnutls.library.functions
580
.gnutls_certificate_type_get(session._c_object)
581
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
582
# ...do the normal thing
583
return session.peer_certificate
584
list_size = ctypes.c_uint()
585
cert_list = (gnutls.library.functions
586
.gnutls_certificate_get_peers
587
(session._c_object, ctypes.byref(list_size)))
588
if list_size.value == 0:
591
return ctypes.string_at(cert.data, cert.size)
594
def fingerprint(openpgp):
595
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
596
# New GnuTLS "datum" with the OpenPGP public key
597
datum = (gnutls.library.types
598
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
601
ctypes.c_uint(len(openpgp))))
602
# New empty GnuTLS certificate
603
crt = gnutls.library.types.gnutls_openpgp_crt_t()
604
(gnutls.library.functions
605
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
606
# Import the OpenPGP public key into the certificate
607
(gnutls.library.functions
608
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
609
gnutls.library.constants
610
.GNUTLS_OPENPGP_FMT_RAW))
611
# Verify the self signature in the key
612
crtverify = ctypes.c_uint()
613
(gnutls.library.functions
614
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
615
if crtverify.value != 0:
616
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
617
raise gnutls.errors.CertificateSecurityError("Verify failed")
618
# New buffer for the fingerprint
619
buf = ctypes.create_string_buffer(20)
620
buf_len = ctypes.c_size_t()
621
# Get the fingerprint from the certificate into the buffer
622
(gnutls.library.functions
623
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
624
ctypes.byref(buf_len)))
625
# Deinit the certificate
626
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
627
# Convert the buffer to a Python bytestring
628
fpr = ctypes.string_at(buf, buf_len.value)
629
# Convert the bytestring to hexadecimal notation
630
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
634
class TCP_handler(SocketServer.BaseRequestHandler, object):
635
"""A TCP request handler class.
636
Instantiated by IPv6_TCPServer for each request to handle it.
987
637
Note: This will run in its own forked process."""
989
639
def handle(self):
990
640
logger.info(u"TCP connection from: %s",
991
641
unicode(self.client_address))
992
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
993
# Open IPC pipe to parent process
994
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
995
session = (gnutls.connection
996
.ClientSession(self.request,
1000
line = self.request.makefile().readline()
1001
logger.debug(u"Protocol version: %r", line)
1003
if int(line.strip().split()[0]) > 1:
1005
except (ValueError, IndexError, RuntimeError), error:
1006
logger.error(u"Unknown protocol version: %s", error)
1009
# Note: gnutls.connection.X509Credentials is really a
1010
# generic GnuTLS certificate credentials object so long as
1011
# no X.509 keys are added to it. Therefore, we can use it
1012
# here despite using OpenPGP certificates.
1014
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1015
# u"+AES-256-CBC", u"+SHA1",
1016
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1018
# Use a fallback default, since this MUST be set.
1019
priority = self.server.gnutls_priority
1020
if priority is None:
1021
priority = u"NORMAL"
1022
(gnutls.library.functions
1023
.gnutls_priority_set_direct(session._c_object,
1028
except gnutls.errors.GNUTLSError, error:
1029
logger.warning(u"Handshake failed: %s", error)
1030
# Do not run session.bye() here: the session is not
1031
# established. Just abandon the request.
1033
logger.debug(u"Handshake succeeded")
1035
fpr = self.fingerprint(self.peer_certificate(session))
1036
except (TypeError, gnutls.errors.GNUTLSError), error:
1037
logger.warning(u"Bad certificate: %s", error)
1040
logger.debug(u"Fingerprint: %s", fpr)
1042
for c in self.server.clients:
1043
if c.fingerprint == fpr:
1047
ipc.write(u"NOTFOUND %s %s\n"
1048
% (fpr, unicode(self.client_address)))
1051
# Have to check if client.still_valid(), since it is
1052
# possible that the client timed out while establishing
1053
# the GnuTLS session.
1054
if not client.still_valid():
1055
ipc.write(u"INVALID %s\n" % client.name)
1058
ipc.write(u"SENDING %s\n" % client.name)
1060
while sent_size < len(client.secret):
1061
sent = session.send(client.secret[sent_size:])
1062
logger.debug(u"Sent: %d, remaining: %d",
1063
sent, len(client.secret)
1064
- (sent_size + sent))
1069
def peer_certificate(session):
1070
"Return the peer's OpenPGP certificate as a bytestring"
1071
# If not an OpenPGP certificate...
1072
if (gnutls.library.functions
1073
.gnutls_certificate_type_get(session._c_object)
1074
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1075
# ...do the normal thing
1076
return session.peer_certificate
1077
list_size = ctypes.c_uint(1)
1078
cert_list = (gnutls.library.functions
1079
.gnutls_certificate_get_peers
1080
(session._c_object, ctypes.byref(list_size)))
1081
if not bool(cert_list) and list_size.value != 0:
1082
raise gnutls.errors.GNUTLSError(u"error getting peer"
1084
if list_size.value == 0:
1087
return ctypes.string_at(cert.data, cert.size)
1090
def fingerprint(openpgp):
1091
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1092
# New GnuTLS "datum" with the OpenPGP public key
1093
datum = (gnutls.library.types
1094
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1097
ctypes.c_uint(len(openpgp))))
1098
# New empty GnuTLS certificate
1099
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1100
(gnutls.library.functions
1101
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1102
# Import the OpenPGP public key into the certificate
1103
(gnutls.library.functions
1104
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1105
gnutls.library.constants
1106
.GNUTLS_OPENPGP_FMT_RAW))
1107
# Verify the self signature in the key
1108
crtverify = ctypes.c_uint()
1109
(gnutls.library.functions
1110
.gnutls_openpgp_crt_verify_self(crt, 0,
1111
ctypes.byref(crtverify)))
1112
if crtverify.value != 0:
1113
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1114
raise (gnutls.errors.CertificateSecurityError
1116
# New buffer for the fingerprint
1117
buf = ctypes.create_string_buffer(20)
1118
buf_len = ctypes.c_size_t()
1119
# Get the fingerprint from the certificate into the buffer
1120
(gnutls.library.functions
1121
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1122
ctypes.byref(buf_len)))
1123
# Deinit the certificate
1124
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1125
# Convert the buffer to a Python bytestring
1126
fpr = ctypes.string_at(buf, buf_len.value)
1127
# Convert the bytestring to hexadecimal notation
1128
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1132
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1133
"""Like socketserver.ForkingMixIn, but also pass a pipe."""
1134
def process_request(self, request, client_address):
1135
"""Overrides and wraps the original process_request().
1137
This function creates a new pipe in self.pipe
1139
self.pipe = os.pipe()
1140
super(ForkingMixInWithPipe,
1141
self).process_request(request, client_address)
1142
os.close(self.pipe[1]) # close write end
1143
self.add_pipe(self.pipe[0])
1144
def add_pipe(self, pipe):
1145
"""Dummy function; override as necessary"""
1149
class IPv6_TCPServer(ForkingMixInWithPipe,
1150
socketserver.TCPServer, object):
1151
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
642
session = (gnutls.connection
643
.ClientSession(self.request,
647
line = self.request.makefile().readline()
648
logger.debug(u"Protocol version: %r", line)
650
if int(line.strip().split()[0]) > 1:
652
except (ValueError, IndexError, RuntimeError), error:
653
logger.error(u"Unknown protocol version: %s", error)
656
# Note: gnutls.connection.X509Credentials is really a generic
657
# GnuTLS certificate credentials object so long as no X.509
658
# keys are added to it. Therefore, we can use it here despite
659
# using OpenPGP certificates.
661
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
662
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
664
# Use a fallback default, since this MUST be set.
665
priority = self.server.settings.get("priority", "NORMAL")
666
(gnutls.library.functions
667
.gnutls_priority_set_direct(session._c_object,
672
except gnutls.errors.GNUTLSError, error:
673
logger.warning(u"Handshake failed: %s", error)
674
# Do not run session.bye() here: the session is not
675
# established. Just abandon the request.
678
fpr = fingerprint(peer_certificate(session))
679
except (TypeError, gnutls.errors.GNUTLSError), error:
680
logger.warning(u"Bad certificate: %s", error)
683
logger.debug(u"Fingerprint: %s", fpr)
684
for c in self.server.clients:
685
if c.fingerprint == fpr:
689
logger.warning(u"Client not found for fingerprint: %s",
693
# Have to check if client.still_valid(), since it is possible
694
# that the client timed out while establishing the GnuTLS
696
if not client.still_valid():
697
logger.warning(u"Client %(name)s is invalid",
701
## This won't work here, since we're in a fork.
702
# client.bump_timeout()
704
while sent_size < len(client.secret):
705
sent = session.send(client.secret[sent_size:])
706
logger.debug(u"Sent: %d, remaining: %d",
707
sent, len(client.secret)
708
- (sent_size + sent))
713
class IPv6_TCPServer(SocketServer.ForkingMixIn,
714
SocketServer.TCPServer, object):
715
"""IPv6 TCP server. Accepts 'None' as address and/or port.
717
settings: Server settings
718
clients: Set() of Client objects
1154
719
enabled: Boolean; whether this server is activated yet
1155
interface: None or a network interface name (string)
1156
use_ipv6: Boolean; to use IPv6 or not
1158
def __init__(self, server_address, RequestHandlerClass,
1159
interface=None, use_ipv6=True):
1160
self.interface = interface
1162
self.address_family = socket.AF_INET6
1163
socketserver.TCPServer.__init__(self, server_address,
1164
RequestHandlerClass)
721
address_family = socket.AF_INET6
722
def __init__(self, *args, **kwargs):
723
if "settings" in kwargs:
724
self.settings = kwargs["settings"]
725
del kwargs["settings"]
726
if "clients" in kwargs:
727
self.clients = kwargs["clients"]
728
del kwargs["clients"]
730
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1165
731
def server_bind(self):
1166
732
"""This overrides the normal server_bind() function
1167
733
to bind to an interface if one was specified, and also NOT to
1168
734
bind to an address or port if they were not specified."""
1169
if self.interface is not None:
1170
if SO_BINDTODEVICE is None:
1171
logger.error(u"SO_BINDTODEVICE does not exist;"
1172
u" cannot bind to interface %s",
1176
self.socket.setsockopt(socket.SOL_SOCKET,
1180
except socket.error, error:
1181
if error[0] == errno.EPERM:
1182
logger.error(u"No permission to"
1183
u" bind to interface %s",
1185
elif error[0] == errno.ENOPROTOOPT:
1186
logger.error(u"SO_BINDTODEVICE not available;"
1187
u" cannot bind to interface %s",
735
if self.settings["interface"]:
736
# 25 is from /usr/include/asm-i486/socket.h
737
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
739
self.socket.setsockopt(socket.SOL_SOCKET,
741
self.settings["interface"])
742
except socket.error, error:
743
if error[0] == errno.EPERM:
744
logger.error(u"No permission to"
745
u" bind to interface %s",
746
self.settings["interface"])
1191
749
# Only bind(2) the socket if we really need to.
1192
750
if self.server_address[0] or self.server_address[1]:
1193
751
if not self.server_address[0]:
1194
if self.address_family == socket.AF_INET6:
1195
any_address = u"::" # in6addr_any
1197
any_address = socket.INADDR_ANY
1198
self.server_address = (any_address,
753
self.server_address = (in6addr_any,
1199
754
self.server_address[1])
1200
755
elif not self.server_address[1]:
1201
756
self.server_address = (self.server_address[0],
1203
# if self.interface:
758
# if self.settings["interface"]:
1204
759
# self.server_address = (self.server_address[0],
1207
762
# if_nametoindex
1209
return socketserver.TCPServer.server_bind(self)
1212
class MandosServer(IPv6_TCPServer):
1216
clients: set of Client objects
1217
gnutls_priority GnuTLS priority string
1218
use_dbus: Boolean; to emit D-Bus signals or not
1220
Assumes a gobject.MainLoop event loop.
1222
def __init__(self, server_address, RequestHandlerClass,
1223
interface=None, use_ipv6=True, clients=None,
1224
gnutls_priority=None, use_dbus=True):
1225
self.enabled = False
1226
self.clients = clients
1227
if self.clients is None:
1228
self.clients = set()
1229
self.use_dbus = use_dbus
1230
self.gnutls_priority = gnutls_priority
1231
IPv6_TCPServer.__init__(self, server_address,
1232
RequestHandlerClass,
1233
interface = interface,
1234
use_ipv6 = use_ipv6)
765
return super(IPv6_TCPServer, self).server_bind()
1235
766
def server_activate(self):
1236
767
if self.enabled:
1237
return socketserver.TCPServer.server_activate(self)
768
return super(IPv6_TCPServer, self).server_activate()
1238
769
def enable(self):
1239
770
self.enabled = True
1240
def add_pipe(self, pipe):
1241
# Call "handle_ipc" for both data and EOF events
1242
gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1244
def handle_ipc(self, source, condition, file_objects={}):
1246
gobject.IO_IN: u"IN", # There is data to read.
1247
gobject.IO_OUT: u"OUT", # Data can be written (without
1249
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1250
gobject.IO_ERR: u"ERR", # Error condition.
1251
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1252
# broken, usually for pipes and
1255
conditions_string = ' | '.join(name
1257
condition_names.iteritems()
1258
if cond & condition)
1259
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1262
# Turn the pipe file descriptor into a Python file object
1263
if source not in file_objects:
1264
file_objects[source] = os.fdopen(source, u"r", 1)
1266
# Read a line from the file object
1267
cmdline = file_objects[source].readline()
1268
if not cmdline: # Empty line means end of file
1269
# close the IPC pipe
1270
file_objects[source].close()
1271
del file_objects[source]
1273
# Stop calling this function
1276
logger.debug(u"IPC command: %r", cmdline)
1278
# Parse and act on command
1279
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1281
if cmd == u"NOTFOUND":
1282
logger.warning(u"Client not found for fingerprint: %s",
1286
mandos_dbus_service.ClientNotFound(args)
1287
elif cmd == u"INVALID":
1288
for client in self.clients:
1289
if client.name == args:
1290
logger.warning(u"Client %s is invalid", args)
1296
logger.error(u"Unknown client %s is invalid", args)
1297
elif cmd == u"SENDING":
1298
for client in self.clients:
1299
if client.name == args:
1300
logger.info(u"Sending secret to %s", client.name)
1307
logger.error(u"Sending secret to unknown client %s",
1310
logger.error(u"Unknown IPC command: %r", cmdline)
1312
# Keep calling this function
1316
773
def string_to_delta(interval):
1317
774
"""Parse a string and return a datetime.timedelta
1319
>>> string_to_delta(u'7d')
776
>>> string_to_delta('7d')
1320
777
datetime.timedelta(7)
1321
>>> string_to_delta(u'60s')
778
>>> string_to_delta('60s')
1322
779
datetime.timedelta(0, 60)
1323
>>> string_to_delta(u'60m')
780
>>> string_to_delta('60m')
1324
781
datetime.timedelta(0, 3600)
1325
>>> string_to_delta(u'24h')
782
>>> string_to_delta('24h')
1326
783
datetime.timedelta(1)
1327
784
>>> string_to_delta(u'1w')
1328
785
datetime.timedelta(7)
1329
>>> string_to_delta(u'5m 30s')
786
>>> string_to_delta('5m 30s')
1330
787
datetime.timedelta(0, 330)
1332
789
timevalue = datetime.timedelta(0)