129
104
max_renames: integer; maximum number of renames
130
105
rename_count: integer; counter so we only rename after collisions
131
106
a sensible number of times
132
group: D-Bus Entry Group
134
bus: dbus.SystemBus()
136
108
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
137
servicetype = None, port = None, TXT = None,
138
domain = u"", host = u"", max_renames = 32768,
139
protocol = avahi.PROTO_UNSPEC, bus = None):
109
type = None, port = None, TXT = None, domain = "",
110
host = "", max_renames = 32768):
140
111
self.interface = interface
142
self.type = servicetype
144
self.TXT = TXT if TXT is not None else []
145
119
self.domain = domain
147
121
self.rename_count = 0
148
122
self.max_renames = max_renames
149
self.protocol = protocol
150
self.group = None # our entry group
153
123
def rename(self):
154
124
"""Derived from the Avahi example code"""
155
125
if self.rename_count >= self.max_renames:
156
logger.critical(u"No suitable Zeroconf service name found"
157
u" after %i retries, exiting.",
159
raise AvahiServiceError(u"Too many renames")
160
self.name = unicode(self.server.GetAlternativeServiceName(self.name))
161
logger.info(u"Changing Zeroconf service name to %r ...",
163
syslogger.setFormatter(logging.Formatter
164
(u'Mandos (%s) [%%(process)d]:'
165
u' %%(levelname)s: %%(message)s'
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))
170
except dbus.exceptions.DBusException, error:
171
logger.critical(u"DBusException: %s", error)
174
136
self.rename_count += 1
175
137
def remove(self):
176
138
"""Derived from the Avahi example code"""
177
if self.group is not None:
139
if group is not None:
180
142
"""Derived from the Avahi example code"""
181
if self.group is None:
182
self.group = dbus.Interface(
183
self.bus.get_object(avahi.DBUS_NAME,
184
self.server.EntryGroupNew()),
185
avahi.DBUS_INTERFACE_ENTRY_GROUP)
186
self.group.connect_to_signal('StateChanged',
188
.entry_group_state_changed)
189
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
190
self.name, self.type)
191
self.group.AddService(
194
dbus.UInt32(0), # flags
195
self.name, self.type,
196
self.domain, self.host,
197
dbus.UInt16(self.port),
198
avahi.string_array_to_txt_array(self.TXT))
200
def entry_group_state_changed(self, state, error):
201
"""Derived from the Avahi example code"""
202
logger.debug(u"Avahi entry group state change: %i", state)
204
if state == avahi.ENTRY_GROUP_ESTABLISHED:
205
logger.debug(u"Zeroconf service established.")
206
elif state == avahi.ENTRY_GROUP_COLLISION:
207
logger.warning(u"Zeroconf service name collision.")
209
elif state == avahi.ENTRY_GROUP_FAILURE:
210
logger.critical(u"Avahi: Error in group state changed %s",
212
raise AvahiGroupError(u"State changed: %s"
215
"""Derived from the Avahi example code"""
216
if self.group is not None:
219
def server_state_changed(self, state):
220
"""Derived from the Avahi example code"""
221
logger.debug(u"Avahi server state change: %i", state)
222
if state == avahi.SERVER_COLLISION:
223
logger.error(u"Zeroconf server name collision")
225
elif state == avahi.SERVER_RUNNING:
228
"""Derived from the Avahi example code"""
229
if self.server is None:
230
self.server = dbus.Interface(
231
self.bus.get_object(avahi.DBUS_NAME,
232
avahi.DBUS_PATH_SERVER),
233
avahi.DBUS_INTERFACE_SERVER)
234
self.server.connect_to_signal(u"StateChanged",
235
self.server_state_changed)
236
self.server_state_changed(self.server.GetState())
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
239
168
class Client(object):
240
169
"""A representation of a client host served by this server.
243
_approved: bool(); 'None' if not yet approved/disapproved
244
approval_delay: datetime.timedelta(); Time to wait for approval
245
approval_duration: datetime.timedelta(); Duration of one approval
246
checker: subprocess.Popen(); a running checker process used
247
to see if the client lives.
248
'None' if no process is running.
171
name: string; from the config file, used in log messages
172
fingerprint: string (40 or 32 hexadecimal digits); used to
173
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.
185
checker_initiator_tag: a gobject event source tag, or None
186
stop_initiator_tag: - '' -
249
187
checker_callback_tag: - '' -
250
checker_command: string; External command which is run to check
251
if client lives. %() expansions are done at
188
checker_command: string; External command which is run to check if
189
client lives. %() expansions are done at
252
190
runtime with vars(self) as dict, so that for
253
191
instance %(name)s can be used in the command.
254
checker_initiator_tag: a gobject event source tag, or None
255
created: datetime.datetime(); (UTC) object creation
256
current_checker_command: string; current running checker_command
257
disable_hook: If set, called by disable() as disable_hook(self)
258
disable_initiator_tag: - '' -
260
fingerprint: string (40 or 32 hexadecimal digits); used to
261
uniquely identify the client
262
host: string; available for use by the checker command
263
interval: datetime.timedelta(); How often to start a new checker
264
last_checked_ok: datetime.datetime(); (UTC) or None
265
last_enabled: datetime.datetime(); (UTC)
266
name: string; from the config file, used in log messages and
268
secret: bytestring; sent verbatim (over TLS) to client
269
timeout: datetime.timedelta(); How long from last_checked_ok
270
until this client is disabled
271
runtime_expansions: Allowed attributes for runtime expansion.
193
_timeout: Real variable for 'timeout'
194
_interval: Real variable for 'interval'
195
_timeout_milliseconds: Used when calling gobject.timeout_add()
196
_interval_milliseconds: - '' -
274
runtime_expansions = (u"approval_delay", u"approval_duration",
275
u"created", u"enabled", u"fingerprint",
276
u"host", u"interval", u"last_checked_ok",
277
u"last_enabled", u"name", u"timeout")
280
def _timedelta_to_milliseconds(td):
281
"Convert a datetime.timedelta() to milliseconds"
282
return ((td.days * 24 * 60 * 60 * 1000)
283
+ (td.seconds * 1000)
284
+ (td.microseconds // 1000))
286
def timeout_milliseconds(self):
287
"Return the 'timeout' attribute in milliseconds"
288
return self._timedelta_to_milliseconds(self.timeout)
290
def interval_milliseconds(self):
291
"Return the 'interval' attribute in milliseconds"
292
return self._timedelta_to_milliseconds(self.interval)
294
def approval_delay_milliseconds(self):
295
return self._timedelta_to_milliseconds(self.approval_delay)
297
def __init__(self, name = None, disable_hook=None, config=None):
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={}):
298
222
"""Note: the 'checker' key in 'config' sets the
299
223
'checker_command' attribute and *not* the 'checker'
304
226
logger.debug(u"Creating client %r", self.name)
305
227
# Uppercase and remove spaces from fingerprint for later
306
228
# comparison purposes with return value from the fingerprint()
308
self.fingerprint = (config[u"fingerprint"].upper()
230
self.fingerprint = config["fingerprint"].upper()\
310
232
logger.debug(u" Fingerprint: %s", self.fingerprint)
311
if u"secret" in config:
312
self.secret = config[u"secret"].decode(u"base64")
313
elif u"secfile" in config:
314
with open(os.path.expanduser(os.path.expandvars
315
(config[u"secfile"])),
317
self.secret = secfile.read()
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()
319
240
raise TypeError(u"No secret or secfile for client %s"
321
self.host = config.get(u"host", u"")
322
self.created = datetime.datetime.utcnow()
324
self.last_enabled = None
242
self.host = config.get("host", "")
243
self.created = datetime.datetime.now()
325
244
self.last_checked_ok = None
326
self.timeout = string_to_delta(config[u"timeout"])
327
self.interval = string_to_delta(config[u"interval"])
328
self.disable_hook = disable_hook
245
self.timeout = string_to_delta(config["timeout"])
246
self.interval = string_to_delta(config["interval"])
247
self.stop_hook = stop_hook
329
248
self.checker = None
330
249
self.checker_initiator_tag = None
331
self.disable_initiator_tag = None
250
self.stop_initiator_tag = None
332
251
self.checker_callback_tag = None
333
self.checker_command = config[u"checker"]
334
self.current_checker_command = None
335
self.last_connect = None
336
self._approved = None
337
self.approved_by_default = config.get(u"approved_by_default",
339
self.approvals_pending = 0
340
self.approval_delay = string_to_delta(
341
config[u"approval_delay"])
342
self.approval_duration = string_to_delta(
343
config[u"approval_duration"])
344
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
346
def send_changedstate(self):
347
self.changedstate.acquire()
348
self.changedstate.notify_all()
349
self.changedstate.release()
252
self.check_command = config["checker"]
352
254
"""Start this client's checker and timeout hooks"""
353
if getattr(self, u"enabled", False):
356
self.send_changedstate()
357
self.last_enabled = datetime.datetime.utcnow()
358
255
# Schedule a new checker to be started an 'interval' from now,
359
256
# and every interval from then on.
360
self.checker_initiator_tag = (gobject.timeout_add
361
(self.interval_milliseconds(),
363
# Schedule a disable() when 'timeout' has passed
364
self.disable_initiator_tag = (gobject.timeout_add
365
(self.timeout_milliseconds(),
257
self.checker_initiator_tag = gobject.timeout_add\
258
(self._interval_milliseconds,
368
260
# Also start a new checker *right now*.
369
261
self.start_checker()
371
def disable(self, quiet=True):
372
"""Disable this client."""
373
if not getattr(self, "enabled", False):
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)
376
self.send_changedstate()
378
logger.info(u"Disabling client %s", self.name)
379
if getattr(self, u"disable_initiator_tag", False):
380
gobject.source_remove(self.disable_initiator_tag)
381
self.disable_initiator_tag = None
382
if getattr(self, u"checker_initiator_tag", False):
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):
383
280
gobject.source_remove(self.checker_initiator_tag)
384
281
self.checker_initiator_tag = None
385
282
self.stop_checker()
386
if self.disable_hook:
387
self.disable_hook(self)
389
285
# Do not run this again if called by a gobject.timeout_add
392
287
def __del__(self):
393
self.disable_hook = None
396
def checker_callback(self, pid, condition, command):
288
self.stop_hook = None
290
def checker_callback(self, pid, condition):
397
291
"""The checker has completed, so take appropriate actions."""
292
now = datetime.datetime.now()
398
293
self.checker_callback_tag = None
399
294
self.checker = None
400
if os.WIFEXITED(condition):
401
exitstatus = os.WEXITSTATUS(condition)
403
logger.info(u"Checker for %(name)s succeeded",
407
logger.info(u"Checker for %(name)s failed",
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):
410
305
logger.warning(u"Checker for %(name)s crashed?",
413
def checked_ok(self):
414
"""Bump up the timeout for this client.
416
This should only be called when the client has been seen,
419
self.last_checked_ok = datetime.datetime.utcnow()
420
gobject.source_remove(self.disable_initiator_tag)
421
self.disable_initiator_tag = (gobject.timeout_add
422
(self.timeout_milliseconds(),
308
logger.info(u"Checker for %(name)s failed",
425
310
def start_checker(self):
426
311
"""Start a new checker subprocess if one is not running.
428
312
If a checker already exists, leave it running and do
430
314
# The reason for not killing a running checker is that if we
433
317
# client would inevitably timeout, since no checker would get
434
318
# a chance to run to completion. If we instead leave running
435
319
# checkers alone, the checker would have to take more time
436
# than 'timeout' for the client to be disabled, which is as it
439
# If a checker exists, make sure it is not a zombie
441
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
442
except (AttributeError, OSError), error:
443
if (isinstance(error, OSError)
444
and error.errno != errno.ECHILD):
448
logger.warning(u"Checker was a zombie")
449
gobject.source_remove(self.checker_callback_tag)
450
self.checker_callback(pid, status,
451
self.current_checker_command)
452
# Start a new checker if needed
320
# than 'timeout' for the client to be declared invalid, which
321
# is as it should be.
453
322
if self.checker is None:
455
# In case checker_command has exactly one % operator
456
command = self.checker_command % self.host
324
# In case check_command has exactly one % operator
325
command = self.check_command % self.host
457
326
except TypeError:
458
327
# Escape attributes for the shell
459
escaped_attrs = dict(
461
re.escape(unicode(str(getattr(self, attr, u"")),
465
self.runtime_expansions)
328
escaped_attrs = dict((key, re.escape(str(val)))
330
vars(self).iteritems())
468
command = self.checker_command % escaped_attrs
332
command = self.check_command % escaped_attrs
469
333
except TypeError, error:
470
334
logger.error(u'Could not format string "%s":'
471
u' %s', self.checker_command, error)
335
u' %s', self.check_command, error)
472
336
return True # Try again later
473
self.current_checker_command = command
475
338
logger.info(u"Starting checker %r for %s",
476
339
command, self.name)
477
# We don't need to redirect stdout and stderr, since
478
# in normal mode, that is already done by daemon(),
479
# and in debug mode we don't want to. (Stdin is
480
# always replaced by /dev/null.)
481
340
self.checker = subprocess.Popen(command,
483
shell=True, cwd=u"/")
484
self.checker_callback_tag = (gobject.child_watch_add
486
self.checker_callback,
488
# The checker may have completed before the gobject
489
# watch was added. Check for this.
490
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
492
gobject.source_remove(self.checker_callback_tag)
493
self.checker_callback(pid, status, command)
494
except OSError, error:
343
self.checker_callback_tag = gobject.child_watch_add\
345
self.checker_callback)
346
except subprocess.OSError, error:
495
347
logger.error(u"Failed to start subprocess: %s",
497
349
# Re-run this periodically if run by gobject.timeout_add
500
351
def stop_checker(self):
501
352
"""Force the checker process, if any, to stop."""
502
353
if self.checker_callback_tag:
503
354
gobject.source_remove(self.checker_callback_tag)
504
355
self.checker_callback_tag = None
505
if getattr(self, u"checker", None) is None:
356
if getattr(self, "checker", None) is None:
507
358
logger.debug(u"Stopping checker for %(name)s", vars(self))
509
360
os.kill(self.checker.pid, signal.SIGTERM)
511
362
#if self.checker.poll() is None:
512
363
# os.kill(self.checker.pid, signal.SIGKILL)
513
364
except OSError, error:
514
365
if error.errno != errno.ESRCH: # No such process
516
367
self.checker = None
518
def dbus_service_property(dbus_interface, signature=u"v",
519
access=u"readwrite", byte_arrays=False):
520
"""Decorators for marking methods of a DBusObjectWithProperties to
521
become properties on the D-Bus.
523
The decorated method will be called with no arguments by "Get"
524
and with one argument by "Set".
526
The parameters, where they are supported, are the same as
527
dbus.service.method, except there is only "signature", since the
528
type from Get() and the type sent to Set() is the same.
530
# Encoding deeply encoded byte arrays is not supported yet by the
531
# "Set" method, so we fail early here:
532
if byte_arrays and signature != u"ay":
533
raise ValueError(u"Byte arrays not supported for non-'ay'"
534
u" signature %r" % signature)
536
func._dbus_is_property = True
537
func._dbus_interface = dbus_interface
538
func._dbus_signature = signature
539
func._dbus_access = access
540
func._dbus_name = func.__name__
541
if func._dbus_name.endswith(u"_dbus_property"):
542
func._dbus_name = func._dbus_name[:-14]
543
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
548
class DBusPropertyException(dbus.exceptions.DBusException):
549
"""A base class for D-Bus property-related exceptions
551
def __unicode__(self):
552
return unicode(str(self))
555
class DBusPropertyAccessException(DBusPropertyException):
556
"""A property's access permissions disallows an operation.
561
class DBusPropertyNotFound(DBusPropertyException):
562
"""An attempt was made to access a non-existing property.
567
class DBusObjectWithProperties(dbus.service.Object):
568
"""A D-Bus object with properties.
570
Classes inheriting from this can use the dbus_service_property
571
decorator to expose methods as D-Bus properties. It exposes the
572
standard Get(), Set(), and GetAll() methods on the D-Bus.
576
def _is_dbus_property(obj):
577
return getattr(obj, u"_dbus_is_property", False)
579
def _get_all_dbus_properties(self):
580
"""Returns a generator of (name, attribute) pairs
582
return ((prop._dbus_name, prop)
584
inspect.getmembers(self, self._is_dbus_property))
586
def _get_dbus_property(self, interface_name, property_name):
587
"""Returns a bound method if one exists which is a D-Bus
588
property with the specified name and interface.
590
for name in (property_name,
591
property_name + u"_dbus_property"):
592
prop = getattr(self, name, None)
594
or not self._is_dbus_property(prop)
595
or prop._dbus_name != property_name
596
or (interface_name and prop._dbus_interface
597
and interface_name != prop._dbus_interface)):
601
raise DBusPropertyNotFound(self.dbus_object_path + u":"
602
+ interface_name + u"."
605
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
607
def Get(self, interface_name, property_name):
608
"""Standard D-Bus property Get() method, see D-Bus standard.
610
prop = self._get_dbus_property(interface_name, property_name)
611
if prop._dbus_access == u"write":
612
raise DBusPropertyAccessException(property_name)
614
if not hasattr(value, u"variant_level"):
616
return type(value)(value, variant_level=value.variant_level+1)
618
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
619
def Set(self, interface_name, property_name, value):
620
"""Standard D-Bus property Set() method, see D-Bus standard.
622
prop = self._get_dbus_property(interface_name, property_name)
623
if prop._dbus_access == u"read":
624
raise DBusPropertyAccessException(property_name)
625
if prop._dbus_get_args_options[u"byte_arrays"]:
626
# The byte_arrays option is not supported yet on
627
# signatures other than "ay".
628
if prop._dbus_signature != u"ay":
630
value = dbus.ByteArray(''.join(unichr(byte)
634
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
635
out_signature=u"a{sv}")
636
def GetAll(self, interface_name):
637
"""Standard D-Bus property GetAll() method, see D-Bus
640
Note: Will not include properties with access="write".
643
for name, prop in self._get_all_dbus_properties():
645
and interface_name != prop._dbus_interface):
646
# Interface non-empty but did not match
648
# Ignore write-only properties
649
if prop._dbus_access == u"write":
652
if not hasattr(value, u"variant_level"):
655
all[name] = type(value)(value, variant_level=
656
value.variant_level+1)
657
return dbus.Dictionary(all, signature=u"sv")
659
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
661
path_keyword='object_path',
662
connection_keyword='connection')
663
def Introspect(self, object_path, connection):
664
"""Standard D-Bus method, overloaded to insert property tags.
666
xmlstring = dbus.service.Object.Introspect(self, object_path,
669
document = xml.dom.minidom.parseString(xmlstring)
670
def make_tag(document, name, prop):
671
e = document.createElement(u"property")
672
e.setAttribute(u"name", name)
673
e.setAttribute(u"type", prop._dbus_signature)
674
e.setAttribute(u"access", prop._dbus_access)
676
for if_tag in document.getElementsByTagName(u"interface"):
677
for tag in (make_tag(document, name, prop)
679
in self._get_all_dbus_properties()
680
if prop._dbus_interface
681
== if_tag.getAttribute(u"name")):
682
if_tag.appendChild(tag)
683
# Add the names to the return values for the
684
# "org.freedesktop.DBus.Properties" methods
685
if (if_tag.getAttribute(u"name")
686
== u"org.freedesktop.DBus.Properties"):
687
for cn in if_tag.getElementsByTagName(u"method"):
688
if cn.getAttribute(u"name") == u"Get":
689
for arg in cn.getElementsByTagName(u"arg"):
690
if (arg.getAttribute(u"direction")
692
arg.setAttribute(u"name", u"value")
693
elif cn.getAttribute(u"name") == u"GetAll":
694
for arg in cn.getElementsByTagName(u"arg"):
695
if (arg.getAttribute(u"direction")
697
arg.setAttribute(u"name", u"props")
698
xmlstring = document.toxml(u"utf-8")
700
except (AttributeError, xml.dom.DOMException,
701
xml.parsers.expat.ExpatError), error:
702
logger.error(u"Failed to override Introspection method",
707
class ClientDBus(Client, DBusObjectWithProperties):
708
"""A Client class using D-Bus
711
dbus_object_path: dbus.ObjectPath
712
bus: dbus.SystemBus()
715
runtime_expansions = (Client.runtime_expansions
716
+ (u"dbus_object_path",))
718
# dbus.service.Object doesn't use super(), so we can't either.
720
def __init__(self, bus = None, *args, **kwargs):
721
self._approvals_pending = 0
723
Client.__init__(self, *args, **kwargs)
724
# Only now, when this client is initialized, can it show up on
726
client_object_name = unicode(self.name).translate(
727
{ord(u"."): ord(u"_"),
728
ord(u"-"): ord(u"_")})
729
self.dbus_object_path = (dbus.ObjectPath
730
(u"/clients/" + client_object_name))
731
DBusObjectWithProperties.__init__(self, self.bus,
732
self.dbus_object_path)
734
def _get_approvals_pending(self):
735
return self._approvals_pending
736
def _set_approvals_pending(self, value):
737
old_value = self._approvals_pending
738
self._approvals_pending = value
740
if (hasattr(self, "dbus_object_path")
741
and bval is not bool(old_value)):
742
dbus_bool = dbus.Boolean(bval, variant_level=1)
743
self.PropertyChanged(dbus.String(u"ApprovalPending"),
746
approvals_pending = property(_get_approvals_pending,
747
_set_approvals_pending)
748
del _get_approvals_pending, _set_approvals_pending
751
def _datetime_to_dbus(dt, variant_level=0):
752
"""Convert a UTC datetime.datetime() to a D-Bus type."""
753
return dbus.String(dt.isoformat(),
754
variant_level=variant_level)
757
oldstate = getattr(self, u"enabled", False)
758
r = Client.enable(self)
759
if oldstate != self.enabled:
761
self.PropertyChanged(dbus.String(u"Enabled"),
762
dbus.Boolean(True, variant_level=1))
763
self.PropertyChanged(
764
dbus.String(u"LastEnabled"),
765
self._datetime_to_dbus(self.last_enabled,
769
def disable(self, quiet = False):
770
oldstate = getattr(self, u"enabled", False)
771
r = Client.disable(self, quiet=quiet)
772
if not quiet and oldstate != self.enabled:
774
self.PropertyChanged(dbus.String(u"Enabled"),
775
dbus.Boolean(False, variant_level=1))
778
def __del__(self, *args, **kwargs):
780
self.remove_from_connection()
783
if hasattr(DBusObjectWithProperties, u"__del__"):
784
DBusObjectWithProperties.__del__(self, *args, **kwargs)
785
Client.__del__(self, *args, **kwargs)
787
def checker_callback(self, pid, condition, command,
789
self.checker_callback_tag = None
792
self.PropertyChanged(dbus.String(u"CheckerRunning"),
793
dbus.Boolean(False, variant_level=1))
794
if os.WIFEXITED(condition):
795
exitstatus = os.WEXITSTATUS(condition)
797
self.CheckerCompleted(dbus.Int16(exitstatus),
798
dbus.Int64(condition),
799
dbus.String(command))
802
self.CheckerCompleted(dbus.Int16(-1),
803
dbus.Int64(condition),
804
dbus.String(command))
806
return Client.checker_callback(self, pid, condition, command,
809
def checked_ok(self, *args, **kwargs):
810
r = Client.checked_ok(self, *args, **kwargs)
812
self.PropertyChanged(
813
dbus.String(u"LastCheckedOK"),
814
(self._datetime_to_dbus(self.last_checked_ok,
818
def start_checker(self, *args, **kwargs):
819
old_checker = self.checker
820
if self.checker is not None:
821
old_checker_pid = self.checker.pid
823
old_checker_pid = None
824
r = Client.start_checker(self, *args, **kwargs)
825
# Only if new checker process was started
826
if (self.checker is not None
827
and old_checker_pid != self.checker.pid):
829
self.CheckerStarted(self.current_checker_command)
830
self.PropertyChanged(
831
dbus.String(u"CheckerRunning"),
832
dbus.Boolean(True, variant_level=1))
835
def stop_checker(self, *args, **kwargs):
836
old_checker = getattr(self, u"checker", None)
837
r = Client.stop_checker(self, *args, **kwargs)
838
if (old_checker is not None
839
and getattr(self, u"checker", None) is None):
840
self.PropertyChanged(dbus.String(u"CheckerRunning"),
841
dbus.Boolean(False, variant_level=1))
844
def _reset_approved(self):
845
self._approved = None
848
def approve(self, value=True):
849
self.send_changedstate()
850
self._approved = value
851
gobject.timeout_add(self._timedelta_to_milliseconds
852
(self.approval_duration),
853
self._reset_approved)
856
## D-Bus methods, signals & properties
857
_interface = u"se.bsnet.fukt.Mandos.Client"
861
# CheckerCompleted - signal
862
@dbus.service.signal(_interface, signature=u"nxs")
863
def CheckerCompleted(self, exitcode, waitstatus, command):
867
# CheckerStarted - signal
868
@dbus.service.signal(_interface, signature=u"s")
869
def CheckerStarted(self, command):
873
# PropertyChanged - signal
874
@dbus.service.signal(_interface, signature=u"sv")
875
def PropertyChanged(self, property, value):
880
@dbus.service.signal(_interface)
883
Is sent after a successful transfer of secret from the Mandos
884
server to mandos-client
889
@dbus.service.signal(_interface, signature=u"s")
890
def Rejected(self, reason):
894
# NeedApproval - signal
895
@dbus.service.signal(_interface, signature=u"tb")
896
def NeedApproval(self, timeout, default):
903
@dbus.service.method(_interface, in_signature=u"b")
904
def Approve(self, value):
908
@dbus.service.method(_interface)
910
return self.checked_ok()
913
@dbus.service.method(_interface)
918
# StartChecker - method
919
@dbus.service.method(_interface)
920
def StartChecker(self):
925
@dbus.service.method(_interface)
930
# StopChecker - method
931
@dbus.service.method(_interface)
932
def StopChecker(self):
937
# ApprovalPending - property
938
@dbus_service_property(_interface, signature=u"b", access=u"read")
939
def ApprovalPending_dbus_property(self):
940
return dbus.Boolean(bool(self.approvals_pending))
942
# ApprovedByDefault - property
943
@dbus_service_property(_interface, signature=u"b",
945
def ApprovedByDefault_dbus_property(self, value=None):
946
if value is None: # get
947
return dbus.Boolean(self.approved_by_default)
948
self.approved_by_default = bool(value)
950
self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
951
dbus.Boolean(value, variant_level=1))
953
# ApprovalDelay - property
954
@dbus_service_property(_interface, signature=u"t",
956
def ApprovalDelay_dbus_property(self, value=None):
957
if value is None: # get
958
return dbus.UInt64(self.approval_delay_milliseconds())
959
self.approval_delay = datetime.timedelta(0, 0, 0, value)
961
self.PropertyChanged(dbus.String(u"ApprovalDelay"),
962
dbus.UInt64(value, variant_level=1))
964
# ApprovalDuration - property
965
@dbus_service_property(_interface, signature=u"t",
967
def ApprovalDuration_dbus_property(self, value=None):
968
if value is None: # get
969
return dbus.UInt64(self._timedelta_to_milliseconds(
970
self.approval_duration))
971
self.approval_duration = datetime.timedelta(0, 0, 0, value)
973
self.PropertyChanged(dbus.String(u"ApprovalDuration"),
974
dbus.UInt64(value, variant_level=1))
977
@dbus_service_property(_interface, signature=u"s", access=u"read")
978
def Name_dbus_property(self):
979
return dbus.String(self.name)
981
# Fingerprint - property
982
@dbus_service_property(_interface, signature=u"s", access=u"read")
983
def Fingerprint_dbus_property(self):
984
return dbus.String(self.fingerprint)
987
@dbus_service_property(_interface, signature=u"s",
989
def Host_dbus_property(self, value=None):
990
if value is None: # get
991
return dbus.String(self.host)
994
self.PropertyChanged(dbus.String(u"Host"),
995
dbus.String(value, variant_level=1))
998
@dbus_service_property(_interface, signature=u"s", access=u"read")
999
def Created_dbus_property(self):
1000
return dbus.String(self._datetime_to_dbus(self.created))
1002
# LastEnabled - property
1003
@dbus_service_property(_interface, signature=u"s", access=u"read")
1004
def LastEnabled_dbus_property(self):
1005
if self.last_enabled is None:
1006
return dbus.String(u"")
1007
return dbus.String(self._datetime_to_dbus(self.last_enabled))
1009
# Enabled - property
1010
@dbus_service_property(_interface, signature=u"b",
1011
access=u"readwrite")
1012
def Enabled_dbus_property(self, value=None):
1013
if value is None: # get
1014
return dbus.Boolean(self.enabled)
1020
# LastCheckedOK - property
1021
@dbus_service_property(_interface, signature=u"s",
1022
access=u"readwrite")
1023
def LastCheckedOK_dbus_property(self, value=None):
1024
if value is not None:
368
def still_valid(self):
369
"""Has the timeout not yet passed for this client?"""
370
now = datetime.datetime.now()
1027
371
if self.last_checked_ok is None:
1028
return dbus.String(u"")
1029
return dbus.String(self._datetime_to_dbus(self
1032
# Timeout - property
1033
@dbus_service_property(_interface, signature=u"t",
1034
access=u"readwrite")
1035
def Timeout_dbus_property(self, value=None):
1036
if value is None: # get
1037
return dbus.UInt64(self.timeout_milliseconds())
1038
self.timeout = datetime.timedelta(0, 0, 0, value)
1040
self.PropertyChanged(dbus.String(u"Timeout"),
1041
dbus.UInt64(value, variant_level=1))
1042
if getattr(self, u"disable_initiator_tag", None) is None:
1044
# Reschedule timeout
1045
gobject.source_remove(self.disable_initiator_tag)
1046
self.disable_initiator_tag = None
1047
time_to_die = (self.
1048
_timedelta_to_milliseconds((self
1053
if time_to_die <= 0:
1054
# The timeout has passed
1057
self.disable_initiator_tag = (gobject.timeout_add
1058
(time_to_die, self.disable))
1060
# Interval - property
1061
@dbus_service_property(_interface, signature=u"t",
1062
access=u"readwrite")
1063
def Interval_dbus_property(self, value=None):
1064
if value is None: # get
1065
return dbus.UInt64(self.interval_milliseconds())
1066
self.interval = datetime.timedelta(0, 0, 0, value)
1068
self.PropertyChanged(dbus.String(u"Interval"),
1069
dbus.UInt64(value, variant_level=1))
1070
if getattr(self, u"checker_initiator_tag", None) is None:
1072
# Reschedule checker run
1073
gobject.source_remove(self.checker_initiator_tag)
1074
self.checker_initiator_tag = (gobject.timeout_add
1075
(value, self.start_checker))
1076
self.start_checker() # Start one now, too
1078
# Checker - property
1079
@dbus_service_property(_interface, signature=u"s",
1080
access=u"readwrite")
1081
def Checker_dbus_property(self, value=None):
1082
if value is None: # get
1083
return dbus.String(self.checker_command)
1084
self.checker_command = value
1086
self.PropertyChanged(dbus.String(u"Checker"),
1087
dbus.String(self.checker_command,
1090
# CheckerRunning - property
1091
@dbus_service_property(_interface, signature=u"b",
1092
access=u"readwrite")
1093
def CheckerRunning_dbus_property(self, value=None):
1094
if value is None: # get
1095
return dbus.Boolean(self.checker is not None)
1097
self.start_checker()
1101
# ObjectPath - property
1102
@dbus_service_property(_interface, signature=u"o", access=u"read")
1103
def ObjectPath_dbus_property(self):
1104
return self.dbus_object_path # is already a dbus.ObjectPath
1107
@dbus_service_property(_interface, signature=u"ay",
1108
access=u"write", byte_arrays=True)
1109
def Secret_dbus_property(self, value):
1110
self.secret = str(value)
1115
class ProxyClient(object):
1116
def __init__(self, child_pipe, fpr, address):
1117
self._pipe = child_pipe
1118
self._pipe.send(('init', fpr, address))
1119
if not self._pipe.recv():
1122
def __getattribute__(self, name):
1123
if(name == '_pipe'):
1124
return super(ProxyClient, self).__getattribute__(name)
1125
self._pipe.send(('getattr', name))
1126
data = self._pipe.recv()
1127
if data[0] == 'data':
1129
if data[0] == 'function':
1130
def func(*args, **kwargs):
1131
self._pipe.send(('funcall', name, args, kwargs))
1132
return self._pipe.recv()[1]
1135
def __setattr__(self, name, value):
1136
if(name == '_pipe'):
1137
return super(ProxyClient, self).__setattr__(name, value)
1138
self._pipe.send(('setattr', name, value))
1141
class ClientHandler(socketserver.BaseRequestHandler, object):
1142
"""A class to handle client connections.
1144
Instantiated once for each connection to handle it.
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.
1145
427
Note: This will run in its own forked process."""
1147
429
def handle(self):
1148
with contextlib.closing(self.server.child_pipe) as child_pipe:
1149
logger.info(u"TCP connection from: %s",
1150
unicode(self.client_address))
1151
logger.debug(u"Pipe FD: %d",
1152
self.server.child_pipe.fileno())
1154
session = (gnutls.connection
1155
.ClientSession(self.request,
1157
.X509Credentials()))
1159
# Note: gnutls.connection.X509Credentials is really a
1160
# generic GnuTLS certificate credentials object so long as
1161
# no X.509 keys are added to it. Therefore, we can use it
1162
# here despite using OpenPGP certificates.
1164
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1165
# u"+AES-256-CBC", u"+SHA1",
1166
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1168
# Use a fallback default, since this MUST be set.
1169
priority = self.server.gnutls_priority
1170
if priority is None:
1171
priority = u"NORMAL"
1172
(gnutls.library.functions
1173
.gnutls_priority_set_direct(session._c_object,
1176
# Start communication using the Mandos protocol
1177
# Get protocol number
1178
line = self.request.makefile().readline()
1179
logger.debug(u"Protocol version: %r", line)
1181
if int(line.strip().split()[0]) > 1:
1183
except (ValueError, IndexError, RuntimeError), error:
1184
logger.error(u"Unknown protocol version: %s", error)
1187
# Start GnuTLS connection
1190
except gnutls.errors.GNUTLSError, error:
1191
logger.warning(u"Handshake failed: %s", error)
1192
# Do not run session.bye() here: the session is not
1193
# established. Just abandon the request.
1195
logger.debug(u"Handshake succeeded")
1197
approval_required = False
1200
fpr = self.fingerprint(self.peer_certificate
1202
except (TypeError, gnutls.errors.GNUTLSError), error:
1203
logger.warning(u"Bad certificate: %s", error)
1205
logger.debug(u"Fingerprint: %s", fpr)
1208
client = ProxyClient(child_pipe, fpr,
1209
self.client_address)
1213
if client.approval_delay:
1214
delay = client.approval_delay
1215
client.approvals_pending += 1
1216
approval_required = True
1219
if not client.enabled:
1220
logger.warning(u"Client %s is disabled",
1222
if self.server.use_dbus:
1224
client.Rejected("Disabled")
1227
if client._approved or not client.approval_delay:
1228
#We are approved or approval is disabled
1230
elif client._approved is None:
1231
logger.info(u"Client %s needs approval",
1233
if self.server.use_dbus:
1235
client.NeedApproval(
1236
client.approval_delay_milliseconds(),
1237
client.approved_by_default)
1239
logger.warning(u"Client %s was not approved",
1241
if self.server.use_dbus:
1243
client.Rejected("Denied")
1246
#wait until timeout or approved
1247
#x = float(client._timedelta_to_milliseconds(delay))
1248
time = datetime.datetime.now()
1249
client.changedstate.acquire()
1250
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1251
client.changedstate.release()
1252
time2 = datetime.datetime.now()
1253
if (time2 - time) >= delay:
1254
if not client.approved_by_default:
1255
logger.warning("Client %s timed out while"
1256
" waiting for approval",
1258
if self.server.use_dbus:
1260
client.Rejected("Approval timed out")
1265
delay -= time2 - time
1268
while sent_size < len(client.secret):
1270
sent = session.send(client.secret[sent_size:])
1271
except (gnutls.errors.GNUTLSError), error:
1272
logger.warning("gnutls send failed")
1274
logger.debug(u"Sent: %d, remaining: %d",
1275
sent, len(client.secret)
1276
- (sent_size + sent))
1279
logger.info(u"Sending secret to %s", client.name)
1280
# bump the timeout as if seen
1282
if self.server.use_dbus:
1287
if approval_required:
1288
client.approvals_pending -= 1
1291
except (gnutls.errors.GNUTLSError), error:
1292
logger.warning("GnuTLS bye failed")
1295
def peer_certificate(session):
1296
"Return the peer's OpenPGP certificate as a bytestring"
1297
# If not an OpenPGP certificate...
1298
if (gnutls.library.functions
1299
.gnutls_certificate_type_get(session._c_object)
1300
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1301
# ...do the normal thing
1302
return session.peer_certificate
1303
list_size = ctypes.c_uint(1)
1304
cert_list = (gnutls.library.functions
1305
.gnutls_certificate_get_peers
1306
(session._c_object, ctypes.byref(list_size)))
1307
if not bool(cert_list) and list_size.value != 0:
1308
raise gnutls.errors.GNUTLSError(u"error getting peer"
1310
if list_size.value == 0:
1313
return ctypes.string_at(cert.data, cert.size)
1316
def fingerprint(openpgp):
1317
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1318
# New GnuTLS "datum" with the OpenPGP public key
1319
datum = (gnutls.library.types
1320
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1323
ctypes.c_uint(len(openpgp))))
1324
# New empty GnuTLS certificate
1325
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1326
(gnutls.library.functions
1327
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1328
# Import the OpenPGP public key into the certificate
1329
(gnutls.library.functions
1330
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1331
gnutls.library.constants
1332
.GNUTLS_OPENPGP_FMT_RAW))
1333
# Verify the self signature in the key
1334
crtverify = ctypes.c_uint()
1335
(gnutls.library.functions
1336
.gnutls_openpgp_crt_verify_self(crt, 0,
1337
ctypes.byref(crtverify)))
1338
if crtverify.value != 0:
1339
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1340
raise (gnutls.errors.CertificateSecurityError
1342
# New buffer for the fingerprint
1343
buf = ctypes.create_string_buffer(20)
1344
buf_len = ctypes.c_size_t()
1345
# Get the fingerprint from the certificate into the buffer
1346
(gnutls.library.functions
1347
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1348
ctypes.byref(buf_len)))
1349
# Deinit the certificate
1350
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1351
# Convert the buffer to a Python bytestring
1352
fpr = ctypes.string_at(buf, buf_len.value)
1353
# Convert the bytestring to hexadecimal notation
1354
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1358
class MultiprocessingMixIn(object):
1359
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1360
def sub_process_main(self, request, address):
1362
self.finish_request(request, address)
1364
self.handle_error(request, address)
1365
self.close_request(request)
1367
def process_request(self, request, address):
1368
"""Start a new process to process the request."""
1369
multiprocessing.Process(target = self.sub_process_main,
1370
args = (request, address)).start()
1372
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1373
""" adds a pipe to the MixIn """
1374
def process_request(self, request, client_address):
1375
"""Overrides and wraps the original process_request().
1377
This function creates a new pipe in self.pipe
1379
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1381
super(MultiprocessingMixInWithPipe,
1382
self).process_request(request, client_address)
1383
self.child_pipe.close()
1384
self.add_pipe(parent_pipe)
1386
def add_pipe(self, parent_pipe):
1387
"""Dummy function; override as necessary"""
1390
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1391
socketserver.TCPServer, object):
1392
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
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.
1395
enabled: Boolean; whether this server is activated yet
1396
interface: None or a network interface name (string)
1397
use_ipv6: Boolean; to use IPv6 or not
504
settings: Server settings
505
clients: Set() of Client objects
1399
def __init__(self, server_address, RequestHandlerClass,
1400
interface=None, use_ipv6=True):
1401
self.interface = interface
1403
self.address_family = socket.AF_INET6
1404
socketserver.TCPServer.__init__(self, server_address,
1405
RequestHandlerClass)
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)
1406
516
def server_bind(self):
1407
517
"""This overrides the normal server_bind() function
1408
518
to bind to an interface if one was specified, and also NOT to
1409
519
bind to an address or port if they were not specified."""
1410
if self.interface is not None:
1411
if SO_BINDTODEVICE is None:
1412
logger.error(u"SO_BINDTODEVICE does not exist;"
1413
u" cannot bind to interface %s",
1417
self.socket.setsockopt(socket.SOL_SOCKET,
1421
except socket.error, error:
1422
if error[0] == errno.EPERM:
1423
logger.error(u"No permission to"
1424
u" bind to interface %s",
1426
elif error[0] == errno.ENOPROTOOPT:
1427
logger.error(u"SO_BINDTODEVICE not available;"
1428
u" cannot bind to interface %s",
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"])
1432
534
# Only bind(2) the socket if we really need to.
1433
535
if self.server_address[0] or self.server_address[1]:
1434
536
if not self.server_address[0]:
1435
if self.address_family == socket.AF_INET6:
1436
any_address = u"::" # in6addr_any
1438
any_address = socket.INADDR_ANY
1439
self.server_address = (any_address,
538
self.server_address = (in6addr_any,
1440
539
self.server_address[1])
1441
540
elif not self.server_address[1]:
1442
541
self.server_address = (self.server_address[0],
1444
# if self.interface:
543
# if self.settings["interface"]:
1445
544
# self.server_address = (self.server_address[0],
1448
547
# if_nametoindex
1450
return socketserver.TCPServer.server_bind(self)
1453
class MandosServer(IPv6_TCPServer):
1457
clients: set of Client objects
1458
gnutls_priority GnuTLS priority string
1459
use_dbus: Boolean; to emit D-Bus signals or not
1461
Assumes a gobject.MainLoop event loop.
1463
def __init__(self, server_address, RequestHandlerClass,
1464
interface=None, use_ipv6=True, clients=None,
1465
gnutls_priority=None, use_dbus=True):
1466
self.enabled = False
1467
self.clients = clients
1468
if self.clients is None:
1469
self.clients = set()
1470
self.use_dbus = use_dbus
1471
self.gnutls_priority = gnutls_priority
1472
IPv6_TCPServer.__init__(self, server_address,
1473
RequestHandlerClass,
1474
interface = interface,
1475
use_ipv6 = use_ipv6)
1476
def server_activate(self):
1478
return socketserver.TCPServer.server_activate(self)
1481
def add_pipe(self, parent_pipe):
1482
# Call "handle_ipc" for both data and EOF events
1483
gobject.io_add_watch(parent_pipe.fileno(),
1484
gobject.IO_IN | gobject.IO_HUP,
1485
functools.partial(self.handle_ipc,
1486
parent_pipe = parent_pipe))
1488
def handle_ipc(self, source, condition, parent_pipe=None,
1489
client_object=None):
1491
gobject.IO_IN: u"IN", # There is data to read.
1492
gobject.IO_OUT: u"OUT", # Data can be written (without
1494
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1495
gobject.IO_ERR: u"ERR", # Error condition.
1496
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1497
# broken, usually for pipes and
1500
conditions_string = ' | '.join(name
1502
condition_names.iteritems()
1503
if cond & condition)
1504
# error or the other end of multiprocessing.Pipe has closed
1505
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1508
# Read a request from the child
1509
request = parent_pipe.recv()
1510
command = request[0]
1512
if command == 'init':
1514
address = request[2]
1516
for c in self.clients:
1517
if c.fingerprint == fpr:
1521
logger.warning(u"Client not found for fingerprint: %s, ad"
1522
u"dress: %s", fpr, address)
1525
mandos_dbus_service.ClientNotFound(fpr, address[0])
1526
parent_pipe.send(False)
1529
gobject.io_add_watch(parent_pipe.fileno(),
1530
gobject.IO_IN | gobject.IO_HUP,
1531
functools.partial(self.handle_ipc,
1532
parent_pipe = parent_pipe,
1533
client_object = client))
1534
parent_pipe.send(True)
1535
# remove the old hook in favor of the new above hook on same fileno
1537
if command == 'funcall':
1538
funcname = request[1]
1542
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1544
if command == 'getattr':
1545
attrname = request[1]
1546
if callable(client_object.__getattribute__(attrname)):
1547
parent_pipe.send(('function',))
1549
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1551
if command == 'setattr':
1552
attrname = request[1]
1554
setattr(client_object, attrname, value)
550
return super(type(self), self).server_bind()
1559
553
def string_to_delta(interval):
1560
554
"""Parse a string and return a datetime.timedelta
1562
>>> string_to_delta(u'7d')
556
>>> string_to_delta('7d')
1563
557
datetime.timedelta(7)
1564
>>> string_to_delta(u'60s')
558
>>> string_to_delta('60s')
1565
559
datetime.timedelta(0, 60)
1566
>>> string_to_delta(u'60m')
560
>>> string_to_delta('60m')
1567
561
datetime.timedelta(0, 3600)
1568
>>> string_to_delta(u'24h')
562
>>> string_to_delta('24h')
1569
563
datetime.timedelta(1)
1570
564
>>> string_to_delta(u'1w')
1571
565
datetime.timedelta(7)
1572
>>> string_to_delta(u'5m 30s')
1573
datetime.timedelta(0, 330)
1575
timevalue = datetime.timedelta(0)
1576
for s in interval.split():
1578
suffix = unicode(s[-1])
1581
delta = datetime.timedelta(value)
1582
elif suffix == u"s":
1583
delta = datetime.timedelta(0, value)
1584
elif suffix == u"m":
1585
delta = datetime.timedelta(0, 0, 0, 0, value)
1586
elif suffix == u"h":
1587
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1588
elif suffix == u"w":
1589
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1591
raise ValueError(u"Unknown suffix %r" % suffix)
1592
except (ValueError, IndexError), e:
1593
raise ValueError(e.message)
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))
1598
610
def if_nametoindex(interface):
1599
"""Call the C function if_nametoindex(), or equivalent
1601
Note: This function cannot accept a unicode string."""
611
"""Call the C function if_nametoindex(), or equivalent"""
1602
612
global if_nametoindex
1604
if_nametoindex = (ctypes.cdll.LoadLibrary
1605
(ctypes.util.find_library(u"c"))
614
if "ctypes.util" not in sys.modules:
616
if_nametoindex = ctypes.cdll.LoadLibrary\
617
(ctypes.util.find_library("c")).if_nametoindex
1607
618
except (OSError, AttributeError):
1608
logger.warning(u"Doing if_nametoindex the hard way")
619
if "struct" not in sys.modules:
621
if "fcntl" not in sys.modules:
1609
623
def if_nametoindex(interface):
1610
624
"Get an interface index the hard way, i.e. using fcntl()"
1611
625
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1612
with contextlib.closing(socket.socket()) as s:
1613
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1614
struct.pack(str(u"16s16x"),
1616
interface_index = struct.unpack(str(u"I"),
627
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
628
struct.pack("16s16x", interface))
630
interface_index = struct.unpack("I", ifreq[16:20])[0]
1618
631
return interface_index
1619
632
return if_nametoindex(interface)
1622
635
def daemon(nochdir = False, noclose = False):
1623
636
"""See daemon(3). Standard BSD Unix function.
1625
637
This should really exist as os.daemon, but it doesn't (yet)."""
1686
689
# Default values for config file for server-global settings
1687
server_defaults = { u"interface": u"",
1692
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1693
u"servicename": u"Mandos",
1694
u"use_dbus": u"True",
1695
u"use_ipv6": u"True",
690
server_defaults = { "interface": "",
695
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
696
"servicename": "Mandos",
1699
699
# Parse config file for server-global settings
1700
server_config = configparser.SafeConfigParser(server_defaults)
700
server_config = ConfigParser.SafeConfigParser(server_defaults)
1701
701
del server_defaults
1702
server_config.read(os.path.join(options.configdir,
702
server_config.read(os.path.join(options.configdir, "mandos.conf"))
703
server_section = "server"
1704
704
# Convert the SafeConfigParser object to a dict
1705
server_settings = server_config.defaults()
1706
# Use the appropriate methods on the non-string config options
1707
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1708
server_settings[option] = server_config.getboolean(u"DEFAULT",
1710
if server_settings["port"]:
1711
server_settings["port"] = server_config.getint(u"DEFAULT",
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")
1713
709
del server_config
1715
711
# Override the settings from the config file with command line
1716
712
# options, if set.
1717
for option in (u"interface", u"address", u"port", u"debug",
1718
u"priority", u"servicename", u"configdir",
1719
u"use_dbus", u"use_ipv6", u"debuglevel"):
713
for option in ("interface", "address", "port", "debug",
714
"priority", "servicename", "configdir"):
1720
715
value = getattr(options, option)
1721
716
if value is not None:
1722
717
server_settings[option] = value
1724
# Force all strings to be unicode
1725
for option in server_settings.keys():
1726
if type(server_settings[option]) is str:
1727
server_settings[option] = unicode(server_settings[option])
1728
719
# Now we have our good server settings in "server_settings"
1730
##################################################################
1733
debug = server_settings[u"debug"]
1734
debuglevel = server_settings[u"debuglevel"]
1735
use_dbus = server_settings[u"use_dbus"]
1736
use_ipv6 = server_settings[u"use_ipv6"]
1738
if server_settings[u"servicename"] != u"Mandos":
1739
syslogger.setFormatter(logging.Formatter
1740
(u'Mandos (%s) [%%(process)d]:'
1741
u' %%(levelname)s: %%(message)s'
1742
% server_settings[u"servicename"]))
1744
# Parse config file with clients
1745
client_defaults = { u"timeout": u"1h",
1747
u"checker": u"fping -q -- %%(host)s",
1749
u"approval_delay": u"0s",
1750
u"approval_duration": u"1s",
1752
client_config = configparser.SafeConfigParser(client_defaults)
1753
client_config.read(os.path.join(server_settings[u"configdir"],
1756
global mandos_dbus_service
1757
mandos_dbus_service = None
1759
tcp_server = MandosServer((server_settings[u"address"],
1760
server_settings[u"port"]),
1762
interface=(server_settings[u"interface"]
1766
server_settings[u"priority"],
721
debug = server_settings["debug"]
1769
pidfilename = u"/var/run/mandos.pid"
1771
pidfile = open(pidfilename, u"w")
1773
logger.error(u"Could not open file %r", pidfilename)
1776
uid = pwd.getpwnam(u"_mandos").pw_uid
1777
gid = pwd.getpwnam(u"_mandos").pw_gid
1780
uid = pwd.getpwnam(u"mandos").pw_uid
1781
gid = pwd.getpwnam(u"mandos").pw_gid
1784
uid = pwd.getpwnam(u"nobody").pw_uid
1785
gid = pwd.getpwnam(u"nobody").pw_gid
1792
except OSError, error:
1793
if error[0] != errno.EPERM:
1796
if not debug and not debuglevel:
1797
724
syslogger.setLevel(logging.WARNING)
1798
725
console.setLevel(logging.WARNING)
1800
level = getattr(logging, debuglevel.upper())
1801
syslogger.setLevel(level)
1802
console.setLevel(level)
1805
# Enable all possible GnuTLS debugging
1807
# "Use a log level over 10 to enable all debugging options."
1809
gnutls.library.functions.gnutls_global_set_log_level(11)
1811
@gnutls.library.types.gnutls_log_func
1812
def debug_gnutls(level, string):
1813
logger.debug(u"GnuTLS: %s", string[:-1])
1815
(gnutls.library.functions
1816
.gnutls_global_set_log_function(debug_gnutls))
1818
# Redirect stdin so all checkers get /dev/null
1819
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1820
os.dup2(null, sys.stdin.fileno())
1824
# No console logging
1825
logger.removeHandler(console)
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"])
1828
748
global main_loop
1829
751
# From the Avahi example code
1830
752
DBusGMainLoop(set_as_default=True )
1831
753
main_loop = gobject.MainLoop()
1832
754
bus = dbus.SystemBus()
755
server = dbus.Interface(
756
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
757
avahi.DBUS_INTERFACE_SERVER )
1833
758
# End of Avahi example code
1836
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1837
bus, do_not_queue=True)
1838
except dbus.exceptions.NameExistsException, e:
1839
logger.error(unicode(e) + u", disabling D-Bus")
1841
server_settings[u"use_dbus"] = False
1842
tcp_server.use_dbus = False
1843
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1844
service = AvahiService(name = server_settings[u"servicename"],
1845
servicetype = u"_mandos._tcp",
1846
protocol = protocol, bus = bus)
1847
if server_settings["interface"]:
1848
service.interface = (if_nametoindex
1849
(str(server_settings[u"interface"])))
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")
1852
# Close all input and output, do double fork, etc.
777
logger.removeHandler(console)
1855
global multiprocessing_manager
1856
multiprocessing_manager = multiprocessing.Manager()
1858
client_class = Client
1860
client_class = functools.partial(ClientDBus, bus = bus)
1861
def client_config_items(config, section):
1862
special_settings = {
1863
"approved_by_default":
1864
lambda: config.getboolean(section,
1865
"approved_by_default"),
1867
for name, value in config.items(section):
1869
yield (name, special_settings[name]())
1873
tcp_server.clients.update(set(
1874
client_class(name = section,
1875
config= dict(client_config_items(
1876
client_config, section)))
1877
for section in client_config.sections()))
1878
if not tcp_server.clients:
1879
logger.warning(u"No clients defined")
780
pidfilename = "/var/run/mandos/mandos.pid"
783
pidfile = open(pidfilename, "w")
784
pidfile.write(str(pid) + "\n")
788
logger.error(u"Could not write %s file with PID %d",
789
pidfilename, os.getpid())
792
"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
805
atexit.register(cleanup)
1885
pidfile.write(str(pid) + "\n")
1888
logger.error(u"Could not write to file %r with PID %d",
1891
# "pidfile" was never created
1895
808
signal.signal(signal.SIGINT, signal.SIG_IGN)
1897
809
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1898
810
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1901
class MandosDBusService(dbus.service.Object):
1902
"""A D-Bus proxy object"""
1904
dbus.service.Object.__init__(self, bus, u"/")
1905
_interface = u"se.bsnet.fukt.Mandos"
1907
@dbus.service.signal(_interface, signature=u"o")
1908
def ClientAdded(self, objpath):
1912
@dbus.service.signal(_interface, signature=u"ss")
1913
def ClientNotFound(self, fingerprint, address):
1917
@dbus.service.signal(_interface, signature=u"os")
1918
def ClientRemoved(self, objpath, name):
1922
@dbus.service.method(_interface, out_signature=u"ao")
1923
def GetAllClients(self):
1925
return dbus.Array(c.dbus_object_path
1926
for c in tcp_server.clients)
1928
@dbus.service.method(_interface,
1929
out_signature=u"a{oa{sv}}")
1930
def GetAllClientsWithProperties(self):
1932
return dbus.Dictionary(
1933
((c.dbus_object_path, c.GetAll(u""))
1934
for c in tcp_server.clients),
1935
signature=u"oa{sv}")
1937
@dbus.service.method(_interface, in_signature=u"o")
1938
def RemoveClient(self, object_path):
1940
for c in tcp_server.clients:
1941
if c.dbus_object_path == object_path:
1942
tcp_server.clients.remove(c)
1943
c.remove_from_connection()
1944
# Don't signal anything except ClientRemoved
1945
c.disable(quiet=True)
1947
self.ClientRemoved(object_path, c.name)
1949
raise KeyError(object_path)
1953
mandos_dbus_service = MandosDBusService()
1956
"Cleanup function; run on exit"
1959
while tcp_server.clients:
1960
client = tcp_server.clients.pop()
1962
client.remove_from_connection()
1963
client.disable_hook = None
1964
# Don't signal anything except ClientRemoved
1965
client.disable(quiet=True)
1968
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
1971
atexit.register(cleanup)
1973
for client in tcp_server.clients:
1976
mandos_dbus_service.ClientAdded(client.dbus_object_path)
1980
tcp_server.server_activate()
812
for client in clients:
815
tcp_server = IPv6_TCPServer((server_settings["address"],
816
server_settings["port"]),
818
settings=server_settings,
1982
820
# Find out what port we got
1983
821
service.port = tcp_server.socket.getsockname()[1]
1985
logger.info(u"Now listening on address %r, port %d,"
1986
" flowinfo %d, scope_id %d"
1987
% tcp_server.socket.getsockname())
1989
logger.info(u"Now listening on address %r, port %d"
1990
% tcp_server.socket.getsockname())
822
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
823
u" scope_id %d" % tcp_server.socket.getsockname())
1992
825
#service.interface = tcp_server.socket.getsockname()[3]
1995
828
# From the Avahi example code
829
server.connect_to_signal("StateChanged", server_state_changed)
831
server_state_changed(server.GetState())
1998
832
except dbus.exceptions.DBusException, error:
1999
833
logger.critical(u"DBusException: %s", error)
2002
835
# End of Avahi example code
2004
837
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2005
838
lambda *args, **kwargs:
2006
(tcp_server.handle_request
2007
(*args[2:], **kwargs) or True))
839
tcp_server.handle_request\
840
(*args[2:], **kwargs) or True)
2009
842
logger.debug(u"Starting main loop")
843
main_loop_started = True
2011
845
except AvahiError, error:
2012
logger.critical(u"AvahiError: %s", error)
846
logger.critical(u"AvahiError: %s" + unicode(error))
2015
848
except KeyboardInterrupt:
2018
logger.debug(u"Server received KeyboardInterrupt")
2019
logger.debug(u"Server exiting")
2020
# Must run before the D-Bus bus name gets deregistered
2023
852
if __name__ == '__main__':