154
124
self.rename_count = 0
155
125
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
126
def rename(self):
162
127
"""Derived from the Avahi example code"""
163
128
if self.rename_count >= self.max_renames:
164
logger.critical("No suitable Zeroconf service name found"
165
" after %i retries, exiting.",
129
logger.critical(u"No suitable Zeroconf service name found"
130
u" after %i retries, exiting.",
166
131
self.rename_count)
167
raise AvahiServiceError("Too many renames")
168
self.name = unicode(self.server
169
.GetAlternativeServiceName(self.name))
170
logger.info("Changing Zeroconf service name to %r ...",
132
raise AvahiServiceError(u"Too many renames")
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))
175
except dbus.exceptions.DBusException as error:
176
logger.critical("DBusException: %s", error)
179
141
self.rename_count += 1
180
142
def remove(self):
181
143
"""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:
144
if group is not None:
188
147
"""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))
285
class Client(object):
150
group = dbus.Interface(bus.get_object
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
173
def _datetime_to_dbus(dt, variant_level=0):
174
"""Convert a UTC datetime.datetime() to a D-Bus type."""
175
return dbus.String(dt.isoformat(), variant_level=variant_level)
178
class Client(dbus.service.Object):
286
179
"""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
181
name: string; from the config file, used in log messages
182
fingerprint: string (40 or 32 hexadecimal digits); used to
183
uniquely identify the client
184
secret: bytestring; sent verbatim (over TLS) to client
185
host: string; available for use by the checker command
186
created: datetime.datetime(); (UTC) object creation
187
last_enabled: datetime.datetime(); (UTC)
189
last_checked_ok: datetime.datetime(); (UTC) or None
190
timeout: datetime.timedelta(); How long from last_checked_ok
191
until this client is invalid
192
interval: datetime.timedelta(); How often to start a new checker
193
disable_hook: If set, called by disable() as disable_hook(self)
292
194
checker: subprocess.Popen(); a running checker process used
293
195
to see if the client lives.
294
196
'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
197
checker_initiator_tag: a gobject event source tag, or None
198
disable_initiator_tag: - '' -
199
checker_callback_tag: - '' -
200
checker_command: string; External command which is run to check if
201
client lives. %() expansions are done at
298
202
runtime with vars(self) as dict, so that for
299
203
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
204
use_dbus: bool(); Whether to provide D-Bus interface and signals
205
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
328
runtime_expansions = ("approval_delay", "approval_duration",
329
"created", "enabled", "fingerprint",
330
"host", "interval", "last_checked_ok",
331
"last_enabled", "name", "timeout")
333
207
def timeout_milliseconds(self):
334
208
"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)
209
return ((self.timeout.days * 24 * 60 * 60 * 1000)
210
+ (self.timeout.seconds * 1000)
211
+ (self.timeout.microseconds // 1000))
341
213
def interval_milliseconds(self):
342
214
"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):
215
return ((self.interval.days * 24 * 60 * 60 * 1000)
216
+ (self.interval.seconds * 1000)
217
+ (self.interval.microseconds // 1000))
219
def __init__(self, name = None, disable_hook=None, config=None,
349
221
"""Note: the 'checker' key in 'config' sets the
350
222
'checker_command' attribute and *not* the 'checker'
353
225
if config is None:
355
logger.debug("Creating client %r", self.name)
227
logger.debug(u"Creating client %r", self.name)
228
self.use_dbus = use_dbus
230
self.dbus_object_path = (dbus.ObjectPath
232
+ self.name.replace(".", "_")))
233
dbus.service.Object.__init__(self, bus,
234
self.dbus_object_path)
356
235
# Uppercase and remove spaces from fingerprint for later
357
236
# comparison purposes with return value from the fingerprint()
359
238
self.fingerprint = (config["fingerprint"].upper()
361
logger.debug(" Fingerprint: %s", self.fingerprint)
240
logger.debug(u" Fingerprint: %s", self.fingerprint)
362
241
if "secret" in config:
363
self.secret = config["secret"].decode("base64")
242
self.secret = config["secret"].decode(u"base64")
364
243
elif "secfile" in config:
365
with open(os.path.expanduser(os.path.expandvars
366
(config["secfile"])),
244
with closing(open(os.path.expanduser
246
(config["secfile"])))) as secfile:
368
247
self.secret = secfile.read()
370
raise TypeError("No secret or secfile for client %s"
249
raise TypeError(u"No secret or secfile for client %s"
372
251
self.host = config.get("host", "")
373
252
self.created = datetime.datetime.utcnow()
375
self.last_approval_request = None
376
self.last_enabled = datetime.datetime.utcnow()
254
self.last_enabled = None
377
255
self.last_checked_ok = None
378
self.last_checker_status = None
379
256
self.timeout = string_to_delta(config["timeout"])
380
self.extended_timeout = string_to_delta(config
381
["extended_timeout"])
382
257
self.interval = string_to_delta(config["interval"])
258
self.disable_hook = disable_hook
383
259
self.checker = None
384
260
self.checker_initiator_tag = None
385
261
self.disable_initiator_tag = None
386
self.expires = datetime.datetime.utcnow() + self.timeout
387
262
self.checker_callback_tag = None
388
263
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()
419
265
def enable(self):
420
266
"""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
267
self.last_enabled = datetime.datetime.utcnow()
268
# Schedule a new checker to be started an 'interval' from now,
269
# and every interval from then on.
270
self.checker_initiator_tag = (gobject.timeout_add
271
(self.interval_milliseconds(),
273
# Also start a new checker *right now*.
275
# Schedule a disable() when 'timeout' has passed
276
self.disable_initiator_tag = (gobject.timeout_add
277
(self.timeout_milliseconds(),
426
279
self.enabled = True
427
self.last_enabled = datetime.datetime.utcnow()
282
self.PropertyChanged(dbus.String(u"enabled"),
283
dbus.Boolean(True, variant_level=1))
284
self.PropertyChanged(dbus.String(u"last_enabled"),
285
(_datetime_to_dbus(self.last_enabled,
430
def disable(self, quiet=True):
431
289
"""Disable this client."""
432
290
if not getattr(self, "enabled", False):
435
self.send_changedstate()
437
logger.info("Disabling client %s", self.name)
292
logger.info(u"Disabling client %s", self.name)
438
293
if getattr(self, "disable_initiator_tag", False):
439
294
gobject.source_remove(self.disable_initiator_tag)
440
295
self.disable_initiator_tag = None
442
296
if getattr(self, "checker_initiator_tag", False):
443
297
gobject.source_remove(self.checker_initiator_tag)
444
298
self.checker_initiator_tag = None
445
299
self.stop_checker()
300
if self.disable_hook:
301
self.disable_hook(self)
446
302
self.enabled = False
305
self.PropertyChanged(dbus.String(u"enabled"),
306
dbus.Boolean(False, variant_level=1))
447
307
# Do not run this again if called by a gobject.timeout_add
450
310
def __del__(self):
311
self.disable_hook = None
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
314
def checker_callback(self, pid, condition, command):
468
315
"""The checker has completed, so take appropriate actions."""
469
316
self.checker_callback_tag = None
470
317
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?",
320
self.PropertyChanged(dbus.String(u"checker_running"),
321
dbus.Boolean(False, variant_level=1))
322
if (os.WIFEXITED(condition)
323
and (os.WEXITSTATUS(condition) == 0)):
324
logger.info(u"Checker for %(name)s succeeded",
328
self.CheckerCompleted(dbus.Boolean(True),
329
dbus.UInt16(condition),
330
dbus.String(command))
332
elif not os.WIFEXITED(condition):
333
logger.warning(u"Checker for %(name)s crashed?",
337
self.CheckerCompleted(dbus.Boolean(False),
338
dbus.UInt16(condition),
339
dbus.String(command))
341
logger.info(u"Checker for %(name)s failed",
345
self.CheckerCompleted(dbus.Boolean(False),
346
dbus.UInt16(condition),
347
dbus.String(command))
485
def checked_ok(self, timeout=None):
349
def bump_timeout(self):
486
350
"""Bump up the timeout for this client.
488
351
This should only be called when the client has been seen,
492
timeout = self.timeout
493
354
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()
355
gobject.source_remove(self.disable_initiator_tag)
356
self.disable_initiator_tag = (gobject.timeout_add
357
(self.timeout_milliseconds(),
361
self.PropertyChanged(
362
dbus.String(u"last_checked_ok"),
363
(_datetime_to_dbus(self.last_checked_ok,
505
366
def start_checker(self):
506
367
"""Start a new checker subprocess if one is not running.
508
368
If a checker already exists, leave it running and do
510
370
# The reason for not killing a running checker is that if we
584
423
self.checker_callback_tag = None
585
424
if getattr(self, "checker", None) is None:
587
logger.debug("Stopping checker for %(name)s", vars(self))
426
logger.debug(u"Stopping checker for %(name)s", vars(self))
589
428
os.kill(self.checker.pid, signal.SIGTERM)
591
430
#if self.checker.poll() is None:
592
431
# os.kill(self.checker.pid, signal.SIGKILL)
593
except OSError as error:
432
except OSError, error:
594
433
if error.errno != errno.ESRCH: # No such process
596
435
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"
437
self.PropertyChanged(dbus.String(u"checker_running"),
438
dbus.Boolean(False, variant_level=1))
440
def still_valid(self):
441
"""Has the timeout not yet passed for this client?"""
442
if not getattr(self, "enabled", False):
444
now = datetime.datetime.utcnow()
445
if self.last_checked_ok is None:
446
return now < (self.created + self.timeout)
448
return now < (self.last_checked_ok + self.timeout)
450
## D-Bus methods & signals
451
_interface = u"org.mandos_system.Mandos.Client"
453
# BumpTimeout - method
454
BumpTimeout = dbus.service.method(_interface)(bump_timeout)
455
BumpTimeout.__name__ = "BumpTimeout"
1096
457
# CheckerCompleted - signal
1097
@dbus.service.signal(_interface, signature="nxs")
1098
def CheckerCompleted(self, exitcode, waitstatus, command):
458
@dbus.service.signal(_interface, signature="bqs")
459
def CheckerCompleted(self, success, condition, command):
1165
579
# 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)
580
StopChecker = dbus.service.method(_interface)(stop_checker)
581
StopChecker.__name__ = "StopChecker"
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.
586
def peer_certificate(session):
587
"Return the peer's OpenPGP certificate as a bytestring"
588
# If not an OpenPGP certificate...
589
if (gnutls.library.functions
590
.gnutls_certificate_type_get(session._c_object)
591
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
592
# ...do the normal thing
593
return session.peer_certificate
594
list_size = ctypes.c_uint()
595
cert_list = (gnutls.library.functions
596
.gnutls_certificate_get_peers
597
(session._c_object, ctypes.byref(list_size)))
598
if list_size.value == 0:
601
return ctypes.string_at(cert.data, cert.size)
604
def fingerprint(openpgp):
605
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
606
# New GnuTLS "datum" with the OpenPGP public key
607
datum = (gnutls.library.types
608
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
611
ctypes.c_uint(len(openpgp))))
612
# New empty GnuTLS certificate
613
crt = gnutls.library.types.gnutls_openpgp_crt_t()
614
(gnutls.library.functions
615
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
616
# Import the OpenPGP public key into the certificate
617
(gnutls.library.functions
618
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
619
gnutls.library.constants
620
.GNUTLS_OPENPGP_FMT_RAW))
621
# Verify the self signature in the key
622
crtverify = ctypes.c_uint()
623
(gnutls.library.functions
624
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
625
if crtverify.value != 0:
626
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
627
raise gnutls.errors.CertificateSecurityError("Verify failed")
628
# New buffer for the fingerprint
629
buf = ctypes.create_string_buffer(20)
630
buf_len = ctypes.c_size_t()
631
# Get the fingerprint from the certificate into the buffer
632
(gnutls.library.functions
633
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
634
ctypes.byref(buf_len)))
635
# Deinit the certificate
636
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
637
# Convert the buffer to a Python bytestring
638
fpr = ctypes.string_at(buf, buf_len.value)
639
# Convert the bytestring to hexadecimal notation
640
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
644
class TCP_handler(SocketServer.BaseRequestHandler, object):
645
"""A TCP request handler class.
646
Instantiated by IPv6_TCPServer for each request to handle it.
1376
647
Note: This will run in its own forked process."""
1378
649
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
650
logger.info(u"TCP connection from: %s",
651
unicode(self.client_address))
652
session = (gnutls.connection
653
.ClientSession(self.request,
657
line = self.request.makefile().readline()
658
logger.debug(u"Protocol version: %r", line)
660
if int(line.strip().split()[0]) > 1:
662
except (ValueError, IndexError, RuntimeError), error:
663
logger.error(u"Unknown protocol version: %s", error)
666
# Note: gnutls.connection.X509Credentials is really a generic
667
# GnuTLS certificate credentials object so long as no X.509
668
# keys are added to it. Therefore, we can use it here despite
669
# using OpenPGP certificates.
671
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
672
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
674
# Use a fallback default, since this MUST be set.
675
priority = self.server.settings.get("priority", "NORMAL")
676
(gnutls.library.functions
677
.gnutls_priority_set_direct(session._c_object,
682
except gnutls.errors.GNUTLSError, error:
683
logger.warning(u"Handshake failed: %s", error)
684
# Do not run session.bye() here: the session is not
685
# established. Just abandon the request.
688
fpr = fingerprint(peer_certificate(session))
689
except (TypeError, gnutls.errors.GNUTLSError), error:
690
logger.warning(u"Bad certificate: %s", error)
693
logger.debug(u"Fingerprint: %s", fpr)
694
for c in self.server.clients:
695
if c.fingerprint == fpr:
699
logger.warning(u"Client not found for fingerprint: %s",
703
# Have to check if client.still_valid(), since it is possible
704
# that the client timed out while establishing the GnuTLS
706
if not client.still_valid():
707
logger.warning(u"Client %(name)s is invalid",
711
## This won't work here, since we're in a fork.
712
# client.bump_timeout()
714
while sent_size < len(client.secret):
715
sent = session.send(client.secret[sent_size:])
716
logger.debug(u"Sent: %d, remaining: %d",
717
sent, len(client.secret)
718
- (sent_size + sent))
723
class IPv6_TCPServer(SocketServer.ForkingMixIn,
724
SocketServer.TCPServer, object):
725
"""IPv6 TCP server. Accepts 'None' as address and/or port.
727
settings: Server settings
728
clients: Set() of Client objects
1633
729
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
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)
731
address_family = socket.AF_INET6
732
def __init__(self, *args, **kwargs):
733
if "settings" in kwargs:
734
self.settings = kwargs["settings"]
735
del kwargs["settings"]
736
if "clients" in kwargs:
737
self.clients = kwargs["clients"]
738
del kwargs["clients"]
740
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1644
741
def server_bind(self):
1645
742
"""This overrides the normal server_bind() function
1646
743
to bind to an interface if one was specified, and also NOT to
1647
744
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",
745
if self.settings["interface"]:
746
# 25 is from /usr/include/asm-i486/socket.h
747
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
749
self.socket.setsockopt(socket.SOL_SOCKET,
751
self.settings["interface"])
752
except socket.error, error:
753
if error[0] == errno.EPERM:
754
logger.error(u"No permission to"
755
u" bind to interface %s",
756
self.settings["interface"])
1670
759
# Only bind(2) the socket if we really need to.
1671
760
if self.server_address[0] or self.server_address[1]:
1672
761
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,
763
self.server_address = (in6addr_any,
1678
764
self.server_address[1])
1679
765
elif not self.server_address[1]:
1680
766
self.server_address = (self.server_address[0],
1682
# if self.interface:
768
# if self.settings["interface"]:
1683
769
# self.server_address = (self.server_address[0],
1686
772
# if_nametoindex
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)
775
return super(IPv6_TCPServer, self).server_bind()
1714
776
def server_activate(self):
1715
777
if self.enabled:
1716
return socketserver.TCPServer.server_activate(self)
778
return super(IPv6_TCPServer, self).server_activate()
1718
779
def enable(self):
1719
780
self.enabled = True
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)
1811
783
def string_to_delta(interval):
1812
784
"""Parse a string and return a datetime.timedelta
1814
786
>>> string_to_delta('7d')
1815
787
datetime.timedelta(7)
1816
788
>>> string_to_delta('60s')
2084
1052
# No console logging
2085
1053
logger.removeHandler(console)
2087
# Need to fork before connecting to D-Bus
2089
1054
# Close all input and output, do double fork, etc.
2093
# From the Avahi example code
2094
DBusGMainLoop(set_as_default=True )
2095
main_loop = gobject.MainLoop()
2096
bus = dbus.SystemBus()
2097
# 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")
1059
pidfile.write(str(pid) + "\n")
1063
logger.error(u"Could not write to file %r with PID %d",
1066
# "pidfile" was never created
1071
"Cleanup function; run on exit"
1073
# From the Avahi example code
1074
if not group is None:
1077
# End of Avahi example code
1080
client = clients.pop()
1081
client.disable_hook = None
1084
atexit.register(cleanup)
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
2242
1087
signal.signal(signal.SIGINT, signal.SIG_IGN)
2244
1088
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2245
1089
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2248
class MandosDBusService(dbus.service.Object):
1092
class MandosServer(dbus.service.Object):
2249
1093
"""A D-Bus proxy object"""
2250
1094
def __init__(self):
2251
dbus.service.Object.__init__(self, bus, "/")
2252
_interface = "se.recompile.Mandos"
1095
dbus.service.Object.__init__(self, bus,
1097
_interface = u"org.mandos_system.Mandos"
1099
@dbus.service.signal(_interface, signature="oa{sv}")
1100
def ClientAdded(self, objpath, properties):
2254
1104
@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):
1105
def ClientRemoved(self, objpath):
2269
1109
@dbus.service.method(_interface, out_signature="ao")
2270
1110
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}}")
1111
return dbus.Array(c.dbus_object_path for c in clients)
1113
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
2278
1114
def GetAllClientsWithProperties(self):
2280
1115
return dbus.Dictionary(
2281
((c.dbus_object_path, c.GetAll(""))
2282
for c in tcp_server.clients.itervalues()),
1116
((c.dbus_object_path, c.GetAllProperties())
2283
1118
signature="oa{sv}")
2285
1120
@dbus.service.method(_interface, in_signature="o")
2286
1121
def RemoveClient(self, object_path):
2288
for c in tcp_server.clients.itervalues():
2289
1123
if c.dbus_object_path == object_path:
2290
del tcp_server.clients[c.name]
2291
c.remove_from_connection()
2292
1125
# Don't signal anything except ClientRemoved
2293
c.disable(quiet=True)
2294
1128
# Emit D-Bus signal
2295
self.ClientRemoved(object_path, c.name)
1129
self.ClientRemoved(object_path)
2297
raise KeyError(object_path)
1132
@dbus.service.method(_interface)
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():
1138
mandos_server = MandosServer()
1140
for client in clients:
2367
1142
# Emit D-Bus signal
2368
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2369
# Need to initiate checking of clients
2371
client.init_checker()
1143
mandos_server.ClientAdded(client.dbus_object_path,
1144
client.GetAllProperties())
2374
1147
tcp_server.enable()
2375
1148
tcp_server.server_activate()
2377
1150
# Find out what port we got
2378
1151
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())
1152
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
1153
u" scope_id %d" % tcp_server.socket.getsockname())
2387
1155
#service.interface = tcp_server.socket.getsockname()[3]
2390
1158
# From the Avahi example code
1159
server.connect_to_signal("StateChanged", server_state_changed)
2393
except dbus.exceptions.DBusException as error:
2394
logger.critical("DBusException: %s", error)
1161
server_state_changed(server.GetState())
1162
except dbus.exceptions.DBusException, error:
1163
logger.critical(u"DBusException: %s", error)
2397
1165
# End of Avahi example code