45
44
import gnutls.library.functions
46
45
import gnutls.library.constants
47
46
import gnutls.library.types
48
import ConfigParser as configparser
57
57
import logging.handlers
63
import cPickle as pickle
64
import multiprocessing
71
62
from dbus.mainloop.glib import DBusGMainLoop
74
import xml.dom.minidom
78
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
79
except AttributeError:
81
from IN import SO_BINDTODEVICE
83
SO_BINDTODEVICE = None
88
logger = logging.getLogger()
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:'
65
# Brief description of the operation of this program:
67
# This server announces itself as a Zeroconf service. Connecting
68
# clients use the TLS protocol, with the unusual quirk that this
69
# server program acts as a TLS "client" while the connecting clients
70
# acts as a TLS "server". The clients (acting as a TLS "server") must
71
# supply an OpenPGP certificate, and the fingerprint of this
72
# certificate is used by this server to look up (in a list read from a
73
# file at start time) which binary blob to give the client. No other
74
# authentication or authorization is done by this server.
77
logger = logging.Logger('mandos')
78
syslogger = logging.handlers.SysLogHandler\
79
(facility = logging.handlers.SysLogHandler.LOG_DAEMON)
80
syslogger.setFormatter(logging.Formatter\
81
('%(levelname)s: %(message)s'))
95
82
logger.addHandler(syslogger)
97
console = logging.StreamHandler()
98
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
102
logger.addHandler(console)
105
class AvahiError(Exception):
106
def __init__(self, value, *args, **kwargs):
108
super(AvahiError, self).__init__(value, *args, **kwargs)
109
def __unicode__(self):
110
return unicode(repr(self.value))
112
class AvahiServiceError(AvahiError):
115
class AvahiGroupError(AvahiError):
119
class AvahiService(object):
120
"""An Avahi (Zeroconf) service.
123
interface: integer; avahi.IF_UNSPEC or an interface index.
124
Used to optionally bind to the specified interface.
125
name: string; Example: 'Mandos'
126
type: string; Example: '_mandos._tcp'.
127
See <http://www.dns-sd.org/ServiceTypes.html>
128
port: integer; what port to announce
129
TXT: list of strings; TXT record for the service
130
domain: string; Domain to publish on, default to .local if empty.
131
host: string; Host to publish records for, default is localhost
132
max_renames: integer; maximum number of renames
133
rename_count: integer; counter so we only rename after collisions
134
a sensible number of times
135
group: D-Bus Entry Group
137
bus: dbus.SystemBus()
139
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
140
servicetype = None, port = None, TXT = None,
141
domain = "", host = "", max_renames = 32768,
142
protocol = avahi.PROTO_UNSPEC, bus = None):
143
self.interface = interface
145
self.type = servicetype
147
self.TXT = TXT if TXT is not None else []
150
self.rename_count = 0
151
self.max_renames = max_renames
152
self.protocol = protocol
153
self.group = None # our entry group
156
self.entry_group_state_changed_match = None
158
"""Derived from the Avahi example code"""
159
if self.rename_count >= self.max_renames:
160
logger.critical("No suitable Zeroconf service name found"
161
" after %i retries, exiting.",
163
raise AvahiServiceError("Too many renames")
164
self.name = unicode(self.server
165
.GetAlternativeServiceName(self.name))
166
logger.info("Changing Zeroconf service name to %r ...",
171
except dbus.exceptions.DBusException as error:
172
logger.critical("DBusException: %s", error)
175
self.rename_count += 1
177
"""Derived from the Avahi example code"""
178
if self.entry_group_state_changed_match is not None:
179
self.entry_group_state_changed_match.remove()
180
self.entry_group_state_changed_match = None
181
if self.group is not None:
184
"""Derived from the Avahi example code"""
186
if self.group is None:
187
self.group = dbus.Interface(
188
self.bus.get_object(avahi.DBUS_NAME,
189
self.server.EntryGroupNew()),
190
avahi.DBUS_INTERFACE_ENTRY_GROUP)
191
self.entry_group_state_changed_match = (
192
self.group.connect_to_signal(
193
'StateChanged', self.entry_group_state_changed))
194
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
195
self.name, self.type)
196
self.group.AddService(
199
dbus.UInt32(0), # flags
200
self.name, self.type,
201
self.domain, self.host,
202
dbus.UInt16(self.port),
203
avahi.string_array_to_txt_array(self.TXT))
205
def entry_group_state_changed(self, state, error):
206
"""Derived from the Avahi example code"""
207
logger.debug("Avahi entry group state change: %i", state)
209
if state == avahi.ENTRY_GROUP_ESTABLISHED:
210
logger.debug("Zeroconf service established.")
211
elif state == avahi.ENTRY_GROUP_COLLISION:
212
logger.info("Zeroconf service name collision.")
214
elif state == avahi.ENTRY_GROUP_FAILURE:
215
logger.critical("Avahi: Error in group state changed %s",
217
raise AvahiGroupError("State changed: %s"
220
"""Derived from the Avahi example code"""
221
if self.group is not None:
224
except (dbus.exceptions.UnknownMethodException,
225
dbus.exceptions.DBusException) as e:
229
def server_state_changed(self, state, error=None):
230
"""Derived from the Avahi example code"""
231
logger.debug("Avahi server state change: %i", state)
232
bad_states = { avahi.SERVER_INVALID:
233
"Zeroconf server invalid",
234
avahi.SERVER_REGISTERING: None,
235
avahi.SERVER_COLLISION:
236
"Zeroconf server name collision",
237
avahi.SERVER_FAILURE:
238
"Zeroconf server failure" }
239
if state in bad_states:
240
if bad_states[state] is not None:
242
logger.error(bad_states[state])
244
logger.error(bad_states[state] + ": %r", error)
246
elif state == avahi.SERVER_RUNNING:
250
logger.debug("Unknown state: %r", state)
252
logger.debug("Unknown state: %r: %r", state, error)
254
"""Derived from the Avahi example code"""
255
if self.server is None:
256
self.server = dbus.Interface(
257
self.bus.get_object(avahi.DBUS_NAME,
258
avahi.DBUS_PATH_SERVER,
259
follow_name_owner_changes=True),
260
avahi.DBUS_INTERFACE_SERVER)
261
self.server.connect_to_signal("StateChanged",
262
self.server_state_changed)
263
self.server_state_changed(self.server.GetState())
265
class AvahiServiceToSyslog(AvahiService):
267
"""Add the new name to the syslog messages"""
268
ret = AvahiService.rename(self)
269
syslogger.setFormatter(logging.Formatter
270
('Mandos (%s) [%%(process)d]:'
271
' %%(levelname)s: %%(message)s'
275
def _timedelta_to_milliseconds(td):
276
"Convert a datetime.timedelta() to milliseconds"
277
return ((td.days * 24 * 60 * 60 * 1000)
278
+ (td.seconds * 1000)
279
+ (td.microseconds // 1000))
85
# This variable is used to optionally bind to a specified interface.
86
# It is a global variable to fit in with the other variables from the
88
serviceInterface = avahi.IF_UNSPEC
89
# From the Avahi example code:
90
serviceName = "Mandos"
91
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
92
servicePort = None # Not known at startup
93
serviceTXT = [] # TXT record for the service
94
domain = "" # Domain to publish on, default to .local
95
host = "" # Host to publish records for, default to localhost
96
group = None #our entry group
97
rename_count = 12 # Counter so we only rename after collisions a
98
# sensible number of times
99
# End of Avahi example code
281
102
class Client(object):
282
103
"""A representation of a client host served by this server.
285
_approved: bool(); 'None' if not yet approved/disapproved
286
approval_delay: datetime.timedelta(); Time to wait for approval
287
approval_duration: datetime.timedelta(); Duration of one approval
288
checker: subprocess.Popen(); a running checker process used
289
to see if the client lives.
290
'None' if no process is running.
291
checker_callback_tag: a gobject event source tag, or None
292
checker_command: string; External command which is run to check
293
if client lives. %() expansions are done at
105
name: string; from the config file, used in log messages
106
fingerprint: string (40 or 32 hexadecimal digits); used to
107
uniquely identify the client
108
secret: bytestring; sent verbatim (over TLS) to client
109
fqdn: string (FQDN); available for use by the checker command
110
created: datetime.datetime()
111
last_seen: datetime.datetime() or None if not yet seen
112
timeout: datetime.timedelta(); How long from last_seen until
113
this client is invalid
114
interval: datetime.timedelta(); How often to start a new checker
115
stop_hook: If set, called by stop() as stop_hook(self)
116
checker: subprocess.Popen(); a running checker process used
117
to see if the client lives.
118
Is None if no process is running.
119
checker_initiator_tag: a gobject event source tag, or None
120
stop_initiator_tag: - '' -
121
checker_callback_tag: - '' -
122
checker_command: string; External command which is run to check if
123
client lives. %()s expansions are done at
294
124
runtime with vars(self) as dict, so that for
295
125
instance %(name)s can be used in the command.
296
checker_initiator_tag: a gobject event source tag, or None
297
created: datetime.datetime(); (UTC) object creation
298
current_checker_command: string; current running checker_command
299
disable_hook: If set, called by disable() as disable_hook(self)
300
disable_initiator_tag: a gobject event source tag, or None
302
fingerprint: string (40 or 32 hexadecimal digits); used to
303
uniquely identify the client
304
host: string; available for use by the checker command
305
interval: datetime.timedelta(); How often to start a new checker
306
last_approval_request: datetime.datetime(); (UTC) or None
307
last_checked_ok: datetime.datetime(); (UTC) or None
308
last_enabled: datetime.datetime(); (UTC)
309
name: string; from the config file, used in log messages and
311
secret: bytestring; sent verbatim (over TLS) to client
312
timeout: datetime.timedelta(); How long from last_checked_ok
313
until this client is disabled
314
extended_timeout: extra long timeout when password has been sent
315
runtime_expansions: Allowed attributes for runtime expansion.
316
expires: datetime.datetime(); time (UTC) when a client will be
127
_timeout: Real variable for 'timeout'
128
_interval: Real variable for 'interval'
129
_timeout_milliseconds: Used by gobject.timeout_add()
130
_interval_milliseconds: - '' -
320
runtime_expansions = ("approval_delay", "approval_duration",
321
"created", "enabled", "fingerprint",
322
"host", "interval", "last_checked_ok",
323
"last_enabled", "name", "timeout")
325
def timeout_milliseconds(self):
326
"Return the 'timeout' attribute in milliseconds"
327
return _timedelta_to_milliseconds(self.timeout)
329
def extended_timeout_milliseconds(self):
330
"Return the 'extended_timeout' attribute in milliseconds"
331
return _timedelta_to_milliseconds(self.extended_timeout)
333
def interval_milliseconds(self):
334
"Return the 'interval' attribute in milliseconds"
335
return _timedelta_to_milliseconds(self.interval)
337
def approval_delay_milliseconds(self):
338
return _timedelta_to_milliseconds(self.approval_delay)
340
def __init__(self, name = None, disable_hook=None, config=None):
341
"""Note: the 'checker' key in 'config' sets the
342
'checker_command' attribute and *not* the 'checker'
132
def _set_timeout(self, timeout):
133
"Setter function for 'timeout' attribute"
134
self._timeout = timeout
135
self._timeout_milliseconds = ((self.timeout.days
136
* 24 * 60 * 60 * 1000)
137
+ (self.timeout.seconds * 1000)
138
+ (self.timeout.microseconds
140
timeout = property(lambda self: self._timeout,
143
def _set_interval(self, interval):
144
"Setter function for 'interval' attribute"
145
self._interval = interval
146
self._interval_milliseconds = ((self.interval.days
147
* 24 * 60 * 60 * 1000)
148
+ (self.interval.seconds
150
+ (self.interval.microseconds
152
interval = property(lambda self: self._interval,
155
def __init__(self, name=None, options=None, stop_hook=None,
156
fingerprint=None, secret=None, secfile=None,
157
fqdn=None, timeout=None, interval=-1, checker=None):
158
"""Note: the 'checker' argument sets the 'checker_command'
159
attribute and not the 'checker' attribute.."""
347
logger.debug("Creating client %r", self.name)
348
# Uppercase and remove spaces from fingerprint for later
349
# comparison purposes with return value from the fingerprint()
351
self.fingerprint = (config["fingerprint"].upper()
353
logger.debug(" Fingerprint: %s", self.fingerprint)
354
if "secret" in config:
355
self.secret = config["secret"].decode("base64")
356
elif "secfile" in config:
357
with open(os.path.expanduser(os.path.expandvars
358
(config["secfile"])),
360
self.secret = secfile.read()
362
raise TypeError("No secret or secfile for client %s"
364
self.host = config.get("host", "")
365
self.created = datetime.datetime.utcnow()
367
self.last_approval_request = None
368
self.last_enabled = None
369
self.last_checked_ok = None
370
self.timeout = string_to_delta(config["timeout"])
371
self.extended_timeout = string_to_delta(config
372
["extended_timeout"])
373
self.interval = string_to_delta(config["interval"])
374
self.disable_hook = disable_hook
161
# Uppercase and remove spaces from fingerprint
162
# for later comparison purposes with return value of
163
# the fingerprint() function
164
self.fingerprint = fingerprint.upper().replace(u" ", u"")
166
self.secret = secret.decode(u"base64")
169
self.secret = sf.read()
172
raise RuntimeError(u"No secret or secfile for client %s"
174
self.fqdn = fqdn # string
175
self.created = datetime.datetime.now()
176
self.last_seen = None
178
self.timeout = options.timeout
180
self.timeout = string_to_delta(timeout)
182
self.interval = options.interval
184
self.interval = string_to_delta(interval)
185
self.stop_hook = stop_hook
375
186
self.checker = None
376
187
self.checker_initiator_tag = None
377
self.disable_initiator_tag = None
188
self.stop_initiator_tag = None
379
189
self.checker_callback_tag = None
380
self.checker_command = config["checker"]
381
self.current_checker_command = None
382
self.last_connect = None
383
self._approved = None
384
self.approved_by_default = config.get("approved_by_default",
386
self.approvals_pending = 0
387
self.approval_delay = string_to_delta(
388
config["approval_delay"])
389
self.approval_duration = string_to_delta(
390
config["approval_duration"])
391
self.changedstate = (multiprocessing_manager
392
.Condition(multiprocessing_manager
395
def send_changedstate(self):
396
self.changedstate.acquire()
397
self.changedstate.notify_all()
398
self.changedstate.release()
190
self.check_command = checker
401
192
"""Start this client's checker and timeout hooks"""
402
if getattr(self, "enabled", False):
405
self.send_changedstate()
406
193
# Schedule a new checker to be started an 'interval' from now,
407
194
# and every interval from then on.
408
self.checker_initiator_tag = (gobject.timeout_add
409
(self.interval_milliseconds(),
411
# Schedule a disable() when 'timeout' has passed
412
self.expires = datetime.datetime.utcnow() + self.timeout
413
self.disable_initiator_tag = (gobject.timeout_add
414
(self.timeout_milliseconds(),
417
self.last_enabled = datetime.datetime.utcnow()
195
self.checker_initiator_tag = gobject.timeout_add\
196
(self._interval_milliseconds,
418
198
# Also start a new checker *right now*.
419
199
self.start_checker()
421
def disable(self, quiet=True):
422
"""Disable this client."""
423
if not getattr(self, "enabled", False):
200
# Schedule a stop() when 'timeout' has passed
201
self.stop_initiator_tag = gobject.timeout_add\
202
(self._timeout_milliseconds,
206
The possibility that this client might be restarted is left
207
open, but not currently used."""
208
# If this client doesn't have a secret, it is already stopped.
210
logger.debug(u"Stopping client %s", self.name)
426
self.send_changedstate()
428
logger.info("Disabling client %s", self.name)
429
if getattr(self, "disable_initiator_tag", False):
430
gobject.source_remove(self.disable_initiator_tag)
431
self.disable_initiator_tag = None
433
if getattr(self, "checker_initiator_tag", False):
214
if hasattr(self, "stop_initiator_tag") \
215
and self.stop_initiator_tag:
216
gobject.source_remove(self.stop_initiator_tag)
217
self.stop_initiator_tag = None
218
if hasattr(self, "checker_initiator_tag") \
219
and self.checker_initiator_tag:
434
220
gobject.source_remove(self.checker_initiator_tag)
435
221
self.checker_initiator_tag = None
436
222
self.stop_checker()
437
if self.disable_hook:
438
self.disable_hook(self)
440
225
# Do not run this again if called by a gobject.timeout_add
443
227
def __del__(self):
444
self.disable_hook = None
447
def checker_callback(self, pid, condition, command):
228
self.stop_hook = None
230
def checker_callback(self, pid, condition):
448
231
"""The checker has completed, so take appropriate actions."""
232
now = datetime.datetime.now()
449
233
self.checker_callback_tag = None
450
234
self.checker = None
451
if os.WIFEXITED(condition):
452
exitstatus = os.WEXITSTATUS(condition)
454
logger.info("Checker for %(name)s succeeded",
458
logger.info("Checker for %(name)s failed",
461
logger.warning("Checker for %(name)s crashed?",
235
if os.WIFEXITED(condition) \
236
and (os.WEXITSTATUS(condition) == 0):
237
logger.debug(u"Checker for %(name)s succeeded",
240
gobject.source_remove(self.stop_initiator_tag)
241
self.stop_initiator_tag = gobject.timeout_add\
242
(self._timeout_milliseconds,
244
elif not os.WIFEXITED(condition):
245
logger.warning(u"Checker for %(name)s crashed?",
464
def checked_ok(self, timeout=None):
465
"""Bump up the timeout for this client.
467
This should only be called when the client has been seen,
471
timeout = self.timeout
472
self.last_checked_ok = datetime.datetime.utcnow()
473
if self.disable_initiator_tag is not None:
474
gobject.source_remove(self.disable_initiator_tag)
475
if getattr(self, "enabled", False):
476
self.disable_initiator_tag = (gobject.timeout_add
477
(_timedelta_to_milliseconds
478
(timeout), self.disable))
479
self.expires = datetime.datetime.utcnow() + timeout
481
def need_approval(self):
482
self.last_approval_request = datetime.datetime.utcnow()
248
logger.debug(u"Checker for %(name)s failed",
484
250
def start_checker(self):
485
251
"""Start a new checker subprocess if one is not running.
487
252
If a checker already exists, leave it running and do
489
254
# The reason for not killing a running checker is that if we
492
257
# client would inevitably timeout, since no checker would get
493
258
# a chance to run to completion. If we instead leave running
494
259
# checkers alone, the checker would have to take more time
495
# than 'timeout' for the client to be disabled, which is as it
498
# If a checker exists, make sure it is not a zombie
500
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
501
except (AttributeError, OSError) as error:
502
if (isinstance(error, OSError)
503
and error.errno != errno.ECHILD):
507
logger.warning("Checker was a zombie")
508
gobject.source_remove(self.checker_callback_tag)
509
self.checker_callback(pid, status,
510
self.current_checker_command)
511
# Start a new checker if needed
260
# than 'timeout' for the client to be declared invalid, which
261
# is as it should be.
512
262
if self.checker is None:
514
# In case checker_command has exactly one % operator
515
command = self.checker_command % self.host
264
command = self.check_command % self.fqdn
516
265
except TypeError:
517
# Escape attributes for the shell
518
escaped_attrs = dict(
520
re.escape(unicode(str(getattr(self, attr, "")),
524
self.runtime_expansions)
266
escaped_attrs = dict((key, re.escape(str(val)))
268
vars(self).iteritems())
527
command = self.checker_command % escaped_attrs
528
except TypeError as error:
529
logger.error('Could not format string "%s":'
530
' %s', self.checker_command, error)
270
command = self.check_command % escaped_attrs
271
except TypeError, error:
272
logger.critical(u'Could not format string "%s":'
273
u' %s', self.check_command, error)
531
274
return True # Try again later
532
self.current_checker_command = command
534
logger.info("Starting checker %r for %s",
536
# We don't need to redirect stdout and stderr, since
537
# in normal mode, that is already done by daemon(),
538
# and in debug mode we don't want to. (Stdin is
539
# always replaced by /dev/null.)
540
self.checker = subprocess.Popen(command,
543
self.checker_callback_tag = (gobject.child_watch_add
545
self.checker_callback,
547
# The checker may have completed before the gobject
548
# watch was added. Check for this.
549
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
551
gobject.source_remove(self.checker_callback_tag)
552
self.checker_callback(pid, status, command)
553
except OSError as error:
554
logger.error("Failed to start subprocess: %s",
276
logger.debug(u"Starting checker %r for %s",
278
self.checker = subprocess.\
280
close_fds=True, shell=True,
282
self.checker_callback_tag = gobject.child_watch_add\
284
self.checker_callback)
285
except subprocess.OSError, error:
286
logger.error(u"Failed to start subprocess: %s",
556
288
# Re-run this periodically if run by gobject.timeout_add
559
290
def stop_checker(self):
560
291
"""Force the checker process, if any, to stop."""
561
292
if self.checker_callback_tag:
562
293
gobject.source_remove(self.checker_callback_tag)
563
294
self.checker_callback_tag = None
564
if getattr(self, "checker", None) is None:
295
if not hasattr(self, "checker") or self.checker is None:
566
297
logger.debug("Stopping checker for %(name)s", vars(self))
568
299
os.kill(self.checker.pid, signal.SIGTERM)
570
301
#if self.checker.poll() is None:
571
302
# os.kill(self.checker.pid, signal.SIGKILL)
572
except OSError as error:
573
if error.errno != errno.ESRCH: # No such process
303
except OSError, error:
304
if error.errno != errno.ESRCH:
575
306
self.checker = None
578
def dbus_service_property(dbus_interface, signature="v",
579
access="readwrite", byte_arrays=False):
580
"""Decorators for marking methods of a DBusObjectWithProperties to
581
become properties on the D-Bus.
583
The decorated method will be called with no arguments by "Get"
584
and with one argument by "Set".
586
The parameters, where they are supported, are the same as
587
dbus.service.method, except there is only "signature", since the
588
type from Get() and the type sent to Set() is the same.
590
# Encoding deeply encoded byte arrays is not supported yet by the
591
# "Set" method, so we fail early here:
592
if byte_arrays and signature != "ay":
593
raise ValueError("Byte arrays not supported for non-'ay'"
594
" signature %r" % signature)
596
func._dbus_is_property = True
597
func._dbus_interface = dbus_interface
598
func._dbus_signature = signature
599
func._dbus_access = access
600
func._dbus_name = func.__name__
601
if func._dbus_name.endswith("_dbus_property"):
602
func._dbus_name = func._dbus_name[:-14]
603
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
608
class DBusPropertyException(dbus.exceptions.DBusException):
609
"""A base class for D-Bus property-related exceptions
611
def __unicode__(self):
612
return unicode(str(self))
615
class DBusPropertyAccessException(DBusPropertyException):
616
"""A property's access permissions disallows an operation.
621
class DBusPropertyNotFound(DBusPropertyException):
622
"""An attempt was made to access a non-existing property.
627
class DBusObjectWithProperties(dbus.service.Object):
628
"""A D-Bus object with properties.
630
Classes inheriting from this can use the dbus_service_property
631
decorator to expose methods as D-Bus properties. It exposes the
632
standard Get(), Set(), and GetAll() methods on the D-Bus.
636
def _is_dbus_property(obj):
637
return getattr(obj, "_dbus_is_property", False)
639
def _get_all_dbus_properties(self):
640
"""Returns a generator of (name, attribute) pairs
642
return ((prop.__get__(self)._dbus_name, prop.__get__(self))
643
for cls in self.__class__.__mro__
645
inspect.getmembers(cls, self._is_dbus_property))
647
def _get_dbus_property(self, interface_name, property_name):
648
"""Returns a bound method if one exists which is a D-Bus
649
property with the specified name and interface.
651
for cls in self.__class__.__mro__:
652
for name, value in (inspect.getmembers
653
(cls, self._is_dbus_property)):
654
if (value._dbus_name == property_name
655
and value._dbus_interface == interface_name):
656
return value.__get__(self)
659
raise DBusPropertyNotFound(self.dbus_object_path + ":"
660
+ interface_name + "."
663
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
665
def Get(self, interface_name, property_name):
666
"""Standard D-Bus property Get() method, see D-Bus standard.
668
prop = self._get_dbus_property(interface_name, property_name)
669
if prop._dbus_access == "write":
670
raise DBusPropertyAccessException(property_name)
672
if not hasattr(value, "variant_level"):
674
return type(value)(value, variant_level=value.variant_level+1)
676
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
677
def Set(self, interface_name, property_name, value):
678
"""Standard D-Bus property Set() method, see D-Bus standard.
680
prop = self._get_dbus_property(interface_name, property_name)
681
if prop._dbus_access == "read":
682
raise DBusPropertyAccessException(property_name)
683
if prop._dbus_get_args_options["byte_arrays"]:
684
# The byte_arrays option is not supported yet on
685
# signatures other than "ay".
686
if prop._dbus_signature != "ay":
688
value = dbus.ByteArray(''.join(unichr(byte)
692
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
693
out_signature="a{sv}")
694
def GetAll(self, interface_name):
695
"""Standard D-Bus property GetAll() method, see D-Bus
698
Note: Will not include properties with access="write".
701
for name, prop in self._get_all_dbus_properties():
703
and interface_name != prop._dbus_interface):
704
# Interface non-empty but did not match
706
# Ignore write-only properties
707
if prop._dbus_access == "write":
710
if not hasattr(value, "variant_level"):
713
all[name] = type(value)(value, variant_level=
714
value.variant_level+1)
715
return dbus.Dictionary(all, signature="sv")
717
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
719
path_keyword='object_path',
720
connection_keyword='connection')
721
def Introspect(self, object_path, connection):
722
"""Standard D-Bus method, overloaded to insert property tags.
724
xmlstring = dbus.service.Object.Introspect(self, object_path,
727
document = xml.dom.minidom.parseString(xmlstring)
728
def make_tag(document, name, prop):
729
e = document.createElement("property")
730
e.setAttribute("name", name)
731
e.setAttribute("type", prop._dbus_signature)
732
e.setAttribute("access", prop._dbus_access)
734
for if_tag in document.getElementsByTagName("interface"):
735
for tag in (make_tag(document, name, prop)
737
in self._get_all_dbus_properties()
738
if prop._dbus_interface
739
== if_tag.getAttribute("name")):
740
if_tag.appendChild(tag)
741
# Add the names to the return values for the
742
# "org.freedesktop.DBus.Properties" methods
743
if (if_tag.getAttribute("name")
744
== "org.freedesktop.DBus.Properties"):
745
for cn in if_tag.getElementsByTagName("method"):
746
if cn.getAttribute("name") == "Get":
747
for arg in cn.getElementsByTagName("arg"):
748
if (arg.getAttribute("direction")
750
arg.setAttribute("name", "value")
751
elif cn.getAttribute("name") == "GetAll":
752
for arg in cn.getElementsByTagName("arg"):
753
if (arg.getAttribute("direction")
755
arg.setAttribute("name", "props")
756
xmlstring = document.toxml("utf-8")
758
except (AttributeError, xml.dom.DOMException,
759
xml.parsers.expat.ExpatError) as error:
760
logger.error("Failed to override Introspection method",
765
def datetime_to_dbus (dt, variant_level=0):
766
"""Convert a UTC datetime.datetime() to a D-Bus type."""
768
return dbus.String("", variant_level = variant_level)
769
return dbus.String(dt.isoformat(),
770
variant_level=variant_level)
772
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
774
"""Applied to an empty subclass of a D-Bus object, this metaclass
775
will add additional D-Bus attributes matching a certain pattern.
777
def __new__(mcs, name, bases, attr):
778
# Go through all the base classes which could have D-Bus
779
# methods, signals, or properties in them
780
for base in (b for b in bases
781
if issubclass(b, dbus.service.Object)):
782
# Go though all attributes of the base class
783
for attrname, attribute in inspect.getmembers(base):
784
# Ignore non-D-Bus attributes, and D-Bus attributes
785
# with the wrong interface name
786
if (not hasattr(attribute, "_dbus_interface")
787
or not attribute._dbus_interface
788
.startswith("se.recompile.Mandos")):
790
# Create an alternate D-Bus interface name based on
792
alt_interface = (attribute._dbus_interface
793
.replace("se.recompile.Mandos",
794
"se.bsnet.fukt.Mandos"))
795
# Is this a D-Bus signal?
796
if getattr(attribute, "_dbus_is_signal", False):
797
# Extract the original non-method function by
799
nonmethod_func = (dict(
800
zip(attribute.func_code.co_freevars,
801
attribute.__closure__))["func"]
803
# Create a new, but exactly alike, function
804
# object, and decorate it to be a new D-Bus signal
805
# with the alternate D-Bus interface name
806
new_function = (dbus.service.signal
808
attribute._dbus_signature)
810
nonmethod_func.func_code,
811
nonmethod_func.func_globals,
812
nonmethod_func.func_name,
813
nonmethod_func.func_defaults,
814
nonmethod_func.func_closure)))
815
# Define a creator of a function to call both the
816
# old and new functions, so both the old and new
817
# signals gets sent when the function is called
818
def fixscope(func1, func2):
819
"""This function is a scope container to pass
820
func1 and func2 to the "call_both" function
821
outside of its arguments"""
822
def call_both(*args, **kwargs):
823
"""This function will emit two D-Bus
824
signals by calling func1 and func2"""
825
func1(*args, **kwargs)
826
func2(*args, **kwargs)
828
# Create the "call_both" function and add it to
830
attr[attrname] = fixscope(attribute,
832
# Is this a D-Bus method?
833
elif getattr(attribute, "_dbus_is_method", False):
834
# Create a new, but exactly alike, function
835
# object. Decorate it to be a new D-Bus method
836
# with the alternate D-Bus interface name. Add it
838
attr[attrname] = (dbus.service.method
840
attribute._dbus_in_signature,
841
attribute._dbus_out_signature)
843
(attribute.func_code,
844
attribute.func_globals,
846
attribute.func_defaults,
847
attribute.func_closure)))
848
# Is this a D-Bus property?
849
elif getattr(attribute, "_dbus_is_property", False):
850
# Create a new, but exactly alike, function
851
# object, and decorate it to be a new D-Bus
852
# property with the alternate D-Bus interface
853
# name. Add it to the class.
854
attr[attrname] = (dbus_service_property
856
attribute._dbus_signature,
857
attribute._dbus_access,
859
._dbus_get_args_options
862
(attribute.func_code,
863
attribute.func_globals,
865
attribute.func_defaults,
866
attribute.func_closure)))
867
return type.__new__(mcs, name, bases, attr)
869
class ClientDBus(Client, DBusObjectWithProperties):
870
"""A Client class using D-Bus
873
dbus_object_path: dbus.ObjectPath
874
bus: dbus.SystemBus()
877
runtime_expansions = (Client.runtime_expansions
878
+ ("dbus_object_path",))
880
# dbus.service.Object doesn't use super(), so we can't either.
882
def __init__(self, bus = None, *args, **kwargs):
883
self._approvals_pending = 0
885
Client.__init__(self, *args, **kwargs)
886
# Only now, when this client is initialized, can it show up on
888
client_object_name = unicode(self.name).translate(
891
self.dbus_object_path = (dbus.ObjectPath
892
("/clients/" + client_object_name))
893
DBusObjectWithProperties.__init__(self, self.bus,
894
self.dbus_object_path)
896
def notifychangeproperty(transform_func,
897
dbus_name, type_func=lambda x: x,
899
""" Modify a variable so that it's a property which announces
902
transform_fun: Function that takes a value and a variant_level
903
and transforms it to a D-Bus type.
904
dbus_name: D-Bus name of the variable
905
type_func: Function that transform the value before sending it
906
to the D-Bus. Default: no transform
907
variant_level: D-Bus variant level. Default: 1
909
attrname = "_{0}".format(dbus_name)
910
def setter(self, value):
911
if hasattr(self, "dbus_object_path"):
912
if (not hasattr(self, attrname) or
913
type_func(getattr(self, attrname, None))
914
!= type_func(value)):
915
dbus_value = transform_func(type_func(value),
918
self.PropertyChanged(dbus.String(dbus_name),
920
setattr(self, attrname, value)
922
return property(lambda self: getattr(self, attrname), setter)
925
expires = notifychangeproperty(datetime_to_dbus, "Expires")
926
approvals_pending = notifychangeproperty(dbus.Boolean,
929
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
930
last_enabled = notifychangeproperty(datetime_to_dbus,
932
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
933
type_func = lambda checker:
935
last_checked_ok = notifychangeproperty(datetime_to_dbus,
937
last_approval_request = notifychangeproperty(
938
datetime_to_dbus, "LastApprovalRequest")
939
approved_by_default = notifychangeproperty(dbus.Boolean,
941
approval_delay = notifychangeproperty(dbus.UInt16,
944
_timedelta_to_milliseconds)
945
approval_duration = notifychangeproperty(
946
dbus.UInt16, "ApprovalDuration",
947
type_func = _timedelta_to_milliseconds)
948
host = notifychangeproperty(dbus.String, "Host")
949
timeout = notifychangeproperty(dbus.UInt16, "Timeout",
951
_timedelta_to_milliseconds)
952
extended_timeout = notifychangeproperty(
953
dbus.UInt16, "ExtendedTimeout",
954
type_func = _timedelta_to_milliseconds)
955
interval = notifychangeproperty(dbus.UInt16,
958
_timedelta_to_milliseconds)
959
checker_command = notifychangeproperty(dbus.String, "Checker")
961
del notifychangeproperty
963
def __del__(self, *args, **kwargs):
965
self.remove_from_connection()
968
if hasattr(DBusObjectWithProperties, "__del__"):
969
DBusObjectWithProperties.__del__(self, *args, **kwargs)
970
Client.__del__(self, *args, **kwargs)
972
def checker_callback(self, pid, condition, command,
974
self.checker_callback_tag = None
976
if os.WIFEXITED(condition):
977
exitstatus = os.WEXITSTATUS(condition)
979
self.CheckerCompleted(dbus.Int16(exitstatus),
980
dbus.Int64(condition),
981
dbus.String(command))
984
self.CheckerCompleted(dbus.Int16(-1),
985
dbus.Int64(condition),
986
dbus.String(command))
988
return Client.checker_callback(self, pid, condition, command,
991
def start_checker(self, *args, **kwargs):
992
old_checker = self.checker
993
if self.checker is not None:
994
old_checker_pid = self.checker.pid
996
old_checker_pid = None
997
r = Client.start_checker(self, *args, **kwargs)
998
# Only if new checker process was started
999
if (self.checker is not None
1000
and old_checker_pid != self.checker.pid):
1002
self.CheckerStarted(self.current_checker_command)
1005
def _reset_approved(self):
1006
self._approved = None
1009
def approve(self, value=True):
1010
self.send_changedstate()
1011
self._approved = value
1012
gobject.timeout_add(_timedelta_to_milliseconds
1013
(self.approval_duration),
1014
self._reset_approved)
1017
## D-Bus methods, signals & properties
1018
_interface = "se.recompile.Mandos.Client"
1022
# CheckerCompleted - signal
1023
@dbus.service.signal(_interface, signature="nxs")
1024
def CheckerCompleted(self, exitcode, waitstatus, command):
1028
# CheckerStarted - signal
1029
@dbus.service.signal(_interface, signature="s")
1030
def CheckerStarted(self, command):
1034
# PropertyChanged - signal
1035
@dbus.service.signal(_interface, signature="sv")
1036
def PropertyChanged(self, property, value):
1040
# GotSecret - signal
1041
@dbus.service.signal(_interface)
1042
def GotSecret(self):
1044
Is sent after a successful transfer of secret from the Mandos
1045
server to mandos-client
1050
@dbus.service.signal(_interface, signature="s")
1051
def Rejected(self, reason):
1055
# NeedApproval - signal
1056
@dbus.service.signal(_interface, signature="tb")
1057
def NeedApproval(self, timeout, default):
1059
return self.need_approval()
1064
@dbus.service.method(_interface, in_signature="b")
1065
def Approve(self, value):
1068
# CheckedOK - method
1069
@dbus.service.method(_interface)
1070
def CheckedOK(self):
1074
@dbus.service.method(_interface)
1079
# StartChecker - method
1080
@dbus.service.method(_interface)
1081
def StartChecker(self):
1083
self.start_checker()
1086
@dbus.service.method(_interface)
1091
# StopChecker - method
1092
@dbus.service.method(_interface)
1093
def StopChecker(self):
1098
# ApprovalPending - property
1099
@dbus_service_property(_interface, signature="b", access="read")
1100
def ApprovalPending_dbus_property(self):
1101
return dbus.Boolean(bool(self.approvals_pending))
1103
# ApprovedByDefault - property
1104
@dbus_service_property(_interface, signature="b",
1106
def ApprovedByDefault_dbus_property(self, value=None):
1107
if value is None: # get
1108
return dbus.Boolean(self.approved_by_default)
1109
self.approved_by_default = bool(value)
1111
# ApprovalDelay - property
1112
@dbus_service_property(_interface, signature="t",
1114
def ApprovalDelay_dbus_property(self, value=None):
1115
if value is None: # get
1116
return dbus.UInt64(self.approval_delay_milliseconds())
1117
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1119
# ApprovalDuration - property
1120
@dbus_service_property(_interface, signature="t",
1122
def ApprovalDuration_dbus_property(self, value=None):
1123
if value is None: # get
1124
return dbus.UInt64(_timedelta_to_milliseconds(
1125
self.approval_duration))
1126
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1129
@dbus_service_property(_interface, signature="s", access="read")
1130
def Name_dbus_property(self):
1131
return dbus.String(self.name)
1133
# Fingerprint - property
1134
@dbus_service_property(_interface, signature="s", access="read")
1135
def Fingerprint_dbus_property(self):
1136
return dbus.String(self.fingerprint)
1139
@dbus_service_property(_interface, signature="s",
1141
def Host_dbus_property(self, value=None):
1142
if value is None: # get
1143
return dbus.String(self.host)
1146
# Created - property
1147
@dbus_service_property(_interface, signature="s", access="read")
1148
def Created_dbus_property(self):
1149
return dbus.String(datetime_to_dbus(self.created))
1151
# LastEnabled - property
1152
@dbus_service_property(_interface, signature="s", access="read")
1153
def LastEnabled_dbus_property(self):
1154
return datetime_to_dbus(self.last_enabled)
1156
# Enabled - property
1157
@dbus_service_property(_interface, signature="b",
1159
def Enabled_dbus_property(self, value=None):
1160
if value is None: # get
1161
return dbus.Boolean(self.enabled)
1167
# LastCheckedOK - property
1168
@dbus_service_property(_interface, signature="s",
1170
def LastCheckedOK_dbus_property(self, value=None):
1171
if value is not None:
1174
return datetime_to_dbus(self.last_checked_ok)
1176
# Expires - property
1177
@dbus_service_property(_interface, signature="s", access="read")
1178
def Expires_dbus_property(self):
1179
return datetime_to_dbus(self.expires)
1181
# LastApprovalRequest - property
1182
@dbus_service_property(_interface, signature="s", access="read")
1183
def LastApprovalRequest_dbus_property(self):
1184
return datetime_to_dbus(self.last_approval_request)
1186
# Timeout - property
1187
@dbus_service_property(_interface, signature="t",
1189
def Timeout_dbus_property(self, value=None):
1190
if value is None: # get
1191
return dbus.UInt64(self.timeout_milliseconds())
1192
self.timeout = datetime.timedelta(0, 0, 0, value)
1193
if getattr(self, "disable_initiator_tag", None) is None:
1195
# Reschedule timeout
1196
gobject.source_remove(self.disable_initiator_tag)
1197
self.disable_initiator_tag = None
1199
time_to_die = _timedelta_to_milliseconds((self
1204
if time_to_die <= 0:
1205
# The timeout has passed
1208
self.expires = (datetime.datetime.utcnow()
1209
+ datetime.timedelta(milliseconds =
1211
self.disable_initiator_tag = (gobject.timeout_add
1212
(time_to_die, self.disable))
1214
# ExtendedTimeout - property
1215
@dbus_service_property(_interface, signature="t",
1217
def ExtendedTimeout_dbus_property(self, value=None):
1218
if value is None: # get
1219
return dbus.UInt64(self.extended_timeout_milliseconds())
1220
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1222
# Interval - property
1223
@dbus_service_property(_interface, signature="t",
1225
def Interval_dbus_property(self, value=None):
1226
if value is None: # get
1227
return dbus.UInt64(self.interval_milliseconds())
1228
self.interval = datetime.timedelta(0, 0, 0, value)
1229
if getattr(self, "checker_initiator_tag", None) is None:
1231
# Reschedule checker run
1232
gobject.source_remove(self.checker_initiator_tag)
1233
self.checker_initiator_tag = (gobject.timeout_add
1234
(value, self.start_checker))
1235
self.start_checker() # Start one now, too
1237
# Checker - property
1238
@dbus_service_property(_interface, signature="s",
1240
def Checker_dbus_property(self, value=None):
1241
if value is None: # get
1242
return dbus.String(self.checker_command)
1243
self.checker_command = value
1245
# CheckerRunning - property
1246
@dbus_service_property(_interface, signature="b",
1248
def CheckerRunning_dbus_property(self, value=None):
1249
if value is None: # get
1250
return dbus.Boolean(self.checker is not None)
1252
self.start_checker()
1256
# ObjectPath - property
1257
@dbus_service_property(_interface, signature="o", access="read")
1258
def ObjectPath_dbus_property(self):
1259
return self.dbus_object_path # is already a dbus.ObjectPath
1262
@dbus_service_property(_interface, signature="ay",
1263
access="write", byte_arrays=True)
1264
def Secret_dbus_property(self, value):
1265
self.secret = str(value)
1270
class ProxyClient(object):
1271
def __init__(self, child_pipe, fpr, address):
1272
self._pipe = child_pipe
1273
self._pipe.send(('init', fpr, address))
1274
if not self._pipe.recv():
1277
def __getattribute__(self, name):
1278
if(name == '_pipe'):
1279
return super(ProxyClient, self).__getattribute__(name)
1280
self._pipe.send(('getattr', name))
1281
data = self._pipe.recv()
1282
if data[0] == 'data':
1284
if data[0] == 'function':
1285
def func(*args, **kwargs):
1286
self._pipe.send(('funcall', name, args, kwargs))
1287
return self._pipe.recv()[1]
1290
def __setattr__(self, name, value):
1291
if(name == '_pipe'):
1292
return super(ProxyClient, self).__setattr__(name, value)
1293
self._pipe.send(('setattr', name, value))
1295
class ClientDBusTransitional(ClientDBus):
1296
__metaclass__ = AlternateDBusNamesMetaclass
1298
class ClientHandler(socketserver.BaseRequestHandler, object):
1299
"""A class to handle client connections.
1301
Instantiated once for each connection to handle it.
307
def still_valid(self, now=None):
308
"""Has the timeout not yet passed for this client?"""
310
now = datetime.datetime.now()
311
if self.last_seen is None:
312
return now < (self.created + self.timeout)
314
return now < (self.last_seen + self.timeout)
317
def peer_certificate(session):
318
"Return the peer's OpenPGP certificate as a bytestring"
319
# If not an OpenPGP certificate...
320
if gnutls.library.functions.gnutls_certificate_type_get\
321
(session._c_object) \
322
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
323
# ...do the normal thing
324
return session.peer_certificate
325
list_size = ctypes.c_uint()
326
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
327
(session._c_object, ctypes.byref(list_size))
328
if list_size.value == 0:
331
return ctypes.string_at(cert.data, cert.size)
334
def fingerprint(openpgp):
335
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
336
# New empty GnuTLS certificate
337
crt = gnutls.library.types.gnutls_openpgp_crt_t()
338
gnutls.library.functions.gnutls_openpgp_crt_init\
340
# New GnuTLS "datum" with the OpenPGP public key
341
datum = gnutls.library.types.gnutls_datum_t\
342
(ctypes.cast(ctypes.c_char_p(openpgp),
343
ctypes.POINTER(ctypes.c_ubyte)),
344
ctypes.c_uint(len(openpgp)))
345
# Import the OpenPGP public key into the certificate
346
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
349
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
350
# New buffer for the fingerprint
351
buffer = ctypes.create_string_buffer(20)
352
buffer_length = ctypes.c_size_t()
353
# Get the fingerprint from the certificate into the buffer
354
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
355
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
356
# Deinit the certificate
357
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
358
# Convert the buffer to a Python bytestring
359
fpr = ctypes.string_at(buffer, buffer_length.value)
360
# Convert the bytestring to hexadecimal notation
361
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
365
class tcp_handler(SocketServer.BaseRequestHandler, object):
366
"""A TCP request handler class.
367
Instantiated by IPv6_TCPServer for each request to handle it.
1302
368
Note: This will run in its own forked process."""
1304
370
def handle(self):
1305
with contextlib.closing(self.server.child_pipe) as child_pipe:
1306
logger.info("TCP connection from: %s",
1307
unicode(self.client_address))
1308
logger.debug("Pipe FD: %d",
1309
self.server.child_pipe.fileno())
1311
session = (gnutls.connection
1312
.ClientSession(self.request,
1314
.X509Credentials()))
1316
# Note: gnutls.connection.X509Credentials is really a
1317
# generic GnuTLS certificate credentials object so long as
1318
# no X.509 keys are added to it. Therefore, we can use it
1319
# here despite using OpenPGP certificates.
1321
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1322
# "+AES-256-CBC", "+SHA1",
1323
# "+COMP-NULL", "+CTYPE-OPENPGP",
1325
# Use a fallback default, since this MUST be set.
1326
priority = self.server.gnutls_priority
1327
if priority is None:
1329
(gnutls.library.functions
1330
.gnutls_priority_set_direct(session._c_object,
1333
# Start communication using the Mandos protocol
1334
# Get protocol number
1335
line = self.request.makefile().readline()
1336
logger.debug("Protocol version: %r", line)
1338
if int(line.strip().split()[0]) > 1:
1340
except (ValueError, IndexError, RuntimeError) as error:
1341
logger.error("Unknown protocol version: %s", error)
1344
# Start GnuTLS connection
1347
except gnutls.errors.GNUTLSError as error:
1348
logger.warning("Handshake failed: %s", error)
1349
# Do not run session.bye() here: the session is not
1350
# established. Just abandon the request.
1352
logger.debug("Handshake succeeded")
1354
approval_required = False
1357
fpr = self.fingerprint(self.peer_certificate
1360
gnutls.errors.GNUTLSError) as error:
1361
logger.warning("Bad certificate: %s", error)
1363
logger.debug("Fingerprint: %s", fpr)
1366
client = ProxyClient(child_pipe, fpr,
1367
self.client_address)
1371
if client.approval_delay:
1372
delay = client.approval_delay
1373
client.approvals_pending += 1
1374
approval_required = True
1377
if not client.enabled:
1378
logger.info("Client %s is disabled",
1380
if self.server.use_dbus:
1382
client.Rejected("Disabled")
1385
if client._approved or not client.approval_delay:
1386
#We are approved or approval is disabled
1388
elif client._approved is None:
1389
logger.info("Client %s needs approval",
1391
if self.server.use_dbus:
1393
client.NeedApproval(
1394
client.approval_delay_milliseconds(),
1395
client.approved_by_default)
1397
logger.warning("Client %s was not approved",
1399
if self.server.use_dbus:
1401
client.Rejected("Denied")
1404
#wait until timeout or approved
1405
time = datetime.datetime.now()
1406
client.changedstate.acquire()
1407
(client.changedstate.wait
1408
(float(client._timedelta_to_milliseconds(delay)
1410
client.changedstate.release()
1411
time2 = datetime.datetime.now()
1412
if (time2 - time) >= delay:
1413
if not client.approved_by_default:
1414
logger.warning("Client %s timed out while"
1415
" waiting for approval",
1417
if self.server.use_dbus:
1419
client.Rejected("Approval timed out")
1424
delay -= time2 - time
1427
while sent_size < len(client.secret):
1429
sent = session.send(client.secret[sent_size:])
1430
except gnutls.errors.GNUTLSError as error:
1431
logger.warning("gnutls send failed")
1433
logger.debug("Sent: %d, remaining: %d",
1434
sent, len(client.secret)
1435
- (sent_size + sent))
1438
logger.info("Sending secret to %s", client.name)
1439
# bump the timeout using extended_timeout
1440
client.checked_ok(client.extended_timeout)
1441
if self.server.use_dbus:
1446
if approval_required:
1447
client.approvals_pending -= 1
1450
except gnutls.errors.GNUTLSError as error:
1451
logger.warning("GnuTLS bye failed")
1454
def peer_certificate(session):
1455
"Return the peer's OpenPGP certificate as a bytestring"
1456
# If not an OpenPGP certificate...
1457
if (gnutls.library.functions
1458
.gnutls_certificate_type_get(session._c_object)
1459
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1460
# ...do the normal thing
1461
return session.peer_certificate
1462
list_size = ctypes.c_uint(1)
1463
cert_list = (gnutls.library.functions
1464
.gnutls_certificate_get_peers
1465
(session._c_object, ctypes.byref(list_size)))
1466
if not bool(cert_list) and list_size.value != 0:
1467
raise gnutls.errors.GNUTLSError("error getting peer"
1469
if list_size.value == 0:
1472
return ctypes.string_at(cert.data, cert.size)
1475
def fingerprint(openpgp):
1476
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1477
# New GnuTLS "datum" with the OpenPGP public key
1478
datum = (gnutls.library.types
1479
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1482
ctypes.c_uint(len(openpgp))))
1483
# New empty GnuTLS certificate
1484
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1485
(gnutls.library.functions
1486
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1487
# Import the OpenPGP public key into the certificate
1488
(gnutls.library.functions
1489
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1490
gnutls.library.constants
1491
.GNUTLS_OPENPGP_FMT_RAW))
1492
# Verify the self signature in the key
1493
crtverify = ctypes.c_uint()
1494
(gnutls.library.functions
1495
.gnutls_openpgp_crt_verify_self(crt, 0,
1496
ctypes.byref(crtverify)))
1497
if crtverify.value != 0:
1498
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1499
raise (gnutls.errors.CertificateSecurityError
1501
# New buffer for the fingerprint
1502
buf = ctypes.create_string_buffer(20)
1503
buf_len = ctypes.c_size_t()
1504
# Get the fingerprint from the certificate into the buffer
1505
(gnutls.library.functions
1506
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1507
ctypes.byref(buf_len)))
1508
# Deinit the certificate
1509
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1510
# Convert the buffer to a Python bytestring
1511
fpr = ctypes.string_at(buf, buf_len.value)
1512
# Convert the bytestring to hexadecimal notation
1513
hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1517
class MultiprocessingMixIn(object):
1518
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1519
def sub_process_main(self, request, address):
1521
self.finish_request(request, address)
1523
self.handle_error(request, address)
1524
self.close_request(request)
1526
def process_request(self, request, address):
1527
"""Start a new process to process the request."""
1528
proc = multiprocessing.Process(target = self.sub_process_main,
1535
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1536
""" adds a pipe to the MixIn """
1537
def process_request(self, request, client_address):
1538
"""Overrides and wraps the original process_request().
1540
This function creates a new pipe in self.pipe
1542
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1544
proc = MultiprocessingMixIn.process_request(self, request,
1546
self.child_pipe.close()
1547
self.add_pipe(parent_pipe, proc)
1549
def add_pipe(self, parent_pipe, proc):
1550
"""Dummy function; override as necessary"""
1551
raise NotImplementedError
1554
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1555
socketserver.TCPServer, object):
1556
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
371
logger.debug(u"TCP connection from: %s",
372
unicode(self.client_address))
373
session = gnutls.connection.ClientSession(self.request,
377
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
378
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
380
priority = "SECURE256"
382
gnutls.library.functions.gnutls_priority_set_direct\
383
(session._c_object, priority, None);
387
except gnutls.errors.GNUTLSError, error:
388
logger.debug(u"Handshake failed: %s", error)
389
# Do not run session.bye() here: the session is not
390
# established. Just abandon the request.
393
fpr = fingerprint(peer_certificate(session))
394
except (TypeError, gnutls.errors.GNUTLSError), error:
395
logger.debug(u"Bad certificate: %s", error)
398
logger.debug(u"Fingerprint: %s", fpr)
400
for c in self.server.clients:
401
if c.fingerprint == fpr:
404
# Have to check if client.still_valid(), since it is possible
405
# that the client timed out while establishing the GnuTLS
407
if (not client) or (not client.still_valid()):
409
logger.debug(u"Client %(name)s is invalid",
412
logger.debug(u"Client not found for fingerprint: %s",
417
while sent_size < len(client.secret):
418
sent = session.send(client.secret[sent_size:])
419
logger.debug(u"Sent: %d, remaining: %d",
420
sent, len(client.secret)
421
- (sent_size + sent))
426
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
427
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1559
enabled: Boolean; whether this server is activated yet
1560
interface: None or a network interface name (string)
1561
use_ipv6: Boolean; to use IPv6 or not
429
options: Command line options
430
clients: Set() of Client objects
1563
def __init__(self, server_address, RequestHandlerClass,
1564
interface=None, use_ipv6=True):
1565
self.interface = interface
1567
self.address_family = socket.AF_INET6
1568
socketserver.TCPServer.__init__(self, server_address,
1569
RequestHandlerClass)
432
address_family = socket.AF_INET6
433
def __init__(self, *args, **kwargs):
434
if "options" in kwargs:
435
self.options = kwargs["options"]
436
del kwargs["options"]
437
if "clients" in kwargs:
438
self.clients = kwargs["clients"]
439
del kwargs["clients"]
440
return super(type(self), self).__init__(*args, **kwargs)
1570
441
def server_bind(self):
1571
442
"""This overrides the normal server_bind() function
1572
443
to bind to an interface if one was specified, and also NOT to
1573
444
bind to an address or port if they were not specified."""
1574
if self.interface is not None:
1575
if SO_BINDTODEVICE is None:
1576
logger.error("SO_BINDTODEVICE does not exist;"
1577
" cannot bind to interface %s",
1581
self.socket.setsockopt(socket.SOL_SOCKET,
1585
except socket.error as error:
1586
if error[0] == errno.EPERM:
1587
logger.error("No permission to"
1588
" bind to interface %s",
1590
elif error[0] == errno.ENOPROTOOPT:
1591
logger.error("SO_BINDTODEVICE not available;"
1592
" cannot bind to interface %s",
445
if self.options.interface:
446
if not hasattr(socket, "SO_BINDTODEVICE"):
447
# From /usr/include/asm-i486/socket.h
448
socket.SO_BINDTODEVICE = 25
450
self.socket.setsockopt(socket.SOL_SOCKET,
451
socket.SO_BINDTODEVICE,
452
self.options.interface)
453
except socket.error, error:
454
if error[0] == errno.EPERM:
455
logger.warning(u"No permission to"
456
u" bind to interface %s",
457
self.options.interface)
1596
460
# Only bind(2) the socket if we really need to.
1597
461
if self.server_address[0] or self.server_address[1]:
1598
462
if not self.server_address[0]:
1599
if self.address_family == socket.AF_INET6:
1600
any_address = "::" # in6addr_any
1602
any_address = socket.INADDR_ANY
1603
self.server_address = (any_address,
464
self.server_address = (in6addr_any,
1604
465
self.server_address[1])
1605
elif not self.server_address[1]:
466
elif self.server_address[1] is None:
1606
467
self.server_address = (self.server_address[0],
1608
# if self.interface:
1609
# self.server_address = (self.server_address[0],
1614
return socketserver.TCPServer.server_bind(self)
1617
class MandosServer(IPv6_TCPServer):
1621
clients: set of Client objects
1622
gnutls_priority GnuTLS priority string
1623
use_dbus: Boolean; to emit D-Bus signals or not
1625
Assumes a gobject.MainLoop event loop.
1627
def __init__(self, server_address, RequestHandlerClass,
1628
interface=None, use_ipv6=True, clients=None,
1629
gnutls_priority=None, use_dbus=True):
1630
self.enabled = False
1631
self.clients = clients
1632
if self.clients is None:
1633
self.clients = set()
1634
self.use_dbus = use_dbus
1635
self.gnutls_priority = gnutls_priority
1636
IPv6_TCPServer.__init__(self, server_address,
1637
RequestHandlerClass,
1638
interface = interface,
1639
use_ipv6 = use_ipv6)
1640
def server_activate(self):
1642
return socketserver.TCPServer.server_activate(self)
1647
def add_pipe(self, parent_pipe, proc):
1648
# Call "handle_ipc" for both data and EOF events
1649
gobject.io_add_watch(parent_pipe.fileno(),
1650
gobject.IO_IN | gobject.IO_HUP,
1651
functools.partial(self.handle_ipc,
1656
def handle_ipc(self, source, condition, parent_pipe=None,
1657
proc = None, client_object=None):
1659
gobject.IO_IN: "IN", # There is data to read.
1660
gobject.IO_OUT: "OUT", # Data can be written (without
1662
gobject.IO_PRI: "PRI", # There is urgent data to read.
1663
gobject.IO_ERR: "ERR", # Error condition.
1664
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1665
# broken, usually for pipes and
1668
conditions_string = ' | '.join(name
1670
condition_names.iteritems()
1671
if cond & condition)
1672
# error, or the other end of multiprocessing.Pipe has closed
1673
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1674
# Wait for other process to exit
1678
# Read a request from the child
1679
request = parent_pipe.recv()
1680
command = request[0]
1682
if command == 'init':
1684
address = request[2]
1686
for c in self.clients:
1687
if c.fingerprint == fpr:
1691
logger.info("Client not found for fingerprint: %s, ad"
1692
"dress: %s", fpr, address)
1695
mandos_dbus_service.ClientNotFound(fpr,
1697
parent_pipe.send(False)
1700
gobject.io_add_watch(parent_pipe.fileno(),
1701
gobject.IO_IN | gobject.IO_HUP,
1702
functools.partial(self.handle_ipc,
1708
parent_pipe.send(True)
1709
# remove the old hook in favor of the new above hook on
1712
if command == 'funcall':
1713
funcname = request[1]
1717
parent_pipe.send(('data', getattr(client_object,
1721
if command == 'getattr':
1722
attrname = request[1]
1723
if callable(client_object.__getattribute__(attrname)):
1724
parent_pipe.send(('function',))
1726
parent_pipe.send(('data', client_object
1727
.__getattribute__(attrname)))
1729
if command == 'setattr':
1730
attrname = request[1]
1732
setattr(client_object, attrname, value)
469
return super(type(self), self).server_bind()
1737
472
def string_to_delta(interval):
1738
473
"""Parse a string and return a datetime.timedelta
1740
475
>>> string_to_delta('7d')
1741
476
datetime.timedelta(7)
1742
477
>>> string_to_delta('60s')
616
def killme(status = 0):
617
logger.debug("Stopping server with exit status %d", status)
619
if main_loop_started:
1827
##################################################################
1828
# Parsing of options, both command line and config file
1830
parser = argparse.ArgumentParser()
1831
parser.add_argument("-v", "--version", action="version",
1832
version = "%%(prog)s %s" % version,
1833
help="show version number and exit")
1834
parser.add_argument("-i", "--interface", metavar="IF",
1835
help="Bind to interface IF")
1836
parser.add_argument("-a", "--address",
1837
help="Address to listen for requests on")
1838
parser.add_argument("-p", "--port", type=int,
1839
help="Port number to receive requests on")
1840
parser.add_argument("--check", action="store_true",
1841
help="Run self-test")
1842
parser.add_argument("--debug", action="store_true",
1843
help="Debug mode; run in foreground and log"
1845
parser.add_argument("--debuglevel", metavar="LEVEL",
1846
help="Debug level for stdout output")
1847
parser.add_argument("--priority", help="GnuTLS"
1848
" priority string (see GnuTLS documentation)")
1849
parser.add_argument("--servicename",
1850
metavar="NAME", help="Zeroconf service name")
1851
parser.add_argument("--configdir",
1852
default="/etc/mandos", metavar="DIR",
1853
help="Directory to search for configuration"
1855
parser.add_argument("--no-dbus", action="store_false",
1856
dest="use_dbus", help="Do not provide D-Bus"
1857
" system bus interface")
1858
parser.add_argument("--no-ipv6", action="store_false",
1859
dest="use_ipv6", help="Do not use IPv6")
1860
options = parser.parse_args()
628
global main_loop_started
629
main_loop_started = False
631
parser = OptionParser()
632
parser.add_option("-i", "--interface", type="string",
633
default=None, metavar="IF",
634
help="Bind to interface IF")
635
parser.add_option("-a", "--address", type="string", default=None,
636
help="Address to listen for requests on")
637
parser.add_option("-p", "--port", type="int", default=None,
638
help="Port number to receive requests on")
639
parser.add_option("--timeout", type="string", # Parsed later
641
help="Amount of downtime allowed for clients")
642
parser.add_option("--interval", type="string", # Parsed later
644
help="How often to check that a client is up")
645
parser.add_option("--check", action="store_true", default=False,
646
help="Run self-test")
647
parser.add_option("--debug", action="store_true", default=False,
649
(options, args) = parser.parse_args()
1862
651
if options.check:
1864
653
doctest.testmod()
1867
# Default values for config file for server-global settings
1868
server_defaults = { "interface": "",
1873
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1874
"servicename": "Mandos",
1880
# Parse config file for server-global settings
1881
server_config = configparser.SafeConfigParser(server_defaults)
1883
server_config.read(os.path.join(options.configdir,
1885
# Convert the SafeConfigParser object to a dict
1886
server_settings = server_config.defaults()
1887
# Use the appropriate methods on the non-string config options
1888
for option in ("debug", "use_dbus", "use_ipv6"):
1889
server_settings[option] = server_config.getboolean("DEFAULT",
1891
if server_settings["port"]:
1892
server_settings["port"] = server_config.getint("DEFAULT",
1896
# Override the settings from the config file with command line
1898
for option in ("interface", "address", "port", "debug",
1899
"priority", "servicename", "configdir",
1900
"use_dbus", "use_ipv6", "debuglevel"):
1901
value = getattr(options, option)
1902
if value is not None:
1903
server_settings[option] = value
1905
# Force all strings to be unicode
1906
for option in server_settings.keys():
1907
if type(server_settings[option]) is str:
1908
server_settings[option] = unicode(server_settings[option])
1909
# Now we have our good server settings in "server_settings"
1911
##################################################################
1914
debug = server_settings["debug"]
1915
debuglevel = server_settings["debuglevel"]
1916
use_dbus = server_settings["use_dbus"]
1917
use_ipv6 = server_settings["use_ipv6"]
1919
if server_settings["servicename"] != "Mandos":
1920
syslogger.setFormatter(logging.Formatter
1921
('Mandos (%s) [%%(process)d]:'
1922
' %%(levelname)s: %%(message)s'
1923
% server_settings["servicename"]))
1925
# Parse config file with clients
1926
client_defaults = { "timeout": "5m",
1927
"extended_timeout": "15m",
1929
"checker": "fping -q -- %%(host)s",
1931
"approval_delay": "0s",
1932
"approval_duration": "1s",
1934
client_config = configparser.SafeConfigParser(client_defaults)
1935
client_config.read(os.path.join(server_settings["configdir"],
1938
global mandos_dbus_service
1939
mandos_dbus_service = None
1941
tcp_server = MandosServer((server_settings["address"],
1942
server_settings["port"]),
1944
interface=(server_settings["interface"]
1948
server_settings["priority"],
1951
pidfilename = "/var/run/mandos.pid"
1953
pidfile = open(pidfilename, "w")
1955
logger.error("Could not open file %r", pidfilename)
1958
uid = pwd.getpwnam("_mandos").pw_uid
1959
gid = pwd.getpwnam("_mandos").pw_gid
1962
uid = pwd.getpwnam("mandos").pw_uid
1963
gid = pwd.getpwnam("mandos").pw_gid
1966
uid = pwd.getpwnam("nobody").pw_uid
1967
gid = pwd.getpwnam("nobody").pw_gid
1974
except OSError as error:
1975
if error[0] != errno.EPERM:
1978
if not debug and not debuglevel:
1979
logger.setLevel(logging.WARNING)
1981
level = getattr(logging, debuglevel.upper())
1982
logger.setLevel(level)
1985
logger.setLevel(logging.DEBUG)
1986
# Enable all possible GnuTLS debugging
1988
# "Use a log level over 10 to enable all debugging options."
1990
gnutls.library.functions.gnutls_global_set_log_level(11)
1992
@gnutls.library.types.gnutls_log_func
1993
def debug_gnutls(level, string):
1994
logger.debug("GnuTLS: %s", string[:-1])
1996
(gnutls.library.functions
1997
.gnutls_global_set_log_function(debug_gnutls))
1999
# Redirect stdin so all checkers get /dev/null
2000
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2001
os.dup2(null, sys.stdin.fileno())
2005
# No console logging
2006
logger.removeHandler(console)
2008
# Need to fork before connecting to D-Bus
2010
# Close all input and output, do double fork, etc.
656
# Parse the time arguments
658
options.timeout = string_to_delta(options.timeout)
660
parser.error("option --timeout: Unparseable time")
662
options.interval = string_to_delta(options.interval)
664
parser.error("option --interval: Unparseable time")
667
defaults = { "checker": "fping -q -- %%(fqdn)s" }
668
client_config = ConfigParser.SafeConfigParser(defaults)
669
#client_config.readfp(open("global.conf"), "global.conf")
670
client_config.read("mandos-clients.conf")
2013
672
global main_loop
2014
675
# From the Avahi example code
2015
676
DBusGMainLoop(set_as_default=True )
2016
677
main_loop = gobject.MainLoop()
2017
678
bus = dbus.SystemBus()
679
server = dbus.Interface(
680
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
681
avahi.DBUS_INTERFACE_SERVER )
2018
682
# End of Avahi example code
2021
bus_name = dbus.service.BusName("se.recompile.Mandos",
2022
bus, do_not_queue=True)
2023
old_bus_name = (dbus.service.BusName
2024
("se.bsnet.fukt.Mandos", bus,
2026
except dbus.exceptions.NameExistsException as e:
2027
logger.error(unicode(e) + ", disabling D-Bus")
2029
server_settings["use_dbus"] = False
2030
tcp_server.use_dbus = False
2031
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2032
service = AvahiServiceToSyslog(name =
2033
server_settings["servicename"],
2034
servicetype = "_mandos._tcp",
2035
protocol = protocol, bus = bus)
2036
if server_settings["interface"]:
2037
service.interface = (if_nametoindex
2038
(str(server_settings["interface"])))
2040
global multiprocessing_manager
2041
multiprocessing_manager = multiprocessing.Manager()
2043
client_class = Client
2045
client_class = functools.partial(ClientDBusTransitional,
2047
def client_config_items(config, section):
2048
special_settings = {
2049
"approved_by_default":
2050
lambda: config.getboolean(section,
2051
"approved_by_default"),
2053
for name, value in config.items(section):
2055
yield (name, special_settings[name]())
2059
tcp_server.clients.update(set(
2060
client_class(name = section,
2061
config= dict(client_config_items(
2062
client_config, section)))
2063
for section in client_config.sections()))
2064
if not tcp_server.clients:
2065
logger.warning("No clients defined")
684
debug = options.debug
687
console = logging.StreamHandler()
688
# console.setLevel(logging.DEBUG)
689
console.setFormatter(logging.Formatter\
690
('%(levelname)s: %(message)s'))
691
logger.addHandler(console)
695
def remove_from_clients(client):
696
clients.remove(client)
698
logger.debug(u"No clients left, exiting")
701
clients.update(Set(Client(name=section, options=options,
702
stop_hook = remove_from_clients,
703
**(dict(client_config\
705
for section in client_config.sections()))
2071
pidfile.write(str(pid) + "\n".encode("utf-8"))
2074
logger.error("Could not write to file %r with PID %d",
2077
# "pidfile" was never created
2081
signal.signal(signal.SIGINT, signal.SIG_IGN)
2083
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2084
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2087
class MandosDBusService(dbus.service.Object):
2088
"""A D-Bus proxy object"""
2090
dbus.service.Object.__init__(self, bus, "/")
2091
_interface = "se.recompile.Mandos"
2093
@dbus.service.signal(_interface, signature="o")
2094
def ClientAdded(self, objpath):
2098
@dbus.service.signal(_interface, signature="ss")
2099
def ClientNotFound(self, fingerprint, address):
2103
@dbus.service.signal(_interface, signature="os")
2104
def ClientRemoved(self, objpath, name):
2108
@dbus.service.method(_interface, out_signature="ao")
2109
def GetAllClients(self):
2111
return dbus.Array(c.dbus_object_path
2112
for c in tcp_server.clients)
2114
@dbus.service.method(_interface,
2115
out_signature="a{oa{sv}}")
2116
def GetAllClientsWithProperties(self):
2118
return dbus.Dictionary(
2119
((c.dbus_object_path, c.GetAll(""))
2120
for c in tcp_server.clients),
2123
@dbus.service.method(_interface, in_signature="o")
2124
def RemoveClient(self, object_path):
2126
for c in tcp_server.clients:
2127
if c.dbus_object_path == object_path:
2128
tcp_server.clients.remove(c)
2129
c.remove_from_connection()
2130
# Don't signal anything except ClientRemoved
2131
c.disable(quiet=True)
2133
self.ClientRemoved(object_path, c.name)
2135
raise KeyError(object_path)
2139
class MandosDBusServiceTransitional(MandosDBusService):
2140
__metaclass__ = AlternateDBusNamesMetaclass
2141
mandos_dbus_service = MandosDBusServiceTransitional()
2144
711
"Cleanup function; run on exit"
2147
multiprocessing.active_children()
2148
while tcp_server.clients:
2149
client = tcp_server.clients.pop()
2151
client.remove_from_connection()
2152
client.disable_hook = None
2153
# Don't signal anything except ClientRemoved
2154
client.disable(quiet=True)
2157
mandos_dbus_service.ClientRemoved(client
2161
atexit.register(cleanup)
2163
for client in tcp_server.clients:
2166
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2170
tcp_server.server_activate()
2172
# Find out what port we got
2173
service.port = tcp_server.socket.getsockname()[1]
2175
logger.info("Now listening on address %r, port %d,"
2176
" flowinfo %d, scope_id %d"
2177
% tcp_server.socket.getsockname())
2179
logger.info("Now listening on address %r, port %d"
2180
% tcp_server.socket.getsockname())
2182
#service.interface = tcp_server.socket.getsockname()[3]
2185
713
# From the Avahi example code
2188
except dbus.exceptions.DBusException as error:
2189
logger.critical("DBusException: %s", error)
714
if not group is None:
2192
717
# End of Avahi example code
2194
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2195
lambda *args, **kwargs:
2196
(tcp_server.handle_request
2197
(*args[2:], **kwargs) or True))
720
client = clients.pop()
721
client.stop_hook = None
724
atexit.register(cleanup)
727
signal.signal(signal.SIGINT, signal.SIG_IGN)
728
signal.signal(signal.SIGHUP, lambda signum, frame: killme())
729
signal.signal(signal.SIGTERM, lambda signum, frame: killme())
731
for client in clients:
734
tcp_server = IPv6_TCPServer((options.address, options.port),
738
# Find out what random port we got
740
servicePort = tcp_server.socket.getsockname()[1]
741
logger.debug(u"Now listening on port %d", servicePort)
743
if options.interface is not None:
744
global serviceInterface
745
serviceInterface = if_nametoindex(options.interface)
747
# From the Avahi example code
748
server.connect_to_signal("StateChanged", server_state_changed)
750
server_state_changed(server.GetState())
751
except dbus.exceptions.DBusException, error:
752
logger.critical(u"DBusException: %s", error)
754
# End of Avahi example code
756
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
757
lambda *args, **kwargs:
758
tcp_server.handle_request(*args[2:],
2199
761
logger.debug("Starting main loop")
762
main_loop_started = True
2201
except AvahiError as error:
2202
logger.critical("AvahiError: %s", error)
2205
764
except KeyboardInterrupt:
2207
print("", file=sys.stderr)
2208
logger.debug("Server received KeyboardInterrupt")
2209
logger.debug("Server exiting")
2210
# Must run before the D-Bus bus name gets deregistered
2214
770
if __name__ == '__main__':