147
124
self.rename_count = 0
148
125
self.max_renames = max_renames
149
self.protocol = protocol
150
self.group = None # our entry group
153
126
def rename(self):
154
127
"""Derived from the Avahi example code"""
155
128
if self.rename_count >= self.max_renames:
156
129
logger.critical(u"No suitable Zeroconf service name found"
157
130
u" after %i retries, exiting.",
158
131
self.rename_count)
159
raise AvahiServiceError(u"Too many renames")
160
self.name = unicode(self.server.GetAlternativeServiceName(self.name))
132
raise AvahiServiceError("Too many renames")
133
self.name = server.GetAlternativeServiceName(self.name)
161
134
logger.info(u"Changing Zeroconf service name to %r ...",
163
136
syslogger.setFormatter(logging.Formatter
164
(u'Mandos (%s) [%%(process)d]:'
165
u' %%(levelname)s: %%(message)s'
137
('Mandos (%s): %%(levelname)s:'
138
' %%(message)s' % self.name))
169
141
self.rename_count += 1
170
142
def remove(self):
171
143
"""Derived from the Avahi example code"""
172
if self.group is not None:
144
if group is not None:
175
147
"""Derived from the Avahi example code"""
176
if self.group is None:
177
self.group = dbus.Interface(
178
self.bus.get_object(avahi.DBUS_NAME,
179
self.server.EntryGroupNew()),
180
avahi.DBUS_INTERFACE_ENTRY_GROUP)
181
self.group.connect_to_signal('StateChanged',
183
.entry_group_state_changed)
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)
184
156
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
185
self.name, self.type)
186
self.group.AddService(
189
dbus.UInt32(0), # flags
190
self.name, self.type,
191
self.domain, self.host,
192
dbus.UInt16(self.port),
193
avahi.string_array_to_txt_array(self.TXT))
195
def entry_group_state_changed(self, state, error):
196
"""Derived from the Avahi example code"""
197
logger.debug(u"Avahi state change: %i", state)
199
if state == avahi.ENTRY_GROUP_ESTABLISHED:
200
logger.debug(u"Zeroconf service established.")
201
elif state == avahi.ENTRY_GROUP_COLLISION:
202
logger.warning(u"Zeroconf service name collision.")
204
elif state == avahi.ENTRY_GROUP_FAILURE:
205
logger.critical(u"Avahi: Error in group state changed %s",
207
raise AvahiGroupError(u"State changed: %s"
210
"""Derived from the Avahi example code"""
211
if self.group is not None:
214
def server_state_changed(self, state):
215
"""Derived from the Avahi example code"""
216
if state == avahi.SERVER_COLLISION:
217
logger.error(u"Zeroconf server name collision")
219
elif state == avahi.SERVER_RUNNING:
222
"""Derived from the Avahi example code"""
223
if self.server is None:
224
self.server = dbus.Interface(
225
self.bus.get_object(avahi.DBUS_NAME,
226
avahi.DBUS_PATH_SERVER),
227
avahi.DBUS_INTERFACE_SERVER)
228
self.server.connect_to_signal(u"StateChanged",
229
self.server_state_changed)
230
self.server_state_changed(self.server.GetState())
233
class Client(object):
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
class Client(dbus.service.Object):
234
174
"""A representation of a client host served by this server.
237
name: string; from the config file, used in log messages and
176
name: string; from the config file, used in log messages
239
177
fingerprint: string (40 or 32 hexadecimal digits); used to
240
178
uniquely identify the client
241
secret: bytestring; sent verbatim (over TLS) to client
242
host: string; available for use by the checker command
243
created: datetime.datetime(); (UTC) object creation
244
last_enabled: datetime.datetime(); (UTC)
179
secret: bytestring; sent verbatim (over TLS) to client
180
host: string; available for use by the checker command
181
created: datetime.datetime(); (UTC) object creation
182
started: datetime.datetime(); (UTC) last started
246
183
last_checked_ok: datetime.datetime(); (UTC) or None
247
timeout: datetime.timedelta(); How long from last_checked_ok
248
until this client is disabled
249
interval: datetime.timedelta(); How often to start a new checker
250
disable_hook: If set, called by disable() as disable_hook(self)
251
checker: subprocess.Popen(); a running checker process used
252
to see if the client lives.
253
'None' if no process is running.
184
timeout: datetime.timedelta(); How long from last_checked_ok
185
until this client is invalid
186
interval: datetime.timedelta(); How often to start a new checker
187
stop_hook: If set, called by stop() as stop_hook(self)
188
checker: subprocess.Popen(); a running checker process used
189
to see if the client lives.
190
'None' if no process is running.
254
191
checker_initiator_tag: a gobject event source tag, or None
255
disable_initiator_tag: - '' -
192
stop_initiator_tag: - '' -
256
193
checker_callback_tag: - '' -
257
194
checker_command: string; External command which is run to check if
258
195
client lives. %() expansions are done at
259
196
runtime with vars(self) as dict, so that for
260
197
instance %(name)s can be used in the command.
261
current_checker_command: string; current running checker_command
262
approved_delay: datetime.timedelta(); Time to wait for approval
263
_approved: bool(); 'None' if not yet approved/disapproved
264
approved_duration: datetime.timedelta(); Duration of one approval
199
_timeout: Real variable for 'timeout'
200
_interval: Real variable for 'interval'
201
_timeout_milliseconds: Used when calling gobject.timeout_add()
202
_interval_milliseconds: - '' -
268
def _timedelta_to_milliseconds(td):
269
"Convert a datetime.timedelta() to milliseconds"
270
return ((td.days * 24 * 60 * 60 * 1000)
271
+ (td.seconds * 1000)
272
+ (td.microseconds // 1000))
274
def timeout_milliseconds(self):
275
"Return the 'timeout' attribute in milliseconds"
276
return self._timedelta_to_milliseconds(self.timeout)
278
def interval_milliseconds(self):
279
"Return the 'interval' attribute in milliseconds"
280
return self._timedelta_to_milliseconds(self.interval)
282
def approved_delay_milliseconds(self):
283
return self._timedelta_to_milliseconds(self.approved_delay)
285
def __init__(self, name = None, disable_hook=None, config=None):
204
def _set_timeout(self, timeout):
205
"Setter function for the 'timeout' attribute"
206
self._timeout = timeout
207
self._timeout_milliseconds = ((self.timeout.days
208
* 24 * 60 * 60 * 1000)
209
+ (self.timeout.seconds * 1000)
210
+ (self.timeout.microseconds
213
self.TimeoutChanged(self._timeout_milliseconds)
214
timeout = property(lambda self: self._timeout, _set_timeout)
217
def _set_interval(self, interval):
218
"Setter function for the 'interval' attribute"
219
self._interval = interval
220
self._interval_milliseconds = ((self.interval.days
221
* 24 * 60 * 60 * 1000)
222
+ (self.interval.seconds
224
+ (self.interval.microseconds
227
self.IntervalChanged(self._interval_milliseconds)
228
interval = property(lambda self: self._interval, _set_interval)
231
def __init__(self, name = None, stop_hook=None, config=None):
286
232
"""Note: the 'checker' key in 'config' sets the
287
233
'checker_command' attribute and *not* the 'checker'
235
dbus.service.Object.__init__(self, bus,
237
% name.replace(".", "_"))
290
238
if config is None:
292
241
logger.debug(u"Creating client %r", self.name)
293
242
# Uppercase and remove spaces from fingerprint for later
294
243
# comparison purposes with return value from the fingerprint()
296
self.fingerprint = (config[u"fingerprint"].upper()
245
self.fingerprint = (config["fingerprint"].upper()
297
246
.replace(u" ", u""))
298
247
logger.debug(u" Fingerprint: %s", self.fingerprint)
299
if u"secret" in config:
300
self.secret = config[u"secret"].decode(u"base64")
301
elif u"secfile" in config:
302
with open(os.path.expanduser(os.path.expandvars
303
(config[u"secfile"])),
248
if "secret" in config:
249
self.secret = config["secret"].decode(u"base64")
250
elif "secfile" in config:
251
with closing(open(os.path.expanduser
253
(config["secfile"])))) as secfile:
305
254
self.secret = secfile.read()
307
256
raise TypeError(u"No secret or secfile for client %s"
309
self.host = config.get(u"host", u"")
258
self.host = config.get("host", "")
310
259
self.created = datetime.datetime.utcnow()
312
self.last_enabled = None
313
261
self.last_checked_ok = None
314
self.timeout = string_to_delta(config[u"timeout"])
315
self.interval = string_to_delta(config[u"interval"])
316
self.disable_hook = disable_hook
262
self.timeout = string_to_delta(config["timeout"])
263
self.interval = string_to_delta(config["interval"])
264
self.stop_hook = stop_hook
317
265
self.checker = None
318
266
self.checker_initiator_tag = None
319
self.disable_initiator_tag = None
267
self.stop_initiator_tag = None
320
268
self.checker_callback_tag = None
321
self.checker_command = config[u"checker"]
322
self.current_checker_command = None
323
self.last_connect = None
324
self._approved = None
325
self.approved_by_default = config.get(u"approved_by_default",
327
self.approvals_pending = 0
328
self.approved_delay = string_to_delta(
329
config[u"approved_delay"])
330
self.approved_duration = string_to_delta(
331
config[u"approved_duration"])
332
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
269
self.check_command = config["checker"]
334
def send_changedstate(self):
335
self.changedstate.acquire()
336
self.changedstate.notify_all()
337
self.changedstate.release()
340
272
"""Start this client's checker and timeout hooks"""
341
if getattr(self, u"enabled", False):
344
self.send_changedstate()
345
self.last_enabled = datetime.datetime.utcnow()
273
self.started = datetime.datetime.utcnow()
346
274
# Schedule a new checker to be started an 'interval' from now,
347
275
# and every interval from then on.
348
276
self.checker_initiator_tag = (gobject.timeout_add
349
(self.interval_milliseconds(),
277
(self._interval_milliseconds,
350
278
self.start_checker))
351
# Schedule a disable() when 'timeout' has passed
352
self.disable_initiator_tag = (gobject.timeout_add
353
(self.timeout_milliseconds(),
356
279
# Also start a new checker *right now*.
357
280
self.start_checker()
281
# Schedule a stop() when 'timeout' has passed
282
self.stop_initiator_tag = (gobject.timeout_add
283
(self._timeout_milliseconds,
286
self.StateChanged(True)
359
def disable(self, quiet=True):
360
"""Disable this client."""
361
if not getattr(self, "enabled", False):
289
"""Stop this client."""
290
if getattr(self, "started", None) is not None:
291
logger.info(u"Stopping client %s", self.name)
364
self.send_changedstate()
366
logger.info(u"Disabling client %s", self.name)
367
if getattr(self, u"disable_initiator_tag", False):
368
gobject.source_remove(self.disable_initiator_tag)
369
self.disable_initiator_tag = None
370
if getattr(self, u"checker_initiator_tag", False):
294
if getattr(self, "stop_initiator_tag", False):
295
gobject.source_remove(self.stop_initiator_tag)
296
self.stop_initiator_tag = None
297
if getattr(self, "checker_initiator_tag", False):
371
298
gobject.source_remove(self.checker_initiator_tag)
372
299
self.checker_initiator_tag = None
373
300
self.stop_checker()
374
if self.disable_hook:
375
self.disable_hook(self)
305
self.StateChanged(False)
377
306
# Do not run this again if called by a gobject.timeout_add
380
309
def __del__(self):
381
self.disable_hook = None
310
self.stop_hook = None
384
def checker_callback(self, pid, condition, command):
313
def checker_callback(self, pid, condition):
385
314
"""The checker has completed, so take appropriate actions."""
386
315
self.checker_callback_tag = None
387
316
self.checker = None
388
if os.WIFEXITED(condition):
389
exitstatus = os.WEXITSTATUS(condition)
391
logger.info(u"Checker for %(name)s succeeded",
395
logger.info(u"Checker for %(name)s failed",
317
if (os.WIFEXITED(condition)
318
and (os.WEXITSTATUS(condition) == 0)):
319
logger.info(u"Checker for %(name)s succeeded",
322
self.CheckerCompleted(True)
324
elif not os.WIFEXITED(condition):
398
325
logger.warning(u"Checker for %(name)s crashed?",
328
self.CheckerCompleted(False)
330
logger.info(u"Checker for %(name)s failed",
333
self.CheckerCompleted(False)
401
def checked_ok(self):
335
def bump_timeout(self):
402
336
"""Bump up the timeout for this client.
404
337
This should only be called when the client has been seen,
407
340
self.last_checked_ok = datetime.datetime.utcnow()
408
gobject.source_remove(self.disable_initiator_tag)
409
self.disable_initiator_tag = (gobject.timeout_add
410
(self.timeout_milliseconds(),
341
gobject.source_remove(self.stop_initiator_tag)
342
self.stop_initiator_tag = (gobject.timeout_add
343
(self._timeout_milliseconds,
413
346
def start_checker(self):
414
347
"""Start a new checker subprocess if one is not running.
416
348
If a checker already exists, leave it running and do
418
350
# The reason for not killing a running checker is that if we
488
396
if self.checker_callback_tag:
489
397
gobject.source_remove(self.checker_callback_tag)
490
398
self.checker_callback_tag = None
491
if getattr(self, u"checker", None) is None:
399
if getattr(self, "checker", None) is None:
493
401
logger.debug(u"Stopping checker for %(name)s", vars(self))
495
403
os.kill(self.checker.pid, signal.SIGTERM)
497
405
#if self.checker.poll() is None:
498
406
# os.kill(self.checker.pid, signal.SIGKILL)
499
407
except OSError, error:
500
408
if error.errno != errno.ESRCH: # No such process
502
410
self.checker = None
504
def dbus_service_property(dbus_interface, signature=u"v",
505
access=u"readwrite", byte_arrays=False):
506
"""Decorators for marking methods of a DBusObjectWithProperties to
507
become properties on the D-Bus.
509
The decorated method will be called with no arguments by "Get"
510
and with one argument by "Set".
512
The parameters, where they are supported, are the same as
513
dbus.service.method, except there is only "signature", since the
514
type from Get() and the type sent to Set() is the same.
516
# Encoding deeply encoded byte arrays is not supported yet by the
517
# "Set" method, so we fail early here:
518
if byte_arrays and signature != u"ay":
519
raise ValueError(u"Byte arrays not supported for non-'ay'"
520
u" signature %r" % signature)
522
func._dbus_is_property = True
523
func._dbus_interface = dbus_interface
524
func._dbus_signature = signature
525
func._dbus_access = access
526
func._dbus_name = func.__name__
527
if func._dbus_name.endswith(u"_dbus_property"):
528
func._dbus_name = func._dbus_name[:-14]
529
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
534
class DBusPropertyException(dbus.exceptions.DBusException):
535
"""A base class for D-Bus property-related exceptions
537
def __unicode__(self):
538
return unicode(str(self))
541
class DBusPropertyAccessException(DBusPropertyException):
542
"""A property's access permissions disallows an operation.
547
class DBusPropertyNotFound(DBusPropertyException):
548
"""An attempt was made to access a non-existing property.
553
class DBusObjectWithProperties(dbus.service.Object):
554
"""A D-Bus object with properties.
556
Classes inheriting from this can use the dbus_service_property
557
decorator to expose methods as D-Bus properties. It exposes the
558
standard Get(), Set(), and GetAll() methods on the D-Bus.
562
def _is_dbus_property(obj):
563
return getattr(obj, u"_dbus_is_property", False)
565
def _get_all_dbus_properties(self):
566
"""Returns a generator of (name, attribute) pairs
568
return ((prop._dbus_name, prop)
570
inspect.getmembers(self, self._is_dbus_property))
572
def _get_dbus_property(self, interface_name, property_name):
573
"""Returns a bound method if one exists which is a D-Bus
574
property with the specified name and interface.
576
for name in (property_name,
577
property_name + u"_dbus_property"):
578
prop = getattr(self, name, None)
580
or not self._is_dbus_property(prop)
581
or prop._dbus_name != property_name
582
or (interface_name and prop._dbus_interface
583
and interface_name != prop._dbus_interface)):
587
raise DBusPropertyNotFound(self.dbus_object_path + u":"
588
+ interface_name + u"."
591
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
593
def Get(self, interface_name, property_name):
594
"""Standard D-Bus property Get() method, see D-Bus standard.
596
prop = self._get_dbus_property(interface_name, property_name)
597
if prop._dbus_access == u"write":
598
raise DBusPropertyAccessException(property_name)
600
if not hasattr(value, u"variant_level"):
602
return type(value)(value, variant_level=value.variant_level+1)
604
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
605
def Set(self, interface_name, property_name, value):
606
"""Standard D-Bus property Set() method, see D-Bus standard.
608
prop = self._get_dbus_property(interface_name, property_name)
609
if prop._dbus_access == u"read":
610
raise DBusPropertyAccessException(property_name)
611
if prop._dbus_get_args_options[u"byte_arrays"]:
612
# The byte_arrays option is not supported yet on
613
# signatures other than "ay".
614
if prop._dbus_signature != u"ay":
616
value = dbus.ByteArray(''.join(unichr(byte)
620
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
621
out_signature=u"a{sv}")
622
def GetAll(self, interface_name):
623
"""Standard D-Bus property GetAll() method, see D-Bus
626
Note: Will not include properties with access="write".
629
for name, prop in self._get_all_dbus_properties():
631
and interface_name != prop._dbus_interface):
632
# Interface non-empty but did not match
634
# Ignore write-only properties
635
if prop._dbus_access == u"write":
638
if not hasattr(value, u"variant_level"):
641
all[name] = type(value)(value, variant_level=
642
value.variant_level+1)
643
return dbus.Dictionary(all, signature=u"sv")
645
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
647
path_keyword='object_path',
648
connection_keyword='connection')
649
def Introspect(self, object_path, connection):
650
"""Standard D-Bus method, overloaded to insert property tags.
652
xmlstring = dbus.service.Object.Introspect(self, object_path,
655
document = xml.dom.minidom.parseString(xmlstring)
656
def make_tag(document, name, prop):
657
e = document.createElement(u"property")
658
e.setAttribute(u"name", name)
659
e.setAttribute(u"type", prop._dbus_signature)
660
e.setAttribute(u"access", prop._dbus_access)
662
for if_tag in document.getElementsByTagName(u"interface"):
663
for tag in (make_tag(document, name, prop)
665
in self._get_all_dbus_properties()
666
if prop._dbus_interface
667
== if_tag.getAttribute(u"name")):
668
if_tag.appendChild(tag)
669
# Add the names to the return values for the
670
# "org.freedesktop.DBus.Properties" methods
671
if (if_tag.getAttribute(u"name")
672
== u"org.freedesktop.DBus.Properties"):
673
for cn in if_tag.getElementsByTagName(u"method"):
674
if cn.getAttribute(u"name") == u"Get":
675
for arg in cn.getElementsByTagName(u"arg"):
676
if (arg.getAttribute(u"direction")
678
arg.setAttribute(u"name", u"value")
679
elif cn.getAttribute(u"name") == u"GetAll":
680
for arg in cn.getElementsByTagName(u"arg"):
681
if (arg.getAttribute(u"direction")
683
arg.setAttribute(u"name", u"props")
684
xmlstring = document.toxml(u"utf-8")
686
except (AttributeError, xml.dom.DOMException,
687
xml.parsers.expat.ExpatError), error:
688
logger.error(u"Failed to override Introspection method",
693
class ClientDBus(Client, DBusObjectWithProperties):
694
"""A Client class using D-Bus
697
dbus_object_path: dbus.ObjectPath
698
bus: dbus.SystemBus()
700
# dbus.service.Object doesn't use super(), so we can't either.
702
def __init__(self, bus = None, *args, **kwargs):
703
self._approvals_pending = 0
705
Client.__init__(self, *args, **kwargs)
706
# Only now, when this client is initialized, can it show up on
708
self.dbus_object_path = (dbus.ObjectPath
710
+ self.name.replace(u".", u"_")))
711
DBusObjectWithProperties.__init__(self, self.bus,
712
self.dbus_object_path)
714
def _get_approvals_pending(self):
715
return self._approvals_pending
716
def _set_approvals_pending(self, value):
717
old_value = self._approvals_pending
718
self._approvals_pending = value
720
if (hasattr(self, "dbus_object_path")
721
and bval is not bool(old_value)):
722
dbus_bool = dbus.Boolean(bval, variant_level=1)
723
self.PropertyChanged(dbus.String(u"approved_pending"),
726
approvals_pending = property(_get_approvals_pending,
727
_set_approvals_pending)
728
del _get_approvals_pending, _set_approvals_pending
731
def _datetime_to_dbus(dt, variant_level=0):
732
"""Convert a UTC datetime.datetime() to a D-Bus type."""
733
return dbus.String(dt.isoformat(),
734
variant_level=variant_level)
737
oldstate = getattr(self, u"enabled", False)
738
r = Client.enable(self)
739
if oldstate != self.enabled:
741
self.PropertyChanged(dbus.String(u"enabled"),
742
dbus.Boolean(True, variant_level=1))
743
self.PropertyChanged(
744
dbus.String(u"last_enabled"),
745
self._datetime_to_dbus(self.last_enabled,
749
def disable(self, quiet = False):
750
oldstate = getattr(self, u"enabled", False)
751
r = Client.disable(self, quiet=quiet)
752
if not quiet and oldstate != self.enabled:
754
self.PropertyChanged(dbus.String(u"enabled"),
755
dbus.Boolean(False, variant_level=1))
758
def __del__(self, *args, **kwargs):
760
self.remove_from_connection()
763
if hasattr(DBusObjectWithProperties, u"__del__"):
764
DBusObjectWithProperties.__del__(self, *args, **kwargs)
765
Client.__del__(self, *args, **kwargs)
767
def checker_callback(self, pid, condition, command,
769
self.checker_callback_tag = None
772
self.PropertyChanged(dbus.String(u"checker_running"),
773
dbus.Boolean(False, variant_level=1))
774
if os.WIFEXITED(condition):
775
exitstatus = os.WEXITSTATUS(condition)
777
self.CheckerCompleted(dbus.Int16(exitstatus),
778
dbus.Int64(condition),
779
dbus.String(command))
782
self.CheckerCompleted(dbus.Int16(-1),
783
dbus.Int64(condition),
784
dbus.String(command))
786
return Client.checker_callback(self, pid, condition, command,
789
def checked_ok(self, *args, **kwargs):
790
r = Client.checked_ok(self, *args, **kwargs)
792
self.PropertyChanged(
793
dbus.String(u"last_checked_ok"),
794
(self._datetime_to_dbus(self.last_checked_ok,
798
def start_checker(self, *args, **kwargs):
799
old_checker = self.checker
800
if self.checker is not None:
801
old_checker_pid = self.checker.pid
803
old_checker_pid = None
804
r = Client.start_checker(self, *args, **kwargs)
805
# Only if new checker process was started
806
if (self.checker is not None
807
and old_checker_pid != self.checker.pid):
809
self.CheckerStarted(self.current_checker_command)
810
self.PropertyChanged(
811
dbus.String(u"checker_running"),
812
dbus.Boolean(True, variant_level=1))
815
def stop_checker(self, *args, **kwargs):
816
old_checker = getattr(self, u"checker", None)
817
r = Client.stop_checker(self, *args, **kwargs)
818
if (old_checker is not None
819
and getattr(self, u"checker", None) is None):
820
self.PropertyChanged(dbus.String(u"checker_running"),
821
dbus.Boolean(False, variant_level=1))
824
def _reset_approved(self):
825
self._approved = None
828
def approve(self, value=True):
829
self.send_changedstate()
830
self._approved = value
831
gobject.timeout_add(self._timedelta_to_milliseconds(self.approved_duration),
832
self._reset_approved)
835
## D-Bus methods, signals & properties
836
_interface = u"se.bsnet.fukt.Mandos.Client"
412
def still_valid(self):
413
"""Has the timeout not yet passed for this client?"""
416
now = datetime.datetime.utcnow()
417
if self.last_checked_ok is None:
418
return now < (self.created + self.timeout)
420
return now < (self.last_checked_ok + self.timeout)
422
## D-Bus methods & signals
423
_interface = u"org.mandos_system.Mandos.Client"
425
def _datetime_to_dbus_struct(dt):
426
return dbus.Struct(dt.year, dt.month, dt.day, dt.hour,
427
dt.minute, dt.second, dt.microsecond,
430
# BumpTimeout - method
431
BumpTimeout = dbus.service.method(_interface)(bump_timeout)
432
BumpTimeout.__name__ = "BumpTimeout"
434
# IntervalChanged - signal
435
@dbus.service.signal(_interface, signature="t")
436
def IntervalChanged(self, t):
840
440
# CheckerCompleted - signal
841
@dbus.service.signal(_interface, signature=u"nxs")
842
def CheckerCompleted(self, exitcode, waitstatus, command):
441
@dbus.service.signal(_interface, signature="b")
442
def CheckerCompleted(self, success):
446
# CheckerIsRunning - method
447
@dbus.service.method(_interface, out_signature="b")
448
def CheckerIsRunning(self):
449
"D-Bus getter method"
450
return self.checker is not None
846
452
# CheckerStarted - signal
847
@dbus.service.signal(_interface, signature=u"s")
453
@dbus.service.signal(_interface, signature="s")
848
454
def CheckerStarted(self, command):
852
# PropertyChanged - signal
853
@dbus.service.signal(_interface, signature=u"sv")
854
def PropertyChanged(self, property, value):
859
@dbus.service.signal(_interface)
862
Is sent after a successful transfer of secret from the Mandos
863
server to mandos-client
868
@dbus.service.signal(_interface, signature=u"s")
869
def Rejected(self, reason):
873
# NeedApproval - signal
874
@dbus.service.signal(_interface, signature=u"db")
875
def NeedApproval(self, timeout, default):
882
@dbus.service.method(_interface, in_signature=u"b")
883
def Approve(self, value):
887
@dbus.service.method(_interface)
889
return self.checked_ok()
892
@dbus.service.method(_interface)
458
# GetChecker - method
459
@dbus.service.method(_interface, out_signature="s")
460
def GetChecker(self):
461
"D-Bus getter method"
462
return self.checker_command
464
# GetCreated - method
465
@dbus.service.method(_interface, out_signature="(nyyyyyu)")
466
def GetCreated(self):
467
"D-Bus getter method"
468
return datetime_to_dbus_struct(self.created)
470
# GetFingerprint - method
471
@dbus.service.method(_interface, out_signature="s")
472
def GetFingerprint(self):
473
"D-Bus getter method"
474
return self.fingerprint
477
@dbus.service.method(_interface, out_signature="s")
479
"D-Bus getter method"
482
# GetInterval - method
483
@dbus.service.method(_interface, out_signature="t")
484
def GetInterval(self):
485
"D-Bus getter method"
486
return self._interval_milliseconds
489
@dbus.service.method(_interface, out_signature="s")
491
"D-Bus getter method"
494
# GetStarted - method
495
@dbus.service.method(_interface, out_signature="(nyyyyyu)")
496
def GetStarted(self):
497
"D-Bus getter method"
498
if self.started is not None:
499
return datetime_to_dbus_struct(self.started)
501
return dbus.Struct(0, 0, 0, 0, 0, 0, 0,
504
# GetTimeout - method
505
@dbus.service.method(_interface, out_signature="t")
506
def GetTimeout(self):
507
"D-Bus getter method"
508
return self._timeout_milliseconds
510
# SetChecker - method
511
@dbus.service.method(_interface, in_signature="s")
512
def SetChecker(self, checker):
513
"D-Bus setter method"
514
self.checker_command = checker
517
@dbus.service.method(_interface, in_signature="s")
518
def SetHost(self, host):
519
"D-Bus setter method"
522
# SetInterval - method
523
@dbus.service.method(_interface, in_signature="t")
524
def SetInterval(self, milliseconds):
525
self.interval = datetime.timdeelta(0, 0, 0, milliseconds)
527
# SetTimeout - method
528
@dbus.service.method(_interface, in_signature="t")
529
def SetTimeout(self, milliseconds):
530
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
533
@dbus.service.method(_interface, in_signature="ay",
535
def SetSecret(self, secret):
536
"D-Bus setter method"
537
self.secret = str(secret)
540
Start = dbus.service.method(_interface)(start)
541
Start.__name__ = "Start"
897
543
# StartChecker - method
898
@dbus.service.method(_interface)
899
def StartChecker(self):
904
@dbus.service.method(_interface)
544
StartChecker = dbus.service.method(_interface)(start_checker)
545
StartChecker.__name__ = "StartChecker"
547
# StateChanged - signal
548
@dbus.service.signal(_interface, signature="b")
549
def StateChanged(self, started):
553
# StillValid - method
554
StillValid = (dbus.service.method(_interface, out_signature="b")
556
StillValid.__name__ = "StillValid"
559
Stop = dbus.service.method(_interface)(stop)
560
Stop.__name__ = "Stop"
909
562
# StopChecker - method
910
@dbus.service.method(_interface)
911
def StopChecker(self):
916
# approved_pending - property
917
@dbus_service_property(_interface, signature=u"b", access=u"read")
918
def approved_pending_dbus_property(self):
919
return dbus.Boolean(bool(self.approvals_pending))
921
# approved_by_default - property
922
@dbus_service_property(_interface, signature=u"b",
924
def approved_by_default_dbus_property(self):
925
return dbus.Boolean(self.approved_by_default)
927
# approved_delay - property
928
@dbus_service_property(_interface, signature=u"t",
930
def approved_delay_dbus_property(self):
931
return dbus.UInt64(self.approved_delay_milliseconds())
933
# approved_duration - property
934
@dbus_service_property(_interface, signature=u"t",
936
def approved_duration_dbus_property(self):
937
return dbus.UInt64(self._timedelta_to_milliseconds(
938
self.approved_duration))
941
@dbus_service_property(_interface, signature=u"s", access=u"read")
942
def name_dbus_property(self):
943
return dbus.String(self.name)
945
# fingerprint - property
946
@dbus_service_property(_interface, signature=u"s", access=u"read")
947
def fingerprint_dbus_property(self):
948
return dbus.String(self.fingerprint)
951
@dbus_service_property(_interface, signature=u"s",
953
def host_dbus_property(self, value=None):
954
if value is None: # get
955
return dbus.String(self.host)
958
self.PropertyChanged(dbus.String(u"host"),
959
dbus.String(value, variant_level=1))
962
@dbus_service_property(_interface, signature=u"s", access=u"read")
963
def created_dbus_property(self):
964
return dbus.String(self._datetime_to_dbus(self.created))
966
# last_enabled - property
967
@dbus_service_property(_interface, signature=u"s", access=u"read")
968
def last_enabled_dbus_property(self):
969
if self.last_enabled is None:
970
return dbus.String(u"")
971
return dbus.String(self._datetime_to_dbus(self.last_enabled))
974
@dbus_service_property(_interface, signature=u"b",
976
def enabled_dbus_property(self, value=None):
977
if value is None: # get
978
return dbus.Boolean(self.enabled)
984
# last_checked_ok - property
985
@dbus_service_property(_interface, signature=u"s",
987
def last_checked_ok_dbus_property(self, value=None):
988
if value is not None:
991
if self.last_checked_ok is None:
992
return dbus.String(u"")
993
return dbus.String(self._datetime_to_dbus(self
997
@dbus_service_property(_interface, signature=u"t",
999
def timeout_dbus_property(self, value=None):
1000
if value is None: # get
1001
return dbus.UInt64(self.timeout_milliseconds())
1002
self.timeout = datetime.timedelta(0, 0, 0, value)
1004
self.PropertyChanged(dbus.String(u"timeout"),
1005
dbus.UInt64(value, variant_level=1))
1006
if getattr(self, u"disable_initiator_tag", None) is None:
1008
# Reschedule timeout
1009
gobject.source_remove(self.disable_initiator_tag)
1010
self.disable_initiator_tag = None
1011
time_to_die = (self.
1012
_timedelta_to_milliseconds((self
1017
if time_to_die <= 0:
1018
# The timeout has passed
1021
self.disable_initiator_tag = (gobject.timeout_add
1022
(time_to_die, self.disable))
1024
# interval - property
1025
@dbus_service_property(_interface, signature=u"t",
1026
access=u"readwrite")
1027
def interval_dbus_property(self, value=None):
1028
if value is None: # get
1029
return dbus.UInt64(self.interval_milliseconds())
1030
self.interval = datetime.timedelta(0, 0, 0, value)
1032
self.PropertyChanged(dbus.String(u"interval"),
1033
dbus.UInt64(value, variant_level=1))
1034
if getattr(self, u"checker_initiator_tag", None) is None:
1036
# Reschedule checker run
1037
gobject.source_remove(self.checker_initiator_tag)
1038
self.checker_initiator_tag = (gobject.timeout_add
1039
(value, self.start_checker))
1040
self.start_checker() # Start one now, too
1042
# checker - property
1043
@dbus_service_property(_interface, signature=u"s",
1044
access=u"readwrite")
1045
def checker_dbus_property(self, value=None):
1046
if value is None: # get
1047
return dbus.String(self.checker_command)
1048
self.checker_command = value
1050
self.PropertyChanged(dbus.String(u"checker"),
1051
dbus.String(self.checker_command,
1054
# checker_running - property
1055
@dbus_service_property(_interface, signature=u"b",
1056
access=u"readwrite")
1057
def checker_running_dbus_property(self, value=None):
1058
if value is None: # get
1059
return dbus.Boolean(self.checker is not None)
1061
self.start_checker()
1065
# object_path - property
1066
@dbus_service_property(_interface, signature=u"o", access=u"read")
1067
def object_path_dbus_property(self):
1068
return self.dbus_object_path # is already a dbus.ObjectPath
1071
@dbus_service_property(_interface, signature=u"ay",
1072
access=u"write", byte_arrays=True)
1073
def secret_dbus_property(self, value):
1074
self.secret = str(value)
563
StopChecker = dbus.service.method(_interface)(stop_checker)
564
StopChecker.__name__ = "StopChecker"
566
# TimeoutChanged - signal
567
@dbus.service.signal(_interface, signature="t")
568
def TimeoutChanged(self, t):
572
del _datetime_to_dbus_struct
1079
class ProxyClient(object):
1080
def __init__(self, child_pipe, fpr, address):
1081
self._pipe = child_pipe
1082
self._pipe.send(('init', fpr, address))
1083
if not self._pipe.recv():
1086
def __getattribute__(self, name):
1087
if(name == '_pipe'):
1088
return super(ProxyClient, self).__getattribute__(name)
1089
self._pipe.send(('getattr', name))
1090
data = self._pipe.recv()
1091
if data[0] == 'data':
1093
if data[0] == 'function':
1094
def func(*args, **kwargs):
1095
self._pipe.send(('funcall', name, args, kwargs))
1096
return self._pipe.recv()[1]
1099
def __setattr__(self, name, value):
1100
if(name == '_pipe'):
1101
return super(ProxyClient, self).__setattr__(name, value)
1102
self._pipe.send(('setattr', name, value))
1105
class ClientHandler(socketserver.BaseRequestHandler, object):
1106
"""A class to handle client connections.
1108
Instantiated once for each connection to handle it.
576
def peer_certificate(session):
577
"Return the peer's OpenPGP certificate as a bytestring"
578
# If not an OpenPGP certificate...
579
if (gnutls.library.functions
580
.gnutls_certificate_type_get(session._c_object)
581
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
582
# ...do the normal thing
583
return session.peer_certificate
584
list_size = ctypes.c_uint()
585
cert_list = (gnutls.library.functions
586
.gnutls_certificate_get_peers
587
(session._c_object, ctypes.byref(list_size)))
588
if list_size.value == 0:
591
return ctypes.string_at(cert.data, cert.size)
594
def fingerprint(openpgp):
595
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
596
# New GnuTLS "datum" with the OpenPGP public key
597
datum = (gnutls.library.types
598
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
601
ctypes.c_uint(len(openpgp))))
602
# New empty GnuTLS certificate
603
crt = gnutls.library.types.gnutls_openpgp_crt_t()
604
(gnutls.library.functions
605
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
606
# Import the OpenPGP public key into the certificate
607
(gnutls.library.functions
608
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
609
gnutls.library.constants
610
.GNUTLS_OPENPGP_FMT_RAW))
611
# Verify the self signature in the key
612
crtverify = ctypes.c_uint()
613
(gnutls.library.functions
614
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
615
if crtverify.value != 0:
616
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
617
raise gnutls.errors.CertificateSecurityError("Verify failed")
618
# New buffer for the fingerprint
619
buf = ctypes.create_string_buffer(20)
620
buf_len = ctypes.c_size_t()
621
# Get the fingerprint from the certificate into the buffer
622
(gnutls.library.functions
623
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
624
ctypes.byref(buf_len)))
625
# Deinit the certificate
626
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
627
# Convert the buffer to a Python bytestring
628
fpr = ctypes.string_at(buf, buf_len.value)
629
# Convert the bytestring to hexadecimal notation
630
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
634
class TCP_handler(SocketServer.BaseRequestHandler, object):
635
"""A TCP request handler class.
636
Instantiated by IPv6_TCPServer for each request to handle it.
1109
637
Note: This will run in its own forked process."""
1111
639
def handle(self):
1112
with contextlib.closing(self.server.child_pipe) as child_pipe:
1113
logger.info(u"TCP connection from: %s",
1114
unicode(self.client_address))
1115
logger.debug(u"Pipe FD: %d",
1116
self.server.child_pipe.fileno())
1118
session = (gnutls.connection
1119
.ClientSession(self.request,
1121
.X509Credentials()))
1123
# Note: gnutls.connection.X509Credentials is really a
1124
# generic GnuTLS certificate credentials object so long as
1125
# no X.509 keys are added to it. Therefore, we can use it
1126
# here despite using OpenPGP certificates.
1128
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1129
# u"+AES-256-CBC", u"+SHA1",
1130
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1132
# Use a fallback default, since this MUST be set.
1133
priority = self.server.gnutls_priority
1134
if priority is None:
1135
priority = u"NORMAL"
1136
(gnutls.library.functions
1137
.gnutls_priority_set_direct(session._c_object,
1140
# Start communication using the Mandos protocol
1141
# Get protocol number
1142
line = self.request.makefile().readline()
1143
logger.debug(u"Protocol version: %r", line)
1145
if int(line.strip().split()[0]) > 1:
1147
except (ValueError, IndexError, RuntimeError), error:
1148
logger.error(u"Unknown protocol version: %s", error)
1151
# Start GnuTLS connection
1154
except gnutls.errors.GNUTLSError, error:
1155
logger.warning(u"Handshake failed: %s", error)
1156
# Do not run session.bye() here: the session is not
1157
# established. Just abandon the request.
1159
logger.debug(u"Handshake succeeded")
1161
approval_required = False
1164
fpr = self.fingerprint(self.peer_certificate
1166
except (TypeError, gnutls.errors.GNUTLSError), error:
1167
logger.warning(u"Bad certificate: %s", error)
1169
logger.debug(u"Fingerprint: %s", fpr)
1172
client = ProxyClient(child_pipe, fpr,
1173
self.client_address)
1177
if client.approved_delay:
1178
delay = client.approved_delay
1179
client.approvals_pending += 1
1180
approval_required = True
1183
if not client.enabled:
1184
logger.warning(u"Client %s is disabled",
1186
if self.server.use_dbus:
1188
client.Rejected("Disabled")
1191
if client._approved or not client.approved_delay:
1192
#We are approved or approval is disabled
1194
elif client._approved is None:
1195
logger.info(u"Client %s need approval",
1197
if self.server.use_dbus:
1199
client.NeedApproval(
1200
client.approved_delay_milliseconds(),
1201
client.approved_by_default)
1203
logger.warning(u"Client %s was not approved",
1205
if self.server.use_dbus:
1207
client.Rejected("Disapproved")
1210
#wait until timeout or approved
1211
#x = float(client._timedelta_to_milliseconds(delay))
1212
time = datetime.datetime.now()
1213
client.changedstate.acquire()
1214
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1215
client.changedstate.release()
1216
time2 = datetime.datetime.now()
1217
if (time2 - time) >= delay:
1218
if not client.approved_by_default:
1219
logger.warning("Client %s timed out while"
1220
" waiting for approval",
1222
if self.server.use_dbus:
1224
client.Rejected("Time out")
1229
delay -= time2 - time
1232
while sent_size < len(client.secret):
1234
sent = session.send(client.secret[sent_size:])
1235
except (gnutls.errors.GNUTLSError), error:
1236
logger.warning("gnutls send failed")
1238
logger.debug(u"Sent: %d, remaining: %d",
1239
sent, len(client.secret)
1240
- (sent_size + sent))
1243
logger.info(u"Sending secret to %s", client.name)
1244
# bump the timeout as if seen
1246
if self.server.use_dbus:
1251
if approval_required:
1252
client.approvals_pending -= 1
1255
except (gnutls.errors.GNUTLSError), error:
1256
logger.warning("gnutls bye failed")
1259
def peer_certificate(session):
1260
"Return the peer's OpenPGP certificate as a bytestring"
1261
# If not an OpenPGP certificate...
1262
if (gnutls.library.functions
1263
.gnutls_certificate_type_get(session._c_object)
1264
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1265
# ...do the normal thing
1266
return session.peer_certificate
1267
list_size = ctypes.c_uint(1)
1268
cert_list = (gnutls.library.functions
1269
.gnutls_certificate_get_peers
1270
(session._c_object, ctypes.byref(list_size)))
1271
if not bool(cert_list) and list_size.value != 0:
1272
raise gnutls.errors.GNUTLSError(u"error getting peer"
1274
if list_size.value == 0:
1277
return ctypes.string_at(cert.data, cert.size)
1280
def fingerprint(openpgp):
1281
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1282
# New GnuTLS "datum" with the OpenPGP public key
1283
datum = (gnutls.library.types
1284
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1287
ctypes.c_uint(len(openpgp))))
1288
# New empty GnuTLS certificate
1289
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1290
(gnutls.library.functions
1291
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1292
# Import the OpenPGP public key into the certificate
1293
(gnutls.library.functions
1294
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1295
gnutls.library.constants
1296
.GNUTLS_OPENPGP_FMT_RAW))
1297
# Verify the self signature in the key
1298
crtverify = ctypes.c_uint()
1299
(gnutls.library.functions
1300
.gnutls_openpgp_crt_verify_self(crt, 0,
1301
ctypes.byref(crtverify)))
1302
if crtverify.value != 0:
1303
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1304
raise (gnutls.errors.CertificateSecurityError
1306
# New buffer for the fingerprint
1307
buf = ctypes.create_string_buffer(20)
1308
buf_len = ctypes.c_size_t()
1309
# Get the fingerprint from the certificate into the buffer
1310
(gnutls.library.functions
1311
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1312
ctypes.byref(buf_len)))
1313
# Deinit the certificate
1314
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1315
# Convert the buffer to a Python bytestring
1316
fpr = ctypes.string_at(buf, buf_len.value)
1317
# Convert the bytestring to hexadecimal notation
1318
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1322
class MultiprocessingMixIn(object):
1323
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1324
def sub_process_main(self, request, address):
1326
self.finish_request(request, address)
1328
self.handle_error(request, address)
1329
self.close_request(request)
1331
def process_request(self, request, address):
1332
"""Start a new process to process the request."""
1333
multiprocessing.Process(target = self.sub_process_main,
1334
args = (request, address)).start()
1336
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1337
""" adds a pipe to the MixIn """
1338
def process_request(self, request, client_address):
1339
"""Overrides and wraps the original process_request().
1341
This function creates a new pipe in self.pipe
1343
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1345
super(MultiprocessingMixInWithPipe,
1346
self).process_request(request, client_address)
1347
self.child_pipe.close()
1348
self.add_pipe(parent_pipe)
1350
def add_pipe(self, parent_pipe):
1351
"""Dummy function; override as necessary"""
1354
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1355
socketserver.TCPServer, object):
1356
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
640
logger.info(u"TCP connection from: %s",
641
unicode(self.client_address))
642
session = (gnutls.connection
643
.ClientSession(self.request,
647
line = self.request.makefile().readline()
648
logger.debug(u"Protocol version: %r", line)
650
if int(line.strip().split()[0]) > 1:
652
except (ValueError, IndexError, RuntimeError), error:
653
logger.error(u"Unknown protocol version: %s", error)
656
# Note: gnutls.connection.X509Credentials is really a generic
657
# GnuTLS certificate credentials object so long as no X.509
658
# keys are added to it. Therefore, we can use it here despite
659
# using OpenPGP certificates.
661
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
662
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
664
# Use a fallback default, since this MUST be set.
665
priority = self.server.settings.get("priority", "NORMAL")
666
(gnutls.library.functions
667
.gnutls_priority_set_direct(session._c_object,
672
except gnutls.errors.GNUTLSError, error:
673
logger.warning(u"Handshake failed: %s", error)
674
# Do not run session.bye() here: the session is not
675
# established. Just abandon the request.
678
fpr = fingerprint(peer_certificate(session))
679
except (TypeError, gnutls.errors.GNUTLSError), error:
680
logger.warning(u"Bad certificate: %s", error)
683
logger.debug(u"Fingerprint: %s", fpr)
684
for c in self.server.clients:
685
if c.fingerprint == fpr:
689
logger.warning(u"Client not found for fingerprint: %s",
693
# Have to check if client.still_valid(), since it is possible
694
# that the client timed out while establishing the GnuTLS
696
if not client.still_valid():
697
logger.warning(u"Client %(name)s is invalid",
701
## This won't work here, since we're in a fork.
702
# client.bump_timeout()
704
while sent_size < len(client.secret):
705
sent = session.send(client.secret[sent_size:])
706
logger.debug(u"Sent: %d, remaining: %d",
707
sent, len(client.secret)
708
- (sent_size + sent))
713
class IPv6_TCPServer(SocketServer.ForkingMixIn,
714
SocketServer.TCPServer, object):
715
"""IPv6 TCP server. Accepts 'None' as address and/or port.
717
settings: Server settings
718
clients: Set() of Client objects
1359
719
enabled: Boolean; whether this server is activated yet
1360
interface: None or a network interface name (string)
1361
use_ipv6: Boolean; to use IPv6 or not
1363
def __init__(self, server_address, RequestHandlerClass,
1364
interface=None, use_ipv6=True):
1365
self.interface = interface
1367
self.address_family = socket.AF_INET6
1368
socketserver.TCPServer.__init__(self, server_address,
1369
RequestHandlerClass)
721
address_family = socket.AF_INET6
722
def __init__(self, *args, **kwargs):
723
if "settings" in kwargs:
724
self.settings = kwargs["settings"]
725
del kwargs["settings"]
726
if "clients" in kwargs:
727
self.clients = kwargs["clients"]
728
del kwargs["clients"]
730
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1370
731
def server_bind(self):
1371
732
"""This overrides the normal server_bind() function
1372
733
to bind to an interface if one was specified, and also NOT to
1373
734
bind to an address or port if they were not specified."""
1374
if self.interface is not None:
1375
if SO_BINDTODEVICE is None:
1376
logger.error(u"SO_BINDTODEVICE does not exist;"
1377
u" cannot bind to interface %s",
1381
self.socket.setsockopt(socket.SOL_SOCKET,
1385
except socket.error, error:
1386
if error[0] == errno.EPERM:
1387
logger.error(u"No permission to"
1388
u" bind to interface %s",
1390
elif error[0] == errno.ENOPROTOOPT:
1391
logger.error(u"SO_BINDTODEVICE not available;"
1392
u" cannot bind to interface %s",
735
if self.settings["interface"]:
736
# 25 is from /usr/include/asm-i486/socket.h
737
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
739
self.socket.setsockopt(socket.SOL_SOCKET,
741
self.settings["interface"])
742
except socket.error, error:
743
if error[0] == errno.EPERM:
744
logger.error(u"No permission to"
745
u" bind to interface %s",
746
self.settings["interface"])
1396
749
# Only bind(2) the socket if we really need to.
1397
750
if self.server_address[0] or self.server_address[1]:
1398
751
if not self.server_address[0]:
1399
if self.address_family == socket.AF_INET6:
1400
any_address = u"::" # in6addr_any
1402
any_address = socket.INADDR_ANY
1403
self.server_address = (any_address,
753
self.server_address = (in6addr_any,
1404
754
self.server_address[1])
1405
755
elif not self.server_address[1]:
1406
756
self.server_address = (self.server_address[0],
1408
# if self.interface:
758
# if self.settings["interface"]:
1409
759
# self.server_address = (self.server_address[0],
1412
762
# if_nametoindex
1414
return socketserver.TCPServer.server_bind(self)
1417
class MandosServer(IPv6_TCPServer):
1421
clients: set of Client objects
1422
gnutls_priority GnuTLS priority string
1423
use_dbus: Boolean; to emit D-Bus signals or not
1425
Assumes a gobject.MainLoop event loop.
1427
def __init__(self, server_address, RequestHandlerClass,
1428
interface=None, use_ipv6=True, clients=None,
1429
gnutls_priority=None, use_dbus=True):
1430
self.enabled = False
1431
self.clients = clients
1432
if self.clients is None:
1433
self.clients = set()
1434
self.use_dbus = use_dbus
1435
self.gnutls_priority = gnutls_priority
1436
IPv6_TCPServer.__init__(self, server_address,
1437
RequestHandlerClass,
1438
interface = interface,
1439
use_ipv6 = use_ipv6)
765
return super(IPv6_TCPServer, self).server_bind()
1440
766
def server_activate(self):
1441
767
if self.enabled:
1442
return socketserver.TCPServer.server_activate(self)
768
return super(IPv6_TCPServer, self).server_activate()
1443
769
def enable(self):
1444
770
self.enabled = True
1445
def add_pipe(self, parent_pipe):
1446
# Call "handle_ipc" for both data and EOF events
1447
gobject.io_add_watch(parent_pipe.fileno(),
1448
gobject.IO_IN | gobject.IO_HUP,
1449
functools.partial(self.handle_ipc,
1450
parent_pipe = parent_pipe))
1452
def handle_ipc(self, source, condition, parent_pipe=None,
1453
client_object=None):
1455
gobject.IO_IN: u"IN", # There is data to read.
1456
gobject.IO_OUT: u"OUT", # Data can be written (without
1458
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1459
gobject.IO_ERR: u"ERR", # Error condition.
1460
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1461
# broken, usually for pipes and
1464
conditions_string = ' | '.join(name
1466
condition_names.iteritems()
1467
if cond & condition)
1468
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1471
# error or the other end of multiprocessing.Pipe has closed
1472
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1475
# Read a request from the child
1476
request = parent_pipe.recv()
1477
logger.debug(u"IPC request: %s", repr(request))
1478
command = request[0]
1480
if command == 'init':
1482
address = request[2]
1484
for c in self.clients:
1485
if c.fingerprint == fpr:
1489
logger.warning(u"Client not found for fingerprint: %s, ad"
1490
u"dress: %s", fpr, address)
1493
mandos_dbus_service.ClientNotFound(fpr, address)
1494
parent_pipe.send(False)
1497
gobject.io_add_watch(parent_pipe.fileno(),
1498
gobject.IO_IN | gobject.IO_HUP,
1499
functools.partial(self.handle_ipc,
1500
parent_pipe = parent_pipe,
1501
client_object = client))
1502
parent_pipe.send(True)
1503
# remove the old hook in favor of the new above hook on same fileno
1505
if command == 'funcall':
1506
funcname = request[1]
1510
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1512
if command == 'getattr':
1513
attrname = request[1]
1514
if callable(client_object.__getattribute__(attrname)):
1515
parent_pipe.send(('function',))
1517
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1519
if command == 'setattr':
1520
attrname = request[1]
1522
setattr(client_object, attrname, value)
1527
773
def string_to_delta(interval):
1528
774
"""Parse a string and return a datetime.timedelta
1530
>>> string_to_delta(u'7d')
776
>>> string_to_delta('7d')
1531
777
datetime.timedelta(7)
1532
>>> string_to_delta(u'60s')
778
>>> string_to_delta('60s')
1533
779
datetime.timedelta(0, 60)
1534
>>> string_to_delta(u'60m')
780
>>> string_to_delta('60m')
1535
781
datetime.timedelta(0, 3600)
1536
>>> string_to_delta(u'24h')
782
>>> string_to_delta('24h')
1537
783
datetime.timedelta(1)
1538
784
>>> string_to_delta(u'1w')
1539
785
datetime.timedelta(7)
1540
>>> string_to_delta(u'5m 30s')
786
>>> string_to_delta('5m 30s')
1541
787
datetime.timedelta(0, 330)
1543
789
timevalue = datetime.timedelta(0)
1654
909
# Default values for config file for server-global settings
1655
server_defaults = { u"interface": u"",
1660
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1661
u"servicename": u"Mandos",
1662
u"use_dbus": u"True",
1663
u"use_ipv6": u"True",
910
server_defaults = { "interface": "",
915
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
916
"servicename": "Mandos",
1667
919
# Parse config file for server-global settings
1668
server_config = configparser.SafeConfigParser(server_defaults)
920
server_config = ConfigParser.SafeConfigParser(server_defaults)
1669
921
del server_defaults
1670
server_config.read(os.path.join(options.configdir,
922
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1672
923
# Convert the SafeConfigParser object to a dict
1673
924
server_settings = server_config.defaults()
1674
# Use the appropriate methods on the non-string config options
1675
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1676
server_settings[option] = server_config.getboolean(u"DEFAULT",
1678
if server_settings["port"]:
1679
server_settings["port"] = server_config.getint(u"DEFAULT",
925
# Use getboolean on the boolean config option
926
server_settings["debug"] = (server_config.getboolean
927
("DEFAULT", "debug"))
1681
928
del server_config
1683
930
# Override the settings from the config file with command line
1684
931
# options, if set.
1685
for option in (u"interface", u"address", u"port", u"debug",
1686
u"priority", u"servicename", u"configdir",
1687
u"use_dbus", u"use_ipv6", u"debuglevel"):
932
for option in ("interface", "address", "port", "debug",
933
"priority", "servicename", "configdir"):
1688
934
value = getattr(options, option)
1689
935
if value is not None:
1690
936
server_settings[option] = value
1692
# Force all strings to be unicode
1693
for option in server_settings.keys():
1694
if type(server_settings[option]) is str:
1695
server_settings[option] = unicode(server_settings[option])
1696
938
# Now we have our good server settings in "server_settings"
1698
##################################################################
1701
debug = server_settings[u"debug"]
1702
debuglevel = server_settings[u"debuglevel"]
1703
use_dbus = server_settings[u"use_dbus"]
1704
use_ipv6 = server_settings[u"use_ipv6"]
1706
if server_settings[u"servicename"] != u"Mandos":
940
debug = server_settings["debug"]
943
syslogger.setLevel(logging.WARNING)
944
console.setLevel(logging.WARNING)
946
if server_settings["servicename"] != "Mandos":
1707
947
syslogger.setFormatter(logging.Formatter
1708
(u'Mandos (%s) [%%(process)d]:'
1709
u' %%(levelname)s: %%(message)s'
1710
% server_settings[u"servicename"]))
948
('Mandos (%s): %%(levelname)s:'
950
% server_settings["servicename"]))
1712
952
# Parse config file with clients
1713
client_defaults = { u"timeout": u"1h",
1715
u"checker": u"fping -q -- %%(host)s",
1717
u"approved_delay": u"0s",
1718
u"approved_duration": u"1s",
953
client_defaults = { "timeout": "1h",
955
"checker": "fping -q -- %(host)s",
1720
client_config = configparser.SafeConfigParser(client_defaults)
1721
client_config.read(os.path.join(server_settings[u"configdir"],
1724
global mandos_dbus_service
1725
mandos_dbus_service = None
1727
tcp_server = MandosServer((server_settings[u"address"],
1728
server_settings[u"port"]),
1730
interface=server_settings[u"interface"],
1733
server_settings[u"priority"],
1735
pidfilename = u"/var/run/mandos.pid"
1737
pidfile = open(pidfilename, u"w")
1739
logger.error(u"Could not open file %r", pidfilename)
1742
uid = pwd.getpwnam(u"_mandos").pw_uid
1743
gid = pwd.getpwnam(u"_mandos").pw_gid
1746
uid = pwd.getpwnam(u"mandos").pw_uid
1747
gid = pwd.getpwnam(u"mandos").pw_gid
1750
uid = pwd.getpwnam(u"nobody").pw_uid
1751
gid = pwd.getpwnam(u"nobody").pw_gid
958
client_config = ConfigParser.SafeConfigParser(client_defaults)
959
client_config.read(os.path.join(server_settings["configdir"],
963
tcp_server = IPv6_TCPServer((server_settings["address"],
964
server_settings["port"]),
966
settings=server_settings,
968
pidfilename = "/var/run/mandos.pid"
970
pidfile = open(pidfilename, "w")
971
except IOError, error:
972
logger.error("Could not open file %r", pidfilename)
977
uid = pwd.getpwnam("mandos").pw_uid
980
uid = pwd.getpwnam("nobody").pw_uid
984
gid = pwd.getpwnam("mandos").pw_gid
987
gid = pwd.getpwnam("nogroup").pw_gid
1758
993
except OSError, error:
1759
994
if error[0] != errno.EPERM:
1762
# Enable all possible GnuTLS debugging
1765
if not debug and not debuglevel:
1766
syslogger.setLevel(logging.WARNING)
1767
console.setLevel(logging.WARNING)
1769
level = getattr(logging, debuglevel.upper())
1770
syslogger.setLevel(level)
1771
console.setLevel(level)
1774
# "Use a log level over 10 to enable all debugging options."
1776
gnutls.library.functions.gnutls_global_set_log_level(11)
1778
@gnutls.library.types.gnutls_log_func
1779
def debug_gnutls(level, string):
1780
logger.debug(u"GnuTLS: %s", string[:-1])
1782
(gnutls.library.functions
1783
.gnutls_global_set_log_function(debug_gnutls))
1785
# Redirect stdin so all checkers get /dev/null
1786
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1787
os.dup2(null, sys.stdin.fileno())
1791
# No console logging
1792
logger.removeHandler(console)
998
service = AvahiService(name = server_settings["servicename"],
999
servicetype = "_mandos._tcp", )
1000
if server_settings["interface"]:
1001
service.interface = (if_nametoindex
1002
(server_settings["interface"]))
1795
1004
global main_loop
1796
1007
# From the Avahi example code
1797
1008
DBusGMainLoop(set_as_default=True )
1798
1009
main_loop = gobject.MainLoop()
1799
1010
bus = dbus.SystemBus()
1011
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1012
avahi.DBUS_PATH_SERVER),
1013
avahi.DBUS_INTERFACE_SERVER)
1800
1014
# End of Avahi example code
1803
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1804
bus, do_not_queue=True)
1805
except dbus.exceptions.NameExistsException, e:
1806
logger.error(unicode(e) + u", disabling D-Bus")
1808
server_settings[u"use_dbus"] = False
1809
tcp_server.use_dbus = False
1810
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1811
service = AvahiService(name = server_settings[u"servicename"],
1812
servicetype = u"_mandos._tcp",
1813
protocol = protocol, bus = bus)
1814
if server_settings["interface"]:
1815
service.interface = (if_nametoindex
1816
(str(server_settings[u"interface"])))
1015
bus_name = dbus.service.BusName(u"org.mandos-system.Mandos", bus)
1017
def remove_from_clients(client):
1018
clients.remove(client)
1020
logger.critical(u"No clients left, exiting")
1023
clients.update(Set(Client(name = section,
1024
stop_hook = remove_from_clients,
1026
= dict(client_config.items(section)))
1027
for section in client_config.sections()))
1029
logger.critical(u"No clients defined")
1033
# Redirect stdin so all checkers get /dev/null
1034
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1035
os.dup2(null, sys.stdin.fileno())
1039
# No console logging
1040
logger.removeHandler(console)
1819
1041
# Close all input and output, do double fork, etc.
1822
global multiprocessing_manager
1823
multiprocessing_manager = multiprocessing.Manager()
1825
client_class = Client
1827
client_class = functools.partial(ClientDBus, bus = bus)
1828
def client_config_items(config, section):
1829
special_settings = {
1830
"approved_by_default":
1831
lambda: config.getboolean(section,
1832
"approved_by_default"),
1834
for name, value in config.items(section):
1836
yield (name, special_settings[name]())
1840
tcp_server.clients.update(set(
1841
client_class(name = section,
1842
config= dict(client_config_items(
1843
client_config, section)))
1844
for section in client_config.sections()))
1845
if not tcp_server.clients:
1846
logger.warning(u"No clients defined")
1851
pidfile.write(str(pid) + "\n")
1046
pidfile.write(str(pid) + "\n")
1853
1049
except IOError:
1854
1050
logger.error(u"Could not write to file %r with PID %d",