104
129
max_renames: integer; maximum number of renames
105
130
rename_count: integer; counter so we only rename after collisions
106
131
a sensible number of times
132
group: D-Bus Entry Group
134
bus: dbus.SystemBus()
108
136
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
109
type = None, port = None, TXT = None, domain = "",
110
host = "", max_renames = 32768):
137
servicetype = None, port = None, TXT = None,
138
domain = u"", host = u"", max_renames = 32768,
139
protocol = avahi.PROTO_UNSPEC, bus = None):
111
140
self.interface = interface
142
self.type = servicetype
144
self.TXT = TXT if TXT is not None else []
119
145
self.domain = domain
121
147
self.rename_count = 0
122
148
self.max_renames = max_renames
149
self.protocol = protocol
150
self.group = None # our entry group
123
153
def rename(self):
124
154
"""Derived from the Avahi example code"""
125
155
if self.rename_count >= self.max_renames:
126
logger.critical(u"No suitable service name found after %i"
127
u" retries, exiting.", rename_count)
128
raise AvahiServiceError("Too many renames")
129
self.name = server.GetAlternativeServiceName(self.name)
130
logger.info(u"Changing name to %r ...", str(self.name))
131
syslogger.setFormatter(logging.Formatter\
132
('Mandos (%s): %%(levelname)s:'
133
' %%(message)s' % self.name))
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'
170
except dbus.exceptions.DBusException, error:
171
logger.critical(u"DBusException: %s", error)
136
174
self.rename_count += 1
137
175
def remove(self):
138
176
"""Derived from the Avahi example code"""
139
if group is not None:
177
if self.group is not None:
142
180
"""Derived from the Avahi example code"""
145
group = dbus.Interface\
146
(bus.get_object(avahi.DBUS_NAME,
147
server.EntryGroupNew()),
148
avahi.DBUS_INTERFACE_ENTRY_GROUP)
149
group.connect_to_signal('StateChanged',
150
entry_group_state_changed)
151
logger.debug(u"Adding service '%s' of type '%s' ...",
152
service.name, service.type)
154
self.interface, # interface
155
avahi.PROTO_INET6, # protocol
156
dbus.UInt32(0), # flags
157
self.name, self.type,
158
self.domain, self.host,
159
dbus.UInt16(self.port),
160
avahi.string_array_to_txt_array(self.TXT))
163
# From the Avahi example code:
164
group = None # our entry group
165
# End of Avahi example code
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())
168
239
class Client(object):
169
240
"""A representation of a client host served by this server.
171
name: string; from the config file, used in log messages
243
name: string; from the config file, used in log messages and
172
245
fingerprint: string (40 or 32 hexadecimal digits); used to
173
246
uniquely identify the client
174
secret: bytestring; sent verbatim (over TLS) to client
175
host: string; available for use by the checker command
176
created: datetime.datetime(); object creation, not client host
177
last_checked_ok: datetime.datetime() or None if not yet checked OK
178
timeout: datetime.timedelta(); How long from last_checked_ok
179
until this client is invalid
180
interval: datetime.timedelta(); How often to start a new checker
181
stop_hook: If set, called by stop() as stop_hook(self)
182
checker: subprocess.Popen(); a running checker process used
183
to see if the client lives.
184
'None' if no process is running.
247
secret: bytestring; sent verbatim (over TLS) to client
248
host: string; available for use by the checker command
249
created: datetime.datetime(); (UTC) object creation
250
last_enabled: datetime.datetime(); (UTC)
252
last_checked_ok: datetime.datetime(); (UTC) or None
253
timeout: datetime.timedelta(); How long from last_checked_ok
254
until this client is disabled
255
interval: datetime.timedelta(); How often to start a new checker
256
disable_hook: If set, called by disable() as disable_hook(self)
257
checker: subprocess.Popen(); a running checker process used
258
to see if the client lives.
259
'None' if no process is running.
185
260
checker_initiator_tag: a gobject event source tag, or None
186
stop_initiator_tag: - '' -
261
disable_initiator_tag: - '' -
187
262
checker_callback_tag: - '' -
188
263
checker_command: string; External command which is run to check if
189
264
client lives. %() expansions are done at
190
265
runtime with vars(self) as dict, so that for
191
266
instance %(name)s can be used in the command.
193
_timeout: Real variable for 'timeout'
194
_interval: Real variable for 'interval'
195
_timeout_milliseconds: Used when calling gobject.timeout_add()
196
_interval_milliseconds: - '' -
267
current_checker_command: string; current running checker_command
268
approval_delay: datetime.timedelta(); Time to wait for approval
269
_approved: bool(); 'None' if not yet approved/disapproved
270
approval_duration: datetime.timedelta(); Duration of one approval
198
def _set_timeout(self, timeout):
199
"Setter function for 'timeout' attribute"
200
self._timeout = timeout
201
self._timeout_milliseconds = ((self.timeout.days
202
* 24 * 60 * 60 * 1000)
203
+ (self.timeout.seconds * 1000)
204
+ (self.timeout.microseconds
206
timeout = property(lambda self: self._timeout,
209
def _set_interval(self, interval):
210
"Setter function for 'interval' attribute"
211
self._interval = interval
212
self._interval_milliseconds = ((self.interval.days
213
* 24 * 60 * 60 * 1000)
214
+ (self.interval.seconds
216
+ (self.interval.microseconds
218
interval = property(lambda self: self._interval,
221
def __init__(self, name = None, stop_hook=None, config={}):
274
def _timedelta_to_milliseconds(td):
275
"Convert a datetime.timedelta() to milliseconds"
276
return ((td.days * 24 * 60 * 60 * 1000)
277
+ (td.seconds * 1000)
278
+ (td.microseconds // 1000))
280
def timeout_milliseconds(self):
281
"Return the 'timeout' attribute in milliseconds"
282
return self._timedelta_to_milliseconds(self.timeout)
284
def interval_milliseconds(self):
285
"Return the 'interval' attribute in milliseconds"
286
return self._timedelta_to_milliseconds(self.interval)
288
def approval_delay_milliseconds(self):
289
return self._timedelta_to_milliseconds(self.approval_delay)
291
def __init__(self, name = None, disable_hook=None, config=None):
222
292
"""Note: the 'checker' key in 'config' sets the
223
293
'checker_command' attribute and *not* the 'checker'
226
298
logger.debug(u"Creating client %r", self.name)
227
299
# Uppercase and remove spaces from fingerprint for later
228
300
# comparison purposes with return value from the fingerprint()
230
self.fingerprint = config["fingerprint"].upper()\
302
self.fingerprint = (config[u"fingerprint"].upper()
232
304
logger.debug(u" Fingerprint: %s", self.fingerprint)
233
if "secret" in config:
234
self.secret = config["secret"].decode(u"base64")
235
elif "secfile" in config:
236
sf = open(config["secfile"])
237
self.secret = sf.read()
305
if u"secret" in config:
306
self.secret = config[u"secret"].decode(u"base64")
307
elif u"secfile" in config:
308
with open(os.path.expanduser(os.path.expandvars
309
(config[u"secfile"])),
311
self.secret = secfile.read()
240
313
raise TypeError(u"No secret or secfile for client %s"
242
self.host = config.get("host", "")
243
self.created = datetime.datetime.now()
315
self.host = config.get(u"host", u"")
316
self.created = datetime.datetime.utcnow()
318
self.last_enabled = None
244
319
self.last_checked_ok = None
245
self.timeout = string_to_delta(config["timeout"])
246
self.interval = string_to_delta(config["interval"])
247
self.stop_hook = stop_hook
320
self.timeout = string_to_delta(config[u"timeout"])
321
self.interval = string_to_delta(config[u"interval"])
322
self.disable_hook = disable_hook
248
323
self.checker = None
249
324
self.checker_initiator_tag = None
250
self.stop_initiator_tag = None
325
self.disable_initiator_tag = None
251
326
self.checker_callback_tag = None
252
self.check_command = config["checker"]
327
self.checker_command = config[u"checker"]
328
self.current_checker_command = None
329
self.last_connect = None
330
self._approved = None
331
self.approved_by_default = config.get(u"approved_by_default",
333
self.approvals_pending = 0
334
self.approval_delay = string_to_delta(
335
config[u"approval_delay"])
336
self.approval_duration = string_to_delta(
337
config[u"approval_duration"])
338
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
340
def send_changedstate(self):
341
self.changedstate.acquire()
342
self.changedstate.notify_all()
343
self.changedstate.release()
254
346
"""Start this client's checker and timeout hooks"""
347
if getattr(self, u"enabled", False):
350
self.send_changedstate()
351
self.last_enabled = datetime.datetime.utcnow()
255
352
# Schedule a new checker to be started an 'interval' from now,
256
353
# and every interval from then on.
257
self.checker_initiator_tag = gobject.timeout_add\
258
(self._interval_milliseconds,
354
self.checker_initiator_tag = (gobject.timeout_add
355
(self.interval_milliseconds(),
357
# Schedule a disable() when 'timeout' has passed
358
self.disable_initiator_tag = (gobject.timeout_add
359
(self.timeout_milliseconds(),
260
362
# Also start a new checker *right now*.
261
363
self.start_checker()
262
# Schedule a stop() when 'timeout' has passed
263
self.stop_initiator_tag = gobject.timeout_add\
264
(self._timeout_milliseconds,
268
The possibility that a client might be restarted is left open,
269
but not currently used."""
270
# If this client doesn't have a secret, it is already stopped.
271
if hasattr(self, "secret") and self.secret:
272
logger.info(u"Stopping client %s", self.name)
365
def disable(self, quiet=True):
366
"""Disable this client."""
367
if not getattr(self, "enabled", False):
276
if getattr(self, "stop_initiator_tag", False):
277
gobject.source_remove(self.stop_initiator_tag)
278
self.stop_initiator_tag = None
279
if getattr(self, "checker_initiator_tag", False):
370
self.send_changedstate()
372
logger.info(u"Disabling client %s", self.name)
373
if getattr(self, u"disable_initiator_tag", False):
374
gobject.source_remove(self.disable_initiator_tag)
375
self.disable_initiator_tag = None
376
if getattr(self, u"checker_initiator_tag", False):
280
377
gobject.source_remove(self.checker_initiator_tag)
281
378
self.checker_initiator_tag = None
282
379
self.stop_checker()
380
if self.disable_hook:
381
self.disable_hook(self)
285
383
# Do not run this again if called by a gobject.timeout_add
287
386
def __del__(self):
288
self.stop_hook = None
290
def checker_callback(self, pid, condition):
387
self.disable_hook = None
390
def checker_callback(self, pid, condition, command):
291
391
"""The checker has completed, so take appropriate actions."""
292
now = datetime.datetime.now()
293
392
self.checker_callback_tag = None
294
393
self.checker = None
295
if os.WIFEXITED(condition) \
296
and (os.WEXITSTATUS(condition) == 0):
297
logger.info(u"Checker for %(name)s succeeded",
299
self.last_checked_ok = now
300
gobject.source_remove(self.stop_initiator_tag)
301
self.stop_initiator_tag = gobject.timeout_add\
302
(self._timeout_milliseconds,
304
elif not os.WIFEXITED(condition):
394
if os.WIFEXITED(condition):
395
exitstatus = os.WEXITSTATUS(condition)
397
logger.info(u"Checker for %(name)s succeeded",
401
logger.info(u"Checker for %(name)s failed",
305
404
logger.warning(u"Checker for %(name)s crashed?",
308
logger.info(u"Checker for %(name)s failed",
407
def checked_ok(self):
408
"""Bump up the timeout for this client.
410
This should only be called when the client has been seen,
413
self.last_checked_ok = datetime.datetime.utcnow()
414
gobject.source_remove(self.disable_initiator_tag)
415
self.disable_initiator_tag = (gobject.timeout_add
416
(self.timeout_milliseconds(),
310
419
def start_checker(self):
311
420
"""Start a new checker subprocess if one is not running.
312
422
If a checker already exists, leave it running and do
314
424
# The reason for not killing a running checker is that if we
317
427
# client would inevitably timeout, since no checker would get
318
428
# a chance to run to completion. If we instead leave running
319
429
# checkers alone, the checker would have to take more time
320
# than 'timeout' for the client to be declared invalid, which
321
# is as it should be.
430
# than 'timeout' for the client to be disabled, which is as it
433
# If a checker exists, make sure it is not a zombie
435
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
436
except (AttributeError, OSError), error:
437
if (isinstance(error, OSError)
438
and error.errno != errno.ECHILD):
442
logger.warning(u"Checker was a zombie")
443
gobject.source_remove(self.checker_callback_tag)
444
self.checker_callback(pid, status,
445
self.current_checker_command)
446
# Start a new checker if needed
322
447
if self.checker is None:
324
# In case check_command has exactly one % operator
325
command = self.check_command % self.host
449
# In case checker_command has exactly one % operator
450
command = self.checker_command % self.host
326
451
except TypeError:
327
452
# Escape attributes for the shell
328
escaped_attrs = dict((key, re.escape(str(val)))
453
escaped_attrs = dict((key,
454
re.escape(unicode(str(val),
330
458
vars(self).iteritems())
332
command = self.check_command % escaped_attrs
460
command = self.checker_command % escaped_attrs
333
461
except TypeError, error:
334
462
logger.error(u'Could not format string "%s":'
335
u' %s', self.check_command, error)
463
u' %s', self.checker_command, error)
336
464
return True # Try again later
465
self.current_checker_command = command
338
467
logger.info(u"Starting checker %r for %s",
339
468
command, self.name)
469
# We don't need to redirect stdout and stderr, since
470
# in normal mode, that is already done by daemon(),
471
# and in debug mode we don't want to. (Stdin is
472
# always replaced by /dev/null.)
340
473
self.checker = subprocess.Popen(command,
343
self.checker_callback_tag = gobject.child_watch_add\
345
self.checker_callback)
346
except subprocess.OSError, error:
475
shell=True, cwd=u"/")
476
self.checker_callback_tag = (gobject.child_watch_add
478
self.checker_callback,
480
# The checker may have completed before the gobject
481
# watch was added. Check for this.
482
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
484
gobject.source_remove(self.checker_callback_tag)
485
self.checker_callback(pid, status, command)
486
except OSError, error:
347
487
logger.error(u"Failed to start subprocess: %s",
349
489
# Re-run this periodically if run by gobject.timeout_add
351
492
def stop_checker(self):
352
493
"""Force the checker process, if any, to stop."""
353
494
if self.checker_callback_tag:
354
495
gobject.source_remove(self.checker_callback_tag)
355
496
self.checker_callback_tag = None
356
if getattr(self, "checker", None) is None:
497
if getattr(self, u"checker", None) is None:
358
499
logger.debug(u"Stopping checker for %(name)s", vars(self))
360
501
os.kill(self.checker.pid, signal.SIGTERM)
362
503
#if self.checker.poll() is None:
363
504
# os.kill(self.checker.pid, signal.SIGKILL)
364
505
except OSError, error:
365
506
if error.errno != errno.ESRCH: # No such process
367
508
self.checker = None
368
def still_valid(self):
369
"""Has the timeout not yet passed for this client?"""
370
now = datetime.datetime.now()
510
def dbus_service_property(dbus_interface, signature=u"v",
511
access=u"readwrite", byte_arrays=False):
512
"""Decorators for marking methods of a DBusObjectWithProperties to
513
become properties on the D-Bus.
515
The decorated method will be called with no arguments by "Get"
516
and with one argument by "Set".
518
The parameters, where they are supported, are the same as
519
dbus.service.method, except there is only "signature", since the
520
type from Get() and the type sent to Set() is the same.
522
# Encoding deeply encoded byte arrays is not supported yet by the
523
# "Set" method, so we fail early here:
524
if byte_arrays and signature != u"ay":
525
raise ValueError(u"Byte arrays not supported for non-'ay'"
526
u" signature %r" % signature)
528
func._dbus_is_property = True
529
func._dbus_interface = dbus_interface
530
func._dbus_signature = signature
531
func._dbus_access = access
532
func._dbus_name = func.__name__
533
if func._dbus_name.endswith(u"_dbus_property"):
534
func._dbus_name = func._dbus_name[:-14]
535
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
540
class DBusPropertyException(dbus.exceptions.DBusException):
541
"""A base class for D-Bus property-related exceptions
543
def __unicode__(self):
544
return unicode(str(self))
547
class DBusPropertyAccessException(DBusPropertyException):
548
"""A property's access permissions disallows an operation.
553
class DBusPropertyNotFound(DBusPropertyException):
554
"""An attempt was made to access a non-existing property.
559
class DBusObjectWithProperties(dbus.service.Object):
560
"""A D-Bus object with properties.
562
Classes inheriting from this can use the dbus_service_property
563
decorator to expose methods as D-Bus properties. It exposes the
564
standard Get(), Set(), and GetAll() methods on the D-Bus.
568
def _is_dbus_property(obj):
569
return getattr(obj, u"_dbus_is_property", False)
571
def _get_all_dbus_properties(self):
572
"""Returns a generator of (name, attribute) pairs
574
return ((prop._dbus_name, prop)
576
inspect.getmembers(self, self._is_dbus_property))
578
def _get_dbus_property(self, interface_name, property_name):
579
"""Returns a bound method if one exists which is a D-Bus
580
property with the specified name and interface.
582
for name in (property_name,
583
property_name + u"_dbus_property"):
584
prop = getattr(self, name, None)
586
or not self._is_dbus_property(prop)
587
or prop._dbus_name != property_name
588
or (interface_name and prop._dbus_interface
589
and interface_name != prop._dbus_interface)):
593
raise DBusPropertyNotFound(self.dbus_object_path + u":"
594
+ interface_name + u"."
597
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
599
def Get(self, interface_name, property_name):
600
"""Standard D-Bus property Get() method, see D-Bus standard.
602
prop = self._get_dbus_property(interface_name, property_name)
603
if prop._dbus_access == u"write":
604
raise DBusPropertyAccessException(property_name)
606
if not hasattr(value, u"variant_level"):
608
return type(value)(value, variant_level=value.variant_level+1)
610
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
611
def Set(self, interface_name, property_name, value):
612
"""Standard D-Bus property Set() method, see D-Bus standard.
614
prop = self._get_dbus_property(interface_name, property_name)
615
if prop._dbus_access == u"read":
616
raise DBusPropertyAccessException(property_name)
617
if prop._dbus_get_args_options[u"byte_arrays"]:
618
# The byte_arrays option is not supported yet on
619
# signatures other than "ay".
620
if prop._dbus_signature != u"ay":
622
value = dbus.ByteArray(''.join(unichr(byte)
626
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
627
out_signature=u"a{sv}")
628
def GetAll(self, interface_name):
629
"""Standard D-Bus property GetAll() method, see D-Bus
632
Note: Will not include properties with access="write".
635
for name, prop in self._get_all_dbus_properties():
637
and interface_name != prop._dbus_interface):
638
# Interface non-empty but did not match
640
# Ignore write-only properties
641
if prop._dbus_access == u"write":
644
if not hasattr(value, u"variant_level"):
647
all[name] = type(value)(value, variant_level=
648
value.variant_level+1)
649
return dbus.Dictionary(all, signature=u"sv")
651
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
653
path_keyword='object_path',
654
connection_keyword='connection')
655
def Introspect(self, object_path, connection):
656
"""Standard D-Bus method, overloaded to insert property tags.
658
xmlstring = dbus.service.Object.Introspect(self, object_path,
661
document = xml.dom.minidom.parseString(xmlstring)
662
def make_tag(document, name, prop):
663
e = document.createElement(u"property")
664
e.setAttribute(u"name", name)
665
e.setAttribute(u"type", prop._dbus_signature)
666
e.setAttribute(u"access", prop._dbus_access)
668
for if_tag in document.getElementsByTagName(u"interface"):
669
for tag in (make_tag(document, name, prop)
671
in self._get_all_dbus_properties()
672
if prop._dbus_interface
673
== if_tag.getAttribute(u"name")):
674
if_tag.appendChild(tag)
675
# Add the names to the return values for the
676
# "org.freedesktop.DBus.Properties" methods
677
if (if_tag.getAttribute(u"name")
678
== u"org.freedesktop.DBus.Properties"):
679
for cn in if_tag.getElementsByTagName(u"method"):
680
if cn.getAttribute(u"name") == u"Get":
681
for arg in cn.getElementsByTagName(u"arg"):
682
if (arg.getAttribute(u"direction")
684
arg.setAttribute(u"name", u"value")
685
elif cn.getAttribute(u"name") == u"GetAll":
686
for arg in cn.getElementsByTagName(u"arg"):
687
if (arg.getAttribute(u"direction")
689
arg.setAttribute(u"name", u"props")
690
xmlstring = document.toxml(u"utf-8")
692
except (AttributeError, xml.dom.DOMException,
693
xml.parsers.expat.ExpatError), error:
694
logger.error(u"Failed to override Introspection method",
699
class ClientDBus(Client, DBusObjectWithProperties):
700
"""A Client class using D-Bus
703
dbus_object_path: dbus.ObjectPath
704
bus: dbus.SystemBus()
706
# dbus.service.Object doesn't use super(), so we can't either.
708
def __init__(self, bus = None, *args, **kwargs):
709
self._approvals_pending = 0
711
Client.__init__(self, *args, **kwargs)
712
# Only now, when this client is initialized, can it show up on
714
self.dbus_object_path = (dbus.ObjectPath
716
+ self.name.replace(u".", u"_")))
717
DBusObjectWithProperties.__init__(self, self.bus,
718
self.dbus_object_path)
720
def _get_approvals_pending(self):
721
return self._approvals_pending
722
def _set_approvals_pending(self, value):
723
old_value = self._approvals_pending
724
self._approvals_pending = value
726
if (hasattr(self, "dbus_object_path")
727
and bval is not bool(old_value)):
728
dbus_bool = dbus.Boolean(bval, variant_level=1)
729
self.PropertyChanged(dbus.String(u"ApprovalPending"),
732
approvals_pending = property(_get_approvals_pending,
733
_set_approvals_pending)
734
del _get_approvals_pending, _set_approvals_pending
737
def _datetime_to_dbus(dt, variant_level=0):
738
"""Convert a UTC datetime.datetime() to a D-Bus type."""
739
return dbus.String(dt.isoformat(),
740
variant_level=variant_level)
743
oldstate = getattr(self, u"enabled", False)
744
r = Client.enable(self)
745
if oldstate != self.enabled:
747
self.PropertyChanged(dbus.String(u"Enabled"),
748
dbus.Boolean(True, variant_level=1))
749
self.PropertyChanged(
750
dbus.String(u"LastEnabled"),
751
self._datetime_to_dbus(self.last_enabled,
755
def disable(self, quiet = False):
756
oldstate = getattr(self, u"enabled", False)
757
r = Client.disable(self, quiet=quiet)
758
if not quiet and oldstate != self.enabled:
760
self.PropertyChanged(dbus.String(u"Enabled"),
761
dbus.Boolean(False, variant_level=1))
764
def __del__(self, *args, **kwargs):
766
self.remove_from_connection()
769
if hasattr(DBusObjectWithProperties, u"__del__"):
770
DBusObjectWithProperties.__del__(self, *args, **kwargs)
771
Client.__del__(self, *args, **kwargs)
773
def checker_callback(self, pid, condition, command,
775
self.checker_callback_tag = None
778
self.PropertyChanged(dbus.String(u"CheckerRunning"),
779
dbus.Boolean(False, variant_level=1))
780
if os.WIFEXITED(condition):
781
exitstatus = os.WEXITSTATUS(condition)
783
self.CheckerCompleted(dbus.Int16(exitstatus),
784
dbus.Int64(condition),
785
dbus.String(command))
788
self.CheckerCompleted(dbus.Int16(-1),
789
dbus.Int64(condition),
790
dbus.String(command))
792
return Client.checker_callback(self, pid, condition, command,
795
def checked_ok(self, *args, **kwargs):
796
r = Client.checked_ok(self, *args, **kwargs)
798
self.PropertyChanged(
799
dbus.String(u"LastCheckedOK"),
800
(self._datetime_to_dbus(self.last_checked_ok,
804
def start_checker(self, *args, **kwargs):
805
old_checker = self.checker
806
if self.checker is not None:
807
old_checker_pid = self.checker.pid
809
old_checker_pid = None
810
r = Client.start_checker(self, *args, **kwargs)
811
# Only if new checker process was started
812
if (self.checker is not None
813
and old_checker_pid != self.checker.pid):
815
self.CheckerStarted(self.current_checker_command)
816
self.PropertyChanged(
817
dbus.String(u"CheckerRunning"),
818
dbus.Boolean(True, variant_level=1))
821
def stop_checker(self, *args, **kwargs):
822
old_checker = getattr(self, u"checker", None)
823
r = Client.stop_checker(self, *args, **kwargs)
824
if (old_checker is not None
825
and getattr(self, u"checker", None) is None):
826
self.PropertyChanged(dbus.String(u"CheckerRunning"),
827
dbus.Boolean(False, variant_level=1))
830
def _reset_approved(self):
831
self._approved = None
834
def approve(self, value=True):
835
self.send_changedstate()
836
self._approved = value
837
gobject.timeout_add(self._timedelta_to_milliseconds
838
(self.approval_duration),
839
self._reset_approved)
842
## D-Bus methods, signals & properties
843
_interface = u"se.bsnet.fukt.Mandos.Client"
847
# CheckerCompleted - signal
848
@dbus.service.signal(_interface, signature=u"nxs")
849
def CheckerCompleted(self, exitcode, waitstatus, command):
853
# CheckerStarted - signal
854
@dbus.service.signal(_interface, signature=u"s")
855
def CheckerStarted(self, command):
859
# PropertyChanged - signal
860
@dbus.service.signal(_interface, signature=u"sv")
861
def PropertyChanged(self, property, value):
866
@dbus.service.signal(_interface)
869
Is sent after a successful transfer of secret from the Mandos
870
server to mandos-client
875
@dbus.service.signal(_interface, signature=u"s")
876
def Rejected(self, reason):
880
# NeedApproval - signal
881
@dbus.service.signal(_interface, signature=u"tb")
882
def NeedApproval(self, timeout, default):
889
@dbus.service.method(_interface, in_signature=u"b")
890
def Approve(self, value):
894
@dbus.service.method(_interface)
896
return self.checked_ok()
899
@dbus.service.method(_interface)
904
# StartChecker - method
905
@dbus.service.method(_interface)
906
def StartChecker(self):
911
@dbus.service.method(_interface)
916
# StopChecker - method
917
@dbus.service.method(_interface)
918
def StopChecker(self):
923
# ApprovalPending - property
924
@dbus_service_property(_interface, signature=u"b", access=u"read")
925
def ApprovalPending_dbus_property(self):
926
return dbus.Boolean(bool(self.approvals_pending))
928
# ApprovedByDefault - property
929
@dbus_service_property(_interface, signature=u"b",
931
def ApprovedByDefault_dbus_property(self, value=None):
932
if value is None: # get
933
return dbus.Boolean(self.approved_by_default)
934
self.approved_by_default = bool(value)
936
self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
937
dbus.Boolean(value, variant_level=1))
939
# ApprovalDelay - property
940
@dbus_service_property(_interface, signature=u"t",
942
def ApprovalDelay_dbus_property(self, value=None):
943
if value is None: # get
944
return dbus.UInt64(self.approval_delay_milliseconds())
945
self.approval_delay = datetime.timedelta(0, 0, 0, value)
947
self.PropertyChanged(dbus.String(u"ApprovalDelay"),
948
dbus.UInt64(value, variant_level=1))
950
# ApprovalDuration - property
951
@dbus_service_property(_interface, signature=u"t",
953
def ApprovalDuration_dbus_property(self, value=None):
954
if value is None: # get
955
return dbus.UInt64(self._timedelta_to_milliseconds(
956
self.approval_duration))
957
self.approval_duration = datetime.timedelta(0, 0, 0, value)
959
self.PropertyChanged(dbus.String(u"ApprovalDuration"),
960
dbus.UInt64(value, variant_level=1))
963
@dbus_service_property(_interface, signature=u"s", access=u"read")
964
def Name_dbus_property(self):
965
return dbus.String(self.name)
967
# Fingerprint - property
968
@dbus_service_property(_interface, signature=u"s", access=u"read")
969
def Fingerprint_dbus_property(self):
970
return dbus.String(self.fingerprint)
973
@dbus_service_property(_interface, signature=u"s",
975
def Host_dbus_property(self, value=None):
976
if value is None: # get
977
return dbus.String(self.host)
980
self.PropertyChanged(dbus.String(u"Host"),
981
dbus.String(value, variant_level=1))
984
@dbus_service_property(_interface, signature=u"s", access=u"read")
985
def Created_dbus_property(self):
986
return dbus.String(self._datetime_to_dbus(self.created))
988
# LastEnabled - property
989
@dbus_service_property(_interface, signature=u"s", access=u"read")
990
def LastEnabled_dbus_property(self):
991
if self.last_enabled is None:
992
return dbus.String(u"")
993
return dbus.String(self._datetime_to_dbus(self.last_enabled))
996
@dbus_service_property(_interface, signature=u"b",
998
def Enabled_dbus_property(self, value=None):
999
if value is None: # get
1000
return dbus.Boolean(self.enabled)
1006
# LastCheckedOK - property
1007
@dbus_service_property(_interface, signature=u"s",
1008
access=u"readwrite")
1009
def LastCheckedOK_dbus_property(self, value=None):
1010
if value is not None:
371
1013
if self.last_checked_ok is None:
372
return now < (self.created + self.timeout)
374
return now < (self.last_checked_ok + self.timeout)
377
def peer_certificate(session):
378
"Return the peer's OpenPGP certificate as a bytestring"
379
# If not an OpenPGP certificate...
380
if gnutls.library.functions.gnutls_certificate_type_get\
381
(session._c_object) \
382
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
383
# ...do the normal thing
384
return session.peer_certificate
385
list_size = ctypes.c_uint()
386
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
387
(session._c_object, ctypes.byref(list_size))
388
if list_size.value == 0:
391
return ctypes.string_at(cert.data, cert.size)
394
def fingerprint(openpgp):
395
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
396
# New GnuTLS "datum" with the OpenPGP public key
397
datum = gnutls.library.types.gnutls_datum_t\
398
(ctypes.cast(ctypes.c_char_p(openpgp),
399
ctypes.POINTER(ctypes.c_ubyte)),
400
ctypes.c_uint(len(openpgp)))
401
# New empty GnuTLS certificate
402
crt = gnutls.library.types.gnutls_openpgp_crt_t()
403
gnutls.library.functions.gnutls_openpgp_crt_init\
405
# Import the OpenPGP public key into the certificate
406
gnutls.library.functions.gnutls_openpgp_crt_import\
407
(crt, ctypes.byref(datum),
408
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
409
# New buffer for the fingerprint
410
buffer = ctypes.create_string_buffer(20)
411
buffer_length = ctypes.c_size_t()
412
# Get the fingerprint from the certificate into the buffer
413
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
414
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
415
# Deinit the certificate
416
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
417
# Convert the buffer to a Python bytestring
418
fpr = ctypes.string_at(buffer, buffer_length.value)
419
# Convert the bytestring to hexadecimal notation
420
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
424
class tcp_handler(SocketServer.BaseRequestHandler, object):
425
"""A TCP request handler class.
426
Instantiated by IPv6_TCPServer for each request to handle it.
1014
return dbus.String(u"")
1015
return dbus.String(self._datetime_to_dbus(self
1018
# Timeout - property
1019
@dbus_service_property(_interface, signature=u"t",
1020
access=u"readwrite")
1021
def Timeout_dbus_property(self, value=None):
1022
if value is None: # get
1023
return dbus.UInt64(self.timeout_milliseconds())
1024
self.timeout = datetime.timedelta(0, 0, 0, value)
1026
self.PropertyChanged(dbus.String(u"Timeout"),
1027
dbus.UInt64(value, variant_level=1))
1028
if getattr(self, u"disable_initiator_tag", None) is None:
1030
# Reschedule timeout
1031
gobject.source_remove(self.disable_initiator_tag)
1032
self.disable_initiator_tag = None
1033
time_to_die = (self.
1034
_timedelta_to_milliseconds((self
1039
if time_to_die <= 0:
1040
# The timeout has passed
1043
self.disable_initiator_tag = (gobject.timeout_add
1044
(time_to_die, self.disable))
1046
# Interval - property
1047
@dbus_service_property(_interface, signature=u"t",
1048
access=u"readwrite")
1049
def Interval_dbus_property(self, value=None):
1050
if value is None: # get
1051
return dbus.UInt64(self.interval_milliseconds())
1052
self.interval = datetime.timedelta(0, 0, 0, value)
1054
self.PropertyChanged(dbus.String(u"Interval"),
1055
dbus.UInt64(value, variant_level=1))
1056
if getattr(self, u"checker_initiator_tag", None) is None:
1058
# Reschedule checker run
1059
gobject.source_remove(self.checker_initiator_tag)
1060
self.checker_initiator_tag = (gobject.timeout_add
1061
(value, self.start_checker))
1062
self.start_checker() # Start one now, too
1064
# Checker - property
1065
@dbus_service_property(_interface, signature=u"s",
1066
access=u"readwrite")
1067
def Checker_dbus_property(self, value=None):
1068
if value is None: # get
1069
return dbus.String(self.checker_command)
1070
self.checker_command = value
1072
self.PropertyChanged(dbus.String(u"Checker"),
1073
dbus.String(self.checker_command,
1076
# CheckerRunning - property
1077
@dbus_service_property(_interface, signature=u"b",
1078
access=u"readwrite")
1079
def CheckerRunning_dbus_property(self, value=None):
1080
if value is None: # get
1081
return dbus.Boolean(self.checker is not None)
1083
self.start_checker()
1087
# ObjectPath - property
1088
@dbus_service_property(_interface, signature=u"o", access=u"read")
1089
def ObjectPath_dbus_property(self):
1090
return self.dbus_object_path # is already a dbus.ObjectPath
1093
@dbus_service_property(_interface, signature=u"ay",
1094
access=u"write", byte_arrays=True)
1095
def Secret_dbus_property(self, value):
1096
self.secret = str(value)
1101
class ProxyClient(object):
1102
def __init__(self, child_pipe, fpr, address):
1103
self._pipe = child_pipe
1104
self._pipe.send(('init', fpr, address))
1105
if not self._pipe.recv():
1108
def __getattribute__(self, name):
1109
if(name == '_pipe'):
1110
return super(ProxyClient, self).__getattribute__(name)
1111
self._pipe.send(('getattr', name))
1112
data = self._pipe.recv()
1113
if data[0] == 'data':
1115
if data[0] == 'function':
1116
def func(*args, **kwargs):
1117
self._pipe.send(('funcall', name, args, kwargs))
1118
return self._pipe.recv()[1]
1121
def __setattr__(self, name, value):
1122
if(name == '_pipe'):
1123
return super(ProxyClient, self).__setattr__(name, value)
1124
self._pipe.send(('setattr', name, value))
1127
class ClientHandler(socketserver.BaseRequestHandler, object):
1128
"""A class to handle client connections.
1130
Instantiated once for each connection to handle it.
427
1131
Note: This will run in its own forked process."""
429
1133
def handle(self):
430
logger.info(u"TCP connection from: %s",
431
unicode(self.client_address))
432
session = gnutls.connection.ClientSession\
433
(self.request, gnutls.connection.X509Credentials())
435
line = self.request.makefile().readline()
436
logger.debug(u"Protocol version: %r", line)
438
if int(line.strip().split()[0]) > 1:
440
except (ValueError, IndexError, RuntimeError), error:
441
logger.error(u"Unknown protocol version: %s", error)
444
# Note: gnutls.connection.X509Credentials is really a generic
445
# GnuTLS certificate credentials object so long as no X.509
446
# keys are added to it. Therefore, we can use it here despite
447
# using OpenPGP certificates.
449
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
450
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
452
priority = "NORMAL" # Fallback default, since this
454
if self.server.settings["priority"]:
455
priority = self.server.settings["priority"]
456
gnutls.library.functions.gnutls_priority_set_direct\
457
(session._c_object, priority, None);
461
except gnutls.errors.GNUTLSError, error:
462
logger.warning(u"Handshake failed: %s", error)
463
# Do not run session.bye() here: the session is not
464
# established. Just abandon the request.
467
fpr = fingerprint(peer_certificate(session))
468
except (TypeError, gnutls.errors.GNUTLSError), error:
469
logger.warning(u"Bad certificate: %s", error)
472
logger.debug(u"Fingerprint: %s", fpr)
474
for c in self.server.clients:
475
if c.fingerprint == fpr:
479
logger.warning(u"Client not found for fingerprint: %s",
483
# Have to check if client.still_valid(), since it is possible
484
# that the client timed out while establishing the GnuTLS
486
if not client.still_valid():
487
logger.warning(u"Client %(name)s is invalid",
492
while sent_size < len(client.secret):
493
sent = session.send(client.secret[sent_size:])
494
logger.debug(u"Sent: %d, remaining: %d",
495
sent, len(client.secret)
496
- (sent_size + sent))
501
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
502
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1134
with contextlib.closing(self.server.child_pipe) as child_pipe:
1135
logger.info(u"TCP connection from: %s",
1136
unicode(self.client_address))
1137
logger.debug(u"Pipe FD: %d",
1138
self.server.child_pipe.fileno())
1140
session = (gnutls.connection
1141
.ClientSession(self.request,
1143
.X509Credentials()))
1145
# Note: gnutls.connection.X509Credentials is really a
1146
# generic GnuTLS certificate credentials object so long as
1147
# no X.509 keys are added to it. Therefore, we can use it
1148
# here despite using OpenPGP certificates.
1150
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1151
# u"+AES-256-CBC", u"+SHA1",
1152
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1154
# Use a fallback default, since this MUST be set.
1155
priority = self.server.gnutls_priority
1156
if priority is None:
1157
priority = u"NORMAL"
1158
(gnutls.library.functions
1159
.gnutls_priority_set_direct(session._c_object,
1162
# Start communication using the Mandos protocol
1163
# Get protocol number
1164
line = self.request.makefile().readline()
1165
logger.debug(u"Protocol version: %r", line)
1167
if int(line.strip().split()[0]) > 1:
1169
except (ValueError, IndexError, RuntimeError), error:
1170
logger.error(u"Unknown protocol version: %s", error)
1173
# Start GnuTLS connection
1176
except gnutls.errors.GNUTLSError, error:
1177
logger.warning(u"Handshake failed: %s", error)
1178
# Do not run session.bye() here: the session is not
1179
# established. Just abandon the request.
1181
logger.debug(u"Handshake succeeded")
1183
approval_required = False
1186
fpr = self.fingerprint(self.peer_certificate
1188
except (TypeError, gnutls.errors.GNUTLSError), error:
1189
logger.warning(u"Bad certificate: %s", error)
1191
logger.debug(u"Fingerprint: %s", fpr)
1194
client = ProxyClient(child_pipe, fpr,
1195
self.client_address)
1199
if client.approval_delay:
1200
delay = client.approval_delay
1201
client.approvals_pending += 1
1202
approval_required = True
1205
if not client.enabled:
1206
logger.warning(u"Client %s is disabled",
1208
if self.server.use_dbus:
1210
client.Rejected("Disabled")
1213
if client._approved or not client.approval_delay:
1214
#We are approved or approval is disabled
1216
elif client._approved is None:
1217
logger.info(u"Client %s needs approval",
1219
if self.server.use_dbus:
1221
client.NeedApproval(
1222
client.approval_delay_milliseconds(),
1223
client.approved_by_default)
1225
logger.warning(u"Client %s was not approved",
1227
if self.server.use_dbus:
1229
client.Rejected("Denied")
1232
#wait until timeout or approved
1233
#x = float(client._timedelta_to_milliseconds(delay))
1234
time = datetime.datetime.now()
1235
client.changedstate.acquire()
1236
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1237
client.changedstate.release()
1238
time2 = datetime.datetime.now()
1239
if (time2 - time) >= delay:
1240
if not client.approved_by_default:
1241
logger.warning("Client %s timed out while"
1242
" waiting for approval",
1244
if self.server.use_dbus:
1246
client.Rejected("Timed out")
1251
delay -= time2 - time
1254
while sent_size < len(client.secret):
1256
sent = session.send(client.secret[sent_size:])
1257
except (gnutls.errors.GNUTLSError), error:
1258
logger.warning("gnutls send failed")
1260
logger.debug(u"Sent: %d, remaining: %d",
1261
sent, len(client.secret)
1262
- (sent_size + sent))
1265
logger.info(u"Sending secret to %s", client.name)
1266
# bump the timeout as if seen
1268
if self.server.use_dbus:
1273
if approval_required:
1274
client.approvals_pending -= 1
1277
except (gnutls.errors.GNUTLSError), error:
1278
logger.warning("GnuTLS bye failed")
1281
def peer_certificate(session):
1282
"Return the peer's OpenPGP certificate as a bytestring"
1283
# If not an OpenPGP certificate...
1284
if (gnutls.library.functions
1285
.gnutls_certificate_type_get(session._c_object)
1286
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1287
# ...do the normal thing
1288
return session.peer_certificate
1289
list_size = ctypes.c_uint(1)
1290
cert_list = (gnutls.library.functions
1291
.gnutls_certificate_get_peers
1292
(session._c_object, ctypes.byref(list_size)))
1293
if not bool(cert_list) and list_size.value != 0:
1294
raise gnutls.errors.GNUTLSError(u"error getting peer"
1296
if list_size.value == 0:
1299
return ctypes.string_at(cert.data, cert.size)
1302
def fingerprint(openpgp):
1303
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1304
# New GnuTLS "datum" with the OpenPGP public key
1305
datum = (gnutls.library.types
1306
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1309
ctypes.c_uint(len(openpgp))))
1310
# New empty GnuTLS certificate
1311
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1312
(gnutls.library.functions
1313
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1314
# Import the OpenPGP public key into the certificate
1315
(gnutls.library.functions
1316
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1317
gnutls.library.constants
1318
.GNUTLS_OPENPGP_FMT_RAW))
1319
# Verify the self signature in the key
1320
crtverify = ctypes.c_uint()
1321
(gnutls.library.functions
1322
.gnutls_openpgp_crt_verify_self(crt, 0,
1323
ctypes.byref(crtverify)))
1324
if crtverify.value != 0:
1325
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1326
raise (gnutls.errors.CertificateSecurityError
1328
# New buffer for the fingerprint
1329
buf = ctypes.create_string_buffer(20)
1330
buf_len = ctypes.c_size_t()
1331
# Get the fingerprint from the certificate into the buffer
1332
(gnutls.library.functions
1333
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1334
ctypes.byref(buf_len)))
1335
# Deinit the certificate
1336
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1337
# Convert the buffer to a Python bytestring
1338
fpr = ctypes.string_at(buf, buf_len.value)
1339
# Convert the bytestring to hexadecimal notation
1340
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1344
class MultiprocessingMixIn(object):
1345
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1346
def sub_process_main(self, request, address):
1348
self.finish_request(request, address)
1350
self.handle_error(request, address)
1351
self.close_request(request)
1353
def process_request(self, request, address):
1354
"""Start a new process to process the request."""
1355
multiprocessing.Process(target = self.sub_process_main,
1356
args = (request, address)).start()
1358
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1359
""" adds a pipe to the MixIn """
1360
def process_request(self, request, client_address):
1361
"""Overrides and wraps the original process_request().
1363
This function creates a new pipe in self.pipe
1365
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1367
super(MultiprocessingMixInWithPipe,
1368
self).process_request(request, client_address)
1369
self.child_pipe.close()
1370
self.add_pipe(parent_pipe)
1372
def add_pipe(self, parent_pipe):
1373
"""Dummy function; override as necessary"""
1376
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1377
socketserver.TCPServer, object):
1378
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
504
settings: Server settings
505
clients: Set() of Client objects
1381
enabled: Boolean; whether this server is activated yet
1382
interface: None or a network interface name (string)
1383
use_ipv6: Boolean; to use IPv6 or not
507
address_family = socket.AF_INET6
508
def __init__(self, *args, **kwargs):
509
if "settings" in kwargs:
510
self.settings = kwargs["settings"]
511
del kwargs["settings"]
512
if "clients" in kwargs:
513
self.clients = kwargs["clients"]
514
del kwargs["clients"]
515
return super(type(self), self).__init__(*args, **kwargs)
1385
def __init__(self, server_address, RequestHandlerClass,
1386
interface=None, use_ipv6=True):
1387
self.interface = interface
1389
self.address_family = socket.AF_INET6
1390
socketserver.TCPServer.__init__(self, server_address,
1391
RequestHandlerClass)
516
1392
def server_bind(self):
517
1393
"""This overrides the normal server_bind() function
518
1394
to bind to an interface if one was specified, and also NOT to
519
1395
bind to an address or port if they were not specified."""
520
if self.settings["interface"]:
521
# 25 is from /usr/include/asm-i486/socket.h
522
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
524
self.socket.setsockopt(socket.SOL_SOCKET,
526
self.settings["interface"])
527
except socket.error, error:
528
if error[0] == errno.EPERM:
529
logger.error(u"No permission to"
530
u" bind to interface %s",
531
self.settings["interface"])
1396
if self.interface is not None:
1397
if SO_BINDTODEVICE is None:
1398
logger.error(u"SO_BINDTODEVICE does not exist;"
1399
u" cannot bind to interface %s",
1403
self.socket.setsockopt(socket.SOL_SOCKET,
1407
except socket.error, error:
1408
if error[0] == errno.EPERM:
1409
logger.error(u"No permission to"
1410
u" bind to interface %s",
1412
elif error[0] == errno.ENOPROTOOPT:
1413
logger.error(u"SO_BINDTODEVICE not available;"
1414
u" cannot bind to interface %s",
534
1418
# Only bind(2) the socket if we really need to.
535
1419
if self.server_address[0] or self.server_address[1]:
536
1420
if not self.server_address[0]:
538
self.server_address = (in6addr_any,
1421
if self.address_family == socket.AF_INET6:
1422
any_address = u"::" # in6addr_any
1424
any_address = socket.INADDR_ANY
1425
self.server_address = (any_address,
539
1426
self.server_address[1])
540
1427
elif not self.server_address[1]:
541
1428
self.server_address = (self.server_address[0],
543
# if self.settings["interface"]:
1430
# if self.interface:
544
1431
# self.server_address = (self.server_address[0],
547
1434
# if_nametoindex
550
return super(type(self), self).server_bind()
1436
return socketserver.TCPServer.server_bind(self)
1439
class MandosServer(IPv6_TCPServer):
1443
clients: set of Client objects
1444
gnutls_priority GnuTLS priority string
1445
use_dbus: Boolean; to emit D-Bus signals or not
1447
Assumes a gobject.MainLoop event loop.
1449
def __init__(self, server_address, RequestHandlerClass,
1450
interface=None, use_ipv6=True, clients=None,
1451
gnutls_priority=None, use_dbus=True):
1452
self.enabled = False
1453
self.clients = clients
1454
if self.clients is None:
1455
self.clients = set()
1456
self.use_dbus = use_dbus
1457
self.gnutls_priority = gnutls_priority
1458
IPv6_TCPServer.__init__(self, server_address,
1459
RequestHandlerClass,
1460
interface = interface,
1461
use_ipv6 = use_ipv6)
1462
def server_activate(self):
1464
return socketserver.TCPServer.server_activate(self)
1467
def add_pipe(self, parent_pipe):
1468
# Call "handle_ipc" for both data and EOF events
1469
gobject.io_add_watch(parent_pipe.fileno(),
1470
gobject.IO_IN | gobject.IO_HUP,
1471
functools.partial(self.handle_ipc,
1472
parent_pipe = parent_pipe))
1474
def handle_ipc(self, source, condition, parent_pipe=None,
1475
client_object=None):
1477
gobject.IO_IN: u"IN", # There is data to read.
1478
gobject.IO_OUT: u"OUT", # Data can be written (without
1480
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1481
gobject.IO_ERR: u"ERR", # Error condition.
1482
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1483
# broken, usually for pipes and
1486
conditions_string = ' | '.join(name
1488
condition_names.iteritems()
1489
if cond & condition)
1490
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1493
# error or the other end of multiprocessing.Pipe has closed
1494
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1497
# Read a request from the child
1498
request = parent_pipe.recv()
1499
logger.debug(u"IPC request: %s", repr(request))
1500
command = request[0]
1502
if command == 'init':
1504
address = request[2]
1506
for c in self.clients:
1507
if c.fingerprint == fpr:
1511
logger.warning(u"Client not found for fingerprint: %s, ad"
1512
u"dress: %s", fpr, address)
1515
mandos_dbus_service.ClientNotFound(fpr, address)
1516
parent_pipe.send(False)
1519
gobject.io_add_watch(parent_pipe.fileno(),
1520
gobject.IO_IN | gobject.IO_HUP,
1521
functools.partial(self.handle_ipc,
1522
parent_pipe = parent_pipe,
1523
client_object = client))
1524
parent_pipe.send(True)
1525
# remove the old hook in favor of the new above hook on same fileno
1527
if command == 'funcall':
1528
funcname = request[1]
1532
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1534
if command == 'getattr':
1535
attrname = request[1]
1536
if callable(client_object.__getattribute__(attrname)):
1537
parent_pipe.send(('function',))
1539
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1541
if command == 'setattr':
1542
attrname = request[1]
1544
setattr(client_object, attrname, value)
553
1549
def string_to_delta(interval):
554
1550
"""Parse a string and return a datetime.timedelta
556
>>> string_to_delta('7d')
1552
>>> string_to_delta(u'7d')
557
1553
datetime.timedelta(7)
558
>>> string_to_delta('60s')
1554
>>> string_to_delta(u'60s')
559
1555
datetime.timedelta(0, 60)
560
>>> string_to_delta('60m')
1556
>>> string_to_delta(u'60m')
561
1557
datetime.timedelta(0, 3600)
562
>>> string_to_delta('24h')
1558
>>> string_to_delta(u'24h')
563
1559
datetime.timedelta(1)
564
1560
>>> string_to_delta(u'1w')
565
1561
datetime.timedelta(7)
1562
>>> string_to_delta(u'5m 30s')
1563
datetime.timedelta(0, 330)
568
suffix=unicode(interval[-1])
569
value=int(interval[:-1])
571
delta = datetime.timedelta(value)
573
delta = datetime.timedelta(0, value)
575
delta = datetime.timedelta(0, 0, 0, 0, value)
577
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
579
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
582
except (ValueError, IndexError):
587
def server_state_changed(state):
588
"""Derived from the Avahi example code"""
589
if state == avahi.SERVER_COLLISION:
590
logger.error(u"Server name collision")
592
elif state == avahi.SERVER_RUNNING:
596
def entry_group_state_changed(state, error):
597
"""Derived from the Avahi example code"""
598
logger.debug(u"state change: %i", state)
600
if state == avahi.ENTRY_GROUP_ESTABLISHED:
601
logger.debug(u"Service established.")
602
elif state == avahi.ENTRY_GROUP_COLLISION:
603
logger.warning(u"Service name collision.")
605
elif state == avahi.ENTRY_GROUP_FAILURE:
606
logger.critical(u"Error in group state changed %s",
608
raise AvahiGroupError("State changed: %s", str(error))
1565
timevalue = datetime.timedelta(0)
1566
for s in interval.split():
1568
suffix = unicode(s[-1])
1571
delta = datetime.timedelta(value)
1572
elif suffix == u"s":
1573
delta = datetime.timedelta(0, value)
1574
elif suffix == u"m":
1575
delta = datetime.timedelta(0, 0, 0, 0, value)
1576
elif suffix == u"h":
1577
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1578
elif suffix == u"w":
1579
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1581
raise ValueError(u"Unknown suffix %r" % suffix)
1582
except (ValueError, IndexError), e:
1583
raise ValueError(e.message)
610
1588
def if_nametoindex(interface):
611
"""Call the C function if_nametoindex(), or equivalent"""
1589
"""Call the C function if_nametoindex(), or equivalent
1591
Note: This function cannot accept a unicode string."""
612
1592
global if_nametoindex
614
if "ctypes.util" not in sys.modules:
616
if_nametoindex = ctypes.cdll.LoadLibrary\
617
(ctypes.util.find_library("c")).if_nametoindex
1594
if_nametoindex = (ctypes.cdll.LoadLibrary
1595
(ctypes.util.find_library(u"c"))
618
1597
except (OSError, AttributeError):
619
if "struct" not in sys.modules:
621
if "fcntl" not in sys.modules:
1598
logger.warning(u"Doing if_nametoindex the hard way")
623
1599
def if_nametoindex(interface):
624
1600
"Get an interface index the hard way, i.e. using fcntl()"
625
1601
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
627
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
628
struct.pack("16s16x", interface))
630
interface_index = struct.unpack("I", ifreq[16:20])[0]
1602
with contextlib.closing(socket.socket()) as s:
1603
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1604
struct.pack(str(u"16s16x"),
1606
interface_index = struct.unpack(str(u"I"),
631
1608
return interface_index
632
1609
return if_nametoindex(interface)
635
1612
def daemon(nochdir = False, noclose = False):
636
1613
"""See daemon(3). Standard BSD Unix function.
637
1615
This should really exist as os.daemon, but it doesn't (yet)."""
689
1676
# Default values for config file for server-global settings
690
server_defaults = { "interface": "",
695
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
696
"servicename": "Mandos",
1677
server_defaults = { u"interface": u"",
1682
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1683
u"servicename": u"Mandos",
1684
u"use_dbus": u"True",
1685
u"use_ipv6": u"True",
699
1689
# Parse config file for server-global settings
700
server_config = ConfigParser.SafeConfigParser(server_defaults)
1690
server_config = configparser.SafeConfigParser(server_defaults)
701
1691
del server_defaults
702
server_config.read(os.path.join(options.configdir, "mandos.conf"))
703
server_section = "server"
1692
server_config.read(os.path.join(options.configdir,
704
1694
# Convert the SafeConfigParser object to a dict
705
server_settings = dict(server_config.items(server_section))
706
# Use getboolean on the boolean config option
707
server_settings["debug"] = server_config.getboolean\
708
(server_section, "debug")
1695
server_settings = server_config.defaults()
1696
# Use the appropriate methods on the non-string config options
1697
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1698
server_settings[option] = server_config.getboolean(u"DEFAULT",
1700
if server_settings["port"]:
1701
server_settings["port"] = server_config.getint(u"DEFAULT",
709
1703
del server_config
711
1705
# Override the settings from the config file with command line
712
1706
# options, if set.
713
for option in ("interface", "address", "port", "debug",
714
"priority", "servicename", "configdir"):
1707
for option in (u"interface", u"address", u"port", u"debug",
1708
u"priority", u"servicename", u"configdir",
1709
u"use_dbus", u"use_ipv6", u"debuglevel"):
715
1710
value = getattr(options, option)
716
1711
if value is not None:
717
1712
server_settings[option] = value
1714
# Force all strings to be unicode
1715
for option in server_settings.keys():
1716
if type(server_settings[option]) is str:
1717
server_settings[option] = unicode(server_settings[option])
719
1718
# Now we have our good server settings in "server_settings"
721
debug = server_settings["debug"]
1720
##################################################################
1723
debug = server_settings[u"debug"]
1724
debuglevel = server_settings[u"debuglevel"]
1725
use_dbus = server_settings[u"use_dbus"]
1726
use_ipv6 = server_settings[u"use_ipv6"]
1728
if server_settings[u"servicename"] != u"Mandos":
1729
syslogger.setFormatter(logging.Formatter
1730
(u'Mandos (%s) [%%(process)d]:'
1731
u' %%(levelname)s: %%(message)s'
1732
% server_settings[u"servicename"]))
1734
# Parse config file with clients
1735
client_defaults = { u"timeout": u"1h",
1737
u"checker": u"fping -q -- %%(host)s",
1739
u"approval_delay": u"0s",
1740
u"approval_duration": u"1s",
1742
client_config = configparser.SafeConfigParser(client_defaults)
1743
client_config.read(os.path.join(server_settings[u"configdir"],
1746
global mandos_dbus_service
1747
mandos_dbus_service = None
1749
tcp_server = MandosServer((server_settings[u"address"],
1750
server_settings[u"port"]),
1752
interface=(server_settings[u"interface"]
1756
server_settings[u"priority"],
1758
pidfilename = u"/var/run/mandos.pid"
1760
pidfile = open(pidfilename, u"w")
1762
logger.error(u"Could not open file %r", pidfilename)
1765
uid = pwd.getpwnam(u"_mandos").pw_uid
1766
gid = pwd.getpwnam(u"_mandos").pw_gid
1769
uid = pwd.getpwnam(u"mandos").pw_uid
1770
gid = pwd.getpwnam(u"mandos").pw_gid
1773
uid = pwd.getpwnam(u"nobody").pw_uid
1774
gid = pwd.getpwnam(u"nobody").pw_gid
1781
except OSError, error:
1782
if error[0] != errno.EPERM:
1785
if not debug and not debuglevel:
724
1786
syslogger.setLevel(logging.WARNING)
725
1787
console.setLevel(logging.WARNING)
727
if server_settings["servicename"] != "Mandos":
728
syslogger.setFormatter(logging.Formatter\
729
('Mandos (%s): %%(levelname)s:'
731
% server_settings["servicename"]))
733
# Parse config file with clients
734
client_defaults = { "timeout": "1h",
736
"checker": "fping -q -- %%(host)s",
738
client_config = ConfigParser.SafeConfigParser(client_defaults)
739
client_config.read(os.path.join(server_settings["configdir"],
743
service = AvahiService(name = server_settings["servicename"],
744
type = "_mandos._tcp", );
745
if server_settings["interface"]:
746
service.interface = if_nametoindex(server_settings["interface"])
1789
level = getattr(logging, debuglevel.upper())
1790
syslogger.setLevel(level)
1791
console.setLevel(level)
1794
# Enable all possible GnuTLS debugging
1796
# "Use a log level over 10 to enable all debugging options."
1798
gnutls.library.functions.gnutls_global_set_log_level(11)
1800
@gnutls.library.types.gnutls_log_func
1801
def debug_gnutls(level, string):
1802
logger.debug(u"GnuTLS: %s", string[:-1])
1804
(gnutls.library.functions
1805
.gnutls_global_set_log_function(debug_gnutls))
1807
# Redirect stdin so all checkers get /dev/null
1808
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1809
os.dup2(null, sys.stdin.fileno())
1813
# No console logging
1814
logger.removeHandler(console)
748
1817
global main_loop
751
1818
# From the Avahi example code
752
1819
DBusGMainLoop(set_as_default=True )
753
1820
main_loop = gobject.MainLoop()
754
1821
bus = dbus.SystemBus()
755
server = dbus.Interface(
756
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
757
avahi.DBUS_INTERFACE_SERVER )
758
1822
# End of Avahi example code
761
def remove_from_clients(client):
762
clients.remove(client)
764
logger.critical(u"No clients left, exiting")
767
clients.update(Set(Client(name = section,
768
stop_hook = remove_from_clients,
770
= dict(client_config.items(section)))
771
for section in client_config.sections()))
773
logger.critical(u"No clients defined")
1825
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1826
bus, do_not_queue=True)
1827
except dbus.exceptions.NameExistsException, e:
1828
logger.error(unicode(e) + u", disabling D-Bus")
1830
server_settings[u"use_dbus"] = False
1831
tcp_server.use_dbus = False
1832
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1833
service = AvahiService(name = server_settings[u"servicename"],
1834
servicetype = u"_mandos._tcp",
1835
protocol = protocol, bus = bus)
1836
if server_settings["interface"]:
1837
service.interface = (if_nametoindex
1838
(str(server_settings[u"interface"])))
777
logger.removeHandler(console)
1841
# Close all input and output, do double fork, etc.
780
pidfilename = "/var/run/mandos/mandos.pid"
1844
global multiprocessing_manager
1845
multiprocessing_manager = multiprocessing.Manager()
1847
client_class = Client
1849
client_class = functools.partial(ClientDBus, bus = bus)
1850
def client_config_items(config, section):
1851
special_settings = {
1852
"approved_by_default":
1853
lambda: config.getboolean(section,
1854
"approved_by_default"),
1856
for name, value in config.items(section):
1858
yield (name, special_settings[name]())
1862
tcp_server.clients.update(set(
1863
client_class(name = section,
1864
config= dict(client_config_items(
1865
client_config, section)))
1866
for section in client_config.sections()))
1867
if not tcp_server.clients:
1868
logger.warning(u"No clients defined")
783
pidfile = open(pidfilename, "w")
784
pidfile.write(str(pid) + "\n")
1873
pidfile.write(str(pid) + "\n")
788
logger.error(u"Could not write %s file with PID %d",
789
pidfilename, os.getpid())
1876
logger.error(u"Could not write to file %r with PID %d",
1879
# "pidfile" was never created
1884
signal.signal(signal.SIGINT, signal.SIG_IGN)
1885
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1886
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1889
class MandosDBusService(dbus.service.Object):
1890
"""A D-Bus proxy object"""
1892
dbus.service.Object.__init__(self, bus, u"/")
1893
_interface = u"se.bsnet.fukt.Mandos"
1895
@dbus.service.signal(_interface, signature=u"o")
1896
def ClientAdded(self, objpath):
1900
@dbus.service.signal(_interface, signature=u"ss")
1901
def ClientNotFound(self, fingerprint, address):
1905
@dbus.service.signal(_interface, signature=u"os")
1906
def ClientRemoved(self, objpath, name):
1910
@dbus.service.method(_interface, out_signature=u"ao")
1911
def GetAllClients(self):
1913
return dbus.Array(c.dbus_object_path
1914
for c in tcp_server.clients)
1916
@dbus.service.method(_interface,
1917
out_signature=u"a{oa{sv}}")
1918
def GetAllClientsWithProperties(self):
1920
return dbus.Dictionary(
1921
((c.dbus_object_path, c.GetAll(u""))
1922
for c in tcp_server.clients),
1923
signature=u"oa{sv}")
1925
@dbus.service.method(_interface, in_signature=u"o")
1926
def RemoveClient(self, object_path):
1928
for c in tcp_server.clients:
1929
if c.dbus_object_path == object_path:
1930
tcp_server.clients.remove(c)
1931
c.remove_from_connection()
1932
# Don't signal anything except ClientRemoved
1933
c.disable(quiet=True)
1935
self.ClientRemoved(object_path, c.name)
1937
raise KeyError(object_path)
1941
mandos_dbus_service = MandosDBusService()
792
1944
"Cleanup function; run on exit"
794
# From the Avahi example code
795
if not group is None:
798
# End of Avahi example code
801
client = clients.pop()
802
client.stop_hook = None
1947
while tcp_server.clients:
1948
client = tcp_server.clients.pop()
1950
client.remove_from_connection()
1951
client.disable_hook = None
1952
# Don't signal anything except ClientRemoved
1953
client.disable(quiet=True)
1956
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
805
1959
atexit.register(cleanup)
808
signal.signal(signal.SIGINT, signal.SIG_IGN)
809
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
810
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
812
for client in clients:
815
tcp_server = IPv6_TCPServer((server_settings["address"],
816
server_settings["port"]),
818
settings=server_settings,
1961
for client in tcp_server.clients:
1964
mandos_dbus_service.ClientAdded(client.dbus_object_path)
1968
tcp_server.server_activate()
820
1970
# Find out what port we got
821
1971
service.port = tcp_server.socket.getsockname()[1]
822
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
823
u" scope_id %d" % tcp_server.socket.getsockname())
1973
logger.info(u"Now listening on address %r, port %d,"
1974
" flowinfo %d, scope_id %d"
1975
% tcp_server.socket.getsockname())
1977
logger.info(u"Now listening on address %r, port %d"
1978
% tcp_server.socket.getsockname())
825
1980
#service.interface = tcp_server.socket.getsockname()[3]
828
1983
# From the Avahi example code
829
server.connect_to_signal("StateChanged", server_state_changed)
831
server_state_changed(server.GetState())
832
1986
except dbus.exceptions.DBusException, error:
833
1987
logger.critical(u"DBusException: %s", error)
835
1990
# End of Avahi example code
837
1992
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
838
1993
lambda *args, **kwargs:
839
tcp_server.handle_request\
840
(*args[2:], **kwargs) or True)
1994
(tcp_server.handle_request
1995
(*args[2:], **kwargs) or True))
842
1997
logger.debug(u"Starting main loop")
843
main_loop_started = True
845
1999
except AvahiError, error:
846
logger.critical(u"AvahiError: %s" + unicode(error))
2000
logger.critical(u"AvahiError: %s", error)
848
2003
except KeyboardInterrupt:
2006
logger.debug(u"Server received KeyboardInterrupt")
2007
logger.debug(u"Server exiting")
2008
# Must run before the D-Bus bus name gets deregistered
852
2011
if __name__ == '__main__':