157
107
max_renames: integer; maximum number of renames
158
108
rename_count: integer; counter so we only rename after collisions
159
109
a sensible number of times
160
group: D-Bus Entry Group
162
bus: dbus.SystemBus()
164
111
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
165
servicetype = None, port = None, TXT = None,
166
domain = "", host = "", max_renames = 32768,
167
protocol = avahi.PROTO_UNSPEC, bus = None):
112
servicetype = None, port = None, TXT = None, domain = "",
113
host = "", max_renames = 32768):
168
114
self.interface = interface
170
116
self.type = servicetype
172
self.TXT = TXT if TXT is not None else []
173
122
self.domain = domain
175
124
self.rename_count = 0
176
125
self.max_renames = max_renames
177
self.protocol = protocol
178
self.group = None # our entry group
181
self.entry_group_state_changed_match = None
182
126
def rename(self):
183
127
"""Derived from the Avahi example code"""
184
128
if self.rename_count >= self.max_renames:
185
logger.critical("No suitable Zeroconf service name found"
186
" after %i retries, exiting.",
129
logger.critical(u"No suitable Zeroconf service name found"
130
u" after %i retries, exiting.",
187
131
self.rename_count)
188
132
raise AvahiServiceError("Too many renames")
189
self.name = unicode(self.server
190
.GetAlternativeServiceName(self.name))
191
logger.info("Changing Zeroconf service name to %r ...",
133
self.name = server.GetAlternativeServiceName(self.name)
134
logger.info(u"Changing Zeroconf service name to %r ...",
136
syslogger.setFormatter(logging.Formatter\
137
('Mandos (%s): %%(levelname)s:'
138
' %%(message)s' % self.name))
196
except dbus.exceptions.DBusException as error:
197
logger.critical("DBusException: %s", error)
200
141
self.rename_count += 1
201
142
def remove(self):
202
143
"""Derived from the Avahi example code"""
203
if self.entry_group_state_changed_match is not None:
204
self.entry_group_state_changed_match.remove()
205
self.entry_group_state_changed_match = None
206
if self.group is not None:
144
if group is not None:
209
147
"""Derived from the Avahi example code"""
211
if self.group is None:
212
self.group = dbus.Interface(
213
self.bus.get_object(avahi.DBUS_NAME,
214
self.server.EntryGroupNew()),
215
avahi.DBUS_INTERFACE_ENTRY_GROUP)
216
self.entry_group_state_changed_match = (
217
self.group.connect_to_signal(
218
'StateChanged', self.entry_group_state_changed))
219
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
220
self.name, self.type)
221
self.group.AddService(
224
dbus.UInt32(0), # flags
225
self.name, self.type,
226
self.domain, self.host,
227
dbus.UInt16(self.port),
228
avahi.string_array_to_txt_array(self.TXT))
230
def entry_group_state_changed(self, state, error):
231
"""Derived from the Avahi example code"""
232
logger.debug("Avahi entry group state change: %i", state)
234
if state == avahi.ENTRY_GROUP_ESTABLISHED:
235
logger.debug("Zeroconf service established.")
236
elif state == avahi.ENTRY_GROUP_COLLISION:
237
logger.info("Zeroconf service name collision.")
239
elif state == avahi.ENTRY_GROUP_FAILURE:
240
logger.critical("Avahi: Error in group state changed %s",
242
raise AvahiGroupError("State changed: %s"
245
"""Derived from the Avahi example code"""
246
if self.group is not None:
249
except (dbus.exceptions.UnknownMethodException,
250
dbus.exceptions.DBusException):
254
def server_state_changed(self, state, error=None):
255
"""Derived from the Avahi example code"""
256
logger.debug("Avahi server state change: %i", state)
257
bad_states = { avahi.SERVER_INVALID:
258
"Zeroconf server invalid",
259
avahi.SERVER_REGISTERING: None,
260
avahi.SERVER_COLLISION:
261
"Zeroconf server name collision",
262
avahi.SERVER_FAILURE:
263
"Zeroconf server failure" }
264
if state in bad_states:
265
if bad_states[state] is not None:
267
logger.error(bad_states[state])
269
logger.error(bad_states[state] + ": %r", error)
271
elif state == avahi.SERVER_RUNNING:
275
logger.debug("Unknown state: %r", state)
277
logger.debug("Unknown state: %r: %r", state, error)
279
"""Derived from the Avahi example code"""
280
if self.server is None:
281
self.server = dbus.Interface(
282
self.bus.get_object(avahi.DBUS_NAME,
283
avahi.DBUS_PATH_SERVER,
284
follow_name_owner_changes=True),
285
avahi.DBUS_INTERFACE_SERVER)
286
self.server.connect_to_signal("StateChanged",
287
self.server_state_changed)
288
self.server_state_changed(self.server.GetState())
290
class AvahiServiceToSyslog(AvahiService):
292
"""Add the new name to the syslog messages"""
293
ret = AvahiService.rename(self)
294
syslogger.setFormatter(logging.Formatter
295
('Mandos (%s) [%%(process)d]:'
296
' %%(levelname)s: %%(message)s'
300
def _timedelta_to_milliseconds(td):
301
"Convert a datetime.timedelta() to milliseconds"
302
return ((td.days * 24 * 60 * 60 * 1000)
303
+ (td.seconds * 1000)
304
+ (td.microseconds // 1000))
150
group = dbus.Interface\
151
(bus.get_object(avahi.DBUS_NAME,
152
server.EntryGroupNew()),
153
avahi.DBUS_INTERFACE_ENTRY_GROUP)
154
group.connect_to_signal('StateChanged',
155
entry_group_state_changed)
156
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
157
service.name, service.type)
159
self.interface, # interface
160
avahi.PROTO_INET6, # protocol
161
dbus.UInt32(0), # flags
162
self.name, self.type,
163
self.domain, self.host,
164
dbus.UInt16(self.port),
165
avahi.string_array_to_txt_array(self.TXT))
168
# From the Avahi example code:
169
group = None # our entry group
170
# End of Avahi example code
306
173
class Client(object):
307
174
"""A representation of a client host served by this server.
310
_approved: bool(); 'None' if not yet approved/disapproved
311
approval_delay: datetime.timedelta(); Time to wait for approval
312
approval_duration: datetime.timedelta(); Duration of one approval
313
checker: subprocess.Popen(); a running checker process used
314
to see if the client lives.
315
'None' if no process is running.
316
checker_callback_tag: a gobject event source tag, or None
317
checker_command: string; External command which is run to check
318
if client lives. %() expansions are done at
176
name: string; from the config file, used in log messages
177
fingerprint: string (40 or 32 hexadecimal digits); used to
178
uniquely identify the client
179
secret: bytestring; sent verbatim (over TLS) to client
180
host: string; available for use by the checker command
181
created: datetime.datetime(); object creation, not client host
182
last_checked_ok: datetime.datetime() or None if not yet checked OK
183
timeout: datetime.timedelta(); How long from last_checked_ok
184
until this client is invalid
185
interval: datetime.timedelta(); How often to start a new checker
186
stop_hook: If set, called by stop() as stop_hook(self)
187
checker: subprocess.Popen(); a running checker process used
188
to see if the client lives.
189
'None' if no process is running.
190
checker_initiator_tag: a gobject event source tag, or None
191
stop_initiator_tag: - '' -
192
checker_callback_tag: - '' -
193
checker_command: string; External command which is run to check if
194
client lives. %() expansions are done at
319
195
runtime with vars(self) as dict, so that for
320
196
instance %(name)s can be used in the command.
321
checker_initiator_tag: a gobject event source tag, or None
322
created: datetime.datetime(); (UTC) object creation
323
client_structure: Object describing what attributes a client has
324
and is used for storing the client at exit
325
current_checker_command: string; current running checker_command
326
disable_initiator_tag: a gobject event source tag, or None
328
fingerprint: string (40 or 32 hexadecimal digits); used to
329
uniquely identify the client
330
host: string; available for use by the checker command
331
interval: datetime.timedelta(); How often to start a new checker
332
last_approval_request: datetime.datetime(); (UTC) or None
333
last_checked_ok: datetime.datetime(); (UTC) or None
334
last_checker_status: integer between 0 and 255 reflecting exit status
335
of last checker. -1 reflect crashed checker,
337
last_enabled: datetime.datetime(); (UTC)
338
name: string; from the config file, used in log messages and
340
secret: bytestring; sent verbatim (over TLS) to client
341
timeout: datetime.timedelta(); How long from last_checked_ok
342
until this client is disabled
343
extended_timeout: extra long timeout when password has been sent
344
runtime_expansions: Allowed attributes for runtime expansion.
345
expires: datetime.datetime(); time (UTC) when a client will be
198
_timeout: Real variable for 'timeout'
199
_interval: Real variable for 'interval'
200
_timeout_milliseconds: Used when calling gobject.timeout_add()
201
_interval_milliseconds: - '' -
349
runtime_expansions = ("approval_delay", "approval_duration",
350
"created", "enabled", "fingerprint",
351
"host", "interval", "last_checked_ok",
352
"last_enabled", "name", "timeout")
354
def timeout_milliseconds(self):
355
"Return the 'timeout' attribute in milliseconds"
356
return _timedelta_to_milliseconds(self.timeout)
358
def extended_timeout_milliseconds(self):
359
"Return the 'extended_timeout' attribute in milliseconds"
360
return _timedelta_to_milliseconds(self.extended_timeout)
362
def interval_milliseconds(self):
363
"Return the 'interval' attribute in milliseconds"
364
return _timedelta_to_milliseconds(self.interval)
366
def approval_delay_milliseconds(self):
367
return _timedelta_to_milliseconds(self.approval_delay)
369
def __init__(self, name = None, config=None):
203
def _set_timeout(self, timeout):
204
"Setter function for 'timeout' attribute"
205
self._timeout = timeout
206
self._timeout_milliseconds = ((self.timeout.days
207
* 24 * 60 * 60 * 1000)
208
+ (self.timeout.seconds * 1000)
209
+ (self.timeout.microseconds
211
timeout = property(lambda self: self._timeout,
214
def _set_interval(self, interval):
215
"Setter function for 'interval' attribute"
216
self._interval = interval
217
self._interval_milliseconds = ((self.interval.days
218
* 24 * 60 * 60 * 1000)
219
+ (self.interval.seconds
221
+ (self.interval.microseconds
223
interval = property(lambda self: self._interval,
226
def __init__(self, name = None, stop_hook=None, config=None):
370
227
"""Note: the 'checker' key in 'config' sets the
371
228
'checker_command' attribute and *not* the 'checker'
374
230
if config is None:
376
logger.debug("Creating client %r", self.name)
233
logger.debug(u"Creating client %r", self.name)
377
234
# Uppercase and remove spaces from fingerprint for later
378
235
# comparison purposes with return value from the fingerprint()
380
self.fingerprint = (config["fingerprint"].upper()
382
logger.debug(" Fingerprint: %s", self.fingerprint)
237
self.fingerprint = config["fingerprint"].upper()\
239
logger.debug(u" Fingerprint: %s", self.fingerprint)
383
240
if "secret" in config:
384
self.secret = config["secret"].decode("base64")
241
self.secret = config["secret"].decode(u"base64")
385
242
elif "secfile" in config:
386
with open(os.path.expanduser(os.path.expandvars
387
(config["secfile"])),
389
self.secret = secfile.read()
243
secfile = open(config["secfile"])
244
self.secret = secfile.read()
391
raise TypeError("No secret or secfile for client %s"
247
raise TypeError(u"No secret or secfile for client %s"
393
249
self.host = config.get("host", "")
394
self.created = datetime.datetime.utcnow()
396
self.last_approval_request = None
397
self.last_enabled = datetime.datetime.utcnow()
250
self.created = datetime.datetime.now()
398
251
self.last_checked_ok = None
399
self.last_checker_status = None
400
252
self.timeout = string_to_delta(config["timeout"])
401
self.extended_timeout = string_to_delta(config
402
["extended_timeout"])
403
253
self.interval = string_to_delta(config["interval"])
254
self.stop_hook = stop_hook
404
255
self.checker = None
405
256
self.checker_initiator_tag = None
406
self.disable_initiator_tag = None
407
self.expires = datetime.datetime.utcnow() + self.timeout
257
self.stop_initiator_tag = None
408
258
self.checker_callback_tag = None
409
self.checker_command = config["checker"]
410
self.current_checker_command = None
411
self._approved = None
412
self.approved_by_default = config.get("approved_by_default",
414
self.approvals_pending = 0
415
self.approval_delay = string_to_delta(
416
config["approval_delay"])
417
self.approval_duration = string_to_delta(
418
config["approval_duration"])
419
self.changedstate = (multiprocessing_manager
420
.Condition(multiprocessing_manager
422
self.client_structure = [attr for attr in self.__dict__.iterkeys() if not attr.startswith("_")]
423
self.client_structure.append("client_structure")
425
for name, t in inspect.getmembers(type(self),
426
lambda obj: isinstance(obj, property)):
427
if not name.startswith("_"):
428
self.client_structure.append(name)
430
# Send notice to process children that client state has changed
431
def send_changedstate(self):
432
with self.changedstate:
433
self.changedstate.notify_all()
259
self.check_command = config["checker"]
436
261
"""Start this client's checker and timeout hooks"""
437
if getattr(self, "enabled", False):
440
self.send_changedstate()
441
self.expires = datetime.datetime.utcnow() + self.timeout
443
self.last_enabled = datetime.datetime.utcnow()
446
def disable(self, quiet=True):
447
"""Disable this client."""
448
if not getattr(self, "enabled", False):
262
# Schedule a new checker to be started an 'interval' from now,
263
# and every interval from then on.
264
self.checker_initiator_tag = gobject.timeout_add\
265
(self._interval_milliseconds,
267
# Also start a new checker *right now*.
269
# Schedule a stop() when 'timeout' has passed
270
self.stop_initiator_tag = gobject.timeout_add\
271
(self._timeout_milliseconds,
275
The possibility that a client might be restarted is left open,
276
but not currently used."""
277
# If this client doesn't have a secret, it is already stopped.
278
if hasattr(self, "secret") and self.secret:
279
logger.info(u"Stopping client %s", self.name)
451
self.send_changedstate()
453
logger.info("Disabling client %s", self.name)
454
if getattr(self, "disable_initiator_tag", False):
455
gobject.source_remove(self.disable_initiator_tag)
456
self.disable_initiator_tag = None
283
if getattr(self, "stop_initiator_tag", False):
284
gobject.source_remove(self.stop_initiator_tag)
285
self.stop_initiator_tag = None
458
286
if getattr(self, "checker_initiator_tag", False):
459
287
gobject.source_remove(self.checker_initiator_tag)
460
288
self.checker_initiator_tag = None
461
289
self.stop_checker()
463
292
# Do not run this again if called by a gobject.timeout_add
466
294
def __del__(self):
469
def init_checker(self):
470
# Schedule a new checker to be started an 'interval' from now,
471
# and every interval from then on.
472
self.checker_initiator_tag = (gobject.timeout_add
473
(self.interval_milliseconds(),
475
# Schedule a disable() when 'timeout' has passed
476
self.disable_initiator_tag = (gobject.timeout_add
477
(self.timeout_milliseconds(),
479
# Also start a new checker *right now*.
482
def checker_callback(self, pid, condition, command):
295
self.stop_hook = None
297
def checker_callback(self, pid, condition):
483
298
"""The checker has completed, so take appropriate actions."""
299
now = datetime.datetime.now()
484
300
self.checker_callback_tag = None
485
301
self.checker = None
486
if os.WIFEXITED(condition):
487
self.last_checker_status = os.WEXITSTATUS(condition)
488
if self.last_checker_status == 0:
489
logger.info("Checker for %(name)s succeeded",
493
logger.info("Checker for %(name)s failed",
496
self.last_checker_status = -1
497
logger.warning("Checker for %(name)s crashed?",
302
if os.WIFEXITED(condition) \
303
and (os.WEXITSTATUS(condition) == 0):
304
logger.info(u"Checker for %(name)s succeeded",
306
self.last_checked_ok = now
307
gobject.source_remove(self.stop_initiator_tag)
308
self.stop_initiator_tag = gobject.timeout_add\
309
(self._timeout_milliseconds,
311
elif not os.WIFEXITED(condition):
312
logger.warning(u"Checker for %(name)s crashed?",
500
def checked_ok(self, timeout=None):
501
"""Bump up the timeout for this client.
503
This should only be called when the client has been seen,
507
timeout = self.timeout
508
self.last_checked_ok = datetime.datetime.utcnow()
509
if self.disable_initiator_tag is not None:
510
gobject.source_remove(self.disable_initiator_tag)
511
if getattr(self, "enabled", False):
512
self.disable_initiator_tag = (gobject.timeout_add
513
(_timedelta_to_milliseconds
514
(timeout), self.disable))
515
self.expires = datetime.datetime.utcnow() + timeout
517
def need_approval(self):
518
self.last_approval_request = datetime.datetime.utcnow()
315
logger.info(u"Checker for %(name)s failed",
520
317
def start_checker(self):
521
318
"""Start a new checker subprocess if one is not running.
523
319
If a checker already exists, leave it running and do
525
321
# The reason for not killing a running checker is that if we
599
366
self.checker_callback_tag = None
600
367
if getattr(self, "checker", None) is None:
602
logger.debug("Stopping checker for %(name)s", vars(self))
369
logger.debug(u"Stopping checker for %(name)s", vars(self))
604
371
os.kill(self.checker.pid, signal.SIGTERM)
606
373
#if self.checker.poll() is None:
607
374
# os.kill(self.checker.pid, signal.SIGKILL)
608
except OSError as error:
375
except OSError, error:
609
376
if error.errno != errno.ESRCH: # No such process
611
378
self.checker = None
613
# Encrypts a client secret and stores it in a varible encrypted_secret
614
def encrypt_secret(self, key):
615
# Encryption-key need to be of a specific size, so we hash inputed key
616
hasheng = hashlib.sha256()
618
encryptionkey = hasheng.digest()
620
# Create validation hash so we know at decryption if it was sucessful
621
hasheng = hashlib.sha256()
622
hasheng.update(self.secret)
623
validationhash = hasheng.digest()
626
iv = os.urandom(Crypto.Cipher.AES.block_size)
627
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
628
Crypto.Cipher.AES.MODE_CFB, iv)
629
ciphertext = ciphereng.encrypt(validationhash+self.secret)
630
self.encrypted_secret = (ciphertext, iv)
632
# Decrypt a encrypted client secret
633
def decrypt_secret(self, key):
634
# Decryption-key need to be of a specific size, so we hash inputed key
635
hasheng = hashlib.sha256()
637
encryptionkey = hasheng.digest()
639
# Decrypt encrypted secret
640
ciphertext, iv = self.encrypted_secret
641
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
642
Crypto.Cipher.AES.MODE_CFB, iv)
643
plain = ciphereng.decrypt(ciphertext)
645
# Validate decrypted secret to know if it was succesful
646
hasheng = hashlib.sha256()
647
validationhash = plain[:hasheng.digest_size]
648
secret = plain[hasheng.digest_size:]
649
hasheng.update(secret)
651
# if validation fails, we use key as new secret. Otherwhise, we use
652
# the decrypted secret
653
if hasheng.digest() == validationhash:
657
del self.encrypted_secret
660
def dbus_service_property(dbus_interface, signature="v",
661
access="readwrite", byte_arrays=False):
662
"""Decorators for marking methods of a DBusObjectWithProperties to
663
become properties on the D-Bus.
665
The decorated method will be called with no arguments by "Get"
666
and with one argument by "Set".
668
The parameters, where they are supported, are the same as
669
dbus.service.method, except there is only "signature", since the
670
type from Get() and the type sent to Set() is the same.
672
# Encoding deeply encoded byte arrays is not supported yet by the
673
# "Set" method, so we fail early here:
674
if byte_arrays and signature != "ay":
675
raise ValueError("Byte arrays not supported for non-'ay'"
676
" signature %r" % signature)
678
func._dbus_is_property = True
679
func._dbus_interface = dbus_interface
680
func._dbus_signature = signature
681
func._dbus_access = access
682
func._dbus_name = func.__name__
683
if func._dbus_name.endswith("_dbus_property"):
684
func._dbus_name = func._dbus_name[:-14]
685
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
690
class DBusPropertyException(dbus.exceptions.DBusException):
691
"""A base class for D-Bus property-related exceptions
693
def __unicode__(self):
694
return unicode(str(self))
697
class DBusPropertyAccessException(DBusPropertyException):
698
"""A property's access permissions disallows an operation.
703
class DBusPropertyNotFound(DBusPropertyException):
704
"""An attempt was made to access a non-existing property.
709
class DBusObjectWithProperties(dbus.service.Object):
710
"""A D-Bus object with properties.
712
Classes inheriting from this can use the dbus_service_property
713
decorator to expose methods as D-Bus properties. It exposes the
714
standard Get(), Set(), and GetAll() methods on the D-Bus.
718
def _is_dbus_property(obj):
719
return getattr(obj, "_dbus_is_property", False)
721
def _get_all_dbus_properties(self):
722
"""Returns a generator of (name, attribute) pairs
724
return ((prop.__get__(self)._dbus_name, prop.__get__(self))
725
for cls in self.__class__.__mro__
727
inspect.getmembers(cls, self._is_dbus_property))
729
def _get_dbus_property(self, interface_name, property_name):
730
"""Returns a bound method if one exists which is a D-Bus
731
property with the specified name and interface.
733
for cls in self.__class__.__mro__:
734
for name, value in (inspect.getmembers
735
(cls, self._is_dbus_property)):
736
if (value._dbus_name == property_name
737
and value._dbus_interface == interface_name):
738
return value.__get__(self)
741
raise DBusPropertyNotFound(self.dbus_object_path + ":"
742
+ interface_name + "."
745
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
747
def Get(self, interface_name, property_name):
748
"""Standard D-Bus property Get() method, see D-Bus standard.
750
prop = self._get_dbus_property(interface_name, property_name)
751
if prop._dbus_access == "write":
752
raise DBusPropertyAccessException(property_name)
754
if not hasattr(value, "variant_level"):
756
return type(value)(value, variant_level=value.variant_level+1)
758
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
759
def Set(self, interface_name, property_name, value):
760
"""Standard D-Bus property Set() method, see D-Bus standard.
762
prop = self._get_dbus_property(interface_name, property_name)
763
if prop._dbus_access == "read":
764
raise DBusPropertyAccessException(property_name)
765
if prop._dbus_get_args_options["byte_arrays"]:
766
# The byte_arrays option is not supported yet on
767
# signatures other than "ay".
768
if prop._dbus_signature != "ay":
770
value = dbus.ByteArray(''.join(unichr(byte)
774
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
775
out_signature="a{sv}")
776
def GetAll(self, interface_name):
777
"""Standard D-Bus property GetAll() method, see D-Bus
780
Note: Will not include properties with access="write".
783
for name, prop in self._get_all_dbus_properties():
785
and interface_name != prop._dbus_interface):
786
# Interface non-empty but did not match
788
# Ignore write-only properties
789
if prop._dbus_access == "write":
792
if not hasattr(value, "variant_level"):
793
properties[name] = value
795
properties[name] = type(value)(value, variant_level=
796
value.variant_level+1)
797
return dbus.Dictionary(properties, signature="sv")
799
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
801
path_keyword='object_path',
802
connection_keyword='connection')
803
def Introspect(self, object_path, connection):
804
"""Standard D-Bus method, overloaded to insert property tags.
806
xmlstring = dbus.service.Object.Introspect(self, object_path,
809
document = xml.dom.minidom.parseString(xmlstring)
810
def make_tag(document, name, prop):
811
e = document.createElement("property")
812
e.setAttribute("name", name)
813
e.setAttribute("type", prop._dbus_signature)
814
e.setAttribute("access", prop._dbus_access)
816
for if_tag in document.getElementsByTagName("interface"):
817
for tag in (make_tag(document, name, prop)
819
in self._get_all_dbus_properties()
820
if prop._dbus_interface
821
== if_tag.getAttribute("name")):
822
if_tag.appendChild(tag)
823
# Add the names to the return values for the
824
# "org.freedesktop.DBus.Properties" methods
825
if (if_tag.getAttribute("name")
826
== "org.freedesktop.DBus.Properties"):
827
for cn in if_tag.getElementsByTagName("method"):
828
if cn.getAttribute("name") == "Get":
829
for arg in cn.getElementsByTagName("arg"):
830
if (arg.getAttribute("direction")
832
arg.setAttribute("name", "value")
833
elif cn.getAttribute("name") == "GetAll":
834
for arg in cn.getElementsByTagName("arg"):
835
if (arg.getAttribute("direction")
837
arg.setAttribute("name", "props")
838
xmlstring = document.toxml("utf-8")
840
except (AttributeError, xml.dom.DOMException,
841
xml.parsers.expat.ExpatError) as error:
842
logger.error("Failed to override Introspection method",
847
def datetime_to_dbus (dt, variant_level=0):
848
"""Convert a UTC datetime.datetime() to a D-Bus type."""
850
return dbus.String("", variant_level = variant_level)
851
return dbus.String(dt.isoformat(),
852
variant_level=variant_level)
855
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
857
"""Applied to an empty subclass of a D-Bus object, this metaclass
858
will add additional D-Bus attributes matching a certain pattern.
860
def __new__(mcs, name, bases, attr):
861
# Go through all the base classes which could have D-Bus
862
# methods, signals, or properties in them
863
for base in (b for b in bases
864
if issubclass(b, dbus.service.Object)):
865
# Go though all attributes of the base class
866
for attrname, attribute in inspect.getmembers(base):
867
# Ignore non-D-Bus attributes, and D-Bus attributes
868
# with the wrong interface name
869
if (not hasattr(attribute, "_dbus_interface")
870
or not attribute._dbus_interface
871
.startswith("se.recompile.Mandos")):
873
# Create an alternate D-Bus interface name based on
875
alt_interface = (attribute._dbus_interface
876
.replace("se.recompile.Mandos",
877
"se.bsnet.fukt.Mandos"))
878
# Is this a D-Bus signal?
879
if getattr(attribute, "_dbus_is_signal", False):
880
# Extract the original non-method function by
882
nonmethod_func = (dict(
883
zip(attribute.func_code.co_freevars,
884
attribute.__closure__))["func"]
886
# Create a new, but exactly alike, function
887
# object, and decorate it to be a new D-Bus signal
888
# with the alternate D-Bus interface name
889
new_function = (dbus.service.signal
891
attribute._dbus_signature)
893
nonmethod_func.func_code,
894
nonmethod_func.func_globals,
895
nonmethod_func.func_name,
896
nonmethod_func.func_defaults,
897
nonmethod_func.func_closure)))
898
# Define a creator of a function to call both the
899
# old and new functions, so both the old and new
900
# signals gets sent when the function is called
901
def fixscope(func1, func2):
902
"""This function is a scope container to pass
903
func1 and func2 to the "call_both" function
904
outside of its arguments"""
905
def call_both(*args, **kwargs):
906
"""This function will emit two D-Bus
907
signals by calling func1 and func2"""
908
func1(*args, **kwargs)
909
func2(*args, **kwargs)
911
# Create the "call_both" function and add it to
913
attr[attrname] = fixscope(attribute,
915
# Is this a D-Bus method?
916
elif getattr(attribute, "_dbus_is_method", False):
917
# Create a new, but exactly alike, function
918
# object. Decorate it to be a new D-Bus method
919
# with the alternate D-Bus interface name. Add it
921
attr[attrname] = (dbus.service.method
923
attribute._dbus_in_signature,
924
attribute._dbus_out_signature)
926
(attribute.func_code,
927
attribute.func_globals,
929
attribute.func_defaults,
930
attribute.func_closure)))
931
# Is this a D-Bus property?
932
elif getattr(attribute, "_dbus_is_property", False):
933
# Create a new, but exactly alike, function
934
# object, and decorate it to be a new D-Bus
935
# property with the alternate D-Bus interface
936
# name. Add it to the class.
937
attr[attrname] = (dbus_service_property
939
attribute._dbus_signature,
940
attribute._dbus_access,
942
._dbus_get_args_options
945
(attribute.func_code,
946
attribute.func_globals,
948
attribute.func_defaults,
949
attribute.func_closure)))
950
return type.__new__(mcs, name, bases, attr)
953
class ClientDBus(Client, DBusObjectWithProperties):
954
"""A Client class using D-Bus
957
dbus_object_path: dbus.ObjectPath
958
bus: dbus.SystemBus()
961
runtime_expansions = (Client.runtime_expansions
962
+ ("dbus_object_path",))
964
# dbus.service.Object doesn't use super(), so we can't either.
966
def __init__(self, bus = None, *args, **kwargs):
968
Client.__init__(self, *args, **kwargs)
970
self._approvals_pending = 0
971
# Only now, when this client is initialized, can it show up on
973
client_object_name = unicode(self.name).translate(
976
self.dbus_object_path = (dbus.ObjectPath
977
("/clients/" + client_object_name))
978
DBusObjectWithProperties.__init__(self, self.bus,
979
self.dbus_object_path)
981
def notifychangeproperty(transform_func,
982
dbus_name, type_func=lambda x: x,
984
""" Modify a variable so that it's a property which announces
987
transform_fun: Function that takes a value and a variant_level
988
and transforms it to a D-Bus type.
989
dbus_name: D-Bus name of the variable
990
type_func: Function that transform the value before sending it
991
to the D-Bus. Default: no transform
992
variant_level: D-Bus variant level. Default: 1
994
attrname = "_{0}".format(dbus_name)
995
def setter(self, value):
996
if hasattr(self, "dbus_object_path"):
997
if (not hasattr(self, attrname) or
998
type_func(getattr(self, attrname, None))
999
!= type_func(value)):
1000
dbus_value = transform_func(type_func(value),
1003
self.PropertyChanged(dbus.String(dbus_name),
1005
setattr(self, attrname, value)
1007
return property(lambda self: getattr(self, attrname), setter)
1010
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1011
approvals_pending = notifychangeproperty(dbus.Boolean,
1014
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1015
last_enabled = notifychangeproperty(datetime_to_dbus,
1017
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1018
type_func = lambda checker:
1019
checker is not None)
1020
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1022
last_approval_request = notifychangeproperty(
1023
datetime_to_dbus, "LastApprovalRequest")
1024
approved_by_default = notifychangeproperty(dbus.Boolean,
1025
"ApprovedByDefault")
1026
approval_delay = notifychangeproperty(dbus.UInt16,
1029
_timedelta_to_milliseconds)
1030
approval_duration = notifychangeproperty(
1031
dbus.UInt16, "ApprovalDuration",
1032
type_func = _timedelta_to_milliseconds)
1033
host = notifychangeproperty(dbus.String, "Host")
1034
timeout = notifychangeproperty(dbus.UInt16, "Timeout",
1036
_timedelta_to_milliseconds)
1037
extended_timeout = notifychangeproperty(
1038
dbus.UInt16, "ExtendedTimeout",
1039
type_func = _timedelta_to_milliseconds)
1040
interval = notifychangeproperty(dbus.UInt16,
1043
_timedelta_to_milliseconds)
1044
checker_command = notifychangeproperty(dbus.String, "Checker")
1046
del notifychangeproperty
1048
def __del__(self, *args, **kwargs):
1050
self.remove_from_connection()
1053
if hasattr(DBusObjectWithProperties, "__del__"):
1054
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1055
Client.__del__(self, *args, **kwargs)
1057
def checker_callback(self, pid, condition, command,
1059
self.checker_callback_tag = None
1061
if os.WIFEXITED(condition):
1062
exitstatus = os.WEXITSTATUS(condition)
1064
self.CheckerCompleted(dbus.Int16(exitstatus),
1065
dbus.Int64(condition),
1066
dbus.String(command))
1069
self.CheckerCompleted(dbus.Int16(-1),
1070
dbus.Int64(condition),
1071
dbus.String(command))
1073
return Client.checker_callback(self, pid, condition, command,
1076
def start_checker(self, *args, **kwargs):
1077
old_checker = self.checker
1078
if self.checker is not None:
1079
old_checker_pid = self.checker.pid
1081
old_checker_pid = None
1082
r = Client.start_checker(self, *args, **kwargs)
1083
# Only if new checker process was started
1084
if (self.checker is not None
1085
and old_checker_pid != self.checker.pid):
1087
self.CheckerStarted(self.current_checker_command)
1090
def _reset_approved(self):
1091
self._approved = None
1094
def approve(self, value=True):
1095
self.send_changedstate()
1096
self._approved = value
1097
gobject.timeout_add(_timedelta_to_milliseconds
1098
(self.approval_duration),
1099
self._reset_approved)
1102
## D-Bus methods, signals & properties
1103
_interface = "se.recompile.Mandos.Client"
1107
# CheckerCompleted - signal
1108
@dbus.service.signal(_interface, signature="nxs")
1109
def CheckerCompleted(self, exitcode, waitstatus, command):
1113
# CheckerStarted - signal
1114
@dbus.service.signal(_interface, signature="s")
1115
def CheckerStarted(self, command):
1119
# PropertyChanged - signal
1120
@dbus.service.signal(_interface, signature="sv")
1121
def PropertyChanged(self, property, value):
1125
# GotSecret - signal
1126
@dbus.service.signal(_interface)
1127
def GotSecret(self):
1129
Is sent after a successful transfer of secret from the Mandos
1130
server to mandos-client
1135
@dbus.service.signal(_interface, signature="s")
1136
def Rejected(self, reason):
1140
# NeedApproval - signal
1141
@dbus.service.signal(_interface, signature="tb")
1142
def NeedApproval(self, timeout, default):
1144
return self.need_approval()
1146
# NeRwequest - signal
1147
@dbus.service.signal(_interface, signature="s")
1148
def NewRequest(self, ip):
1150
Is sent after a client request a password.
1157
@dbus.service.method(_interface, in_signature="b")
1158
def Approve(self, value):
1161
# CheckedOK - method
1162
@dbus.service.method(_interface)
1163
def CheckedOK(self):
1167
@dbus.service.method(_interface)
1172
# StartChecker - method
1173
@dbus.service.method(_interface)
1174
def StartChecker(self):
1176
self.start_checker()
1179
@dbus.service.method(_interface)
1184
# StopChecker - method
1185
@dbus.service.method(_interface)
1186
def StopChecker(self):
1191
# ApprovalPending - property
1192
@dbus_service_property(_interface, signature="b", access="read")
1193
def ApprovalPending_dbus_property(self):
1194
return dbus.Boolean(bool(self.approvals_pending))
1196
# ApprovedByDefault - property
1197
@dbus_service_property(_interface, signature="b",
1199
def ApprovedByDefault_dbus_property(self, value=None):
1200
if value is None: # get
1201
return dbus.Boolean(self.approved_by_default)
1202
self.approved_by_default = bool(value)
1204
# ApprovalDelay - property
1205
@dbus_service_property(_interface, signature="t",
1207
def ApprovalDelay_dbus_property(self, value=None):
1208
if value is None: # get
1209
return dbus.UInt64(self.approval_delay_milliseconds())
1210
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1212
# ApprovalDuration - property
1213
@dbus_service_property(_interface, signature="t",
1215
def ApprovalDuration_dbus_property(self, value=None):
1216
if value is None: # get
1217
return dbus.UInt64(_timedelta_to_milliseconds(
1218
self.approval_duration))
1219
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1222
@dbus_service_property(_interface, signature="s", access="read")
1223
def Name_dbus_property(self):
1224
return dbus.String(self.name)
1226
# Fingerprint - property
1227
@dbus_service_property(_interface, signature="s", access="read")
1228
def Fingerprint_dbus_property(self):
1229
return dbus.String(self.fingerprint)
1232
@dbus_service_property(_interface, signature="s",
1234
def Host_dbus_property(self, value=None):
1235
if value is None: # get
1236
return dbus.String(self.host)
1239
# Created - property
1240
@dbus_service_property(_interface, signature="s", access="read")
1241
def Created_dbus_property(self):
1242
return dbus.String(datetime_to_dbus(self.created))
1244
# LastEnabled - property
1245
@dbus_service_property(_interface, signature="s", access="read")
1246
def LastEnabled_dbus_property(self):
1247
return datetime_to_dbus(self.last_enabled)
1249
# Enabled - property
1250
@dbus_service_property(_interface, signature="b",
1252
def Enabled_dbus_property(self, value=None):
1253
if value is None: # get
1254
return dbus.Boolean(self.enabled)
1260
# LastCheckedOK - property
1261
@dbus_service_property(_interface, signature="s",
1263
def LastCheckedOK_dbus_property(self, value=None):
1264
if value is not None:
1267
return datetime_to_dbus(self.last_checked_ok)
1269
# Expires - property
1270
@dbus_service_property(_interface, signature="s", access="read")
1271
def Expires_dbus_property(self):
1272
return datetime_to_dbus(self.expires)
1274
# LastApprovalRequest - property
1275
@dbus_service_property(_interface, signature="s", access="read")
1276
def LastApprovalRequest_dbus_property(self):
1277
return datetime_to_dbus(self.last_approval_request)
1279
# Timeout - property
1280
@dbus_service_property(_interface, signature="t",
1282
def Timeout_dbus_property(self, value=None):
1283
if value is None: # get
1284
return dbus.UInt64(self.timeout_milliseconds())
1285
self.timeout = datetime.timedelta(0, 0, 0, value)
1286
if getattr(self, "disable_initiator_tag", None) is None:
1288
# Reschedule timeout
1289
gobject.source_remove(self.disable_initiator_tag)
1290
self.disable_initiator_tag = None
1292
time_to_die = _timedelta_to_milliseconds((self
1297
if time_to_die <= 0:
1298
# The timeout has passed
1301
self.expires = (datetime.datetime.utcnow()
1302
+ datetime.timedelta(milliseconds =
1304
self.disable_initiator_tag = (gobject.timeout_add
1305
(time_to_die, self.disable))
1307
# ExtendedTimeout - property
1308
@dbus_service_property(_interface, signature="t",
1310
def ExtendedTimeout_dbus_property(self, value=None):
1311
if value is None: # get
1312
return dbus.UInt64(self.extended_timeout_milliseconds())
1313
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1315
# Interval - property
1316
@dbus_service_property(_interface, signature="t",
1318
def Interval_dbus_property(self, value=None):
1319
if value is None: # get
1320
return dbus.UInt64(self.interval_milliseconds())
1321
self.interval = datetime.timedelta(0, 0, 0, value)
1322
if getattr(self, "checker_initiator_tag", None) is None:
1324
# Reschedule checker run
1325
gobject.source_remove(self.checker_initiator_tag)
1326
self.checker_initiator_tag = (gobject.timeout_add
1327
(value, self.start_checker))
1328
self.start_checker() # Start one now, too
1330
# Checker - property
1331
@dbus_service_property(_interface, signature="s",
1333
def Checker_dbus_property(self, value=None):
1334
if value is None: # get
1335
return dbus.String(self.checker_command)
1336
self.checker_command = value
1338
# CheckerRunning - property
1339
@dbus_service_property(_interface, signature="b",
1341
def CheckerRunning_dbus_property(self, value=None):
1342
if value is None: # get
1343
return dbus.Boolean(self.checker is not None)
1345
self.start_checker()
1349
# ObjectPath - property
1350
@dbus_service_property(_interface, signature="o", access="read")
1351
def ObjectPath_dbus_property(self):
1352
return self.dbus_object_path # is already a dbus.ObjectPath
1355
@dbus_service_property(_interface, signature="ay",
1356
access="write", byte_arrays=True)
1357
def Secret_dbus_property(self, value):
1358
self.secret = str(value)
1363
class ProxyClient(object):
1364
def __init__(self, child_pipe, fpr, address):
1365
self._pipe = child_pipe
1366
self._pipe.send(('init', fpr, address))
1367
if not self._pipe.recv():
1370
def __getattribute__(self, name):
1371
if(name == '_pipe'):
1372
return super(ProxyClient, self).__getattribute__(name)
1373
self._pipe.send(('getattr', name))
1374
data = self._pipe.recv()
1375
if data[0] == 'data':
1377
if data[0] == 'function':
1378
def func(*args, **kwargs):
1379
self._pipe.send(('funcall', name, args, kwargs))
1380
return self._pipe.recv()[1]
1383
def __setattr__(self, name, value):
1384
if(name == '_pipe'):
1385
return super(ProxyClient, self).__setattr__(name, value)
1386
self._pipe.send(('setattr', name, value))
1389
class ClientDBusTransitional(ClientDBus):
1390
__metaclass__ = AlternateDBusNamesMetaclass
1393
class ClientHandler(socketserver.BaseRequestHandler, object):
1394
"""A class to handle client connections.
1396
Instantiated once for each connection to handle it.
379
def still_valid(self):
380
"""Has the timeout not yet passed for this client?"""
381
now = datetime.datetime.now()
382
if self.last_checked_ok is None:
383
return now < (self.created + self.timeout)
385
return now < (self.last_checked_ok + self.timeout)
388
def peer_certificate(session):
389
"Return the peer's OpenPGP certificate as a bytestring"
390
# If not an OpenPGP certificate...
391
if gnutls.library.functions.gnutls_certificate_type_get\
392
(session._c_object) \
393
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
394
# ...do the normal thing
395
return session.peer_certificate
396
list_size = ctypes.c_uint()
397
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
398
(session._c_object, ctypes.byref(list_size))
399
if list_size.value == 0:
402
return ctypes.string_at(cert.data, cert.size)
405
def fingerprint(openpgp):
406
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
407
# New GnuTLS "datum" with the OpenPGP public key
408
datum = gnutls.library.types.gnutls_datum_t\
409
(ctypes.cast(ctypes.c_char_p(openpgp),
410
ctypes.POINTER(ctypes.c_ubyte)),
411
ctypes.c_uint(len(openpgp)))
412
# New empty GnuTLS certificate
413
crt = gnutls.library.types.gnutls_openpgp_crt_t()
414
gnutls.library.functions.gnutls_openpgp_crt_init\
416
# Import the OpenPGP public key into the certificate
417
gnutls.library.functions.gnutls_openpgp_crt_import\
418
(crt, ctypes.byref(datum),
419
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
420
# Verify the self signature in the key
421
crtverify = ctypes.c_uint()
422
gnutls.library.functions.gnutls_openpgp_crt_verify_self\
423
(crt, 0, ctypes.byref(crtverify))
424
if crtverify.value != 0:
425
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
426
raise gnutls.errors.CertificateSecurityError("Verify failed")
427
# New buffer for the fingerprint
428
buf = ctypes.create_string_buffer(20)
429
buf_len = ctypes.c_size_t()
430
# Get the fingerprint from the certificate into the buffer
431
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
432
(crt, ctypes.byref(buf), ctypes.byref(buf_len))
433
# Deinit the certificate
434
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
435
# Convert the buffer to a Python bytestring
436
fpr = ctypes.string_at(buf, buf_len.value)
437
# Convert the bytestring to hexadecimal notation
438
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
442
class TCP_handler(SocketServer.BaseRequestHandler, object):
443
"""A TCP request handler class.
444
Instantiated by IPv6_TCPServer for each request to handle it.
1397
445
Note: This will run in its own forked process."""
1399
447
def handle(self):
1400
with contextlib.closing(self.server.child_pipe) as child_pipe:
1401
logger.info("TCP connection from: %s",
1402
unicode(self.client_address))
1403
logger.debug("Pipe FD: %d",
1404
self.server.child_pipe.fileno())
1406
session = (gnutls.connection
1407
.ClientSession(self.request,
1409
.X509Credentials()))
1411
# Note: gnutls.connection.X509Credentials is really a
1412
# generic GnuTLS certificate credentials object so long as
1413
# no X.509 keys are added to it. Therefore, we can use it
1414
# here despite using OpenPGP certificates.
1416
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1417
# "+AES-256-CBC", "+SHA1",
1418
# "+COMP-NULL", "+CTYPE-OPENPGP",
1420
# Use a fallback default, since this MUST be set.
1421
priority = self.server.gnutls_priority
1422
if priority is None:
1424
(gnutls.library.functions
1425
.gnutls_priority_set_direct(session._c_object,
1428
# Start communication using the Mandos protocol
1429
# Get protocol number
1430
line = self.request.makefile().readline()
1431
logger.debug("Protocol version: %r", line)
1433
if int(line.strip().split()[0]) > 1:
1435
except (ValueError, IndexError, RuntimeError) as error:
1436
logger.error("Unknown protocol version: %s", error)
1439
# Start GnuTLS connection
1442
except gnutls.errors.GNUTLSError as error:
1443
logger.warning("Handshake failed: %s", error)
1444
# Do not run session.bye() here: the session is not
1445
# established. Just abandon the request.
1447
logger.debug("Handshake succeeded")
1449
approval_required = False
1452
fpr = self.fingerprint(self.peer_certificate
1455
gnutls.errors.GNUTLSError) as error:
1456
logger.warning("Bad certificate: %s", error)
1458
logger.debug("Fingerprint: %s", fpr)
1459
if self.server.use_dbus:
1461
client.NewRequest(str(self.client_address))
1464
client = ProxyClient(child_pipe, fpr,
1465
self.client_address)
1469
if client.approval_delay:
1470
delay = client.approval_delay
1471
client.approvals_pending += 1
1472
approval_required = True
1475
if not client.enabled:
1476
logger.info("Client %s is disabled",
1478
if self.server.use_dbus:
1480
client.Rejected("Disabled")
1483
if client._approved or not client.approval_delay:
1484
#We are approved or approval is disabled
1486
elif client._approved is None:
1487
logger.info("Client %s needs approval",
1489
if self.server.use_dbus:
1491
client.NeedApproval(
1492
client.approval_delay_milliseconds(),
1493
client.approved_by_default)
1495
logger.warning("Client %s was not approved",
1497
if self.server.use_dbus:
1499
client.Rejected("Denied")
1502
#wait until timeout or approved
1503
time = datetime.datetime.now()
1504
client.changedstate.acquire()
1505
(client.changedstate.wait
1506
(float(client._timedelta_to_milliseconds(delay)
1508
client.changedstate.release()
1509
time2 = datetime.datetime.now()
1510
if (time2 - time) >= delay:
1511
if not client.approved_by_default:
1512
logger.warning("Client %s timed out while"
1513
" waiting for approval",
1515
if self.server.use_dbus:
1517
client.Rejected("Approval timed out")
1522
delay -= time2 - time
1525
while sent_size < len(client.secret):
1527
sent = session.send(client.secret[sent_size:])
1528
except gnutls.errors.GNUTLSError as error:
1529
logger.warning("gnutls send failed")
1531
logger.debug("Sent: %d, remaining: %d",
1532
sent, len(client.secret)
1533
- (sent_size + sent))
1536
logger.info("Sending secret to %s", client.name)
1537
# bump the timeout using extended_timeout
1538
client.checked_ok(client.extended_timeout)
1539
if self.server.use_dbus:
1544
if approval_required:
1545
client.approvals_pending -= 1
1548
except gnutls.errors.GNUTLSError as error:
1549
logger.warning("GnuTLS bye failed")
1552
def peer_certificate(session):
1553
"Return the peer's OpenPGP certificate as a bytestring"
1554
# If not an OpenPGP certificate...
1555
if (gnutls.library.functions
1556
.gnutls_certificate_type_get(session._c_object)
1557
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1558
# ...do the normal thing
1559
return session.peer_certificate
1560
list_size = ctypes.c_uint(1)
1561
cert_list = (gnutls.library.functions
1562
.gnutls_certificate_get_peers
1563
(session._c_object, ctypes.byref(list_size)))
1564
if not bool(cert_list) and list_size.value != 0:
1565
raise gnutls.errors.GNUTLSError("error getting peer"
1567
if list_size.value == 0:
1570
return ctypes.string_at(cert.data, cert.size)
1573
def fingerprint(openpgp):
1574
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1575
# New GnuTLS "datum" with the OpenPGP public key
1576
datum = (gnutls.library.types
1577
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1580
ctypes.c_uint(len(openpgp))))
1581
# New empty GnuTLS certificate
1582
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1583
(gnutls.library.functions
1584
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1585
# Import the OpenPGP public key into the certificate
1586
(gnutls.library.functions
1587
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1588
gnutls.library.constants
1589
.GNUTLS_OPENPGP_FMT_RAW))
1590
# Verify the self signature in the key
1591
crtverify = ctypes.c_uint()
1592
(gnutls.library.functions
1593
.gnutls_openpgp_crt_verify_self(crt, 0,
1594
ctypes.byref(crtverify)))
1595
if crtverify.value != 0:
1596
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1597
raise (gnutls.errors.CertificateSecurityError
1599
# New buffer for the fingerprint
1600
buf = ctypes.create_string_buffer(20)
1601
buf_len = ctypes.c_size_t()
1602
# Get the fingerprint from the certificate into the buffer
1603
(gnutls.library.functions
1604
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1605
ctypes.byref(buf_len)))
1606
# Deinit the certificate
1607
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1608
# Convert the buffer to a Python bytestring
1609
fpr = ctypes.string_at(buf, buf_len.value)
1610
# Convert the bytestring to hexadecimal notation
1611
hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1615
class MultiprocessingMixIn(object):
1616
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1617
def sub_process_main(self, request, address):
1619
self.finish_request(request, address)
1621
self.handle_error(request, address)
1622
self.close_request(request)
1624
def process_request(self, request, address):
1625
"""Start a new process to process the request."""
1626
proc = multiprocessing.Process(target = self.sub_process_main,
1633
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1634
""" adds a pipe to the MixIn """
1635
def process_request(self, request, client_address):
1636
"""Overrides and wraps the original process_request().
1638
This function creates a new pipe in self.pipe
1640
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1642
proc = MultiprocessingMixIn.process_request(self, request,
1644
self.child_pipe.close()
1645
self.add_pipe(parent_pipe, proc)
1647
def add_pipe(self, parent_pipe, proc):
1648
"""Dummy function; override as necessary"""
1649
raise NotImplementedError
1652
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1653
socketserver.TCPServer, object):
1654
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
448
logger.info(u"TCP connection from: %s",
449
unicode(self.client_address))
450
session = gnutls.connection.ClientSession\
451
(self.request, gnutls.connection.X509Credentials())
453
line = self.request.makefile().readline()
454
logger.debug(u"Protocol version: %r", line)
456
if int(line.strip().split()[0]) > 1:
458
except (ValueError, IndexError, RuntimeError), error:
459
logger.error(u"Unknown protocol version: %s", error)
462
# Note: gnutls.connection.X509Credentials is really a generic
463
# GnuTLS certificate credentials object so long as no X.509
464
# keys are added to it. Therefore, we can use it here despite
465
# using OpenPGP certificates.
467
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
468
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
470
priority = "NORMAL" # Fallback default, since this
472
if self.server.settings["priority"]:
473
priority = self.server.settings["priority"]
474
gnutls.library.functions.gnutls_priority_set_direct\
475
(session._c_object, priority, None)
479
except gnutls.errors.GNUTLSError, error:
480
logger.warning(u"Handshake failed: %s", error)
481
# Do not run session.bye() here: the session is not
482
# established. Just abandon the request.
485
fpr = fingerprint(peer_certificate(session))
486
except (TypeError, gnutls.errors.GNUTLSError), error:
487
logger.warning(u"Bad certificate: %s", error)
490
logger.debug(u"Fingerprint: %s", fpr)
492
for c in self.server.clients:
493
if c.fingerprint == fpr:
497
logger.warning(u"Client not found for fingerprint: %s",
501
# Have to check if client.still_valid(), since it is possible
502
# that the client timed out while establishing the GnuTLS
504
if not client.still_valid():
505
logger.warning(u"Client %(name)s is invalid",
510
while sent_size < len(client.secret):
511
sent = session.send(client.secret[sent_size:])
512
logger.debug(u"Sent: %d, remaining: %d",
513
sent, len(client.secret)
514
- (sent_size + sent))
519
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
520
"""IPv6 TCP server. Accepts 'None' as address and/or port.
522
settings: Server settings
523
clients: Set() of Client objects
1657
524
enabled: Boolean; whether this server is activated yet
1658
interface: None or a network interface name (string)
1659
use_ipv6: Boolean; to use IPv6 or not
1661
def __init__(self, server_address, RequestHandlerClass,
1662
interface=None, use_ipv6=True):
1663
self.interface = interface
1665
self.address_family = socket.AF_INET6
1666
socketserver.TCPServer.__init__(self, server_address,
1667
RequestHandlerClass)
526
address_family = socket.AF_INET6
527
def __init__(self, *args, **kwargs):
528
if "settings" in kwargs:
529
self.settings = kwargs["settings"]
530
del kwargs["settings"]
531
if "clients" in kwargs:
532
self.clients = kwargs["clients"]
533
del kwargs["clients"]
535
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1668
536
def server_bind(self):
1669
537
"""This overrides the normal server_bind() function
1670
538
to bind to an interface if one was specified, and also NOT to
1671
539
bind to an address or port if they were not specified."""
1672
if self.interface is not None:
1673
if SO_BINDTODEVICE is None:
1674
logger.error("SO_BINDTODEVICE does not exist;"
1675
" cannot bind to interface %s",
1679
self.socket.setsockopt(socket.SOL_SOCKET,
1683
except socket.error as error:
1684
if error[0] == errno.EPERM:
1685
logger.error("No permission to"
1686
" bind to interface %s",
1688
elif error[0] == errno.ENOPROTOOPT:
1689
logger.error("SO_BINDTODEVICE not available;"
1690
" cannot bind to interface %s",
540
if self.settings["interface"]:
541
# 25 is from /usr/include/asm-i486/socket.h
542
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
544
self.socket.setsockopt(socket.SOL_SOCKET,
546
self.settings["interface"])
547
except socket.error, error:
548
if error[0] == errno.EPERM:
549
logger.error(u"No permission to"
550
u" bind to interface %s",
551
self.settings["interface"])
1694
554
# Only bind(2) the socket if we really need to.
1695
555
if self.server_address[0] or self.server_address[1]:
1696
556
if not self.server_address[0]:
1697
if self.address_family == socket.AF_INET6:
1698
any_address = "::" # in6addr_any
1700
any_address = socket.INADDR_ANY
1701
self.server_address = (any_address,
558
self.server_address = (in6addr_any,
1702
559
self.server_address[1])
1703
560
elif not self.server_address[1]:
1704
561
self.server_address = (self.server_address[0],
1706
# if self.interface:
563
# if self.settings["interface"]:
1707
564
# self.server_address = (self.server_address[0],
1710
567
# if_nametoindex
1712
return socketserver.TCPServer.server_bind(self)
1715
class MandosServer(IPv6_TCPServer):
1719
clients: set of Client objects
1720
gnutls_priority GnuTLS priority string
1721
use_dbus: Boolean; to emit D-Bus signals or not
1723
Assumes a gobject.MainLoop event loop.
1725
def __init__(self, server_address, RequestHandlerClass,
1726
interface=None, use_ipv6=True, clients=None,
1727
gnutls_priority=None, use_dbus=True):
1728
self.enabled = False
1729
self.clients = clients
1730
if self.clients is None:
1732
self.use_dbus = use_dbus
1733
self.gnutls_priority = gnutls_priority
1734
IPv6_TCPServer.__init__(self, server_address,
1735
RequestHandlerClass,
1736
interface = interface,
1737
use_ipv6 = use_ipv6)
570
return super(IPv6_TCPServer, self).server_bind()
1738
571
def server_activate(self):
1739
572
if self.enabled:
1740
return socketserver.TCPServer.server_activate(self)
573
return super(IPv6_TCPServer, self).server_activate()
1742
574
def enable(self):
1743
575
self.enabled = True
1745
def add_pipe(self, parent_pipe, proc):
1746
# Call "handle_ipc" for both data and EOF events
1747
gobject.io_add_watch(parent_pipe.fileno(),
1748
gobject.IO_IN | gobject.IO_HUP,
1749
functools.partial(self.handle_ipc,
1754
def handle_ipc(self, source, condition, parent_pipe=None,
1755
proc = None, client_object=None):
1757
gobject.IO_IN: "IN", # There is data to read.
1758
gobject.IO_OUT: "OUT", # Data can be written (without
1760
gobject.IO_PRI: "PRI", # There is urgent data to read.
1761
gobject.IO_ERR: "ERR", # Error condition.
1762
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1763
# broken, usually for pipes and
1766
conditions_string = ' | '.join(name
1768
condition_names.iteritems()
1769
if cond & condition)
1770
# error, or the other end of multiprocessing.Pipe has closed
1771
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1772
# Wait for other process to exit
1776
# Read a request from the child
1777
request = parent_pipe.recv()
1778
command = request[0]
1780
if command == 'init':
1782
address = request[2]
1784
for c in self.clients.itervalues():
1785
if c.fingerprint == fpr:
1789
logger.info("Client not found for fingerprint: %s, ad"
1790
"dress: %s", fpr, address)
1793
mandos_dbus_service.ClientNotFound(fpr,
1795
parent_pipe.send(False)
1798
gobject.io_add_watch(parent_pipe.fileno(),
1799
gobject.IO_IN | gobject.IO_HUP,
1800
functools.partial(self.handle_ipc,
1806
parent_pipe.send(True)
1807
# remove the old hook in favor of the new above hook on
1810
if command == 'funcall':
1811
funcname = request[1]
1815
parent_pipe.send(('data', getattr(client_object,
1819
if command == 'getattr':
1820
attrname = request[1]
1821
if callable(client_object.__getattribute__(attrname)):
1822
parent_pipe.send(('function',))
1824
parent_pipe.send(('data', client_object
1825
.__getattribute__(attrname)))
1827
if command == 'setattr':
1828
attrname = request[1]
1830
setattr(client_object, attrname, value)
1835
578
def string_to_delta(interval):
1836
579
"""Parse a string and return a datetime.timedelta
1838
581
>>> string_to_delta('7d')
1839
582
datetime.timedelta(7)
1840
583
>>> string_to_delta('60s')
1951
720
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1952
721
"servicename": "Mandos",
1958
724
# Parse config file for server-global settings
1959
server_config = configparser.SafeConfigParser(server_defaults)
725
server_config = ConfigParser.SafeConfigParser(server_defaults)
1960
726
del server_defaults
1961
server_config.read(os.path.join(options.configdir,
727
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1963
728
# Convert the SafeConfigParser object to a dict
1964
729
server_settings = server_config.defaults()
1965
# Use the appropriate methods on the non-string config options
1966
for option in ("debug", "use_dbus", "use_ipv6"):
1967
server_settings[option] = server_config.getboolean("DEFAULT",
1969
if server_settings["port"]:
1970
server_settings["port"] = server_config.getint("DEFAULT",
730
# Use getboolean on the boolean config option
731
server_settings["debug"] = server_config.getboolean\
1972
733
del server_config
1974
735
# Override the settings from the config file with command line
1975
736
# options, if set.
1976
737
for option in ("interface", "address", "port", "debug",
1977
"priority", "servicename", "configdir",
1978
"use_dbus", "use_ipv6", "debuglevel", "restore"):
738
"priority", "servicename", "configdir"):
1979
739
value = getattr(options, option)
1980
740
if value is not None:
1981
741
server_settings[option] = value
1983
# Force all strings to be unicode
1984
for option in server_settings.keys():
1985
if type(server_settings[option]) is str:
1986
server_settings[option] = unicode(server_settings[option])
1987
743
# Now we have our good server settings in "server_settings"
1989
##################################################################
1992
745
debug = server_settings["debug"]
1993
debuglevel = server_settings["debuglevel"]
1994
use_dbus = server_settings["use_dbus"]
1995
use_ipv6 = server_settings["use_ipv6"]
1998
initlogger(logging.DEBUG)
2003
level = getattr(logging, debuglevel.upper())
748
syslogger.setLevel(logging.WARNING)
749
console.setLevel(logging.WARNING)
2006
751
if server_settings["servicename"] != "Mandos":
2007
syslogger.setFormatter(logging.Formatter
2008
('Mandos (%s) [%%(process)d]:'
2009
' %%(levelname)s: %%(message)s'
752
syslogger.setFormatter(logging.Formatter\
753
('Mandos (%s): %%(levelname)s:'
2010
755
% server_settings["servicename"]))
2012
757
# Parse config file with clients
2013
client_defaults = { "timeout": "5m",
2014
"extended_timeout": "15m",
2016
"checker": "fping -q -- %%(host)s",
758
client_defaults = { "timeout": "1h",
760
"checker": "fping -q -- %(host)s",
2018
"approval_delay": "0s",
2019
"approval_duration": "1s",
2021
client_config = configparser.SafeConfigParser(client_defaults)
763
client_config = ConfigParser.SafeConfigParser(client_defaults)
2022
764
client_config.read(os.path.join(server_settings["configdir"],
2023
765
"clients.conf"))
2025
global mandos_dbus_service
2026
mandos_dbus_service = None
2028
tcp_server = MandosServer((server_settings["address"],
2029
server_settings["port"]),
2031
interface=(server_settings["interface"]
2035
server_settings["priority"],
2038
pidfilename = "/var/run/mandos.pid"
2040
pidfile = open(pidfilename, "w")
2042
logger.error("Could not open file %r", pidfilename)
2045
uid = pwd.getpwnam("_mandos").pw_uid
2046
gid = pwd.getpwnam("_mandos").pw_gid
2049
uid = pwd.getpwnam("mandos").pw_uid
2050
gid = pwd.getpwnam("mandos").pw_gid
2053
uid = pwd.getpwnam("nobody").pw_uid
2054
gid = pwd.getpwnam("nobody").pw_gid
768
tcp_server = IPv6_TCPServer((server_settings["address"],
769
server_settings["port"]),
771
settings=server_settings,
773
pidfilename = "/var/run/mandos.pid"
775
pidfile = open(pidfilename, "w")
776
except IOError, error:
777
logger.error("Could not open file %r", pidfilename)
782
uid = pwd.getpwnam("mandos").pw_uid
785
uid = pwd.getpwnam("nobody").pw_uid
789
gid = pwd.getpwnam("mandos").pw_gid
792
gid = pwd.getpwnam("nogroup").pw_gid
2061
except OSError as error:
798
except OSError, error:
2062
799
if error[0] != errno.EPERM:
2066
# Enable all possible GnuTLS debugging
2068
# "Use a log level over 10 to enable all debugging options."
2070
gnutls.library.functions.gnutls_global_set_log_level(11)
2072
@gnutls.library.types.gnutls_log_func
2073
def debug_gnutls(level, string):
2074
logger.debug("GnuTLS: %s", string[:-1])
2076
(gnutls.library.functions
2077
.gnutls_global_set_log_function(debug_gnutls))
2079
# Redirect stdin so all checkers get /dev/null
2080
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2081
os.dup2(null, sys.stdin.fileno())
2085
# No console logging
2086
logger.removeHandler(console)
2088
# Need to fork before connecting to D-Bus
2090
# Close all input and output, do double fork, etc.
803
service = AvahiService(name = server_settings["servicename"],
804
servicetype = "_mandos._tcp", )
805
if server_settings["interface"]:
806
service.interface = if_nametoindex\
807
(server_settings["interface"])
2093
809
global main_loop
2094
812
# From the Avahi example code
2095
813
DBusGMainLoop(set_as_default=True )
2096
814
main_loop = gobject.MainLoop()
2097
815
bus = dbus.SystemBus()
816
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
817
avahi.DBUS_PATH_SERVER),
818
avahi.DBUS_INTERFACE_SERVER)
2098
819
# End of Avahi example code
2101
bus_name = dbus.service.BusName("se.recompile.Mandos",
2102
bus, do_not_queue=True)
2103
old_bus_name = (dbus.service.BusName
2104
("se.bsnet.fukt.Mandos", bus,
2106
except dbus.exceptions.NameExistsException as e:
2107
logger.error(unicode(e) + ", disabling D-Bus")
2109
server_settings["use_dbus"] = False
2110
tcp_server.use_dbus = False
2111
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2112
service = AvahiServiceToSyslog(name =
2113
server_settings["servicename"],
2114
servicetype = "_mandos._tcp",
2115
protocol = protocol, bus = bus)
2116
if server_settings["interface"]:
2117
service.interface = (if_nametoindex
2118
(str(server_settings["interface"])))
2120
global multiprocessing_manager
2121
multiprocessing_manager = multiprocessing.Manager()
2123
client_class = Client
2125
client_class = functools.partial(ClientDBusTransitional,
2128
special_settings = {
2129
# Some settings need to be accessd by special methods;
2130
# booleans need .getboolean(), etc. Here is a list of them:
2131
"approved_by_default":
2133
client_config.getboolean(section, "approved_by_default"),
2135
# Construct a new dict of client settings of this form:
2136
# { client_name: {setting_name: value, ...}, ...}
2137
# with exceptions for any special settings as defined above
2138
client_settings = dict((clientname,
2140
(value if setting not in special_settings
2141
else special_settings[setting](clientname)))
2142
for setting, value in client_config.items(clientname)))
2143
for clientname in client_config.sections())
2145
old_client_settings = {}
2148
# Get client data and settings from last running state.
2149
if server_settings["restore"]:
2151
with open(stored_state_path, "rb") as stored_state:
2152
clients_data, old_client_settings = pickle.load(stored_state)
2153
os.remove(stored_state_path)
2154
except IOError as e:
2155
logger.warning("Could not load persistant state: {0}".format(e))
2156
if e.errno != errno.ENOENT:
2159
for client in clients_data:
2160
client_name = client["name"]
2162
# Decide which value to use after restoring saved state.
2163
# We have three different values: Old config file,
2164
# new config file, and saved state.
2165
# New config value takes precedence if it differs from old
2166
# config value, otherwise use saved state.
2167
for name, value in client_settings[client_name].items():
2169
# For each value in new config, check if it differs
2170
# from the old config value (Except for the "secret"
2172
if name != "secret" and value != old_client_settings[client_name][name]:
2173
setattr(client, name, value)
2177
# Clients who has passed its expire date, can still be enabled if its
2178
# last checker was sucessful. Clients who checkers failed before we
2179
# stored it state is asumed to had failed checker during downtime.
2180
if client["enabled"] and client["last_checked_ok"]:
2181
if ((datetime.datetime.utcnow() - client["last_checked_ok"])
2182
> client["interval"]):
2183
if client["last_checker_status"] != 0:
2184
client["enabled"] = False
2186
client["expires"] = datetime.datetime.utcnow() + client["timeout"]
2188
client["changedstate"] = (multiprocessing_manager
2189
.Condition(multiprocessing_manager
2192
new_client = ClientDBusTransitional.__new__(ClientDBusTransitional)
2193
tcp_server.clients[client_name] = new_client
2194
new_client.bus = bus
2195
for name, value in client.iteritems():
2196
setattr(new_client, name, value)
2197
client_object_name = unicode(client_name).translate(
2198
{ord("."): ord("_"),
2199
ord("-"): ord("_")})
2200
new_client.dbus_object_path = (dbus.ObjectPath
2201
("/clients/" + client_object_name))
2202
DBusObjectWithProperties.__init__(new_client,
2204
new_client.dbus_object_path)
2206
tcp_server.clients[client_name] = Client.__new__(Client)
2207
for name, value in client.iteritems():
2208
setattr(tcp_server.clients[client_name], name, value)
2210
tcp_server.clients[client_name].decrypt_secret(
2211
client_settings[client_name]["secret"])
2213
# Create/remove clients based on new changes made to config
2214
for clientname in set(old_client_settings) - set(client_settings):
2215
del tcp_server.clients[clientname]
2216
for clientname in set(client_settings) - set(old_client_settings):
2217
tcp_server.clients[clientname] = (client_class(name = clientname,
2223
if not tcp_server.clients:
2224
logger.warning("No clients defined")
821
def remove_from_clients(client):
822
clients.remove(client)
824
logger.critical(u"No clients left, exiting")
827
clients.update(Set(Client(name = section,
828
stop_hook = remove_from_clients,
830
= dict(client_config.items(section)))
831
for section in client_config.sections()))
833
logger.critical(u"No clients defined")
837
# Redirect stdin so all checkers get /dev/null
838
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
839
os.dup2(null, sys.stdin.fileno())
844
logger.removeHandler(console)
845
# Close all input and output, do double fork, etc.
850
pidfile.write(str(pid) + "\n")
854
logger.error(u"Could not write to file %r with PID %d",
857
# "pidfile" was never created
862
"Cleanup function; run on exit"
864
# From the Avahi example code
865
if not group is None:
868
# End of Avahi example code
871
client = clients.pop()
872
client.stop_hook = None
875
atexit.register(cleanup)
2230
pidfile.write(str(pid) + "\n".encode("utf-8"))
2233
logger.error("Could not write to file %r with PID %d",
2236
# "pidfile" was never created
2240
878
signal.signal(signal.SIGINT, signal.SIG_IGN)
2242
879
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2243
880
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2246
class MandosDBusService(dbus.service.Object):
2247
"""A D-Bus proxy object"""
2249
dbus.service.Object.__init__(self, bus, "/")
2250
_interface = "se.recompile.Mandos"
2252
@dbus.service.signal(_interface, signature="o")
2253
def ClientAdded(self, objpath):
2257
@dbus.service.signal(_interface, signature="ss")
2258
def ClientNotFound(self, fingerprint, address):
2262
@dbus.service.signal(_interface, signature="os")
2263
def ClientRemoved(self, objpath, name):
2267
@dbus.service.method(_interface, out_signature="ao")
2268
def GetAllClients(self):
2270
return dbus.Array(c.dbus_object_path
2272
tcp_server.clients.itervalues())
2274
@dbus.service.method(_interface,
2275
out_signature="a{oa{sv}}")
2276
def GetAllClientsWithProperties(self):
2278
return dbus.Dictionary(
2279
((c.dbus_object_path, c.GetAll(""))
2280
for c in tcp_server.clients.itervalues()),
2283
@dbus.service.method(_interface, in_signature="o")
2284
def RemoveClient(self, object_path):
2286
for c in tcp_server.clients.itervalues():
2287
if c.dbus_object_path == object_path:
2288
del tcp_server.clients[c.name]
2289
c.remove_from_connection()
2290
# Don't signal anything except ClientRemoved
2291
c.disable(quiet=True)
2293
self.ClientRemoved(object_path, c.name)
2295
raise KeyError(object_path)
2299
class MandosDBusServiceTransitional(MandosDBusService):
2300
__metaclass__ = AlternateDBusNamesMetaclass
2301
mandos_dbus_service = MandosDBusServiceTransitional()
2304
"Cleanup function; run on exit"
2307
multiprocessing.active_children()
2308
if not (tcp_server.clients or client_settings):
2311
# Store client before exiting. Secrets are encrypted with key based
2312
# on what config file has. If config file is removed/edited, old
2313
# secret will thus be unrecovable.
2315
for client in tcp_server.clients.itervalues():
2316
client.encrypt_secret(client_settings[client.name]["secret"])
2320
# A list of attributes that will not be stored when shuting down.
2321
exclude = set(("bus", "changedstate", "secret"))
2322
for name, typ in inspect.getmembers(dbus.service.Object):
2325
client_dict["encrypted_secret"] = client.encrypted_secret
2326
for attr in client.client_structure:
2327
if attr not in exclude:
2328
client_dict[attr] = getattr(client, attr)
2330
clients.append(client_dict)
2331
del client_settings[client.name]["secret"]
2334
with os.fdopen(os.open(stored_state_path, os.O_CREAT|os.O_WRONLY|os.O_TRUNC, 0600), "wb") as stored_state:
2335
pickle.dump((clients, client_settings), stored_state)
2336
except IOError as e:
2337
logger.warning("Could not save persistant state: {0}".format(e))
2338
if e.errno != errno.ENOENT:
2341
# Delete all clients, and settings from config
2342
while tcp_server.clients:
2343
name, client = tcp_server.clients.popitem()
2345
client.remove_from_connection()
2346
# Don't signal anything except ClientRemoved
2347
client.disable(quiet=True)
2350
mandos_dbus_service.ClientRemoved(client
2353
client_settings.clear()
2355
atexit.register(cleanup)
2357
for client in tcp_server.clients.itervalues():
2360
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2361
# Need to initiate checking of clients
2363
client.init_checker()
882
for client in clients:
2366
885
tcp_server.enable()
2367
886
tcp_server.server_activate()
2369
888
# Find out what port we got
2370
889
service.port = tcp_server.socket.getsockname()[1]
2372
logger.info("Now listening on address %r, port %d,"
2373
" flowinfo %d, scope_id %d"
2374
% tcp_server.socket.getsockname())
2376
logger.info("Now listening on address %r, port %d"
2377
% tcp_server.socket.getsockname())
890
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
891
u" scope_id %d" % tcp_server.socket.getsockname())
2379
893
#service.interface = tcp_server.socket.getsockname()[3]
2382
896
# From the Avahi example code
897
server.connect_to_signal("StateChanged", server_state_changed)
2385
except dbus.exceptions.DBusException as error:
2386
logger.critical("DBusException: %s", error)
899
server_state_changed(server.GetState())
900
except dbus.exceptions.DBusException, error:
901
logger.critical(u"DBusException: %s", error)
2389
903
# End of Avahi example code
2391
905
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2392
906
lambda *args, **kwargs:
2393
(tcp_server.handle_request
2394
(*args[2:], **kwargs) or True))
907
tcp_server.handle_request\
908
(*args[2:], **kwargs) or True)
2396
logger.debug("Starting main loop")
910
logger.debug(u"Starting main loop")
2398
except AvahiError as error:
2399
logger.critical("AvahiError: %s", error)
912
except AvahiError, error:
913
logger.critical(u"AvahiError: %s" + unicode(error))
2402
915
except KeyboardInterrupt:
2404
print("", file=sys.stderr)
2405
logger.debug("Server received KeyboardInterrupt")
2406
logger.debug("Server exiting")
2407
# Must run before the D-Bus bus name gets deregistered
2411
919
if __name__ == '__main__':