44
44
import gnutls.library.functions
45
45
import gnutls.library.constants
46
46
import gnutls.library.types
47
import ConfigParser as configparser
56
57
import logging.handlers
58
from contextlib import closing
67
62
from dbus.mainloop.glib import DBusGMainLoop
70
import xml.dom.minidom
74
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
75
except AttributeError:
77
from IN import SO_BINDTODEVICE
79
SO_BINDTODEVICE = None
84
logger = logging.Logger(u'mandos')
85
syslogger = (logging.handlers.SysLogHandler
86
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
87
address = "/dev/log"))
88
syslogger.setFormatter(logging.Formatter
89
(u'Mandos [%(process)d]: %(levelname)s:'
65
# Brief description of the operation of this program:
67
# This server announces itself as a Zeroconf service. Connecting
68
# clients use the TLS protocol, with the unusual quirk that this
69
# server program acts as a TLS "client" while the connecting clients
70
# acts as a TLS "server". The clients (acting as a TLS "server") must
71
# supply an OpenPGP certificate, and the fingerprint of this
72
# certificate is used by this server to look up (in a list read from a
73
# file at start time) which binary blob to give the client. No other
74
# authentication or authorization is done by this server.
77
logger = logging.Logger('mandos')
78
syslogger = logging.handlers.SysLogHandler\
79
(facility = logging.handlers.SysLogHandler.LOG_DAEMON)
80
syslogger.setFormatter(logging.Formatter\
81
('%(levelname)s: %(message)s'))
91
82
logger.addHandler(syslogger)
93
console = logging.StreamHandler()
94
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
97
logger.addHandler(console)
99
class AvahiError(Exception):
100
def __init__(self, value, *args, **kwargs):
102
super(AvahiError, self).__init__(value, *args, **kwargs)
103
def __unicode__(self):
104
return unicode(repr(self.value))
106
class AvahiServiceError(AvahiError):
109
class AvahiGroupError(AvahiError):
113
class AvahiService(object):
114
"""An Avahi (Zeroconf) service.
117
interface: integer; avahi.IF_UNSPEC or an interface index.
118
Used to optionally bind to the specified interface.
119
name: string; Example: u'Mandos'
120
type: string; Example: u'_mandos._tcp'.
121
See <http://www.dns-sd.org/ServiceTypes.html>
122
port: integer; what port to announce
123
TXT: list of strings; TXT record for the service
124
domain: string; Domain to publish on, default to .local if empty.
125
host: string; Host to publish records for, default is localhost
126
max_renames: integer; maximum number of renames
127
rename_count: integer; counter so we only rename after collisions
128
a sensible number of times
129
group: D-Bus Entry Group
131
bus: dbus.SystemBus()
133
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
134
servicetype = None, port = None, TXT = None,
135
domain = u"", host = u"", max_renames = 32768,
136
protocol = avahi.PROTO_UNSPEC, bus = None):
137
self.interface = interface
139
self.type = servicetype
141
self.TXT = TXT if TXT is not None else []
144
self.rename_count = 0
145
self.max_renames = max_renames
146
self.protocol = protocol
147
self.group = None # our entry group
151
"""Derived from the Avahi example code"""
152
if self.rename_count >= self.max_renames:
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'
166
self.rename_count += 1
168
"""Derived from the Avahi example code"""
169
if self.group is not None:
172
"""Derived from the 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())
85
# This variable is used to optionally bind to a specified interface.
86
# It is a global variable to fit in with the other variables from the
88
serviceInterface = avahi.IF_UNSPEC
89
# From the Avahi example code:
90
serviceName = "Mandos"
91
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
92
servicePort = None # Not known at startup
93
serviceTXT = [] # TXT record for the service
94
domain = "" # Domain to publish on, default to .local
95
host = "" # Host to publish records for, default to localhost
96
group = None #our entry group
97
rename_count = 12 # Counter so we only rename after collisions a
98
# sensible number of times
99
# End of Avahi example code
230
102
class Client(object):
231
103
"""A representation of a client host served by this server.
234
name: string; from the config file, used in log messages and
105
name: string; from the config file, used in log messages
236
106
fingerprint: string (40 or 32 hexadecimal digits); used to
237
107
uniquely identify the client
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.
108
secret: bytestring; sent verbatim (over TLS) to client
109
fqdn: string (FQDN); available for use by the checker command
110
created: datetime.datetime()
111
last_seen: datetime.datetime() or None if not yet seen
112
timeout: datetime.timedelta(); How long from last_seen until
113
this client is invalid
114
interval: datetime.timedelta(); How often to start a new checker
115
stop_hook: If set, called by stop() as stop_hook(self)
116
checker: subprocess.Popen(); a running checker process used
117
to see if the client lives.
118
Is None if no process is running.
251
119
checker_initiator_tag: a gobject event source tag, or None
252
disable_initiator_tag: - '' -
120
stop_initiator_tag: - '' -
253
121
checker_callback_tag: - '' -
254
122
checker_command: string; External command which is run to check if
255
client lives. %() expansions are done at
123
client lives. %()s expansions are done at
256
124
runtime with vars(self) as dict, so that for
257
125
instance %(name)s can be used in the command.
258
current_checker_command: string; current running checker_command
127
_timeout: Real variable for 'timeout'
128
_interval: Real variable for 'interval'
129
_timeout_milliseconds: Used by gobject.timeout_add()
130
_interval_milliseconds: - '' -
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):
277
"""Note: the 'checker' key in 'config' sets the
278
'checker_command' attribute and *not* the 'checker'
132
def _set_timeout(self, timeout):
133
"Setter function for 'timeout' attribute"
134
self._timeout = timeout
135
self._timeout_milliseconds = ((self.timeout.days
136
* 24 * 60 * 60 * 1000)
137
+ (self.timeout.seconds * 1000)
138
+ (self.timeout.microseconds
140
timeout = property(lambda self: self._timeout,
143
def _set_interval(self, interval):
144
"Setter function for 'interval' attribute"
145
self._interval = interval
146
self._interval_milliseconds = ((self.interval.days
147
* 24 * 60 * 60 * 1000)
148
+ (self.interval.seconds
150
+ (self.interval.microseconds
152
interval = property(lambda self: self._interval,
155
def __init__(self, name=None, options=None, stop_hook=None,
156
fingerprint=None, secret=None, secfile=None,
157
fqdn=None, timeout=None, interval=-1, checker=None):
158
"""Note: the 'checker' argument sets the 'checker_command'
159
attribute and not the 'checker' attribute.."""
283
logger.debug(u"Creating client %r", self.name)
284
# Uppercase and remove spaces from fingerprint for later
285
# comparison purposes with return value from the fingerprint()
287
self.fingerprint = (config[u"fingerprint"].upper()
289
logger.debug(u" Fingerprint: %s", self.fingerprint)
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()
299
raise TypeError(u"No secret or secfile for client %s"
301
self.host = config.get(u"host", u"")
302
self.created = datetime.datetime.utcnow()
304
self.last_enabled = None
305
self.last_checked_ok = None
306
self.timeout = string_to_delta(config[u"timeout"])
307
self.interval = string_to_delta(config[u"interval"])
308
self.disable_hook = disable_hook
161
# Uppercase and remove spaces from fingerprint
162
# for later comparison purposes with return value of
163
# the fingerprint() function
164
self.fingerprint = fingerprint.upper().replace(u" ", u"")
166
self.secret = secret.decode(u"base64")
169
self.secret = sf.read()
172
raise RuntimeError(u"No secret or secfile for client %s"
174
self.fqdn = fqdn # string
175
self.created = datetime.datetime.now()
176
self.last_seen = None
178
self.timeout = options.timeout
180
self.timeout = string_to_delta(timeout)
182
self.interval = options.interval
184
self.interval = string_to_delta(interval)
185
self.stop_hook = stop_hook
309
186
self.checker = None
310
187
self.checker_initiator_tag = None
311
self.disable_initiator_tag = None
188
self.stop_initiator_tag = None
312
189
self.checker_callback_tag = None
313
self.checker_command = config[u"checker"]
314
self.current_checker_command = None
315
self.last_connect = None
190
self.check_command = checker
318
192
"""Start this client's checker and timeout hooks"""
319
if getattr(self, u"enabled", False):
322
self.last_enabled = datetime.datetime.utcnow()
323
193
# Schedule a new checker to be started an 'interval' from now,
324
194
# and every interval from then on.
325
self.checker_initiator_tag = (gobject.timeout_add
326
(self.interval_milliseconds(),
195
self.checker_initiator_tag = gobject.timeout_add\
196
(self._interval_milliseconds,
328
198
# Also start a new checker *right now*.
329
199
self.start_checker()
330
# Schedule a disable() when 'timeout' has passed
331
self.disable_initiator_tag = (gobject.timeout_add
332
(self.timeout_milliseconds(),
337
"""Disable this client."""
338
if not getattr(self, "enabled", False):
200
# Schedule a stop() when 'timeout' has passed
201
self.stop_initiator_tag = gobject.timeout_add\
202
(self._timeout_milliseconds,
206
The possibility that this client might be restarted is left
207
open, but not currently used."""
208
# If this client doesn't have a secret, it is already stopped.
210
logger.debug(u"Stopping client %s", self.name)
340
logger.info(u"Disabling client %s", self.name)
341
if getattr(self, u"disable_initiator_tag", False):
342
gobject.source_remove(self.disable_initiator_tag)
343
self.disable_initiator_tag = None
344
if getattr(self, u"checker_initiator_tag", False):
214
if hasattr(self, "stop_initiator_tag") \
215
and self.stop_initiator_tag:
216
gobject.source_remove(self.stop_initiator_tag)
217
self.stop_initiator_tag = None
218
if hasattr(self, "checker_initiator_tag") \
219
and self.checker_initiator_tag:
345
220
gobject.source_remove(self.checker_initiator_tag)
346
221
self.checker_initiator_tag = None
347
222
self.stop_checker()
348
if self.disable_hook:
349
self.disable_hook(self)
351
225
# Do not run this again if called by a gobject.timeout_add
354
227
def __del__(self):
355
self.disable_hook = None
358
def checker_callback(self, pid, condition, command):
228
self.stop_hook = None
230
def checker_callback(self, pid, condition):
359
231
"""The checker has completed, so take appropriate actions."""
232
now = datetime.datetime.now()
360
233
self.checker_callback_tag = None
361
234
self.checker = None
362
if os.WIFEXITED(condition):
363
exitstatus = os.WEXITSTATUS(condition)
365
logger.info(u"Checker for %(name)s succeeded",
369
logger.info(u"Checker for %(name)s failed",
235
if os.WIFEXITED(condition) \
236
and (os.WEXITSTATUS(condition) == 0):
237
logger.debug(u"Checker for %(name)s succeeded",
240
gobject.source_remove(self.stop_initiator_tag)
241
self.stop_initiator_tag = gobject.timeout_add\
242
(self._timeout_milliseconds,
244
elif not os.WIFEXITED(condition):
372
245
logger.warning(u"Checker for %(name)s crashed?",
375
def checked_ok(self):
376
"""Bump up the timeout for this client.
378
This should only be called when the client has been seen,
381
self.last_checked_ok = datetime.datetime.utcnow()
382
gobject.source_remove(self.disable_initiator_tag)
383
self.disable_initiator_tag = (gobject.timeout_add
384
(self.timeout_milliseconds(),
248
logger.debug(u"Checker for %(name)s failed",
387
250
def start_checker(self):
388
251
"""Start a new checker subprocess if one is not running.
390
252
If a checker already exists, leave it running and do
392
254
# The reason for not killing a running checker is that if we
397
259
# checkers alone, the checker would have to take more time
398
260
# than 'timeout' for the client to be declared invalid, which
399
261
# is as it should be.
401
# If a checker exists, make sure it is not a zombie
403
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
404
except (AttributeError, OSError), error:
405
if (isinstance(error, OSError)
406
and error.errno != errno.ECHILD):
410
logger.warning(u"Checker was a zombie")
411
gobject.source_remove(self.checker_callback_tag)
412
self.checker_callback(pid, status,
413
self.current_checker_command)
414
# Start a new checker if needed
415
262
if self.checker is None:
417
# In case checker_command has exactly one % operator
418
command = self.checker_command % self.host
264
command = self.check_command % self.fqdn
419
265
except TypeError:
420
# Escape attributes for the shell
421
escaped_attrs = dict((key,
422
re.escape(unicode(str(val),
266
escaped_attrs = dict((key, re.escape(str(val)))
426
268
vars(self).iteritems())
428
command = self.checker_command % escaped_attrs
270
command = self.check_command % escaped_attrs
429
271
except TypeError, error:
430
logger.error(u'Could not format string "%s":'
431
u' %s', self.checker_command, error)
272
logger.critical(u'Could not format string "%s":'
273
u' %s', self.check_command, error)
432
274
return True # Try again later
433
self.current_checker_command = command
435
logger.info(u"Starting checker %r for %s",
437
# We don't need to redirect stdout and stderr, since
438
# in normal mode, that is already done by daemon(),
439
# and in debug mode we don't want to. (Stdin is
440
# always replaced by /dev/null.)
441
self.checker = subprocess.Popen(command,
443
shell=True, cwd=u"/")
444
self.checker_callback_tag = (gobject.child_watch_add
446
self.checker_callback,
448
# The checker may have completed before the gobject
449
# watch was added. Check for this.
450
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
452
gobject.source_remove(self.checker_callback_tag)
453
self.checker_callback(pid, status, command)
454
except OSError, error:
276
logger.debug(u"Starting checker %r for %s",
278
self.checker = subprocess.\
280
close_fds=True, shell=True,
282
self.checker_callback_tag = gobject.child_watch_add\
284
self.checker_callback)
285
except subprocess.OSError, error:
455
286
logger.error(u"Failed to start subprocess: %s",
457
288
# Re-run this periodically if run by gobject.timeout_add
460
290
def stop_checker(self):
461
291
"""Force the checker process, if any, to stop."""
462
292
if self.checker_callback_tag:
463
293
gobject.source_remove(self.checker_callback_tag)
464
294
self.checker_callback_tag = None
465
if getattr(self, u"checker", None) is None:
295
if not hasattr(self, "checker") or self.checker is None:
467
logger.debug(u"Stopping checker for %(name)s", vars(self))
297
logger.debug("Stopping checker for %(name)s", vars(self))
469
299
os.kill(self.checker.pid, signal.SIGTERM)
471
301
#if self.checker.poll() is None:
472
302
# os.kill(self.checker.pid, signal.SIGKILL)
473
303
except OSError, error:
474
if error.errno != errno.ESRCH: # No such process
304
if error.errno != errno.ESRCH:
476
306
self.checker = None
478
def still_valid(self):
307
def still_valid(self, now=None):
479
308
"""Has the timeout not yet passed for this client?"""
480
if not getattr(self, u"enabled", False):
482
now = datetime.datetime.utcnow()
483
if self.last_checked_ok is None:
310
now = datetime.datetime.now()
311
if self.last_seen is None:
484
312
return now < (self.created + self.timeout)
486
return now < (self.last_checked_ok + self.timeout)
489
def dbus_service_property(dbus_interface, signature=u"v",
490
access=u"readwrite", byte_arrays=False):
491
"""Decorators for marking methods of a DBusObjectWithProperties to
492
become properties on the D-Bus.
494
The decorated method will be called with no arguments by "Get"
495
and with one argument by "Set".
497
The parameters, where they are supported, are the same as
498
dbus.service.method, except there is only "signature", since the
499
type from Get() and the type sent to Set() is the same.
502
func._dbus_is_property = True
503
func._dbus_interface = dbus_interface
504
func._dbus_signature = signature
505
func._dbus_access = access
506
func._dbus_name = func.__name__
507
if func._dbus_name.endswith(u"_dbus_property"):
508
func._dbus_name = func._dbus_name[:-14]
509
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
514
class DBusPropertyException(dbus.exceptions.DBusException):
515
"""A base class for D-Bus property-related exceptions
517
def __unicode__(self):
518
return unicode(str(self))
521
class DBusPropertyAccessException(DBusPropertyException):
522
"""A property's access permissions disallows an operation.
527
class DBusPropertyNotFound(DBusPropertyException):
528
"""An attempt was made to access a non-existing property.
533
class DBusObjectWithProperties(dbus.service.Object):
534
"""A D-Bus object with properties.
536
Classes inheriting from this can use the dbus_service_property
537
decorator to expose methods as D-Bus properties. It exposes the
538
standard Get(), Set(), and GetAll() methods on the D-Bus.
542
def _is_dbus_property(obj):
543
return getattr(obj, u"_dbus_is_property", False)
545
def _get_all_dbus_properties(self):
546
"""Returns a generator of (name, attribute) pairs
548
return ((prop._dbus_name, prop)
550
inspect.getmembers(self, self._is_dbus_property))
552
def _get_dbus_property(self, interface_name, property_name):
553
"""Returns a bound method if one exists which is a D-Bus
554
property with the specified name and interface.
556
for name in (property_name,
557
property_name + u"_dbus_property"):
558
prop = getattr(self, name, None)
560
or not self._is_dbus_property(prop)
561
or prop._dbus_name != property_name
562
or (interface_name and prop._dbus_interface
563
and interface_name != prop._dbus_interface)):
567
raise DBusPropertyNotFound(self.dbus_object_path + u":"
568
+ interface_name + u"."
571
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
573
def Get(self, interface_name, property_name):
574
"""Standard D-Bus property Get() method, see D-Bus standard.
576
prop = self._get_dbus_property(interface_name, property_name)
577
if prop._dbus_access == u"write":
578
raise DBusPropertyAccessException(property_name)
580
if not hasattr(value, u"variant_level"):
582
return type(value)(value, variant_level=value.variant_level+1)
584
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
585
def Set(self, interface_name, property_name, value):
586
"""Standard D-Bus property Set() method, see D-Bus standard.
588
prop = self._get_dbus_property(interface_name, property_name)
589
if prop._dbus_access == u"read":
590
raise DBusPropertyAccessException(property_name)
591
if prop._dbus_get_args_options[u"byte_arrays"]:
592
value = dbus.ByteArray(''.join(unichr(byte)
596
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
597
out_signature=u"a{sv}")
598
def GetAll(self, interface_name):
599
"""Standard D-Bus property GetAll() method, see D-Bus
602
Note: Will not include properties with access="write".
605
for name, prop in self._get_all_dbus_properties():
607
and interface_name != prop._dbus_interface):
608
# Interface non-empty but did not match
610
# Ignore write-only properties
611
if prop._dbus_access == u"write":
614
if not hasattr(value, u"variant_level"):
617
all[name] = type(value)(value, variant_level=
618
value.variant_level+1)
619
return dbus.Dictionary(all, signature=u"sv")
621
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
623
path_keyword='object_path',
624
connection_keyword='connection')
625
def Introspect(self, object_path, connection):
626
"""Standard D-Bus method, overloaded to insert property tags.
628
xmlstring = dbus.service.Object.Introspect(self, object_path,
630
document = xml.dom.minidom.parseString(xmlstring)
632
def make_tag(document, name, prop):
633
e = document.createElement(u"property")
634
e.setAttribute(u"name", name)
635
e.setAttribute(u"type", prop._dbus_signature)
636
e.setAttribute(u"access", prop._dbus_access)
638
for if_tag in document.getElementsByTagName(u"interface"):
639
for tag in (make_tag(document, name, prop)
641
in self._get_all_dbus_properties()
642
if prop._dbus_interface
643
== if_tag.getAttribute(u"name")):
644
if_tag.appendChild(tag)
645
xmlstring = document.toxml(u"utf-8")
650
class ClientDBus(Client, DBusObjectWithProperties):
651
"""A Client class using D-Bus
654
dbus_object_path: dbus.ObjectPath
655
bus: dbus.SystemBus()
657
# dbus.service.Object doesn't use super(), so we can't either.
659
def __init__(self, bus = None, *args, **kwargs):
661
Client.__init__(self, *args, **kwargs)
662
# Only now, when this client is initialized, can it show up on
664
self.dbus_object_path = (dbus.ObjectPath
666
+ self.name.replace(u".", u"_")))
667
DBusObjectWithProperties.__init__(self, self.bus,
668
self.dbus_object_path)
671
def _datetime_to_dbus(dt, variant_level=0):
672
"""Convert a UTC datetime.datetime() to a D-Bus type."""
673
return dbus.String(dt.isoformat(),
674
variant_level=variant_level)
677
oldstate = getattr(self, u"enabled", False)
678
r = Client.enable(self)
679
if oldstate != self.enabled:
681
self.PropertyChanged(dbus.String(u"enabled"),
682
dbus.Boolean(True, variant_level=1))
683
self.PropertyChanged(
684
dbus.String(u"last_enabled"),
685
self._datetime_to_dbus(self.last_enabled,
689
def disable(self, signal = True):
690
oldstate = getattr(self, u"enabled", False)
691
r = Client.disable(self)
692
if signal and oldstate != self.enabled:
694
self.PropertyChanged(dbus.String(u"enabled"),
695
dbus.Boolean(False, variant_level=1))
698
def __del__(self, *args, **kwargs):
700
self.remove_from_connection()
703
if hasattr(DBusObjectWithProperties, u"__del__"):
704
DBusObjectWithProperties.__del__(self, *args, **kwargs)
705
Client.__del__(self, *args, **kwargs)
707
def checker_callback(self, pid, condition, command,
709
self.checker_callback_tag = None
712
self.PropertyChanged(dbus.String(u"checker_running"),
713
dbus.Boolean(False, variant_level=1))
714
if os.WIFEXITED(condition):
715
exitstatus = os.WEXITSTATUS(condition)
717
self.CheckerCompleted(dbus.Int16(exitstatus),
718
dbus.Int64(condition),
719
dbus.String(command))
722
self.CheckerCompleted(dbus.Int16(-1),
723
dbus.Int64(condition),
724
dbus.String(command))
726
return Client.checker_callback(self, pid, condition, command,
729
def checked_ok(self, *args, **kwargs):
730
r = Client.checked_ok(self, *args, **kwargs)
732
self.PropertyChanged(
733
dbus.String(u"last_checked_ok"),
734
(self._datetime_to_dbus(self.last_checked_ok,
738
def start_checker(self, *args, **kwargs):
739
old_checker = self.checker
740
if self.checker is not None:
741
old_checker_pid = self.checker.pid
743
old_checker_pid = None
744
r = Client.start_checker(self, *args, **kwargs)
745
# Only if new checker process was started
746
if (self.checker is not None
747
and old_checker_pid != self.checker.pid):
749
self.CheckerStarted(self.current_checker_command)
750
self.PropertyChanged(
751
dbus.String(u"checker_running"),
752
dbus.Boolean(True, variant_level=1))
755
def stop_checker(self, *args, **kwargs):
756
old_checker = getattr(self, u"checker", None)
757
r = Client.stop_checker(self, *args, **kwargs)
758
if (old_checker is not None
759
and getattr(self, u"checker", None) is None):
760
self.PropertyChanged(dbus.String(u"checker_running"),
761
dbus.Boolean(False, variant_level=1))
764
## D-Bus methods & signals
765
_interface = u"se.bsnet.fukt.Mandos.Client"
768
@dbus.service.method(_interface)
770
return self.checked_ok()
772
# CheckerCompleted - signal
773
@dbus.service.signal(_interface, signature=u"nxs")
774
def CheckerCompleted(self, exitcode, waitstatus, command):
778
# CheckerStarted - signal
779
@dbus.service.signal(_interface, signature=u"s")
780
def CheckerStarted(self, command):
784
# PropertyChanged - signal
785
@dbus.service.signal(_interface, signature=u"sv")
786
def PropertyChanged(self, property, value):
790
# ReceivedSecret - signal
791
@dbus.service.signal(_interface)
792
def ReceivedSecret(self):
797
@dbus.service.signal(_interface)
803
@dbus.service.method(_interface)
808
# StartChecker - method
809
@dbus.service.method(_interface)
810
def StartChecker(self):
815
@dbus.service.method(_interface)
820
# StopChecker - method
821
@dbus.service.method(_interface)
822
def StopChecker(self):
826
@dbus_service_property(_interface, signature=u"s", access=u"read")
827
def name_dbus_property(self):
828
return dbus.String(self.name)
830
# fingerprint - property
831
@dbus_service_property(_interface, signature=u"s", access=u"read")
832
def fingerprint_dbus_property(self):
833
return dbus.String(self.fingerprint)
836
@dbus_service_property(_interface, signature=u"s",
838
def host_dbus_property(self, value=None):
839
if value is None: # get
840
return dbus.String(self.host)
843
self.PropertyChanged(dbus.String(u"host"),
844
dbus.String(value, variant_level=1))
847
@dbus_service_property(_interface, signature=u"s", access=u"read")
848
def created_dbus_property(self):
849
return dbus.String(self._datetime_to_dbus(self.created))
851
# last_enabled - property
852
@dbus_service_property(_interface, signature=u"s", access=u"read")
853
def last_enabled_dbus_property(self):
854
if self.last_enabled is None:
855
return dbus.String(u"")
856
return dbus.String(self._datetime_to_dbus(self.last_enabled))
859
@dbus_service_property(_interface, signature=u"b",
861
def enabled_dbus_property(self, value=None):
862
if value is None: # get
863
return dbus.Boolean(self.enabled)
869
# last_checked_ok - property
870
@dbus_service_property(_interface, signature=u"s",
872
def last_checked_ok_dbus_property(self, value=None):
873
if value is not None:
876
if self.last_checked_ok is None:
877
return dbus.String(u"")
878
return dbus.String(self._datetime_to_dbus(self
882
@dbus_service_property(_interface, signature=u"t",
884
def timeout_dbus_property(self, value=None):
885
if value is None: # get
886
return dbus.UInt64(self.timeout_milliseconds())
887
self.timeout = datetime.timedelta(0, 0, 0, value)
889
self.PropertyChanged(dbus.String(u"timeout"),
890
dbus.UInt64(value, variant_level=1))
891
if getattr(self, u"disable_initiator_tag", None) is None:
894
gobject.source_remove(self.disable_initiator_tag)
895
self.disable_initiator_tag = None
897
_timedelta_to_milliseconds((self
903
# The timeout has passed
906
self.disable_initiator_tag = (gobject.timeout_add
907
(time_to_die, self.disable))
909
# interval - property
910
@dbus_service_property(_interface, signature=u"t",
912
def interval_dbus_property(self, value=None):
913
if value is None: # get
914
return dbus.UInt64(self.interval_milliseconds())
915
self.interval = datetime.timedelta(0, 0, 0, value)
917
self.PropertyChanged(dbus.String(u"interval"),
918
dbus.UInt64(value, variant_level=1))
919
if getattr(self, u"checker_initiator_tag", None) is None:
921
# Reschedule checker run
922
gobject.source_remove(self.checker_initiator_tag)
923
self.checker_initiator_tag = (gobject.timeout_add
924
(value, self.start_checker))
925
self.start_checker() # Start one now, too
928
@dbus_service_property(_interface, signature=u"s",
930
def checker_dbus_property(self, value=None):
931
if value is None: # get
932
return dbus.String(self.checker_command)
933
self.checker_command = value
935
self.PropertyChanged(dbus.String(u"checker"),
936
dbus.String(self.checker_command,
939
# checker_running - property
940
@dbus_service_property(_interface, signature=u"b",
942
def checker_running_dbus_property(self, value=None):
943
if value is None: # get
944
return dbus.Boolean(self.checker is not None)
950
# object_path - property
951
@dbus_service_property(_interface, signature=u"o", access=u"read")
952
def object_path_dbus_property(self):
953
return self.dbus_object_path # is already a dbus.ObjectPath
956
@dbus_service_property(_interface, signature=u"ay",
957
access=u"write", byte_arrays=True)
958
def secret_dbus_property(self, value):
959
self.secret = str(value)
964
class ClientHandler(socketserver.BaseRequestHandler, object):
965
"""A class to handle client connections.
967
Instantiated once for each connection to handle it.
314
return now < (self.last_seen + self.timeout)
317
def peer_certificate(session):
318
"Return the peer's OpenPGP certificate as a bytestring"
319
# If not an OpenPGP certificate...
320
if gnutls.library.functions.gnutls_certificate_type_get\
321
(session._c_object) \
322
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
323
# ...do the normal thing
324
return session.peer_certificate
325
list_size = ctypes.c_uint()
326
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
327
(session._c_object, ctypes.byref(list_size))
328
if list_size.value == 0:
331
return ctypes.string_at(cert.data, cert.size)
334
def fingerprint(openpgp):
335
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
336
# New empty GnuTLS certificate
337
crt = gnutls.library.types.gnutls_openpgp_crt_t()
338
gnutls.library.functions.gnutls_openpgp_crt_init\
340
# New GnuTLS "datum" with the OpenPGP public key
341
datum = gnutls.library.types.gnutls_datum_t\
342
(ctypes.cast(ctypes.c_char_p(openpgp),
343
ctypes.POINTER(ctypes.c_ubyte)),
344
ctypes.c_uint(len(openpgp)))
345
# Import the OpenPGP public key into the certificate
346
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
349
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
350
# New buffer for the fingerprint
351
buffer = ctypes.create_string_buffer(20)
352
buffer_length = ctypes.c_size_t()
353
# Get the fingerprint from the certificate into the buffer
354
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
355
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
356
# Deinit the certificate
357
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
358
# Convert the buffer to a Python bytestring
359
fpr = ctypes.string_at(buffer, buffer_length.value)
360
# Convert the bytestring to hexadecimal notation
361
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
365
class tcp_handler(SocketServer.BaseRequestHandler, object):
366
"""A TCP request handler class.
367
Instantiated by IPv6_TCPServer for each request to handle it.
968
368
Note: This will run in its own forked process."""
970
370
def handle(self):
971
logger.info(u"TCP connection from: %s",
972
unicode(self.client_address))
973
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
974
# Open IPC pipe to parent process
975
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
976
session = (gnutls.connection
977
.ClientSession(self.request,
981
line = self.request.makefile().readline()
982
logger.debug(u"Protocol version: %r", line)
984
if int(line.strip().split()[0]) > 1:
986
except (ValueError, IndexError, RuntimeError), error:
987
logger.error(u"Unknown protocol version: %s", error)
990
# Note: gnutls.connection.X509Credentials is really a
991
# generic GnuTLS certificate credentials object so long as
992
# no X.509 keys are added to it. Therefore, we can use it
993
# here despite using OpenPGP certificates.
995
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
996
# u"+AES-256-CBC", u"+SHA1",
997
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
999
# Use a fallback default, since this MUST be set.
1000
priority = self.server.gnutls_priority
1001
if priority is None:
1002
priority = u"NORMAL"
1003
(gnutls.library.functions
1004
.gnutls_priority_set_direct(session._c_object,
1009
except gnutls.errors.GNUTLSError, error:
1010
logger.warning(u"Handshake failed: %s", error)
1011
# Do not run session.bye() here: the session is not
1012
# established. Just abandon the request.
1014
logger.debug(u"Handshake succeeded")
1016
fpr = self.fingerprint(self.peer_certificate(session))
1017
except (TypeError, gnutls.errors.GNUTLSError), error:
1018
logger.warning(u"Bad certificate: %s", error)
1021
logger.debug(u"Fingerprint: %s", fpr)
1023
for c in self.server.clients:
1024
if c.fingerprint == fpr:
371
logger.debug(u"TCP connection from: %s",
372
unicode(self.client_address))
373
session = gnutls.connection.ClientSession(self.request,
377
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
378
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
380
priority = "SECURE256"
382
gnutls.library.functions.gnutls_priority_set_direct\
383
(session._c_object, priority, None);
387
except gnutls.errors.GNUTLSError, error:
388
logger.debug(u"Handshake failed: %s", error)
389
# Do not run session.bye() here: the session is not
390
# established. Just abandon the request.
393
fpr = fingerprint(peer_certificate(session))
394
except (TypeError, gnutls.errors.GNUTLSError), error:
395
logger.debug(u"Bad certificate: %s", error)
398
logger.debug(u"Fingerprint: %s", fpr)
400
for c in self.server.clients:
401
if c.fingerprint == fpr:
404
# Have to check if client.still_valid(), since it is possible
405
# that the client timed out while establishing the GnuTLS
407
if (not client) or (not client.still_valid()):
409
logger.debug(u"Client %(name)s is invalid",
1028
ipc.write(u"NOTFOUND %s %s\n"
1029
% (fpr, unicode(self.client_address)))
1032
# Have to check if client.still_valid(), since it is
1033
# possible that the client timed out while establishing
1034
# the GnuTLS session.
1035
if not client.still_valid():
1036
ipc.write(u"INVALID %s\n" % client.name)
1039
ipc.write(u"SENDING %s\n" % client.name)
1041
while sent_size < len(client.secret):
1042
sent = session.send(client.secret[sent_size:])
1043
logger.debug(u"Sent: %d, remaining: %d",
1044
sent, len(client.secret)
1045
- (sent_size + sent))
412
logger.debug(u"Client not found for fingerprint: %s",
1050
def peer_certificate(session):
1051
"Return the peer's OpenPGP certificate as a bytestring"
1052
# If not an OpenPGP certificate...
1053
if (gnutls.library.functions
1054
.gnutls_certificate_type_get(session._c_object)
1055
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1056
# ...do the normal thing
1057
return session.peer_certificate
1058
list_size = ctypes.c_uint(1)
1059
cert_list = (gnutls.library.functions
1060
.gnutls_certificate_get_peers
1061
(session._c_object, ctypes.byref(list_size)))
1062
if not bool(cert_list) and list_size.value != 0:
1063
raise gnutls.errors.GNUTLSError(u"error getting peer"
1065
if list_size.value == 0:
1068
return ctypes.string_at(cert.data, cert.size)
1071
def fingerprint(openpgp):
1072
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1073
# New GnuTLS "datum" with the OpenPGP public key
1074
datum = (gnutls.library.types
1075
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1078
ctypes.c_uint(len(openpgp))))
1079
# New empty GnuTLS certificate
1080
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1081
(gnutls.library.functions
1082
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1083
# Import the OpenPGP public key into the certificate
1084
(gnutls.library.functions
1085
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1086
gnutls.library.constants
1087
.GNUTLS_OPENPGP_FMT_RAW))
1088
# Verify the self signature in the key
1089
crtverify = ctypes.c_uint()
1090
(gnutls.library.functions
1091
.gnutls_openpgp_crt_verify_self(crt, 0,
1092
ctypes.byref(crtverify)))
1093
if crtverify.value != 0:
1094
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1095
raise (gnutls.errors.CertificateSecurityError
1097
# New buffer for the fingerprint
1098
buf = ctypes.create_string_buffer(20)
1099
buf_len = ctypes.c_size_t()
1100
# Get the fingerprint from the certificate into the buffer
1101
(gnutls.library.functions
1102
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1103
ctypes.byref(buf_len)))
1104
# Deinit the certificate
1105
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1106
# Convert the buffer to a Python bytestring
1107
fpr = ctypes.string_at(buf, buf_len.value)
1108
# Convert the bytestring to hexadecimal notation
1109
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1113
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1114
"""Like socketserver.ForkingMixIn, but also pass a pipe."""
1115
def process_request(self, request, client_address):
1116
"""Overrides and wraps the original process_request().
1118
This function creates a new pipe in self.pipe
1120
self.pipe = os.pipe()
1121
super(ForkingMixInWithPipe,
1122
self).process_request(request, client_address)
1123
os.close(self.pipe[1]) # close write end
1124
self.add_pipe(self.pipe[0])
1125
def add_pipe(self, pipe):
1126
"""Dummy function; override as necessary"""
1130
class IPv6_TCPServer(ForkingMixInWithPipe,
1131
socketserver.TCPServer, object):
1132
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
417
while sent_size < len(client.secret):
418
sent = session.send(client.secret[sent_size:])
419
logger.debug(u"Sent: %d, remaining: %d",
420
sent, len(client.secret)
421
- (sent_size + sent))
426
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
427
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1135
enabled: Boolean; whether this server is activated yet
1136
interface: None or a network interface name (string)
1137
use_ipv6: Boolean; to use IPv6 or not
429
options: Command line options
430
clients: Set() of Client objects
1139
def __init__(self, server_address, RequestHandlerClass,
1140
interface=None, use_ipv6=True):
1141
self.interface = interface
1143
self.address_family = socket.AF_INET6
1144
socketserver.TCPServer.__init__(self, server_address,
1145
RequestHandlerClass)
432
address_family = socket.AF_INET6
433
def __init__(self, *args, **kwargs):
434
if "options" in kwargs:
435
self.options = kwargs["options"]
436
del kwargs["options"]
437
if "clients" in kwargs:
438
self.clients = kwargs["clients"]
439
del kwargs["clients"]
440
return super(type(self), self).__init__(*args, **kwargs)
1146
441
def server_bind(self):
1147
442
"""This overrides the normal server_bind() function
1148
443
to bind to an interface if one was specified, and also NOT to
1149
444
bind to an address or port if they were not specified."""
1150
if self.interface is not None:
1151
if SO_BINDTODEVICE is None:
1152
logger.error(u"SO_BINDTODEVICE does not exist;"
1153
u" cannot bind to interface %s",
1157
self.socket.setsockopt(socket.SOL_SOCKET,
1161
except socket.error, error:
1162
if error[0] == errno.EPERM:
1163
logger.error(u"No permission to"
1164
u" bind to interface %s",
1166
elif error[0] == errno.ENOPROTOOPT:
1167
logger.error(u"SO_BINDTODEVICE not available;"
1168
u" cannot bind to interface %s",
445
if self.options.interface:
446
if not hasattr(socket, "SO_BINDTODEVICE"):
447
# From /usr/include/asm-i486/socket.h
448
socket.SO_BINDTODEVICE = 25
450
self.socket.setsockopt(socket.SOL_SOCKET,
451
socket.SO_BINDTODEVICE,
452
self.options.interface)
453
except socket.error, error:
454
if error[0] == errno.EPERM:
455
logger.warning(u"No permission to"
456
u" bind to interface %s",
457
self.options.interface)
1172
460
# Only bind(2) the socket if we really need to.
1173
461
if self.server_address[0] or self.server_address[1]:
1174
462
if not self.server_address[0]:
1175
if self.address_family == socket.AF_INET6:
1176
any_address = u"::" # in6addr_any
1178
any_address = socket.INADDR_ANY
1179
self.server_address = (any_address,
464
self.server_address = (in6addr_any,
1180
465
self.server_address[1])
1181
elif not self.server_address[1]:
466
elif self.server_address[1] is None:
1182
467
self.server_address = (self.server_address[0],
1184
# if self.interface:
1185
# self.server_address = (self.server_address[0],
1190
return socketserver.TCPServer.server_bind(self)
1193
class MandosServer(IPv6_TCPServer):
1197
clients: set of Client objects
1198
gnutls_priority GnuTLS priority string
1199
use_dbus: Boolean; to emit D-Bus signals or not
1201
Assumes a gobject.MainLoop event loop.
1203
def __init__(self, server_address, RequestHandlerClass,
1204
interface=None, use_ipv6=True, clients=None,
1205
gnutls_priority=None, use_dbus=True):
1206
self.enabled = False
1207
self.clients = clients
1208
if self.clients is None:
1209
self.clients = set()
1210
self.use_dbus = use_dbus
1211
self.gnutls_priority = gnutls_priority
1212
IPv6_TCPServer.__init__(self, server_address,
1213
RequestHandlerClass,
1214
interface = interface,
1215
use_ipv6 = use_ipv6)
1216
def server_activate(self):
1218
return socketserver.TCPServer.server_activate(self)
1221
def add_pipe(self, pipe):
1222
# Call "handle_ipc" for both data and EOF events
1223
gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1225
def handle_ipc(self, source, condition, file_objects={}):
1227
gobject.IO_IN: u"IN", # There is data to read.
1228
gobject.IO_OUT: u"OUT", # Data can be written (without
1230
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1231
gobject.IO_ERR: u"ERR", # Error condition.
1232
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1233
# broken, usually for pipes and
1236
conditions_string = ' | '.join(name
1238
condition_names.iteritems()
1239
if cond & condition)
1240
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1243
# Turn the pipe file descriptor into a Python file object
1244
if source not in file_objects:
1245
file_objects[source] = os.fdopen(source, u"r", 1)
1247
# Read a line from the file object
1248
cmdline = file_objects[source].readline()
1249
if not cmdline: # Empty line means end of file
1250
# close the IPC pipe
1251
file_objects[source].close()
1252
del file_objects[source]
1254
# Stop calling this function
1257
logger.debug(u"IPC command: %r", cmdline)
1259
# Parse and act on command
1260
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1262
if cmd == u"NOTFOUND":
1263
logger.warning(u"Client not found for fingerprint: %s",
1267
mandos_dbus_service.ClientNotFound(args)
1268
elif cmd == u"INVALID":
1269
for client in self.clients:
1270
if client.name == args:
1271
logger.warning(u"Client %s is invalid", args)
1277
logger.error(u"Unknown client %s is invalid", args)
1278
elif cmd == u"SENDING":
1279
for client in self.clients:
1280
if client.name == args:
1281
logger.info(u"Sending secret to %s", client.name)
1285
client.ReceivedSecret()
1288
logger.error(u"Sending secret to unknown client %s",
1291
logger.error(u"Unknown IPC command: %r", cmdline)
1293
# Keep calling this function
469
return super(type(self), self).server_bind()
1297
472
def string_to_delta(interval):
1298
473
"""Parse a string and return a datetime.timedelta
1300
>>> string_to_delta(u'7d')
475
>>> string_to_delta('7d')
1301
476
datetime.timedelta(7)
1302
>>> string_to_delta(u'60s')
477
>>> string_to_delta('60s')
1303
478
datetime.timedelta(0, 60)
1304
>>> string_to_delta(u'60m')
479
>>> string_to_delta('60m')
1305
480
datetime.timedelta(0, 3600)
1306
>>> string_to_delta(u'24h')
481
>>> string_to_delta('24h')
1307
482
datetime.timedelta(1)
1308
483
>>> string_to_delta(u'1w')
1309
484
datetime.timedelta(7)
1310
>>> string_to_delta(u'5m 30s')
1311
datetime.timedelta(0, 330)
1313
timevalue = datetime.timedelta(0)
1314
for s in interval.split():
1316
suffix = unicode(s[-1])
1319
delta = datetime.timedelta(value)
1320
elif suffix == u"s":
1321
delta = datetime.timedelta(0, value)
1322
elif suffix == u"m":
1323
delta = datetime.timedelta(0, 0, 0, 0, value)
1324
elif suffix == u"h":
1325
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1326
elif suffix == u"w":
1327
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1330
except (ValueError, IndexError):
487
suffix=unicode(interval[-1])
488
value=int(interval[:-1])
490
delta = datetime.timedelta(value)
492
delta = datetime.timedelta(0, value)
494
delta = datetime.timedelta(0, 0, 0, 0, value)
496
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
498
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1331
500
raise ValueError
501
except (ValueError, IndexError):
507
"""Derived from the Avahi example code"""
508
global group, serviceName, serviceType, servicePort, serviceTXT, \
511
group = dbus.Interface(
512
bus.get_object( avahi.DBUS_NAME,
513
server.EntryGroupNew()),
514
avahi.DBUS_INTERFACE_ENTRY_GROUP)
515
group.connect_to_signal('StateChanged',
516
entry_group_state_changed)
517
logger.debug(u"Adding service '%s' of type '%s' ...",
518
serviceName, serviceType)
521
serviceInterface, # interface
522
avahi.PROTO_INET6, # protocol
523
dbus.UInt32(0), # flags
524
serviceName, serviceType,
526
dbus.UInt16(servicePort),
527
avahi.string_array_to_txt_array(serviceTXT))
531
def remove_service():
532
"""From the Avahi example code"""
535
if not group is None:
539
def server_state_changed(state):
540
"""Derived from the Avahi example code"""
541
if state == avahi.SERVER_COLLISION:
542
logger.warning(u"Server name collision")
544
elif state == avahi.SERVER_RUNNING:
548
def entry_group_state_changed(state, error):
549
"""Derived from the Avahi example code"""
550
global serviceName, server, rename_count
552
logger.debug(u"state change: %i", state)
554
if state == avahi.ENTRY_GROUP_ESTABLISHED:
555
logger.debug(u"Service established.")
556
elif state == avahi.ENTRY_GROUP_COLLISION:
558
rename_count = rename_count - 1
560
name = server.GetAlternativeServiceName(name)
561
logger.warning(u"Service name collision, "
562
u"changing name to '%s' ...", name)
567
logger.error(u"No suitable service name found after %i"
568
u" retries, exiting.", n_rename)
570
elif state == avahi.ENTRY_GROUP_FAILURE:
571
logger.error(u"Error in group state changed %s",
1336
576
def if_nametoindex(interface):
1337
"""Call the C function if_nametoindex(), or equivalent
1339
Note: This function cannot accept a unicode string."""
1340
global if_nametoindex
577
"""Call the C function if_nametoindex()"""
1342
if_nametoindex = (ctypes.cdll.LoadLibrary
1343
(ctypes.util.find_library(u"c"))
579
libc = ctypes.cdll.LoadLibrary("libc.so.6")
580
return libc.if_nametoindex(interface)
1345
581
except (OSError, AttributeError):
1346
logger.warning(u"Doing if_nametoindex the hard way")
1347
def if_nametoindex(interface):
1348
"Get an interface index the hard way, i.e. using fcntl()"
1349
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1350
with closing(socket.socket()) as s:
1351
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1352
struct.pack(str(u"16s16x"),
1354
interface_index = struct.unpack(str(u"I"),
1356
return interface_index
1357
return if_nametoindex(interface)
1360
def daemon(nochdir = False, noclose = False):
582
if "struct" not in sys.modules:
584
if "fcntl" not in sys.modules:
586
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
588
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
589
struct.pack("16s16x", interface))
591
interface_index = struct.unpack("I", ifreq[16:20])[0]
592
return interface_index
595
def daemon(nochdir, noclose):
1361
596
"""See daemon(3). Standard BSD Unix function.
1363
597
This should really exist as os.daemon, but it doesn't (yet)."""
1372
604
# Close all standard open file descriptors
1373
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
605
null = os.open("/dev/null", os.O_NOCTTY | os.O_RDWR)
1374
606
if not stat.S_ISCHR(os.fstat(null).st_mode):
1375
607
raise OSError(errno.ENODEV,
1376
u"/dev/null not a character device")
608
"/dev/null not a character device")
1377
609
os.dup2(null, sys.stdin.fileno())
1378
610
os.dup2(null, sys.stdout.fileno())
1379
611
os.dup2(null, sys.stderr.fileno())
616
def killme(status = 0):
617
logger.debug("Stopping server with exit status %d", status)
619
if main_loop_started:
1386
##################################################################
1387
# Parsing of options, both command line and config file
1389
parser = optparse.OptionParser(version = "%%prog %s" % version)
1390
parser.add_option("-i", u"--interface", type=u"string",
1391
metavar="IF", help=u"Bind to interface IF")
1392
parser.add_option("-a", u"--address", type=u"string",
1393
help=u"Address to listen for requests on")
1394
parser.add_option("-p", u"--port", type=u"int",
1395
help=u"Port number to receive requests on")
1396
parser.add_option("--check", action=u"store_true",
1397
help=u"Run self-test")
1398
parser.add_option("--debug", action=u"store_true",
1399
help=u"Debug mode; run in foreground and log to"
1401
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1402
u" priority string (see GnuTLS documentation)")
1403
parser.add_option("--servicename", type=u"string",
1404
metavar=u"NAME", help=u"Zeroconf service name")
1405
parser.add_option("--configdir", type=u"string",
1406
default=u"/etc/mandos", metavar=u"DIR",
1407
help=u"Directory to search for configuration"
1409
parser.add_option("--no-dbus", action=u"store_false",
1410
dest=u"use_dbus", help=u"Do not provide D-Bus"
1411
u" system bus interface")
1412
parser.add_option("--no-ipv6", action=u"store_false",
1413
dest=u"use_ipv6", help=u"Do not use IPv6")
1414
options = parser.parse_args()[0]
628
global main_loop_started
629
main_loop_started = False
631
parser = OptionParser()
632
parser.add_option("-i", "--interface", type="string",
633
default=None, metavar="IF",
634
help="Bind to interface IF")
635
parser.add_option("-a", "--address", type="string", default=None,
636
help="Address to listen for requests on")
637
parser.add_option("-p", "--port", type="int", default=None,
638
help="Port number to receive requests on")
639
parser.add_option("--timeout", type="string", # Parsed later
641
help="Amount of downtime allowed for clients")
642
parser.add_option("--interval", type="string", # Parsed later
644
help="How often to check that a client is up")
645
parser.add_option("--check", action="store_true", default=False,
646
help="Run self-test")
647
parser.add_option("--debug", action="store_true", default=False,
649
(options, args) = parser.parse_args()
1416
651
if options.check:
1418
653
doctest.testmod()
1421
# Default values for config file for server-global settings
1422
server_defaults = { u"interface": u"",
1427
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1428
u"servicename": u"Mandos",
1429
u"use_dbus": u"True",
1430
u"use_ipv6": u"True",
1433
# Parse config file for server-global settings
1434
server_config = configparser.SafeConfigParser(server_defaults)
1436
server_config.read(os.path.join(options.configdir,
1438
# Convert the SafeConfigParser object to a dict
1439
server_settings = server_config.defaults()
1440
# Use the appropriate methods on the non-string config options
1441
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1442
server_settings[option] = server_config.getboolean(u"DEFAULT",
1444
if server_settings["port"]:
1445
server_settings["port"] = server_config.getint(u"DEFAULT",
1449
# Override the settings from the config file with command line
1451
for option in (u"interface", u"address", u"port", u"debug",
1452
u"priority", u"servicename", u"configdir",
1453
u"use_dbus", u"use_ipv6"):
1454
value = getattr(options, option)
1455
if value is not None:
1456
server_settings[option] = value
1458
# Force all strings to be unicode
1459
for option in server_settings.keys():
1460
if type(server_settings[option]) is str:
1461
server_settings[option] = unicode(server_settings[option])
1462
# Now we have our good server settings in "server_settings"
1464
##################################################################
1467
debug = server_settings[u"debug"]
1468
use_dbus = server_settings[u"use_dbus"]
1469
use_ipv6 = server_settings[u"use_ipv6"]
1472
syslogger.setLevel(logging.WARNING)
1473
console.setLevel(logging.WARNING)
1475
if server_settings[u"servicename"] != u"Mandos":
1476
syslogger.setFormatter(logging.Formatter
1477
(u'Mandos (%s) [%%(process)d]:'
1478
u' %%(levelname)s: %%(message)s'
1479
% server_settings[u"servicename"]))
1481
# Parse config file with clients
1482
client_defaults = { u"timeout": u"1h",
1484
u"checker": u"fping -q -- %%(host)s",
1487
client_config = configparser.SafeConfigParser(client_defaults)
1488
client_config.read(os.path.join(server_settings[u"configdir"],
1491
global mandos_dbus_service
1492
mandos_dbus_service = None
1494
tcp_server = MandosServer((server_settings[u"address"],
1495
server_settings[u"port"]),
1497
interface=server_settings[u"interface"],
1500
server_settings[u"priority"],
1502
pidfilename = u"/var/run/mandos.pid"
1504
pidfile = open(pidfilename, u"w")
1506
logger.error(u"Could not open file %r", pidfilename)
1509
uid = pwd.getpwnam(u"_mandos").pw_uid
1510
gid = pwd.getpwnam(u"_mandos").pw_gid
1513
uid = pwd.getpwnam(u"mandos").pw_uid
1514
gid = pwd.getpwnam(u"mandos").pw_gid
1517
uid = pwd.getpwnam(u"nobody").pw_uid
1518
gid = pwd.getpwnam(u"nobody").pw_gid
1525
except OSError, error:
1526
if error[0] != errno.EPERM:
1529
# Enable all possible GnuTLS debugging
1531
# "Use a log level over 10 to enable all debugging options."
1533
gnutls.library.functions.gnutls_global_set_log_level(11)
1535
@gnutls.library.types.gnutls_log_func
1536
def debug_gnutls(level, string):
1537
logger.debug(u"GnuTLS: %s", string[:-1])
1539
(gnutls.library.functions
1540
.gnutls_global_set_log_function(debug_gnutls))
656
# Parse the time arguments
658
options.timeout = string_to_delta(options.timeout)
660
parser.error("option --timeout: Unparseable time")
662
options.interval = string_to_delta(options.interval)
664
parser.error("option --interval: Unparseable time")
667
defaults = { "checker": "fping -q -- %%(fqdn)s" }
668
client_config = ConfigParser.SafeConfigParser(defaults)
669
#client_config.readfp(open("global.conf"), "global.conf")
670
client_config.read("mandos-clients.conf")
1542
672
global main_loop
1543
675
# From the Avahi example code
1544
676
DBusGMainLoop(set_as_default=True )
1545
677
main_loop = gobject.MainLoop()
1546
678
bus = dbus.SystemBus()
679
server = dbus.Interface(
680
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
681
avahi.DBUS_INTERFACE_SERVER )
1547
682
# End of Avahi example code
1549
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1550
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1551
service = AvahiService(name = server_settings[u"servicename"],
1552
servicetype = u"_mandos._tcp",
1553
protocol = protocol, bus = bus)
1554
if server_settings["interface"]:
1555
service.interface = (if_nametoindex
1556
(str(server_settings[u"interface"])))
1558
client_class = Client
1560
client_class = functools.partial(ClientDBus, bus = bus)
1561
tcp_server.clients.update(set(
1562
client_class(name = section,
1563
config= dict(client_config.items(section)))
1564
for section in client_config.sections()))
1565
if not tcp_server.clients:
1566
logger.warning(u"No clients defined")
684
debug = options.debug
1569
# Redirect stdin so all checkers get /dev/null
1570
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1571
os.dup2(null, sys.stdin.fileno())
1575
# No console logging
1576
logger.removeHandler(console)
1577
# Close all input and output, do double fork, etc.
1581
with closing(pidfile):
1583
pidfile.write(str(pid) + "\n")
1586
logger.error(u"Could not write to file %r with PID %d",
1589
# "pidfile" was never created
687
console = logging.StreamHandler()
688
# console.setLevel(logging.DEBUG)
689
console.setFormatter(logging.Formatter\
690
('%(levelname)s: %(message)s'))
691
logger.addHandler(console)
695
def remove_from_clients(client):
696
clients.remove(client)
698
logger.debug(u"No clients left, exiting")
701
clients.update(Set(Client(name=section, options=options,
702
stop_hook = remove_from_clients,
703
**(dict(client_config\
705
for section in client_config.sections()))
1594
711
"Cleanup function; run on exit"
713
# From the Avahi example code
714
if not group is None:
717
# End of Avahi example code
1597
while tcp_server.clients:
1598
client = tcp_server.clients.pop()
1599
client.disable_hook = None
720
client = clients.pop()
721
client.stop_hook = None
1602
724
atexit.register(cleanup)
1605
727
signal.signal(signal.SIGINT, signal.SIG_IGN)
1606
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1607
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1610
class MandosDBusService(dbus.service.Object):
1611
"""A D-Bus proxy object"""
1613
dbus.service.Object.__init__(self, bus, u"/")
1614
_interface = u"se.bsnet.fukt.Mandos"
1616
@dbus.service.signal(_interface, signature=u"oa{sv}")
1617
def ClientAdded(self, objpath, properties):
1621
@dbus.service.signal(_interface, signature=u"s")
1622
def ClientNotFound(self, fingerprint):
1626
@dbus.service.signal(_interface, signature=u"os")
1627
def ClientRemoved(self, objpath, name):
1631
@dbus.service.method(_interface, out_signature=u"ao")
1632
def GetAllClients(self):
1634
return dbus.Array(c.dbus_object_path
1635
for c in tcp_server.clients)
1637
@dbus.service.method(_interface,
1638
out_signature=u"a{oa{sv}}")
1639
def GetAllClientsWithProperties(self):
1641
return dbus.Dictionary(
1642
((c.dbus_object_path, c.GetAll(u""))
1643
for c in tcp_server.clients),
1644
signature=u"oa{sv}")
1646
@dbus.service.method(_interface, in_signature=u"o")
1647
def RemoveClient(self, object_path):
1649
for c in tcp_server.clients:
1650
if c.dbus_object_path == object_path:
1651
tcp_server.clients.remove(c)
1652
c.remove_from_connection()
1653
# Don't signal anything except ClientRemoved
1654
c.disable(signal=False)
1656
self.ClientRemoved(object_path, c.name)
1662
mandos_dbus_service = MandosDBusService()
1664
for client in tcp_server.clients:
1667
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1672
tcp_server.server_activate()
1674
# Find out what port we got
1675
service.port = tcp_server.socket.getsockname()[1]
1677
logger.info(u"Now listening on address %r, port %d,"
1678
" flowinfo %d, scope_id %d"
1679
% tcp_server.socket.getsockname())
1681
logger.info(u"Now listening on address %r, port %d"
1682
% tcp_server.socket.getsockname())
1684
#service.interface = tcp_server.socket.getsockname()[3]
1687
# From the Avahi example code
1690
except dbus.exceptions.DBusException, error:
1691
logger.critical(u"DBusException: %s", error)
1693
# End of Avahi example code
1695
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
1696
lambda *args, **kwargs:
1697
(tcp_server.handle_request
1698
(*args[2:], **kwargs) or True))
1700
logger.debug(u"Starting main loop")
728
signal.signal(signal.SIGHUP, lambda signum, frame: killme())
729
signal.signal(signal.SIGTERM, lambda signum, frame: killme())
731
for client in clients:
734
tcp_server = IPv6_TCPServer((options.address, options.port),
738
# Find out what random port we got
740
servicePort = tcp_server.socket.getsockname()[1]
741
logger.debug(u"Now listening on port %d", servicePort)
743
if options.interface is not None:
744
global serviceInterface
745
serviceInterface = if_nametoindex(options.interface)
747
# From the Avahi example code
748
server.connect_to_signal("StateChanged", server_state_changed)
750
server_state_changed(server.GetState())
751
except dbus.exceptions.DBusException, error:
752
logger.critical(u"DBusException: %s", error)
754
# End of Avahi example code
756
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
757
lambda *args, **kwargs:
758
tcp_server.handle_request(*args[2:],
761
logger.debug("Starting main loop")
762
main_loop_started = True
1702
except AvahiError, error:
1703
logger.critical(u"AvahiError: %s", error)
1705
764
except KeyboardInterrupt:
1708
logger.debug(u"Server received KeyboardInterrupt")
1709
logger.debug(u"Server exiting")
1711
770
if __name__ == '__main__':