104
126
max_renames: integer; maximum number of renames
105
127
rename_count: integer; counter so we only rename after collisions
106
128
a sensible number of times
129
group: D-Bus Entry Group
131
bus: dbus.SystemBus()
108
133
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
109
type = None, port = None, TXT = None, domain = "",
110
host = "", max_renames = 32768):
134
servicetype = None, port = None, TXT = None,
135
domain = u"", host = u"", max_renames = 32768,
136
protocol = avahi.PROTO_UNSPEC, bus = None):
111
137
self.interface = interface
139
self.type = servicetype
141
self.TXT = TXT if TXT is not None else []
119
142
self.domain = domain
121
144
self.rename_count = 0
145
self.max_renames = max_renames
146
self.protocol = protocol
147
self.group = None # our entry group
122
150
def rename(self):
123
151
"""Derived from the Avahi example code"""
124
152
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))
153
logger.critical(u"No suitable Zeroconf service name found"
154
u" after %i retries, exiting.",
156
raise AvahiServiceError(u"Too many renames")
157
self.name = self.server.GetAlternativeServiceName(self.name)
158
logger.info(u"Changing Zeroconf service name to %r ...",
160
syslogger.setFormatter(logging.Formatter
161
(u'Mandos (%s) [%%(process)d]:'
162
u' %%(levelname)s: %%(message)s'
135
166
self.rename_count += 1
136
167
def remove(self):
137
168
"""Derived from the Avahi example code"""
138
if group is not None:
169
if self.group is not None:
141
172
"""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
173
if self.group is None:
174
self.group = dbus.Interface(
175
self.bus.get_object(avahi.DBUS_NAME,
176
self.server.EntryGroupNew()),
177
avahi.DBUS_INTERFACE_ENTRY_GROUP)
178
self.group.connect_to_signal('StateChanged',
180
.entry_group_state_changed)
181
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
182
self.name, self.type)
183
self.group.AddService(
186
dbus.UInt32(0), # flags
187
self.name, self.type,
188
self.domain, self.host,
189
dbus.UInt16(self.port),
190
avahi.string_array_to_txt_array(self.TXT))
192
def entry_group_state_changed(self, state, error):
193
"""Derived from the Avahi example code"""
194
logger.debug(u"Avahi state change: %i", state)
196
if state == avahi.ENTRY_GROUP_ESTABLISHED:
197
logger.debug(u"Zeroconf service established.")
198
elif state == avahi.ENTRY_GROUP_COLLISION:
199
logger.warning(u"Zeroconf service name collision.")
201
elif state == avahi.ENTRY_GROUP_FAILURE:
202
logger.critical(u"Avahi: Error in group state changed %s",
204
raise AvahiGroupError(u"State changed: %s"
207
"""Derived from the Avahi example code"""
208
if self.group is not None:
211
def server_state_changed(self, state):
212
"""Derived from the Avahi example code"""
213
if state == avahi.SERVER_COLLISION:
214
logger.error(u"Zeroconf server name collision")
216
elif state == avahi.SERVER_RUNNING:
219
"""Derived from the Avahi example code"""
220
if self.server is None:
221
self.server = dbus.Interface(
222
self.bus.get_object(avahi.DBUS_NAME,
223
avahi.DBUS_PATH_SERVER),
224
avahi.DBUS_INTERFACE_SERVER)
225
self.server.connect_to_signal(u"StateChanged",
226
self.server_state_changed)
227
self.server_state_changed(self.server.GetState())
167
230
class Client(object):
168
231
"""A representation of a client host served by this server.
170
name: string; from the config file, used in log messages
234
name: string; from the config file, used in log messages and
171
236
fingerprint: string (40 or 32 hexadecimal digits); used to
172
237
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.
238
secret: bytestring; sent verbatim (over TLS) to client
239
host: string; available for use by the checker command
240
created: datetime.datetime(); (UTC) object creation
241
last_enabled: datetime.datetime(); (UTC)
243
last_checked_ok: datetime.datetime(); (UTC) or None
244
timeout: datetime.timedelta(); How long from last_checked_ok
245
until this client is invalid
246
interval: datetime.timedelta(); How often to start a new checker
247
disable_hook: If set, called by disable() as disable_hook(self)
248
checker: subprocess.Popen(); a running checker process used
249
to see if the client lives.
250
'None' if no process is running.
184
251
checker_initiator_tag: a gobject event source tag, or None
185
stop_initiator_tag: - '' -
252
disable_initiator_tag: - '' -
186
253
checker_callback_tag: - '' -
187
254
checker_command: string; External command which is run to check if
188
255
client lives. %() expansions are done at
189
256
runtime with vars(self) as dict, so that for
190
257
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: - '' -
258
current_checker_command: string; current running checker_command
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={}):
262
def _timedelta_to_milliseconds(td):
263
"Convert a datetime.timedelta() to milliseconds"
264
return ((td.days * 24 * 60 * 60 * 1000)
265
+ (td.seconds * 1000)
266
+ (td.microseconds // 1000))
268
def timeout_milliseconds(self):
269
"Return the 'timeout' attribute in milliseconds"
270
return self._timedelta_to_milliseconds(self.timeout)
272
def interval_milliseconds(self):
273
"Return the 'interval' attribute in milliseconds"
274
return self._timedelta_to_milliseconds(self.interval)
276
def __init__(self, name = None, disable_hook=None, config=None):
221
277
"""Note: the 'checker' key in 'config' sets the
222
278
'checker_command' attribute and *not* the 'checker'
225
283
logger.debug(u"Creating client %r", self.name)
226
284
# Uppercase and remove spaces from fingerprint for later
227
285
# comparison purposes with return value from the fingerprint()
229
self.fingerprint = config["fingerprint"].upper()\
287
self.fingerprint = (config[u"fingerprint"].upper()
231
289
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()
290
if u"secret" in config:
291
self.secret = config[u"secret"].decode(u"base64")
292
elif u"secfile" in config:
293
with closing(open(os.path.expanduser
295
(config[u"secfile"])),
297
self.secret = secfile.read()
239
299
raise TypeError(u"No secret or secfile for client %s"
241
self.host = config.get("host", "")
242
self.created = datetime.datetime.now()
301
self.host = config.get(u"host", u"")
302
self.created = datetime.datetime.utcnow()
304
self.last_enabled = None
243
305
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
306
self.timeout = string_to_delta(config[u"timeout"])
307
self.interval = string_to_delta(config[u"interval"])
308
self.disable_hook = disable_hook
247
309
self.checker = None
248
310
self.checker_initiator_tag = None
249
self.stop_initiator_tag = None
311
self.disable_initiator_tag = None
250
312
self.checker_callback_tag = None
251
self.check_command = config["checker"]
313
self.checker_command = config[u"checker"]
314
self.current_checker_command = None
315
self.last_connect = None
253
318
"""Start this client's checker and timeout hooks"""
319
if getattr(self, u"enabled", False):
322
self.last_enabled = datetime.datetime.utcnow()
254
323
# Schedule a new checker to be started an 'interval' from now,
255
324
# and every interval from then on.
256
self.checker_initiator_tag = gobject.timeout_add\
257
(self._interval_milliseconds,
325
self.checker_initiator_tag = (gobject.timeout_add
326
(self.interval_milliseconds(),
328
# Schedule a disable() when 'timeout' has passed
329
self.disable_initiator_tag = (gobject.timeout_add
330
(self.timeout_milliseconds(),
259
333
# Also start a new checker *right now*.
260
334
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)
336
def disable(self, quiet=True):
337
"""Disable this client."""
338
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):
341
logger.info(u"Disabling client %s", self.name)
342
if getattr(self, u"disable_initiator_tag", False):
343
gobject.source_remove(self.disable_initiator_tag)
344
self.disable_initiator_tag = None
345
if getattr(self, u"checker_initiator_tag", False):
279
346
gobject.source_remove(self.checker_initiator_tag)
280
347
self.checker_initiator_tag = None
281
348
self.stop_checker()
349
if self.disable_hook:
350
self.disable_hook(self)
284
352
# Do not run this again if called by a gobject.timeout_add
286
355
def __del__(self):
287
self.stop_hook = None
289
def checker_callback(self, pid, condition):
356
self.disable_hook = None
359
def checker_callback(self, pid, condition, command):
290
360
"""The checker has completed, so take appropriate actions."""
291
now = datetime.datetime.now()
292
361
self.checker_callback_tag = None
293
362
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):
363
if os.WIFEXITED(condition):
364
exitstatus = os.WEXITSTATUS(condition)
366
logger.info(u"Checker for %(name)s succeeded",
370
logger.info(u"Checker for %(name)s failed",
304
373
logger.warning(u"Checker for %(name)s crashed?",
307
logger.info(u"Checker for %(name)s failed",
376
def checked_ok(self):
377
"""Bump up the timeout for this client.
379
This should only be called when the client has been seen,
382
self.last_checked_ok = datetime.datetime.utcnow()
383
gobject.source_remove(self.disable_initiator_tag)
384
self.disable_initiator_tag = (gobject.timeout_add
385
(self.timeout_milliseconds(),
309
388
def start_checker(self):
310
389
"""Start a new checker subprocess if one is not running.
311
391
If a checker already exists, leave it running and do
313
393
# The reason for not killing a running checker is that if we
318
398
# checkers alone, the checker would have to take more time
319
399
# than 'timeout' for the client to be declared invalid, which
320
400
# is as it should be.
402
# If a checker exists, make sure it is not a zombie
404
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
405
except (AttributeError, OSError), error:
406
if (isinstance(error, OSError)
407
and error.errno != errno.ECHILD):
411
logger.warning(u"Checker was a zombie")
412
gobject.source_remove(self.checker_callback_tag)
413
self.checker_callback(pid, status,
414
self.current_checker_command)
415
# Start a new checker if needed
321
416
if self.checker is None:
323
# In case check_command has exactly one % operator
324
command = self.check_command % self.host
418
# In case checker_command has exactly one % operator
419
command = self.checker_command % self.host
325
420
except TypeError:
326
421
# Escape attributes for the shell
327
escaped_attrs = dict((key, re.escape(str(val)))
422
escaped_attrs = dict((key,
423
re.escape(unicode(str(val),
329
427
vars(self).iteritems())
331
command = self.check_command % escaped_attrs
429
command = self.checker_command % escaped_attrs
332
430
except TypeError, error:
333
431
logger.error(u'Could not format string "%s":'
334
u' %s', self.check_command, error)
432
u' %s', self.checker_command, error)
335
433
return True # Try again later
434
self.current_checker_command = command
337
436
logger.info(u"Starting checker %r for %s",
338
437
command, self.name)
438
# We don't need to redirect stdout and stderr, since
439
# in normal mode, that is already done by daemon(),
440
# and in debug mode we don't want to. (Stdin is
441
# always replaced by /dev/null.)
339
442
self.checker = subprocess.Popen(command,
342
self.checker_callback_tag = gobject.child_watch_add\
344
self.checker_callback)
345
except subprocess.OSError, error:
444
shell=True, cwd=u"/")
445
self.checker_callback_tag = (gobject.child_watch_add
447
self.checker_callback,
449
# The checker may have completed before the gobject
450
# watch was added. Check for this.
451
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
453
gobject.source_remove(self.checker_callback_tag)
454
self.checker_callback(pid, status, command)
455
except OSError, error:
346
456
logger.error(u"Failed to start subprocess: %s",
348
458
# Re-run this periodically if run by gobject.timeout_add
350
461
def stop_checker(self):
351
462
"""Force the checker process, if any, to stop."""
352
463
if self.checker_callback_tag:
353
464
gobject.source_remove(self.checker_callback_tag)
354
465
self.checker_callback_tag = None
355
if getattr(self, "checker", None) is None:
466
if getattr(self, u"checker", None) is None:
357
468
logger.debug(u"Stopping checker for %(name)s", vars(self))
359
470
os.kill(self.checker.pid, signal.SIGTERM)
361
472
#if self.checker.poll() is None:
362
473
# os.kill(self.checker.pid, signal.SIGKILL)
363
474
except OSError, error:
364
475
if error.errno != errno.ESRCH: # No such process
366
477
self.checker = None
367
479
def still_valid(self):
368
480
"""Has the timeout not yet passed for this client?"""
369
now = datetime.datetime.now()
481
if not getattr(self, u"enabled", False):
483
now = datetime.datetime.utcnow()
370
484
if self.last_checked_ok is None:
371
485
return now < (self.created + self.timeout)
373
487
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.
490
def dbus_service_property(dbus_interface, signature=u"v",
491
access=u"readwrite", byte_arrays=False):
492
"""Decorators for marking methods of a DBusObjectWithProperties to
493
become properties on the D-Bus.
495
The decorated method will be called with no arguments by "Get"
496
and with one argument by "Set".
498
The parameters, where they are supported, are the same as
499
dbus.service.method, except there is only "signature", since the
500
type from Get() and the type sent to Set() is the same.
503
func._dbus_is_property = True
504
func._dbus_interface = dbus_interface
505
func._dbus_signature = signature
506
func._dbus_access = access
507
func._dbus_name = func.__name__
508
if func._dbus_name.endswith(u"_dbus_property"):
509
func._dbus_name = func._dbus_name[:-14]
510
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
515
class DBusPropertyException(dbus.exceptions.DBusException):
516
"""A base class for D-Bus property-related exceptions
518
def __unicode__(self):
519
return unicode(str(self))
522
class DBusPropertyAccessException(DBusPropertyException):
523
"""A property's access permissions disallows an operation.
528
class DBusPropertyNotFound(DBusPropertyException):
529
"""An attempt was made to access a non-existing property.
534
class DBusObjectWithProperties(dbus.service.Object):
535
"""A D-Bus object with properties.
537
Classes inheriting from this can use the dbus_service_property
538
decorator to expose methods as D-Bus properties. It exposes the
539
standard Get(), Set(), and GetAll() methods on the D-Bus.
543
def _is_dbus_property(obj):
544
return getattr(obj, u"_dbus_is_property", False)
546
def _get_all_dbus_properties(self):
547
"""Returns a generator of (name, attribute) pairs
549
return ((prop._dbus_name, prop)
551
inspect.getmembers(self, self._is_dbus_property))
553
def _get_dbus_property(self, interface_name, property_name):
554
"""Returns a bound method if one exists which is a D-Bus
555
property with the specified name and interface.
557
for name in (property_name,
558
property_name + u"_dbus_property"):
559
prop = getattr(self, name, None)
561
or not self._is_dbus_property(prop)
562
or prop._dbus_name != property_name
563
or (interface_name and prop._dbus_interface
564
and interface_name != prop._dbus_interface)):
568
raise DBusPropertyNotFound(self.dbus_object_path + u":"
569
+ interface_name + u"."
572
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
574
def Get(self, interface_name, property_name):
575
"""Standard D-Bus property Get() method, see D-Bus standard.
577
prop = self._get_dbus_property(interface_name, property_name)
578
if prop._dbus_access == u"write":
579
raise DBusPropertyAccessException(property_name)
581
if not hasattr(value, u"variant_level"):
583
return type(value)(value, variant_level=value.variant_level+1)
585
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
586
def Set(self, interface_name, property_name, value):
587
"""Standard D-Bus property Set() method, see D-Bus standard.
589
prop = self._get_dbus_property(interface_name, property_name)
590
if prop._dbus_access == u"read":
591
raise DBusPropertyAccessException(property_name)
592
if prop._dbus_get_args_options[u"byte_arrays"]:
593
value = dbus.ByteArray(''.join(unichr(byte)
597
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
598
out_signature=u"a{sv}")
599
def GetAll(self, interface_name):
600
"""Standard D-Bus property GetAll() method, see D-Bus
603
Note: Will not include properties with access="write".
606
for name, prop in self._get_all_dbus_properties():
608
and interface_name != prop._dbus_interface):
609
# Interface non-empty but did not match
611
# Ignore write-only properties
612
if prop._dbus_access == u"write":
615
if not hasattr(value, u"variant_level"):
618
all[name] = type(value)(value, variant_level=
619
value.variant_level+1)
620
return dbus.Dictionary(all, signature=u"sv")
622
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
624
path_keyword='object_path',
625
connection_keyword='connection')
626
def Introspect(self, object_path, connection):
627
"""Standard D-Bus method, overloaded to insert property tags.
629
xmlstring = dbus.service.Object.Introspect(self, object_path,
632
document = xml.dom.minidom.parseString(xmlstring)
633
def make_tag(document, name, prop):
634
e = document.createElement(u"property")
635
e.setAttribute(u"name", name)
636
e.setAttribute(u"type", prop._dbus_signature)
637
e.setAttribute(u"access", prop._dbus_access)
639
for if_tag in document.getElementsByTagName(u"interface"):
640
for tag in (make_tag(document, name, prop)
642
in self._get_all_dbus_properties()
643
if prop._dbus_interface
644
== if_tag.getAttribute(u"name")):
645
if_tag.appendChild(tag)
646
# Add the names to the return values for the
647
# "org.freedesktop.DBus.Properties" methods
648
if (if_tag.getAttribute(u"name")
649
== u"org.freedesktop.DBus.Properties"):
650
for cn in if_tag.getElementsByTagName(u"method"):
651
if cn.getAttribute(u"name") == u"Get":
652
for arg in cn.getElementsByTagName(u"arg"):
653
if (arg.getAttribute(u"direction")
655
arg.setAttribute(u"name", u"value")
656
elif cn.getAttribute(u"name") == u"GetAll":
657
for arg in cn.getElementsByTagName(u"arg"):
658
if (arg.getAttribute(u"direction")
660
arg.setAttribute(u"name", u"props")
661
xmlstring = document.toxml(u"utf-8")
663
except (AttributeError, xml.dom.DOMException,
664
xml.parsers.expat.ExpatError), error:
665
logger.error(u"Failed to override Introspection method",
670
class ClientDBus(Client, DBusObjectWithProperties):
671
"""A Client class using D-Bus
674
dbus_object_path: dbus.ObjectPath
675
bus: dbus.SystemBus()
677
# dbus.service.Object doesn't use super(), so we can't either.
679
def __init__(self, bus = None, *args, **kwargs):
681
Client.__init__(self, *args, **kwargs)
682
# Only now, when this client is initialized, can it show up on
684
self.dbus_object_path = (dbus.ObjectPath
686
+ self.name.replace(u".", u"_")))
687
DBusObjectWithProperties.__init__(self, self.bus,
688
self.dbus_object_path)
691
def _datetime_to_dbus(dt, variant_level=0):
692
"""Convert a UTC datetime.datetime() to a D-Bus type."""
693
return dbus.String(dt.isoformat(),
694
variant_level=variant_level)
697
oldstate = getattr(self, u"enabled", False)
698
r = Client.enable(self)
699
if oldstate != self.enabled:
701
self.PropertyChanged(dbus.String(u"enabled"),
702
dbus.Boolean(True, variant_level=1))
703
self.PropertyChanged(
704
dbus.String(u"last_enabled"),
705
self._datetime_to_dbus(self.last_enabled,
709
def disable(self, quiet = False):
710
oldstate = getattr(self, u"enabled", False)
711
r = Client.disable(self, quiet=quiet)
712
if not quiet and oldstate != self.enabled:
714
self.PropertyChanged(dbus.String(u"enabled"),
715
dbus.Boolean(False, variant_level=1))
718
def __del__(self, *args, **kwargs):
720
self.remove_from_connection()
723
if hasattr(DBusObjectWithProperties, u"__del__"):
724
DBusObjectWithProperties.__del__(self, *args, **kwargs)
725
Client.__del__(self, *args, **kwargs)
727
def checker_callback(self, pid, condition, command,
729
self.checker_callback_tag = None
732
self.PropertyChanged(dbus.String(u"checker_running"),
733
dbus.Boolean(False, variant_level=1))
734
if os.WIFEXITED(condition):
735
exitstatus = os.WEXITSTATUS(condition)
737
self.CheckerCompleted(dbus.Int16(exitstatus),
738
dbus.Int64(condition),
739
dbus.String(command))
742
self.CheckerCompleted(dbus.Int16(-1),
743
dbus.Int64(condition),
744
dbus.String(command))
746
return Client.checker_callback(self, pid, condition, command,
749
def checked_ok(self, *args, **kwargs):
750
r = Client.checked_ok(self, *args, **kwargs)
752
self.PropertyChanged(
753
dbus.String(u"last_checked_ok"),
754
(self._datetime_to_dbus(self.last_checked_ok,
758
def start_checker(self, *args, **kwargs):
759
old_checker = self.checker
760
if self.checker is not None:
761
old_checker_pid = self.checker.pid
763
old_checker_pid = None
764
r = Client.start_checker(self, *args, **kwargs)
765
# Only if new checker process was started
766
if (self.checker is not None
767
and old_checker_pid != self.checker.pid):
769
self.CheckerStarted(self.current_checker_command)
770
self.PropertyChanged(
771
dbus.String(u"checker_running"),
772
dbus.Boolean(True, variant_level=1))
775
def stop_checker(self, *args, **kwargs):
776
old_checker = getattr(self, u"checker", None)
777
r = Client.stop_checker(self, *args, **kwargs)
778
if (old_checker is not None
779
and getattr(self, u"checker", None) is None):
780
self.PropertyChanged(dbus.String(u"checker_running"),
781
dbus.Boolean(False, variant_level=1))
784
## D-Bus methods & signals
785
_interface = u"se.bsnet.fukt.Mandos.Client"
788
@dbus.service.method(_interface)
790
return self.checked_ok()
792
# CheckerCompleted - signal
793
@dbus.service.signal(_interface, signature=u"nxs")
794
def CheckerCompleted(self, exitcode, waitstatus, command):
798
# CheckerStarted - signal
799
@dbus.service.signal(_interface, signature=u"s")
800
def CheckerStarted(self, command):
804
# PropertyChanged - signal
805
@dbus.service.signal(_interface, signature=u"sv")
806
def PropertyChanged(self, property, value):
811
@dbus.service.signal(_interface)
817
@dbus.service.signal(_interface)
823
@dbus.service.method(_interface)
828
# StartChecker - method
829
@dbus.service.method(_interface)
830
def StartChecker(self):
835
@dbus.service.method(_interface)
840
# StopChecker - method
841
@dbus.service.method(_interface)
842
def StopChecker(self):
846
@dbus_service_property(_interface, signature=u"s", access=u"read")
847
def name_dbus_property(self):
848
return dbus.String(self.name)
850
# fingerprint - property
851
@dbus_service_property(_interface, signature=u"s", access=u"read")
852
def fingerprint_dbus_property(self):
853
return dbus.String(self.fingerprint)
856
@dbus_service_property(_interface, signature=u"s",
858
def host_dbus_property(self, value=None):
859
if value is None: # get
860
return dbus.String(self.host)
863
self.PropertyChanged(dbus.String(u"host"),
864
dbus.String(value, variant_level=1))
867
@dbus_service_property(_interface, signature=u"s", access=u"read")
868
def created_dbus_property(self):
869
return dbus.String(self._datetime_to_dbus(self.created))
871
# last_enabled - property
872
@dbus_service_property(_interface, signature=u"s", access=u"read")
873
def last_enabled_dbus_property(self):
874
if self.last_enabled is None:
875
return dbus.String(u"")
876
return dbus.String(self._datetime_to_dbus(self.last_enabled))
879
@dbus_service_property(_interface, signature=u"b",
881
def enabled_dbus_property(self, value=None):
882
if value is None: # get
883
return dbus.Boolean(self.enabled)
889
# last_checked_ok - property
890
@dbus_service_property(_interface, signature=u"s",
892
def last_checked_ok_dbus_property(self, value=None):
893
if value is not None:
896
if self.last_checked_ok is None:
897
return dbus.String(u"")
898
return dbus.String(self._datetime_to_dbus(self
902
@dbus_service_property(_interface, signature=u"t",
904
def timeout_dbus_property(self, value=None):
905
if value is None: # get
906
return dbus.UInt64(self.timeout_milliseconds())
907
self.timeout = datetime.timedelta(0, 0, 0, value)
909
self.PropertyChanged(dbus.String(u"timeout"),
910
dbus.UInt64(value, variant_level=1))
911
if getattr(self, u"disable_initiator_tag", None) is None:
914
gobject.source_remove(self.disable_initiator_tag)
915
self.disable_initiator_tag = None
917
_timedelta_to_milliseconds((self
923
# The timeout has passed
926
self.disable_initiator_tag = (gobject.timeout_add
927
(time_to_die, self.disable))
929
# interval - property
930
@dbus_service_property(_interface, signature=u"t",
932
def interval_dbus_property(self, value=None):
933
if value is None: # get
934
return dbus.UInt64(self.interval_milliseconds())
935
self.interval = datetime.timedelta(0, 0, 0, value)
937
self.PropertyChanged(dbus.String(u"interval"),
938
dbus.UInt64(value, variant_level=1))
939
if getattr(self, u"checker_initiator_tag", None) is None:
941
# Reschedule checker run
942
gobject.source_remove(self.checker_initiator_tag)
943
self.checker_initiator_tag = (gobject.timeout_add
944
(value, self.start_checker))
945
self.start_checker() # Start one now, too
948
@dbus_service_property(_interface, signature=u"s",
950
def checker_dbus_property(self, value=None):
951
if value is None: # get
952
return dbus.String(self.checker_command)
953
self.checker_command = value
955
self.PropertyChanged(dbus.String(u"checker"),
956
dbus.String(self.checker_command,
959
# checker_running - property
960
@dbus_service_property(_interface, signature=u"b",
962
def checker_running_dbus_property(self, value=None):
963
if value is None: # get
964
return dbus.Boolean(self.checker is not None)
970
# object_path - property
971
@dbus_service_property(_interface, signature=u"o", access=u"read")
972
def object_path_dbus_property(self):
973
return self.dbus_object_path # is already a dbus.ObjectPath
976
@dbus_service_property(_interface, signature=u"ay",
977
access=u"write", byte_arrays=True)
978
def secret_dbus_property(self, value):
979
self.secret = str(value)
984
class ClientHandler(socketserver.BaseRequestHandler, object):
985
"""A class to handle client connections.
987
Instantiated once for each connection to handle it.
426
988
Note: This will run in its own forked process."""
428
990
def handle(self):
429
991
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.
992
unicode(self.client_address))
993
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
994
# Open IPC pipe to parent process
995
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
996
session = (gnutls.connection
997
.ClientSession(self.request,
1001
line = self.request.makefile().readline()
1002
logger.debug(u"Protocol version: %r", line)
1004
if int(line.strip().split()[0]) > 1:
1006
except (ValueError, IndexError, RuntimeError), error:
1007
logger.error(u"Unknown protocol version: %s", error)
1010
# Note: gnutls.connection.X509Credentials is really a
1011
# generic GnuTLS certificate credentials object so long as
1012
# no X.509 keys are added to it. Therefore, we can use it
1013
# here despite using OpenPGP certificates.
1015
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1016
# u"+AES-256-CBC", u"+SHA1",
1017
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1019
# Use a fallback default, since this MUST be set.
1020
priority = self.server.gnutls_priority
1021
if priority is None:
1022
priority = u"NORMAL"
1023
(gnutls.library.functions
1024
.gnutls_priority_set_direct(session._c_object,
1029
except gnutls.errors.GNUTLSError, error:
1030
logger.warning(u"Handshake failed: %s", error)
1031
# Do not run session.bye() here: the session is not
1032
# established. Just abandon the request.
1034
logger.debug(u"Handshake succeeded")
1036
fpr = self.fingerprint(self.peer_certificate(session))
1037
except (TypeError, gnutls.errors.GNUTLSError), error:
1038
logger.warning(u"Bad certificate: %s", error)
1041
logger.debug(u"Fingerprint: %s", fpr)
1043
for c in self.server.clients:
1044
if c.fingerprint == fpr:
1048
ipc.write(u"NOTFOUND %s %s\n"
1049
% (fpr, unicode(self.client_address)))
1052
# Have to check if client.still_valid(), since it is
1053
# possible that the client timed out while establishing
1054
# the GnuTLS session.
1055
if not client.still_valid():
1056
ipc.write(u"INVALID %s\n" % client.name)
1059
ipc.write(u"SENDING %s\n" % client.name)
1061
while sent_size < len(client.secret):
1062
sent = session.send(client.secret[sent_size:])
1063
logger.debug(u"Sent: %d, remaining: %d",
1064
sent, len(client.secret)
1065
- (sent_size + sent))
1070
def peer_certificate(session):
1071
"Return the peer's OpenPGP certificate as a bytestring"
1072
# If not an OpenPGP certificate...
1073
if (gnutls.library.functions
1074
.gnutls_certificate_type_get(session._c_object)
1075
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1076
# ...do the normal thing
1077
return session.peer_certificate
1078
list_size = ctypes.c_uint(1)
1079
cert_list = (gnutls.library.functions
1080
.gnutls_certificate_get_peers
1081
(session._c_object, ctypes.byref(list_size)))
1082
if not bool(cert_list) and list_size.value != 0:
1083
raise gnutls.errors.GNUTLSError(u"error getting peer"
1085
if list_size.value == 0:
1088
return ctypes.string_at(cert.data, cert.size)
1091
def fingerprint(openpgp):
1092
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1093
# New GnuTLS "datum" with the OpenPGP public key
1094
datum = (gnutls.library.types
1095
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1098
ctypes.c_uint(len(openpgp))))
1099
# New empty GnuTLS certificate
1100
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1101
(gnutls.library.functions
1102
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1103
# Import the OpenPGP public key into the certificate
1104
(gnutls.library.functions
1105
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1106
gnutls.library.constants
1107
.GNUTLS_OPENPGP_FMT_RAW))
1108
# Verify the self signature in the key
1109
crtverify = ctypes.c_uint()
1110
(gnutls.library.functions
1111
.gnutls_openpgp_crt_verify_self(crt, 0,
1112
ctypes.byref(crtverify)))
1113
if crtverify.value != 0:
1114
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1115
raise (gnutls.errors.CertificateSecurityError
1117
# New buffer for the fingerprint
1118
buf = ctypes.create_string_buffer(20)
1119
buf_len = ctypes.c_size_t()
1120
# Get the fingerprint from the certificate into the buffer
1121
(gnutls.library.functions
1122
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1123
ctypes.byref(buf_len)))
1124
# Deinit the certificate
1125
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1126
# Convert the buffer to a Python bytestring
1127
fpr = ctypes.string_at(buf, buf_len.value)
1128
# Convert the bytestring to hexadecimal notation
1129
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1133
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1134
"""Like socketserver.ForkingMixIn, but also pass a pipe."""
1135
def process_request(self, request, client_address):
1136
"""Overrides and wraps the original process_request().
1138
This function creates a new pipe in self.pipe
1140
self.pipe = os.pipe()
1141
super(ForkingMixInWithPipe,
1142
self).process_request(request, client_address)
1143
os.close(self.pipe[1]) # close write end
1144
self.add_pipe(self.pipe[0])
1145
def add_pipe(self, pipe):
1146
"""Dummy function; override as necessary"""
1150
class IPv6_TCPServer(ForkingMixInWithPipe,
1151
socketserver.TCPServer, object):
1152
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
503
settings: Server settings
504
clients: Set() of Client objects
1155
enabled: Boolean; whether this server is activated yet
1156
interface: None or a network interface name (string)
1157
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)
1159
def __init__(self, server_address, RequestHandlerClass,
1160
interface=None, use_ipv6=True):
1161
self.interface = interface
1163
self.address_family = socket.AF_INET6
1164
socketserver.TCPServer.__init__(self, server_address,
1165
RequestHandlerClass)
515
1166
def server_bind(self):
516
1167
"""This overrides the normal server_bind() function
517
1168
to bind to an interface if one was specified, and also NOT to
518
1169
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"])
1170
if self.interface is not None:
1171
if SO_BINDTODEVICE is None:
1172
logger.error(u"SO_BINDTODEVICE does not exist;"
1173
u" cannot bind to interface %s",
1177
self.socket.setsockopt(socket.SOL_SOCKET,
1181
except socket.error, error:
1182
if error[0] == errno.EPERM:
1183
logger.error(u"No permission to"
1184
u" bind to interface %s",
1186
elif error[0] == errno.ENOPROTOOPT:
1187
logger.error(u"SO_BINDTODEVICE not available;"
1188
u" cannot bind to interface %s",
533
1192
# Only bind(2) the socket if we really need to.
534
1193
if self.server_address[0] or self.server_address[1]:
535
1194
if not self.server_address[0]:
537
self.server_address = (in6addr_any,
1195
if self.address_family == socket.AF_INET6:
1196
any_address = u"::" # in6addr_any
1198
any_address = socket.INADDR_ANY
1199
self.server_address = (any_address,
538
1200
self.server_address[1])
539
1201
elif not self.server_address[1]:
540
1202
self.server_address = (self.server_address[0],
542
# if self.settings["interface"]:
1204
# if self.interface:
543
1205
# self.server_address = (self.server_address[0],
546
1208
# if_nametoindex
549
return super(type(self), self).server_bind()
1210
return socketserver.TCPServer.server_bind(self)
1213
class MandosServer(IPv6_TCPServer):
1217
clients: set of Client objects
1218
gnutls_priority GnuTLS priority string
1219
use_dbus: Boolean; to emit D-Bus signals or not
1221
Assumes a gobject.MainLoop event loop.
1223
def __init__(self, server_address, RequestHandlerClass,
1224
interface=None, use_ipv6=True, clients=None,
1225
gnutls_priority=None, use_dbus=True):
1226
self.enabled = False
1227
self.clients = clients
1228
if self.clients is None:
1229
self.clients = set()
1230
self.use_dbus = use_dbus
1231
self.gnutls_priority = gnutls_priority
1232
IPv6_TCPServer.__init__(self, server_address,
1233
RequestHandlerClass,
1234
interface = interface,
1235
use_ipv6 = use_ipv6)
1236
def server_activate(self):
1238
return socketserver.TCPServer.server_activate(self)
1241
def add_pipe(self, pipe):
1242
# Call "handle_ipc" for both data and EOF events
1243
gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1245
def handle_ipc(self, source, condition, file_objects={}):
1247
gobject.IO_IN: u"IN", # There is data to read.
1248
gobject.IO_OUT: u"OUT", # Data can be written (without
1250
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1251
gobject.IO_ERR: u"ERR", # Error condition.
1252
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1253
# broken, usually for pipes and
1256
conditions_string = ' | '.join(name
1258
condition_names.iteritems()
1259
if cond & condition)
1260
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1263
# Turn the pipe file descriptor into a Python file object
1264
if source not in file_objects:
1265
file_objects[source] = os.fdopen(source, u"r", 1)
1267
# Read a line from the file object
1268
cmdline = file_objects[source].readline()
1269
if not cmdline: # Empty line means end of file
1270
# close the IPC pipe
1271
file_objects[source].close()
1272
del file_objects[source]
1274
# Stop calling this function
1277
logger.debug(u"IPC command: %r", cmdline)
1279
# Parse and act on command
1280
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1282
if cmd == u"NOTFOUND":
1283
fpr, address = args.split(None, 1)
1284
logger.warning(u"Client not found for fingerprint: %s, ad"
1285
u"dress: %s", fpr, address)
1288
mandos_dbus_service.ClientNotFound(fpr, address)
1289
elif cmd == u"INVALID":
1290
for client in self.clients:
1291
if client.name == args:
1292
logger.warning(u"Client %s is invalid", args)
1298
logger.error(u"Unknown client %s is invalid", args)
1299
elif cmd == u"SENDING":
1300
for client in self.clients:
1301
if client.name == args:
1302
logger.info(u"Sending secret to %s", client.name)
1309
logger.error(u"Sending secret to unknown client %s",
1312
logger.error(u"Unknown IPC command: %r", cmdline)
1314
# Keep calling this function
552
1318
def string_to_delta(interval):
553
1319
"""Parse a string and return a datetime.timedelta
555
>>> string_to_delta('7d')
1321
>>> string_to_delta(u'7d')
556
1322
datetime.timedelta(7)
557
>>> string_to_delta('60s')
1323
>>> string_to_delta(u'60s')
558
1324
datetime.timedelta(0, 60)
559
>>> string_to_delta('60m')
1325
>>> string_to_delta(u'60m')
560
1326
datetime.timedelta(0, 3600)
561
>>> string_to_delta('24h')
1327
>>> string_to_delta(u'24h')
562
1328
datetime.timedelta(1)
563
1329
>>> string_to_delta(u'1w')
564
1330
datetime.timedelta(7)
1331
>>> string_to_delta(u'5m 30s')
1332
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))
1334
timevalue = datetime.timedelta(0)
1335
for s in interval.split():
1337
suffix = unicode(s[-1])
1340
delta = datetime.timedelta(value)
1341
elif suffix == u"s":
1342
delta = datetime.timedelta(0, value)
1343
elif suffix == u"m":
1344
delta = datetime.timedelta(0, 0, 0, 0, value)
1345
elif suffix == u"h":
1346
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1347
elif suffix == u"w":
1348
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1350
raise ValueError(u"Unknown suffix %r" % suffix)
1351
except (ValueError, IndexError), e:
1352
raise ValueError(e.message)
609
1357
def if_nametoindex(interface):
610
"""Call the C function if_nametoindex(), or equivalent"""
1358
"""Call the C function if_nametoindex(), or equivalent
1360
Note: This function cannot accept a unicode string."""
611
1361
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
1363
if_nametoindex = (ctypes.cdll.LoadLibrary
1364
(ctypes.util.find_library(u"c"))
617
1366
except (OSError, AttributeError):
618
if "struct" not in sys.modules:
620
if "fcntl" not in sys.modules:
1367
logger.warning(u"Doing if_nametoindex the hard way")
622
1368
def if_nametoindex(interface):
623
1369
"Get an interface index the hard way, i.e. using fcntl()"
624
1370
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]
1371
with closing(socket.socket()) as s:
1372
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1373
struct.pack(str(u"16s16x"),
1375
interface_index = struct.unpack(str(u"I"),
630
1377
return interface_index
631
1378
return if_nametoindex(interface)
634
1381
def daemon(nochdir = False, noclose = False):
635
1382
"""See daemon(3). Standard BSD Unix function.
636
1384
This should really exist as os.daemon, but it doesn't (yet)."""
688
1443
# Default values for config file for server-global settings
689
server_defaults = { "interface": "",
694
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
695
"servicename": "Mandos",
1444
server_defaults = { u"interface": u"",
1449
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1450
u"servicename": u"Mandos",
1451
u"use_dbus": u"True",
1452
u"use_ipv6": u"True",
698
1455
# Parse config file for server-global settings
699
server_config = ConfigParser.SafeConfigParser(server_defaults)
1456
server_config = configparser.SafeConfigParser(server_defaults)
700
1457
del server_defaults
701
server_config.read(os.path.join(options.configdir, "mandos.conf"))
702
server_section = "server"
1458
server_config.read(os.path.join(options.configdir,
703
1460
# 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")
1461
server_settings = server_config.defaults()
1462
# Use the appropriate methods on the non-string config options
1463
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1464
server_settings[option] = server_config.getboolean(u"DEFAULT",
1466
if server_settings["port"]:
1467
server_settings["port"] = server_config.getint(u"DEFAULT",
708
1469
del server_config
710
1471
# Override the settings from the config file with command line
711
1472
# options, if set.
712
for option in ("interface", "address", "port", "debug",
713
"priority", "servicename", "configdir"):
1473
for option in (u"interface", u"address", u"port", u"debug",
1474
u"priority", u"servicename", u"configdir",
1475
u"use_dbus", u"use_ipv6"):
714
1476
value = getattr(options, option)
715
1477
if value is not None:
716
1478
server_settings[option] = value
1480
# Force all strings to be unicode
1481
for option in server_settings.keys():
1482
if type(server_settings[option]) is str:
1483
server_settings[option] = unicode(server_settings[option])
718
1484
# Now we have our good server settings in "server_settings"
720
debug = server_settings["debug"]
1486
##################################################################
1489
debug = server_settings[u"debug"]
1490
use_dbus = server_settings[u"use_dbus"]
1491
use_ipv6 = server_settings[u"use_ipv6"]
723
1494
syslogger.setLevel(logging.WARNING)
724
1495
console.setLevel(logging.WARNING)
726
if server_settings["servicename"] != "Mandos":
727
syslogger.setFormatter(logging.Formatter\
728
('Mandos (%s): %%(levelname)s:'
730
% server_settings["servicename"]))
1497
if server_settings[u"servicename"] != u"Mandos":
1498
syslogger.setFormatter(logging.Formatter
1499
(u'Mandos (%s) [%%(process)d]:'
1500
u' %%(levelname)s: %%(message)s'
1501
% server_settings[u"servicename"]))
732
1503
# Parse config file with clients
733
client_defaults = { "timeout": "1h",
735
"checker": "fping -q -- %%(host)s",
1504
client_defaults = { u"timeout": u"1h",
1506
u"checker": u"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"])
1509
client_config = configparser.SafeConfigParser(client_defaults)
1510
client_config.read(os.path.join(server_settings[u"configdir"],
1513
global mandos_dbus_service
1514
mandos_dbus_service = None
1516
tcp_server = MandosServer((server_settings[u"address"],
1517
server_settings[u"port"]),
1519
interface=server_settings[u"interface"],
1522
server_settings[u"priority"],
1524
pidfilename = u"/var/run/mandos.pid"
1526
pidfile = open(pidfilename, u"w")
1528
logger.error(u"Could not open file %r", pidfilename)
1531
uid = pwd.getpwnam(u"_mandos").pw_uid
1532
gid = pwd.getpwnam(u"_mandos").pw_gid
1535
uid = pwd.getpwnam(u"mandos").pw_uid
1536
gid = pwd.getpwnam(u"mandos").pw_gid
1539
uid = pwd.getpwnam(u"nobody").pw_uid
1540
gid = pwd.getpwnam(u"nobody").pw_gid
1547
except OSError, error:
1548
if error[0] != errno.EPERM:
1551
# Enable all possible GnuTLS debugging
1553
# "Use a log level over 10 to enable all debugging options."
1555
gnutls.library.functions.gnutls_global_set_log_level(11)
1557
@gnutls.library.types.gnutls_log_func
1558
def debug_gnutls(level, string):
1559
logger.debug(u"GnuTLS: %s", string[:-1])
1561
(gnutls.library.functions
1562
.gnutls_global_set_log_function(debug_gnutls))
747
1564
global main_loop
750
1565
# From the Avahi example code
751
1566
DBusGMainLoop(set_as_default=True )
752
1567
main_loop = gobject.MainLoop()
753
1568
bus = dbus.SystemBus()
754
server = dbus.Interface(
755
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
756
avahi.DBUS_INTERFACE_SERVER )
757
1569
# 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")
1572
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1573
bus, do_not_queue=True)
1574
except dbus.exceptions.NameExistsException, e:
1575
logger.error(unicode(e) + u", disabling D-Bus")
1577
server_settings[u"use_dbus"] = False
1578
tcp_server.use_dbus = False
1579
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1580
service = AvahiService(name = server_settings[u"servicename"],
1581
servicetype = u"_mandos._tcp",
1582
protocol = protocol, bus = bus)
1583
if server_settings["interface"]:
1584
service.interface = (if_nametoindex
1585
(str(server_settings[u"interface"])))
1587
client_class = Client
1589
client_class = functools.partial(ClientDBus, bus = bus)
1590
tcp_server.clients.update(set(
1591
client_class(name = section,
1592
config= dict(client_config.items(section)))
1593
for section in client_config.sections()))
1594
if not tcp_server.clients:
1595
logger.warning(u"No clients defined")
1598
# Redirect stdin so all checkers get /dev/null
1599
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1600
os.dup2(null, sys.stdin.fileno())
1604
# No console logging
776
1605
logger.removeHandler(console)
1606
# Close all input and output, do double fork, etc.
779
pidfilename = "/var/run/mandos/mandos.pid"
782
pidfile = open(pidfilename, "w")
783
pidfile.write(str(pid) + "\n")
1610
with closing(pidfile):
1612
pidfile.write(str(pid) + "\n")
787
logger.error(u"Could not write %s file with PID %d",
788
pidfilename, os.getpid())
1615
logger.error(u"Could not write to file %r with PID %d",
1618
# "pidfile" was never created
1623
signal.signal(signal.SIGINT, signal.SIG_IGN)
1624
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1625
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1628
class MandosDBusService(dbus.service.Object):
1629
"""A D-Bus proxy object"""
1631
dbus.service.Object.__init__(self, bus, u"/")
1632
_interface = u"se.bsnet.fukt.Mandos"
1634
@dbus.service.signal(_interface, signature=u"oa{sv}")
1635
def ClientAdded(self, objpath, properties):
1639
@dbus.service.signal(_interface, signature=u"ss")
1640
def ClientNotFound(self, fingerprint, address):
1644
@dbus.service.signal(_interface, signature=u"os")
1645
def ClientRemoved(self, objpath, name):
1649
@dbus.service.method(_interface, out_signature=u"ao")
1650
def GetAllClients(self):
1652
return dbus.Array(c.dbus_object_path
1653
for c in tcp_server.clients)
1655
@dbus.service.method(_interface,
1656
out_signature=u"a{oa{sv}}")
1657
def GetAllClientsWithProperties(self):
1659
return dbus.Dictionary(
1660
((c.dbus_object_path, c.GetAll(u""))
1661
for c in tcp_server.clients),
1662
signature=u"oa{sv}")
1664
@dbus.service.method(_interface, in_signature=u"o")
1665
def RemoveClient(self, object_path):
1667
for c in tcp_server.clients:
1668
if c.dbus_object_path == object_path:
1669
tcp_server.clients.remove(c)
1670
c.remove_from_connection()
1671
# Don't signal anything except ClientRemoved
1672
c.disable(quiet=True)
1674
self.ClientRemoved(object_path, c.name)
1676
raise KeyError(object_path)
1680
mandos_dbus_service = MandosDBusService()
791
1683
"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
1686
while tcp_server.clients:
1687
client = tcp_server.clients.pop()
1689
client.remove_from_connection()
1690
client.disable_hook = None
1691
# Don't signal anything except ClientRemoved
1692
client.disable(quiet=True)
1695
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
804
1698
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,
1700
for client in tcp_server.clients:
1703
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1708
tcp_server.server_activate()
819
1710
# Find out what port we got
820
1711
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())
1713
logger.info(u"Now listening on address %r, port %d,"
1714
" flowinfo %d, scope_id %d"
1715
% tcp_server.socket.getsockname())
1717
logger.info(u"Now listening on address %r, port %d"
1718
% tcp_server.socket.getsockname())
824
1720
#service.interface = tcp_server.socket.getsockname()[3]
827
1723
# From the Avahi example code
828
server.connect_to_signal("StateChanged", server_state_changed)
830
server_state_changed(server.GetState())
831
1726
except dbus.exceptions.DBusException, error:
832
1727
logger.critical(u"DBusException: %s", error)
834
1730
# End of Avahi example code
836
1732
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
837
1733
lambda *args, **kwargs:
838
tcp_server.handle_request\
839
(*args[2:], **kwargs) or True)
1734
(tcp_server.handle_request
1735
(*args[2:], **kwargs) or True))
841
1737
logger.debug(u"Starting main loop")
842
main_loop_started = True
844
1739
except AvahiError, error:
845
logger.critical(u"AvahiError: %s" + unicode(error))
1740
logger.critical(u"AvahiError: %s", error)
847
1743
except KeyboardInterrupt:
1746
logger.debug(u"Server received KeyboardInterrupt")
1747
logger.debug(u"Server exiting")
1748
# Must run before the D-Bus bus name gets deregistered
851
1751
if __name__ == '__main__':