136
110
max_renames: integer; maximum number of renames
137
111
rename_count: integer; counter so we only rename after collisions
138
112
a sensible number of times
139
group: D-Bus Entry Group
141
bus: dbus.SystemBus()
143
114
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
144
servicetype = None, port = None, TXT = None,
145
domain = "", host = "", max_renames = 32768,
146
protocol = avahi.PROTO_UNSPEC, bus = None):
115
type = None, port = None, TXT = None, domain = "",
116
host = "", max_renames = 12):
147
117
self.interface = interface
149
self.type = servicetype
151
self.TXT = TXT if TXT is not None else []
152
125
self.domain = domain
154
127
self.rename_count = 0
155
self.max_renames = max_renames
156
self.protocol = protocol
157
self.group = None # our entry group
160
self.entry_group_state_changed_match = None
161
128
def rename(self):
162
129
"""Derived from the Avahi example code"""
163
130
if self.rename_count >= self.max_renames:
164
logger.critical("No suitable Zeroconf service name found"
165
" after %i retries, exiting.",
131
logger.critical(u"No suitable service name found after %i"
132
u" retries, exiting.", rename_count)
167
133
raise AvahiServiceError("Too many renames")
168
self.name = unicode(self.server
169
.GetAlternativeServiceName(self.name))
170
logger.info("Changing Zeroconf service name to %r ...",
134
name = server.GetAlternativeServiceName(name)
135
logger.error(u"Changing name to %r ...", name)
175
except dbus.exceptions.DBusException as error:
176
logger.critical("DBusException: %s", error)
179
138
self.rename_count += 1
180
139
def remove(self):
181
140
"""Derived from the Avahi example code"""
182
if self.entry_group_state_changed_match is not None:
183
self.entry_group_state_changed_match.remove()
184
self.entry_group_state_changed_match = None
185
if self.group is not None:
141
if group is not None:
188
144
"""Derived from the Avahi example code"""
190
if self.group is None:
191
self.group = dbus.Interface(
192
self.bus.get_object(avahi.DBUS_NAME,
193
self.server.EntryGroupNew()),
194
avahi.DBUS_INTERFACE_ENTRY_GROUP)
195
self.entry_group_state_changed_match = (
196
self.group.connect_to_signal(
197
'StateChanged', self.entry_group_state_changed))
198
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
199
self.name, self.type)
200
self.group.AddService(
203
dbus.UInt32(0), # flags
204
self.name, self.type,
205
self.domain, self.host,
206
dbus.UInt16(self.port),
207
avahi.string_array_to_txt_array(self.TXT))
209
def entry_group_state_changed(self, state, error):
210
"""Derived from the Avahi example code"""
211
logger.debug("Avahi entry group state change: %i", state)
213
if state == avahi.ENTRY_GROUP_ESTABLISHED:
214
logger.debug("Zeroconf service established.")
215
elif state == avahi.ENTRY_GROUP_COLLISION:
216
logger.info("Zeroconf service name collision.")
218
elif state == avahi.ENTRY_GROUP_FAILURE:
219
logger.critical("Avahi: Error in group state changed %s",
221
raise AvahiGroupError("State changed: %s"
224
"""Derived from the Avahi example code"""
225
if self.group is not None:
228
except (dbus.exceptions.UnknownMethodException,
229
dbus.exceptions.DBusException) as e:
233
def server_state_changed(self, state, error=None):
234
"""Derived from the Avahi example code"""
235
logger.debug("Avahi server state change: %i", state)
236
bad_states = { avahi.SERVER_INVALID:
237
"Zeroconf server invalid",
238
avahi.SERVER_REGISTERING: None,
239
avahi.SERVER_COLLISION:
240
"Zeroconf server name collision",
241
avahi.SERVER_FAILURE:
242
"Zeroconf server failure" }
243
if state in bad_states:
244
if bad_states[state] is not None:
246
logger.error(bad_states[state])
248
logger.error(bad_states[state] + ": %r", error)
250
elif state == avahi.SERVER_RUNNING:
254
logger.debug("Unknown state: %r", state)
256
logger.debug("Unknown state: %r: %r", state, error)
258
"""Derived from the Avahi example code"""
259
if self.server is None:
260
self.server = dbus.Interface(
261
self.bus.get_object(avahi.DBUS_NAME,
262
avahi.DBUS_PATH_SERVER,
263
follow_name_owner_changes=True),
264
avahi.DBUS_INTERFACE_SERVER)
265
self.server.connect_to_signal("StateChanged",
266
self.server_state_changed)
267
self.server_state_changed(self.server.GetState())
269
class AvahiServiceToSyslog(AvahiService):
271
"""Add the new name to the syslog messages"""
272
ret = AvahiService.rename(self)
273
syslogger.setFormatter(logging.Formatter
274
('Mandos (%s) [%%(process)d]:'
275
' %%(levelname)s: %%(message)s'
279
def _timedelta_to_milliseconds(td):
280
"Convert a datetime.timedelta() to milliseconds"
281
return ((td.days * 24 * 60 * 60 * 1000)
282
+ (td.seconds * 1000)
283
+ (td.microseconds // 1000))
147
group = dbus.Interface\
148
(bus.get_object(avahi.DBUS_NAME,
149
server.EntryGroupNew()),
150
avahi.DBUS_INTERFACE_ENTRY_GROUP)
151
group.connect_to_signal('StateChanged',
152
entry_group_state_changed)
153
logger.debug(u"Adding service '%s' of type '%s' ...",
154
service.name, service.type)
156
self.interface, # interface
157
avahi.PROTO_INET6, # protocol
158
dbus.UInt32(0), # flags
159
self.name, self.type,
160
self.domain, self.host,
161
dbus.UInt16(self.port),
162
avahi.string_array_to_txt_array(self.TXT))
165
# From the Avahi example code:
166
group = None # our entry group
167
# End of Avahi example code
285
170
class Client(object):
286
171
"""A representation of a client host served by this server.
289
_approved: bool(); 'None' if not yet approved/disapproved
290
approval_delay: datetime.timedelta(); Time to wait for approval
291
approval_duration: datetime.timedelta(); Duration of one approval
292
checker: subprocess.Popen(); a running checker process used
293
to see if the client lives.
294
'None' if no process is running.
295
checker_callback_tag: a gobject event source tag, or None
296
checker_command: string; External command which is run to check
297
if client lives. %() expansions are done at
173
name: string; from the config file, used in log messages
174
fingerprint: string (40 or 32 hexadecimal digits); used to
175
uniquely identify the client
176
secret: bytestring; sent verbatim (over TLS) to client
177
fqdn: string (FQDN); available for use by the checker command
178
created: datetime.datetime(); object creation, not client host
179
last_checked_ok: datetime.datetime() or None if not yet checked OK
180
timeout: datetime.timedelta(); How long from last_checked_ok
181
until this client is invalid
182
interval: datetime.timedelta(); How often to start a new checker
183
stop_hook: If set, called by stop() as stop_hook(self)
184
checker: subprocess.Popen(); a running checker process used
185
to see if the client lives.
186
'None' if no process is running.
187
checker_initiator_tag: a gobject event source tag, or None
188
stop_initiator_tag: - '' -
189
checker_callback_tag: - '' -
190
checker_command: string; External command which is run to check if
191
client lives. %() expansions are done at
298
192
runtime with vars(self) as dict, so that for
299
193
instance %(name)s can be used in the command.
300
checker_initiator_tag: a gobject event source tag, or None
301
created: datetime.datetime(); (UTC) object creation
302
client_structure: Object describing what attributes a client has
303
and is used for storing the client at exit
304
current_checker_command: string; current running checker_command
305
disable_initiator_tag: a gobject event source tag, or None
307
fingerprint: string (40 or 32 hexadecimal digits); used to
308
uniquely identify the client
309
host: string; available for use by the checker command
310
interval: datetime.timedelta(); How often to start a new checker
311
last_approval_request: datetime.datetime(); (UTC) or None
312
last_checked_ok: datetime.datetime(); (UTC) or None
313
last_checker_status: integer between 0 and 255 reflecting exit
314
status of last checker. -1 reflect crashed
316
last_enabled: datetime.datetime(); (UTC)
317
name: string; from the config file, used in log messages and
319
secret: bytestring; sent verbatim (over TLS) to client
320
timeout: datetime.timedelta(); How long from last_checked_ok
321
until this client is disabled
322
extended_timeout: extra long timeout when password has been sent
323
runtime_expansions: Allowed attributes for runtime expansion.
324
expires: datetime.datetime(); time (UTC) when a client will be
195
_timeout: Real variable for 'timeout'
196
_interval: Real variable for 'interval'
197
_timeout_milliseconds: Used when calling gobject.timeout_add()
198
_interval_milliseconds: - '' -
328
runtime_expansions = ("approval_delay", "approval_duration",
329
"created", "enabled", "fingerprint",
330
"host", "interval", "last_checked_ok",
331
"last_enabled", "name", "timeout")
333
def timeout_milliseconds(self):
334
"Return the 'timeout' attribute in milliseconds"
335
return _timedelta_to_milliseconds(self.timeout)
337
def extended_timeout_milliseconds(self):
338
"Return the 'extended_timeout' attribute in milliseconds"
339
return _timedelta_to_milliseconds(self.extended_timeout)
341
def interval_milliseconds(self):
342
"Return the 'interval' attribute in milliseconds"
343
return _timedelta_to_milliseconds(self.interval)
345
def approval_delay_milliseconds(self):
346
return _timedelta_to_milliseconds(self.approval_delay)
348
def __init__(self, name = None, config=None):
200
def _set_timeout(self, timeout):
201
"Setter function for 'timeout' attribute"
202
self._timeout = timeout
203
self._timeout_milliseconds = ((self.timeout.days
204
* 24 * 60 * 60 * 1000)
205
+ (self.timeout.seconds * 1000)
206
+ (self.timeout.microseconds
208
timeout = property(lambda self: self._timeout,
211
def _set_interval(self, interval):
212
"Setter function for 'interval' attribute"
213
self._interval = interval
214
self._interval_milliseconds = ((self.interval.days
215
* 24 * 60 * 60 * 1000)
216
+ (self.interval.seconds
218
+ (self.interval.microseconds
220
interval = property(lambda self: self._interval,
223
def __init__(self, name = None, stop_hook=None, config={}):
349
224
"""Note: the 'checker' key in 'config' sets the
350
225
'checker_command' attribute and *not* the 'checker'
355
logger.debug("Creating client %r", self.name)
228
logger.debug(u"Creating client %r", self.name)
356
229
# Uppercase and remove spaces from fingerprint for later
357
230
# comparison purposes with return value from the fingerprint()
359
self.fingerprint = (config["fingerprint"].upper()
361
logger.debug(" Fingerprint: %s", self.fingerprint)
232
self.fingerprint = config["fingerprint"].upper()\
234
logger.debug(u" Fingerprint: %s", self.fingerprint)
362
235
if "secret" in config:
363
self.secret = config["secret"].decode("base64")
236
self.secret = config["secret"].decode(u"base64")
364
237
elif "secfile" in config:
365
with open(os.path.expanduser(os.path.expandvars
366
(config["secfile"])),
368
self.secret = secfile.read()
238
sf = open(config["secfile"])
239
self.secret = sf.read()
370
raise TypeError("No secret or secfile for client %s"
242
raise TypeError(u"No secret or secfile for client %s"
372
self.host = config.get("host", "")
373
self.created = datetime.datetime.utcnow()
375
self.last_approval_request = None
376
self.last_enabled = datetime.datetime.utcnow()
244
self.fqdn = config.get("fqdn", "")
245
self.created = datetime.datetime.now()
377
246
self.last_checked_ok = None
378
self.last_checker_status = None
379
247
self.timeout = string_to_delta(config["timeout"])
380
self.extended_timeout = string_to_delta(config
381
["extended_timeout"])
382
248
self.interval = string_to_delta(config["interval"])
249
self.stop_hook = stop_hook
383
250
self.checker = None
384
251
self.checker_initiator_tag = None
385
self.disable_initiator_tag = None
386
self.expires = datetime.datetime.utcnow() + self.timeout
252
self.stop_initiator_tag = None
387
253
self.checker_callback_tag = None
388
self.checker_command = config["checker"]
389
self.current_checker_command = None
390
self._approved = None
391
self.approved_by_default = config.get("approved_by_default",
393
self.approvals_pending = 0
394
self.approval_delay = string_to_delta(
395
config["approval_delay"])
396
self.approval_duration = string_to_delta(
397
config["approval_duration"])
398
self.changedstate = (multiprocessing_manager
399
.Condition(multiprocessing_manager
401
self.client_structure = [attr for attr
402
in self.__dict__.iterkeys()
403
if not attr.startswith("_")]
404
self.client_structure.append("client_structure")
407
for name, t in inspect.getmembers(type(self),
411
if not name.startswith("_"):
412
self.client_structure.append(name)
414
# Send notice to process children that client state has changed
415
def send_changedstate(self):
416
with self.changedstate:
417
self.changedstate.notify_all()
254
self.check_command = config["checker"]
420
256
"""Start this client's checker and timeout hooks"""
421
if getattr(self, "enabled", False):
424
self.send_changedstate()
425
self.expires = datetime.datetime.utcnow() + self.timeout
427
self.last_enabled = datetime.datetime.utcnow()
430
def disable(self, quiet=True):
431
"""Disable this client."""
432
if not getattr(self, "enabled", False):
257
# Schedule a new checker to be started an 'interval' from now,
258
# and every interval from then on.
259
self.checker_initiator_tag = gobject.timeout_add\
260
(self._interval_milliseconds,
262
# Also start a new checker *right now*.
264
# Schedule a stop() when 'timeout' has passed
265
self.stop_initiator_tag = gobject.timeout_add\
266
(self._timeout_milliseconds,
270
The possibility that a client might be restarted is left open,
271
but not currently used."""
272
# If this client doesn't have a secret, it is already stopped.
274
logger.info(u"Stopping client %s", self.name)
435
self.send_changedstate()
437
logger.info("Disabling client %s", self.name)
438
if getattr(self, "disable_initiator_tag", False):
439
gobject.source_remove(self.disable_initiator_tag)
440
self.disable_initiator_tag = None
278
if getattr(self, "stop_initiator_tag", False):
279
gobject.source_remove(self.stop_initiator_tag)
280
self.stop_initiator_tag = None
442
281
if getattr(self, "checker_initiator_tag", False):
443
282
gobject.source_remove(self.checker_initiator_tag)
444
283
self.checker_initiator_tag = None
445
284
self.stop_checker()
447
287
# Do not run this again if called by a gobject.timeout_add
450
289
def __del__(self):
453
def init_checker(self):
454
# Schedule a new checker to be started an 'interval' from now,
455
# and every interval from then on.
456
self.checker_initiator_tag = (gobject.timeout_add
457
(self.interval_milliseconds(),
459
# Schedule a disable() when 'timeout' has passed
460
self.disable_initiator_tag = (gobject.timeout_add
461
(self.timeout_milliseconds(),
463
# Also start a new checker *right now*.
467
def checker_callback(self, pid, condition, command):
290
self.stop_hook = None
292
def checker_callback(self, pid, condition):
468
293
"""The checker has completed, so take appropriate actions."""
294
now = datetime.datetime.now()
469
295
self.checker_callback_tag = None
470
296
self.checker = None
471
if os.WIFEXITED(condition):
472
self.last_checker_status = os.WEXITSTATUS(condition)
473
if self.last_checker_status == 0:
474
logger.info("Checker for %(name)s succeeded",
478
logger.info("Checker for %(name)s failed",
481
self.last_checker_status = -1
482
logger.warning("Checker for %(name)s crashed?",
297
if os.WIFEXITED(condition) \
298
and (os.WEXITSTATUS(condition) == 0):
299
logger.info(u"Checker for %(name)s succeeded",
301
self.last_checked_ok = now
302
gobject.source_remove(self.stop_initiator_tag)
303
self.stop_initiator_tag = gobject.timeout_add\
304
(self._timeout_milliseconds,
306
elif not os.WIFEXITED(condition):
307
logger.warning(u"Checker for %(name)s crashed?",
485
def checked_ok(self, timeout=None):
486
"""Bump up the timeout for this client.
488
This should only be called when the client has been seen,
492
timeout = self.timeout
493
self.last_checked_ok = datetime.datetime.utcnow()
494
if self.disable_initiator_tag is not None:
495
gobject.source_remove(self.disable_initiator_tag)
496
if getattr(self, "enabled", False):
497
self.disable_initiator_tag = (gobject.timeout_add
498
(_timedelta_to_milliseconds
499
(timeout), self.disable))
500
self.expires = datetime.datetime.utcnow() + timeout
502
def need_approval(self):
503
self.last_approval_request = datetime.datetime.utcnow()
310
logger.info(u"Checker for %(name)s failed",
505
312
def start_checker(self):
506
313
"""Start a new checker subprocess if one is not running.
508
314
If a checker already exists, leave it running and do
510
316
# The reason for not killing a running checker is that if we
587
360
logger.debug("Stopping checker for %(name)s", vars(self))
589
362
os.kill(self.checker.pid, signal.SIGTERM)
591
364
#if self.checker.poll() is None:
592
365
# os.kill(self.checker.pid, signal.SIGKILL)
593
except OSError as error:
366
except OSError, error:
594
367
if error.errno != errno.ESRCH: # No such process
596
369
self.checker = None
598
# Encrypts a client secret and stores it in a varible
600
def encrypt_secret(self, key):
601
# Encryption-key need to be of a specific size, so we hash
603
hasheng = hashlib.sha256()
605
encryptionkey = hasheng.digest()
607
# Create validation hash so we know at decryption if it was
609
hasheng = hashlib.sha256()
610
hasheng.update(self.secret)
611
validationhash = hasheng.digest()
614
iv = os.urandom(Crypto.Cipher.AES.block_size)
615
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
616
Crypto.Cipher.AES.MODE_CFB, iv)
617
ciphertext = ciphereng.encrypt(validationhash+self.secret)
618
self.encrypted_secret = (ciphertext, iv)
620
# Decrypt a encrypted client secret
621
def decrypt_secret(self, key):
622
# Decryption-key need to be of a specific size, so we hash
624
hasheng = hashlib.sha256()
626
encryptionkey = hasheng.digest()
628
# Decrypt encrypted secret
629
ciphertext, iv = self.encrypted_secret
630
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
631
Crypto.Cipher.AES.MODE_CFB, iv)
632
plain = ciphereng.decrypt(ciphertext)
634
# Validate decrypted secret to know if it was succesful
635
hasheng = hashlib.sha256()
636
validationhash = plain[:hasheng.digest_size]
637
secret = plain[hasheng.digest_size:]
638
hasheng.update(secret)
640
# If validation fails, we use key as new secret. Otherwise, we
641
# use the decrypted secret
642
if hasheng.digest() == validationhash:
646
del self.encrypted_secret
649
def dbus_service_property(dbus_interface, signature="v",
650
access="readwrite", byte_arrays=False):
651
"""Decorators for marking methods of a DBusObjectWithProperties to
652
become properties on the D-Bus.
654
The decorated method will be called with no arguments by "Get"
655
and with one argument by "Set".
657
The parameters, where they are supported, are the same as
658
dbus.service.method, except there is only "signature", since the
659
type from Get() and the type sent to Set() is the same.
661
# Encoding deeply encoded byte arrays is not supported yet by the
662
# "Set" method, so we fail early here:
663
if byte_arrays and signature != "ay":
664
raise ValueError("Byte arrays not supported for non-'ay'"
665
" signature %r" % signature)
667
func._dbus_is_property = True
668
func._dbus_interface = dbus_interface
669
func._dbus_signature = signature
670
func._dbus_access = access
671
func._dbus_name = func.__name__
672
if func._dbus_name.endswith("_dbus_property"):
673
func._dbus_name = func._dbus_name[:-14]
674
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
679
class DBusPropertyException(dbus.exceptions.DBusException):
680
"""A base class for D-Bus property-related exceptions
682
def __unicode__(self):
683
return unicode(str(self))
686
class DBusPropertyAccessException(DBusPropertyException):
687
"""A property's access permissions disallows an operation.
692
class DBusPropertyNotFound(DBusPropertyException):
693
"""An attempt was made to access a non-existing property.
698
class DBusObjectWithProperties(dbus.service.Object):
699
"""A D-Bus object with properties.
701
Classes inheriting from this can use the dbus_service_property
702
decorator to expose methods as D-Bus properties. It exposes the
703
standard Get(), Set(), and GetAll() methods on the D-Bus.
707
def _is_dbus_property(obj):
708
return getattr(obj, "_dbus_is_property", False)
710
def _get_all_dbus_properties(self):
711
"""Returns a generator of (name, attribute) pairs
713
return ((prop.__get__(self)._dbus_name, prop.__get__(self))
714
for cls in self.__class__.__mro__
716
inspect.getmembers(cls, self._is_dbus_property))
718
def _get_dbus_property(self, interface_name, property_name):
719
"""Returns a bound method if one exists which is a D-Bus
720
property with the specified name and interface.
722
for cls in self.__class__.__mro__:
723
for name, value in (inspect.getmembers
724
(cls, self._is_dbus_property)):
725
if (value._dbus_name == property_name
726
and value._dbus_interface == interface_name):
727
return value.__get__(self)
730
raise DBusPropertyNotFound(self.dbus_object_path + ":"
731
+ interface_name + "."
734
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
736
def Get(self, interface_name, property_name):
737
"""Standard D-Bus property Get() method, see D-Bus standard.
739
prop = self._get_dbus_property(interface_name, property_name)
740
if prop._dbus_access == "write":
741
raise DBusPropertyAccessException(property_name)
743
if not hasattr(value, "variant_level"):
745
return type(value)(value, variant_level=value.variant_level+1)
747
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
748
def Set(self, interface_name, property_name, value):
749
"""Standard D-Bus property Set() method, see D-Bus standard.
751
prop = self._get_dbus_property(interface_name, property_name)
752
if prop._dbus_access == "read":
753
raise DBusPropertyAccessException(property_name)
754
if prop._dbus_get_args_options["byte_arrays"]:
755
# The byte_arrays option is not supported yet on
756
# signatures other than "ay".
757
if prop._dbus_signature != "ay":
759
value = dbus.ByteArray(''.join(unichr(byte)
763
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
764
out_signature="a{sv}")
765
def GetAll(self, interface_name):
766
"""Standard D-Bus property GetAll() method, see D-Bus
769
Note: Will not include properties with access="write".
772
for name, prop in self._get_all_dbus_properties():
774
and interface_name != prop._dbus_interface):
775
# Interface non-empty but did not match
777
# Ignore write-only properties
778
if prop._dbus_access == "write":
781
if not hasattr(value, "variant_level"):
784
all[name] = type(value)(value, variant_level=
785
value.variant_level+1)
786
return dbus.Dictionary(all, signature="sv")
788
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
790
path_keyword='object_path',
791
connection_keyword='connection')
792
def Introspect(self, object_path, connection):
793
"""Standard D-Bus method, overloaded to insert property tags.
795
xmlstring = dbus.service.Object.Introspect(self, object_path,
798
document = xml.dom.minidom.parseString(xmlstring)
799
def make_tag(document, name, prop):
800
e = document.createElement("property")
801
e.setAttribute("name", name)
802
e.setAttribute("type", prop._dbus_signature)
803
e.setAttribute("access", prop._dbus_access)
805
for if_tag in document.getElementsByTagName("interface"):
806
for tag in (make_tag(document, name, prop)
808
in self._get_all_dbus_properties()
809
if prop._dbus_interface
810
== if_tag.getAttribute("name")):
811
if_tag.appendChild(tag)
812
# Add the names to the return values for the
813
# "org.freedesktop.DBus.Properties" methods
814
if (if_tag.getAttribute("name")
815
== "org.freedesktop.DBus.Properties"):
816
for cn in if_tag.getElementsByTagName("method"):
817
if cn.getAttribute("name") == "Get":
818
for arg in cn.getElementsByTagName("arg"):
819
if (arg.getAttribute("direction")
821
arg.setAttribute("name", "value")
822
elif cn.getAttribute("name") == "GetAll":
823
for arg in cn.getElementsByTagName("arg"):
824
if (arg.getAttribute("direction")
826
arg.setAttribute("name", "props")
827
xmlstring = document.toxml("utf-8")
829
except (AttributeError, xml.dom.DOMException,
830
xml.parsers.expat.ExpatError) as error:
831
logger.error("Failed to override Introspection method",
836
def datetime_to_dbus (dt, variant_level=0):
837
"""Convert a UTC datetime.datetime() to a D-Bus type."""
839
return dbus.String("", variant_level = variant_level)
840
return dbus.String(dt.isoformat(),
841
variant_level=variant_level)
843
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
845
"""Applied to an empty subclass of a D-Bus object, this metaclass
846
will add additional D-Bus attributes matching a certain pattern.
848
def __new__(mcs, name, bases, attr):
849
# Go through all the base classes which could have D-Bus
850
# methods, signals, or properties in them
851
for base in (b for b in bases
852
if issubclass(b, dbus.service.Object)):
853
# Go though all attributes of the base class
854
for attrname, attribute in inspect.getmembers(base):
855
# Ignore non-D-Bus attributes, and D-Bus attributes
856
# with the wrong interface name
857
if (not hasattr(attribute, "_dbus_interface")
858
or not attribute._dbus_interface
859
.startswith("se.recompile.Mandos")):
861
# Create an alternate D-Bus interface name based on
863
alt_interface = (attribute._dbus_interface
864
.replace("se.recompile.Mandos",
865
"se.bsnet.fukt.Mandos"))
866
# Is this a D-Bus signal?
867
if getattr(attribute, "_dbus_is_signal", False):
868
# Extract the original non-method function by
870
nonmethod_func = (dict(
871
zip(attribute.func_code.co_freevars,
872
attribute.__closure__))["func"]
874
# Create a new, but exactly alike, function
875
# object, and decorate it to be a new D-Bus signal
876
# with the alternate D-Bus interface name
877
new_function = (dbus.service.signal
879
attribute._dbus_signature)
881
nonmethod_func.func_code,
882
nonmethod_func.func_globals,
883
nonmethod_func.func_name,
884
nonmethod_func.func_defaults,
885
nonmethod_func.func_closure)))
886
# Define a creator of a function to call both the
887
# old and new functions, so both the old and new
888
# signals gets sent when the function is called
889
def fixscope(func1, func2):
890
"""This function is a scope container to pass
891
func1 and func2 to the "call_both" function
892
outside of its arguments"""
893
def call_both(*args, **kwargs):
894
"""This function will emit two D-Bus
895
signals by calling func1 and func2"""
896
func1(*args, **kwargs)
897
func2(*args, **kwargs)
899
# Create the "call_both" function and add it to
901
attr[attrname] = fixscope(attribute,
903
# Is this a D-Bus method?
904
elif getattr(attribute, "_dbus_is_method", False):
905
# Create a new, but exactly alike, function
906
# object. Decorate it to be a new D-Bus method
907
# with the alternate D-Bus interface name. Add it
909
attr[attrname] = (dbus.service.method
911
attribute._dbus_in_signature,
912
attribute._dbus_out_signature)
914
(attribute.func_code,
915
attribute.func_globals,
917
attribute.func_defaults,
918
attribute.func_closure)))
919
# Is this a D-Bus property?
920
elif getattr(attribute, "_dbus_is_property", False):
921
# Create a new, but exactly alike, function
922
# object, and decorate it to be a new D-Bus
923
# property with the alternate D-Bus interface
924
# name. Add it to the class.
925
attr[attrname] = (dbus_service_property
927
attribute._dbus_signature,
928
attribute._dbus_access,
930
._dbus_get_args_options
933
(attribute.func_code,
934
attribute.func_globals,
936
attribute.func_defaults,
937
attribute.func_closure)))
938
return type.__new__(mcs, name, bases, attr)
940
class ClientDBus(Client, DBusObjectWithProperties):
941
"""A Client class using D-Bus
944
dbus_object_path: dbus.ObjectPath
945
bus: dbus.SystemBus()
948
runtime_expansions = (Client.runtime_expansions
949
+ ("dbus_object_path",))
951
# dbus.service.Object doesn't use super(), so we can't either.
953
def __init__(self, bus = None, *args, **kwargs):
955
self._approvals_pending = 0
956
Client.__init__(self, *args, **kwargs)
959
def add_to_dbus(self):
960
# Only now, when this client is initialized, can it show up on
962
client_object_name = unicode(self.name).translate(
965
self.dbus_object_path = (dbus.ObjectPath
966
("/clients/" + client_object_name))
967
DBusObjectWithProperties.__init__(self, self.bus,
968
self.dbus_object_path)
970
def notifychangeproperty(transform_func,
971
dbus_name, type_func=lambda x: x,
973
""" Modify a variable so that it's a property which announces
976
transform_fun: Function that takes a value and a variant_level
977
and transforms it to a D-Bus type.
978
dbus_name: D-Bus name of the variable
979
type_func: Function that transform the value before sending it
980
to the D-Bus. Default: no transform
981
variant_level: D-Bus variant level. Default: 1
983
attrname = "_{0}".format(dbus_name)
984
def setter(self, value):
985
if hasattr(self, "dbus_object_path"):
986
if (not hasattr(self, attrname) or
987
type_func(getattr(self, attrname, None))
988
!= type_func(value)):
989
dbus_value = transform_func(type_func(value),
992
self.PropertyChanged(dbus.String(dbus_name),
994
setattr(self, attrname, value)
996
return property(lambda self: getattr(self, attrname), setter)
999
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1000
approvals_pending = notifychangeproperty(dbus.Boolean,
1003
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1004
last_enabled = notifychangeproperty(datetime_to_dbus,
1006
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1007
type_func = lambda checker:
1008
checker is not None)
1009
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1011
last_approval_request = notifychangeproperty(
1012
datetime_to_dbus, "LastApprovalRequest")
1013
approved_by_default = notifychangeproperty(dbus.Boolean,
1014
"ApprovedByDefault")
1015
approval_delay = notifychangeproperty(dbus.UInt16,
1018
_timedelta_to_milliseconds)
1019
approval_duration = notifychangeproperty(
1020
dbus.UInt16, "ApprovalDuration",
1021
type_func = _timedelta_to_milliseconds)
1022
host = notifychangeproperty(dbus.String, "Host")
1023
timeout = notifychangeproperty(dbus.UInt16, "Timeout",
1025
_timedelta_to_milliseconds)
1026
extended_timeout = notifychangeproperty(
1027
dbus.UInt16, "ExtendedTimeout",
1028
type_func = _timedelta_to_milliseconds)
1029
interval = notifychangeproperty(dbus.UInt16,
1032
_timedelta_to_milliseconds)
1033
checker_command = notifychangeproperty(dbus.String, "Checker")
1035
del notifychangeproperty
1037
def __del__(self, *args, **kwargs):
1039
self.remove_from_connection()
1042
if hasattr(DBusObjectWithProperties, "__del__"):
1043
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1044
Client.__del__(self, *args, **kwargs)
1046
def checker_callback(self, pid, condition, command,
1048
self.checker_callback_tag = None
1050
if os.WIFEXITED(condition):
1051
exitstatus = os.WEXITSTATUS(condition)
1053
self.CheckerCompleted(dbus.Int16(exitstatus),
1054
dbus.Int64(condition),
1055
dbus.String(command))
1058
self.CheckerCompleted(dbus.Int16(-1),
1059
dbus.Int64(condition),
1060
dbus.String(command))
1062
return Client.checker_callback(self, pid, condition, command,
1065
def start_checker(self, *args, **kwargs):
1066
old_checker = self.checker
1067
if self.checker is not None:
1068
old_checker_pid = self.checker.pid
1070
old_checker_pid = None
1071
r = Client.start_checker(self, *args, **kwargs)
1072
# Only if new checker process was started
1073
if (self.checker is not None
1074
and old_checker_pid != self.checker.pid):
1076
self.CheckerStarted(self.current_checker_command)
1079
def _reset_approved(self):
1080
self._approved = None
1083
def approve(self, value=True):
1084
self.send_changedstate()
1085
self._approved = value
1086
gobject.timeout_add(_timedelta_to_milliseconds
1087
(self.approval_duration),
1088
self._reset_approved)
1091
## D-Bus methods, signals & properties
1092
_interface = "se.recompile.Mandos.Client"
1096
# CheckerCompleted - signal
1097
@dbus.service.signal(_interface, signature="nxs")
1098
def CheckerCompleted(self, exitcode, waitstatus, command):
1102
# CheckerStarted - signal
1103
@dbus.service.signal(_interface, signature="s")
1104
def CheckerStarted(self, command):
1108
# PropertyChanged - signal
1109
@dbus.service.signal(_interface, signature="sv")
1110
def PropertyChanged(self, property, value):
1114
# GotSecret - signal
1115
@dbus.service.signal(_interface)
1116
def GotSecret(self):
1118
Is sent after a successful transfer of secret from the Mandos
1119
server to mandos-client
1124
@dbus.service.signal(_interface, signature="s")
1125
def Rejected(self, reason):
1129
# NeedApproval - signal
1130
@dbus.service.signal(_interface, signature="tb")
1131
def NeedApproval(self, timeout, default):
1133
return self.need_approval()
1138
@dbus.service.method(_interface, in_signature="b")
1139
def Approve(self, value):
1142
# CheckedOK - method
1143
@dbus.service.method(_interface)
1144
def CheckedOK(self):
1148
@dbus.service.method(_interface)
1153
# StartChecker - method
1154
@dbus.service.method(_interface)
1155
def StartChecker(self):
1157
self.start_checker()
1160
@dbus.service.method(_interface)
1165
# StopChecker - method
1166
@dbus.service.method(_interface)
1167
def StopChecker(self):
1172
# ApprovalPending - property
1173
@dbus_service_property(_interface, signature="b", access="read")
1174
def ApprovalPending_dbus_property(self):
1175
return dbus.Boolean(bool(self.approvals_pending))
1177
# ApprovedByDefault - property
1178
@dbus_service_property(_interface, signature="b",
1180
def ApprovedByDefault_dbus_property(self, value=None):
1181
if value is None: # get
1182
return dbus.Boolean(self.approved_by_default)
1183
self.approved_by_default = bool(value)
1185
# ApprovalDelay - property
1186
@dbus_service_property(_interface, signature="t",
1188
def ApprovalDelay_dbus_property(self, value=None):
1189
if value is None: # get
1190
return dbus.UInt64(self.approval_delay_milliseconds())
1191
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1193
# ApprovalDuration - property
1194
@dbus_service_property(_interface, signature="t",
1196
def ApprovalDuration_dbus_property(self, value=None):
1197
if value is None: # get
1198
return dbus.UInt64(_timedelta_to_milliseconds(
1199
self.approval_duration))
1200
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1203
@dbus_service_property(_interface, signature="s", access="read")
1204
def Name_dbus_property(self):
1205
return dbus.String(self.name)
1207
# Fingerprint - property
1208
@dbus_service_property(_interface, signature="s", access="read")
1209
def Fingerprint_dbus_property(self):
1210
return dbus.String(self.fingerprint)
1213
@dbus_service_property(_interface, signature="s",
1215
def Host_dbus_property(self, value=None):
1216
if value is None: # get
1217
return dbus.String(self.host)
1220
# Created - property
1221
@dbus_service_property(_interface, signature="s", access="read")
1222
def Created_dbus_property(self):
1223
return dbus.String(datetime_to_dbus(self.created))
1225
# LastEnabled - property
1226
@dbus_service_property(_interface, signature="s", access="read")
1227
def LastEnabled_dbus_property(self):
1228
return datetime_to_dbus(self.last_enabled)
1230
# Enabled - property
1231
@dbus_service_property(_interface, signature="b",
1233
def Enabled_dbus_property(self, value=None):
1234
if value is None: # get
1235
return dbus.Boolean(self.enabled)
1241
# LastCheckedOK - property
1242
@dbus_service_property(_interface, signature="s",
1244
def LastCheckedOK_dbus_property(self, value=None):
1245
if value is not None:
1248
return datetime_to_dbus(self.last_checked_ok)
1250
# Expires - property
1251
@dbus_service_property(_interface, signature="s", access="read")
1252
def Expires_dbus_property(self):
1253
return datetime_to_dbus(self.expires)
1255
# LastApprovalRequest - property
1256
@dbus_service_property(_interface, signature="s", access="read")
1257
def LastApprovalRequest_dbus_property(self):
1258
return datetime_to_dbus(self.last_approval_request)
1260
# Timeout - property
1261
@dbus_service_property(_interface, signature="t",
1263
def Timeout_dbus_property(self, value=None):
1264
if value is None: # get
1265
return dbus.UInt64(self.timeout_milliseconds())
1266
self.timeout = datetime.timedelta(0, 0, 0, value)
1267
if getattr(self, "disable_initiator_tag", None) is None:
1269
# Reschedule timeout
1270
gobject.source_remove(self.disable_initiator_tag)
1271
self.disable_initiator_tag = None
1273
time_to_die = _timedelta_to_milliseconds((self
1278
if time_to_die <= 0:
1279
# The timeout has passed
1282
self.expires = (datetime.datetime.utcnow()
1283
+ datetime.timedelta(milliseconds =
1285
self.disable_initiator_tag = (gobject.timeout_add
1286
(time_to_die, self.disable))
1288
# ExtendedTimeout - property
1289
@dbus_service_property(_interface, signature="t",
1291
def ExtendedTimeout_dbus_property(self, value=None):
1292
if value is None: # get
1293
return dbus.UInt64(self.extended_timeout_milliseconds())
1294
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1296
# Interval - property
1297
@dbus_service_property(_interface, signature="t",
1299
def Interval_dbus_property(self, value=None):
1300
if value is None: # get
1301
return dbus.UInt64(self.interval_milliseconds())
1302
self.interval = datetime.timedelta(0, 0, 0, value)
1303
if getattr(self, "checker_initiator_tag", None) is None:
1305
# Reschedule checker run
1306
gobject.source_remove(self.checker_initiator_tag)
1307
self.checker_initiator_tag = (gobject.timeout_add
1308
(value, self.start_checker))
1309
self.start_checker() # Start one now, too
1311
# Checker - property
1312
@dbus_service_property(_interface, signature="s",
1314
def Checker_dbus_property(self, value=None):
1315
if value is None: # get
1316
return dbus.String(self.checker_command)
1317
self.checker_command = value
1319
# CheckerRunning - property
1320
@dbus_service_property(_interface, signature="b",
1322
def CheckerRunning_dbus_property(self, value=None):
1323
if value is None: # get
1324
return dbus.Boolean(self.checker is not None)
1326
self.start_checker()
1330
# ObjectPath - property
1331
@dbus_service_property(_interface, signature="o", access="read")
1332
def ObjectPath_dbus_property(self):
1333
return self.dbus_object_path # is already a dbus.ObjectPath
1336
@dbus_service_property(_interface, signature="ay",
1337
access="write", byte_arrays=True)
1338
def Secret_dbus_property(self, value):
1339
self.secret = str(value)
1344
class ProxyClient(object):
1345
def __init__(self, child_pipe, fpr, address):
1346
self._pipe = child_pipe
1347
self._pipe.send(('init', fpr, address))
1348
if not self._pipe.recv():
1351
def __getattribute__(self, name):
1352
if(name == '_pipe'):
1353
return super(ProxyClient, self).__getattribute__(name)
1354
self._pipe.send(('getattr', name))
1355
data = self._pipe.recv()
1356
if data[0] == 'data':
1358
if data[0] == 'function':
1359
def func(*args, **kwargs):
1360
self._pipe.send(('funcall', name, args, kwargs))
1361
return self._pipe.recv()[1]
1364
def __setattr__(self, name, value):
1365
if(name == '_pipe'):
1366
return super(ProxyClient, self).__setattr__(name, value)
1367
self._pipe.send(('setattr', name, value))
1369
class ClientDBusTransitional(ClientDBus):
1370
__metaclass__ = AlternateDBusNamesMetaclass
1372
class ClientHandler(socketserver.BaseRequestHandler, object):
1373
"""A class to handle client connections.
1375
Instantiated once for each connection to handle it.
370
def still_valid(self):
371
"""Has the timeout not yet passed for this client?"""
372
now = datetime.datetime.now()
373
if self.last_checked_ok is None:
374
return now < (self.created + self.timeout)
376
return now < (self.last_checked_ok + self.timeout)
379
def peer_certificate(session):
380
"Return the peer's OpenPGP certificate as a bytestring"
381
# If not an OpenPGP certificate...
382
if gnutls.library.functions.gnutls_certificate_type_get\
383
(session._c_object) \
384
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
385
# ...do the normal thing
386
return session.peer_certificate
387
list_size = ctypes.c_uint()
388
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
389
(session._c_object, ctypes.byref(list_size))
390
if list_size.value == 0:
393
return ctypes.string_at(cert.data, cert.size)
396
def fingerprint(openpgp):
397
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
398
# New GnuTLS "datum" with the OpenPGP public key
399
datum = gnutls.library.types.gnutls_datum_t\
400
(ctypes.cast(ctypes.c_char_p(openpgp),
401
ctypes.POINTER(ctypes.c_ubyte)),
402
ctypes.c_uint(len(openpgp)))
403
# New empty GnuTLS certificate
404
crt = gnutls.library.types.gnutls_openpgp_crt_t()
405
gnutls.library.functions.gnutls_openpgp_crt_init\
407
# Import the OpenPGP public key into the certificate
408
gnutls.library.functions.gnutls_openpgp_crt_import\
409
(crt, ctypes.byref(datum),
410
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
411
# New buffer for the fingerprint
412
buffer = ctypes.create_string_buffer(20)
413
buffer_length = ctypes.c_size_t()
414
# Get the fingerprint from the certificate into the buffer
415
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
416
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
417
# Deinit the certificate
418
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
419
# Convert the buffer to a Python bytestring
420
fpr = ctypes.string_at(buffer, buffer_length.value)
421
# Convert the bytestring to hexadecimal notation
422
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
426
class tcp_handler(SocketServer.BaseRequestHandler, object):
427
"""A TCP request handler class.
428
Instantiated by IPv6_TCPServer for each request to handle it.
1376
429
Note: This will run in its own forked process."""
1378
431
def handle(self):
1379
with contextlib.closing(self.server.child_pipe) as child_pipe:
1380
logger.info("TCP connection from: %s",
1381
unicode(self.client_address))
1382
logger.debug("Pipe FD: %d",
1383
self.server.child_pipe.fileno())
1385
session = (gnutls.connection
1386
.ClientSession(self.request,
1388
.X509Credentials()))
1390
# Note: gnutls.connection.X509Credentials is really a
1391
# generic GnuTLS certificate credentials object so long as
1392
# no X.509 keys are added to it. Therefore, we can use it
1393
# here despite using OpenPGP certificates.
1395
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1396
# "+AES-256-CBC", "+SHA1",
1397
# "+COMP-NULL", "+CTYPE-OPENPGP",
1399
# Use a fallback default, since this MUST be set.
1400
priority = self.server.gnutls_priority
1401
if priority is None:
1403
(gnutls.library.functions
1404
.gnutls_priority_set_direct(session._c_object,
1407
# Start communication using the Mandos protocol
1408
# Get protocol number
1409
line = self.request.makefile().readline()
1410
logger.debug("Protocol version: %r", line)
1412
if int(line.strip().split()[0]) > 1:
1414
except (ValueError, IndexError, RuntimeError) as error:
1415
logger.error("Unknown protocol version: %s", error)
1418
# Start GnuTLS connection
1421
except gnutls.errors.GNUTLSError as error:
1422
logger.warning("Handshake failed: %s", error)
1423
# Do not run session.bye() here: the session is not
1424
# established. Just abandon the request.
1426
logger.debug("Handshake succeeded")
1428
approval_required = False
1431
fpr = self.fingerprint(self.peer_certificate
1434
gnutls.errors.GNUTLSError) as error:
1435
logger.warning("Bad certificate: %s", error)
1437
logger.debug("Fingerprint: %s", fpr)
1440
client = ProxyClient(child_pipe, fpr,
1441
self.client_address)
1445
if client.approval_delay:
1446
delay = client.approval_delay
1447
client.approvals_pending += 1
1448
approval_required = True
1451
if not client.enabled:
1452
logger.info("Client %s is disabled",
1454
if self.server.use_dbus:
1456
client.Rejected("Disabled")
1459
if client._approved or not client.approval_delay:
1460
#We are approved or approval is disabled
1462
elif client._approved is None:
1463
logger.info("Client %s needs approval",
1465
if self.server.use_dbus:
1467
client.NeedApproval(
1468
client.approval_delay_milliseconds(),
1469
client.approved_by_default)
1471
logger.warning("Client %s was not approved",
1473
if self.server.use_dbus:
1475
client.Rejected("Denied")
1478
#wait until timeout or approved
1479
time = datetime.datetime.now()
1480
client.changedstate.acquire()
1481
(client.changedstate.wait
1482
(float(client._timedelta_to_milliseconds(delay)
1484
client.changedstate.release()
1485
time2 = datetime.datetime.now()
1486
if (time2 - time) >= delay:
1487
if not client.approved_by_default:
1488
logger.warning("Client %s timed out while"
1489
" waiting for approval",
1491
if self.server.use_dbus:
1493
client.Rejected("Approval timed out")
1498
delay -= time2 - time
1501
while sent_size < len(client.secret):
1503
sent = session.send(client.secret[sent_size:])
1504
except gnutls.errors.GNUTLSError as error:
1505
logger.warning("gnutls send failed")
1507
logger.debug("Sent: %d, remaining: %d",
1508
sent, len(client.secret)
1509
- (sent_size + sent))
1512
logger.info("Sending secret to %s", client.name)
1513
# bump the timeout using extended_timeout
1514
client.checked_ok(client.extended_timeout)
1515
if self.server.use_dbus:
1520
if approval_required:
1521
client.approvals_pending -= 1
1524
except gnutls.errors.GNUTLSError as error:
1525
logger.warning("GnuTLS bye failed")
1528
def peer_certificate(session):
1529
"Return the peer's OpenPGP certificate as a bytestring"
1530
# If not an OpenPGP certificate...
1531
if (gnutls.library.functions
1532
.gnutls_certificate_type_get(session._c_object)
1533
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1534
# ...do the normal thing
1535
return session.peer_certificate
1536
list_size = ctypes.c_uint(1)
1537
cert_list = (gnutls.library.functions
1538
.gnutls_certificate_get_peers
1539
(session._c_object, ctypes.byref(list_size)))
1540
if not bool(cert_list) and list_size.value != 0:
1541
raise gnutls.errors.GNUTLSError("error getting peer"
1543
if list_size.value == 0:
1546
return ctypes.string_at(cert.data, cert.size)
1549
def fingerprint(openpgp):
1550
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1551
# New GnuTLS "datum" with the OpenPGP public key
1552
datum = (gnutls.library.types
1553
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1556
ctypes.c_uint(len(openpgp))))
1557
# New empty GnuTLS certificate
1558
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1559
(gnutls.library.functions
1560
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1561
# Import the OpenPGP public key into the certificate
1562
(gnutls.library.functions
1563
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1564
gnutls.library.constants
1565
.GNUTLS_OPENPGP_FMT_RAW))
1566
# Verify the self signature in the key
1567
crtverify = ctypes.c_uint()
1568
(gnutls.library.functions
1569
.gnutls_openpgp_crt_verify_self(crt, 0,
1570
ctypes.byref(crtverify)))
1571
if crtverify.value != 0:
1572
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1573
raise (gnutls.errors.CertificateSecurityError
1575
# New buffer for the fingerprint
1576
buf = ctypes.create_string_buffer(20)
1577
buf_len = ctypes.c_size_t()
1578
# Get the fingerprint from the certificate into the buffer
1579
(gnutls.library.functions
1580
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1581
ctypes.byref(buf_len)))
1582
# Deinit the certificate
1583
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1584
# Convert the buffer to a Python bytestring
1585
fpr = ctypes.string_at(buf, buf_len.value)
1586
# Convert the bytestring to hexadecimal notation
1587
hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1591
class MultiprocessingMixIn(object):
1592
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1593
def sub_process_main(self, request, address):
1595
self.finish_request(request, address)
1597
self.handle_error(request, address)
1598
self.close_request(request)
1600
def process_request(self, request, address):
1601
"""Start a new process to process the request."""
1602
proc = multiprocessing.Process(target = self.sub_process_main,
1609
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1610
""" adds a pipe to the MixIn """
1611
def process_request(self, request, client_address):
1612
"""Overrides and wraps the original process_request().
1614
This function creates a new pipe in self.pipe
1616
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1618
proc = MultiprocessingMixIn.process_request(self, request,
1620
self.child_pipe.close()
1621
self.add_pipe(parent_pipe, proc)
1623
def add_pipe(self, parent_pipe, proc):
1624
"""Dummy function; override as necessary"""
1625
raise NotImplementedError
1628
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1629
socketserver.TCPServer, object):
1630
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
432
logger.info(u"TCP connection from: %s",
433
unicode(self.client_address))
434
session = gnutls.connection.ClientSession\
435
(self.request, gnutls.connection.X509Credentials())
437
line = self.request.makefile().readline()
438
logger.debug(u"Protocol version: %r", line)
440
if int(line.strip().split()[0]) > 1:
442
except (ValueError, IndexError, RuntimeError), error:
443
logger.error(u"Unknown protocol version: %s", error)
446
# Note: gnutls.connection.X509Credentials is really a generic
447
# GnuTLS certificate credentials object so long as no X.509
448
# keys are added to it. Therefore, we can use it here despite
449
# using OpenPGP certificates.
451
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
452
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
454
priority = "NORMAL" # Fallback default, since this
456
if self.server.settings["priority"]:
457
priority = self.server.settings["priority"]
458
gnutls.library.functions.gnutls_priority_set_direct\
459
(session._c_object, priority, None);
463
except gnutls.errors.GNUTLSError, error:
464
logger.warning(u"Handshake failed: %s", error)
465
# Do not run session.bye() here: the session is not
466
# established. Just abandon the request.
469
fpr = fingerprint(peer_certificate(session))
470
except (TypeError, gnutls.errors.GNUTLSError), error:
471
logger.warning(u"Bad certificate: %s", error)
474
logger.debug(u"Fingerprint: %s", fpr)
476
for c in self.server.clients:
477
if c.fingerprint == fpr:
481
logger.warning(u"Client not found for fingerprint: %s",
485
# Have to check if client.still_valid(), since it is possible
486
# that the client timed out while establishing the GnuTLS
488
if not client.still_valid():
489
logger.warning(u"Client %(name)s is invalid",
494
while sent_size < len(client.secret):
495
sent = session.send(client.secret[sent_size:])
496
logger.debug(u"Sent: %d, remaining: %d",
497
sent, len(client.secret)
498
- (sent_size + sent))
503
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
504
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1633
enabled: Boolean; whether this server is activated yet
1634
interface: None or a network interface name (string)
1635
use_ipv6: Boolean; to use IPv6 or not
506
settings: Server settings
507
clients: Set() of Client objects
1637
def __init__(self, server_address, RequestHandlerClass,
1638
interface=None, use_ipv6=True):
1639
self.interface = interface
1641
self.address_family = socket.AF_INET6
1642
socketserver.TCPServer.__init__(self, server_address,
1643
RequestHandlerClass)
509
address_family = socket.AF_INET6
510
def __init__(self, *args, **kwargs):
511
if "settings" in kwargs:
512
self.settings = kwargs["settings"]
513
del kwargs["settings"]
514
if "clients" in kwargs:
515
self.clients = kwargs["clients"]
516
del kwargs["clients"]
517
return super(type(self), self).__init__(*args, **kwargs)
1644
518
def server_bind(self):
1645
519
"""This overrides the normal server_bind() function
1646
520
to bind to an interface if one was specified, and also NOT to
1647
521
bind to an address or port if they were not specified."""
1648
if self.interface is not None:
1649
if SO_BINDTODEVICE is None:
1650
logger.error("SO_BINDTODEVICE does not exist;"
1651
" cannot bind to interface %s",
1655
self.socket.setsockopt(socket.SOL_SOCKET,
1659
except socket.error as error:
1660
if error[0] == errno.EPERM:
1661
logger.error("No permission to"
1662
" bind to interface %s",
1664
elif error[0] == errno.ENOPROTOOPT:
1665
logger.error("SO_BINDTODEVICE not available;"
1666
" cannot bind to interface %s",
522
if self.settings["interface"]:
523
# 25 is from /usr/include/asm-i486/socket.h
524
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
526
self.socket.setsockopt(socket.SOL_SOCKET,
528
self.settings["interface"])
529
except socket.error, error:
530
if error[0] == errno.EPERM:
531
logger.error(u"No permission to"
532
u" bind to interface %s",
533
self.settings["interface"])
1670
536
# Only bind(2) the socket if we really need to.
1671
537
if self.server_address[0] or self.server_address[1]:
1672
538
if not self.server_address[0]:
1673
if self.address_family == socket.AF_INET6:
1674
any_address = "::" # in6addr_any
1676
any_address = socket.INADDR_ANY
1677
self.server_address = (any_address,
540
self.server_address = (in6addr_any,
1678
541
self.server_address[1])
1679
elif not self.server_address[1]:
542
elif self.server_address[1] is None:
1680
543
self.server_address = (self.server_address[0],
1682
# if self.interface:
1683
# self.server_address = (self.server_address[0],
1688
return socketserver.TCPServer.server_bind(self)
1691
class MandosServer(IPv6_TCPServer):
1695
clients: set of Client objects
1696
gnutls_priority GnuTLS priority string
1697
use_dbus: Boolean; to emit D-Bus signals or not
1699
Assumes a gobject.MainLoop event loop.
1701
def __init__(self, server_address, RequestHandlerClass,
1702
interface=None, use_ipv6=True, clients=None,
1703
gnutls_priority=None, use_dbus=True):
1704
self.enabled = False
1705
self.clients = clients
1706
if self.clients is None:
1708
self.use_dbus = use_dbus
1709
self.gnutls_priority = gnutls_priority
1710
IPv6_TCPServer.__init__(self, server_address,
1711
RequestHandlerClass,
1712
interface = interface,
1713
use_ipv6 = use_ipv6)
1714
def server_activate(self):
1716
return socketserver.TCPServer.server_activate(self)
1721
def add_pipe(self, parent_pipe, proc):
1722
# Call "handle_ipc" for both data and EOF events
1723
gobject.io_add_watch(parent_pipe.fileno(),
1724
gobject.IO_IN | gobject.IO_HUP,
1725
functools.partial(self.handle_ipc,
1730
def handle_ipc(self, source, condition, parent_pipe=None,
1731
proc = None, client_object=None):
1733
gobject.IO_IN: "IN", # There is data to read.
1734
gobject.IO_OUT: "OUT", # Data can be written (without
1736
gobject.IO_PRI: "PRI", # There is urgent data to read.
1737
gobject.IO_ERR: "ERR", # Error condition.
1738
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1739
# broken, usually for pipes and
1742
conditions_string = ' | '.join(name
1744
condition_names.iteritems()
1745
if cond & condition)
1746
# error, or the other end of multiprocessing.Pipe has closed
1747
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1748
# Wait for other process to exit
1752
# Read a request from the child
1753
request = parent_pipe.recv()
1754
command = request[0]
1756
if command == 'init':
1758
address = request[2]
1760
for c in self.clients.itervalues():
1761
if c.fingerprint == fpr:
1765
logger.info("Client not found for fingerprint: %s, ad"
1766
"dress: %s", fpr, address)
1769
mandos_dbus_service.ClientNotFound(fpr,
1771
parent_pipe.send(False)
1774
gobject.io_add_watch(parent_pipe.fileno(),
1775
gobject.IO_IN | gobject.IO_HUP,
1776
functools.partial(self.handle_ipc,
1782
parent_pipe.send(True)
1783
# remove the old hook in favor of the new above hook on
1786
if command == 'funcall':
1787
funcname = request[1]
1791
parent_pipe.send(('data', getattr(client_object,
1795
if command == 'getattr':
1796
attrname = request[1]
1797
if callable(client_object.__getattribute__(attrname)):
1798
parent_pipe.send(('function',))
1800
parent_pipe.send(('data', client_object
1801
.__getattribute__(attrname)))
1803
if command == 'setattr':
1804
attrname = request[1]
1806
setattr(client_object, attrname, value)
545
return super(type(self), self).server_bind()
1811
548
def string_to_delta(interval):
1812
549
"""Parse a string and return a datetime.timedelta
1814
551
>>> string_to_delta('7d')
1815
552
datetime.timedelta(7)
1816
553
>>> string_to_delta('60s')
1952
688
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1953
689
"servicename": "Mandos",
1959
692
# Parse config file for server-global settings
1960
server_config = configparser.SafeConfigParser(server_defaults)
693
server_config = ConfigParser.SafeConfigParser(server_defaults)
1961
694
del server_defaults
1962
server_config.read(os.path.join(options.configdir,
695
server_config.read(os.path.join(options.configdir, "server.conf"))
696
server_section = "server"
1964
697
# Convert the SafeConfigParser object to a dict
1965
server_settings = server_config.defaults()
1966
# Use the appropriate methods on the non-string config options
1967
for option in ("debug", "use_dbus", "use_ipv6"):
1968
server_settings[option] = server_config.getboolean("DEFAULT",
1970
if server_settings["port"]:
1971
server_settings["port"] = server_config.getint("DEFAULT",
698
server_settings = dict(server_config.items(server_section))
699
# Use getboolean on the boolean config option
700
server_settings["debug"] = server_config.getboolean\
701
(server_section, "debug")
1973
702
del server_config
1975
704
# Override the settings from the config file with command line
1976
705
# options, if set.
1977
706
for option in ("interface", "address", "port", "debug",
1978
"priority", "servicename", "configdir",
1979
"use_dbus", "use_ipv6", "debuglevel", "restore"):
707
"priority", "servicename", "configdir"):
1980
708
value = getattr(options, option)
1981
709
if value is not None:
1982
710
server_settings[option] = value
1984
# Force all strings to be unicode
1985
for option in server_settings.keys():
1986
if type(server_settings[option]) is str:
1987
server_settings[option] = unicode(server_settings[option])
1988
712
# Now we have our good server settings in "server_settings"
1990
##################################################################
1993
debug = server_settings["debug"]
1994
debuglevel = server_settings["debuglevel"]
1995
use_dbus = server_settings["use_dbus"]
1996
use_ipv6 = server_settings["use_ipv6"]
1998
if server_settings["servicename"] != "Mandos":
1999
syslogger.setFormatter(logging.Formatter
2000
('Mandos (%s) [%%(process)d]:'
2001
' %%(levelname)s: %%(message)s'
2002
% server_settings["servicename"]))
2004
714
# Parse config file with clients
2005
client_defaults = { "timeout": "5m",
2006
"extended_timeout": "15m",
2008
"checker": "fping -q -- %%(host)s",
2010
"approval_delay": "0s",
2011
"approval_duration": "1s",
715
client_defaults = { "timeout": "1h",
717
"checker": "fping -q -- %%(fqdn)s",
2013
client_config = configparser.SafeConfigParser(client_defaults)
719
client_config = ConfigParser.SafeConfigParser(client_defaults)
2014
720
client_config.read(os.path.join(server_settings["configdir"],
2015
721
"clients.conf"))
2017
global mandos_dbus_service
2018
mandos_dbus_service = None
2020
tcp_server = MandosServer((server_settings["address"],
2021
server_settings["port"]),
2023
interface=(server_settings["interface"]
2027
server_settings["priority"],
2030
pidfilename = "/var/run/mandos.pid"
2032
pidfile = open(pidfilename, "w")
2034
logger.error("Could not open file %r", pidfilename)
2037
uid = pwd.getpwnam("_mandos").pw_uid
2038
gid = pwd.getpwnam("_mandos").pw_gid
2041
uid = pwd.getpwnam("mandos").pw_uid
2042
gid = pwd.getpwnam("mandos").pw_gid
2045
uid = pwd.getpwnam("nobody").pw_uid
2046
gid = pwd.getpwnam("nobody").pw_gid
2053
except OSError as error:
2054
if error[0] != errno.EPERM:
2057
if not debug and not debuglevel:
2058
logger.setLevel(logging.WARNING)
2060
level = getattr(logging, debuglevel.upper())
2061
logger.setLevel(level)
2064
logger.setLevel(logging.DEBUG)
2065
# Enable all possible GnuTLS debugging
2067
# "Use a log level over 10 to enable all debugging options."
2069
gnutls.library.functions.gnutls_global_set_log_level(11)
2071
@gnutls.library.types.gnutls_log_func
2072
def debug_gnutls(level, string):
2073
logger.debug("GnuTLS: %s", string[:-1])
2075
(gnutls.library.functions
2076
.gnutls_global_set_log_function(debug_gnutls))
2078
# Redirect stdin so all checkers get /dev/null
2079
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2080
os.dup2(null, sys.stdin.fileno())
2084
# No console logging
2085
logger.removeHandler(console)
2087
# Need to fork before connecting to D-Bus
2089
# Close all input and output, do double fork, etc.
724
service = AvahiService(name = server_settings["servicename"],
725
type = "_mandos._tcp", );
726
if server_settings["interface"]:
727
service.interface = if_nametoindex(server_settings["interface"])
2092
729
global main_loop
2093
732
# From the Avahi example code
2094
733
DBusGMainLoop(set_as_default=True )
2095
734
main_loop = gobject.MainLoop()
2096
735
bus = dbus.SystemBus()
736
server = dbus.Interface(
737
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
738
avahi.DBUS_INTERFACE_SERVER )
2097
739
# End of Avahi example code
2100
bus_name = dbus.service.BusName("se.recompile.Mandos",
2101
bus, do_not_queue=True)
2102
old_bus_name = (dbus.service.BusName
2103
("se.bsnet.fukt.Mandos", bus,
2105
except dbus.exceptions.NameExistsException as e:
2106
logger.error(unicode(e) + ", disabling D-Bus")
2108
server_settings["use_dbus"] = False
2109
tcp_server.use_dbus = False
2110
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2111
service = AvahiServiceToSyslog(name =
2112
server_settings["servicename"],
2113
servicetype = "_mandos._tcp",
2114
protocol = protocol, bus = bus)
2115
if server_settings["interface"]:
2116
service.interface = (if_nametoindex
2117
(str(server_settings["interface"])))
2119
global multiprocessing_manager
2120
multiprocessing_manager = multiprocessing.Manager()
2122
client_class = Client
2124
client_class = functools.partial(ClientDBusTransitional,
2127
special_settings = {
2128
# Some settings need to be accessd by special methods;
2129
# booleans need .getboolean(), etc. Here is a list of them:
2130
"approved_by_default":
2132
client_config.getboolean(section, "approved_by_default"),
2134
# Construct a new dict of client settings of this form:
2135
# { client_name: {setting_name: value, ...}, ...}
2136
# with exceptions for any special settings as defined above
2137
client_settings = dict((clientname,
2140
setting not in special_settings
2141
else special_settings[setting]
2144
in client_config.items(clientname)))
2145
for clientname in client_config.sections())
2147
old_client_settings = {}
2150
# Get client data and settings from last running state.
2151
if server_settings["restore"]:
2153
with open(stored_state_path, "rb") as stored_state:
2154
clients_data, old_client_settings = (
2155
pickle.load(stored_state))
2156
os.remove(stored_state_path)
2157
except IOError as e:
2158
logger.warning("Could not load persistant state: {0}"
2160
if e.errno != errno.ENOENT:
2163
for client in clients_data:
2164
client_name = client["name"]
2166
# Decide which value to use after restoring saved state.
2167
# We have three different values: Old config file,
2168
# new config file, and saved state.
2169
# New config value takes precedence if it differs from old
2170
# config value, otherwise use saved state.
2171
for name, value in client_settings[client_name].items():
2173
# For each value in new config, check if it differs
2174
# from the old config value (Except for the "secret"
2176
if (name != "secret" and
2177
value != old_client_settings[client_name][name]):
2178
setattr(client, name, value)
2182
# Clients who has passed its expire date, can still be enabled
2183
# if its last checker was sucessful. Clients who checkers
2184
# failed before we stored it state is asumed to had failed
2185
# checker during downtime.
2186
if client["enabled"] and client["last_checked_ok"]:
2187
if ((datetime.datetime.utcnow()
2188
- client["last_checked_ok"]) > client["interval"]):
2189
if client["last_checker_status"] != 0:
2190
client["enabled"] = False
2192
client["expires"] = (datetime.datetime.utcnow()
2193
+ client["timeout"])
2195
client["changedstate"] = (multiprocessing_manager
2196
.Condition(multiprocessing_manager
2199
new_client = ClientDBusTransitional.__new__(
2200
ClientDBusTransitional)
2201
tcp_server.clients[client_name] = new_client
2202
new_client.bus = bus
2203
for name, value in client.iteritems():
2204
setattr(new_client, name, value)
2205
new_client._approvals_pending = 0
2206
new_client.add_to_dbus()
2208
tcp_server.clients[client_name] = Client.__new__(Client)
2209
for name, value in client.iteritems():
2210
setattr(tcp_server.clients[client_name], name, value)
2212
tcp_server.clients[client_name].decrypt_secret(
2213
client_settings[client_name]["secret"])
2215
# Create/remove clients based on new changes made to config
2216
for clientname in set(old_client_settings) - set(client_settings):
2217
del tcp_server.clients[clientname]
2218
for clientname in set(client_settings) - set(old_client_settings):
2219
tcp_server.clients[clientname] = client_class(name
2225
if not tcp_server.clients:
2226
logger.warning("No clients defined")
2232
pidfile.write(str(pid) + "\n".encode("utf-8"))
2235
logger.error("Could not write to file %r with PID %d",
2238
# "pidfile" was never created
741
debug = server_settings["debug"]
744
console = logging.StreamHandler()
745
# console.setLevel(logging.DEBUG)
746
console.setFormatter(logging.Formatter\
747
('%(levelname)s: %(message)s'))
748
logger.addHandler(console)
752
def remove_from_clients(client):
753
clients.remove(client)
755
logger.critical(u"No clients left, exiting")
758
clients.update(Set(Client(name = section,
759
stop_hook = remove_from_clients,
761
= dict(client_config.items(section)))
762
for section in client_config.sections()))
768
"Cleanup function; run on exit"
770
# From the Avahi example code
771
if not group is None:
774
# End of Avahi example code
777
client = clients.pop()
778
client.stop_hook = None
781
atexit.register(cleanup)
2242
784
signal.signal(signal.SIGINT, signal.SIG_IGN)
2244
785
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2245
786
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2248
class MandosDBusService(dbus.service.Object):
2249
"""A D-Bus proxy object"""
2251
dbus.service.Object.__init__(self, bus, "/")
2252
_interface = "se.recompile.Mandos"
2254
@dbus.service.signal(_interface, signature="o")
2255
def ClientAdded(self, objpath):
2259
@dbus.service.signal(_interface, signature="ss")
2260
def ClientNotFound(self, fingerprint, address):
2264
@dbus.service.signal(_interface, signature="os")
2265
def ClientRemoved(self, objpath, name):
2269
@dbus.service.method(_interface, out_signature="ao")
2270
def GetAllClients(self):
2272
return dbus.Array(c.dbus_object_path
2274
tcp_server.clients.itervalues())
2276
@dbus.service.method(_interface,
2277
out_signature="a{oa{sv}}")
2278
def GetAllClientsWithProperties(self):
2280
return dbus.Dictionary(
2281
((c.dbus_object_path, c.GetAll(""))
2282
for c in tcp_server.clients.itervalues()),
2285
@dbus.service.method(_interface, in_signature="o")
2286
def RemoveClient(self, object_path):
2288
for c in tcp_server.clients.itervalues():
2289
if c.dbus_object_path == object_path:
2290
del tcp_server.clients[c.name]
2291
c.remove_from_connection()
2292
# Don't signal anything except ClientRemoved
2293
c.disable(quiet=True)
2295
self.ClientRemoved(object_path, c.name)
2297
raise KeyError(object_path)
2301
class MandosDBusServiceTransitional(MandosDBusService):
2302
__metaclass__ = AlternateDBusNamesMetaclass
2303
mandos_dbus_service = MandosDBusServiceTransitional()
2306
"Cleanup function; run on exit"
2309
multiprocessing.active_children()
2310
if not (tcp_server.clients or client_settings):
2313
# Store client before exiting. Secrets are encrypted with key
2314
# based on what config file has. If config file is
2315
# removed/edited, old secret will thus be unrecovable.
2317
for client in tcp_server.clients.itervalues():
2318
client.encrypt_secret(
2319
client_settings[client.name]["secret"])
2323
# A list of attributes that will not be stored when
2325
exclude = set(("bus", "changedstate", "secret"))
2326
for name, typ in inspect.getmembers(dbus.service.Object):
2329
client_dict["encrypted_secret"] = client.encrypted_secret
2330
for attr in client.client_structure:
2331
if attr not in exclude:
2332
client_dict[attr] = getattr(client, attr)
2334
clients.append(client_dict)
2335
del client_settings[client.name]["secret"]
2338
with os.fdopen(os.open(stored_state_path,
2339
os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
2340
stat.S_IRUSR | stat.S_IWUSR),
2341
"wb") as stored_state:
2342
pickle.dump((clients, client_settings), stored_state)
2343
except IOError as e:
2344
logger.warning("Could not save persistant state: {0}"
2346
if e.errno != errno.ENOENT:
2349
# Delete all clients, and settings from config
2350
while tcp_server.clients:
2351
name, client = tcp_server.clients.popitem()
2353
client.remove_from_connection()
2354
# Don't signal anything except ClientRemoved
2355
client.disable(quiet=True)
2358
mandos_dbus_service.ClientRemoved(client
2361
client_settings.clear()
2363
atexit.register(cleanup)
2365
for client in tcp_server.clients.itervalues():
2368
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2369
# Need to initiate checking of clients
2371
client.init_checker()
2375
tcp_server.server_activate()
788
for client in clients:
791
tcp_server = IPv6_TCPServer((server_settings["address"],
792
server_settings["port"]),
794
settings=server_settings,
2377
796
# Find out what port we got
2378
797
service.port = tcp_server.socket.getsockname()[1]
2380
logger.info("Now listening on address %r, port %d,"
2381
" flowinfo %d, scope_id %d"
2382
% tcp_server.socket.getsockname())
2384
logger.info("Now listening on address %r, port %d"
2385
% tcp_server.socket.getsockname())
798
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
799
u" scope_id %d" % tcp_server.socket.getsockname())
2387
801
#service.interface = tcp_server.socket.getsockname()[3]
2390
804
# From the Avahi example code
805
server.connect_to_signal("StateChanged", server_state_changed)
2393
except dbus.exceptions.DBusException as error:
2394
logger.critical("DBusException: %s", error)
807
server_state_changed(server.GetState())
808
except dbus.exceptions.DBusException, error:
809
logger.critical(u"DBusException: %s", error)
2397
811
# End of Avahi example code
2399
813
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2400
814
lambda *args, **kwargs:
2401
(tcp_server.handle_request
2402
(*args[2:], **kwargs) or True))
815
tcp_server.handle_request\
816
(*args[2:], **kwargs) or True)
2404
818
logger.debug("Starting main loop")
819
main_loop_started = True
2406
except AvahiError as error:
2407
logger.critical("AvahiError: %s", error)
821
except AvahiError, error:
822
logger.critical(u"AvahiError: %s" + unicode(error))
2410
824
except KeyboardInterrupt:
2412
print("", file=sys.stderr)
2413
logger.debug("Server received KeyboardInterrupt")
2414
logger.debug("Server exiting")
2415
# Must run before the D-Bus bus name gets deregistered
2419
828
if __name__ == '__main__':