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())
31
# This variable is used to optionally bind to a specified interface.
32
# It is a global variable to fit in with the other variables from the
33
# Avahi server example code.
34
serviceInterface = avahi.IF_UNSPEC
35
# From the Avahi server example code:
36
serviceName = "Mandos"
37
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
38
servicePort = None # Not known at startup
39
serviceTXT = [] # TXT record for the service
40
domain = "" # Domain to publish on, default to .local
41
host = "" # Host to publish records for, default to localhost
42
group = None #our entry group
43
rename_count = 12 # Counter so we only rename after collisions a
44
# sensible number of times
45
# End of Avahi example code
230
48
class Client(object):
231
49
"""A representation of a client host served by this server.
234
name: string; from the config file, used in log messages and
51
name: string; from the config file, used in log messages
236
52
fingerprint: string (40 or 32 hexadecimal digits); used to
237
53
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.
54
secret: bytestring; sent verbatim (over TLS) to client
55
fqdn: string (FQDN); available for use by the checker command
56
created: datetime.datetime()
57
last_seen: datetime.datetime() or None if not yet seen
58
timeout: datetime.timedelta(); How long from last_seen until
59
this client is invalid
60
interval: datetime.timedelta(); How often to start a new checker
61
stop_hook: If set, called by stop() as stop_hook(self)
62
checker: subprocess.Popen(); a running checker process used
63
to see if the client lives.
64
Is None if no process is running.
251
65
checker_initiator_tag: a gobject event source tag, or None
252
disable_initiator_tag: - '' -
66
stop_initiator_tag: - '' -
253
67
checker_callback_tag: - '' -
254
68
checker_command: string; External command which is run to check if
255
client lives. %() expansions are done at
69
client lives. %()s expansions are done at
256
70
runtime with vars(self) as dict, so that for
257
71
instance %(name)s can be used in the command.
258
current_checker_command: string; current running checker_command
73
_timeout: Real variable for 'timeout'
74
_interval: Real variable for 'interval'
75
_timeout_milliseconds: Used by gobject.timeout_add()
76
_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'
78
def _set_timeout(self, timeout):
79
"Setter function for 'timeout' attribute"
80
self._timeout = timeout
81
self._timeout_milliseconds = ((self.timeout.days
82
* 24 * 60 * 60 * 1000)
83
+ (self.timeout.seconds * 1000)
84
+ (self.timeout.microseconds
86
timeout = property(lambda self: self._timeout,
89
def _set_interval(self, interval):
90
"Setter function for 'interval' attribute"
91
self._interval = interval
92
self._interval_milliseconds = ((self.interval.days
93
* 24 * 60 * 60 * 1000)
94
+ (self.interval.seconds
96
+ (self.interval.microseconds
98
interval = property(lambda self: self._interval,
101
def __init__(self, name=None, options=None, stop_hook=None,
102
fingerprint=None, secret=None, secfile=None, fqdn=None,
103
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
105
# Uppercase and remove spaces from fingerprint
106
# for later comparison purposes with return value of
107
# the fingerprint() function
108
self.fingerprint = fingerprint.upper().replace(u" ", u"")
110
self.secret = secret.decode(u"base64")
113
self.secret = sf.read()
116
raise RuntimeError(u"No secret or secfile for client %s"
118
self.fqdn = fqdn # string
119
self.created = datetime.datetime.now()
120
self.last_seen = None
122
timeout = options.timeout
123
self.timeout = timeout
125
interval = options.interval
127
interval = string_to_delta(interval)
128
self.interval = interval
129
self.stop_hook = stop_hook
308
130
self.checker = None
309
131
self.checker_initiator_tag = None
310
self.disable_initiator_tag = None
132
self.stop_initiator_tag = None
311
133
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()
134
self.check_command = checker
136
"""Start this clients checker and timeout hooks"""
322
137
# Schedule a new checker to be started an 'interval' from now,
323
138
# and every interval from then on.
324
self.checker_initiator_tag = (gobject.timeout_add
325
(self.interval_milliseconds(),
139
self.checker_initiator_tag = gobject.timeout_add\
140
(self._interval_milliseconds,
327
142
# Also start a new checker *right now*.
328
143
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):
144
# Schedule a stop() when 'timeout' has passed
145
self.stop_initiator_tag = gobject.timeout_add\
146
(self._timeout_milliseconds,
150
The possibility that this client might be restarted is left
151
open, but not currently used."""
153
sys.stderr.write(u"Stopping client %s\n" % self.name)
155
if self.stop_initiator_tag:
156
gobject.source_remove(self.stop_initiator_tag)
157
self.stop_initiator_tag = None
158
if self.checker_initiator_tag:
344
159
gobject.source_remove(self.checker_initiator_tag)
345
160
self.checker_initiator_tag = None
346
161
self.stop_checker()
347
if self.disable_hook:
348
self.disable_hook(self)
350
164
# Do not run this again if called by a gobject.timeout_add
353
166
def __del__(self):
354
self.disable_hook = None
357
def checker_callback(self, pid, condition, command):
167
# Some code duplication here and in stop()
168
if hasattr(self, "stop_initiator_tag") \
169
and self.stop_initiator_tag:
170
gobject.source_remove(self.stop_initiator_tag)
171
self.stop_initiator_tag = None
172
if hasattr(self, "checker_initiator_tag") \
173
and self.checker_initiator_tag:
174
gobject.source_remove(self.checker_initiator_tag)
175
self.checker_initiator_tag = None
177
def checker_callback(self, pid, condition):
358
178
"""The checker has completed, so take appropriate actions."""
179
now = datetime.datetime.now()
180
if os.WIFEXITED(condition) \
181
and (os.WEXITSTATUS(condition) == 0):
183
sys.stderr.write(u"Checker for %(name)s succeeded\n"
186
gobject.source_remove(self.stop_initiator_tag)
187
self.stop_initiator_tag = gobject.timeout_add\
188
(self._timeout_milliseconds,
191
if not os.WIFEXITED(condition):
192
sys.stderr.write(u"Checker for %(name)s crashed?\n"
195
sys.stderr.write(u"Checker for %(name)s failed\n"
359
198
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",
371
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(),
386
199
def start_checker(self):
387
200
"""Start a new checker subprocess if one is not running.
389
201
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
203
if self.checker is None:
205
sys.stderr.write(u"Starting checker for %s\n"
411
# In case checker_command has exactly one % operator
412
command = self.checker_command % self.host
208
command = self.check_command % self.fqdn
413
209
except TypeError:
414
# Escape attributes for the shell
415
escaped_attrs = dict((key,
416
re.escape(unicode(str(val),
210
escaped_attrs = dict((key, re.escape(str(val)))
420
212
vars(self).iteritems())
422
command = self.checker_command % escaped_attrs
423
except TypeError, error:
424
logger.error(u'Could not format string "%s":'
425
u' %s', self.checker_command, error)
426
return True # Try again later
427
self.current_checker_command = command
213
command = self.check_command % escaped_attrs
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:
449
logger.error(u"Failed to start subprocess: %s",
215
self.checker = subprocess.\
217
stdout=subprocess.PIPE,
218
close_fds=True, shell=True,
220
self.checker_callback_tag = gobject.\
221
child_watch_add(self.checker.pid,
224
except subprocess.OSError, error:
225
sys.stderr.write(u"Failed to start subprocess: %s\n"
451
227
# Re-run this periodically if run by gobject.timeout_add
454
229
def stop_checker(self):
455
230
"""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:
231
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
233
gobject.source_remove(self.checker_callback_tag)
234
self.checker_callback_tag = None
235
os.kill(self.checker.pid, signal.SIGTERM)
236
if self.checker.poll() is None:
237
os.kill(self.checker.pid, signal.SIGKILL)
470
238
self.checker = None
472
def still_valid(self):
239
def still_valid(self, now=None):
473
240
"""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:
242
now = datetime.datetime.now()
243
if self.last_seen is None:
478
244
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.
246
return now < (self.last_seen + self.timeout)
249
def peer_certificate(session):
250
# If not an OpenPGP certificate...
251
if gnutls.library.functions.gnutls_certificate_type_get\
252
(session._c_object) \
253
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
254
# ...do the normal thing
255
return session.peer_certificate
256
list_size = ctypes.c_uint()
257
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
258
(session._c_object, ctypes.byref(list_size))
259
if list_size.value == 0:
262
return ctypes.string_at(cert.data, cert.size)
265
def fingerprint(openpgp):
266
# New empty GnuTLS certificate
267
crt = gnutls.library.types.gnutls_openpgp_crt_t()
268
gnutls.library.functions.gnutls_openpgp_crt_init\
270
# New GnuTLS "datum" with the OpenPGP public key
271
datum = gnutls.library.types.gnutls_datum_t\
272
(ctypes.cast(ctypes.c_char_p(openpgp),
273
ctypes.POINTER(ctypes.c_ubyte)),
274
ctypes.c_uint(len(openpgp)))
275
# Import the OpenPGP public key into the certificate
276
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
279
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
280
# New buffer for the fingerprint
281
buffer = ctypes.create_string_buffer(20)
282
buffer_length = ctypes.c_size_t()
283
# Get the fingerprint from the certificate into the buffer
284
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
285
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
286
# Deinit the certificate
287
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
288
# Convert the buffer to a Python bytestring
289
fpr = ctypes.string_at(buffer, buffer_length.value)
290
# Convert the bytestring to hexadecimal notation
291
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
295
class tcp_handler(SocketServer.BaseRequestHandler, object):
296
"""A TCP request handler class.
297
Instantiated by IPv6_TCPServer for each request to handle it.
930
298
Note: This will run in its own forked process."""
932
300
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:
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))
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
302
sys.stderr.write(u"TCP request came\n")
303
sys.stderr.write(u"Request: %s\n" % self.request)
304
sys.stderr.write(u"Client Address: %s\n"
305
% unicode(self.client_address))
306
sys.stderr.write(u"Server: %s\n" % self.server)
307
session = gnutls.connection.ClientSession(self.request,
311
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
312
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
314
priority = "SECURE256"
316
gnutls.library.functions.gnutls_priority_set_direct\
317
(session._c_object, priority, None);
321
except gnutls.errors.GNUTLSError, error:
323
sys.stderr.write(u"Handshake failed: %s\n" % error)
324
# Do not run session.bye() here: the session is not
325
# established. Just abandon the request.
328
fpr = fingerprint(peer_certificate(session))
329
except (TypeError, gnutls.errors.GNUTLSError), error:
331
sys.stderr.write(u"Bad certificate: %s\n" % error)
335
sys.stderr.write(u"Fingerprint: %s\n" % fpr)
338
if c.fingerprint == fpr:
341
# Have to check if client.still_valid(), since it is possible
342
# that the client timed out while establishing the GnuTLS
344
if (not client) or (not client.still_valid()):
347
sys.stderr.write(u"Client %(name)s is invalid\n"
350
sys.stderr.write(u"Client not found for "
351
u"fingerprint: %s\n" % fpr)
355
while sent_size < len(client.secret):
356
sent = session.send(client.secret[sent_size:])
358
sys.stderr.write(u"Sent: %d, remaining: %d\n"
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
sys.stderr.write(u"Warning: No permission to" \
395
u" bind to interface %s\n"
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)
457
sys.stderr.write(u"Adding service '%s' of type '%s' ...\n"
458
% (serviceName, serviceType))
461
serviceInterface, # interface
462
avahi.PROTO_INET6, # protocol
463
dbus.UInt32(0), # flags
464
serviceName, serviceType,
466
dbus.UInt16(servicePort),
467
avahi.string_array_to_txt_array(serviceTXT))
471
def remove_service():
472
"""From the Avahi server example code"""
475
if not group is None:
479
def server_state_changed(state):
480
"""From the Avahi server example code"""
481
if state == avahi.SERVER_COLLISION:
482
sys.stderr.write(u"WARNING: Server name collision\n")
484
elif state == avahi.SERVER_RUNNING:
488
def entry_group_state_changed(state, error):
489
"""From the Avahi server example code"""
490
global serviceName, server, rename_count
493
sys.stderr.write(u"state change: %i\n" % state)
495
if state == avahi.ENTRY_GROUP_ESTABLISHED:
497
sys.stderr.write(u"Service established.\n")
498
elif state == avahi.ENTRY_GROUP_COLLISION:
500
rename_count = rename_count - 1
502
name = server.GetAlternativeServiceName(name)
503
sys.stderr.write(u"WARNING: Service name collision, "
504
u"changing name to '%s' ...\n" % name)
509
sys.stderr.write(u"ERROR: No suitable service name found "
510
u"after %i retries, exiting.\n"
513
elif state == avahi.ENTRY_GROUP_FAILURE:
514
sys.stderr.write(u"Error in group state changed %s\n"
1298
520
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
521
"""Call the C function if_nametoindex()"""
1304
if_nametoindex = (ctypes.cdll.LoadLibrary
1305
(ctypes.util.find_library(u"c"))
523
libc = ctypes.cdll.LoadLibrary("libc.so.6")
524
return libc.if_nametoindex(interface)
1307
525
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]
526
if "struct" not in sys.modules:
528
if "fcntl" not in sys.modules:
530
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
532
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
533
struct.pack("16s16x", interface))
535
interface_index = struct.unpack("I", ifreq[16:20])[0]
536
return interface_index
539
if __name__ == '__main__':
540
parser = OptionParser()
541
parser.add_option("-i", "--interface", type="string",
542
default=None, metavar="IF",
543
help="Bind to interface IF")
544
parser.add_option("--cert", type="string", default="cert.pem",
546
help="Public key certificate PEM file to use")
547
parser.add_option("--key", type="string", default="key.pem",
549
help="Private key PEM file to use")
550
parser.add_option("--ca", type="string", default="ca.pem",
552
help="Certificate Authority certificate PEM file to use")
553
parser.add_option("--crl", type="string", default="crl.pem",
555
help="Certificate Revokation List PEM file to use")
556
parser.add_option("-p", "--port", type="int", default=None,
557
help="Port number to receive requests on")
558
parser.add_option("--timeout", type="string", # Parsed later
560
help="Amount of downtime allowed for clients")
561
parser.add_option("--interval", type="string", # Parsed later
563
help="How often to check that a client is up")
564
parser.add_option("--check", action="store_true", default=False,
565
help="Run self-test")
566
parser.add_option("--debug", action="store_true", default=False,
568
(options, args) = parser.parse_args()
1378
570
if options.check:
1380
572
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
575
# Parse the time arguments
577
options.timeout = string_to_delta(options.timeout)
579
parser.error("option --timeout: Unparseable time")
581
options.interval = string_to_delta(options.interval)
583
parser.error("option --interval: Unparseable time")
586
defaults = { "checker": "sleep 1; fping -q -- %%(fqdn)s" }
587
client_config = ConfigParser.SafeConfigParser(defaults)
588
#client_config.readfp(open("secrets.conf"), "secrets.conf")
589
client_config.read("mandos-clients.conf")
591
# From the Avahi server example code
1506
592
DBusGMainLoop(set_as_default=True )
1507
593
main_loop = gobject.MainLoop()
1508
594
bus = dbus.SystemBus()
595
server = dbus.Interface(
596
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
597
avahi.DBUS_INTERFACE_SERVER )
1509
598
# 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")
600
debug = options.debug
603
def remove_from_clients(client):
604
clients.remove(client)
607
sys.stderr.write(u"No clients left, exiting\n")
610
clients.update(Set(Client(name=section, options=options,
611
stop_hook = remove_from_clients,
612
**(dict(client_config\
614
for section in client_config.sections()))
615
for client in clients:
618
tcp_server = IPv6_TCPServer((None, options.port),
622
# Find out what random port we got
623
servicePort = tcp_server.socket.getsockname()[1]
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")
625
sys.stderr.write(u"Now listening on port %d\n" % servicePort)
627
if options.interface is not None:
628
serviceInterface = if_nametoindex(options.interface)
630
# From the Avahi server example code
631
server.connect_to_signal("StateChanged", server_state_changed)
632
server_state_changed(server.GetState())
633
# End of Avahi example code
635
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
636
lambda *args, **kwargs:
637
tcp_server.handle_request(*args[2:],
1664
except AvahiError, error:
1665
logger.critical(u"AvahiError: %s", error)
1667
641
except KeyboardInterrupt:
1670
logger.debug(u"Server received KeyboardInterrupt")
1671
logger.debug(u"Server exiting")
1673
if __name__ == '__main__':
646
# From the Avahi server example code
647
if not group is None:
649
# End of Avahi example code
651
for client in clients:
652
client.stop_hook = None