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())
267
def _timedelta_to_milliseconds(td):
268
"Convert a datetime.timedelta() to milliseconds"
269
return ((td.days * 24 * 60 * 60 * 1000)
270
+ (td.seconds * 1000)
271
+ (td.microseconds // 1000))
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
273
56
class Client(object):
274
57
"""A representation of a client host served by this server.
277
_approved: bool(); 'None' if not yet approved/disapproved
278
approval_delay: datetime.timedelta(); Time to wait for approval
279
approval_duration: datetime.timedelta(); Duration of one approval
280
checker: subprocess.Popen(); a running checker process used
281
to see if the client lives.
282
'None' if no process is running.
283
checker_callback_tag: a gobject event source tag, or None
284
checker_command: string; External command which is run to check
285
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
286
78
runtime with vars(self) as dict, so that for
287
79
instance %(name)s can be used in the command.
288
checker_initiator_tag: a gobject event source tag, or None
289
created: datetime.datetime(); (UTC) object creation
290
current_checker_command: string; current running checker_command
291
disable_hook: If set, called by disable() as disable_hook(self)
292
disable_initiator_tag: a gobject event source tag, or None
294
fingerprint: string (40 or 32 hexadecimal digits); used to
295
uniquely identify the client
296
host: string; available for use by the checker command
297
interval: datetime.timedelta(); How often to start a new checker
298
last_approval_request: datetime.datetime(); (UTC) or None
299
last_checked_ok: datetime.datetime(); (UTC) or None
300
last_enabled: datetime.datetime(); (UTC)
301
name: string; from the config file, used in log messages and
303
secret: bytestring; sent verbatim (over TLS) to client
304
timeout: datetime.timedelta(); How long from last_checked_ok
305
until this client is disabled
306
extended_timeout: extra long timeout when password has been sent
307
runtime_expansions: Allowed attributes for runtime expansion.
308
expires: 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: - '' -
312
runtime_expansions = ("approval_delay", "approval_duration",
313
"created", "enabled", "fingerprint",
314
"host", "interval", "last_checked_ok",
315
"last_enabled", "name", "timeout")
317
def timeout_milliseconds(self):
318
"Return the 'timeout' attribute in milliseconds"
319
return _timedelta_to_milliseconds(self.timeout)
321
def extended_timeout_milliseconds(self):
322
"Return the 'extended_timeout' attribute in milliseconds"
323
return _timedelta_to_milliseconds(self.extended_timeout)
325
def interval_milliseconds(self):
326
"Return the 'interval' attribute in milliseconds"
327
return _timedelta_to_milliseconds(self.interval)
329
def approval_delay_milliseconds(self):
330
return _timedelta_to_milliseconds(self.approval_delay)
332
def __init__(self, name = None, disable_hook=None, config=None):
333
"""Note: the 'checker' key in 'config' sets the
334
'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):
339
logger.debug("Creating client %r", self.name)
340
# Uppercase and remove spaces from fingerprint for later
341
# comparison purposes with return value from the fingerprint()
343
self.fingerprint = (config["fingerprint"].upper()
345
logger.debug(" Fingerprint: %s", self.fingerprint)
346
if "secret" in config:
347
self.secret = config["secret"].decode("base64")
348
elif "secfile" in config:
349
with open(os.path.expanduser(os.path.expandvars
350
(config["secfile"])),
352
self.secret = secfile.read()
354
raise TypeError("No secret or secfile for client %s"
356
self.host = config.get("host", "")
357
self.created = datetime.datetime.utcnow()
359
self.last_approval_request = None
360
self.last_enabled = None
361
self.last_checked_ok = None
362
self.timeout = string_to_delta(config["timeout"])
363
self.extended_timeout = string_to_delta(config["extended_timeout"])
364
self.interval = string_to_delta(config["interval"])
365
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
366
138
self.checker = None
367
139
self.checker_initiator_tag = None
368
self.disable_initiator_tag = None
140
self.stop_initiator_tag = None
370
141
self.checker_callback_tag = None
371
self.checker_command = config["checker"]
372
self.current_checker_command = None
373
self.last_connect = None
374
self._approved = None
375
self.approved_by_default = config.get("approved_by_default",
377
self.approvals_pending = 0
378
self.approval_delay = string_to_delta(
379
config["approval_delay"])
380
self.approval_duration = string_to_delta(
381
config["approval_duration"])
382
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
384
def send_changedstate(self):
385
self.changedstate.acquire()
386
self.changedstate.notify_all()
387
self.changedstate.release()
390
"""Start this client's checker and timeout hooks"""
391
if getattr(self, "enabled", False):
394
self.send_changedstate()
142
self.check_command = checker
144
"""Start this clients checker and timeout hooks"""
395
145
# Schedule a new checker to be started an 'interval' from now,
396
146
# and every interval from then on.
397
self.checker_initiator_tag = (gobject.timeout_add
398
(self.interval_milliseconds(),
400
# Schedule a disable() when 'timeout' has passed
401
self.expires = datetime.datetime.utcnow() + self.timeout
402
self.disable_initiator_tag = (gobject.timeout_add
403
(self.timeout_milliseconds(),
406
self.last_enabled = datetime.datetime.utcnow()
147
self.checker_initiator_tag = gobject.timeout_add\
148
(self._interval_milliseconds,
407
150
# Also start a new checker *right now*.
408
151
self.start_checker()
410
def disable(self, quiet=True):
411
"""Disable this client."""
412
if not getattr(self, "enabled", False):
415
self.send_changedstate()
417
logger.info("Disabling client %s", self.name)
418
if getattr(self, "disable_initiator_tag", False):
419
gobject.source_remove(self.disable_initiator_tag)
420
self.disable_initiator_tag = None
422
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:
423
166
gobject.source_remove(self.checker_initiator_tag)
424
167
self.checker_initiator_tag = None
425
168
self.stop_checker()
426
if self.disable_hook:
427
self.disable_hook(self)
429
171
# Do not run this again if called by a gobject.timeout_add
432
173
def __del__(self):
433
self.disable_hook = None
436
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):
437
185
"""The checker has completed, so take appropriate actions."""
438
self.checker_callback_tag = None
440
if os.WIFEXITED(condition):
441
exitstatus = os.WEXITSTATUS(condition)
443
logger.info("Checker for %(name)s succeeded",
447
logger.info("Checker for %(name)s failed",
450
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?",
453
def checked_ok(self, timeout=None):
454
"""Bump up the timeout for this client.
456
This should only be called when the client has been seen,
460
timeout = self.timeout
461
self.last_checked_ok = datetime.datetime.utcnow()
462
gobject.source_remove(self.disable_initiator_tag)
463
self.expires = datetime.datetime.utcnow() + timeout
464
self.disable_initiator_tag = (gobject.timeout_add
465
(_timedelta_to_milliseconds(timeout),
468
def need_approval(self):
469
self.last_approval_request = datetime.datetime.utcnow()
200
logger.debug(u"Checker for %(name)s failed",
203
self.checker_callback_tag = None
471
204
def start_checker(self):
472
205
"""Start a new checker subprocess if one is not running.
474
206
If a checker already exists, leave it running and do
476
# The reason for not killing a running checker is that if we
477
# did that, then if a checker (for some reason) started
478
# running slowly and taking more than 'interval' time, the
479
# client would inevitably timeout, since no checker would get
480
# a chance to run to completion. If we instead leave running
481
# checkers alone, the checker would have to take more time
482
# than 'timeout' for the client to be disabled, which is as it
485
# If a checker exists, make sure it is not a zombie
487
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
488
except (AttributeError, OSError) as error:
489
if (isinstance(error, OSError)
490
and error.errno != errno.ECHILD):
494
logger.warning("Checker was a zombie")
495
gobject.source_remove(self.checker_callback_tag)
496
self.checker_callback(pid, status,
497
self.current_checker_command)
498
# Start a new checker if needed
499
208
if self.checker is None:
209
logger.debug(u"Starting checker for %s",
501
# In case checker_command has exactly one % operator
502
command = self.checker_command % self.host
212
command = self.check_command % self.fqdn
503
213
except TypeError:
504
# Escape attributes for the shell
505
escaped_attrs = dict(
507
re.escape(unicode(str(getattr(self, attr, "")),
511
self.runtime_expansions)
214
escaped_attrs = dict((key, re.escape(str(val)))
216
vars(self).iteritems())
514
command = self.checker_command % escaped_attrs
515
except TypeError as error:
516
logger.error('Could not format string "%s":'
517
' %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)
518
222
return True # Try again later
519
self.current_checker_command = command
521
logger.info("Starting checker %r for %s",
523
# We don't need to redirect stdout and stderr, since
524
# in normal mode, that is already done by daemon(),
525
# and in debug mode we don't want to. (Stdin is
526
# always replaced by /dev/null.)
527
self.checker = subprocess.Popen(command,
530
self.checker_callback_tag = (gobject.child_watch_add
532
self.checker_callback,
534
# The checker may have completed before the gobject
535
# watch was added. Check for this.
536
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
538
gobject.source_remove(self.checker_callback_tag)
539
self.checker_callback(pid, status, command)
540
except OSError as error:
541
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",
543
236
# Re-run this periodically if run by gobject.timeout_add
546
238
def stop_checker(self):
547
239
"""Force the checker process, if any, to stop."""
548
if self.checker_callback_tag:
549
gobject.source_remove(self.checker_callback_tag)
550
self.checker_callback_tag = None
551
if getattr(self, "checker", None) is None:
240
if not hasattr(self, "checker") or self.checker is None:
553
logger.debug("Stopping checker for %(name)s", vars(self))
555
os.kill(self.checker.pid, signal.SIGTERM)
557
#if self.checker.poll() is None:
558
# os.kill(self.checker.pid, signal.SIGKILL)
559
except OSError as error:
560
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
def datetime_to_dbus (dt, variant_level=0):
755
"""Convert a UTC datetime.datetime() to a D-Bus type."""
757
return dbus.String("", variant_level = variant_level)
758
return dbus.String(dt.isoformat(),
759
variant_level=variant_level)
762
class ClientDBus(Client, DBusObjectWithProperties):
763
"""A Client class using D-Bus
766
dbus_object_path: dbus.ObjectPath
767
bus: dbus.SystemBus()
770
runtime_expansions = (Client.runtime_expansions
771
+ ("dbus_object_path",))
773
# dbus.service.Object doesn't use super(), so we can't either.
775
def __init__(self, bus = None, *args, **kwargs):
776
self._approvals_pending = 0
778
Client.__init__(self, *args, **kwargs)
779
# Only now, when this client is initialized, can it show up on
781
client_object_name = unicode(self.name).translate(
784
self.dbus_object_path = (dbus.ObjectPath
785
("/clients/" + client_object_name))
786
DBusObjectWithProperties.__init__(self, self.bus,
787
self.dbus_object_path)
789
def notifychangeproperty(transform_func,
790
dbus_name, type_func=lambda x: x,
792
""" Modify a variable so that its a property that announce its
794
transform_fun: Function that takes a value and transform it to
796
dbus_name: DBus name of the variable
797
type_func: Function that transform the value before sending it
799
variant_level: DBus variant level. default: 1
802
def setter(self, value):
803
old_value = real_value[0]
804
real_value[0] = value
805
if hasattr(self, "dbus_object_path"):
806
if type_func(old_value) != type_func(real_value[0]):
807
dbus_value = transform_func(type_func(real_value[0]),
809
self.PropertyChanged(dbus.String(dbus_name),
812
return property(lambda self: real_value[0], setter)
815
expires = notifychangeproperty(datetime_to_dbus, "Expires")
816
approvals_pending = notifychangeproperty(dbus.Boolean,
819
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
820
last_enabled = notifychangeproperty(datetime_to_dbus,
822
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
823
type_func = lambda checker: checker is not None)
824
last_checked_ok = notifychangeproperty(datetime_to_dbus,
826
last_approval_request = notifychangeproperty(datetime_to_dbus,
827
"LastApprovalRequest")
828
approved_by_default = notifychangeproperty(dbus.Boolean,
830
approval_delay = notifychangeproperty(dbus.UInt16, "ApprovalDelay",
831
type_func = _timedelta_to_milliseconds)
832
approval_duration = notifychangeproperty(dbus.UInt16, "ApprovalDuration",
833
type_func = _timedelta_to_milliseconds)
834
host = notifychangeproperty(dbus.String, "Host")
835
timeout = notifychangeproperty(dbus.UInt16, "Timeout",
836
type_func = _timedelta_to_milliseconds)
837
extended_timeout = notifychangeproperty(dbus.UInt16, "ExtendedTimeout",
838
type_func = _timedelta_to_milliseconds)
839
interval = notifychangeproperty(dbus.UInt16, "Interval",
840
type_func = _timedelta_to_milliseconds)
841
checker_command = notifychangeproperty(dbus.String, "Checker")
843
del notifychangeproperty
845
def __del__(self, *args, **kwargs):
847
self.remove_from_connection()
850
if hasattr(DBusObjectWithProperties, "__del__"):
851
DBusObjectWithProperties.__del__(self, *args, **kwargs)
852
Client.__del__(self, *args, **kwargs)
854
def checker_callback(self, pid, condition, command,
242
gobject.source_remove(self.checker_callback_tag)
856
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)
857
247
self.checker = None
858
if os.WIFEXITED(condition):
859
exitstatus = os.WEXITSTATUS(condition)
861
self.CheckerCompleted(dbus.Int16(exitstatus),
862
dbus.Int64(condition),
863
dbus.String(command))
866
self.CheckerCompleted(dbus.Int16(-1),
867
dbus.Int64(condition),
868
dbus.String(command))
870
return Client.checker_callback(self, pid, condition, command,
873
def start_checker(self, *args, **kwargs):
874
old_checker = self.checker
875
if self.checker is not None:
876
old_checker_pid = self.checker.pid
878
old_checker_pid = None
879
r = Client.start_checker(self, *args, **kwargs)
880
# Only if new checker process was started
881
if (self.checker is not None
882
and old_checker_pid != self.checker.pid):
884
self.CheckerStarted(self.current_checker_command)
887
def _reset_approved(self):
888
self._approved = None
891
def approve(self, value=True):
892
self.send_changedstate()
893
self._approved = value
894
gobject.timeout_add(_timedelta_to_milliseconds
895
(self.approval_duration),
896
self._reset_approved)
899
## D-Bus methods, signals & properties
900
_interface = "se.bsnet.fukt.Mandos.Client"
904
# CheckerCompleted - signal
905
@dbus.service.signal(_interface, signature="nxs")
906
def CheckerCompleted(self, exitcode, waitstatus, command):
910
# CheckerStarted - signal
911
@dbus.service.signal(_interface, signature="s")
912
def CheckerStarted(self, command):
916
# PropertyChanged - signal
917
@dbus.service.signal(_interface, signature="sv")
918
def PropertyChanged(self, property, value):
923
@dbus.service.signal(_interface)
926
Is sent after a successful transfer of secret from the Mandos
927
server to mandos-client
932
@dbus.service.signal(_interface, signature="s")
933
def Rejected(self, reason):
937
# NeedApproval - signal
938
@dbus.service.signal(_interface, signature="tb")
939
def NeedApproval(self, timeout, default):
941
return self.need_approval()
946
@dbus.service.method(_interface, in_signature="b")
947
def Approve(self, value):
951
@dbus.service.method(_interface)
956
@dbus.service.method(_interface)
961
# StartChecker - method
962
@dbus.service.method(_interface)
963
def StartChecker(self):
968
@dbus.service.method(_interface)
973
# StopChecker - method
974
@dbus.service.method(_interface)
975
def StopChecker(self):
980
# ApprovalPending - property
981
@dbus_service_property(_interface, signature="b", access="read")
982
def ApprovalPending_dbus_property(self):
983
return dbus.Boolean(bool(self.approvals_pending))
985
# ApprovedByDefault - property
986
@dbus_service_property(_interface, signature="b",
988
def ApprovedByDefault_dbus_property(self, value=None):
989
if value is None: # get
990
return dbus.Boolean(self.approved_by_default)
991
self.approved_by_default = bool(value)
993
# ApprovalDelay - property
994
@dbus_service_property(_interface, signature="t",
996
def ApprovalDelay_dbus_property(self, value=None):
997
if value is None: # get
998
return dbus.UInt64(self.approval_delay_milliseconds())
999
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1001
# ApprovalDuration - property
1002
@dbus_service_property(_interface, signature="t",
1004
def ApprovalDuration_dbus_property(self, value=None):
1005
if value is None: # get
1006
return dbus.UInt64(_timedelta_to_milliseconds(
1007
self.approval_duration))
1008
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1011
@dbus_service_property(_interface, signature="s", access="read")
1012
def Name_dbus_property(self):
1013
return dbus.String(self.name)
1015
# Fingerprint - property
1016
@dbus_service_property(_interface, signature="s", access="read")
1017
def Fingerprint_dbus_property(self):
1018
return dbus.String(self.fingerprint)
1021
@dbus_service_property(_interface, signature="s",
1023
def Host_dbus_property(self, value=None):
1024
if value is None: # get
1025
return dbus.String(self.host)
1028
# Created - property
1029
@dbus_service_property(_interface, signature="s", access="read")
1030
def Created_dbus_property(self):
1031
return dbus.String(datetime_to_dbus(self.created))
1033
# LastEnabled - property
1034
@dbus_service_property(_interface, signature="s", access="read")
1035
def LastEnabled_dbus_property(self):
1036
return datetime_to_dbus(self.last_enabled)
1038
# Enabled - property
1039
@dbus_service_property(_interface, signature="b",
1041
def Enabled_dbus_property(self, value=None):
1042
if value is None: # get
1043
return dbus.Boolean(self.enabled)
1049
# LastCheckedOK - property
1050
@dbus_service_property(_interface, signature="s",
1052
def LastCheckedOK_dbus_property(self, value=None):
1053
if value is not None:
1056
return datetime_to_dbus(self.last_checked_ok)
1058
# Expires - property
1059
@dbus_service_property(_interface, signature="s", access="read")
1060
def Expires_dbus_property(self):
1061
return datetime_to_dbus(self.expires)
1063
# LastApprovalRequest - property
1064
@dbus_service_property(_interface, signature="s", access="read")
1065
def LastApprovalRequest_dbus_property(self):
1066
return datetime_to_dbus(self.last_approval_request)
1068
# Timeout - property
1069
@dbus_service_property(_interface, signature="t",
1071
def Timeout_dbus_property(self, value=None):
1072
if value is None: # get
1073
return dbus.UInt64(self.timeout_milliseconds())
1074
self.timeout = datetime.timedelta(0, 0, 0, value)
1075
if getattr(self, "disable_initiator_tag", None) is None:
1077
# Reschedule timeout
1078
gobject.source_remove(self.disable_initiator_tag)
1079
self.disable_initiator_tag = None
1081
time_to_die = (self.
1082
_timedelta_to_milliseconds((self
1087
if time_to_die <= 0:
1088
# The timeout has passed
1091
self.expires = (datetime.datetime.utcnow()
1092
+ datetime.timedelta(milliseconds = time_to_die))
1093
self.disable_initiator_tag = (gobject.timeout_add
1094
(time_to_die, self.disable))
1096
# ExtendedTimeout - property
1097
@dbus_service_property(_interface, signature="t",
1099
def ExtendedTimeout_dbus_property(self, value=None):
1100
if value is None: # get
1101
return dbus.UInt64(self.extended_timeout_milliseconds())
1102
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1104
# Interval - property
1105
@dbus_service_property(_interface, signature="t",
1107
def Interval_dbus_property(self, value=None):
1108
if value is None: # get
1109
return dbus.UInt64(self.interval_milliseconds())
1110
self.interval = datetime.timedelta(0, 0, 0, value)
1111
if getattr(self, "checker_initiator_tag", None) is None:
1113
# Reschedule checker run
1114
gobject.source_remove(self.checker_initiator_tag)
1115
self.checker_initiator_tag = (gobject.timeout_add
1116
(value, self.start_checker))
1117
self.start_checker() # Start one now, too
1119
# Checker - property
1120
@dbus_service_property(_interface, signature="s",
1122
def Checker_dbus_property(self, value=None):
1123
if value is None: # get
1124
return dbus.String(self.checker_command)
1125
self.checker_command = value
1127
# CheckerRunning - property
1128
@dbus_service_property(_interface, signature="b",
1130
def CheckerRunning_dbus_property(self, value=None):
1131
if value is None: # get
1132
return dbus.Boolean(self.checker is not None)
1134
self.start_checker()
1138
# ObjectPath - property
1139
@dbus_service_property(_interface, signature="o", access="read")
1140
def ObjectPath_dbus_property(self):
1141
return self.dbus_object_path # is already a dbus.ObjectPath
1144
@dbus_service_property(_interface, signature="ay",
1145
access="write", byte_arrays=True)
1146
def Secret_dbus_property(self, value):
1147
self.secret = str(value)
1152
class ProxyClient(object):
1153
def __init__(self, child_pipe, fpr, address):
1154
self._pipe = child_pipe
1155
self._pipe.send(('init', fpr, address))
1156
if not self._pipe.recv():
1159
def __getattribute__(self, name):
1160
if(name == '_pipe'):
1161
return super(ProxyClient, self).__getattribute__(name)
1162
self._pipe.send(('getattr', name))
1163
data = self._pipe.recv()
1164
if data[0] == 'data':
1166
if data[0] == 'function':
1167
def func(*args, **kwargs):
1168
self._pipe.send(('funcall', name, args, kwargs))
1169
return self._pipe.recv()[1]
1172
def __setattr__(self, name, value):
1173
if(name == '_pipe'):
1174
return super(ProxyClient, self).__setattr__(name, value)
1175
self._pipe.send(('setattr', name, value))
1178
class ClientHandler(socketserver.BaseRequestHandler, object):
1179
"""A class to handle client connections.
1181
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.
1182
307
Note: This will run in its own forked process."""
1184
309
def handle(self):
1185
with contextlib.closing(self.server.child_pipe) as child_pipe:
1186
logger.info("TCP connection from: %s",
1187
unicode(self.client_address))
1188
logger.debug("Pipe FD: %d",
1189
self.server.child_pipe.fileno())
1191
session = (gnutls.connection
1192
.ClientSession(self.request,
1194
.X509Credentials()))
1196
# Note: gnutls.connection.X509Credentials is really a
1197
# generic GnuTLS certificate credentials object so long as
1198
# no X.509 keys are added to it. Therefore, we can use it
1199
# here despite using OpenPGP certificates.
1201
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1202
# "+AES-256-CBC", "+SHA1",
1203
# "+COMP-NULL", "+CTYPE-OPENPGP",
1205
# Use a fallback default, since this MUST be set.
1206
priority = self.server.gnutls_priority
1207
if priority is None:
1209
(gnutls.library.functions
1210
.gnutls_priority_set_direct(session._c_object,
1213
# Start communication using the Mandos protocol
1214
# Get protocol number
1215
line = self.request.makefile().readline()
1216
logger.debug("Protocol version: %r", line)
1218
if int(line.strip().split()[0]) > 1:
1220
except (ValueError, IndexError, RuntimeError) as error:
1221
logger.error("Unknown protocol version: %s", error)
1224
# Start GnuTLS connection
1227
except gnutls.errors.GNUTLSError as error:
1228
logger.warning("Handshake failed: %s", error)
1229
# Do not run session.bye() here: the session is not
1230
# established. Just abandon the request.
1232
logger.debug("Handshake succeeded")
1234
approval_required = False
1237
fpr = self.fingerprint(self.peer_certificate
1240
gnutls.errors.GNUTLSError) as error:
1241
logger.warning("Bad certificate: %s", error)
1243
logger.debug("Fingerprint: %s", fpr)
1246
client = ProxyClient(child_pipe, fpr,
1247
self.client_address)
1251
if client.approval_delay:
1252
delay = client.approval_delay
1253
client.approvals_pending += 1
1254
approval_required = True
1257
if not client.enabled:
1258
logger.info("Client %s is disabled",
1260
if self.server.use_dbus:
1262
client.Rejected("Disabled")
1265
if client._approved or not client.approval_delay:
1266
#We are approved or approval is disabled
1268
elif client._approved is None:
1269
logger.info("Client %s needs approval",
1271
if self.server.use_dbus:
1273
client.NeedApproval(
1274
client.approval_delay_milliseconds(),
1275
client.approved_by_default)
1277
logger.warning("Client %s was not approved",
1279
if self.server.use_dbus:
1281
client.Rejected("Denied")
1284
#wait until timeout or approved
1285
#x = float(client._timedelta_to_milliseconds(delay))
1286
time = datetime.datetime.now()
1287
client.changedstate.acquire()
1288
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1289
client.changedstate.release()
1290
time2 = datetime.datetime.now()
1291
if (time2 - time) >= delay:
1292
if not client.approved_by_default:
1293
logger.warning("Client %s timed out while"
1294
" waiting for approval",
1296
if self.server.use_dbus:
1298
client.Rejected("Approval timed out")
1303
delay -= time2 - time
1306
while sent_size < len(client.secret):
1308
sent = session.send(client.secret[sent_size:])
1309
except gnutls.errors.GNUTLSError as error:
1310
logger.warning("gnutls send failed")
1312
logger.debug("Sent: %d, remaining: %d",
1313
sent, len(client.secret)
1314
- (sent_size + sent))
1317
logger.info("Sending secret to %s", client.name)
1318
# bump the timeout as if seen
1319
client.checked_ok(client.extended_timeout)
1320
if self.server.use_dbus:
1325
if approval_required:
1326
client.approvals_pending -= 1
1329
except gnutls.errors.GNUTLSError as error:
1330
logger.warning("GnuTLS bye failed")
1333
def peer_certificate(session):
1334
"Return the peer's OpenPGP certificate as a bytestring"
1335
# If not an OpenPGP certificate...
1336
if (gnutls.library.functions
1337
.gnutls_certificate_type_get(session._c_object)
1338
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1339
# ...do the normal thing
1340
return session.peer_certificate
1341
list_size = ctypes.c_uint(1)
1342
cert_list = (gnutls.library.functions
1343
.gnutls_certificate_get_peers
1344
(session._c_object, ctypes.byref(list_size)))
1345
if not bool(cert_list) and list_size.value != 0:
1346
raise gnutls.errors.GNUTLSError("error getting peer"
1348
if list_size.value == 0:
1351
return ctypes.string_at(cert.data, cert.size)
1354
def fingerprint(openpgp):
1355
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1356
# New GnuTLS "datum" with the OpenPGP public key
1357
datum = (gnutls.library.types
1358
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1361
ctypes.c_uint(len(openpgp))))
1362
# New empty GnuTLS certificate
1363
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1364
(gnutls.library.functions
1365
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1366
# Import the OpenPGP public key into the certificate
1367
(gnutls.library.functions
1368
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1369
gnutls.library.constants
1370
.GNUTLS_OPENPGP_FMT_RAW))
1371
# Verify the self signature in the key
1372
crtverify = ctypes.c_uint()
1373
(gnutls.library.functions
1374
.gnutls_openpgp_crt_verify_self(crt, 0,
1375
ctypes.byref(crtverify)))
1376
if crtverify.value != 0:
1377
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1378
raise (gnutls.errors.CertificateSecurityError
1380
# New buffer for the fingerprint
1381
buf = ctypes.create_string_buffer(20)
1382
buf_len = ctypes.c_size_t()
1383
# Get the fingerprint from the certificate into the buffer
1384
(gnutls.library.functions
1385
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1386
ctypes.byref(buf_len)))
1387
# Deinit the certificate
1388
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1389
# Convert the buffer to a Python bytestring
1390
fpr = ctypes.string_at(buf, buf_len.value)
1391
# Convert the bytestring to hexadecimal notation
1392
hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1396
class MultiprocessingMixIn(object):
1397
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1398
def sub_process_main(self, request, address):
1400
self.finish_request(request, address)
1402
self.handle_error(request, address)
1403
self.close_request(request)
1405
def process_request(self, request, address):
1406
"""Start a new process to process the request."""
1407
multiprocessing.Process(target = self.sub_process_main,
1408
args = (request, address)).start()
1411
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1412
""" adds a pipe to the MixIn """
1413
def process_request(self, request, client_address):
1414
"""Overrides and wraps the original process_request().
1416
This function creates a new pipe in self.pipe
1418
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1420
super(MultiprocessingMixInWithPipe,
1421
self).process_request(request, client_address)
1422
self.child_pipe.close()
1423
self.add_pipe(parent_pipe)
1425
def add_pipe(self, parent_pipe):
1426
"""Dummy function; override as necessary"""
1427
raise NotImplementedError
1430
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1431
socketserver.TCPServer, object):
1432
"""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.
1435
enabled: Boolean; whether this server is activated yet
1436
interface: None or a network interface name (string)
1437
use_ipv6: Boolean; to use IPv6 or not
368
options: Command line options
369
clients: Set() of Client objects
1439
def __init__(self, server_address, RequestHandlerClass,
1440
interface=None, use_ipv6=True):
1441
self.interface = interface
1443
self.address_family = socket.AF_INET6
1444
socketserver.TCPServer.__init__(self, server_address,
1445
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)
1446
380
def server_bind(self):
1447
381
"""This overrides the normal server_bind() function
1448
382
to bind to an interface if one was specified, and also NOT to
1449
383
bind to an address or port if they were not specified."""
1450
if self.interface is not None:
1451
if SO_BINDTODEVICE is None:
1452
logger.error("SO_BINDTODEVICE does not exist;"
1453
" cannot bind to interface %s",
1457
self.socket.setsockopt(socket.SOL_SOCKET,
1461
except socket.error as error:
1462
if error[0] == errno.EPERM:
1463
logger.error("No permission to"
1464
" bind to interface %s",
1466
elif error[0] == errno.ENOPROTOOPT:
1467
logger.error("SO_BINDTODEVICE not available;"
1468
" 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)
1472
399
# Only bind(2) the socket if we really need to.
1473
400
if self.server_address[0] or self.server_address[1]:
1474
401
if not self.server_address[0]:
1475
if self.address_family == socket.AF_INET6:
1476
any_address = "::" # in6addr_any
1478
any_address = socket.INADDR_ANY
1479
self.server_address = (any_address,
403
self.server_address = (in6addr_any,
1480
404
self.server_address[1])
1481
elif not self.server_address[1]:
405
elif self.server_address[1] is None:
1482
406
self.server_address = (self.server_address[0],
1484
# if self.interface:
1485
# self.server_address = (self.server_address[0],
1490
return socketserver.TCPServer.server_bind(self)
1493
class MandosServer(IPv6_TCPServer):
1497
clients: set of Client objects
1498
gnutls_priority GnuTLS priority string
1499
use_dbus: Boolean; to emit D-Bus signals or not
1501
Assumes a gobject.MainLoop event loop.
1503
def __init__(self, server_address, RequestHandlerClass,
1504
interface=None, use_ipv6=True, clients=None,
1505
gnutls_priority=None, use_dbus=True):
1506
self.enabled = False
1507
self.clients = clients
1508
if self.clients is None:
1509
self.clients = set()
1510
self.use_dbus = use_dbus
1511
self.gnutls_priority = gnutls_priority
1512
IPv6_TCPServer.__init__(self, server_address,
1513
RequestHandlerClass,
1514
interface = interface,
1515
use_ipv6 = use_ipv6)
1516
def server_activate(self):
1518
return socketserver.TCPServer.server_activate(self)
1521
def add_pipe(self, parent_pipe):
1522
# Call "handle_ipc" for both data and EOF events
1523
gobject.io_add_watch(parent_pipe.fileno(),
1524
gobject.IO_IN | gobject.IO_HUP,
1525
functools.partial(self.handle_ipc,
1526
parent_pipe = parent_pipe))
1528
def handle_ipc(self, source, condition, parent_pipe=None,
1529
client_object=None):
1531
gobject.IO_IN: "IN", # There is data to read.
1532
gobject.IO_OUT: "OUT", # Data can be written (without
1534
gobject.IO_PRI: "PRI", # There is urgent data to read.
1535
gobject.IO_ERR: "ERR", # Error condition.
1536
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1537
# broken, usually for pipes and
1540
conditions_string = ' | '.join(name
1542
condition_names.iteritems()
1543
if cond & condition)
1544
# error or the other end of multiprocessing.Pipe has closed
1545
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1548
# Read a request from the child
1549
request = parent_pipe.recv()
1550
command = request[0]
1552
if command == 'init':
1554
address = request[2]
1556
for c in self.clients:
1557
if c.fingerprint == fpr:
1561
logger.info("Client not found for fingerprint: %s, ad"
1562
"dress: %s", fpr, address)
1565
mandos_dbus_service.ClientNotFound(fpr, address[0])
1566
parent_pipe.send(False)
1569
gobject.io_add_watch(parent_pipe.fileno(),
1570
gobject.IO_IN | gobject.IO_HUP,
1571
functools.partial(self.handle_ipc,
1572
parent_pipe = parent_pipe,
1573
client_object = client))
1574
parent_pipe.send(True)
1575
# remove the old hook in favor of the new above hook on same fileno
1577
if command == 'funcall':
1578
funcname = request[1]
1582
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1584
if command == 'getattr':
1585
attrname = request[1]
1586
if callable(client_object.__getattribute__(attrname)):
1587
parent_pipe.send(('function',))
1589
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1591
if command == 'setattr':
1592
attrname = request[1]
1594
setattr(client_object, attrname, value)
408
return super(type(self), self).server_bind()
1599
411
def string_to_delta(interval):
1600
412
"""Parse a string and return a datetime.timedelta
1602
414
>>> string_to_delta('7d')
1603
415
datetime.timedelta(7)
1604
416
>>> string_to_delta('60s')
1607
419
datetime.timedelta(0, 3600)
1608
420
>>> string_to_delta('24h')
1609
421
datetime.timedelta(1)
1610
>>> string_to_delta('1w')
422
>>> string_to_delta(u'1w')
1611
423
datetime.timedelta(7)
1612
>>> string_to_delta('5m 30s')
1613
datetime.timedelta(0, 330)
1615
timevalue = datetime.timedelta(0)
1616
for s in interval.split():
1618
suffix = unicode(s[-1])
1621
delta = datetime.timedelta(value)
1623
delta = datetime.timedelta(0, value)
1625
delta = datetime.timedelta(0, 0, 0, 0, value)
1627
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1629
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1631
raise ValueError("Unknown suffix %r" % suffix)
1632
except (ValueError, IndexError) as e:
1633
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",
1638
517
def if_nametoindex(interface):
1639
"""Call the C function if_nametoindex(), or equivalent
1641
Note: This function cannot accept a unicode string."""
1642
global if_nametoindex
518
"""Call the C function if_nametoindex()"""
1644
if_nametoindex = (ctypes.cdll.LoadLibrary
1645
(ctypes.util.find_library("c"))
520
libc = ctypes.cdll.LoadLibrary("libc.so.6")
521
return libc.if_nametoindex(interface)
1647
522
except (OSError, AttributeError):
1648
logger.warning("Doing if_nametoindex the hard way")
1649
def if_nametoindex(interface):
1650
"Get an interface index the hard way, i.e. using fcntl()"
1651
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1652
with contextlib.closing(socket.socket()) as s:
1653
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1654
struct.pack(str("16s16x"),
1656
interface_index = struct.unpack(str("I"),
1658
return interface_index
1659
return if_nametoindex(interface)
1662
def daemon(nochdir = False, noclose = False):
1663
"""See daemon(3). Standard BSD Unix function.
1665
This should really exist as os.daemon, but it doesn't (yet)."""
1674
# Close all standard open file descriptors
1675
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1676
if not stat.S_ISCHR(os.fstat(null).st_mode):
1677
raise OSError(errno.ENODEV,
1678
"%s not a character device"
1680
os.dup2(null, sys.stdin.fileno())
1681
os.dup2(null, sys.stdout.fileno())
1682
os.dup2(null, sys.stderr.fileno())
1689
##################################################################
1690
# Parsing of options, both command line and config file
1692
parser = argparse.ArgumentParser()
1693
parser.add_argument("-v", "--version", action="version",
1694
version = "%%(prog)s %s" % version,
1695
help="show version number and exit")
1696
parser.add_argument("-i", "--interface", metavar="IF",
1697
help="Bind to interface IF")
1698
parser.add_argument("-a", "--address",
1699
help="Address to listen for requests on")
1700
parser.add_argument("-p", "--port", type=int,
1701
help="Port number to receive requests on")
1702
parser.add_argument("--check", action="store_true",
1703
help="Run self-test")
1704
parser.add_argument("--debug", action="store_true",
1705
help="Debug mode; run in foreground and log"
1707
parser.add_argument("--debuglevel", metavar="LEVEL",
1708
help="Debug level for stdout output")
1709
parser.add_argument("--priority", help="GnuTLS"
1710
" priority string (see GnuTLS documentation)")
1711
parser.add_argument("--servicename",
1712
metavar="NAME", help="Zeroconf service name")
1713
parser.add_argument("--configdir",
1714
default="/etc/mandos", metavar="DIR",
1715
help="Directory to search for configuration"
1717
parser.add_argument("--no-dbus", action="store_false",
1718
dest="use_dbus", help="Do not provide D-Bus"
1719
" system bus interface")
1720
parser.add_argument("--no-ipv6", action="store_false",
1721
dest="use_ipv6", help="Do not use IPv6")
1722
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()
1724
567
if options.check:
1726
569
doctest.testmod()
1729
# Default values for config file for server-global settings
1730
server_defaults = { "interface": "",
1735
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1736
"servicename": "Mandos",
1742
# Parse config file for server-global settings
1743
server_config = configparser.SafeConfigParser(server_defaults)
1745
server_config.read(os.path.join(options.configdir,
1747
# Convert the SafeConfigParser object to a dict
1748
server_settings = server_config.defaults()
1749
# Use the appropriate methods on the non-string config options
1750
for option in ("debug", "use_dbus", "use_ipv6"):
1751
server_settings[option] = server_config.getboolean("DEFAULT",
1753
if server_settings["port"]:
1754
server_settings["port"] = server_config.getint("DEFAULT",
1758
# Override the settings from the config file with command line
1760
for option in ("interface", "address", "port", "debug",
1761
"priority", "servicename", "configdir",
1762
"use_dbus", "use_ipv6", "debuglevel"):
1763
value = getattr(options, option)
1764
if value is not None:
1765
server_settings[option] = value
1767
# Force all strings to be unicode
1768
for option in server_settings.keys():
1769
if type(server_settings[option]) is str:
1770
server_settings[option] = unicode(server_settings[option])
1771
# Now we have our good server settings in "server_settings"
1773
##################################################################
1776
debug = server_settings["debug"]
1777
debuglevel = server_settings["debuglevel"]
1778
use_dbus = server_settings["use_dbus"]
1779
use_ipv6 = server_settings["use_ipv6"]
1781
if server_settings["servicename"] != "Mandos":
1782
syslogger.setFormatter(logging.Formatter
1783
('Mandos (%s) [%%(process)d]:'
1784
' %%(levelname)s: %%(message)s'
1785
% server_settings["servicename"]))
1787
# Parse config file with clients
1788
client_defaults = { "timeout": "5m",
1789
"extended_timeout": "15m",
1791
"checker": "fping -q -- %%(host)s",
1793
"approval_delay": "0s",
1794
"approval_duration": "1s",
1796
client_config = configparser.SafeConfigParser(client_defaults)
1797
client_config.read(os.path.join(server_settings["configdir"],
1800
global mandos_dbus_service
1801
mandos_dbus_service = None
1803
tcp_server = MandosServer((server_settings["address"],
1804
server_settings["port"]),
1806
interface=(server_settings["interface"]
1810
server_settings["priority"],
1813
pidfilename = "/var/run/mandos.pid"
1815
pidfile = open(pidfilename, "w")
1817
logger.error("Could not open file %r", pidfilename)
1820
uid = pwd.getpwnam("_mandos").pw_uid
1821
gid = pwd.getpwnam("_mandos").pw_gid
1824
uid = pwd.getpwnam("mandos").pw_uid
1825
gid = pwd.getpwnam("mandos").pw_gid
1828
uid = pwd.getpwnam("nobody").pw_uid
1829
gid = pwd.getpwnam("nobody").pw_gid
1836
except OSError as error:
1837
if error[0] != errno.EPERM:
1840
if not debug and not debuglevel:
1841
syslogger.setLevel(logging.WARNING)
1842
console.setLevel(logging.WARNING)
1844
level = getattr(logging, debuglevel.upper())
1845
syslogger.setLevel(level)
1846
console.setLevel(level)
1849
# Enable all possible GnuTLS debugging
1851
# "Use a log level over 10 to enable all debugging options."
1853
gnutls.library.functions.gnutls_global_set_log_level(11)
1855
@gnutls.library.types.gnutls_log_func
1856
def debug_gnutls(level, string):
1857
logger.debug("GnuTLS: %s", string[:-1])
1859
(gnutls.library.functions
1860
.gnutls_global_set_log_function(debug_gnutls))
1862
# Redirect stdin so all checkers get /dev/null
1863
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1864
os.dup2(null, sys.stdin.fileno())
1868
# No console logging
1869
logger.removeHandler(console)
1871
# Need to fork before connecting to D-Bus
1873
# Close all input and output, do double fork, etc.
1877
# 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
1878
589
DBusGMainLoop(set_as_default=True )
1879
590
main_loop = gobject.MainLoop()
1880
591
bus = dbus.SystemBus()
1881
# End of Avahi example code
1884
bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
1885
bus, do_not_queue=True)
1886
except dbus.exceptions.NameExistsException as e:
1887
logger.error(unicode(e) + ", disabling D-Bus")
1889
server_settings["use_dbus"] = False
1890
tcp_server.use_dbus = False
1891
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1892
service = AvahiService(name = server_settings["servicename"],
1893
servicetype = "_mandos._tcp",
1894
protocol = protocol, bus = bus)
1895
if server_settings["interface"]:
1896
service.interface = (if_nametoindex
1897
(str(server_settings["interface"])))
1899
global multiprocessing_manager
1900
multiprocessing_manager = multiprocessing.Manager()
1902
client_class = Client
1904
client_class = functools.partial(ClientDBus, bus = bus)
1905
def client_config_items(config, section):
1906
special_settings = {
1907
"approved_by_default":
1908
lambda: config.getboolean(section,
1909
"approved_by_default"),
1911
for name, value in config.items(section):
1913
yield (name, special_settings[name]())
1917
tcp_server.clients.update(set(
1918
client_class(name = section,
1919
config= dict(client_config_items(
1920
client_config, section)))
1921
for section in client_config.sections()))
1922
if not tcp_server.clients:
1923
logger.warning("No clients defined")
1929
pidfile.write(str(pid) + "\n".encode("utf-8"))
1932
logger.error("Could not write to file %r with PID %d",
1935
# "pidfile" was never created
1939
signal.signal(signal.SIGINT, signal.SIG_IGN)
1941
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1942
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1945
class MandosDBusService(dbus.service.Object):
1946
"""A D-Bus proxy object"""
1948
dbus.service.Object.__init__(self, bus, "/")
1949
_interface = "se.bsnet.fukt.Mandos"
1951
@dbus.service.signal(_interface, signature="o")
1952
def ClientAdded(self, objpath):
1956
@dbus.service.signal(_interface, signature="ss")
1957
def ClientNotFound(self, fingerprint, address):
1961
@dbus.service.signal(_interface, signature="os")
1962
def ClientRemoved(self, objpath, name):
1966
@dbus.service.method(_interface, out_signature="ao")
1967
def GetAllClients(self):
1969
return dbus.Array(c.dbus_object_path
1970
for c in tcp_server.clients)
1972
@dbus.service.method(_interface,
1973
out_signature="a{oa{sv}}")
1974
def GetAllClientsWithProperties(self):
1976
return dbus.Dictionary(
1977
((c.dbus_object_path, c.GetAll(""))
1978
for c in tcp_server.clients),
1981
@dbus.service.method(_interface, in_signature="o")
1982
def RemoveClient(self, object_path):
1984
for c in tcp_server.clients:
1985
if c.dbus_object_path == object_path:
1986
tcp_server.clients.remove(c)
1987
c.remove_from_connection()
1988
# Don't signal anything except ClientRemoved
1989
c.disable(quiet=True)
1991
self.ClientRemoved(object_path, c.name)
1993
raise KeyError(object_path)
1997
mandos_dbus_service = MandosDBusService()
2000
"Cleanup function; run on exit"
2003
while tcp_server.clients:
2004
client = tcp_server.clients.pop()
2006
client.remove_from_connection()
2007
client.disable_hook = None
2008
# Don't signal anything except ClientRemoved
2009
client.disable(quiet=True)
2012
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2015
atexit.register(cleanup)
2017
for client in tcp_server.clients:
2020
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2024
tcp_server.server_activate()
2026
# Find out what port we got
2027
service.port = tcp_server.socket.getsockname()[1]
2029
logger.info("Now listening on address %r, port %d,"
2030
" flowinfo %d, scope_id %d"
2031
% tcp_server.socket.getsockname())
2033
logger.info("Now listening on address %r, port %d"
2034
% tcp_server.socket.getsockname())
2036
#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:],
2039
# From the Avahi example code
2042
except dbus.exceptions.DBusException as error:
2043
logger.critical("DBusException: %s", error)
2046
# End of Avahi example code
2048
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2049
lambda *args, **kwargs:
2050
(tcp_server.handle_request
2051
(*args[2:], **kwargs) or True))
2053
logger.debug("Starting main loop")
2055
except AvahiError as error:
2056
logger.critical("AvahiError: %s", error)
2059
636
except KeyboardInterrupt:
2061
print("", file=sys.stderr)
2062
logger.debug("Server received KeyboardInterrupt")
2063
logger.debug("Server exiting")
2064
# Must run before the D-Bus bus name gets deregistered
2068
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