45
14
import gnutls.library.functions
46
15
import gnutls.library.constants
47
16
import gnutls.library.types
48
import ConfigParser as configparser
57
import logging.handlers
63
import cPickle as pickle
64
import multiprocessing
70
28
from dbus.mainloop.glib import DBusGMainLoop
73
import xml.dom.minidom
77
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
78
except AttributeError:
80
from IN import SO_BINDTODEVICE
82
SO_BINDTODEVICE = None
87
#logger = logging.getLogger('mandos')
32
import logging.handlers
34
# logghandler.setFormatter(logging.Formatter('%(levelname)s %(message)s')
88
36
logger = logging.Logger('mandos')
89
syslogger = (logging.handlers.SysLogHandler
90
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
91
address = str("/dev/log")))
92
syslogger.setFormatter(logging.Formatter
93
('Mandos [%(process)d]: %(levelname)s:'
95
logger.addHandler(syslogger)
97
console = logging.StreamHandler()
98
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
101
logger.addHandler(console)
103
class AvahiError(Exception):
104
def __init__(self, value, *args, **kwargs):
106
super(AvahiError, self).__init__(value, *args, **kwargs)
107
def __unicode__(self):
108
return unicode(repr(self.value))
110
class AvahiServiceError(AvahiError):
113
class AvahiGroupError(AvahiError):
117
class AvahiService(object):
118
"""An Avahi (Zeroconf) service.
121
interface: integer; avahi.IF_UNSPEC or an interface index.
122
Used to optionally bind to the specified interface.
123
name: string; Example: 'Mandos'
124
type: string; Example: '_mandos._tcp'.
125
See <http://www.dns-sd.org/ServiceTypes.html>
126
port: integer; what port to announce
127
TXT: list of strings; TXT record for the service
128
domain: string; Domain to publish on, default to .local if empty.
129
host: string; Host to publish records for, default is localhost
130
max_renames: integer; maximum number of renames
131
rename_count: integer; counter so we only rename after collisions
132
a sensible number of times
133
group: D-Bus Entry Group
135
bus: dbus.SystemBus()
137
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
138
servicetype = None, port = None, TXT = None,
139
domain = "", host = "", max_renames = 32768,
140
protocol = avahi.PROTO_UNSPEC, bus = None):
141
self.interface = interface
143
self.type = servicetype
145
self.TXT = TXT if TXT is not None else []
148
self.rename_count = 0
149
self.max_renames = max_renames
150
self.protocol = protocol
151
self.group = None # our entry group
154
self.entry_group_state_changed_match = None
156
"""Derived from the Avahi example code"""
157
if self.rename_count >= self.max_renames:
158
logger.critical("No suitable Zeroconf service name found"
159
" after %i retries, exiting.",
161
raise AvahiServiceError("Too many renames")
162
self.name = unicode(self.server.GetAlternativeServiceName(self.name))
163
logger.info("Changing Zeroconf service name to %r ...",
165
syslogger.setFormatter(logging.Formatter
166
('Mandos (%s) [%%(process)d]:'
167
' %%(levelname)s: %%(message)s'
172
except dbus.exceptions.DBusException as error:
173
logger.critical("DBusException: %s", error)
176
self.rename_count += 1
178
"""Derived from the Avahi example code"""
179
if self.entry_group_state_changed_match is not None:
180
self.entry_group_state_changed_match.remove()
181
self.entry_group_state_changed_match = None
182
if self.group is not None:
185
"""Derived from the Avahi example code"""
187
if self.group is None:
188
self.group = dbus.Interface(
189
self.bus.get_object(avahi.DBUS_NAME,
190
self.server.EntryGroupNew()),
191
avahi.DBUS_INTERFACE_ENTRY_GROUP)
192
self.entry_group_state_changed_match = (
193
self.group.connect_to_signal(
194
'StateChanged', self .entry_group_state_changed))
195
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
196
self.name, self.type)
197
self.group.AddService(
200
dbus.UInt32(0), # flags
201
self.name, self.type,
202
self.domain, self.host,
203
dbus.UInt16(self.port),
204
avahi.string_array_to_txt_array(self.TXT))
206
def entry_group_state_changed(self, state, error):
207
"""Derived from the Avahi example code"""
208
logger.debug("Avahi entry group state change: %i", state)
210
if state == avahi.ENTRY_GROUP_ESTABLISHED:
211
logger.debug("Zeroconf service established.")
212
elif state == avahi.ENTRY_GROUP_COLLISION:
213
logger.info("Zeroconf service name collision.")
215
elif state == avahi.ENTRY_GROUP_FAILURE:
216
logger.critical("Avahi: Error in group state changed %s",
218
raise AvahiGroupError("State changed: %s"
221
"""Derived from the Avahi example code"""
222
if self.group is not None:
225
except (dbus.exceptions.UnknownMethodException,
226
dbus.exceptions.DBusException) as e:
230
def server_state_changed(self, state, error=None):
231
"""Derived from the Avahi example code"""
232
logger.debug("Avahi server state change: %i", state)
233
bad_states = { avahi.SERVER_INVALID:
234
"Zeroconf server invalid",
235
avahi.SERVER_REGISTERING: None,
236
avahi.SERVER_COLLISION:
237
"Zeroconf server name collision",
238
avahi.SERVER_FAILURE:
239
"Zeroconf server failure" }
240
if state in bad_states:
241
if bad_states[state] is not None:
243
logger.error(bad_states[state])
245
logger.error(bad_states[state] + ": %r", error)
247
elif state == avahi.SERVER_RUNNING:
251
logger.debug("Unknown state: %r", state)
253
logger.debug("Unknown state: %r: %r", state, error)
255
"""Derived from the Avahi example code"""
256
if self.server is None:
257
self.server = dbus.Interface(
258
self.bus.get_object(avahi.DBUS_NAME,
259
avahi.DBUS_PATH_SERVER,
260
follow_name_owner_changes=True),
261
avahi.DBUS_INTERFACE_SERVER)
262
self.server.connect_to_signal("StateChanged",
263
self.server_state_changed)
264
self.server_state_changed(self.server.GetState())
37
logger.addHandler(logging.handlers.SysLogHandler(facility = logging.handlers.SysLogHandler.LOG_DAEMON))
39
# This variable is used to optionally bind to a specified interface.
40
# It is a global variable to fit in with the other variables from the
41
# Avahi server example code.
42
serviceInterface = avahi.IF_UNSPEC
43
# From the Avahi server example code:
44
serviceName = "Mandos"
45
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
46
servicePort = None # Not known at startup
47
serviceTXT = [] # TXT record for the service
48
domain = "" # Domain to publish on, default to .local
49
host = "" # Host to publish records for, default to localhost
50
group = None #our entry group
51
rename_count = 12 # Counter so we only rename after collisions a
52
# sensible number of times
53
# End of Avahi example code
267
56
class Client(object):
268
57
"""A representation of a client host served by this server.
271
_approved: bool(); 'None' if not yet approved/disapproved
272
approval_delay: datetime.timedelta(); Time to wait for approval
273
approval_duration: datetime.timedelta(); Duration of one approval
274
checker: subprocess.Popen(); a running checker process used
275
to see if the client lives.
276
'None' if no process is running.
277
checker_callback_tag: a gobject event source tag, or None
278
checker_command: string; External command which is run to check
279
if client lives. %() expansions are done at
59
name: string; from the config file, used in log messages
60
fingerprint: string (40 or 32 hexadecimal digits); used to
61
uniquely identify the client
62
secret: bytestring; sent verbatim (over TLS) to client
63
fqdn: string (FQDN); available for use by the checker command
64
created: datetime.datetime()
65
last_seen: datetime.datetime() or None if not yet seen
66
timeout: datetime.timedelta(); How long from last_seen until
67
this client is invalid
68
interval: datetime.timedelta(); How often to start a new checker
69
stop_hook: If set, called by stop() as stop_hook(self)
70
checker: subprocess.Popen(); a running checker process used
71
to see if the client lives.
72
Is None if no process is running.
73
checker_initiator_tag: a gobject event source tag, or None
74
stop_initiator_tag: - '' -
75
checker_callback_tag: - '' -
76
checker_command: string; External command which is run to check if
77
client lives. %()s expansions are done at
280
78
runtime with vars(self) as dict, so that for
281
79
instance %(name)s can be used in the command.
282
checker_initiator_tag: a gobject event source tag, or None
283
created: datetime.datetime(); (UTC) object creation
284
current_checker_command: string; current running checker_command
285
disable_hook: If set, called by disable() as disable_hook(self)
286
disable_initiator_tag: a gobject event source tag, or None
288
fingerprint: string (40 or 32 hexadecimal digits); used to
289
uniquely identify the client
290
host: string; available for use by the checker command
291
interval: datetime.timedelta(); How often to start a new checker
292
last_approval_request: datetime.datetime(); (UTC) or None
293
last_checked_ok: datetime.datetime(); (UTC) or None
294
last_enabled: datetime.datetime(); (UTC)
295
name: string; from the config file, used in log messages and
297
secret: bytestring; sent verbatim (over TLS) to client
298
timeout: datetime.timedelta(); How long from last_checked_ok
299
until this client is disabled
300
extended_timeout: extra long timeout when password has been sent
301
runtime_expansions: Allowed attributes for runtime expansion.
302
expire: datetime.datetime(); time (UTC) when a client will be
81
_timeout: Real variable for 'timeout'
82
_interval: Real variable for 'interval'
83
_timeout_milliseconds: Used by gobject.timeout_add()
84
_interval_milliseconds: - '' -
306
runtime_expansions = ("approval_delay", "approval_duration",
307
"created", "enabled", "fingerprint",
308
"host", "interval", "last_checked_ok",
309
"last_enabled", "name", "timeout")
312
def _timedelta_to_milliseconds(td):
313
"Convert a datetime.timedelta() to milliseconds"
314
return ((td.days * 24 * 60 * 60 * 1000)
315
+ (td.seconds * 1000)
316
+ (td.microseconds // 1000))
318
def timeout_milliseconds(self):
319
"Return the 'timeout' attribute in milliseconds"
320
return self._timedelta_to_milliseconds(self.timeout)
322
def extended_timeout_milliseconds(self):
323
"Return the 'extended_timeout' attribute in milliseconds"
324
return self._timedelta_to_milliseconds(self.extended_timeout)
326
def interval_milliseconds(self):
327
"Return the 'interval' attribute in milliseconds"
328
return self._timedelta_to_milliseconds(self.interval)
330
def approval_delay_milliseconds(self):
331
return self._timedelta_to_milliseconds(self.approval_delay)
333
def __init__(self, name = None, disable_hook=None, config=None):
334
"""Note: the 'checker' key in 'config' sets the
335
'checker_command' attribute and *not* the 'checker'
86
def _set_timeout(self, timeout):
87
"Setter function for 'timeout' attribute"
88
self._timeout = timeout
89
self._timeout_milliseconds = ((self.timeout.days
90
* 24 * 60 * 60 * 1000)
91
+ (self.timeout.seconds * 1000)
92
+ (self.timeout.microseconds
94
timeout = property(lambda self: self._timeout,
97
def _set_interval(self, interval):
98
"Setter function for 'interval' attribute"
99
self._interval = interval
100
self._interval_milliseconds = ((self.interval.days
101
* 24 * 60 * 60 * 1000)
102
+ (self.interval.seconds
104
+ (self.interval.microseconds
106
interval = property(lambda self: self._interval,
109
def __init__(self, name=None, options=None, stop_hook=None,
110
fingerprint=None, secret=None, secfile=None, fqdn=None,
111
timeout=None, interval=-1, checker=None):
340
logger.debug("Creating client %r", self.name)
341
# Uppercase and remove spaces from fingerprint for later
342
# comparison purposes with return value from the fingerprint()
344
self.fingerprint = (config["fingerprint"].upper()
346
logger.debug(" Fingerprint: %s", self.fingerprint)
347
if "secret" in config:
348
self.secret = config["secret"].decode("base64")
349
elif "secfile" in config:
350
with open(os.path.expanduser(os.path.expandvars
351
(config["secfile"])),
353
self.secret = secfile.read()
355
raise TypeError("No secret or secfile for client %s"
357
self.host = config.get("host", "")
358
self.created = datetime.datetime.utcnow()
360
self.last_approval_request = None
361
self.last_enabled = None
362
self.last_checked_ok = None
363
self.timeout = string_to_delta(config["timeout"])
364
self.extended_timeout = string_to_delta(config["extended_timeout"])
365
self.interval = string_to_delta(config["interval"])
366
self.disable_hook = disable_hook
113
# Uppercase and remove spaces from fingerprint
114
# for later comparison purposes with return value of
115
# the fingerprint() function
116
self.fingerprint = fingerprint.upper().replace(u" ", u"")
118
self.secret = secret.decode(u"base64")
121
self.secret = sf.read()
124
raise RuntimeError(u"No secret or secfile for client %s"
126
self.fqdn = fqdn # string
127
self.created = datetime.datetime.now()
128
self.last_seen = None
130
timeout = options.timeout
131
self.timeout = timeout
133
interval = options.interval
135
interval = string_to_delta(interval)
136
self.interval = interval
137
self.stop_hook = stop_hook
367
138
self.checker = None
368
139
self.checker_initiator_tag = None
369
self.disable_initiator_tag = None
140
self.stop_initiator_tag = None
371
141
self.checker_callback_tag = None
372
self.checker_command = config["checker"]
373
self.current_checker_command = None
374
self.last_connect = None
375
self._approved = None
376
self.approved_by_default = config.get("approved_by_default",
378
self.approvals_pending = 0
379
self.approval_delay = string_to_delta(
380
config["approval_delay"])
381
self.approval_duration = string_to_delta(
382
config["approval_duration"])
383
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
385
def send_changedstate(self):
386
self.changedstate.acquire()
387
self.changedstate.notify_all()
388
self.changedstate.release()
391
"""Start this client's checker and timeout hooks"""
392
if getattr(self, "enabled", False):
395
self.send_changedstate()
396
self.last_enabled = datetime.datetime.utcnow()
142
self.check_command = checker
144
"""Start this clients checker and timeout hooks"""
397
145
# Schedule a new checker to be started an 'interval' from now,
398
146
# and every interval from then on.
399
self.checker_initiator_tag = (gobject.timeout_add
400
(self.interval_milliseconds(),
402
# Schedule a disable() when 'timeout' has passed
403
self.expires = datetime.datetime.utcnow() + self.timeout
404
self.disable_initiator_tag = (gobject.timeout_add
405
(self.timeout_milliseconds(),
147
self.checker_initiator_tag = gobject.timeout_add\
148
(self._interval_milliseconds,
408
150
# Also start a new checker *right now*.
409
151
self.start_checker()
411
def disable(self, quiet=True):
412
"""Disable this client."""
413
if not getattr(self, "enabled", False):
416
self.send_changedstate()
418
logger.info("Disabling client %s", self.name)
419
if getattr(self, "disable_initiator_tag", False):
420
gobject.source_remove(self.disable_initiator_tag)
421
self.disable_initiator_tag = None
423
if getattr(self, "checker_initiator_tag", False):
152
# Schedule a stop() when 'timeout' has passed
153
self.stop_initiator_tag = gobject.timeout_add\
154
(self._timeout_milliseconds,
158
The possibility that this client might be restarted is left
159
open, but not currently used."""
160
logger.debug(u"Stopping client %s", self.name)
162
if self.stop_initiator_tag:
163
gobject.source_remove(self.stop_initiator_tag)
164
self.stop_initiator_tag = None
165
if self.checker_initiator_tag:
424
166
gobject.source_remove(self.checker_initiator_tag)
425
167
self.checker_initiator_tag = None
426
168
self.stop_checker()
427
if self.disable_hook:
428
self.disable_hook(self)
430
171
# Do not run this again if called by a gobject.timeout_add
433
173
def __del__(self):
434
self.disable_hook = None
437
def checker_callback(self, pid, condition, command):
174
# Some code duplication here and in stop()
175
if hasattr(self, "stop_initiator_tag") \
176
and self.stop_initiator_tag:
177
gobject.source_remove(self.stop_initiator_tag)
178
self.stop_initiator_tag = None
179
if hasattr(self, "checker_initiator_tag") \
180
and self.checker_initiator_tag:
181
gobject.source_remove(self.checker_initiator_tag)
182
self.checker_initiator_tag = None
184
def checker_callback(self, pid, condition):
438
185
"""The checker has completed, so take appropriate actions."""
439
self.checker_callback_tag = None
441
if os.WIFEXITED(condition):
442
exitstatus = os.WEXITSTATUS(condition)
444
logger.info("Checker for %(name)s succeeded",
448
logger.info("Checker for %(name)s failed",
451
logger.warning("Checker for %(name)s crashed?",
186
now = datetime.datetime.now()
187
if os.WIFEXITED(condition) \
188
and (os.WEXITSTATUS(condition) == 0):
189
logger.debug(u"Checker for %(name)s succeeded",
192
gobject.source_remove(self.stop_initiator_tag)
193
self.stop_initiator_tag = gobject.timeout_add\
194
(self._timeout_milliseconds,
196
if not os.WIFEXITED(condition):
197
logger.warning(u"Checker for %(name)s crashed?",
454
def checked_ok(self, timeout=None):
455
"""Bump up the timeout for this client.
457
This should only be called when the client has been seen,
461
timeout = self.timeout
462
self.last_checked_ok = datetime.datetime.utcnow()
463
gobject.source_remove(self.disable_initiator_tag)
464
self.expires = datetime.datetime.utcnow() + timeout
465
self.disable_initiator_tag = (gobject.timeout_add
466
(self._timedelta_to_milliseconds(timeout),
469
def need_approval(self):
470
self.last_approval_request = datetime.datetime.utcnow()
200
logger.debug(u"Checker for %(name)s failed",
203
self.checker_callback_tag = None
472
204
def start_checker(self):
473
205
"""Start a new checker subprocess if one is not running.
475
206
If a checker already exists, leave it running and do
477
# The reason for not killing a running checker is that if we
478
# did that, then if a checker (for some reason) started
479
# running slowly and taking more than 'interval' time, the
480
# client would inevitably timeout, since no checker would get
481
# a chance to run to completion. If we instead leave running
482
# checkers alone, the checker would have to take more time
483
# than 'timeout' for the client to be disabled, which is as it
486
# If a checker exists, make sure it is not a zombie
488
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
489
except (AttributeError, OSError) as error:
490
if (isinstance(error, OSError)
491
and error.errno != errno.ECHILD):
495
logger.warning("Checker was a zombie")
496
gobject.source_remove(self.checker_callback_tag)
497
self.checker_callback(pid, status,
498
self.current_checker_command)
499
# Start a new checker if needed
500
208
if self.checker is None:
209
logger.debug(u"Starting checker for %s",
502
# In case checker_command has exactly one % operator
503
command = self.checker_command % self.host
212
command = self.check_command % self.fqdn
504
213
except TypeError:
505
# Escape attributes for the shell
506
escaped_attrs = dict(
508
re.escape(unicode(str(getattr(self, attr, "")),
512
self.runtime_expansions)
214
escaped_attrs = dict((key, re.escape(str(val)))
216
vars(self).iteritems())
515
command = self.checker_command % escaped_attrs
516
except TypeError as error:
517
logger.error('Could not format string "%s":'
518
' %s', self.checker_command, error)
218
command = self.check_command % escaped_attrs
219
except TypeError, error:
220
logger.critical(u'Could not format string "%s": %s',
221
self.check_command, error)
519
222
return True # Try again later
520
self.current_checker_command = command
522
logger.info("Starting checker %r for %s",
524
# We don't need to redirect stdout and stderr, since
525
# in normal mode, that is already done by daemon(),
526
# and in debug mode we don't want to. (Stdin is
527
# always replaced by /dev/null.)
528
self.checker = subprocess.Popen(command,
531
self.checker_callback_tag = (gobject.child_watch_add
533
self.checker_callback,
535
# The checker may have completed before the gobject
536
# watch was added. Check for this.
537
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
539
gobject.source_remove(self.checker_callback_tag)
540
self.checker_callback(pid, status, command)
541
except OSError as error:
542
logger.error("Failed to start subprocess: %s",
224
self.checker = subprocess.\
226
stdout=subprocess.PIPE,
227
close_fds=True, shell=True,
229
self.checker_callback_tag = gobject.\
230
child_watch_add(self.checker.pid,
233
except subprocess.OSError, error:
234
logger.error(u"Failed to start subprocess: %s",
544
236
# Re-run this periodically if run by gobject.timeout_add
547
238
def stop_checker(self):
548
239
"""Force the checker process, if any, to stop."""
549
if self.checker_callback_tag:
550
gobject.source_remove(self.checker_callback_tag)
551
self.checker_callback_tag = None
552
if getattr(self, "checker", None) is None:
240
if not hasattr(self, "checker") or self.checker is None:
554
logger.debug("Stopping checker for %(name)s", vars(self))
556
os.kill(self.checker.pid, signal.SIGTERM)
558
#if self.checker.poll() is None:
559
# os.kill(self.checker.pid, signal.SIGKILL)
560
except OSError as error:
561
if error.errno != errno.ESRCH: # No such process
565
def dbus_service_property(dbus_interface, signature="v",
566
access="readwrite", byte_arrays=False):
567
"""Decorators for marking methods of a DBusObjectWithProperties to
568
become properties on the D-Bus.
570
The decorated method will be called with no arguments by "Get"
571
and with one argument by "Set".
573
The parameters, where they are supported, are the same as
574
dbus.service.method, except there is only "signature", since the
575
type from Get() and the type sent to Set() is the same.
577
# Encoding deeply encoded byte arrays is not supported yet by the
578
# "Set" method, so we fail early here:
579
if byte_arrays and signature != "ay":
580
raise ValueError("Byte arrays not supported for non-'ay'"
581
" signature %r" % signature)
583
func._dbus_is_property = True
584
func._dbus_interface = dbus_interface
585
func._dbus_signature = signature
586
func._dbus_access = access
587
func._dbus_name = func.__name__
588
if func._dbus_name.endswith("_dbus_property"):
589
func._dbus_name = func._dbus_name[:-14]
590
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
595
class DBusPropertyException(dbus.exceptions.DBusException):
596
"""A base class for D-Bus property-related exceptions
598
def __unicode__(self):
599
return unicode(str(self))
602
class DBusPropertyAccessException(DBusPropertyException):
603
"""A property's access permissions disallows an operation.
608
class DBusPropertyNotFound(DBusPropertyException):
609
"""An attempt was made to access a non-existing property.
614
class DBusObjectWithProperties(dbus.service.Object):
615
"""A D-Bus object with properties.
617
Classes inheriting from this can use the dbus_service_property
618
decorator to expose methods as D-Bus properties. It exposes the
619
standard Get(), Set(), and GetAll() methods on the D-Bus.
623
def _is_dbus_property(obj):
624
return getattr(obj, "_dbus_is_property", False)
626
def _get_all_dbus_properties(self):
627
"""Returns a generator of (name, attribute) pairs
629
return ((prop._dbus_name, prop)
631
inspect.getmembers(self, self._is_dbus_property))
633
def _get_dbus_property(self, interface_name, property_name):
634
"""Returns a bound method if one exists which is a D-Bus
635
property with the specified name and interface.
637
for name in (property_name,
638
property_name + "_dbus_property"):
639
prop = getattr(self, name, None)
641
or not self._is_dbus_property(prop)
642
or prop._dbus_name != property_name
643
or (interface_name and prop._dbus_interface
644
and interface_name != prop._dbus_interface)):
648
raise DBusPropertyNotFound(self.dbus_object_path + ":"
649
+ interface_name + "."
652
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
654
def Get(self, interface_name, property_name):
655
"""Standard D-Bus property Get() method, see D-Bus standard.
657
prop = self._get_dbus_property(interface_name, property_name)
658
if prop._dbus_access == "write":
659
raise DBusPropertyAccessException(property_name)
661
if not hasattr(value, "variant_level"):
663
return type(value)(value, variant_level=value.variant_level+1)
665
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
666
def Set(self, interface_name, property_name, value):
667
"""Standard D-Bus property Set() method, see D-Bus standard.
669
prop = self._get_dbus_property(interface_name, property_name)
670
if prop._dbus_access == "read":
671
raise DBusPropertyAccessException(property_name)
672
if prop._dbus_get_args_options["byte_arrays"]:
673
# The byte_arrays option is not supported yet on
674
# signatures other than "ay".
675
if prop._dbus_signature != "ay":
677
value = dbus.ByteArray(''.join(unichr(byte)
681
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
682
out_signature="a{sv}")
683
def GetAll(self, interface_name):
684
"""Standard D-Bus property GetAll() method, see D-Bus
687
Note: Will not include properties with access="write".
690
for name, prop in self._get_all_dbus_properties():
692
and interface_name != prop._dbus_interface):
693
# Interface non-empty but did not match
695
# Ignore write-only properties
696
if prop._dbus_access == "write":
699
if not hasattr(value, "variant_level"):
702
all[name] = type(value)(value, variant_level=
703
value.variant_level+1)
704
return dbus.Dictionary(all, signature="sv")
706
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
708
path_keyword='object_path',
709
connection_keyword='connection')
710
def Introspect(self, object_path, connection):
711
"""Standard D-Bus method, overloaded to insert property tags.
713
xmlstring = dbus.service.Object.Introspect(self, object_path,
716
document = xml.dom.minidom.parseString(xmlstring)
717
def make_tag(document, name, prop):
718
e = document.createElement("property")
719
e.setAttribute("name", name)
720
e.setAttribute("type", prop._dbus_signature)
721
e.setAttribute("access", prop._dbus_access)
723
for if_tag in document.getElementsByTagName("interface"):
724
for tag in (make_tag(document, name, prop)
726
in self._get_all_dbus_properties()
727
if prop._dbus_interface
728
== if_tag.getAttribute("name")):
729
if_tag.appendChild(tag)
730
# Add the names to the return values for the
731
# "org.freedesktop.DBus.Properties" methods
732
if (if_tag.getAttribute("name")
733
== "org.freedesktop.DBus.Properties"):
734
for cn in if_tag.getElementsByTagName("method"):
735
if cn.getAttribute("name") == "Get":
736
for arg in cn.getElementsByTagName("arg"):
737
if (arg.getAttribute("direction")
739
arg.setAttribute("name", "value")
740
elif cn.getAttribute("name") == "GetAll":
741
for arg in cn.getElementsByTagName("arg"):
742
if (arg.getAttribute("direction")
744
arg.setAttribute("name", "props")
745
xmlstring = document.toxml("utf-8")
747
except (AttributeError, xml.dom.DOMException,
748
xml.parsers.expat.ExpatError) as error:
749
logger.error("Failed to override Introspection method",
754
class ClientDBus(Client, DBusObjectWithProperties):
755
"""A Client class using D-Bus
758
dbus_object_path: dbus.ObjectPath
759
bus: dbus.SystemBus()
762
runtime_expansions = (Client.runtime_expansions
763
+ ("dbus_object_path",))
765
# dbus.service.Object doesn't use super(), so we can't either.
767
def __init__(self, bus = None, *args, **kwargs):
768
self._approvals_pending = 0
770
Client.__init__(self, *args, **kwargs)
771
# Only now, when this client is initialized, can it show up on
773
client_object_name = unicode(self.name).translate(
776
self.dbus_object_path = (dbus.ObjectPath
777
("/clients/" + client_object_name))
778
DBusObjectWithProperties.__init__(self, self.bus,
779
self.dbus_object_path)
780
def _set_expires(self, value):
781
old_value = getattr(self, "_expires", None)
782
self._expires = value
783
if hasattr(self, "dbus_object_path") and old_value != value:
784
dbus_time = (self._datetime_to_dbus(self._expires,
786
self.PropertyChanged(dbus.String("Expires"),
788
expires = property(lambda self: self._expires, _set_expires)
791
def _get_approvals_pending(self):
792
return self._approvals_pending
793
def _set_approvals_pending(self, value):
794
old_value = self._approvals_pending
795
self._approvals_pending = value
797
if (hasattr(self, "dbus_object_path")
798
and bval is not bool(old_value)):
799
dbus_bool = dbus.Boolean(bval, variant_level=1)
800
self.PropertyChanged(dbus.String("ApprovalPending"),
803
approvals_pending = property(_get_approvals_pending,
804
_set_approvals_pending)
805
del _get_approvals_pending, _set_approvals_pending
808
def _datetime_to_dbus(dt, variant_level=0):
809
"""Convert a UTC datetime.datetime() to a D-Bus type."""
811
return dbus.String("", variant_level = variant_level)
812
return dbus.String(dt.isoformat(),
813
variant_level=variant_level)
816
oldstate = getattr(self, "enabled", False)
817
r = Client.enable(self)
818
if oldstate != self.enabled:
820
self.PropertyChanged(dbus.String("Enabled"),
821
dbus.Boolean(True, variant_level=1))
822
self.PropertyChanged(
823
dbus.String("LastEnabled"),
824
self._datetime_to_dbus(self.last_enabled,
828
def disable(self, quiet = False):
829
oldstate = getattr(self, "enabled", False)
830
r = Client.disable(self, quiet=quiet)
831
if not quiet and oldstate != self.enabled:
833
self.PropertyChanged(dbus.String("Enabled"),
834
dbus.Boolean(False, variant_level=1))
837
def __del__(self, *args, **kwargs):
839
self.remove_from_connection()
842
if hasattr(DBusObjectWithProperties, "__del__"):
843
DBusObjectWithProperties.__del__(self, *args, **kwargs)
844
Client.__del__(self, *args, **kwargs)
846
def checker_callback(self, pid, condition, command,
242
gobject.source_remove(self.checker_callback_tag)
848
243
self.checker_callback_tag = None
244
os.kill(self.checker.pid, signal.SIGTERM)
245
if self.checker.poll() is None:
246
os.kill(self.checker.pid, signal.SIGKILL)
849
247
self.checker = None
851
self.PropertyChanged(dbus.String("CheckerRunning"),
852
dbus.Boolean(False, variant_level=1))
853
if os.WIFEXITED(condition):
854
exitstatus = os.WEXITSTATUS(condition)
856
self.CheckerCompleted(dbus.Int16(exitstatus),
857
dbus.Int64(condition),
858
dbus.String(command))
861
self.CheckerCompleted(dbus.Int16(-1),
862
dbus.Int64(condition),
863
dbus.String(command))
865
return Client.checker_callback(self, pid, condition, command,
868
def checked_ok(self, *args, **kwargs):
869
Client.checked_ok(self, *args, **kwargs)
871
self.PropertyChanged(
872
dbus.String("LastCheckedOK"),
873
(self._datetime_to_dbus(self.last_checked_ok,
876
def need_approval(self, *args, **kwargs):
877
r = Client.need_approval(self, *args, **kwargs)
879
self.PropertyChanged(
880
dbus.String("LastApprovalRequest"),
881
(self._datetime_to_dbus(self.last_approval_request,
885
def start_checker(self, *args, **kwargs):
886
old_checker = self.checker
887
if self.checker is not None:
888
old_checker_pid = self.checker.pid
890
old_checker_pid = None
891
r = Client.start_checker(self, *args, **kwargs)
892
# Only if new checker process was started
893
if (self.checker is not None
894
and old_checker_pid != self.checker.pid):
896
self.CheckerStarted(self.current_checker_command)
897
self.PropertyChanged(
898
dbus.String("CheckerRunning"),
899
dbus.Boolean(True, variant_level=1))
902
def stop_checker(self, *args, **kwargs):
903
old_checker = getattr(self, "checker", None)
904
r = Client.stop_checker(self, *args, **kwargs)
905
if (old_checker is not None
906
and getattr(self, "checker", None) is None):
907
self.PropertyChanged(dbus.String("CheckerRunning"),
908
dbus.Boolean(False, variant_level=1))
911
def _reset_approved(self):
912
self._approved = None
915
def approve(self, value=True):
916
self.send_changedstate()
917
self._approved = value
918
gobject.timeout_add(self._timedelta_to_milliseconds
919
(self.approval_duration),
920
self._reset_approved)
923
## D-Bus methods, signals & properties
924
_interface = "se.bsnet.fukt.Mandos.Client"
928
# CheckerCompleted - signal
929
@dbus.service.signal(_interface, signature="nxs")
930
def CheckerCompleted(self, exitcode, waitstatus, command):
934
# CheckerStarted - signal
935
@dbus.service.signal(_interface, signature="s")
936
def CheckerStarted(self, command):
940
# PropertyChanged - signal
941
@dbus.service.signal(_interface, signature="sv")
942
def PropertyChanged(self, property, value):
947
@dbus.service.signal(_interface)
950
Is sent after a successful transfer of secret from the Mandos
951
server to mandos-client
956
@dbus.service.signal(_interface, signature="s")
957
def Rejected(self, reason):
961
# NeedApproval - signal
962
@dbus.service.signal(_interface, signature="tb")
963
def NeedApproval(self, timeout, default):
965
return self.need_approval()
970
@dbus.service.method(_interface, in_signature="b")
971
def Approve(self, value):
975
@dbus.service.method(_interface)
980
@dbus.service.method(_interface)
985
# StartChecker - method
986
@dbus.service.method(_interface)
987
def StartChecker(self):
992
@dbus.service.method(_interface)
997
# StopChecker - method
998
@dbus.service.method(_interface)
999
def StopChecker(self):
1004
# ApprovalPending - property
1005
@dbus_service_property(_interface, signature="b", access="read")
1006
def ApprovalPending_dbus_property(self):
1007
return dbus.Boolean(bool(self.approvals_pending))
1009
# ApprovedByDefault - property
1010
@dbus_service_property(_interface, signature="b",
1012
def ApprovedByDefault_dbus_property(self, value=None):
1013
if value is None: # get
1014
return dbus.Boolean(self.approved_by_default)
1015
old_value = self.approved_by_default
1016
self.approved_by_default = bool(value)
1018
if old_value != self.approved_by_default:
1019
self.PropertyChanged(dbus.String("ApprovedByDefault"),
1020
dbus.Boolean(value, variant_level=1))
1022
# ApprovalDelay - property
1023
@dbus_service_property(_interface, signature="t",
1025
def ApprovalDelay_dbus_property(self, value=None):
1026
if value is None: # get
1027
return dbus.UInt64(self.approval_delay_milliseconds())
1028
old_value = self.approval_delay
1029
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1031
if old_value != self.approval_delay:
1032
self.PropertyChanged(dbus.String("ApprovalDelay"),
1033
dbus.UInt64(value, variant_level=1))
1035
# ApprovalDuration - property
1036
@dbus_service_property(_interface, signature="t",
1038
def ApprovalDuration_dbus_property(self, value=None):
1039
if value is None: # get
1040
return dbus.UInt64(self._timedelta_to_milliseconds(
1041
self.approval_duration))
1042
old_value = self.approval_duration
1043
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1045
if old_value != self.approval_duration:
1046
self.PropertyChanged(dbus.String("ApprovalDuration"),
1047
dbus.UInt64(value, variant_level=1))
1050
@dbus_service_property(_interface, signature="s", access="read")
1051
def Name_dbus_property(self):
1052
return dbus.String(self.name)
1054
# Fingerprint - property
1055
@dbus_service_property(_interface, signature="s", access="read")
1056
def Fingerprint_dbus_property(self):
1057
return dbus.String(self.fingerprint)
1060
@dbus_service_property(_interface, signature="s",
1062
def Host_dbus_property(self, value=None):
1063
if value is None: # get
1064
return dbus.String(self.host)
1065
old_value = self.host
1068
if old_value != self.host:
1069
self.PropertyChanged(dbus.String("Host"),
1070
dbus.String(value, variant_level=1))
1072
# Created - property
1073
@dbus_service_property(_interface, signature="s", access="read")
1074
def Created_dbus_property(self):
1075
return dbus.String(self._datetime_to_dbus(self.created))
1077
# LastEnabled - property
1078
@dbus_service_property(_interface, signature="s", access="read")
1079
def LastEnabled_dbus_property(self):
1080
return self._datetime_to_dbus(self.last_enabled)
1082
# Enabled - property
1083
@dbus_service_property(_interface, signature="b",
1085
def Enabled_dbus_property(self, value=None):
1086
if value is None: # get
1087
return dbus.Boolean(self.enabled)
1093
# LastCheckedOK - property
1094
@dbus_service_property(_interface, signature="s",
1096
def LastCheckedOK_dbus_property(self, value=None):
1097
if value is not None:
1100
return self._datetime_to_dbus(self.last_checked_ok)
1102
# Expires - property
1103
@dbus_service_property(_interface, signature="s", access="read")
1104
def Expires_dbus_property(self):
1105
return self._datetime_to_dbus(self.expires)
1107
# LastApprovalRequest - property
1108
@dbus_service_property(_interface, signature="s", access="read")
1109
def LastApprovalRequest_dbus_property(self):
1110
return self._datetime_to_dbus(self.last_approval_request)
1112
# Timeout - property
1113
@dbus_service_property(_interface, signature="t",
1115
def Timeout_dbus_property(self, value=None):
1116
if value is None: # get
1117
return dbus.UInt64(self.timeout_milliseconds())
1118
old_value = self.timeout
1119
self.timeout = datetime.timedelta(0, 0, 0, value)
1121
if old_value != self.timeout:
1122
self.PropertyChanged(dbus.String("Timeout"),
1123
dbus.UInt64(value, variant_level=1))
1124
if getattr(self, "disable_initiator_tag", None) is None:
1126
# Reschedule timeout
1127
gobject.source_remove(self.disable_initiator_tag)
1128
self.disable_initiator_tag = None
1130
time_to_die = (self.
1131
_timedelta_to_milliseconds((self
1136
if time_to_die <= 0:
1137
# The timeout has passed
1140
self.expires = (datetime.datetime.utcnow()
1141
+ datetime.timedelta(milliseconds = time_to_die))
1142
self.disable_initiator_tag = (gobject.timeout_add
1143
(time_to_die, self.disable))
1145
# ExtendedTimeout - property
1146
@dbus_service_property(_interface, signature="t",
1148
def ExtendedTimeout_dbus_property(self, value=None):
1149
if value is None: # get
1150
return dbus.UInt64(self.extended_timeout_milliseconds())
1151
old_value = self.extended_timeout
1152
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1154
if old_value != self.extended_timeout:
1155
self.PropertyChanged(dbus.String("ExtendedTimeout"),
1156
dbus.UInt64(value, variant_level=1))
1158
# Interval - property
1159
@dbus_service_property(_interface, signature="t",
1161
def Interval_dbus_property(self, value=None):
1162
if value is None: # get
1163
return dbus.UInt64(self.interval_milliseconds())
1164
old_value = self.interval
1165
self.interval = datetime.timedelta(0, 0, 0, value)
1167
if old_value != self.interval:
1168
self.PropertyChanged(dbus.String("Interval"),
1169
dbus.UInt64(value, variant_level=1))
1170
if getattr(self, "checker_initiator_tag", None) is None:
1172
# Reschedule checker run
1173
gobject.source_remove(self.checker_initiator_tag)
1174
self.checker_initiator_tag = (gobject.timeout_add
1175
(value, self.start_checker))
1176
self.start_checker() # Start one now, too
1178
# Checker - property
1179
@dbus_service_property(_interface, signature="s",
1181
def Checker_dbus_property(self, value=None):
1182
if value is None: # get
1183
return dbus.String(self.checker_command)
1184
old_value = self.checker_command
1185
self.checker_command = value
1187
if old_value != self.checker_command:
1188
self.PropertyChanged(dbus.String("Checker"),
1189
dbus.String(self.checker_command,
1192
# CheckerRunning - property
1193
@dbus_service_property(_interface, signature="b",
1195
def CheckerRunning_dbus_property(self, value=None):
1196
if value is None: # get
1197
return dbus.Boolean(self.checker is not None)
1199
self.start_checker()
1203
# ObjectPath - property
1204
@dbus_service_property(_interface, signature="o", access="read")
1205
def ObjectPath_dbus_property(self):
1206
return self.dbus_object_path # is already a dbus.ObjectPath
1209
@dbus_service_property(_interface, signature="ay",
1210
access="write", byte_arrays=True)
1211
def Secret_dbus_property(self, value):
1212
self.secret = str(value)
1217
class ProxyClient(object):
1218
def __init__(self, child_pipe, fpr, address):
1219
self._pipe = child_pipe
1220
self._pipe.send(('init', fpr, address))
1221
if not self._pipe.recv():
1224
def __getattribute__(self, name):
1225
if(name == '_pipe'):
1226
return super(ProxyClient, self).__getattribute__(name)
1227
self._pipe.send(('getattr', name))
1228
data = self._pipe.recv()
1229
if data[0] == 'data':
1231
if data[0] == 'function':
1232
def func(*args, **kwargs):
1233
self._pipe.send(('funcall', name, args, kwargs))
1234
return self._pipe.recv()[1]
1237
def __setattr__(self, name, value):
1238
if(name == '_pipe'):
1239
return super(ProxyClient, self).__setattr__(name, value)
1240
self._pipe.send(('setattr', name, value))
1243
class ClientHandler(socketserver.BaseRequestHandler, object):
1244
"""A class to handle client connections.
1246
Instantiated once for each connection to handle it.
248
def still_valid(self, now=None):
249
"""Has the timeout not yet passed for this client?"""
251
now = datetime.datetime.now()
252
if self.last_seen is None:
253
return now < (self.created + self.timeout)
255
return now < (self.last_seen + self.timeout)
258
def peer_certificate(session):
259
# If not an OpenPGP certificate...
260
if gnutls.library.functions.gnutls_certificate_type_get\
261
(session._c_object) \
262
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
263
# ...do the normal thing
264
return session.peer_certificate
265
list_size = ctypes.c_uint()
266
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
267
(session._c_object, ctypes.byref(list_size))
268
if list_size.value == 0:
271
return ctypes.string_at(cert.data, cert.size)
274
def fingerprint(openpgp):
275
# New empty GnuTLS certificate
276
crt = gnutls.library.types.gnutls_openpgp_crt_t()
277
gnutls.library.functions.gnutls_openpgp_crt_init\
279
# New GnuTLS "datum" with the OpenPGP public key
280
datum = gnutls.library.types.gnutls_datum_t\
281
(ctypes.cast(ctypes.c_char_p(openpgp),
282
ctypes.POINTER(ctypes.c_ubyte)),
283
ctypes.c_uint(len(openpgp)))
284
# Import the OpenPGP public key into the certificate
285
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
288
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
289
# New buffer for the fingerprint
290
buffer = ctypes.create_string_buffer(20)
291
buffer_length = ctypes.c_size_t()
292
# Get the fingerprint from the certificate into the buffer
293
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
294
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
295
# Deinit the certificate
296
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
297
# Convert the buffer to a Python bytestring
298
fpr = ctypes.string_at(buffer, buffer_length.value)
299
# Convert the bytestring to hexadecimal notation
300
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
304
class tcp_handler(SocketServer.BaseRequestHandler, object):
305
"""A TCP request handler class.
306
Instantiated by IPv6_TCPServer for each request to handle it.
1247
307
Note: This will run in its own forked process."""
1249
309
def handle(self):
1250
with contextlib.closing(self.server.child_pipe) as child_pipe:
1251
logger.info("TCP connection from: %s",
1252
unicode(self.client_address))
1253
logger.debug("Pipe FD: %d",
1254
self.server.child_pipe.fileno())
1256
session = (gnutls.connection
1257
.ClientSession(self.request,
1259
.X509Credentials()))
1261
# Note: gnutls.connection.X509Credentials is really a
1262
# generic GnuTLS certificate credentials object so long as
1263
# no X.509 keys are added to it. Therefore, we can use it
1264
# here despite using OpenPGP certificates.
1266
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1267
# "+AES-256-CBC", "+SHA1",
1268
# "+COMP-NULL", "+CTYPE-OPENPGP",
1270
# Use a fallback default, since this MUST be set.
1271
priority = self.server.gnutls_priority
1272
if priority is None:
1274
(gnutls.library.functions
1275
.gnutls_priority_set_direct(session._c_object,
1278
# Start communication using the Mandos protocol
1279
# Get protocol number
1280
line = self.request.makefile().readline()
1281
logger.debug("Protocol version: %r", line)
1283
if int(line.strip().split()[0]) > 1:
1285
except (ValueError, IndexError, RuntimeError) as error:
1286
logger.error("Unknown protocol version: %s", error)
1289
# Start GnuTLS connection
1292
except gnutls.errors.GNUTLSError as error:
1293
logger.warning("Handshake failed: %s", error)
1294
# Do not run session.bye() here: the session is not
1295
# established. Just abandon the request.
1297
logger.debug("Handshake succeeded")
1299
approval_required = False
1302
fpr = self.fingerprint(self.peer_certificate
1305
gnutls.errors.GNUTLSError) as error:
1306
logger.warning("Bad certificate: %s", error)
1308
logger.debug("Fingerprint: %s", fpr)
1311
client = ProxyClient(child_pipe, fpr,
1312
self.client_address)
1316
if client.approval_delay:
1317
delay = client.approval_delay
1318
client.approvals_pending += 1
1319
approval_required = True
1322
if not client.enabled:
1323
logger.info("Client %s is disabled",
1325
if self.server.use_dbus:
1327
client.Rejected("Disabled")
1330
if client._approved or not client.approval_delay:
1331
#We are approved or approval is disabled
1333
elif client._approved is None:
1334
logger.info("Client %s needs approval",
1336
if self.server.use_dbus:
1338
client.NeedApproval(
1339
client.approval_delay_milliseconds(),
1340
client.approved_by_default)
1342
logger.warning("Client %s was not approved",
1344
if self.server.use_dbus:
1346
client.Rejected("Denied")
1349
#wait until timeout or approved
1350
#x = float(client._timedelta_to_milliseconds(delay))
1351
time = datetime.datetime.now()
1352
client.changedstate.acquire()
1353
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1354
client.changedstate.release()
1355
time2 = datetime.datetime.now()
1356
if (time2 - time) >= delay:
1357
if not client.approved_by_default:
1358
logger.warning("Client %s timed out while"
1359
" waiting for approval",
1361
if self.server.use_dbus:
1363
client.Rejected("Approval timed out")
1368
delay -= time2 - time
1371
while sent_size < len(client.secret):
1373
sent = session.send(client.secret[sent_size:])
1374
except gnutls.errors.GNUTLSError as error:
1375
logger.warning("gnutls send failed")
1377
logger.debug("Sent: %d, remaining: %d",
1378
sent, len(client.secret)
1379
- (sent_size + sent))
1382
logger.info("Sending secret to %s", client.name)
1383
# bump the timeout as if seen
1384
client.checked_ok(client.extended_timeout)
1385
if self.server.use_dbus:
1390
if approval_required:
1391
client.approvals_pending -= 1
1394
except gnutls.errors.GNUTLSError as error:
1395
logger.warning("GnuTLS bye failed")
1398
def peer_certificate(session):
1399
"Return the peer's OpenPGP certificate as a bytestring"
1400
# If not an OpenPGP certificate...
1401
if (gnutls.library.functions
1402
.gnutls_certificate_type_get(session._c_object)
1403
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1404
# ...do the normal thing
1405
return session.peer_certificate
1406
list_size = ctypes.c_uint(1)
1407
cert_list = (gnutls.library.functions
1408
.gnutls_certificate_get_peers
1409
(session._c_object, ctypes.byref(list_size)))
1410
if not bool(cert_list) and list_size.value != 0:
1411
raise gnutls.errors.GNUTLSError("error getting peer"
1413
if list_size.value == 0:
1416
return ctypes.string_at(cert.data, cert.size)
1419
def fingerprint(openpgp):
1420
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1421
# New GnuTLS "datum" with the OpenPGP public key
1422
datum = (gnutls.library.types
1423
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1426
ctypes.c_uint(len(openpgp))))
1427
# New empty GnuTLS certificate
1428
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1429
(gnutls.library.functions
1430
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1431
# Import the OpenPGP public key into the certificate
1432
(gnutls.library.functions
1433
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1434
gnutls.library.constants
1435
.GNUTLS_OPENPGP_FMT_RAW))
1436
# Verify the self signature in the key
1437
crtverify = ctypes.c_uint()
1438
(gnutls.library.functions
1439
.gnutls_openpgp_crt_verify_self(crt, 0,
1440
ctypes.byref(crtverify)))
1441
if crtverify.value != 0:
1442
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1443
raise (gnutls.errors.CertificateSecurityError
1445
# New buffer for the fingerprint
1446
buf = ctypes.create_string_buffer(20)
1447
buf_len = ctypes.c_size_t()
1448
# Get the fingerprint from the certificate into the buffer
1449
(gnutls.library.functions
1450
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1451
ctypes.byref(buf_len)))
1452
# Deinit the certificate
1453
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1454
# Convert the buffer to a Python bytestring
1455
fpr = ctypes.string_at(buf, buf_len.value)
1456
# Convert the bytestring to hexadecimal notation
1457
hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1461
class MultiprocessingMixIn(object):
1462
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1463
def sub_process_main(self, request, address):
1465
self.finish_request(request, address)
1467
self.handle_error(request, address)
1468
self.close_request(request)
1470
def process_request(self, request, address):
1471
"""Start a new process to process the request."""
1472
multiprocessing.Process(target = self.sub_process_main,
1473
args = (request, address)).start()
1475
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1476
""" adds a pipe to the MixIn """
1477
def process_request(self, request, client_address):
1478
"""Overrides and wraps the original process_request().
1480
This function creates a new pipe in self.pipe
1482
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1484
super(MultiprocessingMixInWithPipe,
1485
self).process_request(request, client_address)
1486
self.child_pipe.close()
1487
self.add_pipe(parent_pipe)
1489
def add_pipe(self, parent_pipe):
1490
"""Dummy function; override as necessary"""
1491
raise NotImplementedError
1493
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1494
socketserver.TCPServer, object):
1495
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
310
logger.debug(u"TCP connection from: %s",
311
unicode(self.client_address))
312
session = gnutls.connection.ClientSession(self.request,
316
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
317
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
319
priority = "SECURE256"
321
gnutls.library.functions.gnutls_priority_set_direct\
322
(session._c_object, priority, None);
326
except gnutls.errors.GNUTLSError, error:
327
logger.debug(u"Handshake failed: %s", error)
328
# Do not run session.bye() here: the session is not
329
# established. Just abandon the request.
332
fpr = fingerprint(peer_certificate(session))
333
except (TypeError, gnutls.errors.GNUTLSError), error:
334
logger.debug(u"Bad certificate: %s", error)
337
logger.debug(u"Fingerprint: %s", fpr)
340
if c.fingerprint == fpr:
343
# Have to check if client.still_valid(), since it is possible
344
# that the client timed out while establishing the GnuTLS
346
if (not client) or (not client.still_valid()):
348
logger.debug(u"Client %(name)s is invalid",
351
logger.debug(u"Client not found for fingerprint: %s",
356
while sent_size < len(client.secret):
357
sent = session.send(client.secret[sent_size:])
358
logger.debug(u"Sent: %d, remaining: %d",
359
sent, len(client.secret)
360
- (sent_size + sent))
365
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
366
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1498
enabled: Boolean; whether this server is activated yet
1499
interface: None or a network interface name (string)
1500
use_ipv6: Boolean; to use IPv6 or not
368
options: Command line options
369
clients: Set() of Client objects
1502
def __init__(self, server_address, RequestHandlerClass,
1503
interface=None, use_ipv6=True):
1504
self.interface = interface
1506
self.address_family = socket.AF_INET6
1507
socketserver.TCPServer.__init__(self, server_address,
1508
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)
1509
380
def server_bind(self):
1510
381
"""This overrides the normal server_bind() function
1511
382
to bind to an interface if one was specified, and also NOT to
1512
383
bind to an address or port if they were not specified."""
1513
if self.interface is not None:
1514
if SO_BINDTODEVICE is None:
1515
logger.error("SO_BINDTODEVICE does not exist;"
1516
" cannot bind to interface %s",
1520
self.socket.setsockopt(socket.SOL_SOCKET,
1524
except socket.error as error:
1525
if error[0] == errno.EPERM:
1526
logger.error("No permission to"
1527
" bind to interface %s",
1529
elif error[0] == errno.ENOPROTOOPT:
1530
logger.error("SO_BINDTODEVICE not available;"
1531
" cannot bind to interface %s",
384
if self.options.interface:
385
if not hasattr(socket, "SO_BINDTODEVICE"):
386
# From /usr/include/asm-i486/socket.h
387
socket.SO_BINDTODEVICE = 25
389
self.socket.setsockopt(socket.SOL_SOCKET,
390
socket.SO_BINDTODEVICE,
391
self.options.interface)
392
except socket.error, error:
393
if error[0] == errno.EPERM:
394
logger.warning(u"No permission to"
395
u" bind to interface %s",
396
self.options.interface)
1535
399
# Only bind(2) the socket if we really need to.
1536
400
if self.server_address[0] or self.server_address[1]:
1537
401
if not self.server_address[0]:
1538
if self.address_family == socket.AF_INET6:
1539
any_address = "::" # in6addr_any
1541
any_address = socket.INADDR_ANY
1542
self.server_address = (any_address,
403
self.server_address = (in6addr_any,
1543
404
self.server_address[1])
1544
elif not self.server_address[1]:
405
elif self.server_address[1] is None:
1545
406
self.server_address = (self.server_address[0],
1547
# if self.interface:
1548
# self.server_address = (self.server_address[0],
1553
return socketserver.TCPServer.server_bind(self)
1556
class MandosServer(IPv6_TCPServer):
1560
clients: set of Client objects
1561
gnutls_priority GnuTLS priority string
1562
use_dbus: Boolean; to emit D-Bus signals or not
1564
Assumes a gobject.MainLoop event loop.
1566
def __init__(self, server_address, RequestHandlerClass,
1567
interface=None, use_ipv6=True, clients=None,
1568
gnutls_priority=None, use_dbus=True):
1569
self.enabled = False
1570
self.clients = clients
1571
if self.clients is None:
1572
self.clients = set()
1573
self.use_dbus = use_dbus
1574
self.gnutls_priority = gnutls_priority
1575
IPv6_TCPServer.__init__(self, server_address,
1576
RequestHandlerClass,
1577
interface = interface,
1578
use_ipv6 = use_ipv6)
1579
def server_activate(self):
1581
return socketserver.TCPServer.server_activate(self)
1584
def add_pipe(self, parent_pipe):
1585
# Call "handle_ipc" for both data and EOF events
1586
gobject.io_add_watch(parent_pipe.fileno(),
1587
gobject.IO_IN | gobject.IO_HUP,
1588
functools.partial(self.handle_ipc,
1589
parent_pipe = parent_pipe))
1591
def handle_ipc(self, source, condition, parent_pipe=None,
1592
client_object=None):
1594
gobject.IO_IN: "IN", # There is data to read.
1595
gobject.IO_OUT: "OUT", # Data can be written (without
1597
gobject.IO_PRI: "PRI", # There is urgent data to read.
1598
gobject.IO_ERR: "ERR", # Error condition.
1599
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1600
# broken, usually for pipes and
1603
conditions_string = ' | '.join(name
1605
condition_names.iteritems()
1606
if cond & condition)
1607
# error or the other end of multiprocessing.Pipe has closed
1608
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1611
# Read a request from the child
1612
request = parent_pipe.recv()
1613
command = request[0]
1615
if command == 'init':
1617
address = request[2]
1619
for c in self.clients:
1620
if c.fingerprint == fpr:
1624
logger.info("Client not found for fingerprint: %s, ad"
1625
"dress: %s", fpr, address)
1628
mandos_dbus_service.ClientNotFound(fpr, address[0])
1629
parent_pipe.send(False)
1632
gobject.io_add_watch(parent_pipe.fileno(),
1633
gobject.IO_IN | gobject.IO_HUP,
1634
functools.partial(self.handle_ipc,
1635
parent_pipe = parent_pipe,
1636
client_object = client))
1637
parent_pipe.send(True)
1638
# remove the old hook in favor of the new above hook on same fileno
1640
if command == 'funcall':
1641
funcname = request[1]
1645
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1647
if command == 'getattr':
1648
attrname = request[1]
1649
if callable(client_object.__getattribute__(attrname)):
1650
parent_pipe.send(('function',))
1652
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1654
if command == 'setattr':
1655
attrname = request[1]
1657
setattr(client_object, attrname, value)
408
return super(type(self), self).server_bind()
1662
411
def string_to_delta(interval):
1663
412
"""Parse a string and return a datetime.timedelta
1665
414
>>> string_to_delta('7d')
1666
415
datetime.timedelta(7)
1667
416
>>> string_to_delta('60s')
1670
419
datetime.timedelta(0, 3600)
1671
420
>>> string_to_delta('24h')
1672
421
datetime.timedelta(1)
1673
>>> string_to_delta('1w')
422
>>> string_to_delta(u'1w')
1674
423
datetime.timedelta(7)
1675
>>> string_to_delta('5m 30s')
1676
datetime.timedelta(0, 330)
1678
timevalue = datetime.timedelta(0)
1679
for s in interval.split():
1681
suffix = unicode(s[-1])
1684
delta = datetime.timedelta(value)
1686
delta = datetime.timedelta(0, value)
1688
delta = datetime.timedelta(0, 0, 0, 0, value)
1690
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1692
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1694
raise ValueError("Unknown suffix %r" % suffix)
1695
except (ValueError, IndexError) as e:
1696
raise ValueError(*(e.args))
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)
440
except (ValueError, IndexError):
446
"""From the Avahi server example code"""
447
global group, serviceName, serviceType, servicePort, serviceTXT, \
450
group = dbus.Interface(
451
bus.get_object( avahi.DBUS_NAME,
452
server.EntryGroupNew()),
453
avahi.DBUS_INTERFACE_ENTRY_GROUP)
454
group.connect_to_signal('StateChanged',
455
entry_group_state_changed)
456
logger.debug(u"Adding service '%s' of type '%s' ...",
457
serviceName, serviceType)
460
serviceInterface, # interface
461
avahi.PROTO_INET6, # protocol
462
dbus.UInt32(0), # flags
463
serviceName, serviceType,
465
dbus.UInt16(servicePort),
466
avahi.string_array_to_txt_array(serviceTXT))
470
def remove_service():
471
"""From the Avahi server example code"""
474
if not group is None:
478
def server_state_changed(state):
479
"""From the Avahi server example code"""
480
if state == avahi.SERVER_COLLISION:
481
logger.warning(u"Server name collision")
483
elif state == avahi.SERVER_RUNNING:
487
def entry_group_state_changed(state, error):
488
"""From the Avahi server example code"""
489
global serviceName, server, rename_count
491
logger.debug(u"state change: %i", state)
493
if state == avahi.ENTRY_GROUP_ESTABLISHED:
494
logger.debug(u"Service established.")
495
elif state == avahi.ENTRY_GROUP_COLLISION:
497
rename_count = rename_count - 1
499
name = server.GetAlternativeServiceName(name)
500
logger.warning(u"Service name collision, "
501
u"changing name to '%s' ...", name)
506
logger.error(u"No suitable service name found "
507
u"after %i retries, exiting.",
510
elif state == avahi.ENTRY_GROUP_FAILURE:
511
logger.error(u"Error in group state changed %s",
1701
517
def if_nametoindex(interface):
1702
"""Call the C function if_nametoindex(), or equivalent
1704
Note: This function cannot accept a unicode string."""
1705
global if_nametoindex
518
"""Call the C function if_nametoindex()"""
1707
if_nametoindex = (ctypes.cdll.LoadLibrary
1708
(ctypes.util.find_library("c"))
520
libc = ctypes.cdll.LoadLibrary("libc.so.6")
521
return libc.if_nametoindex(interface)
1710
522
except (OSError, AttributeError):
1711
logger.warning("Doing if_nametoindex the hard way")
1712
def if_nametoindex(interface):
1713
"Get an interface index the hard way, i.e. using fcntl()"
1714
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1715
with contextlib.closing(socket.socket()) as s:
1716
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1717
struct.pack(str("16s16x"),
1719
interface_index = struct.unpack(str("I"),
1721
return interface_index
1722
return if_nametoindex(interface)
1725
def daemon(nochdir = False, noclose = False):
1726
"""See daemon(3). Standard BSD Unix function.
1728
This should really exist as os.daemon, but it doesn't (yet)."""
1737
# Close all standard open file descriptors
1738
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1739
if not stat.S_ISCHR(os.fstat(null).st_mode):
1740
raise OSError(errno.ENODEV,
1741
"%s not a character device"
1743
os.dup2(null, sys.stdin.fileno())
1744
os.dup2(null, sys.stdout.fileno())
1745
os.dup2(null, sys.stderr.fileno())
1752
##################################################################
1753
# Parsing of options, both command line and config file
1755
parser = argparse.ArgumentParser()
1756
parser.add_argument("-v", "--version", action="version",
1757
version = "%%(prog)s %s" % version,
1758
help="show version number and exit")
1759
parser.add_argument("-i", "--interface", metavar="IF",
1760
help="Bind to interface IF")
1761
parser.add_argument("-a", "--address",
1762
help="Address to listen for requests on")
1763
parser.add_argument("-p", "--port", type=int,
1764
help="Port number to receive requests on")
1765
parser.add_argument("--check", action="store_true",
1766
help="Run self-test")
1767
parser.add_argument("--debug", action="store_true",
1768
help="Debug mode; run in foreground and log"
1770
parser.add_argument("--debuglevel", metavar="LEVEL",
1771
help="Debug level for stdout output")
1772
parser.add_argument("--priority", help="GnuTLS"
1773
" priority string (see GnuTLS documentation)")
1774
parser.add_argument("--servicename",
1775
metavar="NAME", help="Zeroconf service name")
1776
parser.add_argument("--configdir",
1777
default="/etc/mandos", metavar="DIR",
1778
help="Directory to search for configuration"
1780
parser.add_argument("--no-dbus", action="store_false",
1781
dest="use_dbus", help="Do not provide D-Bus"
1782
" system bus interface")
1783
parser.add_argument("--no-ipv6", action="store_false",
1784
dest="use_ipv6", help="Do not use IPv6")
1785
options = parser.parse_args()
523
if "struct" not in sys.modules:
525
if "fcntl" not in sys.modules:
527
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
529
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
530
struct.pack("16s16x", interface))
532
interface_index = struct.unpack("I", ifreq[16:20])[0]
533
return interface_index
536
if __name__ == '__main__':
537
parser = OptionParser()
538
parser.add_option("-i", "--interface", type="string",
539
default=None, metavar="IF",
540
help="Bind to interface IF")
541
parser.add_option("--cert", type="string", default="cert.pem",
543
help="Public key certificate PEM file to use")
544
parser.add_option("--key", type="string", default="key.pem",
546
help="Private key PEM file to use")
547
parser.add_option("--ca", type="string", default="ca.pem",
549
help="Certificate Authority certificate PEM file to use")
550
parser.add_option("--crl", type="string", default="crl.pem",
552
help="Certificate Revokation List PEM file to use")
553
parser.add_option("-p", "--port", type="int", default=None,
554
help="Port number to receive requests on")
555
parser.add_option("--timeout", type="string", # Parsed later
557
help="Amount of downtime allowed for clients")
558
parser.add_option("--interval", type="string", # Parsed later
560
help="How often to check that a client is up")
561
parser.add_option("--check", action="store_true", default=False,
562
help="Run self-test")
563
parser.add_option("--debug", action="store_true", default=False,
565
(options, args) = parser.parse_args()
1787
567
if options.check:
1789
569
doctest.testmod()
1792
# Default values for config file for server-global settings
1793
server_defaults = { "interface": "",
1798
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1799
"servicename": "Mandos",
1805
# Parse config file for server-global settings
1806
server_config = configparser.SafeConfigParser(server_defaults)
1808
server_config.read(os.path.join(options.configdir,
1810
# Convert the SafeConfigParser object to a dict
1811
server_settings = server_config.defaults()
1812
# Use the appropriate methods on the non-string config options
1813
for option in ("debug", "use_dbus", "use_ipv6"):
1814
server_settings[option] = server_config.getboolean("DEFAULT",
1816
if server_settings["port"]:
1817
server_settings["port"] = server_config.getint("DEFAULT",
1821
# Override the settings from the config file with command line
1823
for option in ("interface", "address", "port", "debug",
1824
"priority", "servicename", "configdir",
1825
"use_dbus", "use_ipv6", "debuglevel"):
1826
value = getattr(options, option)
1827
if value is not None:
1828
server_settings[option] = value
1830
# Force all strings to be unicode
1831
for option in server_settings.keys():
1832
if type(server_settings[option]) is str:
1833
server_settings[option] = unicode(server_settings[option])
1834
# Now we have our good server settings in "server_settings"
1836
##################################################################
1839
debug = server_settings["debug"]
1840
debuglevel = server_settings["debuglevel"]
1841
use_dbus = server_settings["use_dbus"]
1842
use_ipv6 = server_settings["use_ipv6"]
1844
if server_settings["servicename"] != "Mandos":
1845
syslogger.setFormatter(logging.Formatter
1846
('Mandos (%s) [%%(process)d]:'
1847
' %%(levelname)s: %%(message)s'
1848
% server_settings["servicename"]))
1850
# Parse config file with clients
1851
client_defaults = { "timeout": "5m",
1852
"extended_timeout": "15m",
1854
"checker": "fping -q -- %%(host)s",
1856
"approval_delay": "0s",
1857
"approval_duration": "1s",
1859
client_config = configparser.SafeConfigParser(client_defaults)
1860
client_config.read(os.path.join(server_settings["configdir"],
1863
global mandos_dbus_service
1864
mandos_dbus_service = None
1866
tcp_server = MandosServer((server_settings["address"],
1867
server_settings["port"]),
1869
interface=(server_settings["interface"]
1873
server_settings["priority"],
1876
pidfilename = "/var/run/mandos.pid"
1878
pidfile = open(pidfilename, "w")
1880
logger.error("Could not open file %r", pidfilename)
1883
uid = pwd.getpwnam("_mandos").pw_uid
1884
gid = pwd.getpwnam("_mandos").pw_gid
1887
uid = pwd.getpwnam("mandos").pw_uid
1888
gid = pwd.getpwnam("mandos").pw_gid
1891
uid = pwd.getpwnam("nobody").pw_uid
1892
gid = pwd.getpwnam("nobody").pw_gid
1899
except OSError as error:
1900
if error[0] != errno.EPERM:
1903
if not debug and not debuglevel:
1904
syslogger.setLevel(logging.WARNING)
1905
console.setLevel(logging.WARNING)
1907
level = getattr(logging, debuglevel.upper())
1908
syslogger.setLevel(level)
1909
console.setLevel(level)
1912
# Enable all possible GnuTLS debugging
1914
# "Use a log level over 10 to enable all debugging options."
1916
gnutls.library.functions.gnutls_global_set_log_level(11)
1918
@gnutls.library.types.gnutls_log_func
1919
def debug_gnutls(level, string):
1920
logger.debug("GnuTLS: %s", string[:-1])
1922
(gnutls.library.functions
1923
.gnutls_global_set_log_function(debug_gnutls))
1925
# Redirect stdin so all checkers get /dev/null
1926
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1927
os.dup2(null, sys.stdin.fileno())
1931
# No console logging
1932
logger.removeHandler(console)
1934
# Need to fork before connecting to D-Bus
1936
# Close all input and output, do double fork, etc.
1940
# From the Avahi example code
572
# Parse the time arguments
574
options.timeout = string_to_delta(options.timeout)
576
parser.error("option --timeout: Unparseable time")
578
options.interval = string_to_delta(options.interval)
580
parser.error("option --interval: Unparseable time")
583
defaults = { "checker": "sleep 1; fping -q -- %%(fqdn)s" }
584
client_config = ConfigParser.SafeConfigParser(defaults)
585
#client_config.readfp(open("secrets.conf"), "secrets.conf")
586
client_config.read("mandos-clients.conf")
588
# From the Avahi server example code
1941
589
DBusGMainLoop(set_as_default=True )
1942
590
main_loop = gobject.MainLoop()
1943
591
bus = dbus.SystemBus()
1944
# End of Avahi example code
1947
bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
1948
bus, do_not_queue=True)
1949
except dbus.exceptions.NameExistsException as e:
1950
logger.error(unicode(e) + ", disabling D-Bus")
1952
server_settings["use_dbus"] = False
1953
tcp_server.use_dbus = False
1954
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1955
service = AvahiService(name = server_settings["servicename"],
1956
servicetype = "_mandos._tcp",
1957
protocol = protocol, bus = bus)
1958
if server_settings["interface"]:
1959
service.interface = (if_nametoindex
1960
(str(server_settings["interface"])))
1962
global multiprocessing_manager
1963
multiprocessing_manager = multiprocessing.Manager()
1965
client_class = Client
1967
client_class = functools.partial(ClientDBus, bus = bus)
1968
def client_config_items(config, section):
1969
special_settings = {
1970
"approved_by_default":
1971
lambda: config.getboolean(section,
1972
"approved_by_default"),
1974
for name, value in config.items(section):
1976
yield (name, special_settings[name]())
1980
tcp_server.clients.update(set(
1981
client_class(name = section,
1982
config= dict(client_config_items(
1983
client_config, section)))
1984
for section in client_config.sections()))
1985
if not tcp_server.clients:
1986
logger.warning("No clients defined")
1992
pidfile.write(str(pid) + "\n".encode("utf-8"))
1995
logger.error("Could not write to file %r with PID %d",
1998
# "pidfile" was never created
2002
signal.signal(signal.SIGINT, signal.SIG_IGN)
2004
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2005
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2008
class MandosDBusService(dbus.service.Object):
2009
"""A D-Bus proxy object"""
2011
dbus.service.Object.__init__(self, bus, "/")
2012
_interface = "se.bsnet.fukt.Mandos"
2014
@dbus.service.signal(_interface, signature="o")
2015
def ClientAdded(self, objpath):
2019
@dbus.service.signal(_interface, signature="ss")
2020
def ClientNotFound(self, fingerprint, address):
2024
@dbus.service.signal(_interface, signature="os")
2025
def ClientRemoved(self, objpath, name):
2029
@dbus.service.method(_interface, out_signature="ao")
2030
def GetAllClients(self):
2032
return dbus.Array(c.dbus_object_path
2033
for c in tcp_server.clients)
2035
@dbus.service.method(_interface,
2036
out_signature="a{oa{sv}}")
2037
def GetAllClientsWithProperties(self):
2039
return dbus.Dictionary(
2040
((c.dbus_object_path, c.GetAll(""))
2041
for c in tcp_server.clients),
2044
@dbus.service.method(_interface, in_signature="o")
2045
def RemoveClient(self, object_path):
2047
for c in tcp_server.clients:
2048
if c.dbus_object_path == object_path:
2049
tcp_server.clients.remove(c)
2050
c.remove_from_connection()
2051
# Don't signal anything except ClientRemoved
2052
c.disable(quiet=True)
2054
self.ClientRemoved(object_path, c.name)
2056
raise KeyError(object_path)
2060
mandos_dbus_service = MandosDBusService()
2063
"Cleanup function; run on exit"
2066
while tcp_server.clients:
2067
client = tcp_server.clients.pop()
2069
client.remove_from_connection()
2070
client.disable_hook = None
2071
# Don't signal anything except ClientRemoved
2072
client.disable(quiet=True)
2075
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2078
atexit.register(cleanup)
2080
for client in tcp_server.clients:
2083
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2087
tcp_server.server_activate()
2089
# Find out what port we got
2090
service.port = tcp_server.socket.getsockname()[1]
2092
logger.info("Now listening on address %r, port %d,"
2093
" flowinfo %d, scope_id %d"
2094
% tcp_server.socket.getsockname())
2096
logger.info("Now listening on address %r, port %d"
2097
% tcp_server.socket.getsockname())
2099
#service.interface = tcp_server.socket.getsockname()[3]
592
server = dbus.Interface(
593
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
594
avahi.DBUS_INTERFACE_SERVER )
595
# End of Avahi example code
597
debug = options.debug
600
def remove_from_clients(client):
601
clients.remove(client)
603
logger.debug(u"No clients left, exiting")
606
clients.update(Set(Client(name=section, options=options,
607
stop_hook = remove_from_clients,
608
**(dict(client_config\
610
for section in client_config.sections()))
611
for client in clients:
614
tcp_server = IPv6_TCPServer((None, options.port),
618
# Find out what random port we got
619
servicePort = tcp_server.socket.getsockname()[1]
620
logger.debug(u"Now listening on port %d", servicePort)
622
if options.interface is not None:
623
serviceInterface = if_nametoindex(options.interface)
625
# From the Avahi server example code
626
server.connect_to_signal("StateChanged", server_state_changed)
627
server_state_changed(server.GetState())
628
# End of Avahi example code
630
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
631
lambda *args, **kwargs:
632
tcp_server.handle_request(*args[2:],
2102
# From the Avahi example code
2105
except dbus.exceptions.DBusException as error:
2106
logger.critical("DBusException: %s", error)
2109
# End of Avahi example code
2111
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2112
lambda *args, **kwargs:
2113
(tcp_server.handle_request
2114
(*args[2:], **kwargs) or True))
2116
logger.debug("Starting main loop")
2118
except AvahiError as error:
2119
logger.critical("AvahiError: %s", error)
2122
636
except KeyboardInterrupt:
2124
print("", file=sys.stderr)
2125
logger.debug("Server received KeyboardInterrupt")
2126
logger.debug("Server exiting")
2127
# Must run before the D-Bus bus name gets deregistered
2130
if __name__ == '__main__':
641
# From the Avahi server example code
642
if not group is None:
644
# End of Avahi example code
646
for client in clients:
647
client.stop_hook = None