116
98
class AvahiService(object):
117
"""An Avahi (Zeroconf) service.
120
100
interface: integer; avahi.IF_UNSPEC or an interface index.
121
101
Used to optionally bind to the specified interface.
122
name: string; Example: u'Mandos'
123
type: string; Example: u'_mandos._tcp'.
124
See <http://www.dns-sd.org/ServiceTypes.html>
125
port: integer; what port to announce
126
TXT: list of strings; TXT record for the service
127
domain: string; Domain to publish on, default to .local if empty.
128
host: string; Host to publish records for, default is localhost
129
max_renames: integer; maximum number of renames
130
rename_count: integer; counter so we only rename after collisions
131
a sensible number of times
132
group: D-Bus Entry Group
134
bus: dbus.SystemBus()
102
name = string; Example: "Mandos"
103
type = string; Example: "_mandos._tcp".
104
See <http://www.dns-sd.org/ServiceTypes.html>
105
port = integer; what port to announce
106
TXT = list of strings; TXT record for the service
107
domain = string; Domain to publish on, default to .local if empty.
108
host = string; Host to publish records for, default to localhost
110
max_renames = integer; maximum number of renames
111
rename_count = integer; counter so we only rename after collisions
112
a sensible number of times
136
114
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
137
servicetype = None, port = None, TXT = None,
138
domain = u"", host = u"", max_renames = 32768,
139
protocol = avahi.PROTO_UNSPEC, bus = None):
115
type = None, port = None, TXT = None, domain = "",
116
host = "", max_renames = 12):
117
"""An Avahi (Zeroconf) service. """
140
118
self.interface = interface
142
self.type = servicetype
144
self.TXT = TXT if TXT is not None else []
145
126
self.domain = domain
147
128
self.rename_count = 0
148
self.max_renames = max_renames
149
self.protocol = protocol
150
self.group = None # our entry group
153
129
def rename(self):
154
130
"""Derived from the Avahi example code"""
155
131
if self.rename_count >= self.max_renames:
156
logger.critical(u"No suitable Zeroconf service name found"
157
u" after %i retries, exiting.",
159
raise AvahiServiceError(u"Too many renames")
160
self.name = unicode(self.server.GetAlternativeServiceName(self.name))
161
logger.info(u"Changing Zeroconf service name to %r ...",
163
syslogger.setFormatter(logging.Formatter
164
(u'Mandos (%s) [%%(process)d]:'
165
u' %%(levelname)s: %%(message)s'
132
logger.critical(u"No suitable service name found after %i"
133
u" retries, exiting.", rename_count)
134
raise AvahiServiceError("Too many renames")
135
name = server.GetAlternativeServiceName(name)
136
logger.notice(u"Changing name to %r ...", name)
170
except dbus.exceptions.DBusException, error:
171
logger.critical(u"DBusException: %s", error)
174
139
self.rename_count += 1
175
140
def remove(self):
176
141
"""Derived from the Avahi example code"""
177
if self.group is not None:
142
if group is not None:
180
145
"""Derived from the Avahi example code"""
181
if self.group is None:
182
self.group = dbus.Interface(
183
self.bus.get_object(avahi.DBUS_NAME,
184
self.server.EntryGroupNew()),
185
avahi.DBUS_INTERFACE_ENTRY_GROUP)
186
self.group.connect_to_signal('StateChanged',
188
.entry_group_state_changed)
189
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
190
self.name, self.type)
191
self.group.AddService(
194
dbus.UInt32(0), # flags
195
self.name, self.type,
196
self.domain, self.host,
197
dbus.UInt16(self.port),
198
avahi.string_array_to_txt_array(self.TXT))
200
def entry_group_state_changed(self, state, error):
201
"""Derived from the Avahi example code"""
202
logger.debug(u"Avahi entry group state change: %i", state)
204
if state == avahi.ENTRY_GROUP_ESTABLISHED:
205
logger.debug(u"Zeroconf service established.")
206
elif state == avahi.ENTRY_GROUP_COLLISION:
207
logger.warning(u"Zeroconf service name collision.")
209
elif state == avahi.ENTRY_GROUP_FAILURE:
210
logger.critical(u"Avahi: Error in group state changed %s",
212
raise AvahiGroupError(u"State changed: %s"
215
"""Derived from the Avahi example code"""
216
if self.group is not None:
219
def server_state_changed(self, state):
220
"""Derived from the Avahi example code"""
221
logger.debug(u"Avahi server state change: %i", state)
222
if state == avahi.SERVER_COLLISION:
223
logger.error(u"Zeroconf server name collision")
225
elif state == avahi.SERVER_RUNNING:
228
"""Derived from the Avahi example code"""
229
if self.server is None:
230
self.server = dbus.Interface(
231
self.bus.get_object(avahi.DBUS_NAME,
232
avahi.DBUS_PATH_SERVER),
233
avahi.DBUS_INTERFACE_SERVER)
234
self.server.connect_to_signal(u"StateChanged",
235
self.server_state_changed)
236
self.server_state_changed(self.server.GetState())
148
group = dbus.Interface\
149
(bus.get_object(avahi.DBUS_NAME,
150
server.EntryGroupNew()),
151
avahi.DBUS_INTERFACE_ENTRY_GROUP)
152
group.connect_to_signal('StateChanged',
153
entry_group_state_changed)
154
logger.debug(u"Adding service '%s' of type '%s' ...",
155
service.name, service.type)
157
self.interface, # interface
158
avahi.PROTO_INET6, # protocol
159
dbus.UInt32(0), # flags
160
self.name, self.type,
161
self.domain, self.host,
162
dbus.UInt16(self.port),
163
avahi.string_array_to_txt_array(self.TXT))
166
# From the Avahi example code:
167
group = None # our entry group
168
# End of Avahi example code
239
171
class Client(object):
240
172
"""A representation of a client host served by this server.
243
_approved: bool(); 'None' if not yet approved/disapproved
244
approval_delay: datetime.timedelta(); Time to wait for approval
245
approval_duration: datetime.timedelta(); Duration of one approval
246
checker: subprocess.Popen(); a running checker process used
247
to see if the client lives.
248
'None' if no process is running.
174
name: string; from the config file, used in log messages
175
fingerprint: string (40 or 32 hexadecimal digits); used to
176
uniquely identify the client
177
secret: bytestring; sent verbatim (over TLS) to client
178
fqdn: string (FQDN); available for use by the checker command
179
created: datetime.datetime(); object creation, not client host
180
last_checked_ok: datetime.datetime() or None if not yet checked OK
181
timeout: datetime.timedelta(); How long from last_checked_ok
182
until this client is invalid
183
interval: datetime.timedelta(); How often to start a new checker
184
stop_hook: If set, called by stop() as stop_hook(self)
185
checker: subprocess.Popen(); a running checker process used
186
to see if the client lives.
187
'None' if no process is running.
188
checker_initiator_tag: a gobject event source tag, or None
189
stop_initiator_tag: - '' -
249
190
checker_callback_tag: - '' -
250
checker_command: string; External command which is run to check
251
if client lives. %() expansions are done at
191
checker_command: string; External command which is run to check if
192
client lives. %() expansions are done at
252
193
runtime with vars(self) as dict, so that for
253
194
instance %(name)s can be used in the command.
254
checker_initiator_tag: a gobject event source tag, or None
255
created: datetime.datetime(); (UTC) object creation
256
current_checker_command: string; current running checker_command
257
disable_hook: If set, called by disable() as disable_hook(self)
258
disable_initiator_tag: - '' -
260
fingerprint: string (40 or 32 hexadecimal digits); used to
261
uniquely identify the client
262
host: string; available for use by the checker command
263
interval: datetime.timedelta(); How often to start a new checker
264
last_checked_ok: datetime.datetime(); (UTC) or None
265
last_enabled: datetime.datetime(); (UTC)
266
name: string; from the config file, used in log messages and
268
secret: bytestring; sent verbatim (over TLS) to client
269
timeout: datetime.timedelta(); How long from last_checked_ok
270
until this client is disabled
271
runtime_expansions: Allowed attributes for runtime expansion.
196
_timeout: Real variable for 'timeout'
197
_interval: Real variable for 'interval'
198
_timeout_milliseconds: Used when calling gobject.timeout_add()
199
_interval_milliseconds: - '' -
274
runtime_expansions = (u"approval_delay", u"approval_duration",
275
u"created", u"enabled", u"fingerprint",
276
u"host", u"interval", u"last_checked_ok",
277
u"last_enabled", u"name", u"timeout")
280
def _timedelta_to_milliseconds(td):
281
"Convert a datetime.timedelta() to milliseconds"
282
return ((td.days * 24 * 60 * 60 * 1000)
283
+ (td.seconds * 1000)
284
+ (td.microseconds // 1000))
286
def timeout_milliseconds(self):
287
"Return the 'timeout' attribute in milliseconds"
288
return self._timedelta_to_milliseconds(self.timeout)
290
def interval_milliseconds(self):
291
"Return the 'interval' attribute in milliseconds"
292
return self._timedelta_to_milliseconds(self.interval)
294
def approval_delay_milliseconds(self):
295
return self._timedelta_to_milliseconds(self.approval_delay)
297
def __init__(self, name = None, disable_hook=None, config=None):
298
"""Note: the 'checker' key in 'config' sets the
299
'checker_command' attribute and *not* the 'checker'
201
def _set_timeout(self, timeout):
202
"Setter function for 'timeout' attribute"
203
self._timeout = timeout
204
self._timeout_milliseconds = ((self.timeout.days
205
* 24 * 60 * 60 * 1000)
206
+ (self.timeout.seconds * 1000)
207
+ (self.timeout.microseconds
209
timeout = property(lambda self: self._timeout,
212
def _set_interval(self, interval):
213
"Setter function for 'interval' attribute"
214
self._interval = interval
215
self._interval_milliseconds = ((self.interval.days
216
* 24 * 60 * 60 * 1000)
217
+ (self.interval.seconds
219
+ (self.interval.microseconds
221
interval = property(lambda self: self._interval,
224
def __init__(self, name=None, stop_hook=None, fingerprint=None,
225
secret=None, secfile=None, fqdn=None, timeout=None,
226
interval=-1, checker=None):
227
"""Note: the 'checker' argument sets the 'checker_command'
228
attribute and not the 'checker' attribute.."""
304
230
logger.debug(u"Creating client %r", self.name)
305
# Uppercase and remove spaces from fingerprint for later
306
# comparison purposes with return value from the fingerprint()
308
self.fingerprint = (config[u"fingerprint"].upper()
231
# Uppercase and remove spaces from fingerprint
232
# for later comparison purposes with return value of
233
# the fingerprint() function
234
self.fingerprint = fingerprint.upper().replace(u" ", u"")
310
235
logger.debug(u" Fingerprint: %s", self.fingerprint)
311
if u"secret" in config:
312
self.secret = config[u"secret"].decode(u"base64")
313
elif u"secfile" in config:
314
with open(os.path.expanduser(os.path.expandvars
315
(config[u"secfile"])),
317
self.secret = secfile.read()
237
self.secret = secret.decode(u"base64")
240
self.secret = sf.read()
319
243
raise TypeError(u"No secret or secfile for client %s"
321
self.host = config.get(u"host", u"")
322
self.created = datetime.datetime.utcnow()
324
self.last_enabled = None
246
self.created = datetime.datetime.now()
325
247
self.last_checked_ok = None
326
self.timeout = string_to_delta(config[u"timeout"])
327
self.interval = string_to_delta(config[u"interval"])
328
self.disable_hook = disable_hook
248
self.timeout = string_to_delta(timeout)
249
self.interval = string_to_delta(interval)
250
self.stop_hook = stop_hook
329
251
self.checker = None
330
252
self.checker_initiator_tag = None
331
self.disable_initiator_tag = None
253
self.stop_initiator_tag = None
332
254
self.checker_callback_tag = None
333
self.checker_command = config[u"checker"]
334
self.current_checker_command = None
335
self.last_connect = None
336
self._approved = None
337
self.approved_by_default = config.get(u"approved_by_default",
339
self.approvals_pending = 0
340
self.approval_delay = string_to_delta(
341
config[u"approval_delay"])
342
self.approval_duration = string_to_delta(
343
config[u"approval_duration"])
344
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
346
def send_changedstate(self):
347
self.changedstate.acquire()
348
self.changedstate.notify_all()
349
self.changedstate.release()
255
self.check_command = checker
352
257
"""Start this client's checker and timeout hooks"""
353
if getattr(self, u"enabled", False):
356
self.send_changedstate()
357
self.last_enabled = datetime.datetime.utcnow()
358
258
# Schedule a new checker to be started an 'interval' from now,
359
259
# and every interval from then on.
360
self.checker_initiator_tag = (gobject.timeout_add
361
(self.interval_milliseconds(),
363
# Schedule a disable() when 'timeout' has passed
364
self.disable_initiator_tag = (gobject.timeout_add
365
(self.timeout_milliseconds(),
260
self.checker_initiator_tag = gobject.timeout_add\
261
(self._interval_milliseconds,
368
263
# Also start a new checker *right now*.
369
264
self.start_checker()
371
def disable(self, quiet=True):
372
"""Disable this client."""
373
if not getattr(self, "enabled", False):
265
# Schedule a stop() when 'timeout' has passed
266
self.stop_initiator_tag = gobject.timeout_add\
267
(self._timeout_milliseconds,
271
The possibility that a client might be restarted is left open,
272
but not currently used."""
273
# If this client doesn't have a secret, it is already stopped.
275
logger.debug(u"Stopping client %s", self.name)
376
self.send_changedstate()
378
logger.info(u"Disabling client %s", self.name)
379
if getattr(self, u"disable_initiator_tag", False):
380
gobject.source_remove(self.disable_initiator_tag)
381
self.disable_initiator_tag = None
382
if getattr(self, u"checker_initiator_tag", False):
279
if getattr(self, "stop_initiator_tag", False):
280
gobject.source_remove(self.stop_initiator_tag)
281
self.stop_initiator_tag = None
282
if getattr(self, "checker_initiator_tag", False):
383
283
gobject.source_remove(self.checker_initiator_tag)
384
284
self.checker_initiator_tag = None
385
285
self.stop_checker()
386
if self.disable_hook:
387
self.disable_hook(self)
389
288
# Do not run this again if called by a gobject.timeout_add
392
290
def __del__(self):
393
self.disable_hook = None
396
def checker_callback(self, pid, condition, command):
291
self.stop_hook = None
293
def checker_callback(self, pid, condition):
397
294
"""The checker has completed, so take appropriate actions."""
295
now = datetime.datetime.now()
398
296
self.checker_callback_tag = None
399
297
self.checker = None
400
if os.WIFEXITED(condition):
401
exitstatus = os.WEXITSTATUS(condition)
403
logger.info(u"Checker for %(name)s succeeded",
407
logger.info(u"Checker for %(name)s failed",
298
if os.WIFEXITED(condition) \
299
and (os.WEXITSTATUS(condition) == 0):
300
logger.debug(u"Checker for %(name)s succeeded",
302
self.last_checked_ok = now
303
gobject.source_remove(self.stop_initiator_tag)
304
self.stop_initiator_tag = gobject.timeout_add\
305
(self._timeout_milliseconds,
307
elif not os.WIFEXITED(condition):
410
308
logger.warning(u"Checker for %(name)s crashed?",
413
def checked_ok(self):
414
"""Bump up the timeout for this client.
416
This should only be called when the client has been seen,
419
self.last_checked_ok = datetime.datetime.utcnow()
420
gobject.source_remove(self.disable_initiator_tag)
421
self.disable_initiator_tag = (gobject.timeout_add
422
(self.timeout_milliseconds(),
311
logger.debug(u"Checker for %(name)s failed",
425
313
def start_checker(self):
426
314
"""Start a new checker subprocess if one is not running.
428
315
If a checker already exists, leave it running and do
430
317
# The reason for not killing a running checker is that if we
433
320
# client would inevitably timeout, since no checker would get
434
321
# a chance to run to completion. If we instead leave running
435
322
# checkers alone, the checker would have to take more time
436
# than 'timeout' for the client to be disabled, which is as it
439
# If a checker exists, make sure it is not a zombie
441
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
442
except (AttributeError, OSError), error:
443
if (isinstance(error, OSError)
444
and error.errno != errno.ECHILD):
448
logger.warning(u"Checker was a zombie")
449
gobject.source_remove(self.checker_callback_tag)
450
self.checker_callback(pid, status,
451
self.current_checker_command)
452
# Start a new checker if needed
323
# than 'timeout' for the client to be declared invalid, which
324
# is as it should be.
453
325
if self.checker is None:
455
# In case checker_command has exactly one % operator
456
command = self.checker_command % self.host
327
# In case check_command has exactly one % operator
328
command = self.check_command % self.fqdn
457
329
except TypeError:
458
330
# Escape attributes for the shell
459
escaped_attrs = dict(
461
re.escape(unicode(str(getattr(self, attr, u"")),
465
self.runtime_expansions)
331
escaped_attrs = dict((key, re.escape(str(val)))
333
vars(self).iteritems())
468
command = self.checker_command % escaped_attrs
335
command = self.check_command % escaped_attrs
469
336
except TypeError, error:
470
337
logger.error(u'Could not format string "%s":'
471
u' %s', self.checker_command, error)
338
u' %s', self.check_command, error)
472
339
return True # Try again later
473
self.current_checker_command = command
475
logger.info(u"Starting checker %r for %s",
477
# We don't need to redirect stdout and stderr, since
478
# in normal mode, that is already done by daemon(),
479
# and in debug mode we don't want to. (Stdin is
480
# always replaced by /dev/null.)
341
logger.debug(u"Starting checker %r for %s",
481
343
self.checker = subprocess.Popen(command,
483
shell=True, cwd=u"/")
484
self.checker_callback_tag = (gobject.child_watch_add
486
self.checker_callback,
488
# The checker may have completed before the gobject
489
# watch was added. Check for this.
490
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
492
gobject.source_remove(self.checker_callback_tag)
493
self.checker_callback(pid, status, command)
494
except OSError, error:
346
self.checker_callback_tag = gobject.child_watch_add\
348
self.checker_callback)
349
except subprocess.OSError, error:
495
350
logger.error(u"Failed to start subprocess: %s",
497
352
# Re-run this periodically if run by gobject.timeout_add
500
354
def stop_checker(self):
501
355
"""Force the checker process, if any, to stop."""
502
356
if self.checker_callback_tag:
503
357
gobject.source_remove(self.checker_callback_tag)
504
358
self.checker_callback_tag = None
505
if getattr(self, u"checker", None) is None:
359
if getattr(self, "checker", None) is None:
507
logger.debug(u"Stopping checker for %(name)s", vars(self))
361
logger.debug("Stopping checker for %(name)s", vars(self))
509
363
os.kill(self.checker.pid, signal.SIGTERM)
511
365
#if self.checker.poll() is None:
512
366
# os.kill(self.checker.pid, signal.SIGKILL)
513
367
except OSError, error:
514
368
if error.errno != errno.ESRCH: # No such process
516
370
self.checker = None
518
def dbus_service_property(dbus_interface, signature=u"v",
519
access=u"readwrite", byte_arrays=False):
520
"""Decorators for marking methods of a DBusObjectWithProperties to
521
become properties on the D-Bus.
523
The decorated method will be called with no arguments by "Get"
524
and with one argument by "Set".
526
The parameters, where they are supported, are the same as
527
dbus.service.method, except there is only "signature", since the
528
type from Get() and the type sent to Set() is the same.
530
# Encoding deeply encoded byte arrays is not supported yet by the
531
# "Set" method, so we fail early here:
532
if byte_arrays and signature != u"ay":
533
raise ValueError(u"Byte arrays not supported for non-'ay'"
534
u" signature %r" % signature)
536
func._dbus_is_property = True
537
func._dbus_interface = dbus_interface
538
func._dbus_signature = signature
539
func._dbus_access = access
540
func._dbus_name = func.__name__
541
if func._dbus_name.endswith(u"_dbus_property"):
542
func._dbus_name = func._dbus_name[:-14]
543
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
548
class DBusPropertyException(dbus.exceptions.DBusException):
549
"""A base class for D-Bus property-related exceptions
551
def __unicode__(self):
552
return unicode(str(self))
555
class DBusPropertyAccessException(DBusPropertyException):
556
"""A property's access permissions disallows an operation.
561
class DBusPropertyNotFound(DBusPropertyException):
562
"""An attempt was made to access a non-existing property.
567
class DBusObjectWithProperties(dbus.service.Object):
568
"""A D-Bus object with properties.
570
Classes inheriting from this can use the dbus_service_property
571
decorator to expose methods as D-Bus properties. It exposes the
572
standard Get(), Set(), and GetAll() methods on the D-Bus.
576
def _is_dbus_property(obj):
577
return getattr(obj, u"_dbus_is_property", False)
579
def _get_all_dbus_properties(self):
580
"""Returns a generator of (name, attribute) pairs
582
return ((prop._dbus_name, prop)
584
inspect.getmembers(self, self._is_dbus_property))
586
def _get_dbus_property(self, interface_name, property_name):
587
"""Returns a bound method if one exists which is a D-Bus
588
property with the specified name and interface.
590
for name in (property_name,
591
property_name + u"_dbus_property"):
592
prop = getattr(self, name, None)
594
or not self._is_dbus_property(prop)
595
or prop._dbus_name != property_name
596
or (interface_name and prop._dbus_interface
597
and interface_name != prop._dbus_interface)):
601
raise DBusPropertyNotFound(self.dbus_object_path + u":"
602
+ interface_name + u"."
605
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
607
def Get(self, interface_name, property_name):
608
"""Standard D-Bus property Get() method, see D-Bus standard.
610
prop = self._get_dbus_property(interface_name, property_name)
611
if prop._dbus_access == u"write":
612
raise DBusPropertyAccessException(property_name)
614
if not hasattr(value, u"variant_level"):
616
return type(value)(value, variant_level=value.variant_level+1)
618
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
619
def Set(self, interface_name, property_name, value):
620
"""Standard D-Bus property Set() method, see D-Bus standard.
622
prop = self._get_dbus_property(interface_name, property_name)
623
if prop._dbus_access == u"read":
624
raise DBusPropertyAccessException(property_name)
625
if prop._dbus_get_args_options[u"byte_arrays"]:
626
# The byte_arrays option is not supported yet on
627
# signatures other than "ay".
628
if prop._dbus_signature != u"ay":
630
value = dbus.ByteArray(''.join(unichr(byte)
634
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
635
out_signature=u"a{sv}")
636
def GetAll(self, interface_name):
637
"""Standard D-Bus property GetAll() method, see D-Bus
640
Note: Will not include properties with access="write".
643
for name, prop in self._get_all_dbus_properties():
645
and interface_name != prop._dbus_interface):
646
# Interface non-empty but did not match
648
# Ignore write-only properties
649
if prop._dbus_access == u"write":
652
if not hasattr(value, u"variant_level"):
655
all[name] = type(value)(value, variant_level=
656
value.variant_level+1)
657
return dbus.Dictionary(all, signature=u"sv")
659
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
661
path_keyword='object_path',
662
connection_keyword='connection')
663
def Introspect(self, object_path, connection):
664
"""Standard D-Bus method, overloaded to insert property tags.
666
xmlstring = dbus.service.Object.Introspect(self, object_path,
669
document = xml.dom.minidom.parseString(xmlstring)
670
def make_tag(document, name, prop):
671
e = document.createElement(u"property")
672
e.setAttribute(u"name", name)
673
e.setAttribute(u"type", prop._dbus_signature)
674
e.setAttribute(u"access", prop._dbus_access)
676
for if_tag in document.getElementsByTagName(u"interface"):
677
for tag in (make_tag(document, name, prop)
679
in self._get_all_dbus_properties()
680
if prop._dbus_interface
681
== if_tag.getAttribute(u"name")):
682
if_tag.appendChild(tag)
683
# Add the names to the return values for the
684
# "org.freedesktop.DBus.Properties" methods
685
if (if_tag.getAttribute(u"name")
686
== u"org.freedesktop.DBus.Properties"):
687
for cn in if_tag.getElementsByTagName(u"method"):
688
if cn.getAttribute(u"name") == u"Get":
689
for arg in cn.getElementsByTagName(u"arg"):
690
if (arg.getAttribute(u"direction")
692
arg.setAttribute(u"name", u"value")
693
elif cn.getAttribute(u"name") == u"GetAll":
694
for arg in cn.getElementsByTagName(u"arg"):
695
if (arg.getAttribute(u"direction")
697
arg.setAttribute(u"name", u"props")
698
xmlstring = document.toxml(u"utf-8")
700
except (AttributeError, xml.dom.DOMException,
701
xml.parsers.expat.ExpatError), error:
702
logger.error(u"Failed to override Introspection method",
707
class ClientDBus(Client, DBusObjectWithProperties):
708
"""A Client class using D-Bus
711
dbus_object_path: dbus.ObjectPath
712
bus: dbus.SystemBus()
715
runtime_expansions = (Client.runtime_expansions
716
+ (u"dbus_object_path",))
718
# dbus.service.Object doesn't use super(), so we can't either.
720
def __init__(self, bus = None, *args, **kwargs):
721
self._approvals_pending = 0
723
Client.__init__(self, *args, **kwargs)
724
# Only now, when this client is initialized, can it show up on
726
self.dbus_object_path = (dbus.ObjectPath
728
+ self.name.replace(u".", u"_")))
729
DBusObjectWithProperties.__init__(self, self.bus,
730
self.dbus_object_path)
732
def _get_approvals_pending(self):
733
return self._approvals_pending
734
def _set_approvals_pending(self, value):
735
old_value = self._approvals_pending
736
self._approvals_pending = value
738
if (hasattr(self, "dbus_object_path")
739
and bval is not bool(old_value)):
740
dbus_bool = dbus.Boolean(bval, variant_level=1)
741
self.PropertyChanged(dbus.String(u"ApprovalPending"),
744
approvals_pending = property(_get_approvals_pending,
745
_set_approvals_pending)
746
del _get_approvals_pending, _set_approvals_pending
749
def _datetime_to_dbus(dt, variant_level=0):
750
"""Convert a UTC datetime.datetime() to a D-Bus type."""
751
return dbus.String(dt.isoformat(),
752
variant_level=variant_level)
755
oldstate = getattr(self, u"enabled", False)
756
r = Client.enable(self)
757
if oldstate != self.enabled:
759
self.PropertyChanged(dbus.String(u"Enabled"),
760
dbus.Boolean(True, variant_level=1))
761
self.PropertyChanged(
762
dbus.String(u"LastEnabled"),
763
self._datetime_to_dbus(self.last_enabled,
767
def disable(self, quiet = False):
768
oldstate = getattr(self, u"enabled", False)
769
r = Client.disable(self, quiet=quiet)
770
if not quiet and oldstate != self.enabled:
772
self.PropertyChanged(dbus.String(u"Enabled"),
773
dbus.Boolean(False, variant_level=1))
776
def __del__(self, *args, **kwargs):
778
self.remove_from_connection()
781
if hasattr(DBusObjectWithProperties, u"__del__"):
782
DBusObjectWithProperties.__del__(self, *args, **kwargs)
783
Client.__del__(self, *args, **kwargs)
785
def checker_callback(self, pid, condition, command,
787
self.checker_callback_tag = None
790
self.PropertyChanged(dbus.String(u"CheckerRunning"),
791
dbus.Boolean(False, variant_level=1))
792
if os.WIFEXITED(condition):
793
exitstatus = os.WEXITSTATUS(condition)
795
self.CheckerCompleted(dbus.Int16(exitstatus),
796
dbus.Int64(condition),
797
dbus.String(command))
800
self.CheckerCompleted(dbus.Int16(-1),
801
dbus.Int64(condition),
802
dbus.String(command))
804
return Client.checker_callback(self, pid, condition, command,
807
def checked_ok(self, *args, **kwargs):
808
r = Client.checked_ok(self, *args, **kwargs)
810
self.PropertyChanged(
811
dbus.String(u"LastCheckedOK"),
812
(self._datetime_to_dbus(self.last_checked_ok,
816
def start_checker(self, *args, **kwargs):
817
old_checker = self.checker
818
if self.checker is not None:
819
old_checker_pid = self.checker.pid
821
old_checker_pid = None
822
r = Client.start_checker(self, *args, **kwargs)
823
# Only if new checker process was started
824
if (self.checker is not None
825
and old_checker_pid != self.checker.pid):
827
self.CheckerStarted(self.current_checker_command)
828
self.PropertyChanged(
829
dbus.String(u"CheckerRunning"),
830
dbus.Boolean(True, variant_level=1))
833
def stop_checker(self, *args, **kwargs):
834
old_checker = getattr(self, u"checker", None)
835
r = Client.stop_checker(self, *args, **kwargs)
836
if (old_checker is not None
837
and getattr(self, u"checker", None) is None):
838
self.PropertyChanged(dbus.String(u"CheckerRunning"),
839
dbus.Boolean(False, variant_level=1))
842
def _reset_approved(self):
843
self._approved = None
846
def approve(self, value=True):
847
self.send_changedstate()
848
self._approved = value
849
gobject.timeout_add(self._timedelta_to_milliseconds
850
(self.approval_duration),
851
self._reset_approved)
854
## D-Bus methods, signals & properties
855
_interface = u"se.bsnet.fukt.Mandos.Client"
859
# CheckerCompleted - signal
860
@dbus.service.signal(_interface, signature=u"nxs")
861
def CheckerCompleted(self, exitcode, waitstatus, command):
865
# CheckerStarted - signal
866
@dbus.service.signal(_interface, signature=u"s")
867
def CheckerStarted(self, command):
871
# PropertyChanged - signal
872
@dbus.service.signal(_interface, signature=u"sv")
873
def PropertyChanged(self, property, value):
878
@dbus.service.signal(_interface)
881
Is sent after a successful transfer of secret from the Mandos
882
server to mandos-client
887
@dbus.service.signal(_interface, signature=u"s")
888
def Rejected(self, reason):
892
# NeedApproval - signal
893
@dbus.service.signal(_interface, signature=u"tb")
894
def NeedApproval(self, timeout, default):
901
@dbus.service.method(_interface, in_signature=u"b")
902
def Approve(self, value):
906
@dbus.service.method(_interface)
908
return self.checked_ok()
911
@dbus.service.method(_interface)
916
# StartChecker - method
917
@dbus.service.method(_interface)
918
def StartChecker(self):
923
@dbus.service.method(_interface)
928
# StopChecker - method
929
@dbus.service.method(_interface)
930
def StopChecker(self):
935
# ApprovalPending - property
936
@dbus_service_property(_interface, signature=u"b", access=u"read")
937
def ApprovalPending_dbus_property(self):
938
return dbus.Boolean(bool(self.approvals_pending))
940
# ApprovedByDefault - property
941
@dbus_service_property(_interface, signature=u"b",
943
def ApprovedByDefault_dbus_property(self, value=None):
944
if value is None: # get
945
return dbus.Boolean(self.approved_by_default)
946
self.approved_by_default = bool(value)
948
self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
949
dbus.Boolean(value, variant_level=1))
951
# ApprovalDelay - property
952
@dbus_service_property(_interface, signature=u"t",
954
def ApprovalDelay_dbus_property(self, value=None):
955
if value is None: # get
956
return dbus.UInt64(self.approval_delay_milliseconds())
957
self.approval_delay = datetime.timedelta(0, 0, 0, value)
959
self.PropertyChanged(dbus.String(u"ApprovalDelay"),
960
dbus.UInt64(value, variant_level=1))
962
# ApprovalDuration - property
963
@dbus_service_property(_interface, signature=u"t",
965
def ApprovalDuration_dbus_property(self, value=None):
966
if value is None: # get
967
return dbus.UInt64(self._timedelta_to_milliseconds(
968
self.approval_duration))
969
self.approval_duration = datetime.timedelta(0, 0, 0, value)
971
self.PropertyChanged(dbus.String(u"ApprovalDuration"),
972
dbus.UInt64(value, variant_level=1))
975
@dbus_service_property(_interface, signature=u"s", access=u"read")
976
def Name_dbus_property(self):
977
return dbus.String(self.name)
979
# Fingerprint - property
980
@dbus_service_property(_interface, signature=u"s", access=u"read")
981
def Fingerprint_dbus_property(self):
982
return dbus.String(self.fingerprint)
985
@dbus_service_property(_interface, signature=u"s",
987
def Host_dbus_property(self, value=None):
988
if value is None: # get
989
return dbus.String(self.host)
992
self.PropertyChanged(dbus.String(u"Host"),
993
dbus.String(value, variant_level=1))
996
@dbus_service_property(_interface, signature=u"s", access=u"read")
997
def Created_dbus_property(self):
998
return dbus.String(self._datetime_to_dbus(self.created))
1000
# LastEnabled - property
1001
@dbus_service_property(_interface, signature=u"s", access=u"read")
1002
def LastEnabled_dbus_property(self):
1003
if self.last_enabled is None:
1004
return dbus.String(u"")
1005
return dbus.String(self._datetime_to_dbus(self.last_enabled))
1007
# Enabled - property
1008
@dbus_service_property(_interface, signature=u"b",
1009
access=u"readwrite")
1010
def Enabled_dbus_property(self, value=None):
1011
if value is None: # get
1012
return dbus.Boolean(self.enabled)
1018
# LastCheckedOK - property
1019
@dbus_service_property(_interface, signature=u"s",
1020
access=u"readwrite")
1021
def LastCheckedOK_dbus_property(self, value=None):
1022
if value is not None:
371
def still_valid(self):
372
"""Has the timeout not yet passed for this client?"""
373
now = datetime.datetime.now()
1025
374
if self.last_checked_ok is None:
1026
return dbus.String(u"")
1027
return dbus.String(self._datetime_to_dbus(self
1030
# Timeout - property
1031
@dbus_service_property(_interface, signature=u"t",
1032
access=u"readwrite")
1033
def Timeout_dbus_property(self, value=None):
1034
if value is None: # get
1035
return dbus.UInt64(self.timeout_milliseconds())
1036
self.timeout = datetime.timedelta(0, 0, 0, value)
1038
self.PropertyChanged(dbus.String(u"Timeout"),
1039
dbus.UInt64(value, variant_level=1))
1040
if getattr(self, u"disable_initiator_tag", None) is None:
1042
# Reschedule timeout
1043
gobject.source_remove(self.disable_initiator_tag)
1044
self.disable_initiator_tag = None
1045
time_to_die = (self.
1046
_timedelta_to_milliseconds((self
1051
if time_to_die <= 0:
1052
# The timeout has passed
1055
self.disable_initiator_tag = (gobject.timeout_add
1056
(time_to_die, self.disable))
1058
# Interval - property
1059
@dbus_service_property(_interface, signature=u"t",
1060
access=u"readwrite")
1061
def Interval_dbus_property(self, value=None):
1062
if value is None: # get
1063
return dbus.UInt64(self.interval_milliseconds())
1064
self.interval = datetime.timedelta(0, 0, 0, value)
1066
self.PropertyChanged(dbus.String(u"Interval"),
1067
dbus.UInt64(value, variant_level=1))
1068
if getattr(self, u"checker_initiator_tag", None) is None:
1070
# Reschedule checker run
1071
gobject.source_remove(self.checker_initiator_tag)
1072
self.checker_initiator_tag = (gobject.timeout_add
1073
(value, self.start_checker))
1074
self.start_checker() # Start one now, too
1076
# Checker - property
1077
@dbus_service_property(_interface, signature=u"s",
1078
access=u"readwrite")
1079
def Checker_dbus_property(self, value=None):
1080
if value is None: # get
1081
return dbus.String(self.checker_command)
1082
self.checker_command = value
1084
self.PropertyChanged(dbus.String(u"Checker"),
1085
dbus.String(self.checker_command,
1088
# CheckerRunning - property
1089
@dbus_service_property(_interface, signature=u"b",
1090
access=u"readwrite")
1091
def CheckerRunning_dbus_property(self, value=None):
1092
if value is None: # get
1093
return dbus.Boolean(self.checker is not None)
1095
self.start_checker()
1099
# ObjectPath - property
1100
@dbus_service_property(_interface, signature=u"o", access=u"read")
1101
def ObjectPath_dbus_property(self):
1102
return self.dbus_object_path # is already a dbus.ObjectPath
1105
@dbus_service_property(_interface, signature=u"ay",
1106
access=u"write", byte_arrays=True)
1107
def Secret_dbus_property(self, value):
1108
self.secret = str(value)
1113
class ProxyClient(object):
1114
def __init__(self, child_pipe, fpr, address):
1115
self._pipe = child_pipe
1116
self._pipe.send(('init', fpr, address))
1117
if not self._pipe.recv():
1120
def __getattribute__(self, name):
1121
if(name == '_pipe'):
1122
return super(ProxyClient, self).__getattribute__(name)
1123
self._pipe.send(('getattr', name))
1124
data = self._pipe.recv()
1125
if data[0] == 'data':
1127
if data[0] == 'function':
1128
def func(*args, **kwargs):
1129
self._pipe.send(('funcall', name, args, kwargs))
1130
return self._pipe.recv()[1]
1133
def __setattr__(self, name, value):
1134
if(name == '_pipe'):
1135
return super(ProxyClient, self).__setattr__(name, value)
1136
self._pipe.send(('setattr', name, value))
1139
class ClientHandler(socketserver.BaseRequestHandler, object):
1140
"""A class to handle client connections.
1142
Instantiated once for each connection to handle it.
375
return now < (self.created + self.timeout)
377
return now < (self.last_checked_ok + self.timeout)
380
def peer_certificate(session):
381
"Return the peer's OpenPGP certificate as a bytestring"
382
# If not an OpenPGP certificate...
383
if gnutls.library.functions.gnutls_certificate_type_get\
384
(session._c_object) \
385
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
386
# ...do the normal thing
387
return session.peer_certificate
388
list_size = ctypes.c_uint()
389
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
390
(session._c_object, ctypes.byref(list_size))
391
if list_size.value == 0:
394
return ctypes.string_at(cert.data, cert.size)
397
def fingerprint(openpgp):
398
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
399
# New empty GnuTLS certificate
400
crt = gnutls.library.types.gnutls_openpgp_crt_t()
401
gnutls.library.functions.gnutls_openpgp_crt_init\
403
# New GnuTLS "datum" with the OpenPGP public key
404
datum = gnutls.library.types.gnutls_datum_t\
405
(ctypes.cast(ctypes.c_char_p(openpgp),
406
ctypes.POINTER(ctypes.c_ubyte)),
407
ctypes.c_uint(len(openpgp)))
408
# Import the OpenPGP public key into the certificate
409
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
412
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
413
# New buffer for the fingerprint
414
buffer = ctypes.create_string_buffer(20)
415
buffer_length = ctypes.c_size_t()
416
# Get the fingerprint from the certificate into the buffer
417
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
418
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
419
# Deinit the certificate
420
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
421
# Convert the buffer to a Python bytestring
422
fpr = ctypes.string_at(buffer, buffer_length.value)
423
# Convert the bytestring to hexadecimal notation
424
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
428
class tcp_handler(SocketServer.BaseRequestHandler, object):
429
"""A TCP request handler class.
430
Instantiated by IPv6_TCPServer for each request to handle it.
1143
431
Note: This will run in its own forked process."""
1145
433
def handle(self):
1146
with contextlib.closing(self.server.child_pipe) as child_pipe:
1147
logger.info(u"TCP connection from: %s",
1148
unicode(self.client_address))
1149
logger.debug(u"Pipe FD: %d",
1150
self.server.child_pipe.fileno())
1152
session = (gnutls.connection
1153
.ClientSession(self.request,
1155
.X509Credentials()))
1157
# Note: gnutls.connection.X509Credentials is really a
1158
# generic GnuTLS certificate credentials object so long as
1159
# no X.509 keys are added to it. Therefore, we can use it
1160
# here despite using OpenPGP certificates.
1162
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1163
# u"+AES-256-CBC", u"+SHA1",
1164
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1166
# Use a fallback default, since this MUST be set.
1167
priority = self.server.gnutls_priority
1168
if priority is None:
1169
priority = u"NORMAL"
1170
(gnutls.library.functions
1171
.gnutls_priority_set_direct(session._c_object,
1174
# Start communication using the Mandos protocol
1175
# Get protocol number
1176
line = self.request.makefile().readline()
1177
logger.debug(u"Protocol version: %r", line)
1179
if int(line.strip().split()[0]) > 1:
1181
except (ValueError, IndexError, RuntimeError), error:
1182
logger.error(u"Unknown protocol version: %s", error)
1185
# Start GnuTLS connection
1188
except gnutls.errors.GNUTLSError, error:
1189
logger.warning(u"Handshake failed: %s", error)
1190
# Do not run session.bye() here: the session is not
1191
# established. Just abandon the request.
1193
logger.debug(u"Handshake succeeded")
1195
approval_required = False
1198
fpr = self.fingerprint(self.peer_certificate
1200
except (TypeError, gnutls.errors.GNUTLSError), error:
1201
logger.warning(u"Bad certificate: %s", error)
1203
logger.debug(u"Fingerprint: %s", fpr)
1206
client = ProxyClient(child_pipe, fpr,
1207
self.client_address)
1211
if client.approval_delay:
1212
delay = client.approval_delay
1213
client.approvals_pending += 1
1214
approval_required = True
1217
if not client.enabled:
1218
logger.warning(u"Client %s is disabled",
1220
if self.server.use_dbus:
1222
client.Rejected("Disabled")
1225
if client._approved or not client.approval_delay:
1226
#We are approved or approval is disabled
1228
elif client._approved is None:
1229
logger.info(u"Client %s needs approval",
1231
if self.server.use_dbus:
1233
client.NeedApproval(
1234
client.approval_delay_milliseconds(),
1235
client.approved_by_default)
1237
logger.warning(u"Client %s was not approved",
1239
if self.server.use_dbus:
1241
client.Rejected("Denied")
1244
#wait until timeout or approved
1245
#x = float(client._timedelta_to_milliseconds(delay))
1246
time = datetime.datetime.now()
1247
client.changedstate.acquire()
1248
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1249
client.changedstate.release()
1250
time2 = datetime.datetime.now()
1251
if (time2 - time) >= delay:
1252
if not client.approved_by_default:
1253
logger.warning("Client %s timed out while"
1254
" waiting for approval",
1256
if self.server.use_dbus:
1258
client.Rejected("Approval timed out")
1263
delay -= time2 - time
1266
while sent_size < len(client.secret):
1268
sent = session.send(client.secret[sent_size:])
1269
except (gnutls.errors.GNUTLSError), error:
1270
logger.warning("gnutls send failed")
1272
logger.debug(u"Sent: %d, remaining: %d",
1273
sent, len(client.secret)
1274
- (sent_size + sent))
1277
logger.info(u"Sending secret to %s", client.name)
1278
# bump the timeout as if seen
1280
if self.server.use_dbus:
1285
if approval_required:
1286
client.approvals_pending -= 1
1289
except (gnutls.errors.GNUTLSError), error:
1290
logger.warning("GnuTLS bye failed")
1293
def peer_certificate(session):
1294
"Return the peer's OpenPGP certificate as a bytestring"
1295
# If not an OpenPGP certificate...
1296
if (gnutls.library.functions
1297
.gnutls_certificate_type_get(session._c_object)
1298
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1299
# ...do the normal thing
1300
return session.peer_certificate
1301
list_size = ctypes.c_uint(1)
1302
cert_list = (gnutls.library.functions
1303
.gnutls_certificate_get_peers
1304
(session._c_object, ctypes.byref(list_size)))
1305
if not bool(cert_list) and list_size.value != 0:
1306
raise gnutls.errors.GNUTLSError(u"error getting peer"
1308
if list_size.value == 0:
1311
return ctypes.string_at(cert.data, cert.size)
1314
def fingerprint(openpgp):
1315
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1316
# New GnuTLS "datum" with the OpenPGP public key
1317
datum = (gnutls.library.types
1318
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1321
ctypes.c_uint(len(openpgp))))
1322
# New empty GnuTLS certificate
1323
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1324
(gnutls.library.functions
1325
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1326
# Import the OpenPGP public key into the certificate
1327
(gnutls.library.functions
1328
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1329
gnutls.library.constants
1330
.GNUTLS_OPENPGP_FMT_RAW))
1331
# Verify the self signature in the key
1332
crtverify = ctypes.c_uint()
1333
(gnutls.library.functions
1334
.gnutls_openpgp_crt_verify_self(crt, 0,
1335
ctypes.byref(crtverify)))
1336
if crtverify.value != 0:
1337
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1338
raise (gnutls.errors.CertificateSecurityError
1340
# New buffer for the fingerprint
1341
buf = ctypes.create_string_buffer(20)
1342
buf_len = ctypes.c_size_t()
1343
# Get the fingerprint from the certificate into the buffer
1344
(gnutls.library.functions
1345
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1346
ctypes.byref(buf_len)))
1347
# Deinit the certificate
1348
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1349
# Convert the buffer to a Python bytestring
1350
fpr = ctypes.string_at(buf, buf_len.value)
1351
# Convert the bytestring to hexadecimal notation
1352
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1356
class MultiprocessingMixIn(object):
1357
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1358
def sub_process_main(self, request, address):
1360
self.finish_request(request, address)
1362
self.handle_error(request, address)
1363
self.close_request(request)
1365
def process_request(self, request, address):
1366
"""Start a new process to process the request."""
1367
multiprocessing.Process(target = self.sub_process_main,
1368
args = (request, address)).start()
1370
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1371
""" adds a pipe to the MixIn """
1372
def process_request(self, request, client_address):
1373
"""Overrides and wraps the original process_request().
1375
This function creates a new pipe in self.pipe
1377
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1379
super(MultiprocessingMixInWithPipe,
1380
self).process_request(request, client_address)
1381
self.child_pipe.close()
1382
self.add_pipe(parent_pipe)
1384
def add_pipe(self, parent_pipe):
1385
"""Dummy function; override as necessary"""
1388
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1389
socketserver.TCPServer, object):
1390
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
434
logger.debug(u"TCP connection from: %s",
435
unicode(self.client_address))
436
session = gnutls.connection.ClientSession\
437
(self.request, gnutls.connection.X509Credentials())
438
# Note: gnutls.connection.X509Credentials is really a generic
439
# GnuTLS certificate credentials object so long as no X.509
440
# keys are added to it. Therefore, we can use it here despite
441
# using OpenPGP certificates.
443
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
444
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
446
priority = "NORMAL" # Fallback default, since this
448
if self.server.settings["priority"]:
449
priority = self.server.settings["priority"]
450
gnutls.library.functions.gnutls_priority_set_direct\
451
(session._c_object, priority, None);
455
except gnutls.errors.GNUTLSError, error:
456
logger.debug(u"Handshake failed: %s", error)
457
# Do not run session.bye() here: the session is not
458
# established. Just abandon the request.
461
fpr = fingerprint(peer_certificate(session))
462
except (TypeError, gnutls.errors.GNUTLSError), error:
463
logger.debug(u"Bad certificate: %s", error)
466
logger.debug(u"Fingerprint: %s", fpr)
468
for c in self.server.clients:
469
if c.fingerprint == fpr:
473
logger.debug(u"Client not found for fingerprint: %s", fpr)
476
# Have to check if client.still_valid(), since it is possible
477
# that the client timed out while establishing the GnuTLS
479
if not client.still_valid():
480
logger.debug(u"Client %(name)s is invalid", vars(client))
484
while sent_size < len(client.secret):
485
sent = session.send(client.secret[sent_size:])
486
logger.debug(u"Sent: %d, remaining: %d",
487
sent, len(client.secret)
488
- (sent_size + sent))
493
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
494
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1393
enabled: Boolean; whether this server is activated yet
1394
interface: None or a network interface name (string)
1395
use_ipv6: Boolean; to use IPv6 or not
496
settings: Server settings
497
clients: Set() of Client objects
1397
def __init__(self, server_address, RequestHandlerClass,
1398
interface=None, use_ipv6=True):
1399
self.interface = interface
1401
self.address_family = socket.AF_INET6
1402
socketserver.TCPServer.__init__(self, server_address,
1403
RequestHandlerClass)
499
address_family = socket.AF_INET6
500
def __init__(self, *args, **kwargs):
501
if "settings" in kwargs:
502
self.settings = kwargs["settings"]
503
del kwargs["settings"]
504
if "clients" in kwargs:
505
self.clients = kwargs["clients"]
506
del kwargs["clients"]
507
return super(type(self), self).__init__(*args, **kwargs)
1404
508
def server_bind(self):
1405
509
"""This overrides the normal server_bind() function
1406
510
to bind to an interface if one was specified, and also NOT to
1407
511
bind to an address or port if they were not specified."""
1408
if self.interface is not None:
1409
if SO_BINDTODEVICE is None:
1410
logger.error(u"SO_BINDTODEVICE does not exist;"
1411
u" cannot bind to interface %s",
1415
self.socket.setsockopt(socket.SOL_SOCKET,
1419
except socket.error, error:
1420
if error[0] == errno.EPERM:
1421
logger.error(u"No permission to"
1422
u" bind to interface %s",
1424
elif error[0] == errno.ENOPROTOOPT:
1425
logger.error(u"SO_BINDTODEVICE not available;"
1426
u" cannot bind to interface %s",
512
if self.settings["interface"] != avahi.IF_UNSPEC:
513
# 25 is from /usr/include/asm-i486/socket.h
514
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
516
self.socket.setsockopt(socket.SOL_SOCKET,
518
self.settings["interface"])
519
except socket.error, error:
520
if error[0] == errno.EPERM:
521
logger.warning(u"No permission to"
522
u" bind to interface %s",
523
self.settings["interface"])
1430
526
# Only bind(2) the socket if we really need to.
1431
527
if self.server_address[0] or self.server_address[1]:
1432
528
if not self.server_address[0]:
1433
if self.address_family == socket.AF_INET6:
1434
any_address = u"::" # in6addr_any
1436
any_address = socket.INADDR_ANY
1437
self.server_address = (any_address,
530
self.server_address = (in6addr_any,
1438
531
self.server_address[1])
1439
elif not self.server_address[1]:
532
elif self.server_address[1] is None:
1440
533
self.server_address = (self.server_address[0],
1442
# if self.interface:
1443
# self.server_address = (self.server_address[0],
1448
return socketserver.TCPServer.server_bind(self)
1451
class MandosServer(IPv6_TCPServer):
1455
clients: set of Client objects
1456
gnutls_priority GnuTLS priority string
1457
use_dbus: Boolean; to emit D-Bus signals or not
1459
Assumes a gobject.MainLoop event loop.
1461
def __init__(self, server_address, RequestHandlerClass,
1462
interface=None, use_ipv6=True, clients=None,
1463
gnutls_priority=None, use_dbus=True):
1464
self.enabled = False
1465
self.clients = clients
1466
if self.clients is None:
1467
self.clients = set()
1468
self.use_dbus = use_dbus
1469
self.gnutls_priority = gnutls_priority
1470
IPv6_TCPServer.__init__(self, server_address,
1471
RequestHandlerClass,
1472
interface = interface,
1473
use_ipv6 = use_ipv6)
1474
def server_activate(self):
1476
return socketserver.TCPServer.server_activate(self)
1479
def add_pipe(self, parent_pipe):
1480
# Call "handle_ipc" for both data and EOF events
1481
gobject.io_add_watch(parent_pipe.fileno(),
1482
gobject.IO_IN | gobject.IO_HUP,
1483
functools.partial(self.handle_ipc,
1484
parent_pipe = parent_pipe))
1486
def handle_ipc(self, source, condition, parent_pipe=None,
1487
client_object=None):
1489
gobject.IO_IN: u"IN", # There is data to read.
1490
gobject.IO_OUT: u"OUT", # Data can be written (without
1492
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1493
gobject.IO_ERR: u"ERR", # Error condition.
1494
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1495
# broken, usually for pipes and
1498
conditions_string = ' | '.join(name
1500
condition_names.iteritems()
1501
if cond & condition)
1502
# error or the other end of multiprocessing.Pipe has closed
1503
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1506
# Read a request from the child
1507
request = parent_pipe.recv()
1508
command = request[0]
1510
if command == 'init':
1512
address = request[2]
1514
for c in self.clients:
1515
if c.fingerprint == fpr:
1519
logger.warning(u"Client not found for fingerprint: %s, ad"
1520
u"dress: %s", fpr, address)
1523
mandos_dbus_service.ClientNotFound(fpr, address)
1524
parent_pipe.send(False)
1527
gobject.io_add_watch(parent_pipe.fileno(),
1528
gobject.IO_IN | gobject.IO_HUP,
1529
functools.partial(self.handle_ipc,
1530
parent_pipe = parent_pipe,
1531
client_object = client))
1532
parent_pipe.send(True)
1533
# remove the old hook in favor of the new above hook on same fileno
1535
if command == 'funcall':
1536
funcname = request[1]
1540
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1542
if command == 'getattr':
1543
attrname = request[1]
1544
if callable(client_object.__getattribute__(attrname)):
1545
parent_pipe.send(('function',))
1547
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1549
if command == 'setattr':
1550
attrname = request[1]
1552
setattr(client_object, attrname, value)
535
return super(type(self), self).server_bind()
1557
538
def string_to_delta(interval):
1558
539
"""Parse a string and return a datetime.timedelta
1560
>>> string_to_delta(u'7d')
541
>>> string_to_delta('7d')
1561
542
datetime.timedelta(7)
1562
>>> string_to_delta(u'60s')
543
>>> string_to_delta('60s')
1563
544
datetime.timedelta(0, 60)
1564
>>> string_to_delta(u'60m')
545
>>> string_to_delta('60m')
1565
546
datetime.timedelta(0, 3600)
1566
>>> string_to_delta(u'24h')
547
>>> string_to_delta('24h')
1567
548
datetime.timedelta(1)
1568
549
>>> string_to_delta(u'1w')
1569
550
datetime.timedelta(7)
1570
>>> string_to_delta(u'5m 30s')
1571
datetime.timedelta(0, 330)
1573
timevalue = datetime.timedelta(0)
1574
for s in interval.split():
1576
suffix = unicode(s[-1])
1579
delta = datetime.timedelta(value)
1580
elif suffix == u"s":
1581
delta = datetime.timedelta(0, value)
1582
elif suffix == u"m":
1583
delta = datetime.timedelta(0, 0, 0, 0, value)
1584
elif suffix == u"h":
1585
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1586
elif suffix == u"w":
1587
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1589
raise ValueError(u"Unknown suffix %r" % suffix)
1590
except (ValueError, IndexError), e:
1591
raise ValueError(e.message)
1596
def if_nametoindex(interface):
1597
"""Call the C function if_nametoindex(), or equivalent
553
suffix=unicode(interval[-1])
554
value=int(interval[:-1])
556
delta = datetime.timedelta(value)
558
delta = datetime.timedelta(0, value)
560
delta = datetime.timedelta(0, 0, 0, 0, value)
562
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
564
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
567
except (ValueError, IndexError):
572
def server_state_changed(state):
573
"""Derived from the Avahi example code"""
574
if state == avahi.SERVER_COLLISION:
575
logger.warning(u"Server name collision")
577
elif state == avahi.SERVER_RUNNING:
581
def entry_group_state_changed(state, error):
582
"""Derived from the Avahi example code"""
583
logger.debug(u"state change: %i", state)
1599
Note: This function cannot accept a unicode string."""
1600
global if_nametoindex
585
if state == avahi.ENTRY_GROUP_ESTABLISHED:
586
logger.debug(u"Service established.")
587
elif state == avahi.ENTRY_GROUP_COLLISION:
588
logger.warning(u"Service name collision.")
590
elif state == avahi.ENTRY_GROUP_FAILURE:
591
logger.critical(u"Error in group state changed %s",
593
raise AvahiGroupError("State changed: %s", str(error))
595
def if_nametoindex(interface, _func=[None]):
596
"""Call the C function if_nametoindex(), or equivalent"""
597
if _func[0] is not None:
598
return _func[0](interface)
1602
if_nametoindex = (ctypes.cdll.LoadLibrary
1603
(ctypes.util.find_library(u"c"))
600
if "ctypes.util" not in sys.modules:
604
libc = ctypes.cdll.LoadLibrary\
605
(ctypes.util.find_library("c"))
606
func[0] = libc.if_nametoindex
607
return _func[0](interface)
1605
611
except (OSError, AttributeError):
1606
logger.warning(u"Doing if_nametoindex the hard way")
1607
def if_nametoindex(interface):
612
if "struct" not in sys.modules:
614
if "fcntl" not in sys.modules:
616
def the_hard_way(interface):
1608
617
"Get an interface index the hard way, i.e. using fcntl()"
1609
618
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1610
with contextlib.closing(socket.socket()) as s:
1611
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1612
struct.pack(str(u"16s16x"),
1614
interface_index = struct.unpack(str(u"I"),
620
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
621
struct.pack("16s16x", interface))
623
interface_index = struct.unpack("I", ifreq[16:20])[0]
1616
624
return interface_index
1617
return if_nametoindex(interface)
1620
def daemon(nochdir = False, noclose = False):
625
_func[0] = the_hard_way
626
return _func[0](interface)
629
def daemon(nochdir, noclose):
1621
630
"""See daemon(3). Standard BSD Unix function.
1623
631
This should really exist as os.daemon, but it doesn't (yet)."""
1632
638
# Close all standard open file descriptors
1633
639
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1634
640
if not stat.S_ISCHR(os.fstat(null).st_mode):
1635
641
raise OSError(errno.ENODEV,
1636
u"%s not a character device"
642
"/dev/null not a character device")
1638
643
os.dup2(null, sys.stdin.fileno())
1639
644
os.dup2(null, sys.stdout.fileno())
1640
645
os.dup2(null, sys.stderr.fileno())
1684
681
# Default values for config file for server-global settings
1685
server_defaults = { u"interface": u"",
1690
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1691
u"servicename": u"Mandos",
1692
u"use_dbus": u"True",
1693
u"use_ipv6": u"True",
682
server_defaults = { "interface": "",
687
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
688
"servicename": "Mandos",
1697
691
# Parse config file for server-global settings
1698
server_config = configparser.SafeConfigParser(server_defaults)
692
server_config = ConfigParser.SafeConfigParser(server_defaults)
1699
693
del server_defaults
1700
server_config.read(os.path.join(options.configdir,
694
server_config.read(os.path.join(options.configdir, "server.conf"))
695
server_section = "server"
1702
696
# Convert the SafeConfigParser object to a dict
1703
server_settings = server_config.defaults()
1704
# Use the appropriate methods on the non-string config options
1705
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1706
server_settings[option] = server_config.getboolean(u"DEFAULT",
1708
if server_settings["port"]:
1709
server_settings["port"] = server_config.getint(u"DEFAULT",
697
server_settings = dict(server_config.items(server_section))
698
# Use getboolean on the boolean config option
699
server_settings["debug"] = server_config.getboolean\
700
(server_section, "debug")
1711
701
del server_config
702
if not server_settings["interface"]:
703
server_settings["interface"] = avahi.IF_UNSPEC
1713
705
# Override the settings from the config file with command line
1714
706
# options, if set.
1715
for option in (u"interface", u"address", u"port", u"debug",
1716
u"priority", u"servicename", u"configdir",
1717
u"use_dbus", u"use_ipv6", u"debuglevel"):
707
for option in ("interface", "address", "port", "debug",
708
"priority", "servicename", "configdir"):
1718
709
value = getattr(options, option)
1719
710
if value is not None:
1720
711
server_settings[option] = value
1722
# Force all strings to be unicode
1723
for option in server_settings.keys():
1724
if type(server_settings[option]) is str:
1725
server_settings[option] = unicode(server_settings[option])
1726
713
# Now we have our good server settings in "server_settings"
1728
##################################################################
1731
debug = server_settings[u"debug"]
1732
debuglevel = server_settings[u"debuglevel"]
1733
use_dbus = server_settings[u"use_dbus"]
1734
use_ipv6 = server_settings[u"use_ipv6"]
1736
if server_settings[u"servicename"] != u"Mandos":
1737
syslogger.setFormatter(logging.Formatter
1738
(u'Mandos (%s) [%%(process)d]:'
1739
u' %%(levelname)s: %%(message)s'
1740
% server_settings[u"servicename"]))
1742
715
# Parse config file with clients
1743
client_defaults = { u"timeout": u"1h",
1745
u"checker": u"fping -q -- %%(host)s",
1747
u"approval_delay": u"0s",
1748
u"approval_duration": u"1s",
716
client_defaults = { "timeout": "1h",
718
"checker": "fping -q -- %%(fqdn)s",
1750
client_config = configparser.SafeConfigParser(client_defaults)
1751
client_config.read(os.path.join(server_settings[u"configdir"],
1754
global mandos_dbus_service
1755
mandos_dbus_service = None
1757
tcp_server = MandosServer((server_settings[u"address"],
1758
server_settings[u"port"]),
1760
interface=(server_settings[u"interface"]
1764
server_settings[u"priority"],
1766
pidfilename = u"/var/run/mandos.pid"
1768
pidfile = open(pidfilename, u"w")
1770
logger.error(u"Could not open file %r", pidfilename)
1773
uid = pwd.getpwnam(u"_mandos").pw_uid
1774
gid = pwd.getpwnam(u"_mandos").pw_gid
1777
uid = pwd.getpwnam(u"mandos").pw_uid
1778
gid = pwd.getpwnam(u"mandos").pw_gid
1781
uid = pwd.getpwnam(u"nobody").pw_uid
1782
gid = pwd.getpwnam(u"nobody").pw_gid
1789
except OSError, error:
1790
if error[0] != errno.EPERM:
1793
if not debug and not debuglevel:
1794
syslogger.setLevel(logging.WARNING)
1795
console.setLevel(logging.WARNING)
1797
level = getattr(logging, debuglevel.upper())
1798
syslogger.setLevel(level)
1799
console.setLevel(level)
1802
# Enable all possible GnuTLS debugging
1804
# "Use a log level over 10 to enable all debugging options."
1806
gnutls.library.functions.gnutls_global_set_log_level(11)
1808
@gnutls.library.types.gnutls_log_func
1809
def debug_gnutls(level, string):
1810
logger.debug(u"GnuTLS: %s", string[:-1])
1812
(gnutls.library.functions
1813
.gnutls_global_set_log_function(debug_gnutls))
1815
# Redirect stdin so all checkers get /dev/null
1816
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1817
os.dup2(null, sys.stdin.fileno())
1821
# No console logging
1822
logger.removeHandler(console)
720
client_config = ConfigParser.SafeConfigParser(client_defaults)
721
client_config.read(os.path.join(server_settings["configdir"],
725
service = AvahiService(name = server_settings["servicename"],
726
type = "_mandos._tcp", );
1825
728
global main_loop
1826
731
# From the Avahi example code
1827
732
DBusGMainLoop(set_as_default=True )
1828
733
main_loop = gobject.MainLoop()
1829
734
bus = dbus.SystemBus()
735
server = dbus.Interface(
736
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
737
avahi.DBUS_INTERFACE_SERVER )
1830
738
# End of Avahi example code
1833
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1834
bus, do_not_queue=True)
1835
except dbus.exceptions.NameExistsException, e:
1836
logger.error(unicode(e) + u", disabling D-Bus")
1838
server_settings[u"use_dbus"] = False
1839
tcp_server.use_dbus = False
1840
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1841
service = AvahiService(name = server_settings[u"servicename"],
1842
servicetype = u"_mandos._tcp",
1843
protocol = protocol, bus = bus)
1844
if server_settings["interface"]:
1845
service.interface = (if_nametoindex
1846
(str(server_settings[u"interface"])))
740
debug = server_settings["debug"]
743
console = logging.StreamHandler()
744
# console.setLevel(logging.DEBUG)
745
console.setFormatter(logging.Formatter\
746
('%(levelname)s: %(message)s'))
747
logger.addHandler(console)
751
def remove_from_clients(client):
752
clients.remove(client)
754
logger.debug(u"No clients left, exiting")
757
clients.update(Set(Client(name=section,
758
stop_hook = remove_from_clients,
759
**(dict(client_config\
761
for section in client_config.sections()))
1849
# Close all input and output, do double fork, etc.
1852
global multiprocessing_manager
1853
multiprocessing_manager = multiprocessing.Manager()
1855
client_class = Client
1857
client_class = functools.partial(ClientDBus, bus = bus)
1858
def client_config_items(config, section):
1859
special_settings = {
1860
"approved_by_default":
1861
lambda: config.getboolean(section,
1862
"approved_by_default"),
1864
for name, value in config.items(section):
1866
yield (name, special_settings[name]())
1870
tcp_server.clients.update(set(
1871
client_class(name = section,
1872
config= dict(client_config_items(
1873
client_config, section)))
1874
for section in client_config.sections()))
1875
if not tcp_server.clients:
1876
logger.warning(u"No clients defined")
1881
pidfile.write(str(pid) + "\n")
1884
logger.error(u"Could not write to file %r with PID %d",
1887
# "pidfile" was never created
767
"Cleanup function; run on exit"
769
# From the Avahi example code
770
if not group is None:
773
# End of Avahi example code
776
client = clients.pop()
777
client.stop_hook = None
780
atexit.register(cleanup)
1892
783
signal.signal(signal.SIGINT, signal.SIG_IGN)
1893
784
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1894
785
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1897
class MandosDBusService(dbus.service.Object):
1898
"""A D-Bus proxy object"""
1900
dbus.service.Object.__init__(self, bus, u"/")
1901
_interface = u"se.bsnet.fukt.Mandos"
1903
@dbus.service.signal(_interface, signature=u"o")
1904
def ClientAdded(self, objpath):
1908
@dbus.service.signal(_interface, signature=u"ss")
1909
def ClientNotFound(self, fingerprint, address):
1913
@dbus.service.signal(_interface, signature=u"os")
1914
def ClientRemoved(self, objpath, name):
1918
@dbus.service.method(_interface, out_signature=u"ao")
1919
def GetAllClients(self):
1921
return dbus.Array(c.dbus_object_path
1922
for c in tcp_server.clients)
1924
@dbus.service.method(_interface,
1925
out_signature=u"a{oa{sv}}")
1926
def GetAllClientsWithProperties(self):
1928
return dbus.Dictionary(
1929
((c.dbus_object_path, c.GetAll(u""))
1930
for c in tcp_server.clients),
1931
signature=u"oa{sv}")
1933
@dbus.service.method(_interface, in_signature=u"o")
1934
def RemoveClient(self, object_path):
1936
for c in tcp_server.clients:
1937
if c.dbus_object_path == object_path:
1938
tcp_server.clients.remove(c)
1939
c.remove_from_connection()
1940
# Don't signal anything except ClientRemoved
1941
c.disable(quiet=True)
1943
self.ClientRemoved(object_path, c.name)
1945
raise KeyError(object_path)
1949
mandos_dbus_service = MandosDBusService()
1952
"Cleanup function; run on exit"
1955
while tcp_server.clients:
1956
client = tcp_server.clients.pop()
1958
client.remove_from_connection()
1959
client.disable_hook = None
1960
# Don't signal anything except ClientRemoved
1961
client.disable(quiet=True)
1964
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
1967
atexit.register(cleanup)
1969
for client in tcp_server.clients:
1972
mandos_dbus_service.ClientAdded(client.dbus_object_path)
1976
tcp_server.server_activate()
787
for client in clients:
790
tcp_server = IPv6_TCPServer((server_settings["address"],
791
server_settings["port"]),
793
settings=server_settings,
1978
795
# Find out what port we got
1979
796
service.port = tcp_server.socket.getsockname()[1]
1981
logger.info(u"Now listening on address %r, port %d,"
1982
" flowinfo %d, scope_id %d"
1983
% tcp_server.socket.getsockname())
1985
logger.info(u"Now listening on address %r, port %d"
1986
% tcp_server.socket.getsockname())
797
logger.debug(u"Now listening on port %d", service.port)
1988
#service.interface = tcp_server.socket.getsockname()[3]
799
if not server_settings["interface"]:
800
service.interface = if_nametoindex\
801
(server_settings["interface"])
1991
804
# From the Avahi example code
805
server.connect_to_signal("StateChanged", server_state_changed)
807
server_state_changed(server.GetState())
1994
808
except dbus.exceptions.DBusException, error:
1995
809
logger.critical(u"DBusException: %s", error)
1998
811
# End of Avahi example code
2000
813
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2001
814
lambda *args, **kwargs:
2002
(tcp_server.handle_request
2003
(*args[2:], **kwargs) or True))
815
tcp_server.handle_request\
816
(*args[2:], **kwargs) or True)
2005
logger.debug(u"Starting main loop")
818
logger.debug("Starting main loop")
819
main_loop_started = True
2007
821
except AvahiError, error:
2008
logger.critical(u"AvahiError: %s", error)
822
logger.critical(u"AvahiError: %s" + unicode(error))
2011
824
except KeyboardInterrupt:
2014
logger.debug(u"Server received KeyboardInterrupt")
2015
logger.debug(u"Server exiting")
2016
# Must run before the D-Bus bus name gets deregistered
2019
828
if __name__ == '__main__':