44
14
import gnutls.library.functions
45
15
import gnutls.library.constants
46
16
import gnutls.library.types
47
import ConfigParser as configparser
56
import logging.handlers
58
from contextlib import closing
67
28
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:'
91
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())
32
import logging.handlers
34
# logghandler.setFormatter(logging.Formatter('%(levelname)s %(message)s')
36
logger = logging.Logger('mandos')
37
logger.addHandler(logging.handlers.SysLogHandler(facility = logging.handlers.SysLogHandler.LOG_DAEMON))
39
# This variable is used to optionally bind to a specified interface.
40
# It is a global variable to fit in with the other variables from the
41
# Avahi server example code.
42
serviceInterface = avahi.IF_UNSPEC
43
# From the Avahi server example code:
44
serviceName = "Mandos"
45
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
46
servicePort = None # Not known at startup
47
serviceTXT = [] # TXT record for the service
48
domain = "" # Domain to publish on, default to .local
49
host = "" # Host to publish records for, default to localhost
50
group = None #our entry group
51
rename_count = 12 # Counter so we only rename after collisions a
52
# sensible number of times
53
# End of Avahi example code
230
56
class Client(object):
231
57
"""A representation of a client host served by this server.
234
name: string; from the config file, used in log messages and
59
name: string; from the config file, used in log messages
236
60
fingerprint: string (40 or 32 hexadecimal digits); used to
237
61
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.
62
secret: bytestring; sent verbatim (over TLS) to client
63
fqdn: string (FQDN); available for use by the checker command
64
created: datetime.datetime()
65
last_seen: datetime.datetime() or None if not yet seen
66
timeout: datetime.timedelta(); How long from last_seen until
67
this client is invalid
68
interval: datetime.timedelta(); How often to start a new checker
69
stop_hook: If set, called by stop() as stop_hook(self)
70
checker: subprocess.Popen(); a running checker process used
71
to see if the client lives.
72
Is None if no process is running.
251
73
checker_initiator_tag: a gobject event source tag, or None
252
disable_initiator_tag: - '' -
74
stop_initiator_tag: - '' -
253
75
checker_callback_tag: - '' -
254
76
checker_command: string; External command which is run to check if
255
client lives. %() expansions are done at
77
client lives. %()s expansions are done at
256
78
runtime with vars(self) as dict, so that for
257
79
instance %(name)s can be used in the command.
258
current_checker_command: string; current running checker_command
81
_timeout: Real variable for 'timeout'
82
_interval: Real variable for 'interval'
83
_timeout_milliseconds: Used by gobject.timeout_add()
84
_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'
86
def _set_timeout(self, timeout):
87
"Setter function for 'timeout' attribute"
88
self._timeout = timeout
89
self._timeout_milliseconds = ((self.timeout.days
90
* 24 * 60 * 60 * 1000)
91
+ (self.timeout.seconds * 1000)
92
+ (self.timeout.microseconds
94
timeout = property(lambda self: self._timeout,
97
def _set_interval(self, interval):
98
"Setter function for 'interval' attribute"
99
self._interval = interval
100
self._interval_milliseconds = ((self.interval.days
101
* 24 * 60 * 60 * 1000)
102
+ (self.interval.seconds
104
+ (self.interval.microseconds
106
interval = property(lambda self: self._interval,
109
def __init__(self, name=None, options=None, stop_hook=None,
110
fingerprint=None, secret=None, secfile=None, fqdn=None,
111
timeout=None, interval=-1, checker=None):
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"])))) as secfile:
296
self.secret = secfile.read()
298
raise TypeError(u"No secret or secfile for client %s"
300
self.host = config.get(u"host", u"")
301
self.created = datetime.datetime.utcnow()
303
self.last_enabled = None
304
self.last_checked_ok = None
305
self.timeout = string_to_delta(config[u"timeout"])
306
self.interval = string_to_delta(config[u"interval"])
307
self.disable_hook = disable_hook
113
# Uppercase and remove spaces from fingerprint
114
# for later comparison purposes with return value of
115
# the fingerprint() function
116
self.fingerprint = fingerprint.upper().replace(u" ", u"")
118
self.secret = secret.decode(u"base64")
121
self.secret = sf.read()
124
raise RuntimeError(u"No secret or secfile for client %s"
126
self.fqdn = fqdn # string
127
self.created = datetime.datetime.now()
128
self.last_seen = None
130
timeout = options.timeout
131
self.timeout = timeout
133
interval = options.interval
135
interval = string_to_delta(interval)
136
self.interval = interval
137
self.stop_hook = stop_hook
308
138
self.checker = None
309
139
self.checker_initiator_tag = None
310
self.disable_initiator_tag = None
140
self.stop_initiator_tag = None
311
141
self.checker_callback_tag = None
312
self.checker_command = config[u"checker"]
313
self.current_checker_command = None
314
self.last_connect = None
317
"""Start this client's checker and timeout hooks"""
318
if getattr(self, u"enabled", False):
321
self.last_enabled = datetime.datetime.utcnow()
142
self.check_command = checker
144
"""Start this clients checker and timeout hooks"""
322
145
# Schedule a new checker to be started an 'interval' from now,
323
146
# and every interval from then on.
324
self.checker_initiator_tag = (gobject.timeout_add
325
(self.interval_milliseconds(),
147
self.checker_initiator_tag = gobject.timeout_add\
148
(self._interval_milliseconds,
327
150
# Also start a new checker *right now*.
328
151
self.start_checker()
329
# Schedule a disable() when 'timeout' has passed
330
self.disable_initiator_tag = (gobject.timeout_add
331
(self.timeout_milliseconds(),
336
"""Disable this client."""
337
if not getattr(self, "enabled", False):
339
logger.info(u"Disabling client %s", self.name)
340
if getattr(self, u"disable_initiator_tag", False):
341
gobject.source_remove(self.disable_initiator_tag)
342
self.disable_initiator_tag = None
343
if getattr(self, u"checker_initiator_tag", False):
152
# Schedule a stop() when 'timeout' has passed
153
self.stop_initiator_tag = gobject.timeout_add\
154
(self._timeout_milliseconds,
158
The possibility that this client might be restarted is left
159
open, but not currently used."""
160
logger.debug(u"Stopping client %s", self.name)
162
if self.stop_initiator_tag:
163
gobject.source_remove(self.stop_initiator_tag)
164
self.stop_initiator_tag = None
165
if self.checker_initiator_tag:
344
166
gobject.source_remove(self.checker_initiator_tag)
345
167
self.checker_initiator_tag = None
346
168
self.stop_checker()
347
if self.disable_hook:
348
self.disable_hook(self)
350
171
# Do not run this again if called by a gobject.timeout_add
353
173
def __del__(self):
354
self.disable_hook = None
357
def checker_callback(self, pid, condition, command):
174
# Some code duplication here and in stop()
175
if hasattr(self, "stop_initiator_tag") \
176
and self.stop_initiator_tag:
177
gobject.source_remove(self.stop_initiator_tag)
178
self.stop_initiator_tag = None
179
if hasattr(self, "checker_initiator_tag") \
180
and self.checker_initiator_tag:
181
gobject.source_remove(self.checker_initiator_tag)
182
self.checker_initiator_tag = None
184
def checker_callback(self, pid, condition):
358
185
"""The checker has completed, so take appropriate actions."""
359
self.checker_callback_tag = None
361
if os.WIFEXITED(condition):
362
exitstatus = os.WEXITSTATUS(condition)
364
logger.info(u"Checker for %(name)s succeeded",
368
logger.info(u"Checker for %(name)s failed",
186
now = datetime.datetime.now()
187
if os.WIFEXITED(condition) \
188
and (os.WEXITSTATUS(condition) == 0):
189
logger.debug(u"Checker for %(name)s succeeded",
192
gobject.source_remove(self.stop_initiator_tag)
193
self.stop_initiator_tag = gobject.timeout_add\
194
(self._timeout_milliseconds,
196
if not os.WIFEXITED(condition):
371
197
logger.warning(u"Checker for %(name)s crashed?",
374
def checked_ok(self):
375
"""Bump up the timeout for this client.
377
This should only be called when the client has been seen,
380
self.last_checked_ok = datetime.datetime.utcnow()
381
gobject.source_remove(self.disable_initiator_tag)
382
self.disable_initiator_tag = (gobject.timeout_add
383
(self.timeout_milliseconds(),
200
logger.debug(u"Checker for %(name)s failed",
203
self.checker_callback_tag = None
386
204
def start_checker(self):
387
205
"""Start a new checker subprocess if one is not running.
389
206
If a checker already exists, leave it running and do
391
# The reason for not killing a running checker is that if we
392
# did that, then if a checker (for some reason) started
393
# running slowly and taking more than 'interval' time, the
394
# client would inevitably timeout, since no checker would get
395
# a chance to run to completion. If we instead leave running
396
# checkers alone, the checker would have to take more time
397
# than 'timeout' for the client to be declared invalid, which
398
# is as it should be.
400
# If a checker exists, make sure it is not a zombie
401
if self.checker is not None:
402
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
404
logger.warning(u"Checker was a zombie")
405
gobject.source_remove(self.checker_callback_tag)
406
self.checker_callback(pid, status,
407
self.current_checker_command)
408
# Start a new checker if needed
409
208
if self.checker is None:
209
logger.debug(u"Starting checker for %s",
411
# In case checker_command has exactly one % operator
412
command = self.checker_command % self.host
212
command = self.check_command % self.fqdn
413
213
except TypeError:
414
# Escape attributes for the shell
415
escaped_attrs = dict((key,
416
re.escape(unicode(str(val),
214
escaped_attrs = dict((key, re.escape(str(val)))
420
216
vars(self).iteritems())
422
command = self.checker_command % escaped_attrs
218
command = self.check_command % escaped_attrs
423
219
except TypeError, error:
424
logger.error(u'Could not format string "%s":'
425
u' %s', self.checker_command, error)
220
logger.critical(u'Could not format string "%s": %s',
221
self.check_command, error)
426
222
return True # Try again later
427
self.current_checker_command = command
429
logger.info(u"Starting checker %r for %s",
431
# We don't need to redirect stdout and stderr, since
432
# in normal mode, that is already done by daemon(),
433
# and in debug mode we don't want to. (Stdin is
434
# always replaced by /dev/null.)
435
self.checker = subprocess.Popen(command,
437
shell=True, cwd=u"/")
438
self.checker_callback_tag = (gobject.child_watch_add
440
self.checker_callback,
442
# The checker may have completed before the gobject
443
# watch was added. Check for this.
444
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
446
gobject.source_remove(self.checker_callback_tag)
447
self.checker_callback(pid, status, command)
448
except OSError, error:
224
self.checker = subprocess.\
226
stdout=subprocess.PIPE,
227
close_fds=True, shell=True,
229
self.checker_callback_tag = gobject.\
230
child_watch_add(self.checker.pid,
233
except subprocess.OSError, error:
449
234
logger.error(u"Failed to start subprocess: %s",
451
236
# Re-run this periodically if run by gobject.timeout_add
454
238
def stop_checker(self):
455
239
"""Force the checker process, if any, to stop."""
456
if self.checker_callback_tag:
457
gobject.source_remove(self.checker_callback_tag)
458
self.checker_callback_tag = None
459
if getattr(self, u"checker", None) is None:
240
if not hasattr(self, "checker") or self.checker is None:
461
logger.debug(u"Stopping checker for %(name)s", vars(self))
463
os.kill(self.checker.pid, signal.SIGTERM)
465
#if self.checker.poll() is None:
466
# os.kill(self.checker.pid, signal.SIGKILL)
467
except OSError, error:
468
if error.errno != errno.ESRCH: # No such process
242
gobject.source_remove(self.checker_callback_tag)
243
self.checker_callback_tag = None
244
os.kill(self.checker.pid, signal.SIGTERM)
245
if self.checker.poll() is None:
246
os.kill(self.checker.pid, signal.SIGKILL)
470
247
self.checker = None
472
def still_valid(self):
248
def still_valid(self, now=None):
473
249
"""Has the timeout not yet passed for this client?"""
474
if not getattr(self, u"enabled", False):
476
now = datetime.datetime.utcnow()
477
if self.last_checked_ok is None:
251
now = datetime.datetime.now()
252
if self.last_seen is None:
478
253
return now < (self.created + self.timeout)
480
return now < (self.last_checked_ok + self.timeout)
483
def dbus_service_property(dbus_interface, signature=u"v",
484
access=u"readwrite", byte_arrays=False):
485
"""Decorators for marking methods of a DBusObjectWithProperties to
486
become properties on the D-Bus.
488
The decorated method will be called with no arguments by "Get"
489
and with one argument by "Set".
491
The parameters, where they are supported, are the same as
492
dbus.service.method, except there is only "signature", since the
493
type from Get() and the type sent to Set() is the same.
496
func._dbus_is_property = True
497
func._dbus_interface = dbus_interface
498
func._dbus_signature = signature
499
func._dbus_access = access
500
func._dbus_name = func.__name__
501
if func._dbus_name.endswith(u"_dbus_property"):
502
func._dbus_name = func._dbus_name[:-14]
503
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
508
class DBusPropertyException(dbus.exceptions.DBusException):
509
"""A base class for D-Bus property-related exceptions
511
def __unicode__(self):
512
return unicode(str(self))
515
class DBusPropertyAccessException(DBusPropertyException):
516
"""A property's access permissions disallows an operation.
521
class DBusPropertyNotFound(DBusPropertyException):
522
"""An attempt was made to access a non-existing property.
527
class DBusObjectWithProperties(dbus.service.Object):
528
"""A D-Bus object with properties.
530
Classes inheriting from this can use the dbus_service_property
531
decorator to expose methods as D-Bus properties. It exposes the
532
standard Get(), Set(), and GetAll() methods on the D-Bus.
536
def _is_dbus_property(obj):
537
return getattr(obj, u"_dbus_is_property", False)
539
def _get_all_dbus_properties(self):
540
"""Returns a generator of (name, attribute) pairs
542
return ((prop._dbus_name, prop)
544
inspect.getmembers(self, self._is_dbus_property))
546
def _get_dbus_property(self, interface_name, property_name):
547
"""Returns a bound method if one exists which is a D-Bus
548
property with the specified name and interface.
550
for name in (property_name,
551
property_name + u"_dbus_property"):
552
prop = getattr(self, name, None)
554
or not self._is_dbus_property(prop)
555
or prop._dbus_name != property_name
556
or (interface_name and prop._dbus_interface
557
and interface_name != prop._dbus_interface)):
561
raise DBusPropertyNotFound(self.dbus_object_path + u":"
562
+ interface_name + u"."
565
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
567
def Get(self, interface_name, property_name):
568
"""Standard D-Bus property Get() method, see D-Bus standard.
570
prop = self._get_dbus_property(interface_name, property_name)
571
if prop._dbus_access == u"write":
572
raise DBusPropertyAccessException(property_name)
574
if not hasattr(value, u"variant_level"):
576
return type(value)(value, variant_level=value.variant_level+1)
578
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
579
def Set(self, interface_name, property_name, value):
580
"""Standard D-Bus property Set() method, see D-Bus standard.
582
prop = self._get_dbus_property(interface_name, property_name)
583
if prop._dbus_access == u"read":
584
raise DBusPropertyAccessException(property_name)
585
if prop._dbus_get_args_options[u"byte_arrays"]:
586
value = dbus.ByteArray(''.join(unichr(byte)
590
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
591
out_signature=u"a{sv}")
592
def GetAll(self, interface_name):
593
"""Standard D-Bus property GetAll() method, see D-Bus
596
Note: Will not include properties with access="write".
599
for name, prop in self._get_all_dbus_properties():
601
and interface_name != prop._dbus_interface):
602
# Interface non-empty but did not match
604
# Ignore write-only properties
605
if prop._dbus_access == u"write":
608
if not hasattr(value, u"variant_level"):
611
all[name] = type(value)(value, variant_level=
612
value.variant_level+1)
613
return dbus.Dictionary(all, signature=u"sv")
615
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
617
path_keyword='object_path',
618
connection_keyword='connection')
619
def Introspect(self, object_path, connection):
620
"""Standard D-Bus method, overloaded to insert property tags.
622
xmlstring = dbus.service.Object.Introspect(self, object_path,
624
document = xml.dom.minidom.parseString(xmlstring)
626
def make_tag(document, name, prop):
627
e = document.createElement(u"property")
628
e.setAttribute(u"name", name)
629
e.setAttribute(u"type", prop._dbus_signature)
630
e.setAttribute(u"access", prop._dbus_access)
632
for if_tag in document.getElementsByTagName(u"interface"):
633
for tag in (make_tag(document, name, prop)
635
in self._get_all_dbus_properties()
636
if prop._dbus_interface
637
== if_tag.getAttribute(u"name")):
638
if_tag.appendChild(tag)
639
xmlstring = document.toxml(u"utf-8")
644
class ClientDBus(Client, DBusObjectWithProperties):
645
"""A Client class using D-Bus
648
dbus_object_path: dbus.ObjectPath
649
bus: dbus.SystemBus()
651
# dbus.service.Object doesn't use super(), so we can't either.
653
def __init__(self, bus = None, *args, **kwargs):
655
Client.__init__(self, *args, **kwargs)
656
# Only now, when this client is initialized, can it show up on
658
self.dbus_object_path = (dbus.ObjectPath
660
+ self.name.replace(u".", u"_")))
661
DBusObjectWithProperties.__init__(self, self.bus,
662
self.dbus_object_path)
665
def _datetime_to_dbus(dt, variant_level=0):
666
"""Convert a UTC datetime.datetime() to a D-Bus type."""
667
return dbus.String(dt.isoformat(),
668
variant_level=variant_level)
671
oldstate = getattr(self, u"enabled", False)
672
r = Client.enable(self)
673
if oldstate != self.enabled:
675
self.PropertyChanged(dbus.String(u"enabled"),
676
dbus.Boolean(True, variant_level=1))
677
self.PropertyChanged(
678
dbus.String(u"last_enabled"),
679
self._datetime_to_dbus(self.last_enabled,
683
def disable(self, signal = True):
684
oldstate = getattr(self, u"enabled", False)
685
r = Client.disable(self)
686
if signal and oldstate != self.enabled:
688
self.PropertyChanged(dbus.String(u"enabled"),
689
dbus.Boolean(False, variant_level=1))
692
def __del__(self, *args, **kwargs):
694
self.remove_from_connection()
697
if hasattr(DBusObjectWithProperties, u"__del__"):
698
DBusObjectWithProperties.__del__(self, *args, **kwargs)
699
Client.__del__(self, *args, **kwargs)
701
def checker_callback(self, pid, condition, command,
703
self.checker_callback_tag = None
706
self.PropertyChanged(dbus.String(u"checker_running"),
707
dbus.Boolean(False, variant_level=1))
708
if os.WIFEXITED(condition):
709
exitstatus = os.WEXITSTATUS(condition)
711
self.CheckerCompleted(dbus.Int16(exitstatus),
712
dbus.Int64(condition),
713
dbus.String(command))
716
self.CheckerCompleted(dbus.Int16(-1),
717
dbus.Int64(condition),
718
dbus.String(command))
720
return Client.checker_callback(self, pid, condition, command,
723
def checked_ok(self, *args, **kwargs):
724
r = Client.checked_ok(self, *args, **kwargs)
726
self.PropertyChanged(
727
dbus.String(u"last_checked_ok"),
728
(self._datetime_to_dbus(self.last_checked_ok,
732
def start_checker(self, *args, **kwargs):
733
old_checker = self.checker
734
if self.checker is not None:
735
old_checker_pid = self.checker.pid
737
old_checker_pid = None
738
r = Client.start_checker(self, *args, **kwargs)
739
# Only if new checker process was started
740
if (self.checker is not None
741
and old_checker_pid != self.checker.pid):
743
self.CheckerStarted(self.current_checker_command)
744
self.PropertyChanged(
745
dbus.String(u"checker_running"),
746
dbus.Boolean(True, variant_level=1))
749
def stop_checker(self, *args, **kwargs):
750
old_checker = getattr(self, u"checker", None)
751
r = Client.stop_checker(self, *args, **kwargs)
752
if (old_checker is not None
753
and getattr(self, u"checker", None) is None):
754
self.PropertyChanged(dbus.String(u"checker_running"),
755
dbus.Boolean(False, variant_level=1))
758
## D-Bus methods & signals
759
_interface = u"se.bsnet.fukt.Mandos.Client"
761
# CheckerCompleted - signal
762
@dbus.service.signal(_interface, signature=u"nxs")
763
def CheckerCompleted(self, exitcode, waitstatus, command):
767
# CheckerStarted - signal
768
@dbus.service.signal(_interface, signature=u"s")
769
def CheckerStarted(self, command):
773
# PropertyChanged - signal
774
@dbus.service.signal(_interface, signature=u"sv")
775
def PropertyChanged(self, property, value):
779
# ReceivedSecret - signal
780
@dbus.service.signal(_interface)
781
def ReceivedSecret(self):
786
@dbus.service.signal(_interface)
792
@dbus_service_property(_interface, signature=u"s", access=u"read")
793
def name_dbus_property(self):
794
return dbus.String(self.name)
796
# fingerprint - property
797
@dbus_service_property(_interface, signature=u"s", access=u"read")
798
def fingerprint_dbus_property(self):
799
return dbus.String(self.fingerprint)
802
@dbus_service_property(_interface, signature=u"s",
804
def host_dbus_property(self, value=None):
805
if value is None: # get
806
return dbus.String(self.host)
809
self.PropertyChanged(dbus.String(u"host"),
810
dbus.String(value, variant_level=1))
813
@dbus_service_property(_interface, signature=u"s", access=u"read")
814
def created_dbus_property(self):
815
return dbus.String(self._datetime_to_dbus(self.created))
817
# last_enabled - property
818
@dbus_service_property(_interface, signature=u"s", access=u"read")
819
def last_enabled_dbus_property(self):
820
if self.last_enabled is None:
821
return dbus.String(u"")
822
return dbus.String(self._datetime_to_dbus(self.last_enabled))
825
@dbus_service_property(_interface, signature=u"b",
827
def enabled_dbus_property(self, value=None):
828
if value is None: # get
829
return dbus.Boolean(self.enabled)
835
# last_checked_ok - property
836
@dbus_service_property(_interface, signature=u"s", access=u"read")
837
def last_checked_ok_dbus_property(self):
838
if self.last_checked_ok is None:
839
return dbus.String(u"")
840
return dbus.String(self._datetime_to_dbus(self
844
@dbus_service_property(_interface, signature=u"t",
846
def timeout_dbus_property(self, value=None):
847
if value is None: # get
848
return dbus.UInt64(self.timeout_milliseconds())
849
self.timeout = datetime.timedelta(0, 0, 0, value)
851
self.PropertyChanged(dbus.String(u"timeout"),
852
dbus.UInt64(value, variant_level=1))
853
if getattr(self, u"disable_initiator_tag", None) is None:
856
gobject.source_remove(self.disable_initiator_tag)
857
self.disable_initiator_tag = None
859
_timedelta_to_milliseconds((self
865
# The timeout has passed
868
self.disable_initiator_tag = (gobject.timeout_add
869
(time_to_die, self.disable))
871
# interval - property
872
@dbus_service_property(_interface, signature=u"t",
874
def interval_dbus_property(self, value=None):
875
if value is None: # get
876
return dbus.UInt64(self.interval_milliseconds())
877
self.interval = datetime.timedelta(0, 0, 0, value)
879
self.PropertyChanged(dbus.String(u"interval"),
880
dbus.UInt64(value, variant_level=1))
881
if getattr(self, u"checker_initiator_tag", None) is None:
883
# Reschedule checker run
884
gobject.source_remove(self.checker_initiator_tag)
885
self.checker_initiator_tag = (gobject.timeout_add
886
(value, self.start_checker))
887
self.start_checker() # Start one now, too
890
@dbus_service_property(_interface, signature=u"s",
892
def checker_dbus_property(self, value=None):
893
if value is None: # get
894
return dbus.String(self.checker_command)
895
self.checker_command = value
897
self.PropertyChanged(dbus.String(u"checker"),
898
dbus.String(self.checker_command,
901
# checker_running - property
902
@dbus_service_property(_interface, signature=u"b",
904
def checker_running_dbus_property(self, value=None):
905
if value is None: # get
906
return dbus.Boolean(self.checker is not None)
912
# object_path - property
913
@dbus_service_property(_interface, signature=u"o", access=u"read")
914
def object_path_dbus_property(self):
915
return self.dbus_object_path # is already a dbus.ObjectPath
917
# secret = property xxx
918
@dbus_service_property(_interface, signature=u"ay",
919
access=u"write", byte_arrays=True)
920
def secret_dbus_property(self, value):
921
self.secret = str(value)
926
class ClientHandler(socketserver.BaseRequestHandler, object):
927
"""A class to handle client connections.
929
Instantiated once for each connection to handle it.
255
return now < (self.last_seen + self.timeout)
258
def peer_certificate(session):
259
# If not an OpenPGP certificate...
260
if gnutls.library.functions.gnutls_certificate_type_get\
261
(session._c_object) \
262
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
263
# ...do the normal thing
264
return session.peer_certificate
265
list_size = ctypes.c_uint()
266
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
267
(session._c_object, ctypes.byref(list_size))
268
if list_size.value == 0:
271
return ctypes.string_at(cert.data, cert.size)
274
def fingerprint(openpgp):
275
# New empty GnuTLS certificate
276
crt = gnutls.library.types.gnutls_openpgp_crt_t()
277
gnutls.library.functions.gnutls_openpgp_crt_init\
279
# New GnuTLS "datum" with the OpenPGP public key
280
datum = gnutls.library.types.gnutls_datum_t\
281
(ctypes.cast(ctypes.c_char_p(openpgp),
282
ctypes.POINTER(ctypes.c_ubyte)),
283
ctypes.c_uint(len(openpgp)))
284
# Import the OpenPGP public key into the certificate
285
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
288
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
289
# New buffer for the fingerprint
290
buffer = ctypes.create_string_buffer(20)
291
buffer_length = ctypes.c_size_t()
292
# Get the fingerprint from the certificate into the buffer
293
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
294
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
295
# Deinit the certificate
296
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
297
# Convert the buffer to a Python bytestring
298
fpr = ctypes.string_at(buffer, buffer_length.value)
299
# Convert the bytestring to hexadecimal notation
300
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
304
class tcp_handler(SocketServer.BaseRequestHandler, object):
305
"""A TCP request handler class.
306
Instantiated by IPv6_TCPServer for each request to handle it.
930
307
Note: This will run in its own forked process."""
932
309
def handle(self):
933
logger.info(u"TCP connection from: %s",
934
unicode(self.client_address))
935
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
936
# Open IPC pipe to parent process
937
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
938
session = (gnutls.connection
939
.ClientSession(self.request,
943
line = self.request.makefile().readline()
944
logger.debug(u"Protocol version: %r", line)
946
if int(line.strip().split()[0]) > 1:
948
except (ValueError, IndexError, RuntimeError), error:
949
logger.error(u"Unknown protocol version: %s", error)
952
# Note: gnutls.connection.X509Credentials is really a
953
# generic GnuTLS certificate credentials object so long as
954
# no X.509 keys are added to it. Therefore, we can use it
955
# here despite using OpenPGP certificates.
957
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
958
# u"+AES-256-CBC", u"+SHA1",
959
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
961
# Use a fallback default, since this MUST be set.
962
priority = self.server.gnutls_priority
965
(gnutls.library.functions
966
.gnutls_priority_set_direct(session._c_object,
971
except gnutls.errors.GNUTLSError, error:
972
logger.warning(u"Handshake failed: %s", error)
973
# Do not run session.bye() here: the session is not
974
# established. Just abandon the request.
976
logger.debug(u"Handshake succeeded")
978
fpr = self.fingerprint(self.peer_certificate(session))
979
except (TypeError, gnutls.errors.GNUTLSError), error:
980
logger.warning(u"Bad certificate: %s", error)
983
logger.debug(u"Fingerprint: %s", fpr)
985
for c in self.server.clients:
986
if c.fingerprint == fpr:
310
logger.debug(u"TCP connection from: %s",
311
unicode(self.client_address))
312
session = gnutls.connection.ClientSession(self.request,
316
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
317
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
319
priority = "SECURE256"
321
gnutls.library.functions.gnutls_priority_set_direct\
322
(session._c_object, priority, None);
326
except gnutls.errors.GNUTLSError, error:
327
logger.debug(u"Handshake failed: %s", error)
328
# Do not run session.bye() here: the session is not
329
# established. Just abandon the request.
332
fpr = fingerprint(peer_certificate(session))
333
except (TypeError, gnutls.errors.GNUTLSError), error:
334
logger.debug(u"Bad certificate: %s", error)
337
logger.debug(u"Fingerprint: %s", fpr)
340
if c.fingerprint == fpr:
343
# Have to check if client.still_valid(), since it is possible
344
# that the client timed out while establishing the GnuTLS
346
if (not client) or (not client.still_valid()):
348
logger.debug(u"Client %(name)s is invalid",
990
ipc.write(u"NOTFOUND %s %s\n"
991
% (fpr, unicode(self.client_address)))
994
# Have to check if client.still_valid(), since it is
995
# possible that the client timed out while establishing
996
# the GnuTLS session.
997
if not client.still_valid():
998
ipc.write(u"INVALID %s\n" % client.name)
1001
ipc.write(u"SENDING %s\n" % client.name)
1003
while sent_size < len(client.secret):
1004
sent = session.send(client.secret[sent_size:])
1005
logger.debug(u"Sent: %d, remaining: %d",
1006
sent, len(client.secret)
1007
- (sent_size + sent))
351
logger.debug(u"Client not found for fingerprint: %s",
1012
def peer_certificate(session):
1013
"Return the peer's OpenPGP certificate as a bytestring"
1014
# If not an OpenPGP certificate...
1015
if (gnutls.library.functions
1016
.gnutls_certificate_type_get(session._c_object)
1017
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1018
# ...do the normal thing
1019
return session.peer_certificate
1020
list_size = ctypes.c_uint(1)
1021
cert_list = (gnutls.library.functions
1022
.gnutls_certificate_get_peers
1023
(session._c_object, ctypes.byref(list_size)))
1024
if not bool(cert_list) and list_size.value != 0:
1025
raise gnutls.errors.GNUTLSError(u"error getting peer"
1027
if list_size.value == 0:
1030
return ctypes.string_at(cert.data, cert.size)
1033
def fingerprint(openpgp):
1034
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1035
# New GnuTLS "datum" with the OpenPGP public key
1036
datum = (gnutls.library.types
1037
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1040
ctypes.c_uint(len(openpgp))))
1041
# New empty GnuTLS certificate
1042
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1043
(gnutls.library.functions
1044
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1045
# Import the OpenPGP public key into the certificate
1046
(gnutls.library.functions
1047
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1048
gnutls.library.constants
1049
.GNUTLS_OPENPGP_FMT_RAW))
1050
# Verify the self signature in the key
1051
crtverify = ctypes.c_uint()
1052
(gnutls.library.functions
1053
.gnutls_openpgp_crt_verify_self(crt, 0,
1054
ctypes.byref(crtverify)))
1055
if crtverify.value != 0:
1056
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1057
raise (gnutls.errors.CertificateSecurityError
1059
# New buffer for the fingerprint
1060
buf = ctypes.create_string_buffer(20)
1061
buf_len = ctypes.c_size_t()
1062
# Get the fingerprint from the certificate into the buffer
1063
(gnutls.library.functions
1064
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1065
ctypes.byref(buf_len)))
1066
# Deinit the certificate
1067
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1068
# Convert the buffer to a Python bytestring
1069
fpr = ctypes.string_at(buf, buf_len.value)
1070
# Convert the bytestring to hexadecimal notation
1071
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1075
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1076
"""Like socketserver.ForkingMixIn, but also pass a pipe."""
1077
def process_request(self, request, client_address):
1078
"""Overrides and wraps the original process_request().
1080
This function creates a new pipe in self.pipe
1082
self.pipe = os.pipe()
1083
super(ForkingMixInWithPipe,
1084
self).process_request(request, client_address)
1085
os.close(self.pipe[1]) # close write end
1086
self.add_pipe(self.pipe[0])
1087
def add_pipe(self, pipe):
1088
"""Dummy function; override as necessary"""
1092
class IPv6_TCPServer(ForkingMixInWithPipe,
1093
socketserver.TCPServer, object):
1094
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
356
while sent_size < len(client.secret):
357
sent = session.send(client.secret[sent_size:])
358
logger.debug(u"Sent: %d, remaining: %d",
359
sent, len(client.secret)
360
- (sent_size + sent))
365
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
366
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1097
enabled: Boolean; whether this server is activated yet
1098
interface: None or a network interface name (string)
1099
use_ipv6: Boolean; to use IPv6 or not
368
options: Command line options
369
clients: Set() of Client objects
1101
def __init__(self, server_address, RequestHandlerClass,
1102
interface=None, use_ipv6=True):
1103
self.interface = interface
1105
self.address_family = socket.AF_INET6
1106
socketserver.TCPServer.__init__(self, server_address,
1107
RequestHandlerClass)
371
address_family = socket.AF_INET6
372
def __init__(self, *args, **kwargs):
373
if "options" in kwargs:
374
self.options = kwargs["options"]
375
del kwargs["options"]
376
if "clients" in kwargs:
377
self.clients = kwargs["clients"]
378
del kwargs["clients"]
379
return super(type(self), self).__init__(*args, **kwargs)
1108
380
def server_bind(self):
1109
381
"""This overrides the normal server_bind() function
1110
382
to bind to an interface if one was specified, and also NOT to
1111
383
bind to an address or port if they were not specified."""
1112
if self.interface is not None:
1113
if SO_BINDTODEVICE is None:
1114
logger.error(u"SO_BINDTODEVICE does not exist;"
1115
u" cannot bind to interface %s",
1119
self.socket.setsockopt(socket.SOL_SOCKET,
1123
except socket.error, error:
1124
if error[0] == errno.EPERM:
1125
logger.error(u"No permission to"
1126
u" bind to interface %s",
1128
elif error[0] == errno.ENOPROTOOPT:
1129
logger.error(u"SO_BINDTODEVICE not available;"
1130
u" cannot bind to interface %s",
384
if self.options.interface:
385
if not hasattr(socket, "SO_BINDTODEVICE"):
386
# From /usr/include/asm-i486/socket.h
387
socket.SO_BINDTODEVICE = 25
389
self.socket.setsockopt(socket.SOL_SOCKET,
390
socket.SO_BINDTODEVICE,
391
self.options.interface)
392
except socket.error, error:
393
if error[0] == errno.EPERM:
394
logger.warning(u"No permission to"
395
u" bind to interface %s",
396
self.options.interface)
1134
399
# Only bind(2) the socket if we really need to.
1135
400
if self.server_address[0] or self.server_address[1]:
1136
401
if not self.server_address[0]:
1137
if self.address_family == socket.AF_INET6:
1138
any_address = u"::" # in6addr_any
1140
any_address = socket.INADDR_ANY
1141
self.server_address = (any_address,
403
self.server_address = (in6addr_any,
1142
404
self.server_address[1])
1143
elif not self.server_address[1]:
405
elif self.server_address[1] is None:
1144
406
self.server_address = (self.server_address[0],
1146
# if self.interface:
1147
# self.server_address = (self.server_address[0],
1152
return socketserver.TCPServer.server_bind(self)
1155
class MandosServer(IPv6_TCPServer):
1159
clients: set of Client objects
1160
gnutls_priority GnuTLS priority string
1161
use_dbus: Boolean; to emit D-Bus signals or not
1163
Assumes a gobject.MainLoop event loop.
1165
def __init__(self, server_address, RequestHandlerClass,
1166
interface=None, use_ipv6=True, clients=None,
1167
gnutls_priority=None, use_dbus=True):
1168
self.enabled = False
1169
self.clients = clients
1170
if self.clients is None:
1171
self.clients = set()
1172
self.use_dbus = use_dbus
1173
self.gnutls_priority = gnutls_priority
1174
IPv6_TCPServer.__init__(self, server_address,
1175
RequestHandlerClass,
1176
interface = interface,
1177
use_ipv6 = use_ipv6)
1178
def server_activate(self):
1180
return socketserver.TCPServer.server_activate(self)
1183
def add_pipe(self, pipe):
1184
# Call "handle_ipc" for both data and EOF events
1185
gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1187
def handle_ipc(self, source, condition, file_objects={}):
1189
gobject.IO_IN: u"IN", # There is data to read.
1190
gobject.IO_OUT: u"OUT", # Data can be written (without
1192
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1193
gobject.IO_ERR: u"ERR", # Error condition.
1194
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1195
# broken, usually for pipes and
1198
conditions_string = ' | '.join(name
1200
condition_names.iteritems()
1201
if cond & condition)
1202
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1205
# Turn the pipe file descriptor into a Python file object
1206
if source not in file_objects:
1207
file_objects[source] = os.fdopen(source, u"r", 1)
1209
# Read a line from the file object
1210
cmdline = file_objects[source].readline()
1211
if not cmdline: # Empty line means end of file
1212
# close the IPC pipe
1213
file_objects[source].close()
1214
del file_objects[source]
1216
# Stop calling this function
1219
logger.debug(u"IPC command: %r", cmdline)
1221
# Parse and act on command
1222
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1224
if cmd == u"NOTFOUND":
1225
logger.warning(u"Client not found for fingerprint: %s",
1229
mandos_dbus_service.ClientNotFound(args)
1230
elif cmd == u"INVALID":
1231
for client in self.clients:
1232
if client.name == args:
1233
logger.warning(u"Client %s is invalid", args)
1239
logger.error(u"Unknown client %s is invalid", args)
1240
elif cmd == u"SENDING":
1241
for client in self.clients:
1242
if client.name == args:
1243
logger.info(u"Sending secret to %s", client.name)
1247
client.ReceivedSecret()
1250
logger.error(u"Sending secret to unknown client %s",
1253
logger.error(u"Unknown IPC command: %r", cmdline)
1255
# Keep calling this function
408
return super(type(self), self).server_bind()
1259
411
def string_to_delta(interval):
1260
412
"""Parse a string and return a datetime.timedelta
1262
>>> string_to_delta(u'7d')
414
>>> string_to_delta('7d')
1263
415
datetime.timedelta(7)
1264
>>> string_to_delta(u'60s')
416
>>> string_to_delta('60s')
1265
417
datetime.timedelta(0, 60)
1266
>>> string_to_delta(u'60m')
418
>>> string_to_delta('60m')
1267
419
datetime.timedelta(0, 3600)
1268
>>> string_to_delta(u'24h')
420
>>> string_to_delta('24h')
1269
421
datetime.timedelta(1)
1270
422
>>> string_to_delta(u'1w')
1271
423
datetime.timedelta(7)
1272
>>> string_to_delta(u'5m 30s')
1273
datetime.timedelta(0, 330)
1275
timevalue = datetime.timedelta(0)
1276
for s in interval.split():
1278
suffix = unicode(s[-1])
1281
delta = datetime.timedelta(value)
1282
elif suffix == u"s":
1283
delta = datetime.timedelta(0, value)
1284
elif suffix == u"m":
1285
delta = datetime.timedelta(0, 0, 0, 0, value)
1286
elif suffix == u"h":
1287
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1288
elif suffix == u"w":
1289
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1292
except (ValueError, IndexError):
426
suffix=unicode(interval[-1])
427
value=int(interval[:-1])
429
delta = datetime.timedelta(value)
431
delta = datetime.timedelta(0, value)
433
delta = datetime.timedelta(0, 0, 0, 0, value)
435
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
437
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1293
439
raise ValueError
440
except (ValueError, IndexError):
446
"""From the Avahi server example code"""
447
global group, serviceName, serviceType, servicePort, serviceTXT, \
450
group = dbus.Interface(
451
bus.get_object( avahi.DBUS_NAME,
452
server.EntryGroupNew()),
453
avahi.DBUS_INTERFACE_ENTRY_GROUP)
454
group.connect_to_signal('StateChanged',
455
entry_group_state_changed)
456
logger.debug(u"Adding service '%s' of type '%s' ...",
457
serviceName, serviceType)
460
serviceInterface, # interface
461
avahi.PROTO_INET6, # protocol
462
dbus.UInt32(0), # flags
463
serviceName, serviceType,
465
dbus.UInt16(servicePort),
466
avahi.string_array_to_txt_array(serviceTXT))
470
def remove_service():
471
"""From the Avahi server example code"""
474
if not group is None:
478
def server_state_changed(state):
479
"""From the Avahi server example code"""
480
if state == avahi.SERVER_COLLISION:
481
logger.warning(u"Server name collision")
483
elif state == avahi.SERVER_RUNNING:
487
def entry_group_state_changed(state, error):
488
"""From the Avahi server example code"""
489
global serviceName, server, rename_count
491
logger.debug(u"state change: %i", state)
493
if state == avahi.ENTRY_GROUP_ESTABLISHED:
494
logger.debug(u"Service established.")
495
elif state == avahi.ENTRY_GROUP_COLLISION:
497
rename_count = rename_count - 1
499
name = server.GetAlternativeServiceName(name)
500
logger.warning(u"Service name collision, "
501
u"changing name to '%s' ...", name)
506
logger.error(u"No suitable service name found "
507
u"after %i retries, exiting.",
510
elif state == avahi.ENTRY_GROUP_FAILURE:
511
logger.error(u"Error in group state changed %s",
1298
517
def if_nametoindex(interface):
1299
"""Call the C function if_nametoindex(), or equivalent
1301
Note: This function cannot accept a unicode string."""
1302
global if_nametoindex
518
"""Call the C function if_nametoindex()"""
1304
if_nametoindex = (ctypes.cdll.LoadLibrary
1305
(ctypes.util.find_library(u"c"))
520
libc = ctypes.cdll.LoadLibrary("libc.so.6")
521
return libc.if_nametoindex(interface)
1307
522
except (OSError, AttributeError):
1308
logger.warning(u"Doing if_nametoindex the hard way")
1309
def if_nametoindex(interface):
1310
"Get an interface index the hard way, i.e. using fcntl()"
1311
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1312
with closing(socket.socket()) as s:
1313
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1314
struct.pack(str(u"16s16x"),
1316
interface_index = struct.unpack(str(u"I"),
1318
return interface_index
1319
return if_nametoindex(interface)
1322
def daemon(nochdir = False, noclose = False):
1323
"""See daemon(3). Standard BSD Unix function.
1325
This should really exist as os.daemon, but it doesn't (yet)."""
1334
# Close all standard open file descriptors
1335
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1336
if not stat.S_ISCHR(os.fstat(null).st_mode):
1337
raise OSError(errno.ENODEV,
1338
u"/dev/null not a character device")
1339
os.dup2(null, sys.stdin.fileno())
1340
os.dup2(null, sys.stdout.fileno())
1341
os.dup2(null, sys.stderr.fileno())
1348
##################################################################
1349
# Parsing of options, both command line and config file
1351
parser = optparse.OptionParser(version = "%%prog %s" % version)
1352
parser.add_option("-i", u"--interface", type=u"string",
1353
metavar="IF", help=u"Bind to interface IF")
1354
parser.add_option("-a", u"--address", type=u"string",
1355
help=u"Address to listen for requests on")
1356
parser.add_option("-p", u"--port", type=u"int",
1357
help=u"Port number to receive requests on")
1358
parser.add_option("--check", action=u"store_true",
1359
help=u"Run self-test")
1360
parser.add_option("--debug", action=u"store_true",
1361
help=u"Debug mode; run in foreground and log to"
1363
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1364
u" priority string (see GnuTLS documentation)")
1365
parser.add_option("--servicename", type=u"string",
1366
metavar=u"NAME", help=u"Zeroconf service name")
1367
parser.add_option("--configdir", type=u"string",
1368
default=u"/etc/mandos", metavar=u"DIR",
1369
help=u"Directory to search for configuration"
1371
parser.add_option("--no-dbus", action=u"store_false",
1372
dest=u"use_dbus", help=u"Do not provide D-Bus"
1373
u" system bus interface")
1374
parser.add_option("--no-ipv6", action=u"store_false",
1375
dest=u"use_ipv6", help=u"Do not use IPv6")
1376
options = parser.parse_args()[0]
523
if "struct" not in sys.modules:
525
if "fcntl" not in sys.modules:
527
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
529
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
530
struct.pack("16s16x", interface))
532
interface_index = struct.unpack("I", ifreq[16:20])[0]
533
return interface_index
536
if __name__ == '__main__':
537
parser = OptionParser()
538
parser.add_option("-i", "--interface", type="string",
539
default=None, metavar="IF",
540
help="Bind to interface IF")
541
parser.add_option("--cert", type="string", default="cert.pem",
543
help="Public key certificate PEM file to use")
544
parser.add_option("--key", type="string", default="key.pem",
546
help="Private key PEM file to use")
547
parser.add_option("--ca", type="string", default="ca.pem",
549
help="Certificate Authority certificate PEM file to use")
550
parser.add_option("--crl", type="string", default="crl.pem",
552
help="Certificate Revokation List PEM file to use")
553
parser.add_option("-p", "--port", type="int", default=None,
554
help="Port number to receive requests on")
555
parser.add_option("--timeout", type="string", # Parsed later
557
help="Amount of downtime allowed for clients")
558
parser.add_option("--interval", type="string", # Parsed later
560
help="How often to check that a client is up")
561
parser.add_option("--check", action="store_true", default=False,
562
help="Run self-test")
563
parser.add_option("--debug", action="store_true", default=False,
565
(options, args) = parser.parse_args()
1378
567
if options.check:
1380
569
doctest.testmod()
1383
# Default values for config file for server-global settings
1384
server_defaults = { u"interface": u"",
1389
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1390
u"servicename": u"Mandos",
1391
u"use_dbus": u"True",
1392
u"use_ipv6": u"True",
1395
# Parse config file for server-global settings
1396
server_config = configparser.SafeConfigParser(server_defaults)
1398
server_config.read(os.path.join(options.configdir,
1400
# Convert the SafeConfigParser object to a dict
1401
server_settings = server_config.defaults()
1402
# Use the appropriate methods on the non-string config options
1403
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1404
server_settings[option] = server_config.getboolean(u"DEFAULT",
1406
if server_settings["port"]:
1407
server_settings["port"] = server_config.getint(u"DEFAULT",
1411
# Override the settings from the config file with command line
1413
for option in (u"interface", u"address", u"port", u"debug",
1414
u"priority", u"servicename", u"configdir",
1415
u"use_dbus", u"use_ipv6"):
1416
value = getattr(options, option)
1417
if value is not None:
1418
server_settings[option] = value
1420
# Force all strings to be unicode
1421
for option in server_settings.keys():
1422
if type(server_settings[option]) is str:
1423
server_settings[option] = unicode(server_settings[option])
1424
# Now we have our good server settings in "server_settings"
1426
##################################################################
1429
debug = server_settings[u"debug"]
1430
use_dbus = server_settings[u"use_dbus"]
1431
use_ipv6 = server_settings[u"use_ipv6"]
1434
syslogger.setLevel(logging.WARNING)
1435
console.setLevel(logging.WARNING)
1437
if server_settings[u"servicename"] != u"Mandos":
1438
syslogger.setFormatter(logging.Formatter
1439
(u'Mandos (%s) [%%(process)d]:'
1440
u' %%(levelname)s: %%(message)s'
1441
% server_settings[u"servicename"]))
1443
# Parse config file with clients
1444
client_defaults = { u"timeout": u"1h",
1446
u"checker": u"fping -q -- %%(host)s",
1449
client_config = configparser.SafeConfigParser(client_defaults)
1450
client_config.read(os.path.join(server_settings[u"configdir"],
1453
global mandos_dbus_service
1454
mandos_dbus_service = None
1456
tcp_server = MandosServer((server_settings[u"address"],
1457
server_settings[u"port"]),
1459
interface=server_settings[u"interface"],
1462
server_settings[u"priority"],
1464
pidfilename = u"/var/run/mandos.pid"
1466
pidfile = open(pidfilename, u"w")
1468
logger.error(u"Could not open file %r", pidfilename)
1471
uid = pwd.getpwnam(u"_mandos").pw_uid
1472
gid = pwd.getpwnam(u"_mandos").pw_gid
1475
uid = pwd.getpwnam(u"mandos").pw_uid
1476
gid = pwd.getpwnam(u"mandos").pw_gid
1479
uid = pwd.getpwnam(u"nobody").pw_uid
1480
gid = pwd.getpwnam(u"nobody").pw_gid
1487
except OSError, error:
1488
if error[0] != errno.EPERM:
1491
# Enable all possible GnuTLS debugging
1493
# "Use a log level over 10 to enable all debugging options."
1495
gnutls.library.functions.gnutls_global_set_log_level(11)
1497
@gnutls.library.types.gnutls_log_func
1498
def debug_gnutls(level, string):
1499
logger.debug(u"GnuTLS: %s", string[:-1])
1501
(gnutls.library.functions
1502
.gnutls_global_set_log_function(debug_gnutls))
1505
# From the Avahi example code
572
# Parse the time arguments
574
options.timeout = string_to_delta(options.timeout)
576
parser.error("option --timeout: Unparseable time")
578
options.interval = string_to_delta(options.interval)
580
parser.error("option --interval: Unparseable time")
583
defaults = { "checker": "sleep 1; fping -q -- %%(fqdn)s" }
584
client_config = ConfigParser.SafeConfigParser(defaults)
585
#client_config.readfp(open("secrets.conf"), "secrets.conf")
586
client_config.read("mandos-clients.conf")
588
# From the Avahi server example code
1506
589
DBusGMainLoop(set_as_default=True )
1507
590
main_loop = gobject.MainLoop()
1508
591
bus = dbus.SystemBus()
1509
# End of Avahi example code
1511
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1512
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1513
service = AvahiService(name = server_settings[u"servicename"],
1514
servicetype = u"_mandos._tcp",
1515
protocol = protocol, bus = bus)
1516
if server_settings["interface"]:
1517
service.interface = (if_nametoindex
1518
(str(server_settings[u"interface"])))
1520
client_class = Client
1522
client_class = functools.partial(ClientDBus, bus = bus)
1523
tcp_server.clients.update(set(
1524
client_class(name = section,
1525
config= dict(client_config.items(section)))
1526
for section in client_config.sections()))
1527
if not tcp_server.clients:
1528
logger.warning(u"No clients defined")
1531
# Redirect stdin so all checkers get /dev/null
1532
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1533
os.dup2(null, sys.stdin.fileno())
1537
# No console logging
1538
logger.removeHandler(console)
1539
# Close all input and output, do double fork, etc.
1543
with closing(pidfile):
1545
pidfile.write(str(pid) + "\n")
1548
logger.error(u"Could not write to file %r with PID %d",
1551
# "pidfile" was never created
1556
"Cleanup function; run on exit"
1559
while tcp_server.clients:
1560
client = tcp_server.clients.pop()
1561
client.disable_hook = None
1564
atexit.register(cleanup)
1567
signal.signal(signal.SIGINT, signal.SIG_IGN)
1568
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1569
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1572
class MandosDBusService(dbus.service.Object):
1573
"""A D-Bus proxy object"""
1575
dbus.service.Object.__init__(self, bus, u"/")
1576
_interface = u"se.bsnet.fukt.Mandos"
1578
@dbus.service.signal(_interface, signature=u"oa{sv}")
1579
def ClientAdded(self, objpath, properties):
1583
@dbus.service.signal(_interface, signature=u"s")
1584
def ClientNotFound(self, fingerprint):
1588
@dbus.service.signal(_interface, signature=u"os")
1589
def ClientRemoved(self, objpath, name):
1593
@dbus.service.method(_interface, out_signature=u"ao")
1594
def GetAllClients(self):
1596
return dbus.Array(c.dbus_object_path
1597
for c in tcp_server.clients)
1599
@dbus.service.method(_interface,
1600
out_signature=u"a{oa{sv}}")
1601
def GetAllClientsWithProperties(self):
1603
return dbus.Dictionary(
1604
((c.dbus_object_path, c.GetAll(u""))
1605
for c in tcp_server.clients),
1606
signature=u"oa{sv}")
1608
@dbus.service.method(_interface, in_signature=u"o")
1609
def RemoveClient(self, object_path):
1611
for c in tcp_server.clients:
1612
if c.dbus_object_path == object_path:
1613
tcp_server.clients.remove(c)
1614
c.remove_from_connection()
1615
# Don't signal anything except ClientRemoved
1616
c.disable(signal=False)
1618
self.ClientRemoved(object_path, c.name)
1624
mandos_dbus_service = MandosDBusService()
1626
for client in tcp_server.clients:
1629
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1634
tcp_server.server_activate()
1636
# Find out what port we got
1637
service.port = tcp_server.socket.getsockname()[1]
1639
logger.info(u"Now listening on address %r, port %d,"
1640
" flowinfo %d, scope_id %d"
1641
% tcp_server.socket.getsockname())
1643
logger.info(u"Now listening on address %r, port %d"
1644
% tcp_server.socket.getsockname())
1646
#service.interface = tcp_server.socket.getsockname()[3]
1649
# From the Avahi example code
1652
except dbus.exceptions.DBusException, error:
1653
logger.critical(u"DBusException: %s", error)
1655
# End of Avahi example code
1657
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
1658
lambda *args, **kwargs:
1659
(tcp_server.handle_request
1660
(*args[2:], **kwargs) or True))
1662
logger.debug(u"Starting main loop")
592
server = dbus.Interface(
593
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
594
avahi.DBUS_INTERFACE_SERVER )
595
# End of Avahi example code
597
debug = options.debug
600
def remove_from_clients(client):
601
clients.remove(client)
603
logger.debug(u"No clients left, exiting")
606
clients.update(Set(Client(name=section, options=options,
607
stop_hook = remove_from_clients,
608
**(dict(client_config\
610
for section in client_config.sections()))
611
for client in clients:
614
tcp_server = IPv6_TCPServer((None, options.port),
618
# Find out what random port we got
619
servicePort = tcp_server.socket.getsockname()[1]
620
logger.debug(u"Now listening on port %d", servicePort)
622
if options.interface is not None:
623
serviceInterface = if_nametoindex(options.interface)
625
# From the Avahi server example code
626
server.connect_to_signal("StateChanged", server_state_changed)
627
server_state_changed(server.GetState())
628
# End of Avahi example code
630
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
631
lambda *args, **kwargs:
632
tcp_server.handle_request(*args[2:],
1664
except AvahiError, error:
1665
logger.critical(u"AvahiError: %s", error)
1667
636
except KeyboardInterrupt:
1670
logger.debug(u"Server received KeyboardInterrupt")
1671
logger.debug(u"Server exiting")
1673
if __name__ == '__main__':
641
# From the Avahi server example code
642
if not group is None:
644
# End of Avahi example code
646
for client in clients:
647
client.stop_hook = None