104
132
max_renames: integer; maximum number of renames
105
133
rename_count: integer; counter so we only rename after collisions
106
134
a sensible number of times
135
group: D-Bus Entry Group
137
bus: dbus.SystemBus()
108
139
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
109
type = None, port = None, TXT = None, domain = "",
110
host = "", max_renames = 32768):
140
servicetype = None, port = None, TXT = None,
141
domain = "", host = "", max_renames = 32768,
142
protocol = avahi.PROTO_UNSPEC, bus = None):
111
143
self.interface = interface
145
self.type = servicetype
147
self.TXT = TXT if TXT is not None else []
119
148
self.domain = domain
121
150
self.rename_count = 0
122
151
self.max_renames = max_renames
152
self.protocol = protocol
153
self.group = None # our entry group
156
self.entry_group_state_changed_match = None
123
157
def rename(self):
124
158
"""Derived from the Avahi example code"""
125
159
if self.rename_count >= self.max_renames:
126
logger.critical(u"No suitable service name found after %i"
127
u" retries, exiting.", rename_count)
160
logger.critical("No suitable Zeroconf service name found"
161
" after %i retries, exiting.",
128
163
raise AvahiServiceError("Too many renames")
129
self.name = server.GetAlternativeServiceName(self.name)
130
logger.info(u"Changing name to %r ...", str(self.name))
131
syslogger.setFormatter(logging.Formatter\
132
('Mandos (%s): %%(levelname)s:'
133
' %%(message)s' % self.name))
164
self.name = unicode(self.server
165
.GetAlternativeServiceName(self.name))
166
logger.info("Changing Zeroconf service name to %r ...",
171
except dbus.exceptions.DBusException as error:
172
logger.critical("DBusException: %s", error)
136
175
self.rename_count += 1
137
176
def remove(self):
138
177
"""Derived from the Avahi example code"""
139
if group is not None:
178
if self.entry_group_state_changed_match is not None:
179
self.entry_group_state_changed_match.remove()
180
self.entry_group_state_changed_match = None
181
if self.group is not None:
142
184
"""Derived from the Avahi example code"""
145
group = dbus.Interface\
146
(bus.get_object(avahi.DBUS_NAME,
147
server.EntryGroupNew()),
148
avahi.DBUS_INTERFACE_ENTRY_GROUP)
149
group.connect_to_signal('StateChanged',
150
entry_group_state_changed)
151
logger.debug(u"Adding service '%s' of type '%s' ...",
152
service.name, service.type)
154
self.interface, # interface
155
avahi.PROTO_INET6, # protocol
156
dbus.UInt32(0), # flags
157
self.name, self.type,
158
self.domain, self.host,
159
dbus.UInt16(self.port),
160
avahi.string_array_to_txt_array(self.TXT))
163
# From the Avahi example code:
164
group = None # our entry group
165
# End of Avahi example code
186
if self.group is None:
187
self.group = dbus.Interface(
188
self.bus.get_object(avahi.DBUS_NAME,
189
self.server.EntryGroupNew()),
190
avahi.DBUS_INTERFACE_ENTRY_GROUP)
191
self.entry_group_state_changed_match = (
192
self.group.connect_to_signal(
193
'StateChanged', self.entry_group_state_changed))
194
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
195
self.name, self.type)
196
self.group.AddService(
199
dbus.UInt32(0), # flags
200
self.name, self.type,
201
self.domain, self.host,
202
dbus.UInt16(self.port),
203
avahi.string_array_to_txt_array(self.TXT))
205
def entry_group_state_changed(self, state, error):
206
"""Derived from the Avahi example code"""
207
logger.debug("Avahi entry group state change: %i", state)
209
if state == avahi.ENTRY_GROUP_ESTABLISHED:
210
logger.debug("Zeroconf service established.")
211
elif state == avahi.ENTRY_GROUP_COLLISION:
212
logger.info("Zeroconf service name collision.")
214
elif state == avahi.ENTRY_GROUP_FAILURE:
215
logger.critical("Avahi: Error in group state changed %s",
217
raise AvahiGroupError("State changed: %s"
220
"""Derived from the Avahi example code"""
221
if self.group is not None:
224
except (dbus.exceptions.UnknownMethodException,
225
dbus.exceptions.DBusException) as e:
229
def server_state_changed(self, state, error=None):
230
"""Derived from the Avahi example code"""
231
logger.debug("Avahi server state change: %i", state)
232
bad_states = { avahi.SERVER_INVALID:
233
"Zeroconf server invalid",
234
avahi.SERVER_REGISTERING: None,
235
avahi.SERVER_COLLISION:
236
"Zeroconf server name collision",
237
avahi.SERVER_FAILURE:
238
"Zeroconf server failure" }
239
if state in bad_states:
240
if bad_states[state] is not None:
242
logger.error(bad_states[state])
244
logger.error(bad_states[state] + ": %r", error)
246
elif state == avahi.SERVER_RUNNING:
250
logger.debug("Unknown state: %r", state)
252
logger.debug("Unknown state: %r: %r", state, error)
254
"""Derived from the Avahi example code"""
255
if self.server is None:
256
self.server = dbus.Interface(
257
self.bus.get_object(avahi.DBUS_NAME,
258
avahi.DBUS_PATH_SERVER,
259
follow_name_owner_changes=True),
260
avahi.DBUS_INTERFACE_SERVER)
261
self.server.connect_to_signal("StateChanged",
262
self.server_state_changed)
263
self.server_state_changed(self.server.GetState())
265
class AvahiServiceToSyslog(AvahiService):
267
"""Add the new name to the syslog messages"""
268
ret = AvahiService.rename(self)
269
syslogger.setFormatter(logging.Formatter
270
('Mandos (%s) [%%(process)d]:'
271
' %%(levelname)s: %%(message)s'
275
def _timedelta_to_milliseconds(td):
276
"Convert a datetime.timedelta() to milliseconds"
277
return ((td.days * 24 * 60 * 60 * 1000)
278
+ (td.seconds * 1000)
279
+ (td.microseconds // 1000))
168
281
class Client(object):
169
282
"""A representation of a client host served by this server.
171
name: string; from the config file, used in log messages
285
_approved: bool(); 'None' if not yet approved/disapproved
286
approval_delay: datetime.timedelta(); Time to wait for approval
287
approval_duration: datetime.timedelta(); Duration of one approval
288
checker: subprocess.Popen(); a running checker process used
289
to see if the client lives.
290
'None' if no process is running.
291
checker_callback_tag: a gobject event source tag, or None
292
checker_command: string; External command which is run to check
293
if client lives. %() expansions are done at
294
runtime with vars(self) as dict, so that for
295
instance %(name)s can be used in the command.
296
checker_initiator_tag: a gobject event source tag, or None
297
created: datetime.datetime(); (UTC) object creation
298
current_checker_command: string; current running checker_command
299
disable_hook: If set, called by disable() as disable_hook(self)
300
disable_initiator_tag: a gobject event source tag, or None
172
302
fingerprint: string (40 or 32 hexadecimal digits); used to
173
303
uniquely identify the client
174
secret: bytestring; sent verbatim (over TLS) to client
175
host: string; available for use by the checker command
176
created: datetime.datetime(); object creation, not client host
177
last_checked_ok: datetime.datetime() or None if not yet checked OK
178
timeout: datetime.timedelta(); How long from last_checked_ok
179
until this client is invalid
180
interval: datetime.timedelta(); How often to start a new checker
181
stop_hook: If set, called by stop() as stop_hook(self)
182
checker: subprocess.Popen(); a running checker process used
183
to see if the client lives.
184
'None' if no process is running.
185
checker_initiator_tag: a gobject event source tag, or None
186
stop_initiator_tag: - '' -
187
checker_callback_tag: - '' -
188
checker_command: string; External command which is run to check if
189
client lives. %() expansions are done at
190
runtime with vars(self) as dict, so that for
191
instance %(name)s can be used in the command.
193
_timeout: Real variable for 'timeout'
194
_interval: Real variable for 'interval'
195
_timeout_milliseconds: Used when calling gobject.timeout_add()
196
_interval_milliseconds: - '' -
304
host: string; available for use by the checker command
305
interval: datetime.timedelta(); How often to start a new checker
306
last_approval_request: datetime.datetime(); (UTC) or None
307
last_checked_ok: datetime.datetime(); (UTC) or None
308
last_enabled: datetime.datetime(); (UTC)
309
name: string; from the config file, used in log messages and
311
secret: bytestring; sent verbatim (over TLS) to client
312
timeout: datetime.timedelta(); How long from last_checked_ok
313
until this client is disabled
314
extended_timeout: extra long timeout when password has been sent
315
runtime_expansions: Allowed attributes for runtime expansion.
316
expires: datetime.datetime(); time (UTC) when a client will be
198
def _set_timeout(self, timeout):
199
"Setter function for 'timeout' attribute"
200
self._timeout = timeout
201
self._timeout_milliseconds = ((self.timeout.days
202
* 24 * 60 * 60 * 1000)
203
+ (self.timeout.seconds * 1000)
204
+ (self.timeout.microseconds
206
timeout = property(lambda self: self._timeout,
209
def _set_interval(self, interval):
210
"Setter function for 'interval' attribute"
211
self._interval = interval
212
self._interval_milliseconds = ((self.interval.days
213
* 24 * 60 * 60 * 1000)
214
+ (self.interval.seconds
216
+ (self.interval.microseconds
218
interval = property(lambda self: self._interval,
221
def __init__(self, name = None, stop_hook=None, config={}):
320
runtime_expansions = ("approval_delay", "approval_duration",
321
"created", "enabled", "fingerprint",
322
"host", "interval", "last_checked_ok",
323
"last_enabled", "name", "timeout")
325
def timeout_milliseconds(self):
326
"Return the 'timeout' attribute in milliseconds"
327
return _timedelta_to_milliseconds(self.timeout)
329
def extended_timeout_milliseconds(self):
330
"Return the 'extended_timeout' attribute in milliseconds"
331
return _timedelta_to_milliseconds(self.extended_timeout)
333
def interval_milliseconds(self):
334
"Return the 'interval' attribute in milliseconds"
335
return _timedelta_to_milliseconds(self.interval)
337
def approval_delay_milliseconds(self):
338
return _timedelta_to_milliseconds(self.approval_delay)
340
def __init__(self, name = None, disable_hook=None, config=None):
222
341
"""Note: the 'checker' key in 'config' sets the
223
342
'checker_command' attribute and *not* the 'checker'
226
logger.debug(u"Creating client %r", self.name)
347
logger.debug("Creating client %r", self.name)
227
348
# Uppercase and remove spaces from fingerprint for later
228
349
# comparison purposes with return value from the fingerprint()
230
self.fingerprint = config["fingerprint"].upper()\
232
logger.debug(u" Fingerprint: %s", self.fingerprint)
351
self.fingerprint = (config["fingerprint"].upper()
353
logger.debug(" Fingerprint: %s", self.fingerprint)
233
354
if "secret" in config:
234
self.secret = config["secret"].decode(u"base64")
355
self.secret = config["secret"].decode("base64")
235
356
elif "secfile" in config:
236
sf = open(config["secfile"])
237
self.secret = sf.read()
357
with open(os.path.expanduser(os.path.expandvars
358
(config["secfile"])),
360
self.secret = secfile.read()
240
raise TypeError(u"No secret or secfile for client %s"
362
raise TypeError("No secret or secfile for client %s"
242
364
self.host = config.get("host", "")
243
self.created = datetime.datetime.now()
365
self.created = datetime.datetime.utcnow()
367
self.last_approval_request = None
368
self.last_enabled = None
244
369
self.last_checked_ok = None
245
370
self.timeout = string_to_delta(config["timeout"])
371
self.extended_timeout = string_to_delta(config
372
["extended_timeout"])
246
373
self.interval = string_to_delta(config["interval"])
247
self.stop_hook = stop_hook
374
self.disable_hook = disable_hook
248
375
self.checker = None
249
376
self.checker_initiator_tag = None
250
self.stop_initiator_tag = None
377
self.disable_initiator_tag = None
251
379
self.checker_callback_tag = None
252
self.check_command = config["checker"]
380
self.checker_command = config["checker"]
381
self.current_checker_command = None
382
self.last_connect = None
383
self._approved = None
384
self.approved_by_default = config.get("approved_by_default",
386
self.approvals_pending = 0
387
self.approval_delay = string_to_delta(
388
config["approval_delay"])
389
self.approval_duration = string_to_delta(
390
config["approval_duration"])
391
self.changedstate = (multiprocessing_manager
392
.Condition(multiprocessing_manager
395
def send_changedstate(self):
396
self.changedstate.acquire()
397
self.changedstate.notify_all()
398
self.changedstate.release()
254
401
"""Start this client's checker and timeout hooks"""
402
if getattr(self, "enabled", False):
405
self.send_changedstate()
255
406
# Schedule a new checker to be started an 'interval' from now,
256
407
# and every interval from then on.
257
self.checker_initiator_tag = gobject.timeout_add\
258
(self._interval_milliseconds,
408
self.checker_initiator_tag = (gobject.timeout_add
409
(self.interval_milliseconds(),
411
# Schedule a disable() when 'timeout' has passed
412
self.expires = datetime.datetime.utcnow() + self.timeout
413
self.disable_initiator_tag = (gobject.timeout_add
414
(self.timeout_milliseconds(),
417
self.last_enabled = datetime.datetime.utcnow()
260
418
# Also start a new checker *right now*.
261
419
self.start_checker()
262
# Schedule a stop() when 'timeout' has passed
263
self.stop_initiator_tag = gobject.timeout_add\
264
(self._timeout_milliseconds,
268
The possibility that a client might be restarted is left open,
269
but not currently used."""
270
# If this client doesn't have a secret, it is already stopped.
271
if hasattr(self, "secret") and self.secret:
272
logger.info(u"Stopping client %s", self.name)
421
def disable(self, quiet=True):
422
"""Disable this client."""
423
if not getattr(self, "enabled", False):
276
if getattr(self, "stop_initiator_tag", False):
277
gobject.source_remove(self.stop_initiator_tag)
278
self.stop_initiator_tag = None
426
self.send_changedstate()
428
logger.info("Disabling client %s", self.name)
429
if getattr(self, "disable_initiator_tag", False):
430
gobject.source_remove(self.disable_initiator_tag)
431
self.disable_initiator_tag = None
279
433
if getattr(self, "checker_initiator_tag", False):
280
434
gobject.source_remove(self.checker_initiator_tag)
281
435
self.checker_initiator_tag = None
282
436
self.stop_checker()
437
if self.disable_hook:
438
self.disable_hook(self)
285
440
# Do not run this again if called by a gobject.timeout_add
287
443
def __del__(self):
288
self.stop_hook = None
290
def checker_callback(self, pid, condition):
444
self.disable_hook = None
447
def checker_callback(self, pid, condition, command):
291
448
"""The checker has completed, so take appropriate actions."""
292
now = datetime.datetime.now()
293
449
self.checker_callback_tag = None
294
450
self.checker = None
295
if os.WIFEXITED(condition) \
296
and (os.WEXITSTATUS(condition) == 0):
297
logger.info(u"Checker for %(name)s succeeded",
299
self.last_checked_ok = now
300
gobject.source_remove(self.stop_initiator_tag)
301
self.stop_initiator_tag = gobject.timeout_add\
302
(self._timeout_milliseconds,
304
elif not os.WIFEXITED(condition):
305
logger.warning(u"Checker for %(name)s crashed?",
451
if os.WIFEXITED(condition):
452
exitstatus = os.WEXITSTATUS(condition)
454
logger.info("Checker for %(name)s succeeded",
458
logger.info("Checker for %(name)s failed",
461
logger.warning("Checker for %(name)s crashed?",
308
logger.info(u"Checker for %(name)s failed",
464
def checked_ok(self, timeout=None):
465
"""Bump up the timeout for this client.
467
This should only be called when the client has been seen,
471
timeout = self.timeout
472
self.last_checked_ok = datetime.datetime.utcnow()
473
if self.disable_initiator_tag is not None:
474
gobject.source_remove(self.disable_initiator_tag)
475
if getattr(self, "enabled", False):
476
self.disable_initiator_tag = (gobject.timeout_add
477
(_timedelta_to_milliseconds
478
(timeout), self.disable))
479
self.expires = datetime.datetime.utcnow() + timeout
481
def need_approval(self):
482
self.last_approval_request = datetime.datetime.utcnow()
310
484
def start_checker(self):
311
485
"""Start a new checker subprocess if one is not running.
312
487
If a checker already exists, leave it running and do
314
489
# The reason for not killing a running checker is that if we
355
563
self.checker_callback_tag = None
356
564
if getattr(self, "checker", None) is None:
358
logger.debug(u"Stopping checker for %(name)s", vars(self))
566
logger.debug("Stopping checker for %(name)s", vars(self))
360
568
os.kill(self.checker.pid, signal.SIGTERM)
362
570
#if self.checker.poll() is None:
363
571
# os.kill(self.checker.pid, signal.SIGKILL)
364
except OSError, error:
572
except OSError as error:
365
573
if error.errno != errno.ESRCH: # No such process
367
575
self.checker = None
368
def still_valid(self):
369
"""Has the timeout not yet passed for this client?"""
370
now = datetime.datetime.now()
371
if self.last_checked_ok is None:
372
return now < (self.created + self.timeout)
374
return now < (self.last_checked_ok + self.timeout)
377
def peer_certificate(session):
378
"Return the peer's OpenPGP certificate as a bytestring"
379
# If not an OpenPGP certificate...
380
if gnutls.library.functions.gnutls_certificate_type_get\
381
(session._c_object) \
382
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
383
# ...do the normal thing
384
return session.peer_certificate
385
list_size = ctypes.c_uint()
386
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
387
(session._c_object, ctypes.byref(list_size))
388
if list_size.value == 0:
391
return ctypes.string_at(cert.data, cert.size)
394
def fingerprint(openpgp):
395
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
396
# New GnuTLS "datum" with the OpenPGP public key
397
datum = gnutls.library.types.gnutls_datum_t\
398
(ctypes.cast(ctypes.c_char_p(openpgp),
399
ctypes.POINTER(ctypes.c_ubyte)),
400
ctypes.c_uint(len(openpgp)))
401
# New empty GnuTLS certificate
402
crt = gnutls.library.types.gnutls_openpgp_crt_t()
403
gnutls.library.functions.gnutls_openpgp_crt_init\
405
# Import the OpenPGP public key into the certificate
406
gnutls.library.functions.gnutls_openpgp_crt_import\
407
(crt, ctypes.byref(datum),
408
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
409
# New buffer for the fingerprint
410
buffer = ctypes.create_string_buffer(20)
411
buffer_length = ctypes.c_size_t()
412
# Get the fingerprint from the certificate into the buffer
413
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
414
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
415
# Deinit the certificate
416
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
417
# Convert the buffer to a Python bytestring
418
fpr = ctypes.string_at(buffer, buffer_length.value)
419
# Convert the bytestring to hexadecimal notation
420
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
424
class tcp_handler(SocketServer.BaseRequestHandler, object):
425
"""A TCP request handler class.
426
Instantiated by IPv6_TCPServer for each request to handle it.
578
def dbus_service_property(dbus_interface, signature="v",
579
access="readwrite", byte_arrays=False):
580
"""Decorators for marking methods of a DBusObjectWithProperties to
581
become properties on the D-Bus.
583
The decorated method will be called with no arguments by "Get"
584
and with one argument by "Set".
586
The parameters, where they are supported, are the same as
587
dbus.service.method, except there is only "signature", since the
588
type from Get() and the type sent to Set() is the same.
590
# Encoding deeply encoded byte arrays is not supported yet by the
591
# "Set" method, so we fail early here:
592
if byte_arrays and signature != "ay":
593
raise ValueError("Byte arrays not supported for non-'ay'"
594
" signature %r" % signature)
596
func._dbus_is_property = True
597
func._dbus_interface = dbus_interface
598
func._dbus_signature = signature
599
func._dbus_access = access
600
func._dbus_name = func.__name__
601
if func._dbus_name.endswith("_dbus_property"):
602
func._dbus_name = func._dbus_name[:-14]
603
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
608
class DBusPropertyException(dbus.exceptions.DBusException):
609
"""A base class for D-Bus property-related exceptions
611
def __unicode__(self):
612
return unicode(str(self))
615
class DBusPropertyAccessException(DBusPropertyException):
616
"""A property's access permissions disallows an operation.
621
class DBusPropertyNotFound(DBusPropertyException):
622
"""An attempt was made to access a non-existing property.
627
class DBusObjectWithProperties(dbus.service.Object):
628
"""A D-Bus object with properties.
630
Classes inheriting from this can use the dbus_service_property
631
decorator to expose methods as D-Bus properties. It exposes the
632
standard Get(), Set(), and GetAll() methods on the D-Bus.
636
def _is_dbus_property(obj):
637
return getattr(obj, "_dbus_is_property", False)
639
def _get_all_dbus_properties(self):
640
"""Returns a generator of (name, attribute) pairs
642
return ((prop.__get__(self)._dbus_name, prop.__get__(self))
643
for cls in self.__class__.__mro__
645
inspect.getmembers(cls, self._is_dbus_property))
647
def _get_dbus_property(self, interface_name, property_name):
648
"""Returns a bound method if one exists which is a D-Bus
649
property with the specified name and interface.
651
for cls in self.__class__.__mro__:
652
for name, value in (inspect.getmembers
653
(cls, self._is_dbus_property)):
654
if (value._dbus_name == property_name
655
and value._dbus_interface == interface_name):
656
return value.__get__(self)
659
raise DBusPropertyNotFound(self.dbus_object_path + ":"
660
+ interface_name + "."
663
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
665
def Get(self, interface_name, property_name):
666
"""Standard D-Bus property Get() method, see D-Bus standard.
668
prop = self._get_dbus_property(interface_name, property_name)
669
if prop._dbus_access == "write":
670
raise DBusPropertyAccessException(property_name)
672
if not hasattr(value, "variant_level"):
674
return type(value)(value, variant_level=value.variant_level+1)
676
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
677
def Set(self, interface_name, property_name, value):
678
"""Standard D-Bus property Set() method, see D-Bus standard.
680
prop = self._get_dbus_property(interface_name, property_name)
681
if prop._dbus_access == "read":
682
raise DBusPropertyAccessException(property_name)
683
if prop._dbus_get_args_options["byte_arrays"]:
684
# The byte_arrays option is not supported yet on
685
# signatures other than "ay".
686
if prop._dbus_signature != "ay":
688
value = dbus.ByteArray(''.join(unichr(byte)
692
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
693
out_signature="a{sv}")
694
def GetAll(self, interface_name):
695
"""Standard D-Bus property GetAll() method, see D-Bus
698
Note: Will not include properties with access="write".
701
for name, prop in self._get_all_dbus_properties():
703
and interface_name != prop._dbus_interface):
704
# Interface non-empty but did not match
706
# Ignore write-only properties
707
if prop._dbus_access == "write":
710
if not hasattr(value, "variant_level"):
713
all[name] = type(value)(value, variant_level=
714
value.variant_level+1)
715
return dbus.Dictionary(all, signature="sv")
717
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
719
path_keyword='object_path',
720
connection_keyword='connection')
721
def Introspect(self, object_path, connection):
722
"""Standard D-Bus method, overloaded to insert property tags.
724
xmlstring = dbus.service.Object.Introspect(self, object_path,
727
document = xml.dom.minidom.parseString(xmlstring)
728
def make_tag(document, name, prop):
729
e = document.createElement("property")
730
e.setAttribute("name", name)
731
e.setAttribute("type", prop._dbus_signature)
732
e.setAttribute("access", prop._dbus_access)
734
for if_tag in document.getElementsByTagName("interface"):
735
for tag in (make_tag(document, name, prop)
737
in self._get_all_dbus_properties()
738
if prop._dbus_interface
739
== if_tag.getAttribute("name")):
740
if_tag.appendChild(tag)
741
# Add the names to the return values for the
742
# "org.freedesktop.DBus.Properties" methods
743
if (if_tag.getAttribute("name")
744
== "org.freedesktop.DBus.Properties"):
745
for cn in if_tag.getElementsByTagName("method"):
746
if cn.getAttribute("name") == "Get":
747
for arg in cn.getElementsByTagName("arg"):
748
if (arg.getAttribute("direction")
750
arg.setAttribute("name", "value")
751
elif cn.getAttribute("name") == "GetAll":
752
for arg in cn.getElementsByTagName("arg"):
753
if (arg.getAttribute("direction")
755
arg.setAttribute("name", "props")
756
xmlstring = document.toxml("utf-8")
758
except (AttributeError, xml.dom.DOMException,
759
xml.parsers.expat.ExpatError) as error:
760
logger.error("Failed to override Introspection method",
765
def datetime_to_dbus (dt, variant_level=0):
766
"""Convert a UTC datetime.datetime() to a D-Bus type."""
768
return dbus.String("", variant_level = variant_level)
769
return dbus.String(dt.isoformat(),
770
variant_level=variant_level)
772
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
774
"""Applied to an empty subclass of a D-Bus object, this metaclass
775
will add additional D-Bus attributes matching a certain pattern.
777
def __new__(mcs, name, bases, attr):
778
# Go through all the base classes which could have D-Bus
779
# methods, signals, or properties in them
780
for base in (b for b in bases
781
if issubclass(b, dbus.service.Object)):
782
# Go though all attributes of the base class
783
for attrname, attribute in inspect.getmembers(base):
784
# Ignore non-D-Bus attributes, and D-Bus attributes
785
# with the wrong interface name
786
if (not hasattr(attribute, "_dbus_interface")
787
or not attribute._dbus_interface
788
.startswith("se.recompile.Mandos")):
790
# Create an alternate D-Bus interface name based on
792
alt_interface = (attribute._dbus_interface
793
.replace("se.recompile.Mandos",
794
"se.bsnet.fukt.Mandos"))
795
# Is this a D-Bus signal?
796
if getattr(attribute, "_dbus_is_signal", False):
797
# Extract the original non-method function by
799
nonmethod_func = (dict(
800
zip(attribute.func_code.co_freevars,
801
attribute.__closure__))["func"]
803
# Create a new, but exactly alike, function
804
# object, and decorate it to be a new D-Bus signal
805
# with the alternate D-Bus interface name
806
new_function = (dbus.service.signal
808
attribute._dbus_signature)
810
nonmethod_func.func_code,
811
nonmethod_func.func_globals,
812
nonmethod_func.func_name,
813
nonmethod_func.func_defaults,
814
nonmethod_func.func_closure)))
815
# Define a creator of a function to call both the
816
# old and new functions, so both the old and new
817
# signals gets sent when the function is called
818
def fixscope(func1, func2):
819
"""This function is a scope container to pass
820
func1 and func2 to the "call_both" function
821
outside of its arguments"""
822
def call_both(*args, **kwargs):
823
"""This function will emit two D-Bus
824
signals by calling func1 and func2"""
825
func1(*args, **kwargs)
826
func2(*args, **kwargs)
828
# Create the "call_both" function and add it to
830
attr[attrname] = fixscope(attribute,
832
# Is this a D-Bus method?
833
elif getattr(attribute, "_dbus_is_method", False):
834
# Create a new, but exactly alike, function
835
# object. Decorate it to be a new D-Bus method
836
# with the alternate D-Bus interface name. Add it
838
attr[attrname] = (dbus.service.method
840
attribute._dbus_in_signature,
841
attribute._dbus_out_signature)
843
(attribute.func_code,
844
attribute.func_globals,
846
attribute.func_defaults,
847
attribute.func_closure)))
848
# Is this a D-Bus property?
849
elif getattr(attribute, "_dbus_is_property", False):
850
# Create a new, but exactly alike, function
851
# object, and decorate it to be a new D-Bus
852
# property with the alternate D-Bus interface
853
# name. Add it to the class.
854
attr[attrname] = (dbus_service_property
856
attribute._dbus_signature,
857
attribute._dbus_access,
859
._dbus_get_args_options
862
(attribute.func_code,
863
attribute.func_globals,
865
attribute.func_defaults,
866
attribute.func_closure)))
867
return type.__new__(mcs, name, bases, attr)
869
class ClientDBus(Client, DBusObjectWithProperties):
870
"""A Client class using D-Bus
873
dbus_object_path: dbus.ObjectPath
874
bus: dbus.SystemBus()
877
runtime_expansions = (Client.runtime_expansions
878
+ ("dbus_object_path",))
880
# dbus.service.Object doesn't use super(), so we can't either.
882
def __init__(self, bus = None, *args, **kwargs):
883
self._approvals_pending = 0
885
Client.__init__(self, *args, **kwargs)
886
# Only now, when this client is initialized, can it show up on
888
client_object_name = unicode(self.name).translate(
891
self.dbus_object_path = (dbus.ObjectPath
892
("/clients/" + client_object_name))
893
DBusObjectWithProperties.__init__(self, self.bus,
894
self.dbus_object_path)
896
def notifychangeproperty(transform_func,
897
dbus_name, type_func=lambda x: x,
899
""" Modify a variable so that it's a property which announces
902
transform_fun: Function that takes a value and a variant_level
903
and transforms it to a D-Bus type.
904
dbus_name: D-Bus name of the variable
905
type_func: Function that transform the value before sending it
906
to the D-Bus. Default: no transform
907
variant_level: D-Bus variant level. Default: 1
909
attrname = "_{0}".format(dbus_name)
910
def setter(self, value):
911
if hasattr(self, "dbus_object_path"):
912
if (not hasattr(self, attrname) or
913
type_func(getattr(self, attrname, None))
914
!= type_func(value)):
915
dbus_value = transform_func(type_func(value),
918
self.PropertyChanged(dbus.String(dbus_name),
920
setattr(self, attrname, value)
922
return property(lambda self: getattr(self, attrname), setter)
925
expires = notifychangeproperty(datetime_to_dbus, "Expires")
926
approvals_pending = notifychangeproperty(dbus.Boolean,
929
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
930
last_enabled = notifychangeproperty(datetime_to_dbus,
932
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
933
type_func = lambda checker:
935
last_checked_ok = notifychangeproperty(datetime_to_dbus,
937
last_approval_request = notifychangeproperty(
938
datetime_to_dbus, "LastApprovalRequest")
939
approved_by_default = notifychangeproperty(dbus.Boolean,
941
approval_delay = notifychangeproperty(dbus.UInt16,
944
_timedelta_to_milliseconds)
945
approval_duration = notifychangeproperty(
946
dbus.UInt16, "ApprovalDuration",
947
type_func = _timedelta_to_milliseconds)
948
host = notifychangeproperty(dbus.String, "Host")
949
timeout = notifychangeproperty(dbus.UInt16, "Timeout",
951
_timedelta_to_milliseconds)
952
extended_timeout = notifychangeproperty(
953
dbus.UInt16, "ExtendedTimeout",
954
type_func = _timedelta_to_milliseconds)
955
interval = notifychangeproperty(dbus.UInt16,
958
_timedelta_to_milliseconds)
959
checker_command = notifychangeproperty(dbus.String, "Checker")
961
del notifychangeproperty
963
def __del__(self, *args, **kwargs):
965
self.remove_from_connection()
968
if hasattr(DBusObjectWithProperties, "__del__"):
969
DBusObjectWithProperties.__del__(self, *args, **kwargs)
970
Client.__del__(self, *args, **kwargs)
972
def checker_callback(self, pid, condition, command,
974
self.checker_callback_tag = None
976
if os.WIFEXITED(condition):
977
exitstatus = os.WEXITSTATUS(condition)
979
self.CheckerCompleted(dbus.Int16(exitstatus),
980
dbus.Int64(condition),
981
dbus.String(command))
984
self.CheckerCompleted(dbus.Int16(-1),
985
dbus.Int64(condition),
986
dbus.String(command))
988
return Client.checker_callback(self, pid, condition, command,
991
def start_checker(self, *args, **kwargs):
992
old_checker = self.checker
993
if self.checker is not None:
994
old_checker_pid = self.checker.pid
996
old_checker_pid = None
997
r = Client.start_checker(self, *args, **kwargs)
998
# Only if new checker process was started
999
if (self.checker is not None
1000
and old_checker_pid != self.checker.pid):
1002
self.CheckerStarted(self.current_checker_command)
1005
def _reset_approved(self):
1006
self._approved = None
1009
def approve(self, value=True):
1010
self.send_changedstate()
1011
self._approved = value
1012
gobject.timeout_add(_timedelta_to_milliseconds
1013
(self.approval_duration),
1014
self._reset_approved)
1017
## D-Bus methods, signals & properties
1018
_interface = "se.recompile.Mandos.Client"
1022
# CheckerCompleted - signal
1023
@dbus.service.signal(_interface, signature="nxs")
1024
def CheckerCompleted(self, exitcode, waitstatus, command):
1028
# CheckerStarted - signal
1029
@dbus.service.signal(_interface, signature="s")
1030
def CheckerStarted(self, command):
1034
# PropertyChanged - signal
1035
@dbus.service.signal(_interface, signature="sv")
1036
def PropertyChanged(self, property, value):
1040
# GotSecret - signal
1041
@dbus.service.signal(_interface)
1042
def GotSecret(self):
1044
Is sent after a successful transfer of secret from the Mandos
1045
server to mandos-client
1050
@dbus.service.signal(_interface, signature="s")
1051
def Rejected(self, reason):
1055
# NeedApproval - signal
1056
@dbus.service.signal(_interface, signature="tb")
1057
def NeedApproval(self, timeout, default):
1059
return self.need_approval()
1064
@dbus.service.method(_interface, in_signature="b")
1065
def Approve(self, value):
1068
# CheckedOK - method
1069
@dbus.service.method(_interface)
1070
def CheckedOK(self):
1074
@dbus.service.method(_interface)
1079
# StartChecker - method
1080
@dbus.service.method(_interface)
1081
def StartChecker(self):
1083
self.start_checker()
1086
@dbus.service.method(_interface)
1091
# StopChecker - method
1092
@dbus.service.method(_interface)
1093
def StopChecker(self):
1098
# ApprovalPending - property
1099
@dbus_service_property(_interface, signature="b", access="read")
1100
def ApprovalPending_dbus_property(self):
1101
return dbus.Boolean(bool(self.approvals_pending))
1103
# ApprovedByDefault - property
1104
@dbus_service_property(_interface, signature="b",
1106
def ApprovedByDefault_dbus_property(self, value=None):
1107
if value is None: # get
1108
return dbus.Boolean(self.approved_by_default)
1109
self.approved_by_default = bool(value)
1111
# ApprovalDelay - property
1112
@dbus_service_property(_interface, signature="t",
1114
def ApprovalDelay_dbus_property(self, value=None):
1115
if value is None: # get
1116
return dbus.UInt64(self.approval_delay_milliseconds())
1117
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1119
# ApprovalDuration - property
1120
@dbus_service_property(_interface, signature="t",
1122
def ApprovalDuration_dbus_property(self, value=None):
1123
if value is None: # get
1124
return dbus.UInt64(_timedelta_to_milliseconds(
1125
self.approval_duration))
1126
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1129
@dbus_service_property(_interface, signature="s", access="read")
1130
def Name_dbus_property(self):
1131
return dbus.String(self.name)
1133
# Fingerprint - property
1134
@dbus_service_property(_interface, signature="s", access="read")
1135
def Fingerprint_dbus_property(self):
1136
return dbus.String(self.fingerprint)
1139
@dbus_service_property(_interface, signature="s",
1141
def Host_dbus_property(self, value=None):
1142
if value is None: # get
1143
return dbus.String(self.host)
1146
# Created - property
1147
@dbus_service_property(_interface, signature="s", access="read")
1148
def Created_dbus_property(self):
1149
return dbus.String(datetime_to_dbus(self.created))
1151
# LastEnabled - property
1152
@dbus_service_property(_interface, signature="s", access="read")
1153
def LastEnabled_dbus_property(self):
1154
return datetime_to_dbus(self.last_enabled)
1156
# Enabled - property
1157
@dbus_service_property(_interface, signature="b",
1159
def Enabled_dbus_property(self, value=None):
1160
if value is None: # get
1161
return dbus.Boolean(self.enabled)
1167
# LastCheckedOK - property
1168
@dbus_service_property(_interface, signature="s",
1170
def LastCheckedOK_dbus_property(self, value=None):
1171
if value is not None:
1174
return datetime_to_dbus(self.last_checked_ok)
1176
# Expires - property
1177
@dbus_service_property(_interface, signature="s", access="read")
1178
def Expires_dbus_property(self):
1179
return datetime_to_dbus(self.expires)
1181
# LastApprovalRequest - property
1182
@dbus_service_property(_interface, signature="s", access="read")
1183
def LastApprovalRequest_dbus_property(self):
1184
return datetime_to_dbus(self.last_approval_request)
1186
# Timeout - property
1187
@dbus_service_property(_interface, signature="t",
1189
def Timeout_dbus_property(self, value=None):
1190
if value is None: # get
1191
return dbus.UInt64(self.timeout_milliseconds())
1192
self.timeout = datetime.timedelta(0, 0, 0, value)
1193
if getattr(self, "disable_initiator_tag", None) is None:
1195
# Reschedule timeout
1196
gobject.source_remove(self.disable_initiator_tag)
1197
self.disable_initiator_tag = None
1199
time_to_die = _timedelta_to_milliseconds((self
1204
if time_to_die <= 0:
1205
# The timeout has passed
1208
self.expires = (datetime.datetime.utcnow()
1209
+ datetime.timedelta(milliseconds =
1211
self.disable_initiator_tag = (gobject.timeout_add
1212
(time_to_die, self.disable))
1214
# ExtendedTimeout - property
1215
@dbus_service_property(_interface, signature="t",
1217
def ExtendedTimeout_dbus_property(self, value=None):
1218
if value is None: # get
1219
return dbus.UInt64(self.extended_timeout_milliseconds())
1220
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1222
# Interval - property
1223
@dbus_service_property(_interface, signature="t",
1225
def Interval_dbus_property(self, value=None):
1226
if value is None: # get
1227
return dbus.UInt64(self.interval_milliseconds())
1228
self.interval = datetime.timedelta(0, 0, 0, value)
1229
if getattr(self, "checker_initiator_tag", None) is None:
1231
# Reschedule checker run
1232
gobject.source_remove(self.checker_initiator_tag)
1233
self.checker_initiator_tag = (gobject.timeout_add
1234
(value, self.start_checker))
1235
self.start_checker() # Start one now, too
1237
# Checker - property
1238
@dbus_service_property(_interface, signature="s",
1240
def Checker_dbus_property(self, value=None):
1241
if value is None: # get
1242
return dbus.String(self.checker_command)
1243
self.checker_command = value
1245
# CheckerRunning - property
1246
@dbus_service_property(_interface, signature="b",
1248
def CheckerRunning_dbus_property(self, value=None):
1249
if value is None: # get
1250
return dbus.Boolean(self.checker is not None)
1252
self.start_checker()
1256
# ObjectPath - property
1257
@dbus_service_property(_interface, signature="o", access="read")
1258
def ObjectPath_dbus_property(self):
1259
return self.dbus_object_path # is already a dbus.ObjectPath
1262
@dbus_service_property(_interface, signature="ay",
1263
access="write", byte_arrays=True)
1264
def Secret_dbus_property(self, value):
1265
self.secret = str(value)
1270
class ProxyClient(object):
1271
def __init__(self, child_pipe, fpr, address):
1272
self._pipe = child_pipe
1273
self._pipe.send(('init', fpr, address))
1274
if not self._pipe.recv():
1277
def __getattribute__(self, name):
1278
if(name == '_pipe'):
1279
return super(ProxyClient, self).__getattribute__(name)
1280
self._pipe.send(('getattr', name))
1281
data = self._pipe.recv()
1282
if data[0] == 'data':
1284
if data[0] == 'function':
1285
def func(*args, **kwargs):
1286
self._pipe.send(('funcall', name, args, kwargs))
1287
return self._pipe.recv()[1]
1290
def __setattr__(self, name, value):
1291
if(name == '_pipe'):
1292
return super(ProxyClient, self).__setattr__(name, value)
1293
self._pipe.send(('setattr', name, value))
1295
class ClientDBusTransitional(ClientDBus):
1296
__metaclass__ = AlternateDBusNamesMetaclass
1298
class ClientHandler(socketserver.BaseRequestHandler, object):
1299
"""A class to handle client connections.
1301
Instantiated once for each connection to handle it.
427
1302
Note: This will run in its own forked process."""
429
1304
def handle(self):
430
logger.info(u"TCP connection from: %s",
431
unicode(self.client_address))
432
session = gnutls.connection.ClientSession\
433
(self.request, gnutls.connection.X509Credentials())
435
line = self.request.makefile().readline()
436
logger.debug(u"Protocol version: %r", line)
438
if int(line.strip().split()[0]) > 1:
440
except (ValueError, IndexError, RuntimeError), error:
441
logger.error(u"Unknown protocol version: %s", error)
444
# Note: gnutls.connection.X509Credentials is really a generic
445
# GnuTLS certificate credentials object so long as no X.509
446
# keys are added to it. Therefore, we can use it here despite
447
# using OpenPGP certificates.
449
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
450
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
452
priority = "NORMAL" # Fallback default, since this
454
if self.server.settings["priority"]:
455
priority = self.server.settings["priority"]
456
gnutls.library.functions.gnutls_priority_set_direct\
457
(session._c_object, priority, None);
461
except gnutls.errors.GNUTLSError, error:
462
logger.warning(u"Handshake failed: %s", error)
463
# Do not run session.bye() here: the session is not
464
# established. Just abandon the request.
467
fpr = fingerprint(peer_certificate(session))
468
except (TypeError, gnutls.errors.GNUTLSError), error:
469
logger.warning(u"Bad certificate: %s", error)
472
logger.debug(u"Fingerprint: %s", fpr)
474
for c in self.server.clients:
475
if c.fingerprint == fpr:
479
logger.warning(u"Client not found for fingerprint: %s",
483
# Have to check if client.still_valid(), since it is possible
484
# that the client timed out while establishing the GnuTLS
486
if not client.still_valid():
487
logger.warning(u"Client %(name)s is invalid",
492
while sent_size < len(client.secret):
493
sent = session.send(client.secret[sent_size:])
494
logger.debug(u"Sent: %d, remaining: %d",
495
sent, len(client.secret)
496
- (sent_size + sent))
501
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
502
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1305
with contextlib.closing(self.server.child_pipe) as child_pipe:
1306
logger.info("TCP connection from: %s",
1307
unicode(self.client_address))
1308
logger.debug("Pipe FD: %d",
1309
self.server.child_pipe.fileno())
1311
session = (gnutls.connection
1312
.ClientSession(self.request,
1314
.X509Credentials()))
1316
# Note: gnutls.connection.X509Credentials is really a
1317
# generic GnuTLS certificate credentials object so long as
1318
# no X.509 keys are added to it. Therefore, we can use it
1319
# here despite using OpenPGP certificates.
1321
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1322
# "+AES-256-CBC", "+SHA1",
1323
# "+COMP-NULL", "+CTYPE-OPENPGP",
1325
# Use a fallback default, since this MUST be set.
1326
priority = self.server.gnutls_priority
1327
if priority is None:
1329
(gnutls.library.functions
1330
.gnutls_priority_set_direct(session._c_object,
1333
# Start communication using the Mandos protocol
1334
# Get protocol number
1335
line = self.request.makefile().readline()
1336
logger.debug("Protocol version: %r", line)
1338
if int(line.strip().split()[0]) > 1:
1340
except (ValueError, IndexError, RuntimeError) as error:
1341
logger.error("Unknown protocol version: %s", error)
1344
# Start GnuTLS connection
1347
except gnutls.errors.GNUTLSError as error:
1348
logger.warning("Handshake failed: %s", error)
1349
# Do not run session.bye() here: the session is not
1350
# established. Just abandon the request.
1352
logger.debug("Handshake succeeded")
1354
approval_required = False
1357
fpr = self.fingerprint(self.peer_certificate
1360
gnutls.errors.GNUTLSError) as error:
1361
logger.warning("Bad certificate: %s", error)
1363
logger.debug("Fingerprint: %s", fpr)
1366
client = ProxyClient(child_pipe, fpr,
1367
self.client_address)
1371
if client.approval_delay:
1372
delay = client.approval_delay
1373
client.approvals_pending += 1
1374
approval_required = True
1377
if not client.enabled:
1378
logger.info("Client %s is disabled",
1380
if self.server.use_dbus:
1382
client.Rejected("Disabled")
1385
if client._approved or not client.approval_delay:
1386
#We are approved or approval is disabled
1388
elif client._approved is None:
1389
logger.info("Client %s needs approval",
1391
if self.server.use_dbus:
1393
client.NeedApproval(
1394
client.approval_delay_milliseconds(),
1395
client.approved_by_default)
1397
logger.warning("Client %s was not approved",
1399
if self.server.use_dbus:
1401
client.Rejected("Denied")
1404
#wait until timeout or approved
1405
time = datetime.datetime.now()
1406
client.changedstate.acquire()
1407
(client.changedstate.wait
1408
(float(client._timedelta_to_milliseconds(delay)
1410
client.changedstate.release()
1411
time2 = datetime.datetime.now()
1412
if (time2 - time) >= delay:
1413
if not client.approved_by_default:
1414
logger.warning("Client %s timed out while"
1415
" waiting for approval",
1417
if self.server.use_dbus:
1419
client.Rejected("Approval timed out")
1424
delay -= time2 - time
1427
while sent_size < len(client.secret):
1429
sent = session.send(client.secret[sent_size:])
1430
except gnutls.errors.GNUTLSError as error:
1431
logger.warning("gnutls send failed")
1433
logger.debug("Sent: %d, remaining: %d",
1434
sent, len(client.secret)
1435
- (sent_size + sent))
1438
logger.info("Sending secret to %s", client.name)
1439
# bump the timeout using extended_timeout
1440
client.checked_ok(client.extended_timeout)
1441
if self.server.use_dbus:
1446
if approval_required:
1447
client.approvals_pending -= 1
1450
except gnutls.errors.GNUTLSError as error:
1451
logger.warning("GnuTLS bye failed")
1454
def peer_certificate(session):
1455
"Return the peer's OpenPGP certificate as a bytestring"
1456
# If not an OpenPGP certificate...
1457
if (gnutls.library.functions
1458
.gnutls_certificate_type_get(session._c_object)
1459
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1460
# ...do the normal thing
1461
return session.peer_certificate
1462
list_size = ctypes.c_uint(1)
1463
cert_list = (gnutls.library.functions
1464
.gnutls_certificate_get_peers
1465
(session._c_object, ctypes.byref(list_size)))
1466
if not bool(cert_list) and list_size.value != 0:
1467
raise gnutls.errors.GNUTLSError("error getting peer"
1469
if list_size.value == 0:
1472
return ctypes.string_at(cert.data, cert.size)
1475
def fingerprint(openpgp):
1476
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1477
# New GnuTLS "datum" with the OpenPGP public key
1478
datum = (gnutls.library.types
1479
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1482
ctypes.c_uint(len(openpgp))))
1483
# New empty GnuTLS certificate
1484
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1485
(gnutls.library.functions
1486
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1487
# Import the OpenPGP public key into the certificate
1488
(gnutls.library.functions
1489
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1490
gnutls.library.constants
1491
.GNUTLS_OPENPGP_FMT_RAW))
1492
# Verify the self signature in the key
1493
crtverify = ctypes.c_uint()
1494
(gnutls.library.functions
1495
.gnutls_openpgp_crt_verify_self(crt, 0,
1496
ctypes.byref(crtverify)))
1497
if crtverify.value != 0:
1498
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1499
raise (gnutls.errors.CertificateSecurityError
1501
# New buffer for the fingerprint
1502
buf = ctypes.create_string_buffer(20)
1503
buf_len = ctypes.c_size_t()
1504
# Get the fingerprint from the certificate into the buffer
1505
(gnutls.library.functions
1506
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1507
ctypes.byref(buf_len)))
1508
# Deinit the certificate
1509
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1510
# Convert the buffer to a Python bytestring
1511
fpr = ctypes.string_at(buf, buf_len.value)
1512
# Convert the bytestring to hexadecimal notation
1513
hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1517
class MultiprocessingMixIn(object):
1518
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1519
def sub_process_main(self, request, address):
1521
self.finish_request(request, address)
1523
self.handle_error(request, address)
1524
self.close_request(request)
1526
def process_request(self, request, address):
1527
"""Start a new process to process the request."""
1528
proc = multiprocessing.Process(target = self.sub_process_main,
1535
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1536
""" adds a pipe to the MixIn """
1537
def process_request(self, request, client_address):
1538
"""Overrides and wraps the original process_request().
1540
This function creates a new pipe in self.pipe
1542
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1544
proc = MultiprocessingMixIn.process_request(self, request,
1546
self.child_pipe.close()
1547
self.add_pipe(parent_pipe, proc)
1549
def add_pipe(self, parent_pipe, proc):
1550
"""Dummy function; override as necessary"""
1551
raise NotImplementedError
1554
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1555
socketserver.TCPServer, object):
1556
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
504
settings: Server settings
505
clients: Set() of Client objects
1559
enabled: Boolean; whether this server is activated yet
1560
interface: None or a network interface name (string)
1561
use_ipv6: Boolean; to use IPv6 or not
507
address_family = socket.AF_INET6
508
def __init__(self, *args, **kwargs):
509
if "settings" in kwargs:
510
self.settings = kwargs["settings"]
511
del kwargs["settings"]
512
if "clients" in kwargs:
513
self.clients = kwargs["clients"]
514
del kwargs["clients"]
515
return super(type(self), self).__init__(*args, **kwargs)
1563
def __init__(self, server_address, RequestHandlerClass,
1564
interface=None, use_ipv6=True):
1565
self.interface = interface
1567
self.address_family = socket.AF_INET6
1568
socketserver.TCPServer.__init__(self, server_address,
1569
RequestHandlerClass)
516
1570
def server_bind(self):
517
1571
"""This overrides the normal server_bind() function
518
1572
to bind to an interface if one was specified, and also NOT to
519
1573
bind to an address or port if they were not specified."""
520
if self.settings["interface"]:
521
# 25 is from /usr/include/asm-i486/socket.h
522
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
524
self.socket.setsockopt(socket.SOL_SOCKET,
526
self.settings["interface"])
527
except socket.error, error:
528
if error[0] == errno.EPERM:
529
logger.error(u"No permission to"
530
u" bind to interface %s",
531
self.settings["interface"])
1574
if self.interface is not None:
1575
if SO_BINDTODEVICE is None:
1576
logger.error("SO_BINDTODEVICE does not exist;"
1577
" cannot bind to interface %s",
1581
self.socket.setsockopt(socket.SOL_SOCKET,
1585
except socket.error as error:
1586
if error[0] == errno.EPERM:
1587
logger.error("No permission to"
1588
" bind to interface %s",
1590
elif error[0] == errno.ENOPROTOOPT:
1591
logger.error("SO_BINDTODEVICE not available;"
1592
" cannot bind to interface %s",
534
1596
# Only bind(2) the socket if we really need to.
535
1597
if self.server_address[0] or self.server_address[1]:
536
1598
if not self.server_address[0]:
538
self.server_address = (in6addr_any,
1599
if self.address_family == socket.AF_INET6:
1600
any_address = "::" # in6addr_any
1602
any_address = socket.INADDR_ANY
1603
self.server_address = (any_address,
539
1604
self.server_address[1])
540
1605
elif not self.server_address[1]:
541
1606
self.server_address = (self.server_address[0],
543
# if self.settings["interface"]:
1608
# if self.interface:
544
1609
# self.server_address = (self.server_address[0],
547
1612
# if_nametoindex
550
return super(type(self), self).server_bind()
1614
return socketserver.TCPServer.server_bind(self)
1617
class MandosServer(IPv6_TCPServer):
1621
clients: set of Client objects
1622
gnutls_priority GnuTLS priority string
1623
use_dbus: Boolean; to emit D-Bus signals or not
1625
Assumes a gobject.MainLoop event loop.
1627
def __init__(self, server_address, RequestHandlerClass,
1628
interface=None, use_ipv6=True, clients=None,
1629
gnutls_priority=None, use_dbus=True):
1630
self.enabled = False
1631
self.clients = clients
1632
if self.clients is None:
1633
self.clients = set()
1634
self.use_dbus = use_dbus
1635
self.gnutls_priority = gnutls_priority
1636
IPv6_TCPServer.__init__(self, server_address,
1637
RequestHandlerClass,
1638
interface = interface,
1639
use_ipv6 = use_ipv6)
1640
def server_activate(self):
1642
return socketserver.TCPServer.server_activate(self)
1647
def add_pipe(self, parent_pipe, proc):
1648
# Call "handle_ipc" for both data and EOF events
1649
gobject.io_add_watch(parent_pipe.fileno(),
1650
gobject.IO_IN | gobject.IO_HUP,
1651
functools.partial(self.handle_ipc,
1656
def handle_ipc(self, source, condition, parent_pipe=None,
1657
proc = None, client_object=None):
1659
gobject.IO_IN: "IN", # There is data to read.
1660
gobject.IO_OUT: "OUT", # Data can be written (without
1662
gobject.IO_PRI: "PRI", # There is urgent data to read.
1663
gobject.IO_ERR: "ERR", # Error condition.
1664
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1665
# broken, usually for pipes and
1668
conditions_string = ' | '.join(name
1670
condition_names.iteritems()
1671
if cond & condition)
1672
# error, or the other end of multiprocessing.Pipe has closed
1673
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1674
# Wait for other process to exit
1678
# Read a request from the child
1679
request = parent_pipe.recv()
1680
command = request[0]
1682
if command == 'init':
1684
address = request[2]
1686
for c in self.clients:
1687
if c.fingerprint == fpr:
1691
logger.info("Client not found for fingerprint: %s, ad"
1692
"dress: %s", fpr, address)
1695
mandos_dbus_service.ClientNotFound(fpr,
1697
parent_pipe.send(False)
1700
gobject.io_add_watch(parent_pipe.fileno(),
1701
gobject.IO_IN | gobject.IO_HUP,
1702
functools.partial(self.handle_ipc,
1708
parent_pipe.send(True)
1709
# remove the old hook in favor of the new above hook on
1712
if command == 'funcall':
1713
funcname = request[1]
1717
parent_pipe.send(('data', getattr(client_object,
1721
if command == 'getattr':
1722
attrname = request[1]
1723
if callable(client_object.__getattribute__(attrname)):
1724
parent_pipe.send(('function',))
1726
parent_pipe.send(('data', client_object
1727
.__getattribute__(attrname)))
1729
if command == 'setattr':
1730
attrname = request[1]
1732
setattr(client_object, attrname, value)
553
1737
def string_to_delta(interval):
554
1738
"""Parse a string and return a datetime.timedelta
556
1740
>>> string_to_delta('7d')
557
1741
datetime.timedelta(7)
558
1742
>>> string_to_delta('60s')
695
1873
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
696
1874
"servicename": "Mandos",
699
1880
# Parse config file for server-global settings
700
server_config = ConfigParser.SafeConfigParser(server_defaults)
1881
server_config = configparser.SafeConfigParser(server_defaults)
701
1882
del server_defaults
702
server_config.read(os.path.join(options.configdir, "mandos.conf"))
703
server_section = "server"
1883
server_config.read(os.path.join(options.configdir,
704
1885
# Convert the SafeConfigParser object to a dict
705
server_settings = dict(server_config.items(server_section))
706
# Use getboolean on the boolean config option
707
server_settings["debug"] = server_config.getboolean\
708
(server_section, "debug")
1886
server_settings = server_config.defaults()
1887
# Use the appropriate methods on the non-string config options
1888
for option in ("debug", "use_dbus", "use_ipv6"):
1889
server_settings[option] = server_config.getboolean("DEFAULT",
1891
if server_settings["port"]:
1892
server_settings["port"] = server_config.getint("DEFAULT",
709
1894
del server_config
711
1896
# Override the settings from the config file with command line
712
1897
# options, if set.
713
1898
for option in ("interface", "address", "port", "debug",
714
"priority", "servicename", "configdir"):
1899
"priority", "servicename", "configdir",
1900
"use_dbus", "use_ipv6", "debuglevel"):
715
1901
value = getattr(options, option)
716
1902
if value is not None:
717
1903
server_settings[option] = value
1905
# Force all strings to be unicode
1906
for option in server_settings.keys():
1907
if type(server_settings[option]) is str:
1908
server_settings[option] = unicode(server_settings[option])
719
1909
# Now we have our good server settings in "server_settings"
1911
##################################################################
721
1914
debug = server_settings["debug"]
724
syslogger.setLevel(logging.WARNING)
725
console.setLevel(logging.WARNING)
1915
debuglevel = server_settings["debuglevel"]
1916
use_dbus = server_settings["use_dbus"]
1917
use_ipv6 = server_settings["use_ipv6"]
727
1919
if server_settings["servicename"] != "Mandos":
728
syslogger.setFormatter(logging.Formatter\
729
('Mandos (%s): %%(levelname)s:'
1920
syslogger.setFormatter(logging.Formatter
1921
('Mandos (%s) [%%(process)d]:'
1922
' %%(levelname)s: %%(message)s'
731
1923
% server_settings["servicename"]))
733
1925
# Parse config file with clients
734
client_defaults = { "timeout": "1h",
1926
client_defaults = { "timeout": "5m",
1927
"extended_timeout": "15m",
736
1929
"checker": "fping -q -- %%(host)s",
1931
"approval_delay": "0s",
1932
"approval_duration": "1s",
738
client_config = ConfigParser.SafeConfigParser(client_defaults)
1934
client_config = configparser.SafeConfigParser(client_defaults)
739
1935
client_config.read(os.path.join(server_settings["configdir"],
740
1936
"clients.conf"))
743
service = AvahiService(name = server_settings["servicename"],
744
type = "_mandos._tcp", );
745
if server_settings["interface"]:
746
service.interface = if_nametoindex(server_settings["interface"])
1938
global mandos_dbus_service
1939
mandos_dbus_service = None
1941
tcp_server = MandosServer((server_settings["address"],
1942
server_settings["port"]),
1944
interface=(server_settings["interface"]
1948
server_settings["priority"],
1951
pidfilename = "/var/run/mandos.pid"
1953
pidfile = open(pidfilename, "w")
1955
logger.error("Could not open file %r", pidfilename)
1958
uid = pwd.getpwnam("_mandos").pw_uid
1959
gid = pwd.getpwnam("_mandos").pw_gid
1962
uid = pwd.getpwnam("mandos").pw_uid
1963
gid = pwd.getpwnam("mandos").pw_gid
1966
uid = pwd.getpwnam("nobody").pw_uid
1967
gid = pwd.getpwnam("nobody").pw_gid
1974
except OSError as error:
1975
if error[0] != errno.EPERM:
1978
if not debug and not debuglevel:
1979
logger.setLevel(logging.WARNING)
1981
level = getattr(logging, debuglevel.upper())
1982
logger.setLevel(level)
1985
logger.setLevel(logging.DEBUG)
1986
# Enable all possible GnuTLS debugging
1988
# "Use a log level over 10 to enable all debugging options."
1990
gnutls.library.functions.gnutls_global_set_log_level(11)
1992
@gnutls.library.types.gnutls_log_func
1993
def debug_gnutls(level, string):
1994
logger.debug("GnuTLS: %s", string[:-1])
1996
(gnutls.library.functions
1997
.gnutls_global_set_log_function(debug_gnutls))
1999
# Redirect stdin so all checkers get /dev/null
2000
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2001
os.dup2(null, sys.stdin.fileno())
2005
# No console logging
2006
logger.removeHandler(console)
2008
# Need to fork before connecting to D-Bus
2010
# Close all input and output, do double fork, etc.
748
2013
global main_loop
751
2014
# From the Avahi example code
752
2015
DBusGMainLoop(set_as_default=True )
753
2016
main_loop = gobject.MainLoop()
754
2017
bus = dbus.SystemBus()
755
server = dbus.Interface(
756
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
757
avahi.DBUS_INTERFACE_SERVER )
758
2018
# End of Avahi example code
761
def remove_from_clients(client):
762
clients.remove(client)
764
logger.critical(u"No clients left, exiting")
767
clients.update(Set(Client(name = section,
768
stop_hook = remove_from_clients,
770
= dict(client_config.items(section)))
771
for section in client_config.sections()))
773
logger.critical(u"No clients defined")
2021
bus_name = dbus.service.BusName("se.recompile.Mandos",
2022
bus, do_not_queue=True)
2023
old_bus_name = (dbus.service.BusName
2024
("se.bsnet.fukt.Mandos", bus,
2026
except dbus.exceptions.NameExistsException as e:
2027
logger.error(unicode(e) + ", disabling D-Bus")
2029
server_settings["use_dbus"] = False
2030
tcp_server.use_dbus = False
2031
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2032
service = AvahiServiceToSyslog(name =
2033
server_settings["servicename"],
2034
servicetype = "_mandos._tcp",
2035
protocol = protocol, bus = bus)
2036
if server_settings["interface"]:
2037
service.interface = (if_nametoindex
2038
(str(server_settings["interface"])))
2040
global multiprocessing_manager
2041
multiprocessing_manager = multiprocessing.Manager()
2043
client_class = Client
2045
client_class = functools.partial(ClientDBusTransitional,
2047
def client_config_items(config, section):
2048
special_settings = {
2049
"approved_by_default":
2050
lambda: config.getboolean(section,
2051
"approved_by_default"),
2053
for name, value in config.items(section):
2055
yield (name, special_settings[name]())
2059
tcp_server.clients.update(set(
2060
client_class(name = section,
2061
config= dict(client_config_items(
2062
client_config, section)))
2063
for section in client_config.sections()))
2064
if not tcp_server.clients:
2065
logger.warning("No clients defined")
777
logger.removeHandler(console)
780
pidfilename = "/var/run/mandos/mandos.pid"
783
pidfile = open(pidfilename, "w")
784
pidfile.write(str(pid) + "\n")
788
logger.error(u"Could not write %s file with PID %d",
789
pidfilename, os.getpid())
2071
pidfile.write(str(pid) + "\n".encode("utf-8"))
2074
logger.error("Could not write to file %r with PID %d",
2077
# "pidfile" was never created
2081
signal.signal(signal.SIGINT, signal.SIG_IGN)
2083
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2084
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2087
class MandosDBusService(dbus.service.Object):
2088
"""A D-Bus proxy object"""
2090
dbus.service.Object.__init__(self, bus, "/")
2091
_interface = "se.recompile.Mandos"
2093
@dbus.service.signal(_interface, signature="o")
2094
def ClientAdded(self, objpath):
2098
@dbus.service.signal(_interface, signature="ss")
2099
def ClientNotFound(self, fingerprint, address):
2103
@dbus.service.signal(_interface, signature="os")
2104
def ClientRemoved(self, objpath, name):
2108
@dbus.service.method(_interface, out_signature="ao")
2109
def GetAllClients(self):
2111
return dbus.Array(c.dbus_object_path
2112
for c in tcp_server.clients)
2114
@dbus.service.method(_interface,
2115
out_signature="a{oa{sv}}")
2116
def GetAllClientsWithProperties(self):
2118
return dbus.Dictionary(
2119
((c.dbus_object_path, c.GetAll(""))
2120
for c in tcp_server.clients),
2123
@dbus.service.method(_interface, in_signature="o")
2124
def RemoveClient(self, object_path):
2126
for c in tcp_server.clients:
2127
if c.dbus_object_path == object_path:
2128
tcp_server.clients.remove(c)
2129
c.remove_from_connection()
2130
# Don't signal anything except ClientRemoved
2131
c.disable(quiet=True)
2133
self.ClientRemoved(object_path, c.name)
2135
raise KeyError(object_path)
2139
class MandosDBusServiceTransitional(MandosDBusService):
2140
__metaclass__ = AlternateDBusNamesMetaclass
2141
mandos_dbus_service = MandosDBusServiceTransitional()
792
2144
"Cleanup function; run on exit"
794
# From the Avahi example code
795
if not group is None:
798
# End of Avahi example code
801
client = clients.pop()
802
client.stop_hook = None
2147
multiprocessing.active_children()
2148
while tcp_server.clients:
2149
client = tcp_server.clients.pop()
2151
client.remove_from_connection()
2152
client.disable_hook = None
2153
# Don't signal anything except ClientRemoved
2154
client.disable(quiet=True)
2157
mandos_dbus_service.ClientRemoved(client
805
2161
atexit.register(cleanup)
808
signal.signal(signal.SIGINT, signal.SIG_IGN)
809
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
810
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
812
for client in clients:
815
tcp_server = IPv6_TCPServer((server_settings["address"],
816
server_settings["port"]),
818
settings=server_settings,
2163
for client in tcp_server.clients:
2166
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2170
tcp_server.server_activate()
820
2172
# Find out what port we got
821
2173
service.port = tcp_server.socket.getsockname()[1]
822
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
823
u" scope_id %d" % tcp_server.socket.getsockname())
2175
logger.info("Now listening on address %r, port %d,"
2176
" flowinfo %d, scope_id %d"
2177
% tcp_server.socket.getsockname())
2179
logger.info("Now listening on address %r, port %d"
2180
% tcp_server.socket.getsockname())
825
2182
#service.interface = tcp_server.socket.getsockname()[3]
828
2185
# From the Avahi example code
829
server.connect_to_signal("StateChanged", server_state_changed)
831
server_state_changed(server.GetState())
832
except dbus.exceptions.DBusException, error:
833
logger.critical(u"DBusException: %s", error)
2188
except dbus.exceptions.DBusException as error:
2189
logger.critical("DBusException: %s", error)
835
2192
# End of Avahi example code
837
2194
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
838
2195
lambda *args, **kwargs:
839
tcp_server.handle_request\
840
(*args[2:], **kwargs) or True)
2196
(tcp_server.handle_request
2197
(*args[2:], **kwargs) or True))
842
logger.debug(u"Starting main loop")
843
main_loop_started = True
2199
logger.debug("Starting main loop")
845
except AvahiError, error:
846
logger.critical(u"AvahiError: %s" + unicode(error))
2201
except AvahiError as error:
2202
logger.critical("AvahiError: %s", error)
848
2205
except KeyboardInterrupt:
2207
print("", file=sys.stderr)
2208
logger.debug("Server received KeyboardInterrupt")
2209
logger.debug("Server exiting")
2210
# Must run before the D-Bus bus name gets deregistered
852
2214
if __name__ == '__main__':