136
105
max_renames: integer; maximum number of renames
137
106
rename_count: integer; counter so we only rename after collisions
138
107
a sensible number of times
139
group: D-Bus Entry Group
141
bus: dbus.SystemBus()
143
109
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
144
servicetype = None, port = None, TXT = None,
145
domain = "", host = "", max_renames = 32768,
146
protocol = avahi.PROTO_UNSPEC, bus = None):
110
type = None, port = None, TXT = None, domain = "",
111
host = "", max_renames = 32768):
147
112
self.interface = interface
149
self.type = servicetype
151
self.TXT = TXT if TXT is not None else []
152
120
self.domain = domain
154
122
self.rename_count = 0
155
123
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
124
def rename(self):
162
125
"""Derived from the Avahi example code"""
163
126
if self.rename_count >= self.max_renames:
164
logger.critical("No suitable Zeroconf service name found"
165
" after %i retries, exiting.",
127
logger.critical(u"No suitable Zeroconf service name found"
128
u" after %i retries, exiting.",
167
130
raise AvahiServiceError("Too many renames")
168
self.name = unicode(self.server
169
.GetAlternativeServiceName(self.name))
170
logger.info("Changing Zeroconf service name to %r ...",
131
self.name = server.GetAlternativeServiceName(self.name)
132
logger.info(u"Changing Zeroconf service name to %r ...",
134
syslogger.setFormatter(logging.Formatter\
135
('Mandos (%s): %%(levelname)s:'
136
' %%(message)s' % self.name))
175
except dbus.exceptions.DBusException as error:
176
logger.critical("DBusException: %s", error)
179
139
self.rename_count += 1
180
140
def remove(self):
181
141
"""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:
142
if group is not None:
188
145
"""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))
148
group = dbus.Interface\
149
(bus.get_object(avahi.DBUS_NAME,
150
server.EntryGroupNew()),
151
avahi.DBUS_INTERFACE_ENTRY_GROUP)
152
group.connect_to_signal('StateChanged',
153
entry_group_state_changed)
154
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
155
service.name, service.type)
157
self.interface, # interface
158
avahi.PROTO_INET6, # protocol
159
dbus.UInt32(0), # flags
160
self.name, self.type,
161
self.domain, self.host,
162
dbus.UInt16(self.port),
163
avahi.string_array_to_txt_array(self.TXT))
166
# From the Avahi example code:
167
group = None # our entry group
168
# End of Avahi example code
285
171
class Client(object):
286
172
"""A representation of a client host served by this server.
289
_approved: bool(); 'None' if not yet approved/disapproved
290
approval_delay: datetime.timedelta(); Time to wait for approval
291
approval_duration: datetime.timedelta(); Duration of one approval
292
checker: subprocess.Popen(); a running checker process used
293
to see if the client lives.
294
'None' if no process is running.
295
checker_callback_tag: a gobject event source tag, or None
296
checker_command: string; External command which is run to check
297
if client lives. %() expansions are done at
174
name: string; from the config file, used in log messages
175
fingerprint: string (40 or 32 hexadecimal digits); used to
176
uniquely identify the client
177
secret: bytestring; sent verbatim (over TLS) to client
178
host: string; available for use by the checker command
179
created: datetime.datetime(); object creation, not client host
180
last_checked_ok: datetime.datetime() or None if not yet checked OK
181
timeout: datetime.timedelta(); How long from last_checked_ok
182
until this client is invalid
183
interval: datetime.timedelta(); How often to start a new checker
184
stop_hook: If set, called by stop() as stop_hook(self)
185
checker: subprocess.Popen(); a running checker process used
186
to see if the client lives.
187
'None' if no process is running.
188
checker_initiator_tag: a gobject event source tag, or None
189
stop_initiator_tag: - '' -
190
checker_callback_tag: - '' -
191
checker_command: string; External command which is run to check if
192
client lives. %() expansions are done at
298
193
runtime with vars(self) as dict, so that for
299
194
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
196
_timeout: Real variable for 'timeout'
197
_interval: Real variable for 'interval'
198
_timeout_milliseconds: Used when calling gobject.timeout_add()
199
_interval_milliseconds: - '' -
328
runtime_expansions = ("approval_delay", "approval_duration",
329
"created", "enabled", "fingerprint",
330
"host", "interval", "last_checked_ok",
331
"last_enabled", "name", "timeout")
333
def timeout_milliseconds(self):
334
"Return the 'timeout' attribute in milliseconds"
335
return _timedelta_to_milliseconds(self.timeout)
337
def extended_timeout_milliseconds(self):
338
"Return the 'extended_timeout' attribute in milliseconds"
339
return _timedelta_to_milliseconds(self.extended_timeout)
341
def interval_milliseconds(self):
342
"Return the 'interval' attribute in milliseconds"
343
return _timedelta_to_milliseconds(self.interval)
345
def approval_delay_milliseconds(self):
346
return _timedelta_to_milliseconds(self.approval_delay)
348
def __init__(self, name = None, config=None):
201
def _set_timeout(self, timeout):
202
"Setter function for 'timeout' attribute"
203
self._timeout = timeout
204
self._timeout_milliseconds = ((self.timeout.days
205
* 24 * 60 * 60 * 1000)
206
+ (self.timeout.seconds * 1000)
207
+ (self.timeout.microseconds
209
timeout = property(lambda self: self._timeout,
212
def _set_interval(self, interval):
213
"Setter function for 'interval' attribute"
214
self._interval = interval
215
self._interval_milliseconds = ((self.interval.days
216
* 24 * 60 * 60 * 1000)
217
+ (self.interval.seconds
219
+ (self.interval.microseconds
221
interval = property(lambda self: self._interval,
224
def __init__(self, name = None, stop_hook=None, config={}):
349
225
"""Note: the 'checker' key in 'config' sets the
350
226
'checker_command' attribute and *not* the 'checker'
355
logger.debug("Creating client %r", self.name)
229
logger.debug(u"Creating client %r", self.name)
356
230
# Uppercase and remove spaces from fingerprint for later
357
231
# comparison purposes with return value from the fingerprint()
359
self.fingerprint = (config["fingerprint"].upper()
361
logger.debug(" Fingerprint: %s", self.fingerprint)
233
self.fingerprint = config["fingerprint"].upper()\
235
logger.debug(u" Fingerprint: %s", self.fingerprint)
362
236
if "secret" in config:
363
self.secret = config["secret"].decode("base64")
237
self.secret = config["secret"].decode(u"base64")
364
238
elif "secfile" in config:
365
with open(os.path.expanduser(os.path.expandvars
366
(config["secfile"])),
368
self.secret = secfile.read()
239
sf = open(config["secfile"])
240
self.secret = sf.read()
370
raise TypeError("No secret or secfile for client %s"
243
raise TypeError(u"No secret or secfile for client %s"
372
245
self.host = config.get("host", "")
373
self.created = datetime.datetime.utcnow()
375
self.last_approval_request = None
376
self.last_enabled = datetime.datetime.utcnow()
246
self.created = datetime.datetime.now()
377
247
self.last_checked_ok = None
378
self.last_checker_status = None
379
248
self.timeout = string_to_delta(config["timeout"])
380
self.extended_timeout = string_to_delta(config
381
["extended_timeout"])
382
249
self.interval = string_to_delta(config["interval"])
250
self.stop_hook = stop_hook
383
251
self.checker = None
384
252
self.checker_initiator_tag = None
385
self.disable_initiator_tag = None
386
self.expires = datetime.datetime.utcnow() + self.timeout
253
self.stop_initiator_tag = None
387
254
self.checker_callback_tag = None
388
self.checker_command = config["checker"]
389
self.current_checker_command = None
390
self._approved = None
391
self.approved_by_default = config.get("approved_by_default",
393
self.approvals_pending = 0
394
self.approval_delay = string_to_delta(
395
config["approval_delay"])
396
self.approval_duration = string_to_delta(
397
config["approval_duration"])
398
self.changedstate = (multiprocessing_manager
399
.Condition(multiprocessing_manager
401
self.client_structure = [attr for attr
402
in self.__dict__.iterkeys()
403
if not attr.startswith("_")]
404
self.client_structure.append("client_structure")
407
for name, t in inspect.getmembers(type(self),
411
if not name.startswith("_"):
412
self.client_structure.append(name)
414
# Send notice to process children that client state has changed
415
def send_changedstate(self):
416
with self.changedstate:
417
self.changedstate.notify_all()
255
self.check_command = config["checker"]
420
257
"""Start this client's checker and timeout hooks"""
421
if getattr(self, "enabled", False):
424
self.send_changedstate()
425
self.expires = datetime.datetime.utcnow() + self.timeout
427
self.last_enabled = datetime.datetime.utcnow()
430
def disable(self, quiet=True):
431
"""Disable this client."""
432
if not getattr(self, "enabled", False):
258
# Schedule a new checker to be started an 'interval' from now,
259
# and every interval from then on.
260
self.checker_initiator_tag = gobject.timeout_add\
261
(self._interval_milliseconds,
263
# Also start a new checker *right now*.
265
# Schedule a stop() when 'timeout' has passed
266
self.stop_initiator_tag = gobject.timeout_add\
267
(self._timeout_milliseconds,
271
The possibility that a client might be restarted is left open,
272
but not currently used."""
273
# If this client doesn't have a secret, it is already stopped.
274
if hasattr(self, "secret") and self.secret:
275
logger.info(u"Stopping client %s", self.name)
435
self.send_changedstate()
437
logger.info("Disabling client %s", self.name)
438
if getattr(self, "disable_initiator_tag", False):
439
gobject.source_remove(self.disable_initiator_tag)
440
self.disable_initiator_tag = None
279
if getattr(self, "stop_initiator_tag", False):
280
gobject.source_remove(self.stop_initiator_tag)
281
self.stop_initiator_tag = None
442
282
if getattr(self, "checker_initiator_tag", False):
443
283
gobject.source_remove(self.checker_initiator_tag)
444
284
self.checker_initiator_tag = None
445
285
self.stop_checker()
447
288
# Do not run this again if called by a gobject.timeout_add
450
290
def __del__(self):
453
def init_checker(self):
454
# Schedule a new checker to be started an 'interval' from now,
455
# and every interval from then on.
456
self.checker_initiator_tag = (gobject.timeout_add
457
(self.interval_milliseconds(),
459
# Schedule a disable() when 'timeout' has passed
460
self.disable_initiator_tag = (gobject.timeout_add
461
(self.timeout_milliseconds(),
463
# Also start a new checker *right now*.
467
def checker_callback(self, pid, condition, command):
291
self.stop_hook = None
293
def checker_callback(self, pid, condition):
468
294
"""The checker has completed, so take appropriate actions."""
295
now = datetime.datetime.now()
469
296
self.checker_callback_tag = None
470
297
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?",
298
if os.WIFEXITED(condition) \
299
and (os.WEXITSTATUS(condition) == 0):
300
logger.info(u"Checker for %(name)s succeeded",
302
self.last_checked_ok = now
303
gobject.source_remove(self.stop_initiator_tag)
304
self.stop_initiator_tag = gobject.timeout_add\
305
(self._timeout_milliseconds,
307
elif not os.WIFEXITED(condition):
308
logger.warning(u"Checker for %(name)s crashed?",
485
def checked_ok(self, timeout=None):
486
"""Bump up the timeout for this client.
488
This should only be called when the client has been seen,
492
timeout = self.timeout
493
self.last_checked_ok = datetime.datetime.utcnow()
494
if self.disable_initiator_tag is not None:
495
gobject.source_remove(self.disable_initiator_tag)
496
if getattr(self, "enabled", False):
497
self.disable_initiator_tag = (gobject.timeout_add
498
(_timedelta_to_milliseconds
499
(timeout), self.disable))
500
self.expires = datetime.datetime.utcnow() + timeout
502
def need_approval(self):
503
self.last_approval_request = datetime.datetime.utcnow()
311
logger.info(u"Checker for %(name)s failed",
505
313
def start_checker(self):
506
314
"""Start a new checker subprocess if one is not running.
508
315
If a checker already exists, leave it running and do
510
317
# The reason for not killing a running checker is that if we
584
362
self.checker_callback_tag = None
585
363
if getattr(self, "checker", None) is None:
587
logger.debug("Stopping checker for %(name)s", vars(self))
365
logger.debug(u"Stopping checker for %(name)s", vars(self))
589
367
os.kill(self.checker.pid, signal.SIGTERM)
591
369
#if self.checker.poll() is None:
592
370
# os.kill(self.checker.pid, signal.SIGKILL)
593
except OSError as error:
371
except OSError, error:
594
372
if error.errno != errno.ESRCH: # No such process
596
374
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.UInt64,
1018
_timedelta_to_milliseconds)
1019
approval_duration = notifychangeproperty(
1020
dbus.UInt64, "ApprovalDuration",
1021
type_func = _timedelta_to_milliseconds)
1022
host = notifychangeproperty(dbus.String, "Host")
1023
timeout = notifychangeproperty(dbus.UInt64, "Timeout",
1025
_timedelta_to_milliseconds)
1026
extended_timeout = notifychangeproperty(
1027
dbus.UInt64, "ExtendedTimeout",
1028
type_func = _timedelta_to_milliseconds)
1029
interval = notifychangeproperty(dbus.UInt64,
1032
_timedelta_to_milliseconds)
1033
checker_command = notifychangeproperty(dbus.String, "Checker")
1035
del notifychangeproperty
1037
def __del__(self, *args, **kwargs):
1039
self.remove_from_connection()
1042
if hasattr(DBusObjectWithProperties, "__del__"):
1043
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1044
Client.__del__(self, *args, **kwargs)
1046
def checker_callback(self, pid, condition, command,
1048
self.checker_callback_tag = None
1050
if os.WIFEXITED(condition):
1051
exitstatus = os.WEXITSTATUS(condition)
1053
self.CheckerCompleted(dbus.Int16(exitstatus),
1054
dbus.Int64(condition),
1055
dbus.String(command))
1058
self.CheckerCompleted(dbus.Int16(-1),
1059
dbus.Int64(condition),
1060
dbus.String(command))
1062
return Client.checker_callback(self, pid, condition, command,
1065
def start_checker(self, *args, **kwargs):
1066
old_checker = self.checker
1067
if self.checker is not None:
1068
old_checker_pid = self.checker.pid
1070
old_checker_pid = None
1071
r = Client.start_checker(self, *args, **kwargs)
1072
# Only if new checker process was started
1073
if (self.checker is not None
1074
and old_checker_pid != self.checker.pid):
1076
self.CheckerStarted(self.current_checker_command)
1079
def _reset_approved(self):
1080
self._approved = None
1083
def approve(self, value=True):
1084
self.send_changedstate()
1085
self._approved = value
1086
gobject.timeout_add(_timedelta_to_milliseconds
1087
(self.approval_duration),
1088
self._reset_approved)
1091
## D-Bus methods, signals & properties
1092
_interface = "se.recompile.Mandos.Client"
1096
# CheckerCompleted - signal
1097
@dbus.service.signal(_interface, signature="nxs")
1098
def CheckerCompleted(self, exitcode, waitstatus, command):
1102
# CheckerStarted - signal
1103
@dbus.service.signal(_interface, signature="s")
1104
def CheckerStarted(self, command):
1108
# PropertyChanged - signal
1109
@dbus.service.signal(_interface, signature="sv")
1110
def PropertyChanged(self, property, value):
1114
# GotSecret - signal
1115
@dbus.service.signal(_interface)
1116
def GotSecret(self):
1118
Is sent after a successful transfer of secret from the Mandos
1119
server to mandos-client
1124
@dbus.service.signal(_interface, signature="s")
1125
def Rejected(self, reason):
1129
# NeedApproval - signal
1130
@dbus.service.signal(_interface, signature="tb")
1131
def NeedApproval(self, timeout, default):
1133
return self.need_approval()
1138
@dbus.service.method(_interface, in_signature="b")
1139
def Approve(self, value):
1142
# CheckedOK - method
1143
@dbus.service.method(_interface)
1144
def CheckedOK(self):
1148
@dbus.service.method(_interface)
1153
# StartChecker - method
1154
@dbus.service.method(_interface)
1155
def StartChecker(self):
1157
self.start_checker()
1160
@dbus.service.method(_interface)
1165
# StopChecker - method
1166
@dbus.service.method(_interface)
1167
def StopChecker(self):
1172
# ApprovalPending - property
1173
@dbus_service_property(_interface, signature="b", access="read")
1174
def ApprovalPending_dbus_property(self):
1175
return dbus.Boolean(bool(self.approvals_pending))
1177
# ApprovedByDefault - property
1178
@dbus_service_property(_interface, signature="b",
1180
def ApprovedByDefault_dbus_property(self, value=None):
1181
if value is None: # get
1182
return dbus.Boolean(self.approved_by_default)
1183
self.approved_by_default = bool(value)
1185
# ApprovalDelay - property
1186
@dbus_service_property(_interface, signature="t",
1188
def ApprovalDelay_dbus_property(self, value=None):
1189
if value is None: # get
1190
return dbus.UInt64(self.approval_delay_milliseconds())
1191
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1193
# ApprovalDuration - property
1194
@dbus_service_property(_interface, signature="t",
1196
def ApprovalDuration_dbus_property(self, value=None):
1197
if value is None: # get
1198
return dbus.UInt64(_timedelta_to_milliseconds(
1199
self.approval_duration))
1200
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1203
@dbus_service_property(_interface, signature="s", access="read")
1204
def Name_dbus_property(self):
1205
return dbus.String(self.name)
1207
# Fingerprint - property
1208
@dbus_service_property(_interface, signature="s", access="read")
1209
def Fingerprint_dbus_property(self):
1210
return dbus.String(self.fingerprint)
1213
@dbus_service_property(_interface, signature="s",
1215
def Host_dbus_property(self, value=None):
1216
if value is None: # get
1217
return dbus.String(self.host)
1220
# Created - property
1221
@dbus_service_property(_interface, signature="s", access="read")
1222
def Created_dbus_property(self):
1223
return dbus.String(datetime_to_dbus(self.created))
1225
# LastEnabled - property
1226
@dbus_service_property(_interface, signature="s", access="read")
1227
def LastEnabled_dbus_property(self):
1228
return datetime_to_dbus(self.last_enabled)
1230
# Enabled - property
1231
@dbus_service_property(_interface, signature="b",
1233
def Enabled_dbus_property(self, value=None):
1234
if value is None: # get
1235
return dbus.Boolean(self.enabled)
1241
# LastCheckedOK - property
1242
@dbus_service_property(_interface, signature="s",
1244
def LastCheckedOK_dbus_property(self, value=None):
1245
if value is not None:
1248
return datetime_to_dbus(self.last_checked_ok)
1250
# Expires - property
1251
@dbus_service_property(_interface, signature="s", access="read")
1252
def Expires_dbus_property(self):
1253
return datetime_to_dbus(self.expires)
1255
# LastApprovalRequest - property
1256
@dbus_service_property(_interface, signature="s", access="read")
1257
def LastApprovalRequest_dbus_property(self):
1258
return datetime_to_dbus(self.last_approval_request)
1260
# Timeout - property
1261
@dbus_service_property(_interface, signature="t",
1263
def Timeout_dbus_property(self, value=None):
1264
if value is None: # get
1265
return dbus.UInt64(self.timeout_milliseconds())
1266
self.timeout = datetime.timedelta(0, 0, 0, value)
1267
if getattr(self, "disable_initiator_tag", None) is None:
1269
# Reschedule timeout
1270
gobject.source_remove(self.disable_initiator_tag)
1271
self.disable_initiator_tag = None
1273
time_to_die = _timedelta_to_milliseconds((self
1278
if time_to_die <= 0:
1279
# The timeout has passed
1282
self.expires = (datetime.datetime.utcnow()
1283
+ datetime.timedelta(milliseconds =
1285
self.disable_initiator_tag = (gobject.timeout_add
1286
(time_to_die, self.disable))
1288
# ExtendedTimeout - property
1289
@dbus_service_property(_interface, signature="t",
1291
def ExtendedTimeout_dbus_property(self, value=None):
1292
if value is None: # get
1293
return dbus.UInt64(self.extended_timeout_milliseconds())
1294
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1296
# Interval - property
1297
@dbus_service_property(_interface, signature="t",
1299
def Interval_dbus_property(self, value=None):
1300
if value is None: # get
1301
return dbus.UInt64(self.interval_milliseconds())
1302
self.interval = datetime.timedelta(0, 0, 0, value)
1303
if getattr(self, "checker_initiator_tag", None) is None:
1305
# Reschedule checker run
1306
gobject.source_remove(self.checker_initiator_tag)
1307
self.checker_initiator_tag = (gobject.timeout_add
1308
(value, self.start_checker))
1309
self.start_checker() # Start one now, too
1311
# Checker - property
1312
@dbus_service_property(_interface, signature="s",
1314
def Checker_dbus_property(self, value=None):
1315
if value is None: # get
1316
return dbus.String(self.checker_command)
1317
self.checker_command = value
1319
# CheckerRunning - property
1320
@dbus_service_property(_interface, signature="b",
1322
def CheckerRunning_dbus_property(self, value=None):
1323
if value is None: # get
1324
return dbus.Boolean(self.checker is not None)
1326
self.start_checker()
1330
# ObjectPath - property
1331
@dbus_service_property(_interface, signature="o", access="read")
1332
def ObjectPath_dbus_property(self):
1333
return self.dbus_object_path # is already a dbus.ObjectPath
1336
@dbus_service_property(_interface, signature="ay",
1337
access="write", byte_arrays=True)
1338
def Secret_dbus_property(self, value):
1339
self.secret = str(value)
1344
class ProxyClient(object):
1345
def __init__(self, child_pipe, fpr, address):
1346
self._pipe = child_pipe
1347
self._pipe.send(('init', fpr, address))
1348
if not self._pipe.recv():
1351
def __getattribute__(self, name):
1352
if(name == '_pipe'):
1353
return super(ProxyClient, self).__getattribute__(name)
1354
self._pipe.send(('getattr', name))
1355
data = self._pipe.recv()
1356
if data[0] == 'data':
1358
if data[0] == 'function':
1359
def func(*args, **kwargs):
1360
self._pipe.send(('funcall', name, args, kwargs))
1361
return self._pipe.recv()[1]
1364
def __setattr__(self, name, value):
1365
if(name == '_pipe'):
1366
return super(ProxyClient, self).__setattr__(name, value)
1367
self._pipe.send(('setattr', name, value))
1369
class ClientDBusTransitional(ClientDBus):
1370
__metaclass__ = AlternateDBusNamesMetaclass
1372
class ClientHandler(socketserver.BaseRequestHandler, object):
1373
"""A class to handle client connections.
1375
Instantiated once for each connection to handle it.
375
def still_valid(self):
376
"""Has the timeout not yet passed for this client?"""
377
now = datetime.datetime.now()
378
if self.last_checked_ok is None:
379
return now < (self.created + self.timeout)
381
return now < (self.last_checked_ok + self.timeout)
384
def peer_certificate(session):
385
"Return the peer's OpenPGP certificate as a bytestring"
386
# If not an OpenPGP certificate...
387
if gnutls.library.functions.gnutls_certificate_type_get\
388
(session._c_object) \
389
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
390
# ...do the normal thing
391
return session.peer_certificate
392
list_size = ctypes.c_uint()
393
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
394
(session._c_object, ctypes.byref(list_size))
395
if list_size.value == 0:
398
return ctypes.string_at(cert.data, cert.size)
401
def fingerprint(openpgp):
402
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
403
# New GnuTLS "datum" with the OpenPGP public key
404
datum = gnutls.library.types.gnutls_datum_t\
405
(ctypes.cast(ctypes.c_char_p(openpgp),
406
ctypes.POINTER(ctypes.c_ubyte)),
407
ctypes.c_uint(len(openpgp)))
408
# New empty GnuTLS certificate
409
crt = gnutls.library.types.gnutls_openpgp_crt_t()
410
gnutls.library.functions.gnutls_openpgp_crt_init\
412
# Import the OpenPGP public key into the certificate
413
gnutls.library.functions.gnutls_openpgp_crt_import\
414
(crt, ctypes.byref(datum),
415
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
416
# Verify the self signature in the key
417
crtverify = ctypes.c_uint();
418
gnutls.library.functions.gnutls_openpgp_crt_verify_self\
419
(crt, 0, ctypes.byref(crtverify))
420
if crtverify.value != 0:
421
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
422
raise gnutls.errors.CertificateSecurityError("Verify failed")
423
# New buffer for the fingerprint
424
buffer = ctypes.create_string_buffer(20)
425
buffer_length = ctypes.c_size_t()
426
# Get the fingerprint from the certificate into the buffer
427
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
428
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
429
# Deinit the certificate
430
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
431
# Convert the buffer to a Python bytestring
432
fpr = ctypes.string_at(buffer, buffer_length.value)
433
# Convert the bytestring to hexadecimal notation
434
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
438
class tcp_handler(SocketServer.BaseRequestHandler, object):
439
"""A TCP request handler class.
440
Instantiated by IPv6_TCPServer for each request to handle it.
1376
441
Note: This will run in its own forked process."""
1378
443
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
444
logger.info(u"TCP connection from: %s",
445
unicode(self.client_address))
446
session = gnutls.connection.ClientSession\
447
(self.request, gnutls.connection.X509Credentials())
449
line = self.request.makefile().readline()
450
logger.debug(u"Protocol version: %r", line)
452
if int(line.strip().split()[0]) > 1:
454
except (ValueError, IndexError, RuntimeError), error:
455
logger.error(u"Unknown protocol version: %s", error)
458
# Note: gnutls.connection.X509Credentials is really a generic
459
# GnuTLS certificate credentials object so long as no X.509
460
# keys are added to it. Therefore, we can use it here despite
461
# using OpenPGP certificates.
463
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
464
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
466
priority = "NORMAL" # Fallback default, since this
468
if self.server.settings["priority"]:
469
priority = self.server.settings["priority"]
470
gnutls.library.functions.gnutls_priority_set_direct\
471
(session._c_object, priority, None);
475
except gnutls.errors.GNUTLSError, error:
476
logger.warning(u"Handshake failed: %s", error)
477
# Do not run session.bye() here: the session is not
478
# established. Just abandon the request.
481
fpr = fingerprint(peer_certificate(session))
482
except (TypeError, gnutls.errors.GNUTLSError), error:
483
logger.warning(u"Bad certificate: %s", error)
486
logger.debug(u"Fingerprint: %s", fpr)
488
for c in self.server.clients:
489
if c.fingerprint == fpr:
493
logger.warning(u"Client not found for fingerprint: %s",
497
# Have to check if client.still_valid(), since it is possible
498
# that the client timed out while establishing the GnuTLS
500
if not client.still_valid():
501
logger.warning(u"Client %(name)s is invalid",
506
while sent_size < len(client.secret):
507
sent = session.send(client.secret[sent_size:])
508
logger.debug(u"Sent: %d, remaining: %d",
509
sent, len(client.secret)
510
- (sent_size + sent))
515
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
516
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1633
enabled: Boolean; whether this server is activated yet
1634
interface: None or a network interface name (string)
1635
use_ipv6: Boolean; to use IPv6 or not
518
settings: Server settings
519
clients: Set() of Client objects
1637
def __init__(self, server_address, RequestHandlerClass,
1638
interface=None, use_ipv6=True):
1639
self.interface = interface
1641
self.address_family = socket.AF_INET6
1642
socketserver.TCPServer.__init__(self, server_address,
1643
RequestHandlerClass)
521
address_family = socket.AF_INET6
522
def __init__(self, *args, **kwargs):
523
if "settings" in kwargs:
524
self.settings = kwargs["settings"]
525
del kwargs["settings"]
526
if "clients" in kwargs:
527
self.clients = kwargs["clients"]
528
del kwargs["clients"]
529
return super(type(self), self).__init__(*args, **kwargs)
1644
530
def server_bind(self):
1645
531
"""This overrides the normal server_bind() function
1646
532
to bind to an interface if one was specified, and also NOT to
1647
533
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",
534
if self.settings["interface"]:
535
# 25 is from /usr/include/asm-i486/socket.h
536
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
538
self.socket.setsockopt(socket.SOL_SOCKET,
540
self.settings["interface"])
541
except socket.error, error:
542
if error[0] == errno.EPERM:
543
logger.error(u"No permission to"
544
u" bind to interface %s",
545
self.settings["interface"])
1670
548
# Only bind(2) the socket if we really need to.
1671
549
if self.server_address[0] or self.server_address[1]:
1672
550
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,
552
self.server_address = (in6addr_any,
1678
553
self.server_address[1])
1679
554
elif not self.server_address[1]:
1680
555
self.server_address = (self.server_address[0],
1682
# if self.interface:
557
# if self.settings["interface"]:
1683
558
# self.server_address = (self.server_address[0],
1686
561
# 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)
1714
def server_activate(self):
1716
return socketserver.TCPServer.server_activate(self)
1721
def add_pipe(self, parent_pipe, proc):
1722
# Call "handle_ipc" for both data and EOF events
1723
gobject.io_add_watch(parent_pipe.fileno(),
1724
gobject.IO_IN | gobject.IO_HUP,
1725
functools.partial(self.handle_ipc,
1730
def handle_ipc(self, source, condition, parent_pipe=None,
1731
proc = None, client_object=None):
1733
gobject.IO_IN: "IN", # There is data to read.
1734
gobject.IO_OUT: "OUT", # Data can be written (without
1736
gobject.IO_PRI: "PRI", # There is urgent data to read.
1737
gobject.IO_ERR: "ERR", # Error condition.
1738
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1739
# broken, usually for pipes and
1742
conditions_string = ' | '.join(name
1744
condition_names.iteritems()
1745
if cond & condition)
1746
# error, or the other end of multiprocessing.Pipe has closed
1747
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1748
# Wait for other process to exit
1752
# Read a request from the child
1753
request = parent_pipe.recv()
1754
command = request[0]
1756
if command == 'init':
1758
address = request[2]
1760
for c in self.clients.itervalues():
1761
if c.fingerprint == fpr:
1765
logger.info("Client not found for fingerprint: %s, ad"
1766
"dress: %s", fpr, address)
1769
mandos_dbus_service.ClientNotFound(fpr,
1771
parent_pipe.send(False)
1774
gobject.io_add_watch(parent_pipe.fileno(),
1775
gobject.IO_IN | gobject.IO_HUP,
1776
functools.partial(self.handle_ipc,
1782
parent_pipe.send(True)
1783
# remove the old hook in favor of the new above hook on
1786
if command == 'funcall':
1787
funcname = request[1]
1791
parent_pipe.send(('data', getattr(client_object,
1795
if command == 'getattr':
1796
attrname = request[1]
1797
if callable(client_object.__getattribute__(attrname)):
1798
parent_pipe.send(('function',))
1800
parent_pipe.send(('data', client_object
1801
.__getattribute__(attrname)))
1803
if command == 'setattr':
1804
attrname = request[1]
1806
setattr(client_object, attrname, value)
564
return super(type(self), self).server_bind()
1811
567
def string_to_delta(interval):
1812
568
"""Parse a string and return a datetime.timedelta
1814
570
>>> string_to_delta('7d')
1815
571
datetime.timedelta(7)
1816
572
>>> string_to_delta('60s')
2084
803
# No console logging
2085
804
logger.removeHandler(console)
2087
# Need to fork before connecting to D-Bus
2089
805
# 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")
808
pidfilename = "/var/run/mandos/mandos.pid"
811
pidfile = open(pidfilename, "w")
812
pidfile.write(str(pid) + "\n")
816
logger.error(u"Could not write %s file with PID %d",
817
pidfilename, os.getpid())
820
"Cleanup function; run on exit"
822
# From the Avahi example code
823
if not group is None:
826
# End of Avahi example code
829
client = clients.pop()
830
client.stop_hook = None
833
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
836
signal.signal(signal.SIGINT, signal.SIG_IGN)
2244
837
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2245
838
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2248
class MandosDBusService(dbus.service.Object):
2249
"""A D-Bus proxy object"""
2251
dbus.service.Object.__init__(self, bus, "/")
2252
_interface = "se.recompile.Mandos"
2254
@dbus.service.signal(_interface, signature="o")
2255
def ClientAdded(self, objpath):
2259
@dbus.service.signal(_interface, signature="ss")
2260
def ClientNotFound(self, fingerprint, address):
2264
@dbus.service.signal(_interface, signature="os")
2265
def ClientRemoved(self, objpath, name):
2269
@dbus.service.method(_interface, out_signature="ao")
2270
def GetAllClients(self):
2272
return dbus.Array(c.dbus_object_path
2274
tcp_server.clients.itervalues())
2276
@dbus.service.method(_interface,
2277
out_signature="a{oa{sv}}")
2278
def GetAllClientsWithProperties(self):
2280
return dbus.Dictionary(
2281
((c.dbus_object_path, c.GetAll(""))
2282
for c in tcp_server.clients.itervalues()),
2285
@dbus.service.method(_interface, in_signature="o")
2286
def RemoveClient(self, object_path):
2288
for c in tcp_server.clients.itervalues():
2289
if c.dbus_object_path == object_path:
2290
del tcp_server.clients[c.name]
2291
c.remove_from_connection()
2292
# Don't signal anything except ClientRemoved
2293
c.disable(quiet=True)
2295
self.ClientRemoved(object_path, c.name)
2297
raise KeyError(object_path)
2301
class MandosDBusServiceTransitional(MandosDBusService):
2302
__metaclass__ = AlternateDBusNamesMetaclass
2303
mandos_dbus_service = MandosDBusServiceTransitional()
2306
"Cleanup function; run on exit"
2309
multiprocessing.active_children()
2310
if not (tcp_server.clients or client_settings):
2313
# Store client before exiting. Secrets are encrypted with key
2314
# based on what config file has. If config file is
2315
# removed/edited, old secret will thus be unrecovable.
2317
for client in tcp_server.clients.itervalues():
2318
client.encrypt_secret(
2319
client_settings[client.name]["secret"])
2323
# A list of attributes that will not be stored when
2325
exclude = set(("bus", "changedstate", "secret"))
2326
for name, typ in inspect.getmembers(dbus.service.Object):
2329
client_dict["encrypted_secret"] = client.encrypted_secret
2330
for attr in client.client_structure:
2331
if attr not in exclude:
2332
client_dict[attr] = getattr(client, attr)
2334
clients.append(client_dict)
2335
del client_settings[client.name]["secret"]
2338
with os.fdopen(os.open(stored_state_path,
2339
os.O_CREAT|os.O_WRONLY|os.O_TRUNC,
2340
stat.S_IRUSR | stat.S_IWUSR),
2341
"wb") as stored_state:
2342
pickle.dump((clients, client_settings), stored_state)
2343
except IOError as e:
2344
logger.warning("Could not save persistant state: {0}"
2346
if e.errno != errno.ENOENT:
2349
# Delete all clients, and settings from config
2350
while tcp_server.clients:
2351
name, client = tcp_server.clients.popitem()
2353
client.remove_from_connection()
2354
# Don't signal anything except ClientRemoved
2355
client.disable(quiet=True)
2358
mandos_dbus_service.ClientRemoved(client
2361
client_settings.clear()
2363
atexit.register(cleanup)
2365
for client in tcp_server.clients.itervalues():
2368
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2369
# Need to initiate checking of clients
2371
client.init_checker()
2375
tcp_server.server_activate()
840
for client in clients:
843
tcp_server = IPv6_TCPServer((server_settings["address"],
844
server_settings["port"]),
846
settings=server_settings,
2377
848
# Find out what port we got
2378
849
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())
850
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
851
u" scope_id %d" % tcp_server.socket.getsockname())
2387
853
#service.interface = tcp_server.socket.getsockname()[3]
2390
856
# From the Avahi example code
857
server.connect_to_signal("StateChanged", server_state_changed)
2393
except dbus.exceptions.DBusException as error:
2394
logger.critical("DBusException: %s", error)
859
server_state_changed(server.GetState())
860
except dbus.exceptions.DBusException, error:
861
logger.critical(u"DBusException: %s", error)
2397
863
# End of Avahi example code
2399
865
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2400
866
lambda *args, **kwargs:
2401
(tcp_server.handle_request
2402
(*args[2:], **kwargs) or True))
867
tcp_server.handle_request\
868
(*args[2:], **kwargs) or True)
2404
logger.debug("Starting main loop")
870
logger.debug(u"Starting main loop")
871
main_loop_started = True
2406
except AvahiError as error:
2407
logger.critical("AvahiError: %s", error)
873
except AvahiError, error:
874
logger.critical(u"AvahiError: %s" + unicode(error))
2410
876
except KeyboardInterrupt:
2412
print("", file=sys.stderr)
2413
logger.debug("Server received KeyboardInterrupt")
2414
logger.debug("Server exiting")
2415
# Must run before the D-Bus bus name gets deregistered
2419
880
if __name__ == '__main__':