128
107
max_renames: integer; maximum number of renames
129
108
rename_count: integer; counter so we only rename after collisions
130
109
a sensible number of times
131
group: D-Bus Entry Group
133
bus: dbus.SystemBus()
135
111
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
136
servicetype = None, port = None, TXT = None,
137
domain = u"", host = u"", max_renames = 32768,
138
protocol = avahi.PROTO_UNSPEC, bus = None):
112
servicetype = None, port = None, TXT = None, domain = "",
113
host = "", max_renames = 32768):
139
114
self.interface = interface
141
116
self.type = servicetype
143
self.TXT = TXT if TXT is not None else []
144
122
self.domain = domain
146
124
self.rename_count = 0
147
125
self.max_renames = max_renames
148
self.protocol = protocol
149
self.group = None # our entry group
152
126
def rename(self):
153
127
"""Derived from the Avahi example code"""
154
128
if self.rename_count >= self.max_renames:
155
129
logger.critical(u"No suitable Zeroconf service name found"
156
130
u" after %i retries, exiting.",
157
131
self.rename_count)
158
raise AvahiServiceError(u"Too many renames")
159
self.name = self.server.GetAlternativeServiceName(self.name)
132
raise AvahiServiceError("Too many renames")
133
self.name = server.GetAlternativeServiceName(self.name)
160
134
logger.info(u"Changing Zeroconf service name to %r ...",
162
syslogger.setFormatter(logging.Formatter
163
(u'Mandos (%s) [%%(process)d]:'
164
u' %%(levelname)s: %%(message)s'
136
syslogger.setFormatter(logging.Formatter\
137
('Mandos (%s): %%(levelname)s:'
138
' %%(message)s' % self.name))
168
141
self.rename_count += 1
169
142
def remove(self):
170
143
"""Derived from the Avahi example code"""
171
if self.group is not None:
144
if group is not None:
174
147
"""Derived from the Avahi example code"""
175
if self.group is None:
176
self.group = dbus.Interface(
177
self.bus.get_object(avahi.DBUS_NAME,
178
self.server.EntryGroupNew()),
179
avahi.DBUS_INTERFACE_ENTRY_GROUP)
180
self.group.connect_to_signal('StateChanged',
182
.entry_group_state_changed)
150
group = dbus.Interface\
151
(bus.get_object(avahi.DBUS_NAME,
152
server.EntryGroupNew()),
153
avahi.DBUS_INTERFACE_ENTRY_GROUP)
154
group.connect_to_signal('StateChanged',
155
entry_group_state_changed)
183
156
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
184
self.name, self.type)
185
self.group.AddService(
188
dbus.UInt32(0), # flags
189
self.name, self.type,
190
self.domain, self.host,
191
dbus.UInt16(self.port),
192
avahi.string_array_to_txt_array(self.TXT))
194
def entry_group_state_changed(self, state, error):
195
"""Derived from the Avahi example code"""
196
logger.debug(u"Avahi state change: %i", state)
198
if state == avahi.ENTRY_GROUP_ESTABLISHED:
199
logger.debug(u"Zeroconf service established.")
200
elif state == avahi.ENTRY_GROUP_COLLISION:
201
logger.warning(u"Zeroconf service name collision.")
203
elif state == avahi.ENTRY_GROUP_FAILURE:
204
logger.critical(u"Avahi: Error in group state changed %s",
206
raise AvahiGroupError(u"State changed: %s"
209
"""Derived from the Avahi example code"""
210
if self.group is not None:
213
def server_state_changed(self, state):
214
"""Derived from the Avahi example code"""
215
if state == avahi.SERVER_COLLISION:
216
logger.error(u"Zeroconf server name collision")
218
elif state == avahi.SERVER_RUNNING:
221
"""Derived from the Avahi example code"""
222
if self.server is None:
223
self.server = dbus.Interface(
224
self.bus.get_object(avahi.DBUS_NAME,
225
avahi.DBUS_PATH_SERVER),
226
avahi.DBUS_INTERFACE_SERVER)
227
self.server.connect_to_signal(u"StateChanged",
228
self.server_state_changed)
229
self.server_state_changed(self.server.GetState())
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
232
173
class Client(object):
233
174
"""A representation of a client host served by this server.
236
name: string; from the config file, used in log messages and
176
name: string; from the config file, used in log messages
238
177
fingerprint: string (40 or 32 hexadecimal digits); used to
239
178
uniquely identify the client
240
secret: bytestring; sent verbatim (over TLS) to client
241
host: string; available for use by the checker command
242
created: datetime.datetime(); (UTC) object creation
243
last_enabled: datetime.datetime(); (UTC)
245
last_checked_ok: datetime.datetime(); (UTC) or None
246
timeout: datetime.timedelta(); How long from last_checked_ok
247
until this client is disabled
248
interval: datetime.timedelta(); How often to start a new checker
249
disable_hook: If set, called by disable() as disable_hook(self)
250
checker: subprocess.Popen(); a running checker process used
251
to see if the client lives.
252
'None' if no process is running.
179
secret: bytestring; sent verbatim (over TLS) to client
180
host: string; available for use by the checker command
181
created: datetime.datetime(); object creation, not client host
182
last_checked_ok: datetime.datetime() or None if not yet checked OK
183
timeout: datetime.timedelta(); How long from last_checked_ok
184
until this client is invalid
185
interval: datetime.timedelta(); How often to start a new checker
186
stop_hook: If set, called by stop() as stop_hook(self)
187
checker: subprocess.Popen(); a running checker process used
188
to see if the client lives.
189
'None' if no process is running.
253
190
checker_initiator_tag: a gobject event source tag, or None
254
disable_initiator_tag: - '' -
191
stop_initiator_tag: - '' -
255
192
checker_callback_tag: - '' -
256
193
checker_command: string; External command which is run to check if
257
194
client lives. %() expansions are done at
258
195
runtime with vars(self) as dict, so that for
259
196
instance %(name)s can be used in the command.
260
current_checker_command: string; current running checker_command
261
approved_delay: datetime.timedelta(); Time to wait for approval
262
_approved: bool(); 'None' if not yet approved/disapproved
263
approved_duration: datetime.timedelta(); Duration of one approval
198
_timeout: Real variable for 'timeout'
199
_interval: Real variable for 'interval'
200
_timeout_milliseconds: Used when calling gobject.timeout_add()
201
_interval_milliseconds: - '' -
267
def _timedelta_to_milliseconds(td):
268
"Convert a datetime.timedelta() to milliseconds"
269
return ((td.days * 24 * 60 * 60 * 1000)
270
+ (td.seconds * 1000)
271
+ (td.microseconds // 1000))
273
def timeout_milliseconds(self):
274
"Return the 'timeout' attribute in milliseconds"
275
return self._timedelta_to_milliseconds(self.timeout)
277
def interval_milliseconds(self):
278
"Return the 'interval' attribute in milliseconds"
279
return self._timedelta_to_milliseconds(self.interval)
281
def approved_delay_milliseconds(self):
282
return self._timedelta_to_milliseconds(self.approved_delay)
284
def __init__(self, name = None, disable_hook=None, config=None):
203
def _set_timeout(self, timeout):
204
"Setter function for 'timeout' attribute"
205
self._timeout = timeout
206
self._timeout_milliseconds = ((self.timeout.days
207
* 24 * 60 * 60 * 1000)
208
+ (self.timeout.seconds * 1000)
209
+ (self.timeout.microseconds
211
timeout = property(lambda self: self._timeout,
214
def _set_interval(self, interval):
215
"Setter function for 'interval' attribute"
216
self._interval = interval
217
self._interval_milliseconds = ((self.interval.days
218
* 24 * 60 * 60 * 1000)
219
+ (self.interval.seconds
221
+ (self.interval.microseconds
223
interval = property(lambda self: self._interval,
226
def __init__(self, name = None, stop_hook=None, config=None):
285
227
"""Note: the 'checker' key in 'config' sets the
286
228
'checker_command' attribute and *not* the 'checker'
289
230
if config is None:
291
233
logger.debug(u"Creating client %r", self.name)
292
234
# Uppercase and remove spaces from fingerprint for later
293
235
# comparison purposes with return value from the fingerprint()
295
self.fingerprint = (config[u"fingerprint"].upper()
237
self.fingerprint = config["fingerprint"].upper()\
297
239
logger.debug(u" Fingerprint: %s", self.fingerprint)
298
if u"secret" in config:
299
self.secret = config[u"secret"].decode(u"base64")
300
elif u"secfile" in config:
301
with open(os.path.expanduser(os.path.expandvars
302
(config[u"secfile"])),
304
self.secret = secfile.read()
240
if "secret" in config:
241
self.secret = config["secret"].decode(u"base64")
242
elif "secfile" in config:
243
secfile = open(config["secfile"])
244
self.secret = secfile.read()
306
#XXX Need to allow secret on demand!
307
247
raise TypeError(u"No secret or secfile for client %s"
309
self.host = config.get(u"host", u"")
310
self.created = datetime.datetime.utcnow()
312
self.last_enabled = None
249
self.host = config.get("host", "")
250
self.created = datetime.datetime.now()
313
251
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
252
self.timeout = string_to_delta(config["timeout"])
253
self.interval = string_to_delta(config["interval"])
254
self.stop_hook = stop_hook
317
255
self.checker = None
318
256
self.checker_initiator_tag = None
319
self.disable_initiator_tag = None
257
self.stop_initiator_tag = None
320
258
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.approvals_pending = 0
325
self._approved = None
326
self.approved_by_default = config.get(u"approved_by_default",
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())
334
def send_changedstate(self):
335
self.changedstate.acquire()
336
self.changedstate.notify_all()
337
self.changedstate.release()
259
self.check_command = config["checker"]
340
261
"""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()
346
262
# Schedule a new checker to be started an 'interval' from now,
347
263
# and every interval from then on.
348
self.checker_initiator_tag = (gobject.timeout_add
349
(self.interval_milliseconds(),
351
# Schedule a disable() when 'timeout' has passed
352
self.disable_initiator_tag = (gobject.timeout_add
353
(self.timeout_milliseconds(),
264
self.checker_initiator_tag = gobject.timeout_add\
265
(self._interval_milliseconds,
356
267
# Also start a new checker *right now*.
357
268
self.start_checker()
359
def disable(self, quiet=True):
360
"""Disable this client."""
361
if not getattr(self, "enabled", False):
269
# Schedule a stop() when 'timeout' has passed
270
self.stop_initiator_tag = gobject.timeout_add\
271
(self._timeout_milliseconds,
275
The possibility that a client might be restarted is left open,
276
but not currently used."""
277
# If this client doesn't have a secret, it is already stopped.
278
if hasattr(self, "secret") and self.secret:
279
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):
283
if getattr(self, "stop_initiator_tag", False):
284
gobject.source_remove(self.stop_initiator_tag)
285
self.stop_initiator_tag = None
286
if getattr(self, "checker_initiator_tag", False):
371
287
gobject.source_remove(self.checker_initiator_tag)
372
288
self.checker_initiator_tag = None
373
289
self.stop_checker()
374
if self.disable_hook:
375
self.disable_hook(self)
377
292
# Do not run this again if called by a gobject.timeout_add
380
294
def __del__(self):
381
self.disable_hook = None
384
def checker_callback(self, pid, condition, command):
295
self.stop_hook = None
297
def checker_callback(self, pid, condition):
385
298
"""The checker has completed, so take appropriate actions."""
299
now = datetime.datetime.now()
386
300
self.checker_callback_tag = None
387
301
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",
302
if os.WIFEXITED(condition) \
303
and (os.WEXITSTATUS(condition) == 0):
304
logger.info(u"Checker for %(name)s succeeded",
306
self.last_checked_ok = now
307
gobject.source_remove(self.stop_initiator_tag)
308
self.stop_initiator_tag = gobject.timeout_add\
309
(self._timeout_milliseconds,
311
elif not os.WIFEXITED(condition):
398
312
logger.warning(u"Checker for %(name)s crashed?",
401
def checked_ok(self):
402
"""Bump up the timeout for this client.
404
This should only be called when the client has been seen,
407
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(),
315
logger.info(u"Checker for %(name)s failed",
413
317
def start_checker(self):
414
318
"""Start a new checker subprocess if one is not running.
416
319
If a checker already exists, leave it running and do
418
321
# The reason for not killing a running checker is that if we
466
350
# always replaced by /dev/null.)
467
351
self.checker = subprocess.Popen(command,
469
shell=True, cwd=u"/")
470
self.checker_callback_tag = (gobject.child_watch_add
472
self.checker_callback,
474
# The checker may have completed before the gobject
475
# watch was added. Check for this.
476
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
478
gobject.source_remove(self.checker_callback_tag)
479
self.checker_callback(pid, status, command)
354
self.checker_callback_tag = gobject.child_watch_add\
356
self.checker_callback)
480
357
except OSError, error:
481
358
logger.error(u"Failed to start subprocess: %s",
483
360
# Re-run this periodically if run by gobject.timeout_add
486
362
def stop_checker(self):
487
363
"""Force the checker process, if any, to stop."""
488
364
if self.checker_callback_tag:
489
365
gobject.source_remove(self.checker_callback_tag)
490
366
self.checker_callback_tag = None
491
if getattr(self, u"checker", None) is None:
367
if getattr(self, "checker", None) is None:
493
369
logger.debug(u"Stopping checker for %(name)s", vars(self))
495
371
os.kill(self.checker.pid, signal.SIGTERM)
497
373
#if self.checker.poll() is None:
498
374
# os.kill(self.checker.pid, signal.SIGKILL)
499
375
except OSError, error:
500
376
if error.errno != errno.ESRCH: # No such process
502
378
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):
704
Client.__init__(self, *args, **kwargs)
705
# Only now, when this client is initialized, can it show up on
707
self.dbus_object_path = (dbus.ObjectPath
709
+ self.name.replace(u".", u"_")))
710
DBusObjectWithProperties.__init__(self, self.bus,
711
self.dbus_object_path)
714
def _datetime_to_dbus(dt, variant_level=0):
715
"""Convert a UTC datetime.datetime() to a D-Bus type."""
716
return dbus.String(dt.isoformat(),
717
variant_level=variant_level)
720
oldstate = getattr(self, u"enabled", False)
721
r = Client.enable(self)
722
if oldstate != self.enabled:
724
self.PropertyChanged(dbus.String(u"enabled"),
725
dbus.Boolean(True, variant_level=1))
726
self.PropertyChanged(
727
dbus.String(u"last_enabled"),
728
self._datetime_to_dbus(self.last_enabled,
732
def disable(self, quiet = False):
733
oldstate = getattr(self, u"enabled", False)
734
r = Client.disable(self, quiet=quiet)
735
if not quiet and oldstate != self.enabled:
737
self.PropertyChanged(dbus.String(u"enabled"),
738
dbus.Boolean(False, variant_level=1))
741
def __del__(self, *args, **kwargs):
743
self.remove_from_connection()
746
if hasattr(DBusObjectWithProperties, u"__del__"):
747
DBusObjectWithProperties.__del__(self, *args, **kwargs)
748
Client.__del__(self, *args, **kwargs)
750
def checker_callback(self, pid, condition, command,
752
self.checker_callback_tag = None
755
self.PropertyChanged(dbus.String(u"checker_running"),
756
dbus.Boolean(False, variant_level=1))
757
if os.WIFEXITED(condition):
758
exitstatus = os.WEXITSTATUS(condition)
760
self.CheckerCompleted(dbus.Int16(exitstatus),
761
dbus.Int64(condition),
762
dbus.String(command))
765
self.CheckerCompleted(dbus.Int16(-1),
766
dbus.Int64(condition),
767
dbus.String(command))
769
return Client.checker_callback(self, pid, condition, command,
772
def checked_ok(self, *args, **kwargs):
773
r = Client.checked_ok(self, *args, **kwargs)
775
self.PropertyChanged(
776
dbus.String(u"last_checked_ok"),
777
(self._datetime_to_dbus(self.last_checked_ok,
781
def start_checker(self, *args, **kwargs):
782
old_checker = self.checker
783
if self.checker is not None:
784
old_checker_pid = self.checker.pid
786
old_checker_pid = None
787
r = Client.start_checker(self, *args, **kwargs)
788
# Only if new checker process was started
789
if (self.checker is not None
790
and old_checker_pid != self.checker.pid):
792
self.CheckerStarted(self.current_checker_command)
793
self.PropertyChanged(
794
dbus.String(u"checker_running"),
795
dbus.Boolean(True, variant_level=1))
798
def stop_checker(self, *args, **kwargs):
799
old_checker = getattr(self, u"checker", None)
800
r = Client.stop_checker(self, *args, **kwargs)
801
if (old_checker is not None
802
and getattr(self, u"checker", None) is None):
803
self.PropertyChanged(dbus.String(u"checker_running"),
804
dbus.Boolean(False, variant_level=1))
807
def _reset_approved(self):
808
self._approved = None
811
def approve(self, value=True):
812
self._approved = value
813
gobject.timeout_add(self._timedelta_to_milliseconds(self.approved_duration, self._reset_approved))
815
def approved_pending(self):
816
return self.approvals_pending > 0
819
## D-Bus methods, signals & properties
820
_interface = u"se.bsnet.fukt.Mandos.Client"
824
# CheckerCompleted - signal
825
@dbus.service.signal(_interface, signature=u"nxs")
826
def CheckerCompleted(self, exitcode, waitstatus, command):
830
# CheckerStarted - signal
831
@dbus.service.signal(_interface, signature=u"s")
832
def CheckerStarted(self, command):
836
# PropertyChanged - signal
837
@dbus.service.signal(_interface, signature=u"sv")
838
def PropertyChanged(self, property, value):
843
# Is sent after succesfull transfer of secret from mandos-server to mandos-client
844
@dbus.service.signal(_interface)
850
@dbus.service.signal(_interface, signature=u"s")
851
def Rejected(self, reason):
855
# NeedApproval - signal
856
@dbus.service.signal(_interface, signature=u"db")
857
def NeedApproval(self, timeout, default):
864
@dbus.service.method(_interface, in_signature=u"b")
865
def Approve(self, value):
869
@dbus.service.method(_interface)
871
return self.checked_ok()
874
@dbus.service.method(_interface)
879
# StartChecker - method
880
@dbus.service.method(_interface)
881
def StartChecker(self):
886
@dbus.service.method(_interface)
891
# StopChecker - method
892
@dbus.service.method(_interface)
893
def StopChecker(self):
898
# approved_pending - property
899
@dbus_service_property(_interface, signature=u"b", access=u"read")
900
def approved_pending_dbus_property(self):
901
return dbus.Boolean(self.approved_pending())
903
# approved_by_default - property
904
@dbus_service_property(_interface, signature=u"b",
906
def approved_by_default_dbus_property(self):
907
return dbus.Boolean(self.approved_by_default)
909
# approved_delay - property
910
@dbus_service_property(_interface, signature=u"t",
912
def approved_delay_dbus_property(self):
913
return dbus.UInt64(self.approved_delay_milliseconds())
915
# approved_duration - property
916
@dbus_service_property(_interface, signature=u"t",
918
def approved_duration_dbus_property(self):
919
return dbus.UInt64(self._timedelta_to_milliseconds(
920
self.approved_duration))
923
@dbus_service_property(_interface, signature=u"s", access=u"read")
924
def name_dbus_property(self):
925
return dbus.String(self.name)
927
# fingerprint - property
928
@dbus_service_property(_interface, signature=u"s", access=u"read")
929
def fingerprint_dbus_property(self):
930
return dbus.String(self.fingerprint)
933
@dbus_service_property(_interface, signature=u"s",
935
def host_dbus_property(self, value=None):
936
if value is None: # get
937
return dbus.String(self.host)
940
self.PropertyChanged(dbus.String(u"host"),
941
dbus.String(value, variant_level=1))
944
@dbus_service_property(_interface, signature=u"s", access=u"read")
945
def created_dbus_property(self):
946
return dbus.String(self._datetime_to_dbus(self.created))
948
# last_enabled - property
949
@dbus_service_property(_interface, signature=u"s", access=u"read")
950
def last_enabled_dbus_property(self):
951
if self.last_enabled is None:
952
return dbus.String(u"")
953
return dbus.String(self._datetime_to_dbus(self.last_enabled))
956
@dbus_service_property(_interface, signature=u"b",
958
def enabled_dbus_property(self, value=None):
959
if value is None: # get
960
return dbus.Boolean(self.enabled)
966
# last_checked_ok - property
967
@dbus_service_property(_interface, signature=u"s",
969
def last_checked_ok_dbus_property(self, value=None):
970
if value is not None:
379
def still_valid(self):
380
"""Has the timeout not yet passed for this client?"""
381
now = datetime.datetime.now()
973
382
if self.last_checked_ok is None:
974
return dbus.String(u"")
975
return dbus.String(self._datetime_to_dbus(self
979
@dbus_service_property(_interface, signature=u"t",
981
def timeout_dbus_property(self, value=None):
982
if value is None: # get
983
return dbus.UInt64(self.timeout_milliseconds())
984
self.timeout = datetime.timedelta(0, 0, 0, value)
986
self.PropertyChanged(dbus.String(u"timeout"),
987
dbus.UInt64(value, variant_level=1))
988
if getattr(self, u"disable_initiator_tag", None) is None:
991
gobject.source_remove(self.disable_initiator_tag)
992
self.disable_initiator_tag = None
994
_timedelta_to_milliseconds((self
1000
# The timeout has passed
1003
self.disable_initiator_tag = (gobject.timeout_add
1004
(time_to_die, self.disable))
1006
# interval - property
1007
@dbus_service_property(_interface, signature=u"t",
1008
access=u"readwrite")
1009
def interval_dbus_property(self, value=None):
1010
if value is None: # get
1011
return dbus.UInt64(self.interval_milliseconds())
1012
self.interval = datetime.timedelta(0, 0, 0, value)
1014
self.PropertyChanged(dbus.String(u"interval"),
1015
dbus.UInt64(value, variant_level=1))
1016
if getattr(self, u"checker_initiator_tag", None) is None:
1018
# Reschedule checker run
1019
gobject.source_remove(self.checker_initiator_tag)
1020
self.checker_initiator_tag = (gobject.timeout_add
1021
(value, self.start_checker))
1022
self.start_checker() # Start one now, too
1024
# checker - property
1025
@dbus_service_property(_interface, signature=u"s",
1026
access=u"readwrite")
1027
def checker_dbus_property(self, value=None):
1028
if value is None: # get
1029
return dbus.String(self.checker_command)
1030
self.checker_command = value
1032
self.PropertyChanged(dbus.String(u"checker"),
1033
dbus.String(self.checker_command,
1036
# checker_running - property
1037
@dbus_service_property(_interface, signature=u"b",
1038
access=u"readwrite")
1039
def checker_running_dbus_property(self, value=None):
1040
if value is None: # get
1041
return dbus.Boolean(self.checker is not None)
1043
self.start_checker()
1047
# object_path - property
1048
@dbus_service_property(_interface, signature=u"o", access=u"read")
1049
def object_path_dbus_property(self):
1050
return self.dbus_object_path # is already a dbus.ObjectPath
1053
@dbus_service_property(_interface, signature=u"ay",
1054
access=u"write", byte_arrays=True)
1055
def secret_dbus_property(self, value):
1056
self.secret = str(value)
1061
class ProxyClient(object):
1062
def __init__(self, child_pipe, fpr, address):
1063
self._pipe = child_pipe
1064
self._pipe.send(('init', fpr, address))
1065
if not self._pipe.recv():
1068
def __getattribute__(self, name):
1069
if(name == '_pipe'):
1070
return super(ProxyClient, self).__getattribute__(name)
1071
self._pipe.send(('getattr', name))
1072
data = self._pipe.recv()
1073
if data[0] == 'data':
1075
if data[0] == 'function':
1076
def func(*args, **kwargs):
1077
self._pipe.send(('funcall', name, args, kwargs))
1078
return self._pipe.recv()[1]
1081
def __setattr__(self, name, value):
1082
if(name == '_pipe'):
1083
return super(ProxyClient, self).__setattr__(name, value)
1084
self._pipe.send(('setattr', name, value))
1087
class ClientHandler(socketserver.BaseRequestHandler, object):
1088
"""A class to handle client connections.
1090
Instantiated once for each connection to handle it.
383
return now < (self.created + self.timeout)
385
return now < (self.last_checked_ok + self.timeout)
388
def peer_certificate(session):
389
"Return the peer's OpenPGP certificate as a bytestring"
390
# If not an OpenPGP certificate...
391
if gnutls.library.functions.gnutls_certificate_type_get\
392
(session._c_object) \
393
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
394
# ...do the normal thing
395
return session.peer_certificate
396
list_size = ctypes.c_uint()
397
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
398
(session._c_object, ctypes.byref(list_size))
399
if list_size.value == 0:
402
return ctypes.string_at(cert.data, cert.size)
405
def fingerprint(openpgp):
406
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
407
# New GnuTLS "datum" with the OpenPGP public key
408
datum = gnutls.library.types.gnutls_datum_t\
409
(ctypes.cast(ctypes.c_char_p(openpgp),
410
ctypes.POINTER(ctypes.c_ubyte)),
411
ctypes.c_uint(len(openpgp)))
412
# New empty GnuTLS certificate
413
crt = gnutls.library.types.gnutls_openpgp_crt_t()
414
gnutls.library.functions.gnutls_openpgp_crt_init\
416
# Import the OpenPGP public key into the certificate
417
gnutls.library.functions.gnutls_openpgp_crt_import\
418
(crt, ctypes.byref(datum),
419
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
420
# Verify the self signature in the key
421
crtverify = ctypes.c_uint()
422
gnutls.library.functions.gnutls_openpgp_crt_verify_self\
423
(crt, 0, ctypes.byref(crtverify))
424
if crtverify.value != 0:
425
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
426
raise gnutls.errors.CertificateSecurityError("Verify failed")
427
# New buffer for the fingerprint
428
buf = ctypes.create_string_buffer(20)
429
buf_len = ctypes.c_size_t()
430
# Get the fingerprint from the certificate into the buffer
431
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
432
(crt, ctypes.byref(buf), ctypes.byref(buf_len))
433
# Deinit the certificate
434
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
435
# Convert the buffer to a Python bytestring
436
fpr = ctypes.string_at(buf, buf_len.value)
437
# Convert the bytestring to hexadecimal notation
438
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
442
class TCP_handler(SocketServer.BaseRequestHandler, object):
443
"""A TCP request handler class.
444
Instantiated by IPv6_TCPServer for each request to handle it.
1091
445
Note: This will run in its own forked process."""
1093
447
def handle(self):
1094
with contextlib.closing(self.server.child_pipe) as child_pipe:
1095
logger.info(u"TCP connection from: %s",
1096
unicode(self.client_address))
1097
logger.debug(u"Pipe FD: %d",
1098
self.server.child_pipe.fileno())
1100
session = (gnutls.connection
1101
.ClientSession(self.request,
1103
.X509Credentials()))
1105
# Note: gnutls.connection.X509Credentials is really a
1106
# generic GnuTLS certificate credentials object so long as
1107
# no X.509 keys are added to it. Therefore, we can use it
1108
# here despite using OpenPGP certificates.
1110
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1111
# u"+AES-256-CBC", u"+SHA1",
1112
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1114
# Use a fallback default, since this MUST be set.
1115
priority = self.server.gnutls_priority
1116
if priority is None:
1117
priority = u"NORMAL"
1118
(gnutls.library.functions
1119
.gnutls_priority_set_direct(session._c_object,
1122
# Start communication using the Mandos protocol
1123
# Get protocol number
1124
line = self.request.makefile().readline()
1125
logger.debug(u"Protocol version: %r", line)
1127
if int(line.strip().split()[0]) > 1:
1129
except (ValueError, IndexError, RuntimeError), error:
1130
logger.error(u"Unknown protocol version: %s", error)
1133
# Start GnuTLS connection
1136
except gnutls.errors.GNUTLSError, error:
1137
logger.warning(u"Handshake failed: %s", error)
1138
# Do not run session.bye() here: the session is not
1139
# established. Just abandon the request.
1141
logger.debug(u"Handshake succeeded")
1143
approval_required = False
1146
fpr = self.fingerprint(self.peer_certificate
1148
except (TypeError, gnutls.errors.GNUTLSError), error:
1149
logger.warning(u"Bad certificate: %s", error)
1151
logger.debug(u"Fingerprint: %s", fpr)
1154
client = ProxyClient(child_pipe, fpr,
1155
self.client_address)
1159
if client.approved_delay:
1160
delay = client.approved_delay
1161
client.approvals_pending += 1
1162
approval_required = True
1165
if not client.enabled:
1166
logger.warning(u"Client %s is disabled",
1168
if self.server.use_dbus:
1170
client.Rejected("Disabled")
1173
if client._approved or not client.approved_delay:
1174
#We are approved or approval is disabled
1176
elif client._approved is None:
1177
logger.info(u"Client %s need approval",
1179
if self.server.use_dbus:
1181
client.NeedApproval(
1182
client.approved_delay_milliseconds(),
1183
client.approved_by_default)
1185
logger.warning(u"Client %s was not approved",
1187
if self.server.use_dbus:
1189
client.Rejected("Disapproved")
1192
#wait until timeout or approved
1193
#x = float(client._timedelta_to_milliseconds(delay))
1194
time = datetime.datetime.now()
1195
client.changedstate.acquire()
1196
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1197
client.changedstate.release()
1198
time2 = datetime.datetime.now()
1199
if (time2 - time) >= delay:
1200
if not client.approved_by_default:
1201
logger.warning("Client %s timed out while"
1202
" waiting for approval",
1204
if self.server.use_dbus:
1206
client.Rejected("Time out")
1211
delay -= time2 - time
1214
while sent_size < len(client.secret):
1215
# XXX handle session exception
1216
sent = session.send(client.secret[sent_size:])
1217
logger.debug(u"Sent: %d, remaining: %d",
1218
sent, len(client.secret)
1219
- (sent_size + sent))
1222
logger.info(u"Sending secret to %s", client.name)
1223
# bump the timeout as if seen
1225
if self.server.use_dbus:
1230
if approval_required:
1231
client.approvals_pending -= 1
1235
def peer_certificate(session):
1236
"Return the peer's OpenPGP certificate as a bytestring"
1237
# If not an OpenPGP certificate...
1238
if (gnutls.library.functions
1239
.gnutls_certificate_type_get(session._c_object)
1240
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1241
# ...do the normal thing
1242
return session.peer_certificate
1243
list_size = ctypes.c_uint(1)
1244
cert_list = (gnutls.library.functions
1245
.gnutls_certificate_get_peers
1246
(session._c_object, ctypes.byref(list_size)))
1247
if not bool(cert_list) and list_size.value != 0:
1248
raise gnutls.errors.GNUTLSError(u"error getting peer"
1250
if list_size.value == 0:
1253
return ctypes.string_at(cert.data, cert.size)
1256
def fingerprint(openpgp):
1257
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1258
# New GnuTLS "datum" with the OpenPGP public key
1259
datum = (gnutls.library.types
1260
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1263
ctypes.c_uint(len(openpgp))))
1264
# New empty GnuTLS certificate
1265
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1266
(gnutls.library.functions
1267
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1268
# Import the OpenPGP public key into the certificate
1269
(gnutls.library.functions
1270
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1271
gnutls.library.constants
1272
.GNUTLS_OPENPGP_FMT_RAW))
1273
# Verify the self signature in the key
1274
crtverify = ctypes.c_uint()
1275
(gnutls.library.functions
1276
.gnutls_openpgp_crt_verify_self(crt, 0,
1277
ctypes.byref(crtverify)))
1278
if crtverify.value != 0:
1279
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1280
raise (gnutls.errors.CertificateSecurityError
1282
# New buffer for the fingerprint
1283
buf = ctypes.create_string_buffer(20)
1284
buf_len = ctypes.c_size_t()
1285
# Get the fingerprint from the certificate into the buffer
1286
(gnutls.library.functions
1287
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1288
ctypes.byref(buf_len)))
1289
# Deinit the certificate
1290
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1291
# Convert the buffer to a Python bytestring
1292
fpr = ctypes.string_at(buf, buf_len.value)
1293
# Convert the bytestring to hexadecimal notation
1294
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1298
class MultiprocessingMixIn(object):
1299
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1300
def sub_process_main(self, request, address):
1302
self.finish_request(request, address)
1304
self.handle_error(request, address)
1305
self.close_request(request)
1307
def process_request(self, request, address):
1308
"""Start a new process to process the request."""
1309
multiprocessing.Process(target = self.sub_process_main,
1310
args = (request, address)).start()
1312
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1313
""" adds a pipe to the MixIn """
1314
def process_request(self, request, client_address):
1315
"""Overrides and wraps the original process_request().
1317
This function creates a new pipe in self.pipe
1319
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1321
super(MultiprocessingMixInWithPipe,
1322
self).process_request(request, client_address)
1323
self.child_pipe.close()
1324
self.add_pipe(parent_pipe)
1326
def add_pipe(self, parent_pipe):
1327
"""Dummy function; override as necessary"""
1330
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1331
socketserver.TCPServer, object):
1332
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
448
logger.info(u"TCP connection from: %s",
449
unicode(self.client_address))
450
session = gnutls.connection.ClientSession\
451
(self.request, gnutls.connection.X509Credentials())
453
line = self.request.makefile().readline()
454
logger.debug(u"Protocol version: %r", line)
456
if int(line.strip().split()[0]) > 1:
458
except (ValueError, IndexError, RuntimeError), error:
459
logger.error(u"Unknown protocol version: %s", error)
462
# Note: gnutls.connection.X509Credentials is really a generic
463
# GnuTLS certificate credentials object so long as no X.509
464
# keys are added to it. Therefore, we can use it here despite
465
# using OpenPGP certificates.
467
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
468
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
470
priority = "NORMAL" # Fallback default, since this
472
if self.server.settings["priority"]:
473
priority = self.server.settings["priority"]
474
gnutls.library.functions.gnutls_priority_set_direct\
475
(session._c_object, priority, None)
479
except gnutls.errors.GNUTLSError, error:
480
logger.warning(u"Handshake failed: %s", error)
481
# Do not run session.bye() here: the session is not
482
# established. Just abandon the request.
485
fpr = fingerprint(peer_certificate(session))
486
except (TypeError, gnutls.errors.GNUTLSError), error:
487
logger.warning(u"Bad certificate: %s", error)
490
logger.debug(u"Fingerprint: %s", fpr)
492
for c in self.server.clients:
493
if c.fingerprint == fpr:
497
logger.warning(u"Client not found for fingerprint: %s",
501
# Have to check if client.still_valid(), since it is possible
502
# that the client timed out while establishing the GnuTLS
504
if not client.still_valid():
505
logger.warning(u"Client %(name)s is invalid",
510
while sent_size < len(client.secret):
511
sent = session.send(client.secret[sent_size:])
512
logger.debug(u"Sent: %d, remaining: %d",
513
sent, len(client.secret)
514
- (sent_size + sent))
519
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
520
"""IPv6 TCP server. Accepts 'None' as address and/or port.
522
settings: Server settings
523
clients: Set() of Client objects
1335
524
enabled: Boolean; whether this server is activated yet
1336
interface: None or a network interface name (string)
1337
use_ipv6: Boolean; to use IPv6 or not
1339
def __init__(self, server_address, RequestHandlerClass,
1340
interface=None, use_ipv6=True):
1341
self.interface = interface
1343
self.address_family = socket.AF_INET6
1344
socketserver.TCPServer.__init__(self, server_address,
1345
RequestHandlerClass)
526
address_family = socket.AF_INET6
527
def __init__(self, *args, **kwargs):
528
if "settings" in kwargs:
529
self.settings = kwargs["settings"]
530
del kwargs["settings"]
531
if "clients" in kwargs:
532
self.clients = kwargs["clients"]
533
del kwargs["clients"]
535
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1346
536
def server_bind(self):
1347
537
"""This overrides the normal server_bind() function
1348
538
to bind to an interface if one was specified, and also NOT to
1349
539
bind to an address or port if they were not specified."""
1350
if self.interface is not None:
1351
if SO_BINDTODEVICE is None:
1352
logger.error(u"SO_BINDTODEVICE does not exist;"
1353
u" cannot bind to interface %s",
1357
self.socket.setsockopt(socket.SOL_SOCKET,
1361
except socket.error, error:
1362
if error[0] == errno.EPERM:
1363
logger.error(u"No permission to"
1364
u" bind to interface %s",
1366
elif error[0] == errno.ENOPROTOOPT:
1367
logger.error(u"SO_BINDTODEVICE not available;"
1368
u" cannot bind to interface %s",
540
if self.settings["interface"]:
541
# 25 is from /usr/include/asm-i486/socket.h
542
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
544
self.socket.setsockopt(socket.SOL_SOCKET,
546
self.settings["interface"])
547
except socket.error, error:
548
if error[0] == errno.EPERM:
549
logger.error(u"No permission to"
550
u" bind to interface %s",
551
self.settings["interface"])
1372
554
# Only bind(2) the socket if we really need to.
1373
555
if self.server_address[0] or self.server_address[1]:
1374
556
if not self.server_address[0]:
1375
if self.address_family == socket.AF_INET6:
1376
any_address = u"::" # in6addr_any
1378
any_address = socket.INADDR_ANY
1379
self.server_address = (any_address,
558
self.server_address = (in6addr_any,
1380
559
self.server_address[1])
1381
560
elif not self.server_address[1]:
1382
561
self.server_address = (self.server_address[0],
1384
# if self.interface:
563
# if self.settings["interface"]:
1385
564
# self.server_address = (self.server_address[0],
1388
567
# if_nametoindex
1390
return socketserver.TCPServer.server_bind(self)
1393
class MandosServer(IPv6_TCPServer):
1397
clients: set of Client objects
1398
gnutls_priority GnuTLS priority string
1399
use_dbus: Boolean; to emit D-Bus signals or not
1401
Assumes a gobject.MainLoop event loop.
1403
def __init__(self, server_address, RequestHandlerClass,
1404
interface=None, use_ipv6=True, clients=None,
1405
gnutls_priority=None, use_dbus=True):
1406
self.enabled = False
1407
self.clients = clients
1408
if self.clients is None:
1409
self.clients = set()
1410
self.use_dbus = use_dbus
1411
self.gnutls_priority = gnutls_priority
1412
IPv6_TCPServer.__init__(self, server_address,
1413
RequestHandlerClass,
1414
interface = interface,
1415
use_ipv6 = use_ipv6)
570
return super(IPv6_TCPServer, self).server_bind()
1416
571
def server_activate(self):
1417
572
if self.enabled:
1418
return socketserver.TCPServer.server_activate(self)
573
return super(IPv6_TCPServer, self).server_activate()
1419
574
def enable(self):
1420
575
self.enabled = True
1421
def add_pipe(self, parent_pipe):
1422
# Call "handle_ipc" for both data and EOF events
1423
gobject.io_add_watch(parent_pipe.fileno(),
1424
gobject.IO_IN | gobject.IO_HUP,
1425
functools.partial(self.handle_ipc,
1426
parent_pipe = parent_pipe))
1428
def handle_ipc(self, source, condition, parent_pipe=None,
1429
client_object=None):
1431
gobject.IO_IN: u"IN", # There is data to read.
1432
gobject.IO_OUT: u"OUT", # Data can be written (without
1434
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1435
gobject.IO_ERR: u"ERR", # Error condition.
1436
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1437
# broken, usually for pipes and
1440
conditions_string = ' | '.join(name
1442
condition_names.iteritems()
1443
if cond & condition)
1444
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1447
# error or the other end of multiprocessing.Pipe has closed
1448
if condition & gobject.IO_HUP or condition & gobject.IO_ERR:
1451
# Read a request from the child
1452
request = parent_pipe.recv()
1453
logger.debug(u"IPC request: %s", repr(request))
1454
command = request[0]
1456
if command == 'init':
1458
address = request[2]
1460
for c in self.clients:
1461
if c.fingerprint == fpr:
1465
logger.warning(u"Client not found for fingerprint: %s, ad"
1466
u"dress: %s", fpr, address)
1469
mandos_dbus_service.ClientNotFound(fpr, address)
1470
parent_pipe.send(False)
1473
gobject.io_add_watch(parent_pipe.fileno(),
1474
gobject.IO_IN | gobject.IO_HUP,
1475
functools.partial(self.handle_ipc,
1476
parent_pipe = parent_pipe,
1477
client_object = client))
1478
parent_pipe.send(True)
1479
# remove the old hook in favor of the new above hook on same fileno
1481
if command == 'funcall':
1482
funcname = request[1]
1486
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1488
if command == 'getattr':
1489
attrname = request[1]
1490
if callable(client_object.__getattribute__(attrname)):
1491
parent_pipe.send(('function',))
1493
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1495
if command == 'setattr':
1496
attrname = request[1]
1498
setattr(client_object, attrname, value)
1503
578
def string_to_delta(interval):
1504
579
"""Parse a string and return a datetime.timedelta
1506
>>> string_to_delta(u'7d')
581
>>> string_to_delta('7d')
1507
582
datetime.timedelta(7)
1508
>>> string_to_delta(u'60s')
583
>>> string_to_delta('60s')
1509
584
datetime.timedelta(0, 60)
1510
>>> string_to_delta(u'60m')
585
>>> string_to_delta('60m')
1511
586
datetime.timedelta(0, 3600)
1512
>>> string_to_delta(u'24h')
587
>>> string_to_delta('24h')
1513
588
datetime.timedelta(1)
1514
589
>>> string_to_delta(u'1w')
1515
590
datetime.timedelta(7)
1516
>>> string_to_delta(u'5m 30s')
591
>>> string_to_delta('5m 30s')
1517
592
datetime.timedelta(0, 330)
1519
594
timevalue = datetime.timedelta(0)
1628
714
# Default values for config file for server-global settings
1629
server_defaults = { u"interface": u"",
1634
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1635
u"servicename": u"Mandos",
1636
u"use_dbus": u"True",
1637
u"use_ipv6": u"True",
715
server_defaults = { "interface": "",
720
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
721
"servicename": "Mandos",
1640
724
# Parse config file for server-global settings
1641
server_config = configparser.SafeConfigParser(server_defaults)
725
server_config = ConfigParser.SafeConfigParser(server_defaults)
1642
726
del server_defaults
1643
server_config.read(os.path.join(options.configdir,
727
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1645
728
# Convert the SafeConfigParser object to a dict
1646
729
server_settings = server_config.defaults()
1647
# Use the appropriate methods on the non-string config options
1648
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1649
server_settings[option] = server_config.getboolean(u"DEFAULT",
1651
if server_settings["port"]:
1652
server_settings["port"] = server_config.getint(u"DEFAULT",
730
# Use getboolean on the boolean config option
731
server_settings["debug"] = server_config.getboolean\
1654
733
del server_config
1656
735
# Override the settings from the config file with command line
1657
736
# options, if set.
1658
for option in (u"interface", u"address", u"port", u"debug",
1659
u"priority", u"servicename", u"configdir",
1660
u"use_dbus", u"use_ipv6"):
737
for option in ("interface", "address", "port", "debug",
738
"priority", "servicename", "configdir"):
1661
739
value = getattr(options, option)
1662
740
if value is not None:
1663
741
server_settings[option] = value
1665
# Force all strings to be unicode
1666
for option in server_settings.keys():
1667
if type(server_settings[option]) is str:
1668
server_settings[option] = unicode(server_settings[option])
1669
743
# Now we have our good server settings in "server_settings"
1671
##################################################################
1674
debug = server_settings[u"debug"]
1675
use_dbus = server_settings[u"use_dbus"]
1676
use_ipv6 = server_settings[u"use_ipv6"]
745
debug = server_settings["debug"]
1679
748
syslogger.setLevel(logging.WARNING)
1680
749
console.setLevel(logging.WARNING)
1682
if server_settings[u"servicename"] != u"Mandos":
1683
syslogger.setFormatter(logging.Formatter
1684
(u'Mandos (%s) [%%(process)d]:'
1685
u' %%(levelname)s: %%(message)s'
1686
% server_settings[u"servicename"]))
751
if server_settings["servicename"] != "Mandos":
752
syslogger.setFormatter(logging.Formatter\
753
('Mandos (%s): %%(levelname)s:'
755
% server_settings["servicename"]))
1688
757
# Parse config file with clients
1689
client_defaults = { u"timeout": u"1h",
1691
u"checker": u"fping -q -- %%(host)s",
1693
u"approved_delay": u"5m",
1694
u"approved_duration": u"1s",
758
client_defaults = { "timeout": "1h",
760
"checker": "fping -q -- %(host)s",
1696
client_config = configparser.SafeConfigParser(client_defaults)
1697
client_config.read(os.path.join(server_settings[u"configdir"],
1700
global mandos_dbus_service
1701
mandos_dbus_service = None
1703
tcp_server = MandosServer((server_settings[u"address"],
1704
server_settings[u"port"]),
1706
interface=server_settings[u"interface"],
1709
server_settings[u"priority"],
1711
pidfilename = u"/var/run/mandos.pid"
1713
pidfile = open(pidfilename, u"w")
1715
logger.error(u"Could not open file %r", pidfilename)
1718
uid = pwd.getpwnam(u"_mandos").pw_uid
1719
gid = pwd.getpwnam(u"_mandos").pw_gid
1722
uid = pwd.getpwnam(u"mandos").pw_uid
1723
gid = pwd.getpwnam(u"mandos").pw_gid
1726
uid = pwd.getpwnam(u"nobody").pw_uid
1727
gid = pwd.getpwnam(u"nobody").pw_gid
763
client_config = ConfigParser.SafeConfigParser(client_defaults)
764
client_config.read(os.path.join(server_settings["configdir"],
768
tcp_server = IPv6_TCPServer((server_settings["address"],
769
server_settings["port"]),
771
settings=server_settings,
773
pidfilename = "/var/run/mandos.pid"
775
pidfile = open(pidfilename, "w")
776
except IOError, error:
777
logger.error("Could not open file %r", pidfilename)
782
uid = pwd.getpwnam("mandos").pw_uid
785
uid = pwd.getpwnam("nobody").pw_uid
789
gid = pwd.getpwnam("mandos").pw_gid
792
gid = pwd.getpwnam("nogroup").pw_gid
1734
798
except OSError, error:
1735
799
if error[0] != errno.EPERM:
1738
# Enable all possible GnuTLS debugging
1740
# "Use a log level over 10 to enable all debugging options."
1742
gnutls.library.functions.gnutls_global_set_log_level(11)
1744
@gnutls.library.types.gnutls_log_func
1745
def debug_gnutls(level, string):
1746
logger.debug(u"GnuTLS: %s", string[:-1])
1748
(gnutls.library.functions
1749
.gnutls_global_set_log_function(debug_gnutls))
803
service = AvahiService(name = server_settings["servicename"],
804
servicetype = "_mandos._tcp", )
805
if server_settings["interface"]:
806
service.interface = if_nametoindex\
807
(server_settings["interface"])
1751
809
global main_loop
1752
812
# From the Avahi example code
1753
813
DBusGMainLoop(set_as_default=True )
1754
814
main_loop = gobject.MainLoop()
1755
815
bus = dbus.SystemBus()
816
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
817
avahi.DBUS_PATH_SERVER),
818
avahi.DBUS_INTERFACE_SERVER)
1756
819
# End of Avahi example code
1759
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1760
bus, do_not_queue=True)
1761
except dbus.exceptions.NameExistsException, e:
1762
logger.error(unicode(e) + u", disabling D-Bus")
1764
server_settings[u"use_dbus"] = False
1765
tcp_server.use_dbus = False
1766
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1767
service = AvahiService(name = server_settings[u"servicename"],
1768
servicetype = u"_mandos._tcp",
1769
protocol = protocol, bus = bus)
1770
if server_settings["interface"]:
1771
service.interface = (if_nametoindex
1772
(str(server_settings[u"interface"])))
1774
global multiprocessing_manager
1775
multiprocessing_manager = multiprocessing.Manager()
1777
client_class = Client
1779
client_class = functools.partial(ClientDBus, bus = bus)
1780
def client_config_items(config, section):
1781
special_settings = {
1782
"approved_by_default":
1783
lambda: config.getboolean(section,
1784
"approved_by_default"),
1786
for name, value in config.items(section):
1788
yield (name, special_settings[name]())
1792
tcp_server.clients.update(set(
1793
client_class(name = section,
1794
config= dict(client_config_items(
1795
client_config, section)))
1796
for section in client_config.sections()))
1797
if not tcp_server.clients:
1798
logger.warning(u"No clients defined")
821
def remove_from_clients(client):
822
clients.remove(client)
824
logger.critical(u"No clients left, exiting")
827
clients.update(Set(Client(name = section,
828
stop_hook = remove_from_clients,
830
= dict(client_config.items(section)))
831
for section in client_config.sections()))
833
logger.critical(u"No clients defined")
1801
837
# Redirect stdin so all checkers get /dev/null