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
148
self.max_renames = max_renames
149
self.protocol = protocol
150
self.group = None # our entry group
122
153
def rename(self):
123
154
"""Derived from the Avahi example code"""
124
155
if self.rename_count >= self.max_renames:
125
logger.critical(u"No suitable service name found after %i"
126
u" retries, exiting.", rename_count)
127
raise AvahiServiceError("Too many renames")
128
name = server.GetAlternativeServiceName(name)
129
logger.error(u"Changing name to %r ...", name)
130
syslogger.setFormatter(logging.Formatter\
131
('Mandos (%s): %%(levelname)s:'
132
' %%(message)s' % name))
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'
135
169
self.rename_count += 1
136
170
def remove(self):
137
171
"""Derived from the Avahi example code"""
138
if group is not None:
172
if self.group is not None:
141
175
"""Derived from the Avahi example code"""
144
group = dbus.Interface\
145
(bus.get_object(avahi.DBUS_NAME,
146
server.EntryGroupNew()),
147
avahi.DBUS_INTERFACE_ENTRY_GROUP)
148
group.connect_to_signal('StateChanged',
149
entry_group_state_changed)
150
logger.debug(u"Adding service '%s' of type '%s' ...",
151
service.name, service.type)
153
self.interface, # interface
154
avahi.PROTO_INET6, # protocol
155
dbus.UInt32(0), # flags
156
self.name, self.type,
157
self.domain, self.host,
158
dbus.UInt16(self.port),
159
avahi.string_array_to_txt_array(self.TXT))
162
# From the Avahi example code:
163
group = None # our entry group
164
# End of Avahi example code
176
if self.group is None:
177
self.group = dbus.Interface(
178
self.bus.get_object(avahi.DBUS_NAME,
179
self.server.EntryGroupNew()),
180
avahi.DBUS_INTERFACE_ENTRY_GROUP)
181
self.group.connect_to_signal('StateChanged',
183
.entry_group_state_changed)
184
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
185
self.name, self.type)
186
self.group.AddService(
189
dbus.UInt32(0), # flags
190
self.name, self.type,
191
self.domain, self.host,
192
dbus.UInt16(self.port),
193
avahi.string_array_to_txt_array(self.TXT))
195
def entry_group_state_changed(self, state, error):
196
"""Derived from the Avahi example code"""
197
logger.debug(u"Avahi state change: %i", state)
199
if state == avahi.ENTRY_GROUP_ESTABLISHED:
200
logger.debug(u"Zeroconf service established.")
201
elif state == avahi.ENTRY_GROUP_COLLISION:
202
logger.warning(u"Zeroconf service name collision.")
204
elif state == avahi.ENTRY_GROUP_FAILURE:
205
logger.critical(u"Avahi: Error in group state changed %s",
207
raise AvahiGroupError(u"State changed: %s"
210
"""Derived from the Avahi example code"""
211
if self.group is not None:
214
def server_state_changed(self, state):
215
"""Derived from the Avahi example code"""
216
if state == avahi.SERVER_COLLISION:
217
logger.error(u"Zeroconf server name collision")
219
elif state == avahi.SERVER_RUNNING:
222
"""Derived from the Avahi example code"""
223
if self.server is None:
224
self.server = dbus.Interface(
225
self.bus.get_object(avahi.DBUS_NAME,
226
avahi.DBUS_PATH_SERVER),
227
avahi.DBUS_INTERFACE_SERVER)
228
self.server.connect_to_signal(u"StateChanged",
229
self.server_state_changed)
230
self.server_state_changed(self.server.GetState())
167
233
class Client(object):
168
234
"""A representation of a client host served by this server.
170
name: string; from the config file, used in log messages
237
name: string; from the config file, used in log messages and
171
239
fingerprint: string (40 or 32 hexadecimal digits); used to
172
240
uniquely identify the client
173
secret: bytestring; sent verbatim (over TLS) to client
174
host: string; available for use by the checker command
175
created: datetime.datetime(); object creation, not client host
176
last_checked_ok: datetime.datetime() or None if not yet checked OK
177
timeout: datetime.timedelta(); How long from last_checked_ok
178
until this client is invalid
179
interval: datetime.timedelta(); How often to start a new checker
180
stop_hook: If set, called by stop() as stop_hook(self)
181
checker: subprocess.Popen(); a running checker process used
182
to see if the client lives.
183
'None' if no process is running.
241
secret: bytestring; sent verbatim (over TLS) to client
242
host: string; available for use by the checker command
243
created: datetime.datetime(); (UTC) object creation
244
last_enabled: datetime.datetime(); (UTC)
246
last_checked_ok: datetime.datetime(); (UTC) or None
247
timeout: datetime.timedelta(); How long from last_checked_ok
248
until this client is disabled
249
interval: datetime.timedelta(); How often to start a new checker
250
disable_hook: If set, called by disable() as disable_hook(self)
251
checker: subprocess.Popen(); a running checker process used
252
to see if the client lives.
253
'None' if no process is running.
184
254
checker_initiator_tag: a gobject event source tag, or None
185
stop_initiator_tag: - '' -
255
disable_initiator_tag: - '' -
186
256
checker_callback_tag: - '' -
187
257
checker_command: string; External command which is run to check if
188
258
client lives. %() expansions are done at
189
259
runtime with vars(self) as dict, so that for
190
260
instance %(name)s can be used in the command.
192
_timeout: Real variable for 'timeout'
193
_interval: Real variable for 'interval'
194
_timeout_milliseconds: Used when calling gobject.timeout_add()
195
_interval_milliseconds: - '' -
261
current_checker_command: string; current running checker_command
262
approved_delay: datetime.timedelta(); Time to wait for approval
263
_approved: bool(); 'None' if not yet approved/disapproved
264
approved_duration: datetime.timedelta(); Duration of one approval
197
def _set_timeout(self, timeout):
198
"Setter function for 'timeout' attribute"
199
self._timeout = timeout
200
self._timeout_milliseconds = ((self.timeout.days
201
* 24 * 60 * 60 * 1000)
202
+ (self.timeout.seconds * 1000)
203
+ (self.timeout.microseconds
205
timeout = property(lambda self: self._timeout,
208
def _set_interval(self, interval):
209
"Setter function for 'interval' attribute"
210
self._interval = interval
211
self._interval_milliseconds = ((self.interval.days
212
* 24 * 60 * 60 * 1000)
213
+ (self.interval.seconds
215
+ (self.interval.microseconds
217
interval = property(lambda self: self._interval,
220
def __init__(self, name = None, stop_hook=None, config={}):
268
def _timedelta_to_milliseconds(td):
269
"Convert a datetime.timedelta() to milliseconds"
270
return ((td.days * 24 * 60 * 60 * 1000)
271
+ (td.seconds * 1000)
272
+ (td.microseconds // 1000))
274
def timeout_milliseconds(self):
275
"Return the 'timeout' attribute in milliseconds"
276
return self._timedelta_to_milliseconds(self.timeout)
278
def interval_milliseconds(self):
279
"Return the 'interval' attribute in milliseconds"
280
return self._timedelta_to_milliseconds(self.interval)
282
def approved_delay_milliseconds(self):
283
return self._timedelta_to_milliseconds(self.approved_delay)
285
def __init__(self, name = None, disable_hook=None, config=None):
221
286
"""Note: the 'checker' key in 'config' sets the
222
287
'checker_command' attribute and *not* the 'checker'
225
292
logger.debug(u"Creating client %r", self.name)
226
293
# Uppercase and remove spaces from fingerprint for later
227
294
# comparison purposes with return value from the fingerprint()
229
self.fingerprint = config["fingerprint"].upper()\
296
self.fingerprint = (config[u"fingerprint"].upper()
231
298
logger.debug(u" Fingerprint: %s", self.fingerprint)
232
if "secret" in config:
233
self.secret = config["secret"].decode(u"base64")
234
elif "secfile" in config:
235
sf = open(config["secfile"])
236
self.secret = sf.read()
299
if u"secret" in config:
300
self.secret = config[u"secret"].decode(u"base64")
301
elif u"secfile" in config:
302
with open(os.path.expanduser(os.path.expandvars
303
(config[u"secfile"])),
305
self.secret = secfile.read()
239
307
raise TypeError(u"No secret or secfile for client %s"
241
self.host = config.get("host", "")
242
self.created = datetime.datetime.now()
309
self.host = config.get(u"host", u"")
310
self.created = datetime.datetime.utcnow()
312
self.last_enabled = None
243
313
self.last_checked_ok = None
244
self.timeout = string_to_delta(config["timeout"])
245
self.interval = string_to_delta(config["interval"])
246
self.stop_hook = stop_hook
314
self.timeout = string_to_delta(config[u"timeout"])
315
self.interval = string_to_delta(config[u"interval"])
316
self.disable_hook = disable_hook
247
317
self.checker = None
248
318
self.checker_initiator_tag = None
249
self.stop_initiator_tag = None
319
self.disable_initiator_tag = None
250
320
self.checker_callback_tag = None
251
self.check_command = config["checker"]
321
self.checker_command = config[u"checker"]
322
self.current_checker_command = None
323
self.last_connect = None
324
self._approved = None
325
self.approved_by_default = config.get(u"approved_by_default",
327
self.approvals_pending = 0
328
self.approved_delay = string_to_delta(
329
config[u"approved_delay"])
330
self.approved_duration = string_to_delta(
331
config[u"approved_duration"])
332
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
334
def send_changedstate(self):
335
self.changedstate.acquire()
336
self.changedstate.notify_all()
337
self.changedstate.release()
253
340
"""Start this client's checker and timeout hooks"""
341
if getattr(self, u"enabled", False):
344
self.send_changedstate()
345
self.last_enabled = datetime.datetime.utcnow()
254
346
# Schedule a new checker to be started an 'interval' from now,
255
347
# and every interval from then on.
256
self.checker_initiator_tag = gobject.timeout_add\
257
(self._interval_milliseconds,
348
self.checker_initiator_tag = (gobject.timeout_add
349
(self.interval_milliseconds(),
351
# Schedule a disable() when 'timeout' has passed
352
self.disable_initiator_tag = (gobject.timeout_add
353
(self.timeout_milliseconds(),
259
356
# Also start a new checker *right now*.
260
357
self.start_checker()
261
# Schedule a stop() when 'timeout' has passed
262
self.stop_initiator_tag = gobject.timeout_add\
263
(self._timeout_milliseconds,
267
The possibility that a client might be restarted is left open,
268
but not currently used."""
269
# If this client doesn't have a secret, it is already stopped.
270
if hasattr(self, "secret") and self.secret:
271
logger.info(u"Stopping client %s", self.name)
359
def disable(self, quiet=True):
360
"""Disable this client."""
361
if not getattr(self, "enabled", False):
275
if getattr(self, "stop_initiator_tag", False):
276
gobject.source_remove(self.stop_initiator_tag)
277
self.stop_initiator_tag = None
278
if getattr(self, "checker_initiator_tag", False):
364
self.send_changedstate()
366
logger.info(u"Disabling client %s", self.name)
367
if getattr(self, u"disable_initiator_tag", False):
368
gobject.source_remove(self.disable_initiator_tag)
369
self.disable_initiator_tag = None
370
if getattr(self, u"checker_initiator_tag", False):
279
371
gobject.source_remove(self.checker_initiator_tag)
280
372
self.checker_initiator_tag = None
281
373
self.stop_checker()
374
if self.disable_hook:
375
self.disable_hook(self)
284
377
# Do not run this again if called by a gobject.timeout_add
286
380
def __del__(self):
287
self.stop_hook = None
289
def checker_callback(self, pid, condition):
381
self.disable_hook = None
384
def checker_callback(self, pid, condition, command):
290
385
"""The checker has completed, so take appropriate actions."""
291
now = datetime.datetime.now()
292
386
self.checker_callback_tag = None
293
387
self.checker = None
294
if os.WIFEXITED(condition) \
295
and (os.WEXITSTATUS(condition) == 0):
296
logger.info(u"Checker for %(name)s succeeded",
298
self.last_checked_ok = now
299
gobject.source_remove(self.stop_initiator_tag)
300
self.stop_initiator_tag = gobject.timeout_add\
301
(self._timeout_milliseconds,
303
elif not os.WIFEXITED(condition):
388
if os.WIFEXITED(condition):
389
exitstatus = os.WEXITSTATUS(condition)
391
logger.info(u"Checker for %(name)s succeeded",
395
logger.info(u"Checker for %(name)s failed",
304
398
logger.warning(u"Checker for %(name)s crashed?",
307
logger.info(u"Checker for %(name)s failed",
401
def checked_ok(self):
402
"""Bump up the timeout for this client.
404
This should only be called when the client has been seen,
407
self.last_checked_ok = datetime.datetime.utcnow()
408
gobject.source_remove(self.disable_initiator_tag)
409
self.disable_initiator_tag = (gobject.timeout_add
410
(self.timeout_milliseconds(),
309
413
def start_checker(self):
310
414
"""Start a new checker subprocess if one is not running.
311
416
If a checker already exists, leave it running and do
313
418
# The reason for not killing a running checker is that if we
316
421
# client would inevitably timeout, since no checker would get
317
422
# a chance to run to completion. If we instead leave running
318
423
# checkers alone, the checker would have to take more time
319
# than 'timeout' for the client to be declared invalid, which
320
# is as it should be.
424
# than 'timeout' for the client to be disabled, which is as it
427
# If a checker exists, make sure it is not a zombie
429
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
430
except (AttributeError, OSError), error:
431
if (isinstance(error, OSError)
432
and error.errno != errno.ECHILD):
436
logger.warning(u"Checker was a zombie")
437
gobject.source_remove(self.checker_callback_tag)
438
self.checker_callback(pid, status,
439
self.current_checker_command)
440
# Start a new checker if needed
321
441
if self.checker is None:
323
# In case check_command has exactly one % operator
324
command = self.check_command % self.host
443
# In case checker_command has exactly one % operator
444
command = self.checker_command % self.host
325
445
except TypeError:
326
446
# Escape attributes for the shell
327
escaped_attrs = dict((key, re.escape(str(val)))
447
escaped_attrs = dict((key,
448
re.escape(unicode(str(val),
329
452
vars(self).iteritems())
331
command = self.check_command % escaped_attrs
454
command = self.checker_command % escaped_attrs
332
455
except TypeError, error:
333
456
logger.error(u'Could not format string "%s":'
334
u' %s', self.check_command, error)
457
u' %s', self.checker_command, error)
335
458
return True # Try again later
459
self.current_checker_command = command
337
461
logger.info(u"Starting checker %r for %s",
338
462
command, self.name)
463
# We don't need to redirect stdout and stderr, since
464
# in normal mode, that is already done by daemon(),
465
# and in debug mode we don't want to. (Stdin is
466
# always replaced by /dev/null.)
339
467
self.checker = subprocess.Popen(command,
342
self.checker_callback_tag = gobject.child_watch_add\
344
self.checker_callback)
345
except subprocess.OSError, error:
469
shell=True, cwd=u"/")
470
self.checker_callback_tag = (gobject.child_watch_add
472
self.checker_callback,
474
# The checker may have completed before the gobject
475
# watch was added. Check for this.
476
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
478
gobject.source_remove(self.checker_callback_tag)
479
self.checker_callback(pid, status, command)
480
except OSError, error:
346
481
logger.error(u"Failed to start subprocess: %s",
348
483
# Re-run this periodically if run by gobject.timeout_add
350
486
def stop_checker(self):
351
487
"""Force the checker process, if any, to stop."""
352
488
if self.checker_callback_tag:
353
489
gobject.source_remove(self.checker_callback_tag)
354
490
self.checker_callback_tag = None
355
if getattr(self, "checker", None) is None:
491
if getattr(self, u"checker", None) is None:
357
493
logger.debug(u"Stopping checker for %(name)s", vars(self))
359
495
os.kill(self.checker.pid, signal.SIGTERM)
361
497
#if self.checker.poll() is None:
362
498
# os.kill(self.checker.pid, signal.SIGKILL)
363
499
except OSError, error:
364
500
if error.errno != errno.ESRCH: # No such process
366
502
self.checker = None
367
def still_valid(self):
368
"""Has the timeout not yet passed for this client?"""
369
now = datetime.datetime.now()
504
def dbus_service_property(dbus_interface, signature=u"v",
505
access=u"readwrite", byte_arrays=False):
506
"""Decorators for marking methods of a DBusObjectWithProperties to
507
become properties on the D-Bus.
509
The decorated method will be called with no arguments by "Get"
510
and with one argument by "Set".
512
The parameters, where they are supported, are the same as
513
dbus.service.method, except there is only "signature", since the
514
type from Get() and the type sent to Set() is the same.
516
# Encoding deeply encoded byte arrays is not supported yet by the
517
# "Set" method, so we fail early here:
518
if byte_arrays and signature != u"ay":
519
raise ValueError(u"Byte arrays not supported for non-'ay'"
520
u" signature %r" % signature)
522
func._dbus_is_property = True
523
func._dbus_interface = dbus_interface
524
func._dbus_signature = signature
525
func._dbus_access = access
526
func._dbus_name = func.__name__
527
if func._dbus_name.endswith(u"_dbus_property"):
528
func._dbus_name = func._dbus_name[:-14]
529
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
534
class DBusPropertyException(dbus.exceptions.DBusException):
535
"""A base class for D-Bus property-related exceptions
537
def __unicode__(self):
538
return unicode(str(self))
541
class DBusPropertyAccessException(DBusPropertyException):
542
"""A property's access permissions disallows an operation.
547
class DBusPropertyNotFound(DBusPropertyException):
548
"""An attempt was made to access a non-existing property.
553
class DBusObjectWithProperties(dbus.service.Object):
554
"""A D-Bus object with properties.
556
Classes inheriting from this can use the dbus_service_property
557
decorator to expose methods as D-Bus properties. It exposes the
558
standard Get(), Set(), and GetAll() methods on the D-Bus.
562
def _is_dbus_property(obj):
563
return getattr(obj, u"_dbus_is_property", False)
565
def _get_all_dbus_properties(self):
566
"""Returns a generator of (name, attribute) pairs
568
return ((prop._dbus_name, prop)
570
inspect.getmembers(self, self._is_dbus_property))
572
def _get_dbus_property(self, interface_name, property_name):
573
"""Returns a bound method if one exists which is a D-Bus
574
property with the specified name and interface.
576
for name in (property_name,
577
property_name + u"_dbus_property"):
578
prop = getattr(self, name, None)
580
or not self._is_dbus_property(prop)
581
or prop._dbus_name != property_name
582
or (interface_name and prop._dbus_interface
583
and interface_name != prop._dbus_interface)):
587
raise DBusPropertyNotFound(self.dbus_object_path + u":"
588
+ interface_name + u"."
591
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
593
def Get(self, interface_name, property_name):
594
"""Standard D-Bus property Get() method, see D-Bus standard.
596
prop = self._get_dbus_property(interface_name, property_name)
597
if prop._dbus_access == u"write":
598
raise DBusPropertyAccessException(property_name)
600
if not hasattr(value, u"variant_level"):
602
return type(value)(value, variant_level=value.variant_level+1)
604
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
605
def Set(self, interface_name, property_name, value):
606
"""Standard D-Bus property Set() method, see D-Bus standard.
608
prop = self._get_dbus_property(interface_name, property_name)
609
if prop._dbus_access == u"read":
610
raise DBusPropertyAccessException(property_name)
611
if prop._dbus_get_args_options[u"byte_arrays"]:
612
# The byte_arrays option is not supported yet on
613
# signatures other than "ay".
614
if prop._dbus_signature != u"ay":
616
value = dbus.ByteArray(''.join(unichr(byte)
620
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
621
out_signature=u"a{sv}")
622
def GetAll(self, interface_name):
623
"""Standard D-Bus property GetAll() method, see D-Bus
626
Note: Will not include properties with access="write".
629
for name, prop in self._get_all_dbus_properties():
631
and interface_name != prop._dbus_interface):
632
# Interface non-empty but did not match
634
# Ignore write-only properties
635
if prop._dbus_access == u"write":
638
if not hasattr(value, u"variant_level"):
641
all[name] = type(value)(value, variant_level=
642
value.variant_level+1)
643
return dbus.Dictionary(all, signature=u"sv")
645
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
647
path_keyword='object_path',
648
connection_keyword='connection')
649
def Introspect(self, object_path, connection):
650
"""Standard D-Bus method, overloaded to insert property tags.
652
xmlstring = dbus.service.Object.Introspect(self, object_path,
655
document = xml.dom.minidom.parseString(xmlstring)
656
def make_tag(document, name, prop):
657
e = document.createElement(u"property")
658
e.setAttribute(u"name", name)
659
e.setAttribute(u"type", prop._dbus_signature)
660
e.setAttribute(u"access", prop._dbus_access)
662
for if_tag in document.getElementsByTagName(u"interface"):
663
for tag in (make_tag(document, name, prop)
665
in self._get_all_dbus_properties()
666
if prop._dbus_interface
667
== if_tag.getAttribute(u"name")):
668
if_tag.appendChild(tag)
669
# Add the names to the return values for the
670
# "org.freedesktop.DBus.Properties" methods
671
if (if_tag.getAttribute(u"name")
672
== u"org.freedesktop.DBus.Properties"):
673
for cn in if_tag.getElementsByTagName(u"method"):
674
if cn.getAttribute(u"name") == u"Get":
675
for arg in cn.getElementsByTagName(u"arg"):
676
if (arg.getAttribute(u"direction")
678
arg.setAttribute(u"name", u"value")
679
elif cn.getAttribute(u"name") == u"GetAll":
680
for arg in cn.getElementsByTagName(u"arg"):
681
if (arg.getAttribute(u"direction")
683
arg.setAttribute(u"name", u"props")
684
xmlstring = document.toxml(u"utf-8")
686
except (AttributeError, xml.dom.DOMException,
687
xml.parsers.expat.ExpatError), error:
688
logger.error(u"Failed to override Introspection method",
693
class ClientDBus(Client, DBusObjectWithProperties):
694
"""A Client class using D-Bus
697
dbus_object_path: dbus.ObjectPath
698
bus: dbus.SystemBus()
700
# dbus.service.Object doesn't use super(), so we can't either.
702
def __init__(self, bus = None, *args, **kwargs):
703
self._approvals_pending = 0
705
Client.__init__(self, *args, **kwargs)
706
# Only now, when this client is initialized, can it show up on
708
self.dbus_object_path = (dbus.ObjectPath
710
+ self.name.replace(u".", u"_")))
711
DBusObjectWithProperties.__init__(self, self.bus,
712
self.dbus_object_path)
714
def _get_approvals_pending(self):
715
return self._approvals_pending
716
def _set_approvals_pending(self, value):
717
old_value = self._approvals_pending
718
self._approvals_pending = value
720
if (hasattr(self, "dbus_object_path")
721
and bval is not bool(old_value)):
722
dbus_bool = dbus.Boolean(bval, variant_level=1)
723
self.PropertyChanged(dbus.String(u"approved_pending"),
726
approvals_pending = property(_get_approvals_pending,
727
_set_approvals_pending)
728
del _get_approvals_pending, _set_approvals_pending
731
def _datetime_to_dbus(dt, variant_level=0):
732
"""Convert a UTC datetime.datetime() to a D-Bus type."""
733
return dbus.String(dt.isoformat(),
734
variant_level=variant_level)
737
oldstate = getattr(self, u"enabled", False)
738
r = Client.enable(self)
739
if oldstate != self.enabled:
741
self.PropertyChanged(dbus.String(u"enabled"),
742
dbus.Boolean(True, variant_level=1))
743
self.PropertyChanged(
744
dbus.String(u"last_enabled"),
745
self._datetime_to_dbus(self.last_enabled,
749
def disable(self, quiet = False):
750
oldstate = getattr(self, u"enabled", False)
751
r = Client.disable(self, quiet=quiet)
752
if not quiet and oldstate != self.enabled:
754
self.PropertyChanged(dbus.String(u"enabled"),
755
dbus.Boolean(False, variant_level=1))
758
def __del__(self, *args, **kwargs):
760
self.remove_from_connection()
763
if hasattr(DBusObjectWithProperties, u"__del__"):
764
DBusObjectWithProperties.__del__(self, *args, **kwargs)
765
Client.__del__(self, *args, **kwargs)
767
def checker_callback(self, pid, condition, command,
769
self.checker_callback_tag = None
772
self.PropertyChanged(dbus.String(u"checker_running"),
773
dbus.Boolean(False, variant_level=1))
774
if os.WIFEXITED(condition):
775
exitstatus = os.WEXITSTATUS(condition)
777
self.CheckerCompleted(dbus.Int16(exitstatus),
778
dbus.Int64(condition),
779
dbus.String(command))
782
self.CheckerCompleted(dbus.Int16(-1),
783
dbus.Int64(condition),
784
dbus.String(command))
786
return Client.checker_callback(self, pid, condition, command,
789
def checked_ok(self, *args, **kwargs):
790
r = Client.checked_ok(self, *args, **kwargs)
792
self.PropertyChanged(
793
dbus.String(u"last_checked_ok"),
794
(self._datetime_to_dbus(self.last_checked_ok,
798
def start_checker(self, *args, **kwargs):
799
old_checker = self.checker
800
if self.checker is not None:
801
old_checker_pid = self.checker.pid
803
old_checker_pid = None
804
r = Client.start_checker(self, *args, **kwargs)
805
# Only if new checker process was started
806
if (self.checker is not None
807
and old_checker_pid != self.checker.pid):
809
self.CheckerStarted(self.current_checker_command)
810
self.PropertyChanged(
811
dbus.String(u"checker_running"),
812
dbus.Boolean(True, variant_level=1))
815
def stop_checker(self, *args, **kwargs):
816
old_checker = getattr(self, u"checker", None)
817
r = Client.stop_checker(self, *args, **kwargs)
818
if (old_checker is not None
819
and getattr(self, u"checker", None) is None):
820
self.PropertyChanged(dbus.String(u"checker_running"),
821
dbus.Boolean(False, variant_level=1))
824
def _reset_approved(self):
825
self._approved = None
828
def approve(self, value=True):
829
self.send_changedstate()
830
self._approved = value
831
gobject.timeout_add(self._timedelta_to_milliseconds(self.approved_duration),
832
self._reset_approved)
835
## D-Bus methods, signals & properties
836
_interface = u"se.bsnet.fukt.Mandos.Client"
840
# CheckerCompleted - signal
841
@dbus.service.signal(_interface, signature=u"nxs")
842
def CheckerCompleted(self, exitcode, waitstatus, command):
846
# CheckerStarted - signal
847
@dbus.service.signal(_interface, signature=u"s")
848
def CheckerStarted(self, command):
852
# PropertyChanged - signal
853
@dbus.service.signal(_interface, signature=u"sv")
854
def PropertyChanged(self, property, value):
859
@dbus.service.signal(_interface)
862
Is sent after a successful transfer of secret from the Mandos
863
server to mandos-client
868
@dbus.service.signal(_interface, signature=u"s")
869
def Rejected(self, reason):
873
# NeedApproval - signal
874
@dbus.service.signal(_interface, signature=u"db")
875
def NeedApproval(self, timeout, default):
882
@dbus.service.method(_interface, in_signature=u"b")
883
def Approve(self, value):
887
@dbus.service.method(_interface)
889
return self.checked_ok()
892
@dbus.service.method(_interface)
897
# StartChecker - method
898
@dbus.service.method(_interface)
899
def StartChecker(self):
904
@dbus.service.method(_interface)
909
# StopChecker - method
910
@dbus.service.method(_interface)
911
def StopChecker(self):
916
# approved_pending - property
917
@dbus_service_property(_interface, signature=u"b", access=u"read")
918
def approved_pending_dbus_property(self):
919
return dbus.Boolean(bool(self.approvals_pending))
921
# approved_by_default - property
922
@dbus_service_property(_interface, signature=u"b",
924
def approved_by_default_dbus_property(self):
925
return dbus.Boolean(self.approved_by_default)
927
# approved_delay - property
928
@dbus_service_property(_interface, signature=u"t",
930
def approved_delay_dbus_property(self):
931
return dbus.UInt64(self.approved_delay_milliseconds())
933
# approved_duration - property
934
@dbus_service_property(_interface, signature=u"t",
936
def approved_duration_dbus_property(self):
937
return dbus.UInt64(self._timedelta_to_milliseconds(
938
self.approved_duration))
941
@dbus_service_property(_interface, signature=u"s", access=u"read")
942
def name_dbus_property(self):
943
return dbus.String(self.name)
945
# fingerprint - property
946
@dbus_service_property(_interface, signature=u"s", access=u"read")
947
def fingerprint_dbus_property(self):
948
return dbus.String(self.fingerprint)
951
@dbus_service_property(_interface, signature=u"s",
953
def host_dbus_property(self, value=None):
954
if value is None: # get
955
return dbus.String(self.host)
958
self.PropertyChanged(dbus.String(u"host"),
959
dbus.String(value, variant_level=1))
962
@dbus_service_property(_interface, signature=u"s", access=u"read")
963
def created_dbus_property(self):
964
return dbus.String(self._datetime_to_dbus(self.created))
966
# last_enabled - property
967
@dbus_service_property(_interface, signature=u"s", access=u"read")
968
def last_enabled_dbus_property(self):
969
if self.last_enabled is None:
970
return dbus.String(u"")
971
return dbus.String(self._datetime_to_dbus(self.last_enabled))
974
@dbus_service_property(_interface, signature=u"b",
976
def enabled_dbus_property(self, value=None):
977
if value is None: # get
978
return dbus.Boolean(self.enabled)
984
# last_checked_ok - property
985
@dbus_service_property(_interface, signature=u"s",
987
def last_checked_ok_dbus_property(self, value=None):
988
if value is not None:
370
991
if self.last_checked_ok is None:
371
return now < (self.created + self.timeout)
373
return now < (self.last_checked_ok + self.timeout)
376
def peer_certificate(session):
377
"Return the peer's OpenPGP certificate as a bytestring"
378
# If not an OpenPGP certificate...
379
if gnutls.library.functions.gnutls_certificate_type_get\
380
(session._c_object) \
381
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
382
# ...do the normal thing
383
return session.peer_certificate
384
list_size = ctypes.c_uint()
385
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
386
(session._c_object, ctypes.byref(list_size))
387
if list_size.value == 0:
390
return ctypes.string_at(cert.data, cert.size)
393
def fingerprint(openpgp):
394
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
395
# New GnuTLS "datum" with the OpenPGP public key
396
datum = gnutls.library.types.gnutls_datum_t\
397
(ctypes.cast(ctypes.c_char_p(openpgp),
398
ctypes.POINTER(ctypes.c_ubyte)),
399
ctypes.c_uint(len(openpgp)))
400
# New empty GnuTLS certificate
401
crt = gnutls.library.types.gnutls_openpgp_crt_t()
402
gnutls.library.functions.gnutls_openpgp_crt_init\
404
# Import the OpenPGP public key into the certificate
405
gnutls.library.functions.gnutls_openpgp_crt_import\
406
(crt, ctypes.byref(datum),
407
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
408
# New buffer for the fingerprint
409
buffer = ctypes.create_string_buffer(20)
410
buffer_length = ctypes.c_size_t()
411
# Get the fingerprint from the certificate into the buffer
412
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
413
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
414
# Deinit the certificate
415
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
416
# Convert the buffer to a Python bytestring
417
fpr = ctypes.string_at(buffer, buffer_length.value)
418
# Convert the bytestring to hexadecimal notation
419
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
423
class tcp_handler(SocketServer.BaseRequestHandler, object):
424
"""A TCP request handler class.
425
Instantiated by IPv6_TCPServer for each request to handle it.
992
return dbus.String(u"")
993
return dbus.String(self._datetime_to_dbus(self
997
@dbus_service_property(_interface, signature=u"t",
999
def timeout_dbus_property(self, value=None):
1000
if value is None: # get
1001
return dbus.UInt64(self.timeout_milliseconds())
1002
self.timeout = datetime.timedelta(0, 0, 0, value)
1004
self.PropertyChanged(dbus.String(u"timeout"),
1005
dbus.UInt64(value, variant_level=1))
1006
if getattr(self, u"disable_initiator_tag", None) is None:
1008
# Reschedule timeout
1009
gobject.source_remove(self.disable_initiator_tag)
1010
self.disable_initiator_tag = None
1011
time_to_die = (self.
1012
_timedelta_to_milliseconds((self
1017
if time_to_die <= 0:
1018
# The timeout has passed
1021
self.disable_initiator_tag = (gobject.timeout_add
1022
(time_to_die, self.disable))
1024
# interval - property
1025
@dbus_service_property(_interface, signature=u"t",
1026
access=u"readwrite")
1027
def interval_dbus_property(self, value=None):
1028
if value is None: # get
1029
return dbus.UInt64(self.interval_milliseconds())
1030
self.interval = datetime.timedelta(0, 0, 0, value)
1032
self.PropertyChanged(dbus.String(u"interval"),
1033
dbus.UInt64(value, variant_level=1))
1034
if getattr(self, u"checker_initiator_tag", None) is None:
1036
# Reschedule checker run
1037
gobject.source_remove(self.checker_initiator_tag)
1038
self.checker_initiator_tag = (gobject.timeout_add
1039
(value, self.start_checker))
1040
self.start_checker() # Start one now, too
1042
# checker - property
1043
@dbus_service_property(_interface, signature=u"s",
1044
access=u"readwrite")
1045
def checker_dbus_property(self, value=None):
1046
if value is None: # get
1047
return dbus.String(self.checker_command)
1048
self.checker_command = value
1050
self.PropertyChanged(dbus.String(u"checker"),
1051
dbus.String(self.checker_command,
1054
# checker_running - property
1055
@dbus_service_property(_interface, signature=u"b",
1056
access=u"readwrite")
1057
def checker_running_dbus_property(self, value=None):
1058
if value is None: # get
1059
return dbus.Boolean(self.checker is not None)
1061
self.start_checker()
1065
# object_path - property
1066
@dbus_service_property(_interface, signature=u"o", access=u"read")
1067
def object_path_dbus_property(self):
1068
return self.dbus_object_path # is already a dbus.ObjectPath
1071
@dbus_service_property(_interface, signature=u"ay",
1072
access=u"write", byte_arrays=True)
1073
def secret_dbus_property(self, value):
1074
self.secret = str(value)
1079
class ProxyClient(object):
1080
def __init__(self, child_pipe, fpr, address):
1081
self._pipe = child_pipe
1082
self._pipe.send(('init', fpr, address))
1083
if not self._pipe.recv():
1086
def __getattribute__(self, name):
1087
if(name == '_pipe'):
1088
return super(ProxyClient, self).__getattribute__(name)
1089
self._pipe.send(('getattr', name))
1090
data = self._pipe.recv()
1091
if data[0] == 'data':
1093
if data[0] == 'function':
1094
def func(*args, **kwargs):
1095
self._pipe.send(('funcall', name, args, kwargs))
1096
return self._pipe.recv()[1]
1099
def __setattr__(self, name, value):
1100
if(name == '_pipe'):
1101
return super(ProxyClient, self).__setattr__(name, value)
1102
self._pipe.send(('setattr', name, value))
1105
class ClientHandler(socketserver.BaseRequestHandler, object):
1106
"""A class to handle client connections.
1108
Instantiated once for each connection to handle it.
426
1109
Note: This will run in its own forked process."""
428
1111
def handle(self):
429
logger.info(u"TCP connection from: %s",
430
unicode(self.client_address))
431
session = gnutls.connection.ClientSession\
432
(self.request, gnutls.connection.X509Credentials())
434
line = self.request.makefile().readline()
435
logger.debug(u"Protocol version: %r", line)
437
if int(line.strip().split()[0]) > 1:
439
except (ValueError, IndexError, RuntimeError), error:
440
logger.error(u"Unknown protocol version: %s", error)
443
# Note: gnutls.connection.X509Credentials is really a generic
444
# GnuTLS certificate credentials object so long as no X.509
445
# keys are added to it. Therefore, we can use it here despite
446
# using OpenPGP certificates.
448
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
449
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
451
priority = "NORMAL" # Fallback default, since this
453
if self.server.settings["priority"]:
454
priority = self.server.settings["priority"]
455
gnutls.library.functions.gnutls_priority_set_direct\
456
(session._c_object, priority, None);
460
except gnutls.errors.GNUTLSError, error:
461
logger.warning(u"Handshake failed: %s", error)
462
# Do not run session.bye() here: the session is not
463
# established. Just abandon the request.
466
fpr = fingerprint(peer_certificate(session))
467
except (TypeError, gnutls.errors.GNUTLSError), error:
468
logger.warning(u"Bad certificate: %s", error)
471
logger.debug(u"Fingerprint: %s", fpr)
473
for c in self.server.clients:
474
if c.fingerprint == fpr:
478
logger.warning(u"Client not found for fingerprint: %s",
482
# Have to check if client.still_valid(), since it is possible
483
# that the client timed out while establishing the GnuTLS
485
if not client.still_valid():
486
logger.warning(u"Client %(name)s is invalid",
491
while sent_size < len(client.secret):
492
sent = session.send(client.secret[sent_size:])
493
logger.debug(u"Sent: %d, remaining: %d",
494
sent, len(client.secret)
495
- (sent_size + sent))
500
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
501
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1112
with contextlib.closing(self.server.child_pipe) as child_pipe:
1113
logger.info(u"TCP connection from: %s",
1114
unicode(self.client_address))
1115
logger.debug(u"Pipe FD: %d",
1116
self.server.child_pipe.fileno())
1118
session = (gnutls.connection
1119
.ClientSession(self.request,
1121
.X509Credentials()))
1123
# Note: gnutls.connection.X509Credentials is really a
1124
# generic GnuTLS certificate credentials object so long as
1125
# no X.509 keys are added to it. Therefore, we can use it
1126
# here despite using OpenPGP certificates.
1128
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1129
# u"+AES-256-CBC", u"+SHA1",
1130
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1132
# Use a fallback default, since this MUST be set.
1133
priority = self.server.gnutls_priority
1134
if priority is None:
1135
priority = u"NORMAL"
1136
(gnutls.library.functions
1137
.gnutls_priority_set_direct(session._c_object,
1140
# Start communication using the Mandos protocol
1141
# Get protocol number
1142
line = self.request.makefile().readline()
1143
logger.debug(u"Protocol version: %r", line)
1145
if int(line.strip().split()[0]) > 1:
1147
except (ValueError, IndexError, RuntimeError), error:
1148
logger.error(u"Unknown protocol version: %s", error)
1151
# Start GnuTLS connection
1154
except gnutls.errors.GNUTLSError, error:
1155
logger.warning(u"Handshake failed: %s", error)
1156
# Do not run session.bye() here: the session is not
1157
# established. Just abandon the request.
1159
logger.debug(u"Handshake succeeded")
1161
approval_required = False
1164
fpr = self.fingerprint(self.peer_certificate
1166
except (TypeError, gnutls.errors.GNUTLSError), error:
1167
logger.warning(u"Bad certificate: %s", error)
1169
logger.debug(u"Fingerprint: %s", fpr)
1172
client = ProxyClient(child_pipe, fpr,
1173
self.client_address)
1177
if client.approved_delay:
1178
delay = client.approved_delay
1179
client.approvals_pending += 1
1180
approval_required = True
1183
if not client.enabled:
1184
logger.warning(u"Client %s is disabled",
1186
if self.server.use_dbus:
1188
client.Rejected("Disabled")
1191
if client._approved or not client.approved_delay:
1192
#We are approved or approval is disabled
1194
elif client._approved is None:
1195
logger.info(u"Client %s need approval",
1197
if self.server.use_dbus:
1199
client.NeedApproval(
1200
client.approved_delay_milliseconds(),
1201
client.approved_by_default)
1203
logger.warning(u"Client %s was not approved",
1205
if self.server.use_dbus:
1207
client.Rejected("Disapproved")
1210
#wait until timeout or approved
1211
#x = float(client._timedelta_to_milliseconds(delay))
1212
time = datetime.datetime.now()
1213
client.changedstate.acquire()
1214
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1215
client.changedstate.release()
1216
time2 = datetime.datetime.now()
1217
if (time2 - time) >= delay:
1218
if not client.approved_by_default:
1219
logger.warning("Client %s timed out while"
1220
" waiting for approval",
1222
if self.server.use_dbus:
1224
client.Rejected("Time out")
1229
delay -= time2 - time
1232
while sent_size < len(client.secret):
1234
sent = session.send(client.secret[sent_size:])
1235
except (gnutls.errors.GNUTLSError), error:
1236
logger.warning("gnutls send failed")
1238
logger.debug(u"Sent: %d, remaining: %d",
1239
sent, len(client.secret)
1240
- (sent_size + sent))
1243
logger.info(u"Sending secret to %s", client.name)
1244
# bump the timeout as if seen
1246
if self.server.use_dbus:
1251
if approval_required:
1252
client.approvals_pending -= 1
1255
except (gnutls.errors.GNUTLSError), error:
1256
logger.warning("gnutls bye failed")
1259
def peer_certificate(session):
1260
"Return the peer's OpenPGP certificate as a bytestring"
1261
# If not an OpenPGP certificate...
1262
if (gnutls.library.functions
1263
.gnutls_certificate_type_get(session._c_object)
1264
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1265
# ...do the normal thing
1266
return session.peer_certificate
1267
list_size = ctypes.c_uint(1)
1268
cert_list = (gnutls.library.functions
1269
.gnutls_certificate_get_peers
1270
(session._c_object, ctypes.byref(list_size)))
1271
if not bool(cert_list) and list_size.value != 0:
1272
raise gnutls.errors.GNUTLSError(u"error getting peer"
1274
if list_size.value == 0:
1277
return ctypes.string_at(cert.data, cert.size)
1280
def fingerprint(openpgp):
1281
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1282
# New GnuTLS "datum" with the OpenPGP public key
1283
datum = (gnutls.library.types
1284
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1287
ctypes.c_uint(len(openpgp))))
1288
# New empty GnuTLS certificate
1289
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1290
(gnutls.library.functions
1291
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1292
# Import the OpenPGP public key into the certificate
1293
(gnutls.library.functions
1294
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1295
gnutls.library.constants
1296
.GNUTLS_OPENPGP_FMT_RAW))
1297
# Verify the self signature in the key
1298
crtverify = ctypes.c_uint()
1299
(gnutls.library.functions
1300
.gnutls_openpgp_crt_verify_self(crt, 0,
1301
ctypes.byref(crtverify)))
1302
if crtverify.value != 0:
1303
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1304
raise (gnutls.errors.CertificateSecurityError
1306
# New buffer for the fingerprint
1307
buf = ctypes.create_string_buffer(20)
1308
buf_len = ctypes.c_size_t()
1309
# Get the fingerprint from the certificate into the buffer
1310
(gnutls.library.functions
1311
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1312
ctypes.byref(buf_len)))
1313
# Deinit the certificate
1314
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1315
# Convert the buffer to a Python bytestring
1316
fpr = ctypes.string_at(buf, buf_len.value)
1317
# Convert the bytestring to hexadecimal notation
1318
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1322
class MultiprocessingMixIn(object):
1323
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1324
def sub_process_main(self, request, address):
1326
self.finish_request(request, address)
1328
self.handle_error(request, address)
1329
self.close_request(request)
1331
def process_request(self, request, address):
1332
"""Start a new process to process the request."""
1333
multiprocessing.Process(target = self.sub_process_main,
1334
args = (request, address)).start()
1336
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1337
""" adds a pipe to the MixIn """
1338
def process_request(self, request, client_address):
1339
"""Overrides and wraps the original process_request().
1341
This function creates a new pipe in self.pipe
1343
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1345
super(MultiprocessingMixInWithPipe,
1346
self).process_request(request, client_address)
1347
self.child_pipe.close()
1348
self.add_pipe(parent_pipe)
1350
def add_pipe(self, parent_pipe):
1351
"""Dummy function; override as necessary"""
1354
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1355
socketserver.TCPServer, object):
1356
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
503
settings: Server settings
504
clients: Set() of Client objects
1359
enabled: Boolean; whether this server is activated yet
1360
interface: None or a network interface name (string)
1361
use_ipv6: Boolean; to use IPv6 or not
506
address_family = socket.AF_INET6
507
def __init__(self, *args, **kwargs):
508
if "settings" in kwargs:
509
self.settings = kwargs["settings"]
510
del kwargs["settings"]
511
if "clients" in kwargs:
512
self.clients = kwargs["clients"]
513
del kwargs["clients"]
514
return super(type(self), self).__init__(*args, **kwargs)
1363
def __init__(self, server_address, RequestHandlerClass,
1364
interface=None, use_ipv6=True):
1365
self.interface = interface
1367
self.address_family = socket.AF_INET6
1368
socketserver.TCPServer.__init__(self, server_address,
1369
RequestHandlerClass)
515
1370
def server_bind(self):
516
1371
"""This overrides the normal server_bind() function
517
1372
to bind to an interface if one was specified, and also NOT to
518
1373
bind to an address or port if they were not specified."""
519
if self.settings["interface"]:
520
# 25 is from /usr/include/asm-i486/socket.h
521
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
523
self.socket.setsockopt(socket.SOL_SOCKET,
525
self.settings["interface"])
526
except socket.error, error:
527
if error[0] == errno.EPERM:
528
logger.error(u"No permission to"
529
u" bind to interface %s",
530
self.settings["interface"])
1374
if self.interface is not None:
1375
if SO_BINDTODEVICE is None:
1376
logger.error(u"SO_BINDTODEVICE does not exist;"
1377
u" cannot bind to interface %s",
1381
self.socket.setsockopt(socket.SOL_SOCKET,
1385
except socket.error, error:
1386
if error[0] == errno.EPERM:
1387
logger.error(u"No permission to"
1388
u" bind to interface %s",
1390
elif error[0] == errno.ENOPROTOOPT:
1391
logger.error(u"SO_BINDTODEVICE not available;"
1392
u" cannot bind to interface %s",
533
1396
# Only bind(2) the socket if we really need to.
534
1397
if self.server_address[0] or self.server_address[1]:
535
1398
if not self.server_address[0]:
537
self.server_address = (in6addr_any,
1399
if self.address_family == socket.AF_INET6:
1400
any_address = u"::" # in6addr_any
1402
any_address = socket.INADDR_ANY
1403
self.server_address = (any_address,
538
1404
self.server_address[1])
539
1405
elif not self.server_address[1]:
540
1406
self.server_address = (self.server_address[0],
542
# if self.settings["interface"]:
1408
# if self.interface:
543
1409
# self.server_address = (self.server_address[0],
546
1412
# if_nametoindex
549
return super(type(self), self).server_bind()
1414
return socketserver.TCPServer.server_bind(self)
1417
class MandosServer(IPv6_TCPServer):
1421
clients: set of Client objects
1422
gnutls_priority GnuTLS priority string
1423
use_dbus: Boolean; to emit D-Bus signals or not
1425
Assumes a gobject.MainLoop event loop.
1427
def __init__(self, server_address, RequestHandlerClass,
1428
interface=None, use_ipv6=True, clients=None,
1429
gnutls_priority=None, use_dbus=True):
1430
self.enabled = False
1431
self.clients = clients
1432
if self.clients is None:
1433
self.clients = set()
1434
self.use_dbus = use_dbus
1435
self.gnutls_priority = gnutls_priority
1436
IPv6_TCPServer.__init__(self, server_address,
1437
RequestHandlerClass,
1438
interface = interface,
1439
use_ipv6 = use_ipv6)
1440
def server_activate(self):
1442
return socketserver.TCPServer.server_activate(self)
1445
def add_pipe(self, parent_pipe):
1446
# Call "handle_ipc" for both data and EOF events
1447
gobject.io_add_watch(parent_pipe.fileno(),
1448
gobject.IO_IN | gobject.IO_HUP,
1449
functools.partial(self.handle_ipc,
1450
parent_pipe = parent_pipe))
1452
def handle_ipc(self, source, condition, parent_pipe=None,
1453
client_object=None):
1455
gobject.IO_IN: u"IN", # There is data to read.
1456
gobject.IO_OUT: u"OUT", # Data can be written (without
1458
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1459
gobject.IO_ERR: u"ERR", # Error condition.
1460
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1461
# broken, usually for pipes and
1464
conditions_string = ' | '.join(name
1466
condition_names.iteritems()
1467
if cond & condition)
1468
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1471
# error or the other end of multiprocessing.Pipe has closed
1472
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1475
# Read a request from the child
1476
request = parent_pipe.recv()
1477
logger.debug(u"IPC request: %s", repr(request))
1478
command = request[0]
1480
if command == 'init':
1482
address = request[2]
1484
for c in self.clients:
1485
if c.fingerprint == fpr:
1489
logger.warning(u"Client not found for fingerprint: %s, ad"
1490
u"dress: %s", fpr, address)
1493
mandos_dbus_service.ClientNotFound(fpr, address)
1494
parent_pipe.send(False)
1497
gobject.io_add_watch(parent_pipe.fileno(),
1498
gobject.IO_IN | gobject.IO_HUP,
1499
functools.partial(self.handle_ipc,
1500
parent_pipe = parent_pipe,
1501
client_object = client))
1502
parent_pipe.send(True)
1503
# remove the old hook in favor of the new above hook on same fileno
1505
if command == 'funcall':
1506
funcname = request[1]
1510
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1512
if command == 'getattr':
1513
attrname = request[1]
1514
if callable(client_object.__getattribute__(attrname)):
1515
parent_pipe.send(('function',))
1517
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1519
if command == 'setattr':
1520
attrname = request[1]
1522
setattr(client_object, attrname, value)
552
1527
def string_to_delta(interval):
553
1528
"""Parse a string and return a datetime.timedelta
555
>>> string_to_delta('7d')
1530
>>> string_to_delta(u'7d')
556
1531
datetime.timedelta(7)
557
>>> string_to_delta('60s')
1532
>>> string_to_delta(u'60s')
558
1533
datetime.timedelta(0, 60)
559
>>> string_to_delta('60m')
1534
>>> string_to_delta(u'60m')
560
1535
datetime.timedelta(0, 3600)
561
>>> string_to_delta('24h')
1536
>>> string_to_delta(u'24h')
562
1537
datetime.timedelta(1)
563
1538
>>> string_to_delta(u'1w')
564
1539
datetime.timedelta(7)
1540
>>> string_to_delta(u'5m 30s')
1541
datetime.timedelta(0, 330)
567
suffix=unicode(interval[-1])
568
value=int(interval[:-1])
570
delta = datetime.timedelta(value)
572
delta = datetime.timedelta(0, value)
574
delta = datetime.timedelta(0, 0, 0, 0, value)
576
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
578
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
581
except (ValueError, IndexError):
586
def server_state_changed(state):
587
"""Derived from the Avahi example code"""
588
if state == avahi.SERVER_COLLISION:
589
logger.error(u"Server name collision")
591
elif state == avahi.SERVER_RUNNING:
595
def entry_group_state_changed(state, error):
596
"""Derived from the Avahi example code"""
597
logger.debug(u"state change: %i", state)
599
if state == avahi.ENTRY_GROUP_ESTABLISHED:
600
logger.debug(u"Service established.")
601
elif state == avahi.ENTRY_GROUP_COLLISION:
602
logger.warning(u"Service name collision.")
604
elif state == avahi.ENTRY_GROUP_FAILURE:
605
logger.critical(u"Error in group state changed %s",
607
raise AvahiGroupError("State changed: %s", str(error))
1543
timevalue = datetime.timedelta(0)
1544
for s in interval.split():
1546
suffix = unicode(s[-1])
1549
delta = datetime.timedelta(value)
1550
elif suffix == u"s":
1551
delta = datetime.timedelta(0, value)
1552
elif suffix == u"m":
1553
delta = datetime.timedelta(0, 0, 0, 0, value)
1554
elif suffix == u"h":
1555
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1556
elif suffix == u"w":
1557
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1559
raise ValueError(u"Unknown suffix %r" % suffix)
1560
except (ValueError, IndexError), e:
1561
raise ValueError(e.message)
609
1566
def if_nametoindex(interface):
610
"""Call the C function if_nametoindex(), or equivalent"""
1567
"""Call the C function if_nametoindex(), or equivalent
1569
Note: This function cannot accept a unicode string."""
611
1570
global if_nametoindex
613
if "ctypes.util" not in sys.modules:
615
if_nametoindex = ctypes.cdll.LoadLibrary\
616
(ctypes.util.find_library("c")).if_nametoindex
1572
if_nametoindex = (ctypes.cdll.LoadLibrary
1573
(ctypes.util.find_library(u"c"))
617
1575
except (OSError, AttributeError):
618
if "struct" not in sys.modules:
620
if "fcntl" not in sys.modules:
1576
logger.warning(u"Doing if_nametoindex the hard way")
622
1577
def if_nametoindex(interface):
623
1578
"Get an interface index the hard way, i.e. using fcntl()"
624
1579
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
626
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
627
struct.pack("16s16x", interface))
629
interface_index = struct.unpack("I", ifreq[16:20])[0]
1580
with contextlib.closing(socket.socket()) as s:
1581
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1582
struct.pack(str(u"16s16x"),
1584
interface_index = struct.unpack(str(u"I"),
630
1586
return interface_index
631
1587
return if_nametoindex(interface)
634
1590
def daemon(nochdir = False, noclose = False):
635
1591
"""See daemon(3). Standard BSD Unix function.
636
1593
This should really exist as os.daemon, but it doesn't (yet)."""
688
1654
# Default values for config file for server-global settings
689
server_defaults = { "interface": "",
694
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
695
"servicename": "Mandos",
1655
server_defaults = { u"interface": u"",
1660
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1661
u"servicename": u"Mandos",
1662
u"use_dbus": u"True",
1663
u"use_ipv6": u"True",
698
1667
# Parse config file for server-global settings
699
server_config = ConfigParser.SafeConfigParser(server_defaults)
1668
server_config = configparser.SafeConfigParser(server_defaults)
700
1669
del server_defaults
701
server_config.read(os.path.join(options.configdir, "mandos.conf"))
702
server_section = "server"
1670
server_config.read(os.path.join(options.configdir,
703
1672
# Convert the SafeConfigParser object to a dict
704
server_settings = dict(server_config.items(server_section))
705
# Use getboolean on the boolean config option
706
server_settings["debug"] = server_config.getboolean\
707
(server_section, "debug")
1673
server_settings = server_config.defaults()
1674
# Use the appropriate methods on the non-string config options
1675
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1676
server_settings[option] = server_config.getboolean(u"DEFAULT",
1678
if server_settings["port"]:
1679
server_settings["port"] = server_config.getint(u"DEFAULT",
708
1681
del server_config
710
1683
# Override the settings from the config file with command line
711
1684
# options, if set.
712
for option in ("interface", "address", "port", "debug",
713
"priority", "servicename", "configdir"):
1685
for option in (u"interface", u"address", u"port", u"debug",
1686
u"priority", u"servicename", u"configdir",
1687
u"use_dbus", u"use_ipv6", u"debuglevel"):
714
1688
value = getattr(options, option)
715
1689
if value is not None:
716
1690
server_settings[option] = value
1692
# Force all strings to be unicode
1693
for option in server_settings.keys():
1694
if type(server_settings[option]) is str:
1695
server_settings[option] = unicode(server_settings[option])
718
1696
# Now we have our good server settings in "server_settings"
720
debug = server_settings["debug"]
1698
##################################################################
1701
debug = server_settings[u"debug"]
1702
debuglevel = server_settings[u"debuglevel"]
1703
use_dbus = server_settings[u"use_dbus"]
1704
use_ipv6 = server_settings[u"use_ipv6"]
1706
if server_settings[u"servicename"] != u"Mandos":
1707
syslogger.setFormatter(logging.Formatter
1708
(u'Mandos (%s) [%%(process)d]:'
1709
u' %%(levelname)s: %%(message)s'
1710
% server_settings[u"servicename"]))
1712
# Parse config file with clients
1713
client_defaults = { u"timeout": u"1h",
1715
u"checker": u"fping -q -- %%(host)s",
1717
u"approved_delay": u"0s",
1718
u"approved_duration": u"1s",
1720
client_config = configparser.SafeConfigParser(client_defaults)
1721
client_config.read(os.path.join(server_settings[u"configdir"],
1724
global mandos_dbus_service
1725
mandos_dbus_service = None
1727
tcp_server = MandosServer((server_settings[u"address"],
1728
server_settings[u"port"]),
1730
interface=server_settings[u"interface"],
1733
server_settings[u"priority"],
1735
pidfilename = u"/var/run/mandos.pid"
1737
pidfile = open(pidfilename, u"w")
1739
logger.error(u"Could not open file %r", pidfilename)
1742
uid = pwd.getpwnam(u"_mandos").pw_uid
1743
gid = pwd.getpwnam(u"_mandos").pw_gid
1746
uid = pwd.getpwnam(u"mandos").pw_uid
1747
gid = pwd.getpwnam(u"mandos").pw_gid
1750
uid = pwd.getpwnam(u"nobody").pw_uid
1751
gid = pwd.getpwnam(u"nobody").pw_gid
1758
except OSError, error:
1759
if error[0] != errno.EPERM:
1762
# Enable all possible GnuTLS debugging
1765
if not debug and not debuglevel:
723
1766
syslogger.setLevel(logging.WARNING)
724
1767
console.setLevel(logging.WARNING)
726
if server_settings["servicename"] != "Mandos":
727
syslogger.setFormatter(logging.Formatter\
728
('Mandos (%s): %%(levelname)s:'
730
% server_settings["servicename"]))
732
# Parse config file with clients
733
client_defaults = { "timeout": "1h",
735
"checker": "fping -q -- %%(host)s",
737
client_config = ConfigParser.SafeConfigParser(client_defaults)
738
client_config.read(os.path.join(server_settings["configdir"],
742
service = AvahiService(name = server_settings["servicename"],
743
type = "_mandos._tcp", );
744
if server_settings["interface"]:
745
service.interface = if_nametoindex(server_settings["interface"])
1769
level = getattr(logging, debuglevel.upper())
1770
syslogger.setLevel(level)
1771
console.setLevel(level)
1774
# "Use a log level over 10 to enable all debugging options."
1776
gnutls.library.functions.gnutls_global_set_log_level(11)
1778
@gnutls.library.types.gnutls_log_func
1779
def debug_gnutls(level, string):
1780
logger.debug(u"GnuTLS: %s", string[:-1])
1782
(gnutls.library.functions
1783
.gnutls_global_set_log_function(debug_gnutls))
1785
# Redirect stdin so all checkers get /dev/null
1786
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1787
os.dup2(null, sys.stdin.fileno())
1791
# No console logging
1792
logger.removeHandler(console)
747
1795
global main_loop
750
1796
# From the Avahi example code
751
1797
DBusGMainLoop(set_as_default=True )
752
1798
main_loop = gobject.MainLoop()
753
1799
bus = dbus.SystemBus()
754
server = dbus.Interface(
755
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
756
avahi.DBUS_INTERFACE_SERVER )
757
1800
# End of Avahi example code
760
def remove_from_clients(client):
761
clients.remove(client)
763
logger.critical(u"No clients left, exiting")
766
clients.update(Set(Client(name = section,
767
stop_hook = remove_from_clients,
769
= dict(client_config.items(section)))
770
for section in client_config.sections()))
772
logger.critical(u"No clients defined")
1803
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1804
bus, do_not_queue=True)
1805
except dbus.exceptions.NameExistsException, e:
1806
logger.error(unicode(e) + u", disabling D-Bus")
1808
server_settings[u"use_dbus"] = False
1809
tcp_server.use_dbus = False
1810
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1811
service = AvahiService(name = server_settings[u"servicename"],
1812
servicetype = u"_mandos._tcp",
1813
protocol = protocol, bus = bus)
1814
if server_settings["interface"]:
1815
service.interface = (if_nametoindex
1816
(str(server_settings[u"interface"])))
776
logger.removeHandler(console)
1819
# Close all input and output, do double fork, etc.
779
pidfilename = "/var/run/mandos/mandos.pid"
1822
global multiprocessing_manager
1823
multiprocessing_manager = multiprocessing.Manager()
1825
client_class = Client
1827
client_class = functools.partial(ClientDBus, bus = bus)
1828
def client_config_items(config, section):
1829
special_settings = {
1830
"approved_by_default":
1831
lambda: config.getboolean(section,
1832
"approved_by_default"),
1834
for name, value in config.items(section):
1836
yield (name, special_settings[name]())
1840
tcp_server.clients.update(set(
1841
client_class(name = section,
1842
config= dict(client_config_items(
1843
client_config, section)))
1844
for section in client_config.sections()))
1845
if not tcp_server.clients:
1846
logger.warning(u"No clients defined")
782
pidfile = open(pidfilename, "w")
783
pidfile.write(str(pid) + "\n")
1851
pidfile.write(str(pid) + "\n")
787
logger.error(u"Could not write %s file with PID %d",
788
pidfilename, os.getpid())
1854
logger.error(u"Could not write to file %r with PID %d",
1857
# "pidfile" was never created
1862
signal.signal(signal.SIGINT, signal.SIG_IGN)
1863
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1864
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1867
class MandosDBusService(dbus.service.Object):
1868
"""A D-Bus proxy object"""
1870
dbus.service.Object.__init__(self, bus, u"/")
1871
_interface = u"se.bsnet.fukt.Mandos"
1873
@dbus.service.signal(_interface, signature=u"o")
1874
def ClientAdded(self, objpath):
1878
@dbus.service.signal(_interface, signature=u"ss")
1879
def ClientNotFound(self, fingerprint, address):
1883
@dbus.service.signal(_interface, signature=u"os")
1884
def ClientRemoved(self, objpath, name):
1888
@dbus.service.method(_interface, out_signature=u"ao")
1889
def GetAllClients(self):
1891
return dbus.Array(c.dbus_object_path
1892
for c in tcp_server.clients)
1894
@dbus.service.method(_interface,
1895
out_signature=u"a{oa{sv}}")
1896
def GetAllClientsWithProperties(self):
1898
return dbus.Dictionary(
1899
((c.dbus_object_path, c.GetAll(u""))
1900
for c in tcp_server.clients),
1901
signature=u"oa{sv}")
1903
@dbus.service.method(_interface, in_signature=u"o")
1904
def RemoveClient(self, object_path):
1906
for c in tcp_server.clients:
1907
if c.dbus_object_path == object_path:
1908
tcp_server.clients.remove(c)
1909
c.remove_from_connection()
1910
# Don't signal anything except ClientRemoved
1911
c.disable(quiet=True)
1913
self.ClientRemoved(object_path, c.name)
1915
raise KeyError(object_path)
1919
mandos_dbus_service = MandosDBusService()
791
1922
"Cleanup function; run on exit"
793
# From the Avahi example code
794
if not group is None:
797
# End of Avahi example code
800
client = clients.pop()
801
client.stop_hook = None
1925
while tcp_server.clients:
1926
client = tcp_server.clients.pop()
1928
client.remove_from_connection()
1929
client.disable_hook = None
1930
# Don't signal anything except ClientRemoved
1931
client.disable(quiet=True)
1934
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
804
1937
atexit.register(cleanup)
807
signal.signal(signal.SIGINT, signal.SIG_IGN)
808
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
809
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
811
for client in clients:
814
tcp_server = IPv6_TCPServer((server_settings["address"],
815
server_settings["port"]),
817
settings=server_settings,
1939
for client in tcp_server.clients:
1942
mandos_dbus_service.ClientAdded(client.dbus_object_path)
1946
tcp_server.server_activate()
819
1948
# Find out what port we got
820
1949
service.port = tcp_server.socket.getsockname()[1]
821
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
822
u" scope_id %d" % tcp_server.socket.getsockname())
1951
logger.info(u"Now listening on address %r, port %d,"
1952
" flowinfo %d, scope_id %d"
1953
% tcp_server.socket.getsockname())
1955
logger.info(u"Now listening on address %r, port %d"
1956
% tcp_server.socket.getsockname())
824
1958
#service.interface = tcp_server.socket.getsockname()[3]
827
1961
# From the Avahi example code
828
server.connect_to_signal("StateChanged", server_state_changed)
830
server_state_changed(server.GetState())
831
1964
except dbus.exceptions.DBusException, error:
832
1965
logger.critical(u"DBusException: %s", error)
834
1968
# End of Avahi example code
836
1970
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
837
1971
lambda *args, **kwargs:
838
tcp_server.handle_request\
839
(*args[2:], **kwargs) or True)
1972
(tcp_server.handle_request
1973
(*args[2:], **kwargs) or True))
841
1975
logger.debug(u"Starting main loop")
842
main_loop_started = True
844
1977
except AvahiError, error:
845
logger.critical(u"AvahiError: %s" + unicode(error))
1978
logger.critical(u"AvahiError: %s", error)
847
1981
except KeyboardInterrupt:
1984
logger.debug(u"Server received KeyboardInterrupt")
1985
logger.debug(u"Server exiting")
1986
# Must run before the D-Bus bus name gets deregistered
851
1989
if __name__ == '__main__':