44
14
import gnutls.library.functions
45
15
import gnutls.library.constants
46
16
import gnutls.library.types
47
import ConfigParser as configparser
56
import logging.handlers
62
import cPickle as pickle
63
import multiprocessing
69
28
from dbus.mainloop.glib import DBusGMainLoop
72
import xml.dom.minidom
76
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
77
except AttributeError:
79
from IN import SO_BINDTODEVICE
81
SO_BINDTODEVICE = None
86
#logger = logging.getLogger(u'mandos')
87
logger = logging.Logger(u'mandos')
88
syslogger = (logging.handlers.SysLogHandler
89
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
90
address = "/dev/log"))
91
syslogger.setFormatter(logging.Formatter
92
(u'Mandos [%(process)d]: %(levelname)s:'
94
logger.addHandler(syslogger)
96
console = logging.StreamHandler()
97
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
100
logger.addHandler(console)
102
class AvahiError(Exception):
103
def __init__(self, value, *args, **kwargs):
105
super(AvahiError, self).__init__(value, *args, **kwargs)
106
def __unicode__(self):
107
return unicode(repr(self.value))
109
class AvahiServiceError(AvahiError):
112
class AvahiGroupError(AvahiError):
116
class AvahiService(object):
117
"""An Avahi (Zeroconf) service.
120
interface: integer; avahi.IF_UNSPEC or an interface index.
121
Used to optionally bind to the specified interface.
122
name: string; Example: u'Mandos'
123
type: string; Example: u'_mandos._tcp'.
124
See <http://www.dns-sd.org/ServiceTypes.html>
125
port: integer; what port to announce
126
TXT: list of strings; TXT record for the service
127
domain: string; Domain to publish on, default to .local if empty.
128
host: string; Host to publish records for, default is localhost
129
max_renames: integer; maximum number of renames
130
rename_count: integer; counter so we only rename after collisions
131
a sensible number of times
132
group: D-Bus Entry Group
134
bus: dbus.SystemBus()
136
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
137
servicetype = None, port = None, TXT = None,
138
domain = u"", host = u"", max_renames = 32768,
139
protocol = avahi.PROTO_UNSPEC, bus = None):
140
self.interface = interface
142
self.type = servicetype
144
self.TXT = TXT if TXT is not None else []
147
self.rename_count = 0
148
self.max_renames = max_renames
149
self.protocol = protocol
150
self.group = None # our entry group
154
"""Derived from the Avahi example code"""
155
if self.rename_count >= self.max_renames:
156
logger.critical(u"No suitable Zeroconf service name found"
157
u" after %i retries, exiting.",
159
raise AvahiServiceError(u"Too many renames")
160
self.name = unicode(self.server.GetAlternativeServiceName(self.name))
161
logger.info(u"Changing Zeroconf service name to %r ...",
163
syslogger.setFormatter(logging.Formatter
164
(u'Mandos (%s) [%%(process)d]:'
165
u' %%(levelname)s: %%(message)s'
170
except dbus.exceptions.DBusException, error:
171
logger.critical(u"DBusException: %s", error)
174
self.rename_count += 1
176
"""Derived from the Avahi example code"""
177
if self.group is not None:
180
"""Derived from the Avahi example code"""
181
if self.group is None:
182
self.group = dbus.Interface(
183
self.bus.get_object(avahi.DBUS_NAME,
184
self.server.EntryGroupNew()),
185
avahi.DBUS_INTERFACE_ENTRY_GROUP)
186
self.group.connect_to_signal('StateChanged',
188
.entry_group_state_changed)
189
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
190
self.name, self.type)
191
self.group.AddService(
194
dbus.UInt32(0), # flags
195
self.name, self.type,
196
self.domain, self.host,
197
dbus.UInt16(self.port),
198
avahi.string_array_to_txt_array(self.TXT))
200
def entry_group_state_changed(self, state, error):
201
"""Derived from the Avahi example code"""
202
logger.debug(u"Avahi entry group state change: %i", state)
204
if state == avahi.ENTRY_GROUP_ESTABLISHED:
205
logger.debug(u"Zeroconf service established.")
206
elif state == avahi.ENTRY_GROUP_COLLISION:
207
logger.warning(u"Zeroconf service name collision.")
209
elif state == avahi.ENTRY_GROUP_FAILURE:
210
logger.critical(u"Avahi: Error in group state changed %s",
212
raise AvahiGroupError(u"State changed: %s"
215
"""Derived from the Avahi example code"""
216
if self.group is not None:
219
def server_state_changed(self, state):
220
"""Derived from the Avahi example code"""
221
logger.debug(u"Avahi server state change: %i", state)
222
if state == avahi.SERVER_COLLISION:
223
logger.error(u"Zeroconf server name collision")
225
elif state == avahi.SERVER_RUNNING:
228
"""Derived from the Avahi example code"""
229
if self.server is None:
230
self.server = dbus.Interface(
231
self.bus.get_object(avahi.DBUS_NAME,
232
avahi.DBUS_PATH_SERVER),
233
avahi.DBUS_INTERFACE_SERVER)
234
self.server.connect_to_signal(u"StateChanged",
235
self.server_state_changed)
236
self.server_state_changed(self.server.GetState())
31
# This variable is used to optionally bind to a specified interface.
32
# It is a global variable to fit in with the other variables from the
33
# Avahi server example code.
34
serviceInterface = avahi.IF_UNSPEC
35
# From the Avahi server example code:
36
serviceName = "Mandos"
37
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
38
servicePort = None # Not known at startup
39
serviceTXT = [] # TXT record for the service
40
domain = "" # Domain to publish on, default to .local
41
host = "" # Host to publish records for, default to localhost
42
group = None #our entry group
43
rename_count = 12 # Counter so we only rename after collisions a
44
# sensible number of times
45
# End of Avahi example code
239
48
class Client(object):
240
49
"""A representation of a client host served by this server.
243
name: string; from the config file, used in log messages and
51
name: string; from the config file, used in log messages
245
52
fingerprint: string (40 or 32 hexadecimal digits); used to
246
53
uniquely identify the client
247
secret: bytestring; sent verbatim (over TLS) to client
248
host: string; available for use by the checker command
249
created: datetime.datetime(); (UTC) object creation
250
last_enabled: datetime.datetime(); (UTC)
252
last_checked_ok: datetime.datetime(); (UTC) or None
253
timeout: datetime.timedelta(); How long from last_checked_ok
254
until this client is disabled
255
interval: datetime.timedelta(); How often to start a new checker
256
disable_hook: If set, called by disable() as disable_hook(self)
257
checker: subprocess.Popen(); a running checker process used
258
to see if the client lives.
259
'None' if no process is running.
54
secret: bytestring; sent verbatim (over TLS) to client
55
fqdn: string (FQDN); available for use by the checker command
56
created: datetime.datetime()
57
last_seen: datetime.datetime() or None if not yet seen
58
timeout: datetime.timedelta(); How long from last_seen until
59
this client is invalid
60
interval: datetime.timedelta(); How often to start a new checker
61
stop_hook: If set, called by stop() as stop_hook(self)
62
checker: subprocess.Popen(); a running checker process used
63
to see if the client lives.
64
Is None if no process is running.
260
65
checker_initiator_tag: a gobject event source tag, or None
261
disable_initiator_tag: - '' -
66
stop_initiator_tag: - '' -
262
67
checker_callback_tag: - '' -
263
68
checker_command: string; External command which is run to check if
264
client lives. %() expansions are done at
69
client lives. %()s expansions are done at
265
70
runtime with vars(self) as dict, so that for
266
71
instance %(name)s can be used in the command.
267
current_checker_command: string; current running checker_command
268
approval_delay: datetime.timedelta(); Time to wait for approval
269
_approved: bool(); 'None' if not yet approved/disapproved
270
approval_duration: datetime.timedelta(); Duration of one approval
73
_timeout: Real variable for 'timeout'
74
_interval: Real variable for 'interval'
75
_timeout_milliseconds: Used by gobject.timeout_add()
76
_interval_milliseconds: - '' -
274
def _timedelta_to_milliseconds(td):
275
"Convert a datetime.timedelta() to milliseconds"
276
return ((td.days * 24 * 60 * 60 * 1000)
277
+ (td.seconds * 1000)
278
+ (td.microseconds // 1000))
280
def timeout_milliseconds(self):
281
"Return the 'timeout' attribute in milliseconds"
282
return self._timedelta_to_milliseconds(self.timeout)
284
def interval_milliseconds(self):
285
"Return the 'interval' attribute in milliseconds"
286
return self._timedelta_to_milliseconds(self.interval)
288
def approval_delay_milliseconds(self):
289
return self._timedelta_to_milliseconds(self.approval_delay)
291
def __init__(self, name = None, disable_hook=None, config=None):
292
"""Note: the 'checker' key in 'config' sets the
293
'checker_command' attribute and *not* the 'checker'
78
def _set_timeout(self, timeout):
79
"Setter function for 'timeout' attribute"
80
self._timeout = timeout
81
self._timeout_milliseconds = ((self.timeout.days
82
* 24 * 60 * 60 * 1000)
83
+ (self.timeout.seconds * 1000)
84
+ (self.timeout.microseconds
86
timeout = property(lambda self: self._timeout,
89
def _set_interval(self, interval):
90
"Setter function for 'interval' attribute"
91
self._interval = interval
92
self._interval_milliseconds = ((self.interval.days
93
* 24 * 60 * 60 * 1000)
94
+ (self.interval.seconds
96
+ (self.interval.microseconds
98
interval = property(lambda self: self._interval,
101
def __init__(self, name=None, options=None, stop_hook=None,
102
fingerprint=None, secret=None, secfile=None, fqdn=None,
103
timeout=None, interval=-1, checker=None):
298
logger.debug(u"Creating client %r", self.name)
299
# Uppercase and remove spaces from fingerprint for later
300
# comparison purposes with return value from the fingerprint()
302
self.fingerprint = (config[u"fingerprint"].upper()
304
logger.debug(u" Fingerprint: %s", self.fingerprint)
305
if u"secret" in config:
306
self.secret = config[u"secret"].decode(u"base64")
307
elif u"secfile" in config:
308
with open(os.path.expanduser(os.path.expandvars
309
(config[u"secfile"])),
311
self.secret = secfile.read()
313
raise TypeError(u"No secret or secfile for client %s"
315
self.host = config.get(u"host", u"")
316
self.created = datetime.datetime.utcnow()
318
self.last_enabled = None
319
self.last_checked_ok = None
320
self.timeout = string_to_delta(config[u"timeout"])
321
self.interval = string_to_delta(config[u"interval"])
322
self.disable_hook = disable_hook
105
# Uppercase and remove spaces from fingerprint
106
# for later comparison purposes with return value of
107
# the fingerprint() function
108
self.fingerprint = fingerprint.upper().replace(u" ", u"")
110
self.secret = secret.decode(u"base64")
113
self.secret = sf.read()
116
raise RuntimeError(u"No secret or secfile for client %s"
118
self.fqdn = fqdn # string
119
self.created = datetime.datetime.now()
120
self.last_seen = None
122
timeout = options.timeout
123
self.timeout = timeout
125
interval = options.interval
127
interval = string_to_delta(interval)
128
self.interval = interval
129
self.stop_hook = stop_hook
323
130
self.checker = None
324
131
self.checker_initiator_tag = None
325
self.disable_initiator_tag = None
132
self.stop_initiator_tag = None
326
133
self.checker_callback_tag = None
327
self.checker_command = config[u"checker"]
328
self.current_checker_command = None
329
self.last_connect = None
330
self._approved = None
331
self.approved_by_default = config.get(u"approved_by_default",
333
self.approvals_pending = 0
334
self.approval_delay = string_to_delta(
335
config[u"approval_delay"])
336
self.approval_duration = string_to_delta(
337
config[u"approval_duration"])
338
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
340
def send_changedstate(self):
341
self.changedstate.acquire()
342
self.changedstate.notify_all()
343
self.changedstate.release()
346
"""Start this client's checker and timeout hooks"""
347
if getattr(self, u"enabled", False):
350
self.send_changedstate()
351
self.last_enabled = datetime.datetime.utcnow()
134
self.check_command = checker
136
"""Start this clients checker and timeout hooks"""
352
137
# Schedule a new checker to be started an 'interval' from now,
353
138
# and every interval from then on.
354
self.checker_initiator_tag = (gobject.timeout_add
355
(self.interval_milliseconds(),
357
# Schedule a disable() when 'timeout' has passed
358
self.disable_initiator_tag = (gobject.timeout_add
359
(self.timeout_milliseconds(),
139
self.checker_initiator_tag = gobject.timeout_add\
140
(self._interval_milliseconds,
362
142
# Also start a new checker *right now*.
363
143
self.start_checker()
365
def disable(self, quiet=True):
366
"""Disable this client."""
367
if not getattr(self, "enabled", False):
370
self.send_changedstate()
372
logger.info(u"Disabling client %s", self.name)
373
if getattr(self, u"disable_initiator_tag", False):
374
gobject.source_remove(self.disable_initiator_tag)
375
self.disable_initiator_tag = None
376
if getattr(self, u"checker_initiator_tag", False):
144
# Schedule a stop() when 'timeout' has passed
145
self.stop_initiator_tag = gobject.timeout_add\
146
(self._timeout_milliseconds,
150
The possibility that this client might be restarted is left
151
open, but not currently used."""
153
sys.stderr.write(u"Stopping client %s\n" % self.name)
155
if self.stop_initiator_tag:
156
gobject.source_remove(self.stop_initiator_tag)
157
self.stop_initiator_tag = None
158
if self.checker_initiator_tag:
377
159
gobject.source_remove(self.checker_initiator_tag)
378
160
self.checker_initiator_tag = None
379
161
self.stop_checker()
380
if self.disable_hook:
381
self.disable_hook(self)
383
164
# Do not run this again if called by a gobject.timeout_add
386
166
def __del__(self):
387
self.disable_hook = None
390
def checker_callback(self, pid, condition, command):
167
# Some code duplication here and in stop()
168
if hasattr(self, "stop_initiator_tag") \
169
and self.stop_initiator_tag:
170
gobject.source_remove(self.stop_initiator_tag)
171
self.stop_initiator_tag = None
172
if hasattr(self, "checker_initiator_tag") \
173
and self.checker_initiator_tag:
174
gobject.source_remove(self.checker_initiator_tag)
175
self.checker_initiator_tag = None
177
def checker_callback(self, pid, condition):
391
178
"""The checker has completed, so take appropriate actions."""
179
now = datetime.datetime.now()
180
if os.WIFEXITED(condition) \
181
and (os.WEXITSTATUS(condition) == 0):
183
sys.stderr.write(u"Checker for %(name)s succeeded\n"
186
gobject.source_remove(self.stop_initiator_tag)
187
self.stop_initiator_tag = gobject.timeout_add\
188
(self._timeout_milliseconds,
191
if not os.WIFEXITED(condition):
192
sys.stderr.write(u"Checker for %(name)s crashed?\n"
195
sys.stderr.write(u"Checker for %(name)s failed\n"
392
198
self.checker_callback_tag = None
394
if os.WIFEXITED(condition):
395
exitstatus = os.WEXITSTATUS(condition)
397
logger.info(u"Checker for %(name)s succeeded",
401
logger.info(u"Checker for %(name)s failed",
404
logger.warning(u"Checker for %(name)s crashed?",
407
def checked_ok(self):
408
"""Bump up the timeout for this client.
410
This should only be called when the client has been seen,
413
self.last_checked_ok = datetime.datetime.utcnow()
414
gobject.source_remove(self.disable_initiator_tag)
415
self.disable_initiator_tag = (gobject.timeout_add
416
(self.timeout_milliseconds(),
419
199
def start_checker(self):
420
200
"""Start a new checker subprocess if one is not running.
422
201
If a checker already exists, leave it running and do
424
# The reason for not killing a running checker is that if we
425
# did that, then if a checker (for some reason) started
426
# running slowly and taking more than 'interval' time, the
427
# client would inevitably timeout, since no checker would get
428
# a chance to run to completion. If we instead leave running
429
# checkers alone, the checker would have to take more time
430
# than 'timeout' for the client to be disabled, which is as it
433
# If a checker exists, make sure it is not a zombie
435
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
436
except (AttributeError, OSError), error:
437
if (isinstance(error, OSError)
438
and error.errno != errno.ECHILD):
442
logger.warning(u"Checker was a zombie")
443
gobject.source_remove(self.checker_callback_tag)
444
self.checker_callback(pid, status,
445
self.current_checker_command)
446
# Start a new checker if needed
447
203
if self.checker is None:
205
sys.stderr.write(u"Starting checker for %s\n"
449
# In case checker_command has exactly one % operator
450
command = self.checker_command % self.host
208
command = self.check_command % self.fqdn
451
209
except TypeError:
452
# Escape attributes for the shell
453
escaped_attrs = dict((key,
454
re.escape(unicode(str(val),
210
escaped_attrs = dict((key, re.escape(str(val)))
458
212
vars(self).iteritems())
460
command = self.checker_command % escaped_attrs
461
except TypeError, error:
462
logger.error(u'Could not format string "%s":'
463
u' %s', self.checker_command, error)
464
return True # Try again later
465
self.current_checker_command = command
213
command = self.check_command % escaped_attrs
467
logger.info(u"Starting checker %r for %s",
469
# We don't need to redirect stdout and stderr, since
470
# in normal mode, that is already done by daemon(),
471
# and in debug mode we don't want to. (Stdin is
472
# always replaced by /dev/null.)
473
self.checker = subprocess.Popen(command,
475
shell=True, cwd=u"/")
476
self.checker_callback_tag = (gobject.child_watch_add
478
self.checker_callback,
480
# The checker may have completed before the gobject
481
# watch was added. Check for this.
482
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
484
gobject.source_remove(self.checker_callback_tag)
485
self.checker_callback(pid, status, command)
486
except OSError, error:
487
logger.error(u"Failed to start subprocess: %s",
215
self.checker = subprocess.\
217
stdout=subprocess.PIPE,
218
close_fds=True, shell=True,
220
self.checker_callback_tag = gobject.\
221
child_watch_add(self.checker.pid,
224
except subprocess.OSError, error:
225
sys.stderr.write(u"Failed to start subprocess: %s\n"
489
227
# Re-run this periodically if run by gobject.timeout_add
492
229
def stop_checker(self):
493
230
"""Force the checker process, if any, to stop."""
494
if self.checker_callback_tag:
495
gobject.source_remove(self.checker_callback_tag)
496
self.checker_callback_tag = None
497
if getattr(self, u"checker", None) is None:
231
if not hasattr(self, "checker") or self.checker is None:
499
logger.debug(u"Stopping checker for %(name)s", vars(self))
501
os.kill(self.checker.pid, signal.SIGTERM)
503
#if self.checker.poll() is None:
504
# os.kill(self.checker.pid, signal.SIGKILL)
505
except OSError, error:
506
if error.errno != errno.ESRCH: # No such process
510
def dbus_service_property(dbus_interface, signature=u"v",
511
access=u"readwrite", byte_arrays=False):
512
"""Decorators for marking methods of a DBusObjectWithProperties to
513
become properties on the D-Bus.
515
The decorated method will be called with no arguments by "Get"
516
and with one argument by "Set".
518
The parameters, where they are supported, are the same as
519
dbus.service.method, except there is only "signature", since the
520
type from Get() and the type sent to Set() is the same.
522
# Encoding deeply encoded byte arrays is not supported yet by the
523
# "Set" method, so we fail early here:
524
if byte_arrays and signature != u"ay":
525
raise ValueError(u"Byte arrays not supported for non-'ay'"
526
u" signature %r" % signature)
528
func._dbus_is_property = True
529
func._dbus_interface = dbus_interface
530
func._dbus_signature = signature
531
func._dbus_access = access
532
func._dbus_name = func.__name__
533
if func._dbus_name.endswith(u"_dbus_property"):
534
func._dbus_name = func._dbus_name[:-14]
535
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
540
class DBusPropertyException(dbus.exceptions.DBusException):
541
"""A base class for D-Bus property-related exceptions
543
def __unicode__(self):
544
return unicode(str(self))
547
class DBusPropertyAccessException(DBusPropertyException):
548
"""A property's access permissions disallows an operation.
553
class DBusPropertyNotFound(DBusPropertyException):
554
"""An attempt was made to access a non-existing property.
559
class DBusObjectWithProperties(dbus.service.Object):
560
"""A D-Bus object with properties.
562
Classes inheriting from this can use the dbus_service_property
563
decorator to expose methods as D-Bus properties. It exposes the
564
standard Get(), Set(), and GetAll() methods on the D-Bus.
568
def _is_dbus_property(obj):
569
return getattr(obj, u"_dbus_is_property", False)
571
def _get_all_dbus_properties(self):
572
"""Returns a generator of (name, attribute) pairs
574
return ((prop._dbus_name, prop)
576
inspect.getmembers(self, self._is_dbus_property))
578
def _get_dbus_property(self, interface_name, property_name):
579
"""Returns a bound method if one exists which is a D-Bus
580
property with the specified name and interface.
582
for name in (property_name,
583
property_name + u"_dbus_property"):
584
prop = getattr(self, name, None)
586
or not self._is_dbus_property(prop)
587
or prop._dbus_name != property_name
588
or (interface_name and prop._dbus_interface
589
and interface_name != prop._dbus_interface)):
593
raise DBusPropertyNotFound(self.dbus_object_path + u":"
594
+ interface_name + u"."
597
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
599
def Get(self, interface_name, property_name):
600
"""Standard D-Bus property Get() method, see D-Bus standard.
602
prop = self._get_dbus_property(interface_name, property_name)
603
if prop._dbus_access == u"write":
604
raise DBusPropertyAccessException(property_name)
606
if not hasattr(value, u"variant_level"):
608
return type(value)(value, variant_level=value.variant_level+1)
610
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
611
def Set(self, interface_name, property_name, value):
612
"""Standard D-Bus property Set() method, see D-Bus standard.
614
prop = self._get_dbus_property(interface_name, property_name)
615
if prop._dbus_access == u"read":
616
raise DBusPropertyAccessException(property_name)
617
if prop._dbus_get_args_options[u"byte_arrays"]:
618
# The byte_arrays option is not supported yet on
619
# signatures other than "ay".
620
if prop._dbus_signature != u"ay":
622
value = dbus.ByteArray(''.join(unichr(byte)
626
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
627
out_signature=u"a{sv}")
628
def GetAll(self, interface_name):
629
"""Standard D-Bus property GetAll() method, see D-Bus
632
Note: Will not include properties with access="write".
635
for name, prop in self._get_all_dbus_properties():
637
and interface_name != prop._dbus_interface):
638
# Interface non-empty but did not match
640
# Ignore write-only properties
641
if prop._dbus_access == u"write":
644
if not hasattr(value, u"variant_level"):
647
all[name] = type(value)(value, variant_level=
648
value.variant_level+1)
649
return dbus.Dictionary(all, signature=u"sv")
651
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
653
path_keyword='object_path',
654
connection_keyword='connection')
655
def Introspect(self, object_path, connection):
656
"""Standard D-Bus method, overloaded to insert property tags.
658
xmlstring = dbus.service.Object.Introspect(self, object_path,
661
document = xml.dom.minidom.parseString(xmlstring)
662
def make_tag(document, name, prop):
663
e = document.createElement(u"property")
664
e.setAttribute(u"name", name)
665
e.setAttribute(u"type", prop._dbus_signature)
666
e.setAttribute(u"access", prop._dbus_access)
668
for if_tag in document.getElementsByTagName(u"interface"):
669
for tag in (make_tag(document, name, prop)
671
in self._get_all_dbus_properties()
672
if prop._dbus_interface
673
== if_tag.getAttribute(u"name")):
674
if_tag.appendChild(tag)
675
# Add the names to the return values for the
676
# "org.freedesktop.DBus.Properties" methods
677
if (if_tag.getAttribute(u"name")
678
== u"org.freedesktop.DBus.Properties"):
679
for cn in if_tag.getElementsByTagName(u"method"):
680
if cn.getAttribute(u"name") == u"Get":
681
for arg in cn.getElementsByTagName(u"arg"):
682
if (arg.getAttribute(u"direction")
684
arg.setAttribute(u"name", u"value")
685
elif cn.getAttribute(u"name") == u"GetAll":
686
for arg in cn.getElementsByTagName(u"arg"):
687
if (arg.getAttribute(u"direction")
689
arg.setAttribute(u"name", u"props")
690
xmlstring = document.toxml(u"utf-8")
692
except (AttributeError, xml.dom.DOMException,
693
xml.parsers.expat.ExpatError), error:
694
logger.error(u"Failed to override Introspection method",
699
class ClientDBus(Client, DBusObjectWithProperties):
700
"""A Client class using D-Bus
703
dbus_object_path: dbus.ObjectPath
704
bus: dbus.SystemBus()
706
# dbus.service.Object doesn't use super(), so we can't either.
708
def __init__(self, bus = None, *args, **kwargs):
709
self._approvals_pending = 0
711
Client.__init__(self, *args, **kwargs)
712
# Only now, when this client is initialized, can it show up on
714
self.dbus_object_path = (dbus.ObjectPath
716
+ self.name.replace(u".", u"_")))
717
DBusObjectWithProperties.__init__(self, self.bus,
718
self.dbus_object_path)
720
def _get_approvals_pending(self):
721
return self._approvals_pending
722
def _set_approvals_pending(self, value):
723
old_value = self._approvals_pending
724
self._approvals_pending = value
726
if (hasattr(self, "dbus_object_path")
727
and bval is not bool(old_value)):
728
dbus_bool = dbus.Boolean(bval, variant_level=1)
729
self.PropertyChanged(dbus.String(u"ApprovalPending"),
732
approvals_pending = property(_get_approvals_pending,
733
_set_approvals_pending)
734
del _get_approvals_pending, _set_approvals_pending
737
def _datetime_to_dbus(dt, variant_level=0):
738
"""Convert a UTC datetime.datetime() to a D-Bus type."""
739
return dbus.String(dt.isoformat(),
740
variant_level=variant_level)
743
oldstate = getattr(self, u"enabled", False)
744
r = Client.enable(self)
745
if oldstate != self.enabled:
747
self.PropertyChanged(dbus.String(u"Enabled"),
748
dbus.Boolean(True, variant_level=1))
749
self.PropertyChanged(
750
dbus.String(u"LastEnabled"),
751
self._datetime_to_dbus(self.last_enabled,
755
def disable(self, quiet = False):
756
oldstate = getattr(self, u"enabled", False)
757
r = Client.disable(self, quiet=quiet)
758
if not quiet and oldstate != self.enabled:
760
self.PropertyChanged(dbus.String(u"Enabled"),
761
dbus.Boolean(False, variant_level=1))
764
def __del__(self, *args, **kwargs):
766
self.remove_from_connection()
769
if hasattr(DBusObjectWithProperties, u"__del__"):
770
DBusObjectWithProperties.__del__(self, *args, **kwargs)
771
Client.__del__(self, *args, **kwargs)
773
def checker_callback(self, pid, condition, command,
233
gobject.source_remove(self.checker_callback_tag)
775
234
self.checker_callback_tag = None
235
os.kill(self.checker.pid, signal.SIGTERM)
236
if self.checker.poll() is None:
237
os.kill(self.checker.pid, signal.SIGKILL)
776
238
self.checker = None
778
self.PropertyChanged(dbus.String(u"CheckerRunning"),
779
dbus.Boolean(False, variant_level=1))
780
if os.WIFEXITED(condition):
781
exitstatus = os.WEXITSTATUS(condition)
783
self.CheckerCompleted(dbus.Int16(exitstatus),
784
dbus.Int64(condition),
785
dbus.String(command))
788
self.CheckerCompleted(dbus.Int16(-1),
789
dbus.Int64(condition),
790
dbus.String(command))
792
return Client.checker_callback(self, pid, condition, command,
795
def checked_ok(self, *args, **kwargs):
796
r = Client.checked_ok(self, *args, **kwargs)
798
self.PropertyChanged(
799
dbus.String(u"LastCheckedOK"),
800
(self._datetime_to_dbus(self.last_checked_ok,
804
def start_checker(self, *args, **kwargs):
805
old_checker = self.checker
806
if self.checker is not None:
807
old_checker_pid = self.checker.pid
809
old_checker_pid = None
810
r = Client.start_checker(self, *args, **kwargs)
811
# Only if new checker process was started
812
if (self.checker is not None
813
and old_checker_pid != self.checker.pid):
815
self.CheckerStarted(self.current_checker_command)
816
self.PropertyChanged(
817
dbus.String(u"CheckerRunning"),
818
dbus.Boolean(True, variant_level=1))
821
def stop_checker(self, *args, **kwargs):
822
old_checker = getattr(self, u"checker", None)
823
r = Client.stop_checker(self, *args, **kwargs)
824
if (old_checker is not None
825
and getattr(self, u"checker", None) is None):
826
self.PropertyChanged(dbus.String(u"CheckerRunning"),
827
dbus.Boolean(False, variant_level=1))
830
def _reset_approved(self):
831
self._approved = None
834
def approve(self, value=True):
835
self.send_changedstate()
836
self._approved = value
837
gobject.timeout_add(self._timedelta_to_milliseconds
838
(self.approval_duration),
839
self._reset_approved)
842
## D-Bus methods, signals & properties
843
_interface = u"se.bsnet.fukt.Mandos.Client"
847
# CheckerCompleted - signal
848
@dbus.service.signal(_interface, signature=u"nxs")
849
def CheckerCompleted(self, exitcode, waitstatus, command):
853
# CheckerStarted - signal
854
@dbus.service.signal(_interface, signature=u"s")
855
def CheckerStarted(self, command):
859
# PropertyChanged - signal
860
@dbus.service.signal(_interface, signature=u"sv")
861
def PropertyChanged(self, property, value):
866
@dbus.service.signal(_interface)
869
Is sent after a successful transfer of secret from the Mandos
870
server to mandos-client
875
@dbus.service.signal(_interface, signature=u"s")
876
def Rejected(self, reason):
880
# NeedApproval - signal
881
@dbus.service.signal(_interface, signature=u"tb")
882
def NeedApproval(self, timeout, default):
889
@dbus.service.method(_interface, in_signature=u"b")
890
def Approve(self, value):
894
@dbus.service.method(_interface)
896
return self.checked_ok()
899
@dbus.service.method(_interface)
904
# StartChecker - method
905
@dbus.service.method(_interface)
906
def StartChecker(self):
911
@dbus.service.method(_interface)
916
# StopChecker - method
917
@dbus.service.method(_interface)
918
def StopChecker(self):
923
# ApprovalPending - property
924
@dbus_service_property(_interface, signature=u"b", access=u"read")
925
def ApprovalPending_dbus_property(self):
926
return dbus.Boolean(bool(self.approvals_pending))
928
# ApprovedByDefault - property
929
@dbus_service_property(_interface, signature=u"b",
931
def ApprovedByDefault_dbus_property(self, value=None):
932
if value is None: # get
933
return dbus.Boolean(self.approved_by_default)
934
self.approved_by_default = bool(value)
936
self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
937
dbus.Boolean(value, variant_level=1))
939
# ApprovalDelay - property
940
@dbus_service_property(_interface, signature=u"t",
942
def ApprovalDelay_dbus_property(self, value=None):
943
if value is None: # get
944
return dbus.UInt64(self.approval_delay_milliseconds())
945
self.approval_delay = datetime.timedelta(0, 0, 0, value)
947
self.PropertyChanged(dbus.String(u"ApprovalDelay"),
948
dbus.UInt64(value, variant_level=1))
950
# ApprovalDuration - property
951
@dbus_service_property(_interface, signature=u"t",
953
def ApprovalDuration_dbus_property(self, value=None):
954
if value is None: # get
955
return dbus.UInt64(self._timedelta_to_milliseconds(
956
self.approval_duration))
957
self.approval_duration = datetime.timedelta(0, 0, 0, value)
959
self.PropertyChanged(dbus.String(u"ApprovalDuration"),
960
dbus.UInt64(value, variant_level=1))
963
@dbus_service_property(_interface, signature=u"s", access=u"read")
964
def Name_dbus_property(self):
965
return dbus.String(self.name)
967
# Fingerprint - property
968
@dbus_service_property(_interface, signature=u"s", access=u"read")
969
def Fingerprint_dbus_property(self):
970
return dbus.String(self.fingerprint)
973
@dbus_service_property(_interface, signature=u"s",
975
def Host_dbus_property(self, value=None):
976
if value is None: # get
977
return dbus.String(self.host)
980
self.PropertyChanged(dbus.String(u"Host"),
981
dbus.String(value, variant_level=1))
984
@dbus_service_property(_interface, signature=u"s", access=u"read")
985
def Created_dbus_property(self):
986
return dbus.String(self._datetime_to_dbus(self.created))
988
# LastEnabled - property
989
@dbus_service_property(_interface, signature=u"s", access=u"read")
990
def LastEnabled_dbus_property(self):
991
if self.last_enabled is None:
992
return dbus.String(u"")
993
return dbus.String(self._datetime_to_dbus(self.last_enabled))
996
@dbus_service_property(_interface, signature=u"b",
998
def Enabled_dbus_property(self, value=None):
999
if value is None: # get
1000
return dbus.Boolean(self.enabled)
1006
# LastCheckedOK - property
1007
@dbus_service_property(_interface, signature=u"s",
1008
access=u"readwrite")
1009
def LastCheckedOK_dbus_property(self, value=None):
1010
if value is not None:
1013
if self.last_checked_ok is None:
1014
return dbus.String(u"")
1015
return dbus.String(self._datetime_to_dbus(self
1018
# Timeout - property
1019
@dbus_service_property(_interface, signature=u"t",
1020
access=u"readwrite")
1021
def Timeout_dbus_property(self, value=None):
1022
if value is None: # get
1023
return dbus.UInt64(self.timeout_milliseconds())
1024
self.timeout = datetime.timedelta(0, 0, 0, value)
1026
self.PropertyChanged(dbus.String(u"Timeout"),
1027
dbus.UInt64(value, variant_level=1))
1028
if getattr(self, u"disable_initiator_tag", None) is None:
1030
# Reschedule timeout
1031
gobject.source_remove(self.disable_initiator_tag)
1032
self.disable_initiator_tag = None
1033
time_to_die = (self.
1034
_timedelta_to_milliseconds((self
1039
if time_to_die <= 0:
1040
# The timeout has passed
1043
self.disable_initiator_tag = (gobject.timeout_add
1044
(time_to_die, self.disable))
1046
# Interval - property
1047
@dbus_service_property(_interface, signature=u"t",
1048
access=u"readwrite")
1049
def Interval_dbus_property(self, value=None):
1050
if value is None: # get
1051
return dbus.UInt64(self.interval_milliseconds())
1052
self.interval = datetime.timedelta(0, 0, 0, value)
1054
self.PropertyChanged(dbus.String(u"Interval"),
1055
dbus.UInt64(value, variant_level=1))
1056
if getattr(self, u"checker_initiator_tag", None) is None:
1058
# Reschedule checker run
1059
gobject.source_remove(self.checker_initiator_tag)
1060
self.checker_initiator_tag = (gobject.timeout_add
1061
(value, self.start_checker))
1062
self.start_checker() # Start one now, too
1064
# Checker - property
1065
@dbus_service_property(_interface, signature=u"s",
1066
access=u"readwrite")
1067
def Checker_dbus_property(self, value=None):
1068
if value is None: # get
1069
return dbus.String(self.checker_command)
1070
self.checker_command = value
1072
self.PropertyChanged(dbus.String(u"Checker"),
1073
dbus.String(self.checker_command,
1076
# CheckerRunning - property
1077
@dbus_service_property(_interface, signature=u"b",
1078
access=u"readwrite")
1079
def CheckerRunning_dbus_property(self, value=None):
1080
if value is None: # get
1081
return dbus.Boolean(self.checker is not None)
1083
self.start_checker()
1087
# ObjectPath - property
1088
@dbus_service_property(_interface, signature=u"o", access=u"read")
1089
def ObjectPath_dbus_property(self):
1090
return self.dbus_object_path # is already a dbus.ObjectPath
1093
@dbus_service_property(_interface, signature=u"ay",
1094
access=u"write", byte_arrays=True)
1095
def Secret_dbus_property(self, value):
1096
self.secret = str(value)
1101
class ProxyClient(object):
1102
def __init__(self, child_pipe, fpr, address):
1103
self._pipe = child_pipe
1104
self._pipe.send(('init', fpr, address))
1105
if not self._pipe.recv():
1108
def __getattribute__(self, name):
1109
if(name == '_pipe'):
1110
return super(ProxyClient, self).__getattribute__(name)
1111
self._pipe.send(('getattr', name))
1112
data = self._pipe.recv()
1113
if data[0] == 'data':
1115
if data[0] == 'function':
1116
def func(*args, **kwargs):
1117
self._pipe.send(('funcall', name, args, kwargs))
1118
return self._pipe.recv()[1]
1121
def __setattr__(self, name, value):
1122
if(name == '_pipe'):
1123
return super(ProxyClient, self).__setattr__(name, value)
1124
self._pipe.send(('setattr', name, value))
1127
class ClientHandler(socketserver.BaseRequestHandler, object):
1128
"""A class to handle client connections.
1130
Instantiated once for each connection to handle it.
239
def still_valid(self, now=None):
240
"""Has the timeout not yet passed for this client?"""
242
now = datetime.datetime.now()
243
if self.last_seen is None:
244
return now < (self.created + self.timeout)
246
return now < (self.last_seen + self.timeout)
249
def peer_certificate(session):
250
# If not an OpenPGP certificate...
251
if gnutls.library.functions.gnutls_certificate_type_get\
252
(session._c_object) \
253
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
254
# ...do the normal thing
255
return session.peer_certificate
256
list_size = ctypes.c_uint()
257
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
258
(session._c_object, ctypes.byref(list_size))
259
if list_size.value == 0:
262
return ctypes.string_at(cert.data, cert.size)
265
def fingerprint(openpgp):
266
# New empty GnuTLS certificate
267
crt = gnutls.library.types.gnutls_openpgp_crt_t()
268
gnutls.library.functions.gnutls_openpgp_crt_init\
270
# New GnuTLS "datum" with the OpenPGP public key
271
datum = gnutls.library.types.gnutls_datum_t\
272
(ctypes.cast(ctypes.c_char_p(openpgp),
273
ctypes.POINTER(ctypes.c_ubyte)),
274
ctypes.c_uint(len(openpgp)))
275
# Import the OpenPGP public key into the certificate
276
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
279
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
280
# New buffer for the fingerprint
281
buffer = ctypes.create_string_buffer(20)
282
buffer_length = ctypes.c_size_t()
283
# Get the fingerprint from the certificate into the buffer
284
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
285
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
286
# Deinit the certificate
287
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
288
# Convert the buffer to a Python bytestring
289
fpr = ctypes.string_at(buffer, buffer_length.value)
290
# Convert the bytestring to hexadecimal notation
291
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
295
class tcp_handler(SocketServer.BaseRequestHandler, object):
296
"""A TCP request handler class.
297
Instantiated by IPv6_TCPServer for each request to handle it.
1131
298
Note: This will run in its own forked process."""
1133
300
def handle(self):
1134
with contextlib.closing(self.server.child_pipe) as child_pipe:
1135
logger.info(u"TCP connection from: %s",
1136
unicode(self.client_address))
1137
logger.debug(u"Pipe FD: %d",
1138
self.server.child_pipe.fileno())
1140
session = (gnutls.connection
1141
.ClientSession(self.request,
1143
.X509Credentials()))
1145
# Note: gnutls.connection.X509Credentials is really a
1146
# generic GnuTLS certificate credentials object so long as
1147
# no X.509 keys are added to it. Therefore, we can use it
1148
# here despite using OpenPGP certificates.
1150
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1151
# u"+AES-256-CBC", u"+SHA1",
1152
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1154
# Use a fallback default, since this MUST be set.
1155
priority = self.server.gnutls_priority
1156
if priority is None:
1157
priority = u"NORMAL"
1158
(gnutls.library.functions
1159
.gnutls_priority_set_direct(session._c_object,
1162
# Start communication using the Mandos protocol
1163
# Get protocol number
1164
line = self.request.makefile().readline()
1165
logger.debug(u"Protocol version: %r", line)
1167
if int(line.strip().split()[0]) > 1:
1169
except (ValueError, IndexError, RuntimeError), error:
1170
logger.error(u"Unknown protocol version: %s", error)
1173
# Start GnuTLS connection
1176
except gnutls.errors.GNUTLSError, error:
1177
logger.warning(u"Handshake failed: %s", error)
1178
# Do not run session.bye() here: the session is not
1179
# established. Just abandon the request.
1181
logger.debug(u"Handshake succeeded")
1183
approval_required = False
1186
fpr = self.fingerprint(self.peer_certificate
1188
except (TypeError, gnutls.errors.GNUTLSError), error:
1189
logger.warning(u"Bad certificate: %s", error)
1191
logger.debug(u"Fingerprint: %s", fpr)
1194
client = ProxyClient(child_pipe, fpr,
1195
self.client_address)
1199
if client.approval_delay:
1200
delay = client.approval_delay
1201
client.approvals_pending += 1
1202
approval_required = True
1205
if not client.enabled:
1206
logger.warning(u"Client %s is disabled",
1208
if self.server.use_dbus:
1210
client.Rejected("Disabled")
1213
if client._approved or not client.approval_delay:
1214
#We are approved or approval is disabled
1216
elif client._approved is None:
1217
logger.info(u"Client %s needs approval",
1219
if self.server.use_dbus:
1221
client.NeedApproval(
1222
client.approval_delay_milliseconds(),
1223
client.approved_by_default)
1225
logger.warning(u"Client %s was not approved",
1227
if self.server.use_dbus:
1229
client.Rejected("Denied")
1232
#wait until timeout or approved
1233
#x = float(client._timedelta_to_milliseconds(delay))
1234
time = datetime.datetime.now()
1235
client.changedstate.acquire()
1236
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1237
client.changedstate.release()
1238
time2 = datetime.datetime.now()
1239
if (time2 - time) >= delay:
1240
if not client.approved_by_default:
1241
logger.warning("Client %s timed out while"
1242
" waiting for approval",
1244
if self.server.use_dbus:
1246
client.Rejected("Timed out")
1251
delay -= time2 - time
1254
while sent_size < len(client.secret):
1256
sent = session.send(client.secret[sent_size:])
1257
except (gnutls.errors.GNUTLSError), error:
1258
logger.warning("gnutls send failed")
1260
logger.debug(u"Sent: %d, remaining: %d",
1261
sent, len(client.secret)
1262
- (sent_size + sent))
1265
logger.info(u"Sending secret to %s", client.name)
1266
# bump the timeout as if seen
1268
if self.server.use_dbus:
1273
if approval_required:
1274
client.approvals_pending -= 1
1277
except (gnutls.errors.GNUTLSError), error:
1278
logger.warning("GnuTLS bye failed")
1281
def peer_certificate(session):
1282
"Return the peer's OpenPGP certificate as a bytestring"
1283
# If not an OpenPGP certificate...
1284
if (gnutls.library.functions
1285
.gnutls_certificate_type_get(session._c_object)
1286
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1287
# ...do the normal thing
1288
return session.peer_certificate
1289
list_size = ctypes.c_uint(1)
1290
cert_list = (gnutls.library.functions
1291
.gnutls_certificate_get_peers
1292
(session._c_object, ctypes.byref(list_size)))
1293
if not bool(cert_list) and list_size.value != 0:
1294
raise gnutls.errors.GNUTLSError(u"error getting peer"
1296
if list_size.value == 0:
1299
return ctypes.string_at(cert.data, cert.size)
1302
def fingerprint(openpgp):
1303
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1304
# New GnuTLS "datum" with the OpenPGP public key
1305
datum = (gnutls.library.types
1306
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1309
ctypes.c_uint(len(openpgp))))
1310
# New empty GnuTLS certificate
1311
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1312
(gnutls.library.functions
1313
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1314
# Import the OpenPGP public key into the certificate
1315
(gnutls.library.functions
1316
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1317
gnutls.library.constants
1318
.GNUTLS_OPENPGP_FMT_RAW))
1319
# Verify the self signature in the key
1320
crtverify = ctypes.c_uint()
1321
(gnutls.library.functions
1322
.gnutls_openpgp_crt_verify_self(crt, 0,
1323
ctypes.byref(crtverify)))
1324
if crtverify.value != 0:
1325
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1326
raise (gnutls.errors.CertificateSecurityError
1328
# New buffer for the fingerprint
1329
buf = ctypes.create_string_buffer(20)
1330
buf_len = ctypes.c_size_t()
1331
# Get the fingerprint from the certificate into the buffer
1332
(gnutls.library.functions
1333
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1334
ctypes.byref(buf_len)))
1335
# Deinit the certificate
1336
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1337
# Convert the buffer to a Python bytestring
1338
fpr = ctypes.string_at(buf, buf_len.value)
1339
# Convert the bytestring to hexadecimal notation
1340
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1344
class MultiprocessingMixIn(object):
1345
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1346
def sub_process_main(self, request, address):
1348
self.finish_request(request, address)
1350
self.handle_error(request, address)
1351
self.close_request(request)
1353
def process_request(self, request, address):
1354
"""Start a new process to process the request."""
1355
multiprocessing.Process(target = self.sub_process_main,
1356
args = (request, address)).start()
1358
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1359
""" adds a pipe to the MixIn """
1360
def process_request(self, request, client_address):
1361
"""Overrides and wraps the original process_request().
1363
This function creates a new pipe in self.pipe
1365
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1367
super(MultiprocessingMixInWithPipe,
1368
self).process_request(request, client_address)
1369
self.child_pipe.close()
1370
self.add_pipe(parent_pipe)
1372
def add_pipe(self, parent_pipe):
1373
"""Dummy function; override as necessary"""
1376
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1377
socketserver.TCPServer, object):
1378
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
302
sys.stderr.write(u"TCP request came\n")
303
sys.stderr.write(u"Request: %s\n" % self.request)
304
sys.stderr.write(u"Client Address: %s\n"
305
% unicode(self.client_address))
306
sys.stderr.write(u"Server: %s\n" % self.server)
307
session = gnutls.connection.ClientSession(self.request,
311
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
312
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
314
priority = "SECURE256"
316
gnutls.library.functions.gnutls_priority_set_direct\
317
(session._c_object, priority, None);
321
except gnutls.errors.GNUTLSError, error:
323
sys.stderr.write(u"Handshake failed: %s\n" % error)
324
# Do not run session.bye() here: the session is not
325
# established. Just abandon the request.
328
fpr = fingerprint(peer_certificate(session))
329
except (TypeError, gnutls.errors.GNUTLSError), error:
331
sys.stderr.write(u"Bad certificate: %s\n" % error)
335
sys.stderr.write(u"Fingerprint: %s\n" % fpr)
338
if c.fingerprint == fpr:
341
# Have to check if client.still_valid(), since it is possible
342
# that the client timed out while establishing the GnuTLS
344
if (not client) or (not client.still_valid()):
347
sys.stderr.write(u"Client %(name)s is invalid\n"
350
sys.stderr.write(u"Client not found for "
351
u"fingerprint: %s\n" % fpr)
355
while sent_size < len(client.secret):
356
sent = session.send(client.secret[sent_size:])
358
sys.stderr.write(u"Sent: %d, remaining: %d\n"
359
% (sent, len(client.secret)
360
- (sent_size + sent)))
365
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
366
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1381
enabled: Boolean; whether this server is activated yet
1382
interface: None or a network interface name (string)
1383
use_ipv6: Boolean; to use IPv6 or not
368
options: Command line options
369
clients: Set() of Client objects
1385
def __init__(self, server_address, RequestHandlerClass,
1386
interface=None, use_ipv6=True):
1387
self.interface = interface
1389
self.address_family = socket.AF_INET6
1390
socketserver.TCPServer.__init__(self, server_address,
1391
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)
1392
380
def server_bind(self):
1393
381
"""This overrides the normal server_bind() function
1394
382
to bind to an interface if one was specified, and also NOT to
1395
383
bind to an address or port if they were not specified."""
1396
if self.interface is not None:
1397
if SO_BINDTODEVICE is None:
1398
logger.error(u"SO_BINDTODEVICE does not exist;"
1399
u" cannot bind to interface %s",
1403
self.socket.setsockopt(socket.SOL_SOCKET,
1407
except socket.error, error:
1408
if error[0] == errno.EPERM:
1409
logger.error(u"No permission to"
1410
u" bind to interface %s",
1412
elif error[0] == errno.ENOPROTOOPT:
1413
logger.error(u"SO_BINDTODEVICE not available;"
1414
u" cannot bind to interface %s",
384
if self.options.interface:
385
if not hasattr(socket, "SO_BINDTODEVICE"):
386
# From /usr/include/asm-i486/socket.h
387
socket.SO_BINDTODEVICE = 25
389
self.socket.setsockopt(socket.SOL_SOCKET,
390
socket.SO_BINDTODEVICE,
391
self.options.interface)
392
except socket.error, error:
393
if error[0] == errno.EPERM:
394
sys.stderr.write(u"Warning: No permission to" \
395
u" bind to interface %s\n"
396
% self.options.interface)
1418
399
# Only bind(2) the socket if we really need to.
1419
400
if self.server_address[0] or self.server_address[1]:
1420
401
if not self.server_address[0]:
1421
if self.address_family == socket.AF_INET6:
1422
any_address = u"::" # in6addr_any
1424
any_address = socket.INADDR_ANY
1425
self.server_address = (any_address,
403
self.server_address = (in6addr_any,
1426
404
self.server_address[1])
1427
elif not self.server_address[1]:
405
elif self.server_address[1] is None:
1428
406
self.server_address = (self.server_address[0],
1430
# if self.interface:
1431
# self.server_address = (self.server_address[0],
1436
return socketserver.TCPServer.server_bind(self)
1439
class MandosServer(IPv6_TCPServer):
1443
clients: set of Client objects
1444
gnutls_priority GnuTLS priority string
1445
use_dbus: Boolean; to emit D-Bus signals or not
1447
Assumes a gobject.MainLoop event loop.
1449
def __init__(self, server_address, RequestHandlerClass,
1450
interface=None, use_ipv6=True, clients=None,
1451
gnutls_priority=None, use_dbus=True):
1452
self.enabled = False
1453
self.clients = clients
1454
if self.clients is None:
1455
self.clients = set()
1456
self.use_dbus = use_dbus
1457
self.gnutls_priority = gnutls_priority
1458
IPv6_TCPServer.__init__(self, server_address,
1459
RequestHandlerClass,
1460
interface = interface,
1461
use_ipv6 = use_ipv6)
1462
def server_activate(self):
1464
return socketserver.TCPServer.server_activate(self)
1467
def add_pipe(self, parent_pipe):
1468
# Call "handle_ipc" for both data and EOF events
1469
gobject.io_add_watch(parent_pipe.fileno(),
1470
gobject.IO_IN | gobject.IO_HUP,
1471
functools.partial(self.handle_ipc,
1472
parent_pipe = parent_pipe))
1474
def handle_ipc(self, source, condition, parent_pipe=None,
1475
client_object=None):
1477
gobject.IO_IN: u"IN", # There is data to read.
1478
gobject.IO_OUT: u"OUT", # Data can be written (without
1480
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1481
gobject.IO_ERR: u"ERR", # Error condition.
1482
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1483
# broken, usually for pipes and
1486
conditions_string = ' | '.join(name
1488
condition_names.iteritems()
1489
if cond & condition)
1490
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1493
# error or the other end of multiprocessing.Pipe has closed
1494
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1497
# Read a request from the child
1498
request = parent_pipe.recv()
1499
logger.debug(u"IPC request: %s", repr(request))
1500
command = request[0]
1502
if command == 'init':
1504
address = request[2]
1506
for c in self.clients:
1507
if c.fingerprint == fpr:
1511
logger.warning(u"Client not found for fingerprint: %s, ad"
1512
u"dress: %s", fpr, address)
1515
mandos_dbus_service.ClientNotFound(fpr, address)
1516
parent_pipe.send(False)
1519
gobject.io_add_watch(parent_pipe.fileno(),
1520
gobject.IO_IN | gobject.IO_HUP,
1521
functools.partial(self.handle_ipc,
1522
parent_pipe = parent_pipe,
1523
client_object = client))
1524
parent_pipe.send(True)
1525
# remove the old hook in favor of the new above hook on same fileno
1527
if command == 'funcall':
1528
funcname = request[1]
1532
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1534
if command == 'getattr':
1535
attrname = request[1]
1536
if callable(client_object.__getattribute__(attrname)):
1537
parent_pipe.send(('function',))
1539
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1541
if command == 'setattr':
1542
attrname = request[1]
1544
setattr(client_object, attrname, value)
408
return super(type(self), self).server_bind()
1549
411
def string_to_delta(interval):
1550
412
"""Parse a string and return a datetime.timedelta
1552
>>> string_to_delta(u'7d')
414
>>> string_to_delta('7d')
1553
415
datetime.timedelta(7)
1554
>>> string_to_delta(u'60s')
416
>>> string_to_delta('60s')
1555
417
datetime.timedelta(0, 60)
1556
>>> string_to_delta(u'60m')
418
>>> string_to_delta('60m')
1557
419
datetime.timedelta(0, 3600)
1558
>>> string_to_delta(u'24h')
420
>>> string_to_delta('24h')
1559
421
datetime.timedelta(1)
1560
422
>>> string_to_delta(u'1w')
1561
423
datetime.timedelta(7)
1562
>>> string_to_delta(u'5m 30s')
1563
datetime.timedelta(0, 330)
1565
timevalue = datetime.timedelta(0)
1566
for s in interval.split():
1568
suffix = unicode(s[-1])
1571
delta = datetime.timedelta(value)
1572
elif suffix == u"s":
1573
delta = datetime.timedelta(0, value)
1574
elif suffix == u"m":
1575
delta = datetime.timedelta(0, 0, 0, 0, value)
1576
elif suffix == u"h":
1577
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1578
elif suffix == u"w":
1579
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1581
raise ValueError(u"Unknown suffix %r" % suffix)
1582
except (ValueError, IndexError), e:
1583
raise ValueError(e.message)
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)
457
sys.stderr.write(u"Adding service '%s' of type '%s' ...\n"
458
% (serviceName, serviceType))
461
serviceInterface, # interface
462
avahi.PROTO_INET6, # protocol
463
dbus.UInt32(0), # flags
464
serviceName, serviceType,
466
dbus.UInt16(servicePort),
467
avahi.string_array_to_txt_array(serviceTXT))
471
def remove_service():
472
"""From the Avahi server example code"""
475
if not group is None:
479
def server_state_changed(state):
480
"""From the Avahi server example code"""
481
if state == avahi.SERVER_COLLISION:
482
sys.stderr.write(u"WARNING: Server name collision\n")
484
elif state == avahi.SERVER_RUNNING:
488
def entry_group_state_changed(state, error):
489
"""From the Avahi server example code"""
490
global serviceName, server, rename_count
493
sys.stderr.write(u"state change: %i\n" % state)
495
if state == avahi.ENTRY_GROUP_ESTABLISHED:
497
sys.stderr.write(u"Service established.\n")
498
elif state == avahi.ENTRY_GROUP_COLLISION:
500
rename_count = rename_count - 1
502
name = server.GetAlternativeServiceName(name)
503
sys.stderr.write(u"WARNING: Service name collision, "
504
u"changing name to '%s' ...\n" % name)
509
sys.stderr.write(u"ERROR: No suitable service name found "
510
u"after %i retries, exiting.\n"
513
elif state == avahi.ENTRY_GROUP_FAILURE:
514
sys.stderr.write(u"Error in group state changed %s\n"
1588
520
def if_nametoindex(interface):
1589
"""Call the C function if_nametoindex(), or equivalent
1591
Note: This function cannot accept a unicode string."""
1592
global if_nametoindex
521
"""Call the C function if_nametoindex()"""
1594
if_nametoindex = (ctypes.cdll.LoadLibrary
1595
(ctypes.util.find_library(u"c"))
523
libc = ctypes.cdll.LoadLibrary("libc.so.6")
524
return libc.if_nametoindex(interface)
1597
525
except (OSError, AttributeError):
1598
logger.warning(u"Doing if_nametoindex the hard way")
1599
def if_nametoindex(interface):
1600
"Get an interface index the hard way, i.e. using fcntl()"
1601
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1602
with contextlib.closing(socket.socket()) as s:
1603
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1604
struct.pack(str(u"16s16x"),
1606
interface_index = struct.unpack(str(u"I"),
1608
return interface_index
1609
return if_nametoindex(interface)
1612
def daemon(nochdir = False, noclose = False):
1613
"""See daemon(3). Standard BSD Unix function.
1615
This should really exist as os.daemon, but it doesn't (yet)."""
1624
# Close all standard open file descriptors
1625
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1626
if not stat.S_ISCHR(os.fstat(null).st_mode):
1627
raise OSError(errno.ENODEV,
1628
u"%s not a character device"
1630
os.dup2(null, sys.stdin.fileno())
1631
os.dup2(null, sys.stdout.fileno())
1632
os.dup2(null, sys.stderr.fileno())
1639
##################################################################
1640
# Parsing of options, both command line and config file
1642
parser = optparse.OptionParser(version = "%%prog %s" % version)
1643
parser.add_option("-i", u"--interface", type=u"string",
1644
metavar="IF", help=u"Bind to interface IF")
1645
parser.add_option("-a", u"--address", type=u"string",
1646
help=u"Address to listen for requests on")
1647
parser.add_option("-p", u"--port", type=u"int",
1648
help=u"Port number to receive requests on")
1649
parser.add_option("--check", action=u"store_true",
1650
help=u"Run self-test")
1651
parser.add_option("--debug", action=u"store_true",
1652
help=u"Debug mode; run in foreground and log to"
1654
parser.add_option("--debuglevel", type=u"string", metavar="Level",
1655
help=u"Debug level for stdout output")
1656
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1657
u" priority string (see GnuTLS documentation)")
1658
parser.add_option("--servicename", type=u"string",
1659
metavar=u"NAME", help=u"Zeroconf service name")
1660
parser.add_option("--configdir", type=u"string",
1661
default=u"/etc/mandos", metavar=u"DIR",
1662
help=u"Directory to search for configuration"
1664
parser.add_option("--no-dbus", action=u"store_false",
1665
dest=u"use_dbus", help=u"Do not provide D-Bus"
1666
u" system bus interface")
1667
parser.add_option("--no-ipv6", action=u"store_false",
1668
dest=u"use_ipv6", help=u"Do not use IPv6")
1669
options = parser.parse_args()[0]
526
if "struct" not in sys.modules:
528
if "fcntl" not in sys.modules:
530
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
532
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
533
struct.pack("16s16x", interface))
535
interface_index = struct.unpack("I", ifreq[16:20])[0]
536
return interface_index
539
if __name__ == '__main__':
540
parser = OptionParser()
541
parser.add_option("-i", "--interface", type="string",
542
default=None, metavar="IF",
543
help="Bind to interface IF")
544
parser.add_option("--cert", type="string", default="cert.pem",
546
help="Public key certificate PEM file to use")
547
parser.add_option("--key", type="string", default="key.pem",
549
help="Private key PEM file to use")
550
parser.add_option("--ca", type="string", default="ca.pem",
552
help="Certificate Authority certificate PEM file to use")
553
parser.add_option("--crl", type="string", default="crl.pem",
555
help="Certificate Revokation List PEM file to use")
556
parser.add_option("-p", "--port", type="int", default=None,
557
help="Port number to receive requests on")
558
parser.add_option("--timeout", type="string", # Parsed later
560
help="Amount of downtime allowed for clients")
561
parser.add_option("--interval", type="string", # Parsed later
563
help="How often to check that a client is up")
564
parser.add_option("--check", action="store_true", default=False,
565
help="Run self-test")
566
parser.add_option("--debug", action="store_true", default=False,
568
(options, args) = parser.parse_args()
1671
570
if options.check:
1673
572
doctest.testmod()
1676
# Default values for config file for server-global settings
1677
server_defaults = { u"interface": u"",
1682
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1683
u"servicename": u"Mandos",
1684
u"use_dbus": u"True",
1685
u"use_ipv6": u"True",
1689
# Parse config file for server-global settings
1690
server_config = configparser.SafeConfigParser(server_defaults)
1692
server_config.read(os.path.join(options.configdir,
1694
# Convert the SafeConfigParser object to a dict
1695
server_settings = server_config.defaults()
1696
# Use the appropriate methods on the non-string config options
1697
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1698
server_settings[option] = server_config.getboolean(u"DEFAULT",
1700
if server_settings["port"]:
1701
server_settings["port"] = server_config.getint(u"DEFAULT",
1705
# Override the settings from the config file with command line
1707
for option in (u"interface", u"address", u"port", u"debug",
1708
u"priority", u"servicename", u"configdir",
1709
u"use_dbus", u"use_ipv6", u"debuglevel"):
1710
value = getattr(options, option)
1711
if value is not None:
1712
server_settings[option] = value
1714
# Force all strings to be unicode
1715
for option in server_settings.keys():
1716
if type(server_settings[option]) is str:
1717
server_settings[option] = unicode(server_settings[option])
1718
# Now we have our good server settings in "server_settings"
1720
##################################################################
1723
debug = server_settings[u"debug"]
1724
debuglevel = server_settings[u"debuglevel"]
1725
use_dbus = server_settings[u"use_dbus"]
1726
use_ipv6 = server_settings[u"use_ipv6"]
1728
if server_settings[u"servicename"] != u"Mandos":
1729
syslogger.setFormatter(logging.Formatter
1730
(u'Mandos (%s) [%%(process)d]:'
1731
u' %%(levelname)s: %%(message)s'
1732
% server_settings[u"servicename"]))
1734
# Parse config file with clients
1735
client_defaults = { u"timeout": u"1h",
1737
u"checker": u"fping -q -- %%(host)s",
1739
u"approval_delay": u"0s",
1740
u"approval_duration": u"1s",
1742
client_config = configparser.SafeConfigParser(client_defaults)
1743
client_config.read(os.path.join(server_settings[u"configdir"],
1746
global mandos_dbus_service
1747
mandos_dbus_service = None
1749
tcp_server = MandosServer((server_settings[u"address"],
1750
server_settings[u"port"]),
1752
interface=(server_settings[u"interface"]
1756
server_settings[u"priority"],
1758
pidfilename = u"/var/run/mandos.pid"
1760
pidfile = open(pidfilename, u"w")
1762
logger.error(u"Could not open file %r", pidfilename)
1765
uid = pwd.getpwnam(u"_mandos").pw_uid
1766
gid = pwd.getpwnam(u"_mandos").pw_gid
1769
uid = pwd.getpwnam(u"mandos").pw_uid
1770
gid = pwd.getpwnam(u"mandos").pw_gid
1773
uid = pwd.getpwnam(u"nobody").pw_uid
1774
gid = pwd.getpwnam(u"nobody").pw_gid
1781
except OSError, error:
1782
if error[0] != errno.EPERM:
1785
if not debug and not debuglevel:
1786
syslogger.setLevel(logging.WARNING)
1787
console.setLevel(logging.WARNING)
1789
level = getattr(logging, debuglevel.upper())
1790
syslogger.setLevel(level)
1791
console.setLevel(level)
1794
# Enable all possible GnuTLS debugging
1796
# "Use a log level over 10 to enable all debugging options."
1798
gnutls.library.functions.gnutls_global_set_log_level(11)
1800
@gnutls.library.types.gnutls_log_func
1801
def debug_gnutls(level, string):
1802
logger.debug(u"GnuTLS: %s", string[:-1])
1804
(gnutls.library.functions
1805
.gnutls_global_set_log_function(debug_gnutls))
1807
# Redirect stdin so all checkers get /dev/null
1808
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1809
os.dup2(null, sys.stdin.fileno())
1813
# No console logging
1814
logger.removeHandler(console)
1818
# From the Avahi example code
575
# Parse the time arguments
577
options.timeout = string_to_delta(options.timeout)
579
parser.error("option --timeout: Unparseable time")
581
options.interval = string_to_delta(options.interval)
583
parser.error("option --interval: Unparseable time")
586
defaults = { "checker": "sleep 1; fping -q -- %%(fqdn)s" }
587
client_config = ConfigParser.SafeConfigParser(defaults)
588
#client_config.readfp(open("secrets.conf"), "secrets.conf")
589
client_config.read("mandos-clients.conf")
591
# From the Avahi server example code
1819
592
DBusGMainLoop(set_as_default=True )
1820
593
main_loop = gobject.MainLoop()
1821
594
bus = dbus.SystemBus()
1822
# End of Avahi example code
1825
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1826
bus, do_not_queue=True)
1827
except dbus.exceptions.NameExistsException, e:
1828
logger.error(unicode(e) + u", disabling D-Bus")
1830
server_settings[u"use_dbus"] = False
1831
tcp_server.use_dbus = False
1832
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1833
service = AvahiService(name = server_settings[u"servicename"],
1834
servicetype = u"_mandos._tcp",
1835
protocol = protocol, bus = bus)
1836
if server_settings["interface"]:
1837
service.interface = (if_nametoindex
1838
(str(server_settings[u"interface"])))
1841
# Close all input and output, do double fork, etc.
1844
global multiprocessing_manager
1845
multiprocessing_manager = multiprocessing.Manager()
1847
client_class = Client
1849
client_class = functools.partial(ClientDBus, bus = bus)
1850
def client_config_items(config, section):
1851
special_settings = {
1852
"approved_by_default":
1853
lambda: config.getboolean(section,
1854
"approved_by_default"),
1856
for name, value in config.items(section):
1858
yield (name, special_settings[name]())
1862
tcp_server.clients.update(set(
1863
client_class(name = section,
1864
config= dict(client_config_items(
1865
client_config, section)))
1866
for section in client_config.sections()))
1867
if not tcp_server.clients:
1868
logger.warning(u"No clients defined")
1873
pidfile.write(str(pid) + "\n")
1876
logger.error(u"Could not write to file %r with PID %d",
1879
# "pidfile" was never created
1884
signal.signal(signal.SIGINT, signal.SIG_IGN)
1885
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1886
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1889
class MandosDBusService(dbus.service.Object):
1890
"""A D-Bus proxy object"""
1892
dbus.service.Object.__init__(self, bus, u"/")
1893
_interface = u"se.bsnet.fukt.Mandos"
1895
@dbus.service.signal(_interface, signature=u"o")
1896
def ClientAdded(self, objpath):
1900
@dbus.service.signal(_interface, signature=u"ss")
1901
def ClientNotFound(self, fingerprint, address):
1905
@dbus.service.signal(_interface, signature=u"os")
1906
def ClientRemoved(self, objpath, name):
1910
@dbus.service.method(_interface, out_signature=u"ao")
1911
def GetAllClients(self):
1913
return dbus.Array(c.dbus_object_path
1914
for c in tcp_server.clients)
1916
@dbus.service.method(_interface,
1917
out_signature=u"a{oa{sv}}")
1918
def GetAllClientsWithProperties(self):
1920
return dbus.Dictionary(
1921
((c.dbus_object_path, c.GetAll(u""))
1922
for c in tcp_server.clients),
1923
signature=u"oa{sv}")
1925
@dbus.service.method(_interface, in_signature=u"o")
1926
def RemoveClient(self, object_path):
1928
for c in tcp_server.clients:
1929
if c.dbus_object_path == object_path:
1930
tcp_server.clients.remove(c)
1931
c.remove_from_connection()
1932
# Don't signal anything except ClientRemoved
1933
c.disable(quiet=True)
1935
self.ClientRemoved(object_path, c.name)
1937
raise KeyError(object_path)
1941
mandos_dbus_service = MandosDBusService()
1944
"Cleanup function; run on exit"
1947
while tcp_server.clients:
1948
client = tcp_server.clients.pop()
1950
client.remove_from_connection()
1951
client.disable_hook = None
1952
# Don't signal anything except ClientRemoved
1953
client.disable(quiet=True)
1956
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
1959
atexit.register(cleanup)
1961
for client in tcp_server.clients:
1964
mandos_dbus_service.ClientAdded(client.dbus_object_path)
1968
tcp_server.server_activate()
1970
# Find out what port we got
1971
service.port = tcp_server.socket.getsockname()[1]
1973
logger.info(u"Now listening on address %r, port %d,"
1974
" flowinfo %d, scope_id %d"
1975
% tcp_server.socket.getsockname())
1977
logger.info(u"Now listening on address %r, port %d"
1978
% tcp_server.socket.getsockname())
1980
#service.interface = tcp_server.socket.getsockname()[3]
1983
# From the Avahi example code
1986
except dbus.exceptions.DBusException, error:
1987
logger.critical(u"DBusException: %s", error)
1990
# End of Avahi example code
1992
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
1993
lambda *args, **kwargs:
1994
(tcp_server.handle_request
1995
(*args[2:], **kwargs) or True))
1997
logger.debug(u"Starting main loop")
595
server = dbus.Interface(
596
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
597
avahi.DBUS_INTERFACE_SERVER )
598
# End of Avahi example code
600
debug = options.debug
603
def remove_from_clients(client):
604
clients.remove(client)
607
sys.stderr.write(u"No clients left, exiting\n")
610
clients.update(Set(Client(name=section, options=options,
611
stop_hook = remove_from_clients,
612
**(dict(client_config\
614
for section in client_config.sections()))
615
for client in clients:
618
tcp_server = IPv6_TCPServer((None, options.port),
622
# Find out what random port we got
623
servicePort = tcp_server.socket.getsockname()[1]
625
sys.stderr.write(u"Now listening on port %d\n" % servicePort)
627
if options.interface is not None:
628
serviceInterface = if_nametoindex(options.interface)
630
# From the Avahi server example code
631
server.connect_to_signal("StateChanged", server_state_changed)
632
server_state_changed(server.GetState())
633
# End of Avahi example code
635
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
636
lambda *args, **kwargs:
637
tcp_server.handle_request(*args[2:],
1999
except AvahiError, error:
2000
logger.critical(u"AvahiError: %s", error)
2003
641
except KeyboardInterrupt:
2006
logger.debug(u"Server received KeyboardInterrupt")
2007
logger.debug(u"Server exiting")
2008
# Must run before the D-Bus bus name gets deregistered
2011
if __name__ == '__main__':
646
# From the Avahi server example code
647
if not group is None:
649
# End of Avahi example code
651
for client in clients:
652
client.stop_hook = None