124
147
self.rename_count = 0
125
148
self.max_renames = max_renames
149
self.protocol = protocol
150
self.group = None # our entry group
126
153
def rename(self):
127
154
"""Derived from the Avahi example code"""
128
155
if self.rename_count >= self.max_renames:
129
156
logger.critical(u"No suitable Zeroconf service name found"
130
157
u" after %i retries, exiting.",
131
158
self.rename_count)
132
raise AvahiServiceError("Too many renames")
133
self.name = server.GetAlternativeServiceName(self.name)
159
raise AvahiServiceError(u"Too many renames")
160
self.name = unicode(self.server.GetAlternativeServiceName(self.name))
134
161
logger.info(u"Changing Zeroconf service name to %r ...",
136
163
syslogger.setFormatter(logging.Formatter
137
('Mandos (%s): %%(levelname)s:'
138
' %%(message)s' % self.name))
164
(u'Mandos (%s) [%%(process)d]:'
165
u' %%(levelname)s: %%(message)s'
170
except dbus.exceptions.DBusException, error:
171
logger.critical(u"DBusException: %s", error)
141
174
self.rename_count += 1
142
175
def remove(self):
143
176
"""Derived from the Avahi example code"""
144
if group is not None:
177
if self.group is not None:
147
180
"""Derived from the Avahi example code"""
150
group = dbus.Interface(bus.get_object
152
server.EntryGroupNew()),
153
avahi.DBUS_INTERFACE_ENTRY_GROUP)
154
group.connect_to_signal('StateChanged',
155
entry_group_state_changed)
181
if self.group is None:
182
self.group = dbus.Interface(
183
self.bus.get_object(avahi.DBUS_NAME,
184
self.server.EntryGroupNew()),
185
avahi.DBUS_INTERFACE_ENTRY_GROUP)
186
self.group.connect_to_signal('StateChanged',
188
.entry_group_state_changed)
156
189
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
157
service.name, service.type)
159
self.interface, # interface
160
avahi.PROTO_INET6, # protocol
161
dbus.UInt32(0), # flags
162
self.name, self.type,
163
self.domain, self.host,
164
dbus.UInt16(self.port),
165
avahi.string_array_to_txt_array(self.TXT))
168
# From the Avahi example code:
169
group = None # our entry group
170
# End of Avahi example code
173
def _datetime_to_dbus_struct(dt, variant_level=0):
174
"""Convert a UTC datetime.datetime() to a D-Bus struct.
175
The format is special to this application, since we could not find
176
any other standard way."""
177
return dbus.Struct((dbus.Int16(dt.year),
181
dbus.Byte(dt.minute),
182
dbus.Byte(dt.second),
183
dbus.UInt32(dt.microsecond)),
185
variant_level=variant_level)
188
class Client(dbus.service.Object):
190
self.name, self.type)
191
self.group.AddService(
194
dbus.UInt32(0), # flags
195
self.name, self.type,
196
self.domain, self.host,
197
dbus.UInt16(self.port),
198
avahi.string_array_to_txt_array(self.TXT))
200
def entry_group_state_changed(self, state, error):
201
"""Derived from the Avahi example code"""
202
logger.debug(u"Avahi entry group state change: %i", state)
204
if state == avahi.ENTRY_GROUP_ESTABLISHED:
205
logger.debug(u"Zeroconf service established.")
206
elif state == avahi.ENTRY_GROUP_COLLISION:
207
logger.warning(u"Zeroconf service name collision.")
209
elif state == avahi.ENTRY_GROUP_FAILURE:
210
logger.critical(u"Avahi: Error in group state changed %s",
212
raise AvahiGroupError(u"State changed: %s"
215
"""Derived from the Avahi example code"""
216
if self.group is not None:
219
def server_state_changed(self, state):
220
"""Derived from the Avahi example code"""
221
logger.debug(u"Avahi server state change: %i", state)
222
if state == avahi.SERVER_COLLISION:
223
logger.error(u"Zeroconf server name collision")
225
elif state == avahi.SERVER_RUNNING:
228
"""Derived from the Avahi example code"""
229
if self.server is None:
230
self.server = dbus.Interface(
231
self.bus.get_object(avahi.DBUS_NAME,
232
avahi.DBUS_PATH_SERVER),
233
avahi.DBUS_INTERFACE_SERVER)
234
self.server.connect_to_signal(u"StateChanged",
235
self.server_state_changed)
236
self.server_state_changed(self.server.GetState())
239
class Client(object):
189
240
"""A representation of a client host served by this server.
191
name: string; from the config file, used in log messages
243
name: string; from the config file, used in log messages and
192
245
fingerprint: string (40 or 32 hexadecimal digits); used to
193
246
uniquely identify the client
194
247
secret: bytestring; sent verbatim (over TLS) to client
195
248
host: string; available for use by the checker command
196
249
created: datetime.datetime(); (UTC) object creation
197
last_started: datetime.datetime(); (UTC)
250
last_enabled: datetime.datetime(); (UTC)
199
252
last_checked_ok: datetime.datetime(); (UTC) or None
200
253
timeout: datetime.timedelta(); How long from last_checked_ok
201
until this client is invalid
254
until this client is disabled
202
255
interval: datetime.timedelta(); How often to start a new checker
203
stop_hook: If set, called by stop() as stop_hook(self)
256
disable_hook: If set, called by disable() as disable_hook(self)
204
257
checker: subprocess.Popen(); a running checker process used
205
258
to see if the client lives.
206
259
'None' if no process is running.
207
260
checker_initiator_tag: a gobject event source tag, or None
208
stop_initiator_tag: - '' -
261
disable_initiator_tag: - '' -
209
262
checker_callback_tag: - '' -
210
263
checker_command: string; External command which is run to check if
211
264
client lives. %() expansions are done at
212
265
runtime with vars(self) as dict, so that for
213
266
instance %(name)s can be used in the command.
214
dbus_object_path: dbus.ObjectPath
216
_timeout: Real variable for 'timeout'
217
_interval: Real variable for 'interval'
218
_timeout_milliseconds: Used when calling gobject.timeout_add()
219
_interval_milliseconds: - '' -
267
current_checker_command: string; current running checker_command
268
approval_delay: datetime.timedelta(); Time to wait for approval
269
_approved: bool(); 'None' if not yet approved/disapproved
270
approval_duration: datetime.timedelta(); Duration of one approval
221
def _set_timeout(self, timeout):
222
"Setter function for the 'timeout' attribute"
223
self._timeout = timeout
224
self._timeout_milliseconds = ((self.timeout.days
225
* 24 * 60 * 60 * 1000)
226
+ (self.timeout.seconds * 1000)
227
+ (self.timeout.microseconds
230
self.PropertyChanged(dbus.String(u"timeout"),
231
(dbus.UInt64(self._timeout_milliseconds,
233
timeout = property(lambda self: self._timeout, _set_timeout)
236
def _set_interval(self, interval):
237
"Setter function for the 'interval' attribute"
238
self._interval = interval
239
self._interval_milliseconds = ((self.interval.days
240
* 24 * 60 * 60 * 1000)
241
+ (self.interval.seconds
243
+ (self.interval.microseconds
246
self.PropertyChanged(dbus.String(u"interval"),
247
(dbus.UInt64(self._interval_milliseconds,
249
interval = property(lambda self: self._interval, _set_interval)
252
def __init__(self, name = None, stop_hook=None, config=None):
274
def _timedelta_to_milliseconds(td):
275
"Convert a datetime.timedelta() to milliseconds"
276
return ((td.days * 24 * 60 * 60 * 1000)
277
+ (td.seconds * 1000)
278
+ (td.microseconds // 1000))
280
def timeout_milliseconds(self):
281
"Return the 'timeout' attribute in milliseconds"
282
return self._timedelta_to_milliseconds(self.timeout)
284
def interval_milliseconds(self):
285
"Return the 'interval' attribute in milliseconds"
286
return self._timedelta_to_milliseconds(self.interval)
288
def approval_delay_milliseconds(self):
289
return self._timedelta_to_milliseconds(self.approval_delay)
291
def __init__(self, name = None, disable_hook=None, config=None):
253
292
"""Note: the 'checker' key in 'config' sets the
254
293
'checker_command' attribute and *not* the 'checker'
256
self.dbus_object_path = (dbus.ObjectPath
258
+ name.replace(".", "_")))
259
dbus.service.Object.__init__(self, bus,
260
self.dbus_object_path)
261
296
if config is None:
264
298
logger.debug(u"Creating client %r", self.name)
265
299
# Uppercase and remove spaces from fingerprint for later
266
300
# comparison purposes with return value from the fingerprint()
268
self.fingerprint = (config["fingerprint"].upper()
302
self.fingerprint = (config[u"fingerprint"].upper()
269
303
.replace(u" ", u""))
270
304
logger.debug(u" Fingerprint: %s", self.fingerprint)
271
if "secret" in config:
272
self.secret = config["secret"].decode(u"base64")
273
elif "secfile" in config:
274
with closing(open(os.path.expanduser
276
(config["secfile"])))) as secfile:
305
if u"secret" in config:
306
self.secret = config[u"secret"].decode(u"base64")
307
elif u"secfile" in config:
308
with open(os.path.expanduser(os.path.expandvars
309
(config[u"secfile"])),
277
311
self.secret = secfile.read()
279
313
raise TypeError(u"No secret or secfile for client %s"
281
self.host = config.get("host", "")
315
self.host = config.get(u"host", u"")
282
316
self.created = datetime.datetime.utcnow()
284
self.last_started = None
318
self.last_enabled = None
285
319
self.last_checked_ok = None
286
self.timeout = string_to_delta(config["timeout"])
287
self.interval = string_to_delta(config["interval"])
288
self.stop_hook = stop_hook
320
self.timeout = string_to_delta(config[u"timeout"])
321
self.interval = string_to_delta(config[u"interval"])
322
self.disable_hook = disable_hook
289
323
self.checker = None
290
324
self.checker_initiator_tag = None
291
self.stop_initiator_tag = None
325
self.disable_initiator_tag = None
292
326
self.checker_callback_tag = None
293
self.checker_command = config["checker"]
327
self.checker_command = config[u"checker"]
328
self.current_checker_command = None
329
self.last_connect = None
330
self._approved = None
331
self.approved_by_default = config.get(u"approved_by_default",
333
self.approvals_pending = 0
334
self.approval_delay = string_to_delta(
335
config[u"approval_delay"])
336
self.approval_duration = string_to_delta(
337
config[u"approval_duration"])
338
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
340
def send_changedstate(self):
341
self.changedstate.acquire()
342
self.changedstate.notify_all()
343
self.changedstate.release()
296
346
"""Start this client's checker and timeout hooks"""
297
self.last_started = datetime.datetime.utcnow()
347
if getattr(self, u"enabled", False):
350
self.send_changedstate()
351
self.last_enabled = datetime.datetime.utcnow()
298
352
# Schedule a new checker to be started an 'interval' from now,
299
353
# and every interval from then on.
300
354
self.checker_initiator_tag = (gobject.timeout_add
301
(self._interval_milliseconds,
355
(self.interval_milliseconds(),
302
356
self.start_checker))
357
# Schedule a disable() when 'timeout' has passed
358
self.disable_initiator_tag = (gobject.timeout_add
359
(self.timeout_milliseconds(),
303
362
# Also start a new checker *right now*.
304
363
self.start_checker()
305
# Schedule a stop() when 'timeout' has passed
306
self.stop_initiator_tag = (gobject.timeout_add
307
(self._timeout_milliseconds,
311
self.PropertyChanged(dbus.String(u"started"),
312
dbus.Boolean(True, variant_level=1))
313
self.PropertyChanged(dbus.String(u"last_started"),
314
(_datetime_to_dbus_struct
315
(self.last_started, variant_level=1)))
318
"""Stop this client."""
319
if not getattr(self, "started", False):
365
def disable(self, quiet=True):
366
"""Disable this client."""
367
if not getattr(self, "enabled", False):
321
logger.info(u"Stopping client %s", self.name)
322
if getattr(self, "stop_initiator_tag", False):
323
gobject.source_remove(self.stop_initiator_tag)
324
self.stop_initiator_tag = None
325
if getattr(self, "checker_initiator_tag", False):
370
self.send_changedstate()
372
logger.info(u"Disabling client %s", self.name)
373
if getattr(self, u"disable_initiator_tag", False):
374
gobject.source_remove(self.disable_initiator_tag)
375
self.disable_initiator_tag = None
376
if getattr(self, u"checker_initiator_tag", False):
326
377
gobject.source_remove(self.checker_initiator_tag)
327
378
self.checker_initiator_tag = None
328
379
self.stop_checker()
333
self.PropertyChanged(dbus.String(u"started"),
334
dbus.Boolean(False, variant_level=1))
380
if self.disable_hook:
381
self.disable_hook(self)
335
383
# Do not run this again if called by a gobject.timeout_add
338
386
def __del__(self):
339
self.stop_hook = None
387
self.disable_hook = None
342
390
def checker_callback(self, pid, condition, command):
343
391
"""The checker has completed, so take appropriate actions."""
344
392
self.checker_callback_tag = None
345
393
self.checker = None
347
self.PropertyChanged(dbus.String(u"checker_running"),
348
dbus.Boolean(False, variant_level=1))
349
if (os.WIFEXITED(condition)
350
and (os.WEXITSTATUS(condition) == 0)):
351
logger.info(u"Checker for %(name)s succeeded",
354
self.CheckerCompleted(dbus.Boolean(True),
355
dbus.UInt16(condition),
356
dbus.String(command))
358
elif not os.WIFEXITED(condition):
394
if os.WIFEXITED(condition):
395
exitstatus = os.WEXITSTATUS(condition)
397
logger.info(u"Checker for %(name)s succeeded",
401
logger.info(u"Checker for %(name)s failed",
359
404
logger.warning(u"Checker for %(name)s crashed?",
362
self.CheckerCompleted(dbus.Boolean(False),
363
dbus.UInt16(condition),
364
dbus.String(command))
366
logger.info(u"Checker for %(name)s failed",
369
self.CheckerCompleted(dbus.Boolean(False),
370
dbus.UInt16(condition),
371
dbus.String(command))
373
def bump_timeout(self):
407
def checked_ok(self):
374
408
"""Bump up the timeout for this client.
375
410
This should only be called when the client has been seen,
378
413
self.last_checked_ok = datetime.datetime.utcnow()
379
gobject.source_remove(self.stop_initiator_tag)
380
self.stop_initiator_tag = (gobject.timeout_add
381
(self._timeout_milliseconds,
383
self.PropertyChanged(dbus.String(u"last_checked_ok"),
384
(_datetime_to_dbus_struct
385
(self.last_checked_ok,
414
gobject.source_remove(self.disable_initiator_tag)
415
self.disable_initiator_tag = (gobject.timeout_add
416
(self.timeout_milliseconds(),
388
419
def start_checker(self):
389
420
"""Start a new checker subprocess if one is not running.
390
422
If a checker already exists, leave it running and do
392
424
# The reason for not killing a running checker is that if we
441
494
if self.checker_callback_tag:
442
495
gobject.source_remove(self.checker_callback_tag)
443
496
self.checker_callback_tag = None
444
if getattr(self, "checker", None) is None:
497
if getattr(self, u"checker", None) is None:
446
499
logger.debug(u"Stopping checker for %(name)s", vars(self))
448
501
os.kill(self.checker.pid, signal.SIGTERM)
450
503
#if self.checker.poll() is None:
451
504
# os.kill(self.checker.pid, signal.SIGKILL)
452
505
except OSError, error:
453
506
if error.errno != errno.ESRCH: # No such process
455
508
self.checker = None
456
self.PropertyChanged(dbus.String(u"checker_running"),
510
def dbus_service_property(dbus_interface, signature=u"v",
511
access=u"readwrite", byte_arrays=False):
512
"""Decorators for marking methods of a DBusObjectWithProperties to
513
become properties on the D-Bus.
515
The decorated method will be called with no arguments by "Get"
516
and with one argument by "Set".
518
The parameters, where they are supported, are the same as
519
dbus.service.method, except there is only "signature", since the
520
type from Get() and the type sent to Set() is the same.
522
# Encoding deeply encoded byte arrays is not supported yet by the
523
# "Set" method, so we fail early here:
524
if byte_arrays and signature != u"ay":
525
raise ValueError(u"Byte arrays not supported for non-'ay'"
526
u" signature %r" % signature)
528
func._dbus_is_property = True
529
func._dbus_interface = dbus_interface
530
func._dbus_signature = signature
531
func._dbus_access = access
532
func._dbus_name = func.__name__
533
if func._dbus_name.endswith(u"_dbus_property"):
534
func._dbus_name = func._dbus_name[:-14]
535
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
540
class DBusPropertyException(dbus.exceptions.DBusException):
541
"""A base class for D-Bus property-related exceptions
543
def __unicode__(self):
544
return unicode(str(self))
547
class DBusPropertyAccessException(DBusPropertyException):
548
"""A property's access permissions disallows an operation.
553
class DBusPropertyNotFound(DBusPropertyException):
554
"""An attempt was made to access a non-existing property.
559
class DBusObjectWithProperties(dbus.service.Object):
560
"""A D-Bus object with properties.
562
Classes inheriting from this can use the dbus_service_property
563
decorator to expose methods as D-Bus properties. It exposes the
564
standard Get(), Set(), and GetAll() methods on the D-Bus.
568
def _is_dbus_property(obj):
569
return getattr(obj, u"_dbus_is_property", False)
571
def _get_all_dbus_properties(self):
572
"""Returns a generator of (name, attribute) pairs
574
return ((prop._dbus_name, prop)
576
inspect.getmembers(self, self._is_dbus_property))
578
def _get_dbus_property(self, interface_name, property_name):
579
"""Returns a bound method if one exists which is a D-Bus
580
property with the specified name and interface.
582
for name in (property_name,
583
property_name + u"_dbus_property"):
584
prop = getattr(self, name, None)
586
or not self._is_dbus_property(prop)
587
or prop._dbus_name != property_name
588
or (interface_name and prop._dbus_interface
589
and interface_name != prop._dbus_interface)):
593
raise DBusPropertyNotFound(self.dbus_object_path + u":"
594
+ interface_name + u"."
597
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
599
def Get(self, interface_name, property_name):
600
"""Standard D-Bus property Get() method, see D-Bus standard.
602
prop = self._get_dbus_property(interface_name, property_name)
603
if prop._dbus_access == u"write":
604
raise DBusPropertyAccessException(property_name)
606
if not hasattr(value, u"variant_level"):
608
return type(value)(value, variant_level=value.variant_level+1)
610
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
611
def Set(self, interface_name, property_name, value):
612
"""Standard D-Bus property Set() method, see D-Bus standard.
614
prop = self._get_dbus_property(interface_name, property_name)
615
if prop._dbus_access == u"read":
616
raise DBusPropertyAccessException(property_name)
617
if prop._dbus_get_args_options[u"byte_arrays"]:
618
# The byte_arrays option is not supported yet on
619
# signatures other than "ay".
620
if prop._dbus_signature != u"ay":
622
value = dbus.ByteArray(''.join(unichr(byte)
626
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
627
out_signature=u"a{sv}")
628
def GetAll(self, interface_name):
629
"""Standard D-Bus property GetAll() method, see D-Bus
632
Note: Will not include properties with access="write".
635
for name, prop in self._get_all_dbus_properties():
637
and interface_name != prop._dbus_interface):
638
# Interface non-empty but did not match
640
# Ignore write-only properties
641
if prop._dbus_access == u"write":
644
if not hasattr(value, u"variant_level"):
647
all[name] = type(value)(value, variant_level=
648
value.variant_level+1)
649
return dbus.Dictionary(all, signature=u"sv")
651
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
653
path_keyword='object_path',
654
connection_keyword='connection')
655
def Introspect(self, object_path, connection):
656
"""Standard D-Bus method, overloaded to insert property tags.
658
xmlstring = dbus.service.Object.Introspect(self, object_path,
661
document = xml.dom.minidom.parseString(xmlstring)
662
def make_tag(document, name, prop):
663
e = document.createElement(u"property")
664
e.setAttribute(u"name", name)
665
e.setAttribute(u"type", prop._dbus_signature)
666
e.setAttribute(u"access", prop._dbus_access)
668
for if_tag in document.getElementsByTagName(u"interface"):
669
for tag in (make_tag(document, name, prop)
671
in self._get_all_dbus_properties()
672
if prop._dbus_interface
673
== if_tag.getAttribute(u"name")):
674
if_tag.appendChild(tag)
675
# Add the names to the return values for the
676
# "org.freedesktop.DBus.Properties" methods
677
if (if_tag.getAttribute(u"name")
678
== u"org.freedesktop.DBus.Properties"):
679
for cn in if_tag.getElementsByTagName(u"method"):
680
if cn.getAttribute(u"name") == u"Get":
681
for arg in cn.getElementsByTagName(u"arg"):
682
if (arg.getAttribute(u"direction")
684
arg.setAttribute(u"name", u"value")
685
elif cn.getAttribute(u"name") == u"GetAll":
686
for arg in cn.getElementsByTagName(u"arg"):
687
if (arg.getAttribute(u"direction")
689
arg.setAttribute(u"name", u"props")
690
xmlstring = document.toxml(u"utf-8")
692
except (AttributeError, xml.dom.DOMException,
693
xml.parsers.expat.ExpatError), error:
694
logger.error(u"Failed to override Introspection method",
699
class ClientDBus(Client, DBusObjectWithProperties):
700
"""A Client class using D-Bus
703
dbus_object_path: dbus.ObjectPath
704
bus: dbus.SystemBus()
706
# dbus.service.Object doesn't use super(), so we can't either.
708
def __init__(self, bus = None, *args, **kwargs):
709
self._approvals_pending = 0
711
Client.__init__(self, *args, **kwargs)
712
# Only now, when this client is initialized, can it show up on
714
self.dbus_object_path = (dbus.ObjectPath
716
+ self.name.replace(u".", u"_")))
717
DBusObjectWithProperties.__init__(self, self.bus,
718
self.dbus_object_path)
720
def _get_approvals_pending(self):
721
return self._approvals_pending
722
def _set_approvals_pending(self, value):
723
old_value = self._approvals_pending
724
self._approvals_pending = value
726
if (hasattr(self, "dbus_object_path")
727
and bval is not bool(old_value)):
728
dbus_bool = dbus.Boolean(bval, variant_level=1)
729
self.PropertyChanged(dbus.String(u"ApprovalPending"),
732
approvals_pending = property(_get_approvals_pending,
733
_set_approvals_pending)
734
del _get_approvals_pending, _set_approvals_pending
737
def _datetime_to_dbus(dt, variant_level=0):
738
"""Convert a UTC datetime.datetime() to a D-Bus type."""
739
return dbus.String(dt.isoformat(),
740
variant_level=variant_level)
743
oldstate = getattr(self, u"enabled", False)
744
r = Client.enable(self)
745
if oldstate != self.enabled:
747
self.PropertyChanged(dbus.String(u"Enabled"),
748
dbus.Boolean(True, variant_level=1))
749
self.PropertyChanged(
750
dbus.String(u"LastEnabled"),
751
self._datetime_to_dbus(self.last_enabled,
755
def disable(self, quiet = False):
756
oldstate = getattr(self, u"enabled", False)
757
r = Client.disable(self, quiet=quiet)
758
if not quiet and oldstate != self.enabled:
760
self.PropertyChanged(dbus.String(u"Enabled"),
761
dbus.Boolean(False, variant_level=1))
764
def __del__(self, *args, **kwargs):
766
self.remove_from_connection()
769
if hasattr(DBusObjectWithProperties, u"__del__"):
770
DBusObjectWithProperties.__del__(self, *args, **kwargs)
771
Client.__del__(self, *args, **kwargs)
773
def checker_callback(self, pid, condition, command,
775
self.checker_callback_tag = None
778
self.PropertyChanged(dbus.String(u"CheckerRunning"),
457
779
dbus.Boolean(False, variant_level=1))
459
def still_valid(self):
460
"""Has the timeout not yet passed for this client?"""
461
if not getattr(self, "started", False):
463
now = datetime.datetime.utcnow()
464
if self.last_checked_ok is None:
465
return now < (self.created + self.timeout)
467
return now < (self.last_checked_ok + self.timeout)
469
## D-Bus methods & signals
470
_interface = u"org.mandos_system.Mandos.Client"
472
# BumpTimeout - method
473
BumpTimeout = dbus.service.method(_interface)(bump_timeout)
474
BumpTimeout.__name__ = "BumpTimeout"
780
if os.WIFEXITED(condition):
781
exitstatus = os.WEXITSTATUS(condition)
783
self.CheckerCompleted(dbus.Int16(exitstatus),
784
dbus.Int64(condition),
785
dbus.String(command))
788
self.CheckerCompleted(dbus.Int16(-1),
789
dbus.Int64(condition),
790
dbus.String(command))
792
return Client.checker_callback(self, pid, condition, command,
795
def checked_ok(self, *args, **kwargs):
796
r = Client.checked_ok(self, *args, **kwargs)
798
self.PropertyChanged(
799
dbus.String(u"LastCheckedOK"),
800
(self._datetime_to_dbus(self.last_checked_ok,
804
def start_checker(self, *args, **kwargs):
805
old_checker = self.checker
806
if self.checker is not None:
807
old_checker_pid = self.checker.pid
809
old_checker_pid = None
810
r = Client.start_checker(self, *args, **kwargs)
811
# Only if new checker process was started
812
if (self.checker is not None
813
and old_checker_pid != self.checker.pid):
815
self.CheckerStarted(self.current_checker_command)
816
self.PropertyChanged(
817
dbus.String(u"CheckerRunning"),
818
dbus.Boolean(True, variant_level=1))
821
def stop_checker(self, *args, **kwargs):
822
old_checker = getattr(self, u"checker", None)
823
r = Client.stop_checker(self, *args, **kwargs)
824
if (old_checker is not None
825
and getattr(self, u"checker", None) is None):
826
self.PropertyChanged(dbus.String(u"CheckerRunning"),
827
dbus.Boolean(False, variant_level=1))
830
def _reset_approved(self):
831
self._approved = None
834
def approve(self, value=True):
835
self.send_changedstate()
836
self._approved = value
837
gobject.timeout_add(self._timedelta_to_milliseconds
838
(self.approval_duration),
839
self._reset_approved)
842
## D-Bus methods, signals & properties
843
_interface = u"se.bsnet.fukt.Mandos.Client"
476
847
# CheckerCompleted - signal
477
@dbus.service.signal(_interface, signature="bqs")
478
def CheckerCompleted(self, success, condition, command):
848
@dbus.service.signal(_interface, signature=u"nxs")
849
def CheckerCompleted(self, exitcode, waitstatus, command):
482
853
# CheckerStarted - signal
483
@dbus.service.signal(_interface, signature="s")
854
@dbus.service.signal(_interface, signature=u"s")
484
855
def CheckerStarted(self, command):
488
# GetAllProperties - method
489
@dbus.service.method(_interface, out_signature="a{sv}")
490
def GetAllProperties(self):
492
return dbus.Dictionary({
494
dbus.String(self.name, variant_level=1),
495
dbus.String("fingerprint"):
496
dbus.String(self.fingerprint, variant_level=1),
498
dbus.String(self.host, variant_level=1),
499
dbus.String("created"):
500
_datetime_to_dbus_struct(self.created,
502
dbus.String("last_started"):
503
(_datetime_to_dbus_struct(self.last_started,
505
if self.last_started is not None
506
else dbus.Boolean(False, variant_level=1)),
507
dbus.String("started"):
508
dbus.Boolean(self.started, variant_level=1),
509
dbus.String("last_checked_ok"):
510
(_datetime_to_dbus_struct(self.last_checked_ok,
512
if self.last_checked_ok is not None
513
else dbus.Boolean (False, variant_level=1)),
514
dbus.String("timeout"):
515
dbus.UInt64(self._timeout_milliseconds,
517
dbus.String("interval"):
518
dbus.UInt64(self._interval_milliseconds,
520
dbus.String("checker"):
521
dbus.String(self.checker_command,
523
dbus.String("checker_running"):
524
dbus.Boolean(self.checker is not None,
528
# IsStillValid - method
529
IsStillValid = (dbus.service.method(_interface, out_signature="b")
531
IsStillValid.__name__ = "IsStillValid"
533
859
# PropertyChanged - signal
534
@dbus.service.signal(_interface, signature="sv")
860
@dbus.service.signal(_interface, signature=u"sv")
535
861
def PropertyChanged(self, property, value):
539
# SetChecker - method
540
@dbus.service.method(_interface, in_signature="s")
541
def SetChecker(self, checker):
542
"D-Bus setter method"
543
self.checker_command = checker
546
@dbus.service.method(_interface, in_signature="s")
547
def SetHost(self, host):
548
"D-Bus setter method"
551
# SetInterval - method
552
@dbus.service.method(_interface, in_signature="t")
553
def SetInterval(self, milliseconds):
554
self.interval = datetime.timdeelta(0, 0, 0, milliseconds)
557
@dbus.service.method(_interface, in_signature="ay",
559
def SetSecret(self, secret):
560
"D-Bus setter method"
561
self.secret = str(secret)
563
# SetTimeout - method
564
@dbus.service.method(_interface, in_signature="t")
565
def SetTimeout(self, milliseconds):
566
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
569
Start = dbus.service.method(_interface)(start)
570
Start.__name__ = "Start"
866
@dbus.service.signal(_interface)
869
Is sent after a successful transfer of secret from the Mandos
870
server to mandos-client
875
@dbus.service.signal(_interface, signature=u"s")
876
def Rejected(self, reason):
880
# NeedApproval - signal
881
@dbus.service.signal(_interface, signature=u"tb")
882
def NeedApproval(self, timeout, default):
889
@dbus.service.method(_interface, in_signature=u"b")
890
def Approve(self, value):
894
@dbus.service.method(_interface)
896
return self.checked_ok()
899
@dbus.service.method(_interface)
572
904
# StartChecker - method
573
905
@dbus.service.method(_interface)
576
908
self.start_checker()
579
911
@dbus.service.method(_interface)
584
916
# StopChecker - method
585
StopChecker = dbus.service.method(_interface)(stop_checker)
586
StopChecker.__name__ = "StopChecker"
917
@dbus.service.method(_interface)
918
def StopChecker(self):
923
# ApprovalPending - property
924
@dbus_service_property(_interface, signature=u"b", access=u"read")
925
def ApprovalPending_dbus_property(self):
926
return dbus.Boolean(bool(self.approvals_pending))
928
# ApprovedByDefault - property
929
@dbus_service_property(_interface, signature=u"b",
931
def ApprovedByDefault_dbus_property(self, value=None):
932
if value is None: # get
933
return dbus.Boolean(self.approved_by_default)
934
self.approved_by_default = bool(value)
936
self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
937
dbus.Boolean(value, variant_level=1))
939
# ApprovalDelay - property
940
@dbus_service_property(_interface, signature=u"t",
942
def ApprovalDelay_dbus_property(self, value=None):
943
if value is None: # get
944
return dbus.UInt64(self.approval_delay_milliseconds())
945
self.approval_delay = datetime.timedelta(0, 0, 0, value)
947
self.PropertyChanged(dbus.String(u"ApprovalDelay"),
948
dbus.UInt64(value, variant_level=1))
950
# ApprovalDuration - property
951
@dbus_service_property(_interface, signature=u"t",
953
def ApprovalDuration_dbus_property(self, value=None):
954
if value is None: # get
955
return dbus.UInt64(self._timedelta_to_milliseconds(
956
self.approval_duration))
957
self.approval_duration = datetime.timedelta(0, 0, 0, value)
959
self.PropertyChanged(dbus.String(u"ApprovalDuration"),
960
dbus.UInt64(value, variant_level=1))
963
@dbus_service_property(_interface, signature=u"s", access=u"read")
964
def Name_dbus_property(self):
965
return dbus.String(self.name)
967
# Fingerprint - property
968
@dbus_service_property(_interface, signature=u"s", access=u"read")
969
def Fingerprint_dbus_property(self):
970
return dbus.String(self.fingerprint)
973
@dbus_service_property(_interface, signature=u"s",
975
def Host_dbus_property(self, value=None):
976
if value is None: # get
977
return dbus.String(self.host)
980
self.PropertyChanged(dbus.String(u"Host"),
981
dbus.String(value, variant_level=1))
984
@dbus_service_property(_interface, signature=u"s", access=u"read")
985
def Created_dbus_property(self):
986
return dbus.String(self._datetime_to_dbus(self.created))
988
# LastEnabled - property
989
@dbus_service_property(_interface, signature=u"s", access=u"read")
990
def LastEnabled_dbus_property(self):
991
if self.last_enabled is None:
992
return dbus.String(u"")
993
return dbus.String(self._datetime_to_dbus(self.last_enabled))
996
@dbus_service_property(_interface, signature=u"b",
998
def Enabled_dbus_property(self, value=None):
999
if value is None: # get
1000
return dbus.Boolean(self.enabled)
1006
# LastCheckedOK - property
1007
@dbus_service_property(_interface, signature=u"s",
1008
access=u"readwrite")
1009
def LastCheckedOK_dbus_property(self, value=None):
1010
if value is not None:
1013
if self.last_checked_ok is None:
1014
return dbus.String(u"")
1015
return dbus.String(self._datetime_to_dbus(self
1018
# Timeout - property
1019
@dbus_service_property(_interface, signature=u"t",
1020
access=u"readwrite")
1021
def Timeout_dbus_property(self, value=None):
1022
if value is None: # get
1023
return dbus.UInt64(self.timeout_milliseconds())
1024
self.timeout = datetime.timedelta(0, 0, 0, value)
1026
self.PropertyChanged(dbus.String(u"Timeout"),
1027
dbus.UInt64(value, variant_level=1))
1028
if getattr(self, u"disable_initiator_tag", None) is None:
1030
# Reschedule timeout
1031
gobject.source_remove(self.disable_initiator_tag)
1032
self.disable_initiator_tag = None
1033
time_to_die = (self.
1034
_timedelta_to_milliseconds((self
1039
if time_to_die <= 0:
1040
# The timeout has passed
1043
self.disable_initiator_tag = (gobject.timeout_add
1044
(time_to_die, self.disable))
1046
# Interval - property
1047
@dbus_service_property(_interface, signature=u"t",
1048
access=u"readwrite")
1049
def Interval_dbus_property(self, value=None):
1050
if value is None: # get
1051
return dbus.UInt64(self.interval_milliseconds())
1052
self.interval = datetime.timedelta(0, 0, 0, value)
1054
self.PropertyChanged(dbus.String(u"Interval"),
1055
dbus.UInt64(value, variant_level=1))
1056
if getattr(self, u"checker_initiator_tag", None) is None:
1058
# Reschedule checker run
1059
gobject.source_remove(self.checker_initiator_tag)
1060
self.checker_initiator_tag = (gobject.timeout_add
1061
(value, self.start_checker))
1062
self.start_checker() # Start one now, too
1064
# Checker - property
1065
@dbus_service_property(_interface, signature=u"s",
1066
access=u"readwrite")
1067
def Checker_dbus_property(self, value=None):
1068
if value is None: # get
1069
return dbus.String(self.checker_command)
1070
self.checker_command = value
1072
self.PropertyChanged(dbus.String(u"Checker"),
1073
dbus.String(self.checker_command,
1076
# CheckerRunning - property
1077
@dbus_service_property(_interface, signature=u"b",
1078
access=u"readwrite")
1079
def CheckerRunning_dbus_property(self, value=None):
1080
if value is None: # get
1081
return dbus.Boolean(self.checker is not None)
1083
self.start_checker()
1087
# ObjectPath - property
1088
@dbus_service_property(_interface, signature=u"o", access=u"read")
1089
def ObjectPath_dbus_property(self):
1090
return self.dbus_object_path # is already a dbus.ObjectPath
1093
@dbus_service_property(_interface, signature=u"ay",
1094
access=u"write", byte_arrays=True)
1095
def Secret_dbus_property(self, value):
1096
self.secret = str(value)
591
def peer_certificate(session):
592
"Return the peer's OpenPGP certificate as a bytestring"
593
# If not an OpenPGP certificate...
594
if (gnutls.library.functions
595
.gnutls_certificate_type_get(session._c_object)
596
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
597
# ...do the normal thing
598
return session.peer_certificate
599
list_size = ctypes.c_uint()
600
cert_list = (gnutls.library.functions
601
.gnutls_certificate_get_peers
602
(session._c_object, ctypes.byref(list_size)))
603
if list_size.value == 0:
606
return ctypes.string_at(cert.data, cert.size)
609
def fingerprint(openpgp):
610
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
611
# New GnuTLS "datum" with the OpenPGP public key
612
datum = (gnutls.library.types
613
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
616
ctypes.c_uint(len(openpgp))))
617
# New empty GnuTLS certificate
618
crt = gnutls.library.types.gnutls_openpgp_crt_t()
619
(gnutls.library.functions
620
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
621
# Import the OpenPGP public key into the certificate
622
(gnutls.library.functions
623
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
624
gnutls.library.constants
625
.GNUTLS_OPENPGP_FMT_RAW))
626
# Verify the self signature in the key
627
crtverify = ctypes.c_uint()
628
(gnutls.library.functions
629
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
630
if crtverify.value != 0:
631
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
632
raise gnutls.errors.CertificateSecurityError("Verify failed")
633
# New buffer for the fingerprint
634
buf = ctypes.create_string_buffer(20)
635
buf_len = ctypes.c_size_t()
636
# Get the fingerprint from the certificate into the buffer
637
(gnutls.library.functions
638
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
639
ctypes.byref(buf_len)))
640
# Deinit the certificate
641
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
642
# Convert the buffer to a Python bytestring
643
fpr = ctypes.string_at(buf, buf_len.value)
644
# Convert the bytestring to hexadecimal notation
645
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
649
class TCP_handler(SocketServer.BaseRequestHandler, object):
650
"""A TCP request handler class.
651
Instantiated by IPv6_TCPServer for each request to handle it.
1101
class ProxyClient(object):
1102
def __init__(self, child_pipe, fpr, address):
1103
self._pipe = child_pipe
1104
self._pipe.send(('init', fpr, address))
1105
if not self._pipe.recv():
1108
def __getattribute__(self, name):
1109
if(name == '_pipe'):
1110
return super(ProxyClient, self).__getattribute__(name)
1111
self._pipe.send(('getattr', name))
1112
data = self._pipe.recv()
1113
if data[0] == 'data':
1115
if data[0] == 'function':
1116
def func(*args, **kwargs):
1117
self._pipe.send(('funcall', name, args, kwargs))
1118
return self._pipe.recv()[1]
1121
def __setattr__(self, name, value):
1122
if(name == '_pipe'):
1123
return super(ProxyClient, self).__setattr__(name, value)
1124
self._pipe.send(('setattr', name, value))
1127
class ClientHandler(socketserver.BaseRequestHandler, object):
1128
"""A class to handle client connections.
1130
Instantiated once for each connection to handle it.
652
1131
Note: This will run in its own forked process."""
654
1133
def handle(self):
655
logger.info(u"TCP connection from: %s",
656
unicode(self.client_address))
657
session = (gnutls.connection
658
.ClientSession(self.request,
662
line = self.request.makefile().readline()
663
logger.debug(u"Protocol version: %r", line)
665
if int(line.strip().split()[0]) > 1:
667
except (ValueError, IndexError, RuntimeError), error:
668
logger.error(u"Unknown protocol version: %s", error)
671
# Note: gnutls.connection.X509Credentials is really a generic
672
# GnuTLS certificate credentials object so long as no X.509
673
# keys are added to it. Therefore, we can use it here despite
674
# using OpenPGP certificates.
676
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
677
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
679
# Use a fallback default, since this MUST be set.
680
priority = self.server.settings.get("priority", "NORMAL")
681
(gnutls.library.functions
682
.gnutls_priority_set_direct(session._c_object,
687
except gnutls.errors.GNUTLSError, error:
688
logger.warning(u"Handshake failed: %s", error)
689
# Do not run session.bye() here: the session is not
690
# established. Just abandon the request.
693
fpr = fingerprint(peer_certificate(session))
694
except (TypeError, gnutls.errors.GNUTLSError), error:
695
logger.warning(u"Bad certificate: %s", error)
698
logger.debug(u"Fingerprint: %s", fpr)
699
for c in self.server.clients:
700
if c.fingerprint == fpr:
704
logger.warning(u"Client not found for fingerprint: %s",
708
# Have to check if client.still_valid(), since it is possible
709
# that the client timed out while establishing the GnuTLS
711
if not client.still_valid():
712
logger.warning(u"Client %(name)s is invalid",
716
## This won't work here, since we're in a fork.
717
# client.bump_timeout()
719
while sent_size < len(client.secret):
720
sent = session.send(client.secret[sent_size:])
721
logger.debug(u"Sent: %d, remaining: %d",
722
sent, len(client.secret)
723
- (sent_size + sent))
728
class IPv6_TCPServer(SocketServer.ForkingMixIn,
729
SocketServer.TCPServer, object):
730
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1134
with contextlib.closing(self.server.child_pipe) as child_pipe:
1135
logger.info(u"TCP connection from: %s",
1136
unicode(self.client_address))
1137
logger.debug(u"Pipe FD: %d",
1138
self.server.child_pipe.fileno())
1140
session = (gnutls.connection
1141
.ClientSession(self.request,
1143
.X509Credentials()))
1145
# Note: gnutls.connection.X509Credentials is really a
1146
# generic GnuTLS certificate credentials object so long as
1147
# no X.509 keys are added to it. Therefore, we can use it
1148
# here despite using OpenPGP certificates.
1150
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1151
# u"+AES-256-CBC", u"+SHA1",
1152
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1154
# Use a fallback default, since this MUST be set.
1155
priority = self.server.gnutls_priority
1156
if priority is None:
1157
priority = u"NORMAL"
1158
(gnutls.library.functions
1159
.gnutls_priority_set_direct(session._c_object,
1162
# Start communication using the Mandos protocol
1163
# Get protocol number
1164
line = self.request.makefile().readline()
1165
logger.debug(u"Protocol version: %r", line)
1167
if int(line.strip().split()[0]) > 1:
1169
except (ValueError, IndexError, RuntimeError), error:
1170
logger.error(u"Unknown protocol version: %s", error)
1173
# Start GnuTLS connection
1176
except gnutls.errors.GNUTLSError, error:
1177
logger.warning(u"Handshake failed: %s", error)
1178
# Do not run session.bye() here: the session is not
1179
# established. Just abandon the request.
1181
logger.debug(u"Handshake succeeded")
1183
approval_required = False
1186
fpr = self.fingerprint(self.peer_certificate
1188
except (TypeError, gnutls.errors.GNUTLSError), error:
1189
logger.warning(u"Bad certificate: %s", error)
1191
logger.debug(u"Fingerprint: %s", fpr)
1194
client = ProxyClient(child_pipe, fpr,
1195
self.client_address)
1199
if client.approval_delay:
1200
delay = client.approval_delay
1201
client.approvals_pending += 1
1202
approval_required = True
1205
if not client.enabled:
1206
logger.warning(u"Client %s is disabled",
1208
if self.server.use_dbus:
1210
client.Rejected("Disabled")
1213
if client._approved or not client.approval_delay:
1214
#We are approved or approval is disabled
1216
elif client._approved is None:
1217
logger.info(u"Client %s needs approval",
1219
if self.server.use_dbus:
1221
client.NeedApproval(
1222
client.approval_delay_milliseconds(),
1223
client.approved_by_default)
1225
logger.warning(u"Client %s was not approved",
1227
if self.server.use_dbus:
1229
client.Rejected("Denied")
1232
#wait until timeout or approved
1233
#x = float(client._timedelta_to_milliseconds(delay))
1234
time = datetime.datetime.now()
1235
client.changedstate.acquire()
1236
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1237
client.changedstate.release()
1238
time2 = datetime.datetime.now()
1239
if (time2 - time) >= delay:
1240
if not client.approved_by_default:
1241
logger.warning("Client %s timed out while"
1242
" waiting for approval",
1244
if self.server.use_dbus:
1246
client.Rejected("Timed out")
1251
delay -= time2 - time
1254
while sent_size < len(client.secret):
1256
sent = session.send(client.secret[sent_size:])
1257
except (gnutls.errors.GNUTLSError), error:
1258
logger.warning("gnutls send failed")
1260
logger.debug(u"Sent: %d, remaining: %d",
1261
sent, len(client.secret)
1262
- (sent_size + sent))
1265
logger.info(u"Sending secret to %s", client.name)
1266
# bump the timeout as if seen
1268
if self.server.use_dbus:
1273
if approval_required:
1274
client.approvals_pending -= 1
1277
except (gnutls.errors.GNUTLSError), error:
1278
logger.warning("GnuTLS bye failed")
1281
def peer_certificate(session):
1282
"Return the peer's OpenPGP certificate as a bytestring"
1283
# If not an OpenPGP certificate...
1284
if (gnutls.library.functions
1285
.gnutls_certificate_type_get(session._c_object)
1286
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1287
# ...do the normal thing
1288
return session.peer_certificate
1289
list_size = ctypes.c_uint(1)
1290
cert_list = (gnutls.library.functions
1291
.gnutls_certificate_get_peers
1292
(session._c_object, ctypes.byref(list_size)))
1293
if not bool(cert_list) and list_size.value != 0:
1294
raise gnutls.errors.GNUTLSError(u"error getting peer"
1296
if list_size.value == 0:
1299
return ctypes.string_at(cert.data, cert.size)
1302
def fingerprint(openpgp):
1303
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1304
# New GnuTLS "datum" with the OpenPGP public key
1305
datum = (gnutls.library.types
1306
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1309
ctypes.c_uint(len(openpgp))))
1310
# New empty GnuTLS certificate
1311
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1312
(gnutls.library.functions
1313
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1314
# Import the OpenPGP public key into the certificate
1315
(gnutls.library.functions
1316
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1317
gnutls.library.constants
1318
.GNUTLS_OPENPGP_FMT_RAW))
1319
# Verify the self signature in the key
1320
crtverify = ctypes.c_uint()
1321
(gnutls.library.functions
1322
.gnutls_openpgp_crt_verify_self(crt, 0,
1323
ctypes.byref(crtverify)))
1324
if crtverify.value != 0:
1325
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1326
raise (gnutls.errors.CertificateSecurityError
1328
# New buffer for the fingerprint
1329
buf = ctypes.create_string_buffer(20)
1330
buf_len = ctypes.c_size_t()
1331
# Get the fingerprint from the certificate into the buffer
1332
(gnutls.library.functions
1333
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1334
ctypes.byref(buf_len)))
1335
# Deinit the certificate
1336
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1337
# Convert the buffer to a Python bytestring
1338
fpr = ctypes.string_at(buf, buf_len.value)
1339
# Convert the bytestring to hexadecimal notation
1340
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1344
class MultiprocessingMixIn(object):
1345
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1346
def sub_process_main(self, request, address):
1348
self.finish_request(request, address)
1350
self.handle_error(request, address)
1351
self.close_request(request)
1353
def process_request(self, request, address):
1354
"""Start a new process to process the request."""
1355
multiprocessing.Process(target = self.sub_process_main,
1356
args = (request, address)).start()
1358
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1359
""" adds a pipe to the MixIn """
1360
def process_request(self, request, client_address):
1361
"""Overrides and wraps the original process_request().
1363
This function creates a new pipe in self.pipe
1365
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1367
super(MultiprocessingMixInWithPipe,
1368
self).process_request(request, client_address)
1369
self.child_pipe.close()
1370
self.add_pipe(parent_pipe)
1372
def add_pipe(self, parent_pipe):
1373
"""Dummy function; override as necessary"""
1376
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1377
socketserver.TCPServer, object):
1378
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
732
settings: Server settings
733
clients: Set() of Client objects
734
1381
enabled: Boolean; whether this server is activated yet
1382
interface: None or a network interface name (string)
1383
use_ipv6: Boolean; to use IPv6 or not
736
address_family = socket.AF_INET6
737
def __init__(self, *args, **kwargs):
738
if "settings" in kwargs:
739
self.settings = kwargs["settings"]
740
del kwargs["settings"]
741
if "clients" in kwargs:
742
self.clients = kwargs["clients"]
743
del kwargs["clients"]
745
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1385
def __init__(self, server_address, RequestHandlerClass,
1386
interface=None, use_ipv6=True):
1387
self.interface = interface
1389
self.address_family = socket.AF_INET6
1390
socketserver.TCPServer.__init__(self, server_address,
1391
RequestHandlerClass)
746
1392
def server_bind(self):
747
1393
"""This overrides the normal server_bind() function
748
1394
to bind to an interface if one was specified, and also NOT to
749
1395
bind to an address or port if they were not specified."""
750
if self.settings["interface"]:
751
# 25 is from /usr/include/asm-i486/socket.h
752
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
754
self.socket.setsockopt(socket.SOL_SOCKET,
756
self.settings["interface"])
757
except socket.error, error:
758
if error[0] == errno.EPERM:
759
logger.error(u"No permission to"
760
u" bind to interface %s",
761
self.settings["interface"])
1396
if self.interface is not None:
1397
if SO_BINDTODEVICE is None:
1398
logger.error(u"SO_BINDTODEVICE does not exist;"
1399
u" cannot bind to interface %s",
1403
self.socket.setsockopt(socket.SOL_SOCKET,
1407
except socket.error, error:
1408
if error[0] == errno.EPERM:
1409
logger.error(u"No permission to"
1410
u" bind to interface %s",
1412
elif error[0] == errno.ENOPROTOOPT:
1413
logger.error(u"SO_BINDTODEVICE not available;"
1414
u" cannot bind to interface %s",
764
1418
# Only bind(2) the socket if we really need to.
765
1419
if self.server_address[0] or self.server_address[1]:
766
1420
if not self.server_address[0]:
768
self.server_address = (in6addr_any,
1421
if self.address_family == socket.AF_INET6:
1422
any_address = u"::" # in6addr_any
1424
any_address = socket.INADDR_ANY
1425
self.server_address = (any_address,
769
1426
self.server_address[1])
770
1427
elif not self.server_address[1]:
771
1428
self.server_address = (self.server_address[0],
773
# if self.settings["interface"]:
1430
# if self.interface:
774
1431
# self.server_address = (self.server_address[0],
777
1434
# if_nametoindex
780
return super(IPv6_TCPServer, self).server_bind()
1436
return socketserver.TCPServer.server_bind(self)
1439
class MandosServer(IPv6_TCPServer):
1443
clients: set of Client objects
1444
gnutls_priority GnuTLS priority string
1445
use_dbus: Boolean; to emit D-Bus signals or not
1447
Assumes a gobject.MainLoop event loop.
1449
def __init__(self, server_address, RequestHandlerClass,
1450
interface=None, use_ipv6=True, clients=None,
1451
gnutls_priority=None, use_dbus=True):
1452
self.enabled = False
1453
self.clients = clients
1454
if self.clients is None:
1455
self.clients = set()
1456
self.use_dbus = use_dbus
1457
self.gnutls_priority = gnutls_priority
1458
IPv6_TCPServer.__init__(self, server_address,
1459
RequestHandlerClass,
1460
interface = interface,
1461
use_ipv6 = use_ipv6)
781
1462
def server_activate(self):
782
1463
if self.enabled:
783
return super(IPv6_TCPServer, self).server_activate()
1464
return socketserver.TCPServer.server_activate(self)
784
1465
def enable(self):
785
1466
self.enabled = True
1467
def add_pipe(self, parent_pipe):
1468
# Call "handle_ipc" for both data and EOF events
1469
gobject.io_add_watch(parent_pipe.fileno(),
1470
gobject.IO_IN | gobject.IO_HUP,
1471
functools.partial(self.handle_ipc,
1472
parent_pipe = parent_pipe))
1474
def handle_ipc(self, source, condition, parent_pipe=None,
1475
client_object=None):
1477
gobject.IO_IN: u"IN", # There is data to read.
1478
gobject.IO_OUT: u"OUT", # Data can be written (without
1480
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1481
gobject.IO_ERR: u"ERR", # Error condition.
1482
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1483
# broken, usually for pipes and
1486
conditions_string = ' | '.join(name
1488
condition_names.iteritems()
1489
if cond & condition)
1490
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1493
# error or the other end of multiprocessing.Pipe has closed
1494
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1497
# Read a request from the child
1498
request = parent_pipe.recv()
1499
logger.debug(u"IPC request: %s", repr(request))
1500
command = request[0]
1502
if command == 'init':
1504
address = request[2]
1506
for c in self.clients:
1507
if c.fingerprint == fpr:
1511
logger.warning(u"Client not found for fingerprint: %s, ad"
1512
u"dress: %s", fpr, address)
1515
mandos_dbus_service.ClientNotFound(fpr, address)
1516
parent_pipe.send(False)
1519
gobject.io_add_watch(parent_pipe.fileno(),
1520
gobject.IO_IN | gobject.IO_HUP,
1521
functools.partial(self.handle_ipc,
1522
parent_pipe = parent_pipe,
1523
client_object = client))
1524
parent_pipe.send(True)
1525
# remove the old hook in favor of the new above hook on same fileno
1527
if command == 'funcall':
1528
funcname = request[1]
1532
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1534
if command == 'getattr':
1535
attrname = request[1]
1536
if callable(client_object.__getattribute__(attrname)):
1537
parent_pipe.send(('function',))
1539
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1541
if command == 'setattr':
1542
attrname = request[1]
1544
setattr(client_object, attrname, value)
788
1549
def string_to_delta(interval):
789
1550
"""Parse a string and return a datetime.timedelta
791
>>> string_to_delta('7d')
1552
>>> string_to_delta(u'7d')
792
1553
datetime.timedelta(7)
793
>>> string_to_delta('60s')
1554
>>> string_to_delta(u'60s')
794
1555
datetime.timedelta(0, 60)
795
>>> string_to_delta('60m')
1556
>>> string_to_delta(u'60m')
796
1557
datetime.timedelta(0, 3600)
797
>>> string_to_delta('24h')
1558
>>> string_to_delta(u'24h')
798
1559
datetime.timedelta(1)
799
1560
>>> string_to_delta(u'1w')
800
1561
datetime.timedelta(7)
801
>>> string_to_delta('5m 30s')
1562
>>> string_to_delta(u'5m 30s')
802
1563
datetime.timedelta(0, 330)
804
1565
timevalue = datetime.timedelta(0)
924
1676
# Default values for config file for server-global settings
925
server_defaults = { "interface": "",
930
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
931
"servicename": "Mandos",
1677
server_defaults = { u"interface": u"",
1682
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1683
u"servicename": u"Mandos",
1684
u"use_dbus": u"True",
1685
u"use_ipv6": u"True",
934
1689
# Parse config file for server-global settings
935
server_config = ConfigParser.SafeConfigParser(server_defaults)
1690
server_config = configparser.SafeConfigParser(server_defaults)
936
1691
del server_defaults
937
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1692
server_config.read(os.path.join(options.configdir,
938
1694
# Convert the SafeConfigParser object to a dict
939
1695
server_settings = server_config.defaults()
940
# Use getboolean on the boolean config option
941
server_settings["debug"] = (server_config.getboolean
942
("DEFAULT", "debug"))
1696
# Use the appropriate methods on the non-string config options
1697
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1698
server_settings[option] = server_config.getboolean(u"DEFAULT",
1700
if server_settings["port"]:
1701
server_settings["port"] = server_config.getint(u"DEFAULT",
943
1703
del server_config
945
1705
# Override the settings from the config file with command line
946
1706
# options, if set.
947
for option in ("interface", "address", "port", "debug",
948
"priority", "servicename", "configdir"):
1707
for option in (u"interface", u"address", u"port", u"debug",
1708
u"priority", u"servicename", u"configdir",
1709
u"use_dbus", u"use_ipv6", u"debuglevel"):
949
1710
value = getattr(options, option)
950
1711
if value is not None:
951
1712
server_settings[option] = value
1714
# Force all strings to be unicode
1715
for option in server_settings.keys():
1716
if type(server_settings[option]) is str:
1717
server_settings[option] = unicode(server_settings[option])
953
1718
# Now we have our good server settings in "server_settings"
955
debug = server_settings["debug"]
958
syslogger.setLevel(logging.WARNING)
959
console.setLevel(logging.WARNING)
961
if server_settings["servicename"] != "Mandos":
1720
##################################################################
1723
debug = server_settings[u"debug"]
1724
debuglevel = server_settings[u"debuglevel"]
1725
use_dbus = server_settings[u"use_dbus"]
1726
use_ipv6 = server_settings[u"use_ipv6"]
1728
if server_settings[u"servicename"] != u"Mandos":
962
1729
syslogger.setFormatter(logging.Formatter
963
('Mandos (%s): %%(levelname)s:'
965
% server_settings["servicename"]))
1730
(u'Mandos (%s) [%%(process)d]:'
1731
u' %%(levelname)s: %%(message)s'
1732
% server_settings[u"servicename"]))
967
1734
# Parse config file with clients
968
client_defaults = { "timeout": "1h",
970
"checker": "fping -q -- %(host)s",
1735
client_defaults = { u"timeout": u"1h",
1737
u"checker": u"fping -q -- %%(host)s",
1739
u"approval_delay": u"0s",
1740
u"approval_duration": u"1s",
973
client_config = ConfigParser.SafeConfigParser(client_defaults)
974
client_config.read(os.path.join(server_settings["configdir"],
978
tcp_server = IPv6_TCPServer((server_settings["address"],
979
server_settings["port"]),
981
settings=server_settings,
983
pidfilename = "/var/run/mandos.pid"
985
pidfile = open(pidfilename, "w")
986
except IOError, error:
987
logger.error("Could not open file %r", pidfilename)
990
uid = pwd.getpwnam("_mandos").pw_uid
1742
client_config = configparser.SafeConfigParser(client_defaults)
1743
client_config.read(os.path.join(server_settings[u"configdir"],
1746
global mandos_dbus_service
1747
mandos_dbus_service = None
1749
tcp_server = MandosServer((server_settings[u"address"],
1750
server_settings[u"port"]),
1752
interface=(server_settings[u"interface"]
1756
server_settings[u"priority"],
1758
pidfilename = u"/var/run/mandos.pid"
1760
pidfile = open(pidfilename, u"w")
1762
logger.error(u"Could not open file %r", pidfilename)
1765
uid = pwd.getpwnam(u"_mandos").pw_uid
1766
gid = pwd.getpwnam(u"_mandos").pw_gid
991
1767
except KeyError:
993
uid = pwd.getpwnam("mandos").pw_uid
1769
uid = pwd.getpwnam(u"mandos").pw_uid
1770
gid = pwd.getpwnam(u"mandos").pw_gid
994
1771
except KeyError:
996
uid = pwd.getpwnam("nobody").pw_uid
1773
uid = pwd.getpwnam(u"nobody").pw_uid
1774
gid = pwd.getpwnam(u"nobody").pw_gid
997
1775
except KeyError:
1000
gid = pwd.getpwnam("_mandos").pw_gid
1003
gid = pwd.getpwnam("mandos").pw_gid
1006
gid = pwd.getpwnam("nogroup").pw_gid
1012
1781
except OSError, error:
1013
1782
if error[0] != errno.EPERM:
1017
service = AvahiService(name = server_settings["servicename"],
1018
servicetype = "_mandos._tcp", )
1019
if server_settings["interface"]:
1020
service.interface = (if_nametoindex
1021
(server_settings["interface"]))
1785
if not debug and not debuglevel:
1786
syslogger.setLevel(logging.WARNING)
1787
console.setLevel(logging.WARNING)
1789
level = getattr(logging, debuglevel.upper())
1790
syslogger.setLevel(level)
1791
console.setLevel(level)
1794
# Enable all possible GnuTLS debugging
1796
# "Use a log level over 10 to enable all debugging options."
1798
gnutls.library.functions.gnutls_global_set_log_level(11)
1800
@gnutls.library.types.gnutls_log_func
1801
def debug_gnutls(level, string):
1802
logger.debug(u"GnuTLS: %s", string[:-1])
1804
(gnutls.library.functions
1805
.gnutls_global_set_log_function(debug_gnutls))
1807
# Redirect stdin so all checkers get /dev/null
1808
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1809
os.dup2(null, sys.stdin.fileno())
1813
# No console logging
1814
logger.removeHandler(console)
1023
1817
global main_loop
1026
1818
# From the Avahi example code
1027
1819
DBusGMainLoop(set_as_default=True )
1028
1820
main_loop = gobject.MainLoop()
1029
1821
bus = dbus.SystemBus()
1030
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1031
avahi.DBUS_PATH_SERVER),
1032
avahi.DBUS_INTERFACE_SERVER)
1033
1822
# End of Avahi example code
1034
bus_name = dbus.service.BusName(u"org.mandos-system.Mandos", bus)
1036
clients.update(Set(Client(name = section,
1038
= dict(client_config.items(section)))
1039
for section in client_config.sections()))
1041
logger.critical(u"No clients defined")
1045
# Redirect stdin so all checkers get /dev/null
1046
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1047
os.dup2(null, sys.stdin.fileno())
1051
# No console logging
1052
logger.removeHandler(console)
1825
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1826
bus, do_not_queue=True)
1827
except dbus.exceptions.NameExistsException, e:
1828
logger.error(unicode(e) + u", disabling D-Bus")
1830
server_settings[u"use_dbus"] = False
1831
tcp_server.use_dbus = False
1832
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1833
service = AvahiService(name = server_settings[u"servicename"],
1834
servicetype = u"_mandos._tcp",
1835
protocol = protocol, bus = bus)
1836
if server_settings["interface"]:
1837
service.interface = (if_nametoindex
1838
(str(server_settings[u"interface"])))
1053
1841
# Close all input and output, do double fork, etc.
1844
global multiprocessing_manager
1845
multiprocessing_manager = multiprocessing.Manager()
1847
client_class = Client
1849
client_class = functools.partial(ClientDBus, bus = bus)
1850
def client_config_items(config, section):
1851
special_settings = {
1852
"approved_by_default":
1853
lambda: config.getboolean(section,
1854
"approved_by_default"),
1856
for name, value in config.items(section):
1858
yield (name, special_settings[name]())
1862
tcp_server.clients.update(set(
1863
client_class(name = section,
1864
config= dict(client_config_items(
1865
client_config, section)))
1866
for section in client_config.sections()))
1867
if not tcp_server.clients:
1868
logger.warning(u"No clients defined")
1058
pidfile.write(str(pid) + "\n")
1873
pidfile.write(str(pid) + "\n")
1061
1875
except IOError:
1062
1876
logger.error(u"Could not write to file %r with PID %d",