44
15
import gnutls.library.functions
45
16
import gnutls.library.constants
46
17
import gnutls.library.types
47
import ConfigParser as configparser
56
import logging.handlers
62
import cPickle as pickle
63
import multiprocessing
69
31
from dbus.mainloop.glib import DBusGMainLoop
72
import xml.dom.minidom
76
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
77
except AttributeError:
79
from IN import SO_BINDTODEVICE
81
SO_BINDTODEVICE = None
86
#logger = logging.getLogger(u'mandos')
87
logger = logging.Logger(u'mandos')
88
syslogger = (logging.handlers.SysLogHandler
89
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
90
address = "/dev/log"))
91
syslogger.setFormatter(logging.Formatter
92
(u'Mandos [%(process)d]: %(levelname)s:'
35
import logging.handlers
37
logger = logging.Logger('mandos')
38
syslogger = logging.handlers.SysLogHandler\
39
(facility = logging.handlers.SysLogHandler.LOG_DAEMON)
40
syslogger.setFormatter(logging.Formatter\
41
('%(levelname)s: %(message)s'))
94
42
logger.addHandler(syslogger)
96
console = logging.StreamHandler()
97
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
100
logger.addHandler(console)
102
class AvahiError(Exception):
103
def __init__(self, value, *args, **kwargs):
105
super(AvahiError, self).__init__(value, *args, **kwargs)
106
def __unicode__(self):
107
return unicode(repr(self.value))
109
class AvahiServiceError(AvahiError):
112
class AvahiGroupError(AvahiError):
116
class AvahiService(object):
117
"""An Avahi (Zeroconf) service.
120
interface: integer; avahi.IF_UNSPEC or an interface index.
121
Used to optionally bind to the specified interface.
122
name: string; Example: u'Mandos'
123
type: string; Example: u'_mandos._tcp'.
124
See <http://www.dns-sd.org/ServiceTypes.html>
125
port: integer; what port to announce
126
TXT: list of strings; TXT record for the service
127
domain: string; Domain to publish on, default to .local if empty.
128
host: string; Host to publish records for, default is localhost
129
max_renames: integer; maximum number of renames
130
rename_count: integer; counter so we only rename after collisions
131
a sensible number of times
132
group: D-Bus Entry Group
134
bus: dbus.SystemBus()
136
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
137
servicetype = None, port = None, TXT = None,
138
domain = u"", host = u"", max_renames = 32768,
139
protocol = avahi.PROTO_UNSPEC, bus = None):
140
self.interface = interface
142
self.type = servicetype
144
self.TXT = TXT if TXT is not None else []
147
self.rename_count = 0
148
self.max_renames = max_renames
149
self.protocol = protocol
150
self.group = None # our entry group
154
"""Derived from the Avahi example code"""
155
if self.rename_count >= self.max_renames:
156
logger.critical(u"No suitable Zeroconf service name found"
157
u" after %i retries, exiting.",
159
raise AvahiServiceError(u"Too many renames")
160
self.name = unicode(self.server.GetAlternativeServiceName(self.name))
161
logger.info(u"Changing Zeroconf service name to %r ...",
163
syslogger.setFormatter(logging.Formatter
164
(u'Mandos (%s) [%%(process)d]:'
165
u' %%(levelname)s: %%(message)s'
170
except dbus.exceptions.DBusException, error:
171
logger.critical(u"DBusException: %s", error)
174
self.rename_count += 1
176
"""Derived from the Avahi example code"""
177
if self.group is not None:
180
"""Derived from the Avahi example code"""
181
if self.group is None:
182
self.group = dbus.Interface(
183
self.bus.get_object(avahi.DBUS_NAME,
184
self.server.EntryGroupNew()),
185
avahi.DBUS_INTERFACE_ENTRY_GROUP)
186
self.group.connect_to_signal('StateChanged',
188
.entry_group_state_changed)
189
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
190
self.name, self.type)
191
self.group.AddService(
194
dbus.UInt32(0), # flags
195
self.name, self.type,
196
self.domain, self.host,
197
dbus.UInt16(self.port),
198
avahi.string_array_to_txt_array(self.TXT))
200
def entry_group_state_changed(self, state, error):
201
"""Derived from the Avahi example code"""
202
logger.debug(u"Avahi entry group state change: %i", state)
204
if state == avahi.ENTRY_GROUP_ESTABLISHED:
205
logger.debug(u"Zeroconf service established.")
206
elif state == avahi.ENTRY_GROUP_COLLISION:
207
logger.warning(u"Zeroconf service name collision.")
209
elif state == avahi.ENTRY_GROUP_FAILURE:
210
logger.critical(u"Avahi: Error in group state changed %s",
212
raise AvahiGroupError(u"State changed: %s"
215
"""Derived from the Avahi example code"""
216
if self.group is not None:
219
def server_state_changed(self, state):
220
"""Derived from the Avahi example code"""
221
logger.debug(u"Avahi server state change: %i", state)
222
if state == avahi.SERVER_COLLISION:
223
logger.error(u"Zeroconf server name collision")
225
elif state == avahi.SERVER_RUNNING:
228
"""Derived from the Avahi example code"""
229
if self.server is None:
230
self.server = dbus.Interface(
231
self.bus.get_object(avahi.DBUS_NAME,
232
avahi.DBUS_PATH_SERVER),
233
avahi.DBUS_INTERFACE_SERVER)
234
self.server.connect_to_signal(u"StateChanged",
235
self.server_state_changed)
236
self.server_state_changed(self.server.GetState())
45
# This variable is used to optionally bind to a specified interface.
46
# It is a global variable to fit in with the other variables from the
47
# Avahi server example code.
48
serviceInterface = avahi.IF_UNSPEC
49
# From the Avahi server example code:
50
serviceName = "Mandos"
51
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
52
servicePort = None # Not known at startup
53
serviceTXT = [] # TXT record for the service
54
domain = "" # Domain to publish on, default to .local
55
host = "" # Host to publish records for, default to localhost
56
group = None #our entry group
57
rename_count = 12 # Counter so we only rename after collisions a
58
# sensible number of times
59
# End of Avahi example code
239
62
class Client(object):
240
63
"""A representation of a client host served by this server.
243
_approved: bool(); 'None' if not yet approved/disapproved
244
approval_delay: datetime.timedelta(); Time to wait for approval
245
approval_duration: datetime.timedelta(); Duration of one approval
246
checker: subprocess.Popen(); a running checker process used
247
to see if the client lives.
248
'None' if no process is running.
249
checker_callback_tag: a gobject event source tag, or None
250
checker_command: string; External command which is run to check
251
if client lives. %() expansions are done at
65
name: string; from the config file, used in log messages
66
fingerprint: string (40 or 32 hexadecimal digits); used to
67
uniquely identify the client
68
secret: bytestring; sent verbatim (over TLS) to client
69
fqdn: string (FQDN); available for use by the checker command
70
created: datetime.datetime()
71
last_seen: datetime.datetime() or None if not yet seen
72
timeout: datetime.timedelta(); How long from last_seen until
73
this client is invalid
74
interval: datetime.timedelta(); How often to start a new checker
75
stop_hook: If set, called by stop() as stop_hook(self)
76
checker: subprocess.Popen(); a running checker process used
77
to see if the client lives.
78
Is None if no process is running.
79
checker_initiator_tag: a gobject event source tag, or None
80
stop_initiator_tag: - '' -
81
checker_callback_tag: - '' -
82
checker_command: string; External command which is run to check if
83
client lives. %()s expansions are done at
252
84
runtime with vars(self) as dict, so that for
253
85
instance %(name)s can be used in the command.
254
checker_initiator_tag: a gobject event source tag, or None
255
created: datetime.datetime(); (UTC) object creation
256
current_checker_command: string; current running checker_command
257
disable_hook: If set, called by disable() as disable_hook(self)
258
disable_initiator_tag: a gobject event source tag, or None
260
fingerprint: string (40 or 32 hexadecimal digits); used to
261
uniquely identify the client
262
host: string; available for use by the checker command
263
interval: datetime.timedelta(); How often to start a new checker
264
last_approval_request: datetime.datetime(); (UTC) or None
265
last_checked_ok: datetime.datetime(); (UTC) or None
266
last_enabled: datetime.datetime(); (UTC)
267
name: string; from the config file, used in log messages and
269
secret: bytestring; sent verbatim (over TLS) to client
270
timeout: datetime.timedelta(); How long from last_checked_ok
271
until this client is disabled
272
runtime_expansions: Allowed attributes for runtime expansion.
87
_timeout: Real variable for 'timeout'
88
_interval: Real variable for 'interval'
89
_timeout_milliseconds: Used by gobject.timeout_add()
90
_interval_milliseconds: - '' -
275
runtime_expansions = (u"approval_delay", u"approval_duration",
276
u"created", u"enabled", u"fingerprint",
277
u"host", u"interval", u"last_checked_ok",
278
u"last_enabled", u"name", u"timeout")
281
def _timedelta_to_milliseconds(td):
282
"Convert a datetime.timedelta() to milliseconds"
283
return ((td.days * 24 * 60 * 60 * 1000)
284
+ (td.seconds * 1000)
285
+ (td.microseconds // 1000))
287
def timeout_milliseconds(self):
288
"Return the 'timeout' attribute in milliseconds"
289
return self._timedelta_to_milliseconds(self.timeout)
291
def interval_milliseconds(self):
292
"Return the 'interval' attribute in milliseconds"
293
return self._timedelta_to_milliseconds(self.interval)
295
def approval_delay_milliseconds(self):
296
return self._timedelta_to_milliseconds(self.approval_delay)
298
def __init__(self, name = None, disable_hook=None, config=None):
299
"""Note: the 'checker' key in 'config' sets the
300
'checker_command' attribute and *not* the 'checker'
92
def _set_timeout(self, timeout):
93
"Setter function for 'timeout' attribute"
94
self._timeout = timeout
95
self._timeout_milliseconds = ((self.timeout.days
96
* 24 * 60 * 60 * 1000)
97
+ (self.timeout.seconds * 1000)
98
+ (self.timeout.microseconds
100
timeout = property(lambda self: self._timeout,
103
def _set_interval(self, interval):
104
"Setter function for 'interval' attribute"
105
self._interval = interval
106
self._interval_milliseconds = ((self.interval.days
107
* 24 * 60 * 60 * 1000)
108
+ (self.interval.seconds
110
+ (self.interval.microseconds
112
interval = property(lambda self: self._interval,
115
def __init__(self, name=None, options=None, stop_hook=None,
116
fingerprint=None, secret=None, secfile=None,
117
fqdn=None, timeout=None, interval=-1, checker=None):
305
logger.debug(u"Creating client %r", self.name)
306
# Uppercase and remove spaces from fingerprint for later
307
# comparison purposes with return value from the fingerprint()
309
self.fingerprint = (config[u"fingerprint"].upper()
311
logger.debug(u" Fingerprint: %s", self.fingerprint)
312
if u"secret" in config:
313
self.secret = config[u"secret"].decode(u"base64")
314
elif u"secfile" in config:
315
with open(os.path.expanduser(os.path.expandvars
316
(config[u"secfile"])),
318
self.secret = secfile.read()
320
raise TypeError(u"No secret or secfile for client %s"
322
self.host = config.get(u"host", u"")
323
self.created = datetime.datetime.utcnow()
325
self.last_approval_request = None
326
self.last_enabled = None
327
self.last_checked_ok = None
328
self.timeout = string_to_delta(config[u"timeout"])
329
self.interval = string_to_delta(config[u"interval"])
330
self.disable_hook = disable_hook
119
# Uppercase and remove spaces from fingerprint
120
# for later comparison purposes with return value of
121
# the fingerprint() function
122
self.fingerprint = fingerprint.upper().replace(u" ", u"")
124
self.secret = secret.decode(u"base64")
127
self.secret = sf.read()
130
raise RuntimeError(u"No secret or secfile for client %s"
132
self.fqdn = fqdn # string
133
self.created = datetime.datetime.now()
134
self.last_seen = None
136
self.timeout = options.timeout
138
self.timeout = string_to_delta(timeout)
140
self.interval = options.interval
142
self.interval = string_to_delta(interval)
143
self.stop_hook = stop_hook
331
144
self.checker = None
332
145
self.checker_initiator_tag = None
333
self.disable_initiator_tag = None
146
self.stop_initiator_tag = None
334
147
self.checker_callback_tag = None
335
self.checker_command = config[u"checker"]
336
self.current_checker_command = None
337
self.last_connect = None
338
self._approved = None
339
self.approved_by_default = config.get(u"approved_by_default",
341
self.approvals_pending = 0
342
self.approval_delay = string_to_delta(
343
config[u"approval_delay"])
344
self.approval_duration = string_to_delta(
345
config[u"approval_duration"])
346
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
348
def send_changedstate(self):
349
self.changedstate.acquire()
350
self.changedstate.notify_all()
351
self.changedstate.release()
354
"""Start this client's checker and timeout hooks"""
355
if getattr(self, u"enabled", False):
358
self.send_changedstate()
359
self.last_enabled = datetime.datetime.utcnow()
148
self.check_command = checker
150
"""Start this clients checker and timeout hooks"""
360
151
# Schedule a new checker to be started an 'interval' from now,
361
152
# and every interval from then on.
362
self.checker_initiator_tag = (gobject.timeout_add
363
(self.interval_milliseconds(),
365
# Schedule a disable() when 'timeout' has passed
366
self.disable_initiator_tag = (gobject.timeout_add
367
(self.timeout_milliseconds(),
153
self.checker_initiator_tag = gobject.timeout_add\
154
(self._interval_milliseconds,
370
156
# Also start a new checker *right now*.
371
157
self.start_checker()
373
def disable(self, quiet=True):
374
"""Disable this client."""
375
if not getattr(self, "enabled", False):
378
self.send_changedstate()
380
logger.info(u"Disabling client %s", self.name)
381
if getattr(self, u"disable_initiator_tag", False):
382
gobject.source_remove(self.disable_initiator_tag)
383
self.disable_initiator_tag = None
384
if getattr(self, u"checker_initiator_tag", False):
158
# Schedule a stop() when 'timeout' has passed
159
self.stop_initiator_tag = gobject.timeout_add\
160
(self._timeout_milliseconds,
164
The possibility that this client might be restarted is left
165
open, but not currently used."""
166
logger.debug(u"Stopping client %s", self.name)
168
if self.stop_initiator_tag:
169
gobject.source_remove(self.stop_initiator_tag)
170
self.stop_initiator_tag = None
171
if self.checker_initiator_tag:
385
172
gobject.source_remove(self.checker_initiator_tag)
386
173
self.checker_initiator_tag = None
387
174
self.stop_checker()
388
if self.disable_hook:
389
self.disable_hook(self)
391
177
# Do not run this again if called by a gobject.timeout_add
394
179
def __del__(self):
395
self.disable_hook = None
398
def checker_callback(self, pid, condition, command):
180
# Some code duplication here and in stop()
181
if hasattr(self, "stop_initiator_tag") \
182
and self.stop_initiator_tag:
183
gobject.source_remove(self.stop_initiator_tag)
184
self.stop_initiator_tag = None
185
if hasattr(self, "checker_initiator_tag") \
186
and self.checker_initiator_tag:
187
gobject.source_remove(self.checker_initiator_tag)
188
self.checker_initiator_tag = None
190
def checker_callback(self, pid, condition):
399
191
"""The checker has completed, so take appropriate actions."""
400
self.checker_callback_tag = None
402
if os.WIFEXITED(condition):
403
exitstatus = os.WEXITSTATUS(condition)
405
logger.info(u"Checker for %(name)s succeeded",
409
logger.info(u"Checker for %(name)s failed",
192
now = datetime.datetime.now()
193
if os.WIFEXITED(condition) \
194
and (os.WEXITSTATUS(condition) == 0):
195
logger.debug(u"Checker for %(name)s succeeded",
198
gobject.source_remove(self.stop_initiator_tag)
199
self.stop_initiator_tag = gobject.timeout_add\
200
(self._timeout_milliseconds,
202
elif not os.WIFEXITED(condition):
412
203
logger.warning(u"Checker for %(name)s crashed?",
415
def checked_ok(self):
416
"""Bump up the timeout for this client.
418
This should only be called when the client has been seen,
421
self.last_checked_ok = datetime.datetime.utcnow()
422
gobject.source_remove(self.disable_initiator_tag)
423
self.disable_initiator_tag = (gobject.timeout_add
424
(self.timeout_milliseconds(),
427
def need_approval(self):
428
self.last_approval_request = datetime.datetime.utcnow()
206
logger.debug(u"Checker for %(name)s failed",
209
self.checker_callback_tag = None
430
210
def start_checker(self):
431
211
"""Start a new checker subprocess if one is not running.
433
212
If a checker already exists, leave it running and do
435
# The reason for not killing a running checker is that if we
436
# did that, then if a checker (for some reason) started
437
# running slowly and taking more than 'interval' time, the
438
# client would inevitably timeout, since no checker would get
439
# a chance to run to completion. If we instead leave running
440
# checkers alone, the checker would have to take more time
441
# than 'timeout' for the client to be disabled, which is as it
444
# If a checker exists, make sure it is not a zombie
446
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
447
except (AttributeError, OSError), error:
448
if (isinstance(error, OSError)
449
and error.errno != errno.ECHILD):
453
logger.warning(u"Checker was a zombie")
454
gobject.source_remove(self.checker_callback_tag)
455
self.checker_callback(pid, status,
456
self.current_checker_command)
457
# Start a new checker if needed
458
214
if self.checker is None:
460
# In case checker_command has exactly one % operator
461
command = self.checker_command % self.host
216
command = self.check_command % self.fqdn
462
217
except TypeError:
463
# Escape attributes for the shell
464
escaped_attrs = dict(
466
re.escape(unicode(str(getattr(self, attr, u"")),
470
self.runtime_expansions)
218
escaped_attrs = dict((key, re.escape(str(val)))
220
vars(self).iteritems())
473
command = self.checker_command % escaped_attrs
222
command = self.check_command % escaped_attrs
474
223
except TypeError, error:
475
logger.error(u'Could not format string "%s":'
476
u' %s', self.checker_command, error)
224
logger.critical(u'Could not format string "%s":'
225
u' %s', self.check_command, error)
477
226
return True # Try again later
478
self.current_checker_command = command
480
logger.info(u"Starting checker %r for %s",
482
# We don't need to redirect stdout and stderr, since
483
# in normal mode, that is already done by daemon(),
484
# and in debug mode we don't want to. (Stdin is
485
# always replaced by /dev/null.)
486
self.checker = subprocess.Popen(command,
488
shell=True, cwd=u"/")
489
self.checker_callback_tag = (gobject.child_watch_add
491
self.checker_callback,
493
# The checker may have completed before the gobject
494
# watch was added. Check for this.
495
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
497
gobject.source_remove(self.checker_callback_tag)
498
self.checker_callback(pid, status, command)
499
except OSError, error:
228
logger.debug(u"Starting checker %r for %s",
230
self.checker = subprocess.\
232
close_fds=True, shell=True,
234
self.checker_callback_tag = gobject.child_watch_add\
236
self.checker_callback)
237
except subprocess.OSError, error:
500
238
logger.error(u"Failed to start subprocess: %s",
502
240
# Re-run this periodically if run by gobject.timeout_add
505
242
def stop_checker(self):
506
243
"""Force the checker process, if any, to stop."""
507
if self.checker_callback_tag:
508
gobject.source_remove(self.checker_callback_tag)
509
self.checker_callback_tag = None
510
if getattr(self, u"checker", None) is None:
244
if not hasattr(self, "checker") or self.checker is None:
512
logger.debug(u"Stopping checker for %(name)s", vars(self))
514
os.kill(self.checker.pid, signal.SIGTERM)
516
#if self.checker.poll() is None:
517
# os.kill(self.checker.pid, signal.SIGKILL)
518
except OSError, error:
519
if error.errno != errno.ESRCH: # No such process
523
def dbus_service_property(dbus_interface, signature=u"v",
524
access=u"readwrite", byte_arrays=False):
525
"""Decorators for marking methods of a DBusObjectWithProperties to
526
become properties on the D-Bus.
528
The decorated method will be called with no arguments by "Get"
529
and with one argument by "Set".
531
The parameters, where they are supported, are the same as
532
dbus.service.method, except there is only "signature", since the
533
type from Get() and the type sent to Set() is the same.
535
# Encoding deeply encoded byte arrays is not supported yet by the
536
# "Set" method, so we fail early here:
537
if byte_arrays and signature != u"ay":
538
raise ValueError(u"Byte arrays not supported for non-'ay'"
539
u" signature %r" % signature)
541
func._dbus_is_property = True
542
func._dbus_interface = dbus_interface
543
func._dbus_signature = signature
544
func._dbus_access = access
545
func._dbus_name = func.__name__
546
if func._dbus_name.endswith(u"_dbus_property"):
547
func._dbus_name = func._dbus_name[:-14]
548
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
553
class DBusPropertyException(dbus.exceptions.DBusException):
554
"""A base class for D-Bus property-related exceptions
556
def __unicode__(self):
557
return unicode(str(self))
560
class DBusPropertyAccessException(DBusPropertyException):
561
"""A property's access permissions disallows an operation.
566
class DBusPropertyNotFound(DBusPropertyException):
567
"""An attempt was made to access a non-existing property.
572
class DBusObjectWithProperties(dbus.service.Object):
573
"""A D-Bus object with properties.
575
Classes inheriting from this can use the dbus_service_property
576
decorator to expose methods as D-Bus properties. It exposes the
577
standard Get(), Set(), and GetAll() methods on the D-Bus.
581
def _is_dbus_property(obj):
582
return getattr(obj, u"_dbus_is_property", False)
584
def _get_all_dbus_properties(self):
585
"""Returns a generator of (name, attribute) pairs
587
return ((prop._dbus_name, prop)
589
inspect.getmembers(self, self._is_dbus_property))
591
def _get_dbus_property(self, interface_name, property_name):
592
"""Returns a bound method if one exists which is a D-Bus
593
property with the specified name and interface.
595
for name in (property_name,
596
property_name + u"_dbus_property"):
597
prop = getattr(self, name, None)
599
or not self._is_dbus_property(prop)
600
or prop._dbus_name != property_name
601
or (interface_name and prop._dbus_interface
602
and interface_name != prop._dbus_interface)):
606
raise DBusPropertyNotFound(self.dbus_object_path + u":"
607
+ interface_name + u"."
610
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
612
def Get(self, interface_name, property_name):
613
"""Standard D-Bus property Get() method, see D-Bus standard.
615
prop = self._get_dbus_property(interface_name, property_name)
616
if prop._dbus_access == u"write":
617
raise DBusPropertyAccessException(property_name)
619
if not hasattr(value, u"variant_level"):
621
return type(value)(value, variant_level=value.variant_level+1)
623
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
624
def Set(self, interface_name, property_name, value):
625
"""Standard D-Bus property Set() method, see D-Bus standard.
627
prop = self._get_dbus_property(interface_name, property_name)
628
if prop._dbus_access == u"read":
629
raise DBusPropertyAccessException(property_name)
630
if prop._dbus_get_args_options[u"byte_arrays"]:
631
# The byte_arrays option is not supported yet on
632
# signatures other than "ay".
633
if prop._dbus_signature != u"ay":
635
value = dbus.ByteArray(''.join(unichr(byte)
639
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
640
out_signature=u"a{sv}")
641
def GetAll(self, interface_name):
642
"""Standard D-Bus property GetAll() method, see D-Bus
645
Note: Will not include properties with access="write".
648
for name, prop in self._get_all_dbus_properties():
650
and interface_name != prop._dbus_interface):
651
# Interface non-empty but did not match
653
# Ignore write-only properties
654
if prop._dbus_access == u"write":
657
if not hasattr(value, u"variant_level"):
660
all[name] = type(value)(value, variant_level=
661
value.variant_level+1)
662
return dbus.Dictionary(all, signature=u"sv")
664
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
666
path_keyword='object_path',
667
connection_keyword='connection')
668
def Introspect(self, object_path, connection):
669
"""Standard D-Bus method, overloaded to insert property tags.
671
xmlstring = dbus.service.Object.Introspect(self, object_path,
674
document = xml.dom.minidom.parseString(xmlstring)
675
def make_tag(document, name, prop):
676
e = document.createElement(u"property")
677
e.setAttribute(u"name", name)
678
e.setAttribute(u"type", prop._dbus_signature)
679
e.setAttribute(u"access", prop._dbus_access)
681
for if_tag in document.getElementsByTagName(u"interface"):
682
for tag in (make_tag(document, name, prop)
684
in self._get_all_dbus_properties()
685
if prop._dbus_interface
686
== if_tag.getAttribute(u"name")):
687
if_tag.appendChild(tag)
688
# Add the names to the return values for the
689
# "org.freedesktop.DBus.Properties" methods
690
if (if_tag.getAttribute(u"name")
691
== u"org.freedesktop.DBus.Properties"):
692
for cn in if_tag.getElementsByTagName(u"method"):
693
if cn.getAttribute(u"name") == u"Get":
694
for arg in cn.getElementsByTagName(u"arg"):
695
if (arg.getAttribute(u"direction")
697
arg.setAttribute(u"name", u"value")
698
elif cn.getAttribute(u"name") == u"GetAll":
699
for arg in cn.getElementsByTagName(u"arg"):
700
if (arg.getAttribute(u"direction")
702
arg.setAttribute(u"name", u"props")
703
xmlstring = document.toxml(u"utf-8")
705
except (AttributeError, xml.dom.DOMException,
706
xml.parsers.expat.ExpatError), error:
707
logger.error(u"Failed to override Introspection method",
712
class ClientDBus(Client, DBusObjectWithProperties):
713
"""A Client class using D-Bus
716
dbus_object_path: dbus.ObjectPath
717
bus: dbus.SystemBus()
720
runtime_expansions = (Client.runtime_expansions
721
+ (u"dbus_object_path",))
723
# dbus.service.Object doesn't use super(), so we can't either.
725
def __init__(self, bus = None, *args, **kwargs):
726
self._approvals_pending = 0
728
Client.__init__(self, *args, **kwargs)
729
# Only now, when this client is initialized, can it show up on
731
client_object_name = unicode(self.name).translate(
732
{ord(u"."): ord(u"_"),
733
ord(u"-"): ord(u"_")})
734
self.dbus_object_path = (dbus.ObjectPath
735
(u"/clients/" + client_object_name))
736
DBusObjectWithProperties.__init__(self, self.bus,
737
self.dbus_object_path)
739
def _get_approvals_pending(self):
740
return self._approvals_pending
741
def _set_approvals_pending(self, value):
742
old_value = self._approvals_pending
743
self._approvals_pending = value
745
if (hasattr(self, "dbus_object_path")
746
and bval is not bool(old_value)):
747
dbus_bool = dbus.Boolean(bval, variant_level=1)
748
self.PropertyChanged(dbus.String(u"ApprovalPending"),
751
approvals_pending = property(_get_approvals_pending,
752
_set_approvals_pending)
753
del _get_approvals_pending, _set_approvals_pending
756
def _datetime_to_dbus(dt, variant_level=0):
757
"""Convert a UTC datetime.datetime() to a D-Bus type."""
758
return dbus.String(dt.isoformat(),
759
variant_level=variant_level)
762
oldstate = getattr(self, u"enabled", False)
763
r = Client.enable(self)
764
if oldstate != self.enabled:
766
self.PropertyChanged(dbus.String(u"Enabled"),
767
dbus.Boolean(True, variant_level=1))
768
self.PropertyChanged(
769
dbus.String(u"LastEnabled"),
770
self._datetime_to_dbus(self.last_enabled,
774
def disable(self, quiet = False):
775
oldstate = getattr(self, u"enabled", False)
776
r = Client.disable(self, quiet=quiet)
777
if not quiet and oldstate != self.enabled:
779
self.PropertyChanged(dbus.String(u"Enabled"),
780
dbus.Boolean(False, variant_level=1))
783
def __del__(self, *args, **kwargs):
785
self.remove_from_connection()
788
if hasattr(DBusObjectWithProperties, u"__del__"):
789
DBusObjectWithProperties.__del__(self, *args, **kwargs)
790
Client.__del__(self, *args, **kwargs)
792
def checker_callback(self, pid, condition, command,
246
gobject.source_remove(self.checker_callback_tag)
794
247
self.checker_callback_tag = None
248
os.kill(self.checker.pid, signal.SIGTERM)
249
if self.checker.poll() is None:
250
os.kill(self.checker.pid, signal.SIGKILL)
795
251
self.checker = None
797
self.PropertyChanged(dbus.String(u"CheckerRunning"),
798
dbus.Boolean(False, variant_level=1))
799
if os.WIFEXITED(condition):
800
exitstatus = os.WEXITSTATUS(condition)
802
self.CheckerCompleted(dbus.Int16(exitstatus),
803
dbus.Int64(condition),
804
dbus.String(command))
807
self.CheckerCompleted(dbus.Int16(-1),
808
dbus.Int64(condition),
809
dbus.String(command))
811
return Client.checker_callback(self, pid, condition, command,
814
def checked_ok(self, *args, **kwargs):
815
r = Client.checked_ok(self, *args, **kwargs)
817
self.PropertyChanged(
818
dbus.String(u"LastCheckedOK"),
819
(self._datetime_to_dbus(self.last_checked_ok,
823
def need_approval(self, *args, **kwargs):
824
r = Client.need_approval(self, *args, **kwargs)
826
self.PropertyChanged(
827
dbus.String(u"LastApprovalRequest"),
828
(self._datetime_to_dbus(self.last_approval_request,
832
def start_checker(self, *args, **kwargs):
833
old_checker = self.checker
834
if self.checker is not None:
835
old_checker_pid = self.checker.pid
837
old_checker_pid = None
838
r = Client.start_checker(self, *args, **kwargs)
839
# Only if new checker process was started
840
if (self.checker is not None
841
and old_checker_pid != self.checker.pid):
843
self.CheckerStarted(self.current_checker_command)
844
self.PropertyChanged(
845
dbus.String(u"CheckerRunning"),
846
dbus.Boolean(True, variant_level=1))
849
def stop_checker(self, *args, **kwargs):
850
old_checker = getattr(self, u"checker", None)
851
r = Client.stop_checker(self, *args, **kwargs)
852
if (old_checker is not None
853
and getattr(self, u"checker", None) is None):
854
self.PropertyChanged(dbus.String(u"CheckerRunning"),
855
dbus.Boolean(False, variant_level=1))
858
def _reset_approved(self):
859
self._approved = None
862
def approve(self, value=True):
863
self.send_changedstate()
864
self._approved = value
865
gobject.timeout_add(self._timedelta_to_milliseconds
866
(self.approval_duration),
867
self._reset_approved)
870
## D-Bus methods, signals & properties
871
_interface = u"se.bsnet.fukt.Mandos.Client"
875
# CheckerCompleted - signal
876
@dbus.service.signal(_interface, signature=u"nxs")
877
def CheckerCompleted(self, exitcode, waitstatus, command):
881
# CheckerStarted - signal
882
@dbus.service.signal(_interface, signature=u"s")
883
def CheckerStarted(self, command):
887
# PropertyChanged - signal
888
@dbus.service.signal(_interface, signature=u"sv")
889
def PropertyChanged(self, property, value):
894
@dbus.service.signal(_interface)
897
Is sent after a successful transfer of secret from the Mandos
898
server to mandos-client
903
@dbus.service.signal(_interface, signature=u"s")
904
def Rejected(self, reason):
908
# NeedApproval - signal
909
@dbus.service.signal(_interface, signature=u"tb")
910
def NeedApproval(self, timeout, default):
912
return self.need_approval()
917
@dbus.service.method(_interface, in_signature=u"b")
918
def Approve(self, value):
922
@dbus.service.method(_interface)
924
return self.checked_ok()
927
@dbus.service.method(_interface)
932
# StartChecker - method
933
@dbus.service.method(_interface)
934
def StartChecker(self):
939
@dbus.service.method(_interface)
944
# StopChecker - method
945
@dbus.service.method(_interface)
946
def StopChecker(self):
951
# ApprovalPending - property
952
@dbus_service_property(_interface, signature=u"b", access=u"read")
953
def ApprovalPending_dbus_property(self):
954
return dbus.Boolean(bool(self.approvals_pending))
956
# ApprovedByDefault - property
957
@dbus_service_property(_interface, signature=u"b",
959
def ApprovedByDefault_dbus_property(self, value=None):
960
if value is None: # get
961
return dbus.Boolean(self.approved_by_default)
962
self.approved_by_default = bool(value)
964
self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
965
dbus.Boolean(value, variant_level=1))
967
# ApprovalDelay - property
968
@dbus_service_property(_interface, signature=u"t",
970
def ApprovalDelay_dbus_property(self, value=None):
971
if value is None: # get
972
return dbus.UInt64(self.approval_delay_milliseconds())
973
self.approval_delay = datetime.timedelta(0, 0, 0, value)
975
self.PropertyChanged(dbus.String(u"ApprovalDelay"),
976
dbus.UInt64(value, variant_level=1))
978
# ApprovalDuration - property
979
@dbus_service_property(_interface, signature=u"t",
981
def ApprovalDuration_dbus_property(self, value=None):
982
if value is None: # get
983
return dbus.UInt64(self._timedelta_to_milliseconds(
984
self.approval_duration))
985
self.approval_duration = datetime.timedelta(0, 0, 0, value)
987
self.PropertyChanged(dbus.String(u"ApprovalDuration"),
988
dbus.UInt64(value, variant_level=1))
991
@dbus_service_property(_interface, signature=u"s", access=u"read")
992
def Name_dbus_property(self):
993
return dbus.String(self.name)
995
# Fingerprint - property
996
@dbus_service_property(_interface, signature=u"s", access=u"read")
997
def Fingerprint_dbus_property(self):
998
return dbus.String(self.fingerprint)
1001
@dbus_service_property(_interface, signature=u"s",
1002
access=u"readwrite")
1003
def Host_dbus_property(self, value=None):
1004
if value is None: # get
1005
return dbus.String(self.host)
1008
self.PropertyChanged(dbus.String(u"Host"),
1009
dbus.String(value, variant_level=1))
1011
# Created - property
1012
@dbus_service_property(_interface, signature=u"s", access=u"read")
1013
def Created_dbus_property(self):
1014
return dbus.String(self._datetime_to_dbus(self.created))
1016
# LastEnabled - property
1017
@dbus_service_property(_interface, signature=u"s", access=u"read")
1018
def LastEnabled_dbus_property(self):
1019
if self.last_enabled is None:
1020
return dbus.String(u"")
1021
return dbus.String(self._datetime_to_dbus(self.last_enabled))
1023
# Enabled - property
1024
@dbus_service_property(_interface, signature=u"b",
1025
access=u"readwrite")
1026
def Enabled_dbus_property(self, value=None):
1027
if value is None: # get
1028
return dbus.Boolean(self.enabled)
1034
# LastCheckedOK - property
1035
@dbus_service_property(_interface, signature=u"s",
1036
access=u"readwrite")
1037
def LastCheckedOK_dbus_property(self, value=None):
1038
if value is not None:
1041
if self.last_checked_ok is None:
1042
return dbus.String(u"")
1043
return dbus.String(self._datetime_to_dbus(self
1046
# LastApprovalRequest - property
1047
@dbus_service_property(_interface, signature=u"s", access=u"read")
1048
def LastApprovalRequest_dbus_property(self):
1049
if self.last_approval_request is None:
1050
return dbus.String(u"")
1051
return dbus.String(self.
1052
_datetime_to_dbus(self
1053
.last_approval_request))
1055
# Timeout - property
1056
@dbus_service_property(_interface, signature=u"t",
1057
access=u"readwrite")
1058
def Timeout_dbus_property(self, value=None):
1059
if value is None: # get
1060
return dbus.UInt64(self.timeout_milliseconds())
1061
self.timeout = datetime.timedelta(0, 0, 0, value)
1063
self.PropertyChanged(dbus.String(u"Timeout"),
1064
dbus.UInt64(value, variant_level=1))
1065
if getattr(self, u"disable_initiator_tag", None) is None:
1067
# Reschedule timeout
1068
gobject.source_remove(self.disable_initiator_tag)
1069
self.disable_initiator_tag = None
1070
time_to_die = (self.
1071
_timedelta_to_milliseconds((self
1076
if time_to_die <= 0:
1077
# The timeout has passed
1080
self.disable_initiator_tag = (gobject.timeout_add
1081
(time_to_die, self.disable))
1083
# Interval - property
1084
@dbus_service_property(_interface, signature=u"t",
1085
access=u"readwrite")
1086
def Interval_dbus_property(self, value=None):
1087
if value is None: # get
1088
return dbus.UInt64(self.interval_milliseconds())
1089
self.interval = datetime.timedelta(0, 0, 0, value)
1091
self.PropertyChanged(dbus.String(u"Interval"),
1092
dbus.UInt64(value, variant_level=1))
1093
if getattr(self, u"checker_initiator_tag", None) is None:
1095
# Reschedule checker run
1096
gobject.source_remove(self.checker_initiator_tag)
1097
self.checker_initiator_tag = (gobject.timeout_add
1098
(value, self.start_checker))
1099
self.start_checker() # Start one now, too
1101
# Checker - property
1102
@dbus_service_property(_interface, signature=u"s",
1103
access=u"readwrite")
1104
def Checker_dbus_property(self, value=None):
1105
if value is None: # get
1106
return dbus.String(self.checker_command)
1107
self.checker_command = value
1109
self.PropertyChanged(dbus.String(u"Checker"),
1110
dbus.String(self.checker_command,
1113
# CheckerRunning - property
1114
@dbus_service_property(_interface, signature=u"b",
1115
access=u"readwrite")
1116
def CheckerRunning_dbus_property(self, value=None):
1117
if value is None: # get
1118
return dbus.Boolean(self.checker is not None)
1120
self.start_checker()
1124
# ObjectPath - property
1125
@dbus_service_property(_interface, signature=u"o", access=u"read")
1126
def ObjectPath_dbus_property(self):
1127
return self.dbus_object_path # is already a dbus.ObjectPath
1130
@dbus_service_property(_interface, signature=u"ay",
1131
access=u"write", byte_arrays=True)
1132
def Secret_dbus_property(self, value):
1133
self.secret = str(value)
1138
class ProxyClient(object):
1139
def __init__(self, child_pipe, fpr, address):
1140
self._pipe = child_pipe
1141
self._pipe.send(('init', fpr, address))
1142
if not self._pipe.recv():
1145
def __getattribute__(self, name):
1146
if(name == '_pipe'):
1147
return super(ProxyClient, self).__getattribute__(name)
1148
self._pipe.send(('getattr', name))
1149
data = self._pipe.recv()
1150
if data[0] == 'data':
1152
if data[0] == 'function':
1153
def func(*args, **kwargs):
1154
self._pipe.send(('funcall', name, args, kwargs))
1155
return self._pipe.recv()[1]
1158
def __setattr__(self, name, value):
1159
if(name == '_pipe'):
1160
return super(ProxyClient, self).__setattr__(name, value)
1161
self._pipe.send(('setattr', name, value))
1164
class ClientHandler(socketserver.BaseRequestHandler, object):
1165
"""A class to handle client connections.
1167
Instantiated once for each connection to handle it.
252
def still_valid(self, now=None):
253
"""Has the timeout not yet passed for this client?"""
255
now = datetime.datetime.now()
256
if self.last_seen is None:
257
return now < (self.created + self.timeout)
259
return now < (self.last_seen + self.timeout)
262
def peer_certificate(session):
263
"Return an OpenPGP data packet string for the peer's certificate"
264
# If not an OpenPGP certificate...
265
if gnutls.library.functions.gnutls_certificate_type_get\
266
(session._c_object) \
267
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
268
# ...do the normal thing
269
return session.peer_certificate
270
list_size = ctypes.c_uint()
271
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
272
(session._c_object, ctypes.byref(list_size))
273
if list_size.value == 0:
276
return ctypes.string_at(cert.data, cert.size)
279
def fingerprint(openpgp):
280
"Convert an OpenPGP data string to a hexdigit fingerprint string"
281
# New empty GnuTLS certificate
282
crt = gnutls.library.types.gnutls_openpgp_crt_t()
283
gnutls.library.functions.gnutls_openpgp_crt_init\
285
# New GnuTLS "datum" with the OpenPGP public key
286
datum = gnutls.library.types.gnutls_datum_t\
287
(ctypes.cast(ctypes.c_char_p(openpgp),
288
ctypes.POINTER(ctypes.c_ubyte)),
289
ctypes.c_uint(len(openpgp)))
290
# Import the OpenPGP public key into the certificate
291
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
294
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
295
# New buffer for the fingerprint
296
buffer = ctypes.create_string_buffer(20)
297
buffer_length = ctypes.c_size_t()
298
# Get the fingerprint from the certificate into the buffer
299
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
300
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
301
# Deinit the certificate
302
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
303
# Convert the buffer to a Python bytestring
304
fpr = ctypes.string_at(buffer, buffer_length.value)
305
# Convert the bytestring to hexadecimal notation
306
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
310
class tcp_handler(SocketServer.BaseRequestHandler, object):
311
"""A TCP request handler class.
312
Instantiated by IPv6_TCPServer for each request to handle it.
1168
313
Note: This will run in its own forked process."""
1170
315
def handle(self):
1171
with contextlib.closing(self.server.child_pipe) as child_pipe:
1172
logger.info(u"TCP connection from: %s",
1173
unicode(self.client_address))
1174
logger.debug(u"Pipe FD: %d",
1175
self.server.child_pipe.fileno())
1177
session = (gnutls.connection
1178
.ClientSession(self.request,
1180
.X509Credentials()))
1182
# Note: gnutls.connection.X509Credentials is really a
1183
# generic GnuTLS certificate credentials object so long as
1184
# no X.509 keys are added to it. Therefore, we can use it
1185
# here despite using OpenPGP certificates.
1187
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1188
# u"+AES-256-CBC", u"+SHA1",
1189
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1191
# Use a fallback default, since this MUST be set.
1192
priority = self.server.gnutls_priority
1193
if priority is None:
1194
priority = u"NORMAL"
1195
(gnutls.library.functions
1196
.gnutls_priority_set_direct(session._c_object,
1199
# Start communication using the Mandos protocol
1200
# Get protocol number
1201
line = self.request.makefile().readline()
1202
logger.debug(u"Protocol version: %r", line)
1204
if int(line.strip().split()[0]) > 1:
1206
except (ValueError, IndexError, RuntimeError), error:
1207
logger.error(u"Unknown protocol version: %s", error)
1210
# Start GnuTLS connection
1213
except gnutls.errors.GNUTLSError, error:
1214
logger.warning(u"Handshake failed: %s", error)
1215
# Do not run session.bye() here: the session is not
1216
# established. Just abandon the request.
1218
logger.debug(u"Handshake succeeded")
1220
approval_required = False
1223
fpr = self.fingerprint(self.peer_certificate
1225
except (TypeError, gnutls.errors.GNUTLSError), error:
1226
logger.warning(u"Bad certificate: %s", error)
1228
logger.debug(u"Fingerprint: %s", fpr)
1231
client = ProxyClient(child_pipe, fpr,
1232
self.client_address)
1236
if client.approval_delay:
1237
delay = client.approval_delay
1238
client.approvals_pending += 1
1239
approval_required = True
1242
if not client.enabled:
1243
logger.warning(u"Client %s is disabled",
1245
if self.server.use_dbus:
1247
client.Rejected("Disabled")
1250
if client._approved or not client.approval_delay:
1251
#We are approved or approval is disabled
1253
elif client._approved is None:
1254
logger.info(u"Client %s needs approval",
1256
if self.server.use_dbus:
1258
client.NeedApproval(
1259
client.approval_delay_milliseconds(),
1260
client.approved_by_default)
1262
logger.warning(u"Client %s was not approved",
1264
if self.server.use_dbus:
1266
client.Rejected("Denied")
1269
#wait until timeout or approved
1270
#x = float(client._timedelta_to_milliseconds(delay))
1271
time = datetime.datetime.now()
1272
client.changedstate.acquire()
1273
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1274
client.changedstate.release()
1275
time2 = datetime.datetime.now()
1276
if (time2 - time) >= delay:
1277
if not client.approved_by_default:
1278
logger.warning("Client %s timed out while"
1279
" waiting for approval",
1281
if self.server.use_dbus:
1283
client.Rejected("Approval timed out")
1288
delay -= time2 - time
1291
while sent_size < len(client.secret):
1293
sent = session.send(client.secret[sent_size:])
1294
except (gnutls.errors.GNUTLSError), error:
1295
logger.warning("gnutls send failed")
1297
logger.debug(u"Sent: %d, remaining: %d",
1298
sent, len(client.secret)
1299
- (sent_size + sent))
1302
logger.info(u"Sending secret to %s", client.name)
1303
# bump the timeout as if seen
1305
if self.server.use_dbus:
1310
if approval_required:
1311
client.approvals_pending -= 1
1314
except (gnutls.errors.GNUTLSError), error:
1315
logger.warning("GnuTLS bye failed")
1318
def peer_certificate(session):
1319
"Return the peer's OpenPGP certificate as a bytestring"
1320
# If not an OpenPGP certificate...
1321
if (gnutls.library.functions
1322
.gnutls_certificate_type_get(session._c_object)
1323
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1324
# ...do the normal thing
1325
return session.peer_certificate
1326
list_size = ctypes.c_uint(1)
1327
cert_list = (gnutls.library.functions
1328
.gnutls_certificate_get_peers
1329
(session._c_object, ctypes.byref(list_size)))
1330
if not bool(cert_list) and list_size.value != 0:
1331
raise gnutls.errors.GNUTLSError(u"error getting peer"
1333
if list_size.value == 0:
1336
return ctypes.string_at(cert.data, cert.size)
1339
def fingerprint(openpgp):
1340
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1341
# New GnuTLS "datum" with the OpenPGP public key
1342
datum = (gnutls.library.types
1343
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1346
ctypes.c_uint(len(openpgp))))
1347
# New empty GnuTLS certificate
1348
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1349
(gnutls.library.functions
1350
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1351
# Import the OpenPGP public key into the certificate
1352
(gnutls.library.functions
1353
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1354
gnutls.library.constants
1355
.GNUTLS_OPENPGP_FMT_RAW))
1356
# Verify the self signature in the key
1357
crtverify = ctypes.c_uint()
1358
(gnutls.library.functions
1359
.gnutls_openpgp_crt_verify_self(crt, 0,
1360
ctypes.byref(crtverify)))
1361
if crtverify.value != 0:
1362
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1363
raise (gnutls.errors.CertificateSecurityError
1365
# New buffer for the fingerprint
1366
buf = ctypes.create_string_buffer(20)
1367
buf_len = ctypes.c_size_t()
1368
# Get the fingerprint from the certificate into the buffer
1369
(gnutls.library.functions
1370
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1371
ctypes.byref(buf_len)))
1372
# Deinit the certificate
1373
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1374
# Convert the buffer to a Python bytestring
1375
fpr = ctypes.string_at(buf, buf_len.value)
1376
# Convert the bytestring to hexadecimal notation
1377
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1381
class MultiprocessingMixIn(object):
1382
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1383
def sub_process_main(self, request, address):
1385
self.finish_request(request, address)
1387
self.handle_error(request, address)
1388
self.close_request(request)
1390
def process_request(self, request, address):
1391
"""Start a new process to process the request."""
1392
multiprocessing.Process(target = self.sub_process_main,
1393
args = (request, address)).start()
1395
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1396
""" adds a pipe to the MixIn """
1397
def process_request(self, request, client_address):
1398
"""Overrides and wraps the original process_request().
1400
This function creates a new pipe in self.pipe
1402
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1404
super(MultiprocessingMixInWithPipe,
1405
self).process_request(request, client_address)
1406
self.child_pipe.close()
1407
self.add_pipe(parent_pipe)
1409
def add_pipe(self, parent_pipe):
1410
"""Dummy function; override as necessary"""
1413
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1414
socketserver.TCPServer, object):
1415
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
316
logger.debug(u"TCP connection from: %s",
317
unicode(self.client_address))
318
session = gnutls.connection.ClientSession(self.request,
322
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
323
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
325
priority = "SECURE256"
327
gnutls.library.functions.gnutls_priority_set_direct\
328
(session._c_object, priority, None);
332
except gnutls.errors.GNUTLSError, error:
333
logger.debug(u"Handshake failed: %s", error)
334
# Do not run session.bye() here: the session is not
335
# established. Just abandon the request.
338
fpr = fingerprint(peer_certificate(session))
339
except (TypeError, gnutls.errors.GNUTLSError), error:
340
logger.debug(u"Bad certificate: %s", error)
343
logger.debug(u"Fingerprint: %s", fpr)
346
if c.fingerprint == fpr:
349
# Have to check if client.still_valid(), since it is possible
350
# that the client timed out while establishing the GnuTLS
352
if (not client) or (not client.still_valid()):
354
logger.debug(u"Client %(name)s is invalid",
357
logger.debug(u"Client not found for fingerprint: %s",
362
while sent_size < len(client.secret):
363
sent = session.send(client.secret[sent_size:])
364
logger.debug(u"Sent: %d, remaining: %d",
365
sent, len(client.secret)
366
- (sent_size + sent))
371
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
372
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1418
enabled: Boolean; whether this server is activated yet
1419
interface: None or a network interface name (string)
1420
use_ipv6: Boolean; to use IPv6 or not
374
options: Command line options
375
clients: Set() of Client objects
1422
def __init__(self, server_address, RequestHandlerClass,
1423
interface=None, use_ipv6=True):
1424
self.interface = interface
1426
self.address_family = socket.AF_INET6
1427
socketserver.TCPServer.__init__(self, server_address,
1428
RequestHandlerClass)
377
address_family = socket.AF_INET6
378
def __init__(self, *args, **kwargs):
379
if "options" in kwargs:
380
self.options = kwargs["options"]
381
del kwargs["options"]
382
if "clients" in kwargs:
383
self.clients = kwargs["clients"]
384
del kwargs["clients"]
385
return super(type(self), self).__init__(*args, **kwargs)
1429
386
def server_bind(self):
1430
387
"""This overrides the normal server_bind() function
1431
388
to bind to an interface if one was specified, and also NOT to
1432
389
bind to an address or port if they were not specified."""
1433
if self.interface is not None:
1434
if SO_BINDTODEVICE is None:
1435
logger.error(u"SO_BINDTODEVICE does not exist;"
1436
u" cannot bind to interface %s",
1440
self.socket.setsockopt(socket.SOL_SOCKET,
1444
except socket.error, error:
1445
if error[0] == errno.EPERM:
1446
logger.error(u"No permission to"
1447
u" bind to interface %s",
1449
elif error[0] == errno.ENOPROTOOPT:
1450
logger.error(u"SO_BINDTODEVICE not available;"
1451
u" cannot bind to interface %s",
390
if self.options.interface:
391
if not hasattr(socket, "SO_BINDTODEVICE"):
392
# From /usr/include/asm-i486/socket.h
393
socket.SO_BINDTODEVICE = 25
395
self.socket.setsockopt(socket.SOL_SOCKET,
396
socket.SO_BINDTODEVICE,
397
self.options.interface)
398
except socket.error, error:
399
if error[0] == errno.EPERM:
400
logger.warning(u"No permission to"
401
u" bind to interface %s",
402
self.options.interface)
1455
405
# Only bind(2) the socket if we really need to.
1456
406
if self.server_address[0] or self.server_address[1]:
1457
407
if not self.server_address[0]:
1458
if self.address_family == socket.AF_INET6:
1459
any_address = u"::" # in6addr_any
1461
any_address = socket.INADDR_ANY
1462
self.server_address = (any_address,
409
self.server_address = (in6addr_any,
1463
410
self.server_address[1])
1464
elif not self.server_address[1]:
411
elif self.server_address[1] is None:
1465
412
self.server_address = (self.server_address[0],
1467
# if self.interface:
1468
# self.server_address = (self.server_address[0],
1473
return socketserver.TCPServer.server_bind(self)
1476
class MandosServer(IPv6_TCPServer):
1480
clients: set of Client objects
1481
gnutls_priority GnuTLS priority string
1482
use_dbus: Boolean; to emit D-Bus signals or not
1484
Assumes a gobject.MainLoop event loop.
1486
def __init__(self, server_address, RequestHandlerClass,
1487
interface=None, use_ipv6=True, clients=None,
1488
gnutls_priority=None, use_dbus=True):
1489
self.enabled = False
1490
self.clients = clients
1491
if self.clients is None:
1492
self.clients = set()
1493
self.use_dbus = use_dbus
1494
self.gnutls_priority = gnutls_priority
1495
IPv6_TCPServer.__init__(self, server_address,
1496
RequestHandlerClass,
1497
interface = interface,
1498
use_ipv6 = use_ipv6)
1499
def server_activate(self):
1501
return socketserver.TCPServer.server_activate(self)
1504
def add_pipe(self, parent_pipe):
1505
# Call "handle_ipc" for both data and EOF events
1506
gobject.io_add_watch(parent_pipe.fileno(),
1507
gobject.IO_IN | gobject.IO_HUP,
1508
functools.partial(self.handle_ipc,
1509
parent_pipe = parent_pipe))
1511
def handle_ipc(self, source, condition, parent_pipe=None,
1512
client_object=None):
1514
gobject.IO_IN: u"IN", # There is data to read.
1515
gobject.IO_OUT: u"OUT", # Data can be written (without
1517
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1518
gobject.IO_ERR: u"ERR", # Error condition.
1519
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1520
# broken, usually for pipes and
1523
conditions_string = ' | '.join(name
1525
condition_names.iteritems()
1526
if cond & condition)
1527
# error or the other end of multiprocessing.Pipe has closed
1528
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1531
# Read a request from the child
1532
request = parent_pipe.recv()
1533
command = request[0]
1535
if command == 'init':
1537
address = request[2]
1539
for c in self.clients:
1540
if c.fingerprint == fpr:
1544
logger.warning(u"Client not found for fingerprint: %s, ad"
1545
u"dress: %s", fpr, address)
1548
mandos_dbus_service.ClientNotFound(fpr, address[0])
1549
parent_pipe.send(False)
1552
gobject.io_add_watch(parent_pipe.fileno(),
1553
gobject.IO_IN | gobject.IO_HUP,
1554
functools.partial(self.handle_ipc,
1555
parent_pipe = parent_pipe,
1556
client_object = client))
1557
parent_pipe.send(True)
1558
# remove the old hook in favor of the new above hook on same fileno
1560
if command == 'funcall':
1561
funcname = request[1]
1565
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1567
if command == 'getattr':
1568
attrname = request[1]
1569
if callable(client_object.__getattribute__(attrname)):
1570
parent_pipe.send(('function',))
1572
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1574
if command == 'setattr':
1575
attrname = request[1]
1577
setattr(client_object, attrname, value)
414
return super(type(self), self).server_bind()
1582
417
def string_to_delta(interval):
1583
418
"""Parse a string and return a datetime.timedelta
1585
>>> string_to_delta(u'7d')
420
>>> string_to_delta('7d')
1586
421
datetime.timedelta(7)
1587
>>> string_to_delta(u'60s')
422
>>> string_to_delta('60s')
1588
423
datetime.timedelta(0, 60)
1589
>>> string_to_delta(u'60m')
424
>>> string_to_delta('60m')
1590
425
datetime.timedelta(0, 3600)
1591
>>> string_to_delta(u'24h')
426
>>> string_to_delta('24h')
1592
427
datetime.timedelta(1)
1593
428
>>> string_to_delta(u'1w')
1594
429
datetime.timedelta(7)
1595
>>> string_to_delta(u'5m 30s')
1596
datetime.timedelta(0, 330)
1598
timevalue = datetime.timedelta(0)
1599
for s in interval.split():
1601
suffix = unicode(s[-1])
1604
delta = datetime.timedelta(value)
1605
elif suffix == u"s":
1606
delta = datetime.timedelta(0, value)
1607
elif suffix == u"m":
1608
delta = datetime.timedelta(0, 0, 0, 0, value)
1609
elif suffix == u"h":
1610
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1611
elif suffix == u"w":
1612
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1614
raise ValueError(u"Unknown suffix %r" % suffix)
1615
except (ValueError, IndexError), e:
1616
raise ValueError(e.message)
432
suffix=unicode(interval[-1])
433
value=int(interval[:-1])
435
delta = datetime.timedelta(value)
437
delta = datetime.timedelta(0, value)
439
delta = datetime.timedelta(0, 0, 0, 0, value)
441
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
443
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
446
except (ValueError, IndexError):
452
"""From the Avahi server example code"""
453
global group, serviceName, serviceType, servicePort, serviceTXT, \
456
group = dbus.Interface(
457
bus.get_object( avahi.DBUS_NAME,
458
server.EntryGroupNew()),
459
avahi.DBUS_INTERFACE_ENTRY_GROUP)
460
group.connect_to_signal('StateChanged',
461
entry_group_state_changed)
462
logger.debug(u"Adding service '%s' of type '%s' ...",
463
serviceName, serviceType)
466
serviceInterface, # interface
467
avahi.PROTO_INET6, # protocol
468
dbus.UInt32(0), # flags
469
serviceName, serviceType,
471
dbus.UInt16(servicePort),
472
avahi.string_array_to_txt_array(serviceTXT))
476
def remove_service():
477
"""From the Avahi server example code"""
480
if not group is None:
484
def server_state_changed(state):
485
"""From the Avahi server example code"""
486
if state == avahi.SERVER_COLLISION:
487
logger.warning(u"Server name collision")
489
elif state == avahi.SERVER_RUNNING:
493
def entry_group_state_changed(state, error):
494
"""From the Avahi server example code"""
495
global serviceName, server, rename_count
497
logger.debug(u"state change: %i", state)
499
if state == avahi.ENTRY_GROUP_ESTABLISHED:
500
logger.debug(u"Service established.")
501
elif state == avahi.ENTRY_GROUP_COLLISION:
503
rename_count = rename_count - 1
505
name = server.GetAlternativeServiceName(name)
506
logger.warning(u"Service name collision, "
507
u"changing name to '%s' ...", name)
512
logger.error(u"No suitable service name found after %i"
513
u" retries, exiting.", n_rename)
515
elif state == avahi.ENTRY_GROUP_FAILURE:
516
logger.error(u"Error in group state changed %s",
1621
521
def if_nametoindex(interface):
1622
"""Call the C function if_nametoindex(), or equivalent
1624
Note: This function cannot accept a unicode string."""
1625
global if_nametoindex
522
"""Call the C function if_nametoindex()"""
1627
if_nametoindex = (ctypes.cdll.LoadLibrary
1628
(ctypes.util.find_library(u"c"))
524
libc = ctypes.cdll.LoadLibrary("libc.so.6")
525
return libc.if_nametoindex(interface)
1630
526
except (OSError, AttributeError):
1631
logger.warning(u"Doing if_nametoindex the hard way")
1632
def if_nametoindex(interface):
1633
"Get an interface index the hard way, i.e. using fcntl()"
1634
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1635
with contextlib.closing(socket.socket()) as s:
1636
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1637
struct.pack(str(u"16s16x"),
1639
interface_index = struct.unpack(str(u"I"),
1641
return interface_index
1642
return if_nametoindex(interface)
1645
def daemon(nochdir = False, noclose = False):
527
if "struct" not in sys.modules:
529
if "fcntl" not in sys.modules:
531
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
533
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
534
struct.pack("16s16x", interface))
536
interface_index = struct.unpack("I", ifreq[16:20])[0]
537
return interface_index
540
def daemon(nochdir, noclose):
1646
541
"""See daemon(3). Standard BSD Unix function.
1648
542
This should really exist as os.daemon, but it doesn't (yet)."""
1657
549
# Close all standard open file descriptors
1658
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
550
null = os.open("/dev/null", os.O_NOCTTY | os.O_RDWR)
1659
551
if not stat.S_ISCHR(os.fstat(null).st_mode):
1660
552
raise OSError(errno.ENODEV,
1661
u"%s not a character device"
553
"/dev/null not a character device")
1663
554
os.dup2(null, sys.stdin.fileno())
1664
555
os.dup2(null, sys.stdout.fileno())
1665
556
os.dup2(null, sys.stderr.fileno())
1672
##################################################################
1673
# Parsing of options, both command line and config file
1675
parser = optparse.OptionParser(version = "%%prog %s" % version)
1676
parser.add_option("-i", u"--interface", type=u"string",
1677
metavar="IF", help=u"Bind to interface IF")
1678
parser.add_option("-a", u"--address", type=u"string",
1679
help=u"Address to listen for requests on")
1680
parser.add_option("-p", u"--port", type=u"int",
1681
help=u"Port number to receive requests on")
1682
parser.add_option("--check", action=u"store_true",
1683
help=u"Run self-test")
1684
parser.add_option("--debug", action=u"store_true",
1685
help=u"Debug mode; run in foreground and log to"
1687
parser.add_option("--debuglevel", type=u"string", metavar="LEVEL",
1688
help=u"Debug level for stdout output")
1689
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1690
u" priority string (see GnuTLS documentation)")
1691
parser.add_option("--servicename", type=u"string",
1692
metavar=u"NAME", help=u"Zeroconf service name")
1693
parser.add_option("--configdir", type=u"string",
1694
default=u"/etc/mandos", metavar=u"DIR",
1695
help=u"Directory to search for configuration"
1697
parser.add_option("--no-dbus", action=u"store_false",
1698
dest=u"use_dbus", help=u"Do not provide D-Bus"
1699
u" system bus interface")
1700
parser.add_option("--no-ipv6", action=u"store_false",
1701
dest=u"use_ipv6", help=u"Do not use IPv6")
1702
options = parser.parse_args()[0]
561
def killme(status = 0):
562
logger.debug("Stopping server with exit status %d", status)
564
if main_loop_started:
570
if __name__ == '__main__':
572
main_loop_started = False
573
parser = OptionParser()
574
parser.add_option("-i", "--interface", type="string",
575
default=None, metavar="IF",
576
help="Bind to interface IF")
577
parser.add_option("-p", "--port", type="int", default=None,
578
help="Port number to receive requests on")
579
parser.add_option("--timeout", type="string", # Parsed later
581
help="Amount of downtime allowed for clients")
582
parser.add_option("--interval", type="string", # Parsed later
584
help="How often to check that a client is up")
585
parser.add_option("--check", action="store_true", default=False,
586
help="Run self-test")
587
parser.add_option("--debug", action="store_true", default=False,
589
(options, args) = parser.parse_args()
1704
591
if options.check:
1706
593
doctest.testmod()
1709
# Default values for config file for server-global settings
1710
server_defaults = { u"interface": u"",
1715
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1716
u"servicename": u"Mandos",
1717
u"use_dbus": u"True",
1718
u"use_ipv6": u"True",
1722
# Parse config file for server-global settings
1723
server_config = configparser.SafeConfigParser(server_defaults)
1725
server_config.read(os.path.join(options.configdir,
1727
# Convert the SafeConfigParser object to a dict
1728
server_settings = server_config.defaults()
1729
# Use the appropriate methods on the non-string config options
1730
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1731
server_settings[option] = server_config.getboolean(u"DEFAULT",
1733
if server_settings["port"]:
1734
server_settings["port"] = server_config.getint(u"DEFAULT",
1738
# Override the settings from the config file with command line
1740
for option in (u"interface", u"address", u"port", u"debug",
1741
u"priority", u"servicename", u"configdir",
1742
u"use_dbus", u"use_ipv6", u"debuglevel"):
1743
value = getattr(options, option)
1744
if value is not None:
1745
server_settings[option] = value
1747
# Force all strings to be unicode
1748
for option in server_settings.keys():
1749
if type(server_settings[option]) is str:
1750
server_settings[option] = unicode(server_settings[option])
1751
# Now we have our good server settings in "server_settings"
1753
##################################################################
1756
debug = server_settings[u"debug"]
1757
debuglevel = server_settings[u"debuglevel"]
1758
use_dbus = server_settings[u"use_dbus"]
1759
use_ipv6 = server_settings[u"use_ipv6"]
1761
if server_settings[u"servicename"] != u"Mandos":
1762
syslogger.setFormatter(logging.Formatter
1763
(u'Mandos (%s) [%%(process)d]:'
1764
u' %%(levelname)s: %%(message)s'
1765
% server_settings[u"servicename"]))
1767
# Parse config file with clients
1768
client_defaults = { u"timeout": u"1h",
1770
u"checker": u"fping -q -- %%(host)s",
1772
u"approval_delay": u"0s",
1773
u"approval_duration": u"1s",
1775
client_config = configparser.SafeConfigParser(client_defaults)
1776
client_config.read(os.path.join(server_settings[u"configdir"],
1779
global mandos_dbus_service
1780
mandos_dbus_service = None
1782
tcp_server = MandosServer((server_settings[u"address"],
1783
server_settings[u"port"]),
1785
interface=(server_settings[u"interface"]
1789
server_settings[u"priority"],
1792
pidfilename = u"/var/run/mandos.pid"
1794
pidfile = open(pidfilename, u"w")
1796
logger.error(u"Could not open file %r", pidfilename)
1799
uid = pwd.getpwnam(u"_mandos").pw_uid
1800
gid = pwd.getpwnam(u"_mandos").pw_gid
1803
uid = pwd.getpwnam(u"mandos").pw_uid
1804
gid = pwd.getpwnam(u"mandos").pw_gid
1807
uid = pwd.getpwnam(u"nobody").pw_uid
1808
gid = pwd.getpwnam(u"nobody").pw_gid
1815
except OSError, error:
1816
if error[0] != errno.EPERM:
1819
if not debug and not debuglevel:
1820
syslogger.setLevel(logging.WARNING)
1821
console.setLevel(logging.WARNING)
1823
level = getattr(logging, debuglevel.upper())
1824
syslogger.setLevel(level)
1825
console.setLevel(level)
1828
# Enable all possible GnuTLS debugging
1830
# "Use a log level over 10 to enable all debugging options."
1832
gnutls.library.functions.gnutls_global_set_log_level(11)
1834
@gnutls.library.types.gnutls_log_func
1835
def debug_gnutls(level, string):
1836
logger.debug(u"GnuTLS: %s", string[:-1])
1838
(gnutls.library.functions
1839
.gnutls_global_set_log_function(debug_gnutls))
1841
# Redirect stdin so all checkers get /dev/null
1842
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1843
os.dup2(null, sys.stdin.fileno())
1847
# No console logging
1848
logger.removeHandler(console)
1852
# From the Avahi example code
596
# Parse the time arguments
598
options.timeout = string_to_delta(options.timeout)
600
parser.error("option --timeout: Unparseable time")
602
options.interval = string_to_delta(options.interval)
604
parser.error("option --interval: Unparseable time")
607
defaults = { "checker": "fping -q -- %%(fqdn)s" }
608
client_config = ConfigParser.SafeConfigParser(defaults)
609
#client_config.readfp(open("secrets.conf"), "secrets.conf")
610
client_config.read("mandos-clients.conf")
612
# From the Avahi server example code
1853
613
DBusGMainLoop(set_as_default=True )
1854
614
main_loop = gobject.MainLoop()
1855
615
bus = dbus.SystemBus()
616
server = dbus.Interface(
617
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
618
avahi.DBUS_INTERFACE_SERVER )
1856
619
# End of Avahi example code
1859
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1860
bus, do_not_queue=True)
1861
except dbus.exceptions.NameExistsException, e:
1862
logger.error(unicode(e) + u", disabling D-Bus")
1864
server_settings[u"use_dbus"] = False
1865
tcp_server.use_dbus = False
1866
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1867
service = AvahiService(name = server_settings[u"servicename"],
1868
servicetype = u"_mandos._tcp",
1869
protocol = protocol, bus = bus)
1870
if server_settings["interface"]:
1871
service.interface = (if_nametoindex
1872
(str(server_settings[u"interface"])))
1875
# Close all input and output, do double fork, etc.
1878
global multiprocessing_manager
1879
multiprocessing_manager = multiprocessing.Manager()
1881
client_class = Client
1883
client_class = functools.partial(ClientDBus, bus = bus)
1884
def client_config_items(config, section):
1885
special_settings = {
1886
"approved_by_default":
1887
lambda: config.getboolean(section,
1888
"approved_by_default"),
1890
for name, value in config.items(section):
1892
yield (name, special_settings[name]())
1896
tcp_server.clients.update(set(
1897
client_class(name = section,
1898
config= dict(client_config_items(
1899
client_config, section)))
1900
for section in client_config.sections()))
1901
if not tcp_server.clients:
1902
logger.warning(u"No clients defined")
1908
pidfile.write(str(pid) + "\n")
1911
logger.error(u"Could not write to file %r with PID %d",
1914
# "pidfile" was never created
1918
signal.signal(signal.SIGINT, signal.SIG_IGN)
1920
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1921
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1924
class MandosDBusService(dbus.service.Object):
1925
"""A D-Bus proxy object"""
1927
dbus.service.Object.__init__(self, bus, u"/")
1928
_interface = u"se.bsnet.fukt.Mandos"
1930
@dbus.service.signal(_interface, signature=u"o")
1931
def ClientAdded(self, objpath):
1935
@dbus.service.signal(_interface, signature=u"ss")
1936
def ClientNotFound(self, fingerprint, address):
1940
@dbus.service.signal(_interface, signature=u"os")
1941
def ClientRemoved(self, objpath, name):
1945
@dbus.service.method(_interface, out_signature=u"ao")
1946
def GetAllClients(self):
1948
return dbus.Array(c.dbus_object_path
1949
for c in tcp_server.clients)
1951
@dbus.service.method(_interface,
1952
out_signature=u"a{oa{sv}}")
1953
def GetAllClientsWithProperties(self):
1955
return dbus.Dictionary(
1956
((c.dbus_object_path, c.GetAll(u""))
1957
for c in tcp_server.clients),
1958
signature=u"oa{sv}")
1960
@dbus.service.method(_interface, in_signature=u"o")
1961
def RemoveClient(self, object_path):
1963
for c in tcp_server.clients:
1964
if c.dbus_object_path == object_path:
1965
tcp_server.clients.remove(c)
1966
c.remove_from_connection()
1967
# Don't signal anything except ClientRemoved
1968
c.disable(quiet=True)
1970
self.ClientRemoved(object_path, c.name)
1972
raise KeyError(object_path)
1976
mandos_dbus_service = MandosDBusService()
621
debug = options.debug
624
console = logging.StreamHandler()
625
# console.setLevel(logging.DEBUG)
626
console.setFormatter(logging.Formatter\
627
('%(levelname)s: %(message)s'))
628
logger.addHandler(console)
632
def remove_from_clients(client):
633
clients.remove(client)
635
logger.debug(u"No clients left, exiting")
638
clients.update(Set(Client(name=section, options=options,
639
stop_hook = remove_from_clients,
640
**(dict(client_config\
642
for section in client_config.sections()))
1979
648
"Cleanup function; run on exit"
650
# From the Avahi server example code
651
if not group is None:
654
# End of Avahi example code
1982
while tcp_server.clients:
1983
client = tcp_server.clients.pop()
1985
client.remove_from_connection()
1986
client.disable_hook = None
1987
# Don't signal anything except ClientRemoved
1988
client.disable(quiet=True)
1991
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
656
for client in clients:
657
client.stop_hook = None
1994
660
atexit.register(cleanup)
1996
for client in tcp_server.clients:
1999
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2003
tcp_server.server_activate()
2005
# Find out what port we got
2006
service.port = tcp_server.socket.getsockname()[1]
2008
logger.info(u"Now listening on address %r, port %d,"
2009
" flowinfo %d, scope_id %d"
2010
% tcp_server.socket.getsockname())
2012
logger.info(u"Now listening on address %r, port %d"
2013
% tcp_server.socket.getsockname())
2015
#service.interface = tcp_server.socket.getsockname()[3]
2018
# From the Avahi example code
2021
except dbus.exceptions.DBusException, error:
2022
logger.critical(u"DBusException: %s", error)
2025
# End of Avahi example code
2027
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2028
lambda *args, **kwargs:
2029
(tcp_server.handle_request
2030
(*args[2:], **kwargs) or True))
2032
logger.debug(u"Starting main loop")
663
signal.signal(signal.SIGINT, signal.SIG_IGN)
664
signal.signal(signal.SIGHUP, lambda signum, frame: killme())
665
signal.signal(signal.SIGTERM, lambda signum, frame: killme())
667
for client in clients:
670
tcp_server = IPv6_TCPServer((None, options.port),
674
# Find out what random port we got
675
servicePort = tcp_server.socket.getsockname()[1]
676
logger.debug(u"Now listening on port %d", servicePort)
678
if options.interface is not None:
679
serviceInterface = if_nametoindex(options.interface)
681
# From the Avahi server example code
682
server.connect_to_signal("StateChanged", server_state_changed)
684
server_state_changed(server.GetState())
685
except dbus.exceptions.DBusException, error:
686
logger.critical(u"DBusException: %s", error)
688
# End of Avahi example code
690
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
691
lambda *args, **kwargs:
692
tcp_server.handle_request(*args[2:],
695
main_loop_started = True
2034
except AvahiError, error:
2035
logger.critical(u"AvahiError: %s", error)
2038
697
except KeyboardInterrupt:
2041
logger.debug(u"Server received KeyboardInterrupt")
2042
logger.debug(u"Server exiting")
2043
# Must run before the D-Bus bus name gets deregistered
2046
if __name__ == '__main__':