130
110
max_renames: integer; maximum number of renames
131
111
rename_count: integer; counter so we only rename after collisions
132
112
a sensible number of times
133
group: D-Bus Entry Group
135
bus: dbus.SystemBus()
137
114
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
138
servicetype = None, port = None, TXT = None,
139
domain = "", host = "", max_renames = 32768,
140
protocol = avahi.PROTO_UNSPEC, bus = None):
115
type = None, port = None, TXT = None, domain = "",
116
host = "", max_renames = 12):
141
117
self.interface = interface
143
self.type = servicetype
145
self.TXT = TXT if TXT is not None else []
146
125
self.domain = domain
148
127
self.rename_count = 0
149
self.max_renames = max_renames
150
self.protocol = protocol
151
self.group = None # our entry group
154
128
def rename(self):
155
129
"""Derived from the Avahi example code"""
156
130
if self.rename_count >= self.max_renames:
157
logger.critical("No suitable Zeroconf service name found"
158
" after %i retries, exiting.",
131
logger.critical(u"No suitable service name found after %i"
132
u" retries, exiting.", rename_count)
160
133
raise AvahiServiceError("Too many renames")
161
self.name = unicode(self.server.GetAlternativeServiceName(self.name))
162
logger.info("Changing Zeroconf service name to %r ...",
164
syslogger.setFormatter(logging.Formatter
165
('Mandos (%s) [%%(process)d]:'
166
' %%(levelname)s: %%(message)s'
134
name = server.GetAlternativeServiceName(name)
135
logger.error(u"Changing name to %r ...", name)
171
except dbus.exceptions.DBusException, error:
172
logger.critical("DBusException: %s", error)
175
138
self.rename_count += 1
176
139
def remove(self):
177
140
"""Derived from the Avahi example code"""
178
if self.group is not None:
141
if group is not None:
181
144
"""Derived from the Avahi example code"""
182
if self.group is None:
183
self.group = dbus.Interface(
184
self.bus.get_object(avahi.DBUS_NAME,
185
self.server.EntryGroupNew()),
186
avahi.DBUS_INTERFACE_ENTRY_GROUP)
187
self.group.connect_to_signal('StateChanged',
189
.entry_group_state_changed)
190
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
191
self.name, self.type)
192
self.group.AddService(
195
dbus.UInt32(0), # flags
196
self.name, self.type,
197
self.domain, self.host,
198
dbus.UInt16(self.port),
199
avahi.string_array_to_txt_array(self.TXT))
201
def entry_group_state_changed(self, state, error):
202
"""Derived from the Avahi example code"""
203
logger.debug("Avahi entry group state change: %i", state)
205
if state == avahi.ENTRY_GROUP_ESTABLISHED:
206
logger.debug("Zeroconf service established.")
207
elif state == avahi.ENTRY_GROUP_COLLISION:
208
logger.info("Zeroconf service name collision.")
210
elif state == avahi.ENTRY_GROUP_FAILURE:
211
logger.critical("Avahi: Error in group state changed %s",
213
raise AvahiGroupError("State changed: %s"
216
"""Derived from the Avahi example code"""
217
if self.group is not None:
220
def server_state_changed(self, state):
221
"""Derived from the Avahi example code"""
222
logger.debug("Avahi server state change: %i", state)
223
if state == avahi.SERVER_COLLISION:
224
logger.error("Zeroconf server name collision")
226
elif state == avahi.SERVER_RUNNING:
229
"""Derived from the Avahi example code"""
230
if self.server is None:
231
self.server = dbus.Interface(
232
self.bus.get_object(avahi.DBUS_NAME,
233
avahi.DBUS_PATH_SERVER),
234
avahi.DBUS_INTERFACE_SERVER)
235
self.server.connect_to_signal("StateChanged",
236
self.server_state_changed)
237
self.server_state_changed(self.server.GetState())
147
group = dbus.Interface\
148
(bus.get_object(avahi.DBUS_NAME,
149
server.EntryGroupNew()),
150
avahi.DBUS_INTERFACE_ENTRY_GROUP)
151
group.connect_to_signal('StateChanged',
152
entry_group_state_changed)
153
logger.debug(u"Adding service '%s' of type '%s' ...",
154
service.name, service.type)
156
self.interface, # interface
157
avahi.PROTO_INET6, # protocol
158
dbus.UInt32(0), # flags
159
self.name, self.type,
160
self.domain, self.host,
161
dbus.UInt16(self.port),
162
avahi.string_array_to_txt_array(self.TXT))
165
# From the Avahi example code:
166
group = None # our entry group
167
# End of Avahi example code
240
170
class Client(object):
241
171
"""A representation of a client host served by this server.
244
_approved: bool(); 'None' if not yet approved/disapproved
245
approval_delay: datetime.timedelta(); Time to wait for approval
246
approval_duration: datetime.timedelta(); Duration of one approval
247
checker: subprocess.Popen(); a running checker process used
248
to see if the client lives.
249
'None' if no process is running.
250
checker_callback_tag: a gobject event source tag, or None
251
checker_command: string; External command which is run to check
252
if client lives. %() expansions are done at
173
name: string; from the config file, used in log messages
174
fingerprint: string (40 or 32 hexadecimal digits); used to
175
uniquely identify the client
176
secret: bytestring; sent verbatim (over TLS) to client
177
fqdn: string (FQDN); available for use by the checker command
178
created: datetime.datetime(); object creation, not client host
179
last_checked_ok: datetime.datetime() or None if not yet checked OK
180
timeout: datetime.timedelta(); How long from last_checked_ok
181
until this client is invalid
182
interval: datetime.timedelta(); How often to start a new checker
183
stop_hook: If set, called by stop() as stop_hook(self)
184
checker: subprocess.Popen(); a running checker process used
185
to see if the client lives.
186
'None' if no process is running.
187
checker_initiator_tag: a gobject event source tag, or None
188
stop_initiator_tag: - '' -
189
checker_callback_tag: - '' -
190
checker_command: string; External command which is run to check if
191
client lives. %() expansions are done at
253
192
runtime with vars(self) as dict, so that for
254
193
instance %(name)s can be used in the command.
255
checker_initiator_tag: a gobject event source tag, or None
256
created: datetime.datetime(); (UTC) object creation
257
current_checker_command: string; current running checker_command
258
disable_hook: If set, called by disable() as disable_hook(self)
259
disable_initiator_tag: a gobject event source tag, or None
261
fingerprint: string (40 or 32 hexadecimal digits); used to
262
uniquely identify the client
263
host: string; available for use by the checker command
264
interval: datetime.timedelta(); How often to start a new checker
265
last_approval_request: datetime.datetime(); (UTC) or None
266
last_checked_ok: datetime.datetime(); (UTC) or None
267
last_enabled: datetime.datetime(); (UTC)
268
name: string; from the config file, used in log messages and
270
secret: bytestring; sent verbatim (over TLS) to client
271
timeout: datetime.timedelta(); How long from last_checked_ok
272
until this client is disabled
273
runtime_expansions: Allowed attributes for runtime expansion.
195
_timeout: Real variable for 'timeout'
196
_interval: Real variable for 'interval'
197
_timeout_milliseconds: Used when calling gobject.timeout_add()
198
_interval_milliseconds: - '' -
276
runtime_expansions = ("approval_delay", "approval_duration",
277
"created", "enabled", "fingerprint",
278
"host", "interval", "last_checked_ok",
279
"last_enabled", "name", "timeout")
282
def _timedelta_to_milliseconds(td):
283
"Convert a datetime.timedelta() to milliseconds"
284
return ((td.days * 24 * 60 * 60 * 1000)
285
+ (td.seconds * 1000)
286
+ (td.microseconds // 1000))
288
def timeout_milliseconds(self):
289
"Return the 'timeout' attribute in milliseconds"
290
return self._timedelta_to_milliseconds(self.timeout)
292
def interval_milliseconds(self):
293
"Return the 'interval' attribute in milliseconds"
294
return self._timedelta_to_milliseconds(self.interval)
296
def approval_delay_milliseconds(self):
297
return self._timedelta_to_milliseconds(self.approval_delay)
299
def __init__(self, name = None, disable_hook=None, config=None):
200
def _set_timeout(self, timeout):
201
"Setter function for 'timeout' attribute"
202
self._timeout = timeout
203
self._timeout_milliseconds = ((self.timeout.days
204
* 24 * 60 * 60 * 1000)
205
+ (self.timeout.seconds * 1000)
206
+ (self.timeout.microseconds
208
timeout = property(lambda self: self._timeout,
211
def _set_interval(self, interval):
212
"Setter function for 'interval' attribute"
213
self._interval = interval
214
self._interval_milliseconds = ((self.interval.days
215
* 24 * 60 * 60 * 1000)
216
+ (self.interval.seconds
218
+ (self.interval.microseconds
220
interval = property(lambda self: self._interval,
223
def __init__(self, name = None, stop_hook=None, config={}):
300
224
"""Note: the 'checker' key in 'config' sets the
301
225
'checker_command' attribute and *not* the 'checker'
306
logger.debug("Creating client %r", self.name)
228
logger.debug(u"Creating client %r", self.name)
307
229
# Uppercase and remove spaces from fingerprint for later
308
230
# comparison purposes with return value from the fingerprint()
310
self.fingerprint = (config["fingerprint"].upper()
312
logger.debug(" Fingerprint: %s", self.fingerprint)
232
self.fingerprint = config["fingerprint"].upper()\
234
logger.debug(u" Fingerprint: %s", self.fingerprint)
313
235
if "secret" in config:
314
self.secret = config["secret"].decode("base64")
236
self.secret = config["secret"].decode(u"base64")
315
237
elif "secfile" in config:
316
with open(os.path.expanduser(os.path.expandvars
317
(config["secfile"])),
319
self.secret = secfile.read()
238
sf = open(config["secfile"])
239
self.secret = sf.read()
321
raise TypeError("No secret or secfile for client %s"
242
raise TypeError(u"No secret or secfile for client %s"
323
self.host = config.get("host", "")
324
self.created = datetime.datetime.utcnow()
326
self.last_approval_request = None
327
self.last_enabled = None
244
self.fqdn = config.get("fqdn", "")
245
self.created = datetime.datetime.now()
328
246
self.last_checked_ok = None
329
247
self.timeout = string_to_delta(config["timeout"])
330
248
self.interval = string_to_delta(config["interval"])
331
self.disable_hook = disable_hook
249
self.stop_hook = stop_hook
332
250
self.checker = None
333
251
self.checker_initiator_tag = None
334
self.disable_initiator_tag = None
252
self.stop_initiator_tag = None
335
253
self.checker_callback_tag = None
336
self.checker_command = config["checker"]
337
self.current_checker_command = None
338
self.last_connect = None
339
self._approved = None
340
self.approved_by_default = config.get("approved_by_default",
342
self.approvals_pending = 0
343
self.approval_delay = string_to_delta(
344
config["approval_delay"])
345
self.approval_duration = string_to_delta(
346
config["approval_duration"])
347
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
349
def send_changedstate(self):
350
self.changedstate.acquire()
351
self.changedstate.notify_all()
352
self.changedstate.release()
254
self.check_command = config["checker"]
355
256
"""Start this client's checker and timeout hooks"""
356
if getattr(self, "enabled", False):
359
self.send_changedstate()
360
self.last_enabled = datetime.datetime.utcnow()
361
257
# Schedule a new checker to be started an 'interval' from now,
362
258
# and every interval from then on.
363
self.checker_initiator_tag = (gobject.timeout_add
364
(self.interval_milliseconds(),
366
# Schedule a disable() when 'timeout' has passed
367
self.disable_initiator_tag = (gobject.timeout_add
368
(self.timeout_milliseconds(),
259
self.checker_initiator_tag = gobject.timeout_add\
260
(self._interval_milliseconds,
371
262
# Also start a new checker *right now*.
372
263
self.start_checker()
374
def disable(self, quiet=True):
375
"""Disable this client."""
376
if not getattr(self, "enabled", False):
264
# Schedule a stop() when 'timeout' has passed
265
self.stop_initiator_tag = gobject.timeout_add\
266
(self._timeout_milliseconds,
270
The possibility that a client might be restarted is left open,
271
but not currently used."""
272
# If this client doesn't have a secret, it is already stopped.
274
logger.info(u"Stopping client %s", self.name)
379
self.send_changedstate()
381
logger.info("Disabling client %s", self.name)
382
if getattr(self, "disable_initiator_tag", False):
383
gobject.source_remove(self.disable_initiator_tag)
384
self.disable_initiator_tag = None
278
if getattr(self, "stop_initiator_tag", False):
279
gobject.source_remove(self.stop_initiator_tag)
280
self.stop_initiator_tag = None
385
281
if getattr(self, "checker_initiator_tag", False):
386
282
gobject.source_remove(self.checker_initiator_tag)
387
283
self.checker_initiator_tag = None
388
284
self.stop_checker()
389
if self.disable_hook:
390
self.disable_hook(self)
392
287
# Do not run this again if called by a gobject.timeout_add
395
289
def __del__(self):
396
self.disable_hook = None
399
def checker_callback(self, pid, condition, command):
290
self.stop_hook = None
292
def checker_callback(self, pid, condition):
400
293
"""The checker has completed, so take appropriate actions."""
294
now = datetime.datetime.now()
401
295
self.checker_callback_tag = None
402
296
self.checker = None
403
if os.WIFEXITED(condition):
404
exitstatus = os.WEXITSTATUS(condition)
406
logger.info("Checker for %(name)s succeeded",
410
logger.info("Checker for %(name)s failed",
413
logger.warning("Checker for %(name)s crashed?",
297
if os.WIFEXITED(condition) \
298
and (os.WEXITSTATUS(condition) == 0):
299
logger.info(u"Checker for %(name)s succeeded",
301
self.last_checked_ok = now
302
gobject.source_remove(self.stop_initiator_tag)
303
self.stop_initiator_tag = gobject.timeout_add\
304
(self._timeout_milliseconds,
306
elif not os.WIFEXITED(condition):
307
logger.warning(u"Checker for %(name)s crashed?",
416
def checked_ok(self):
417
"""Bump up the timeout for this client.
419
This should only be called when the client has been seen,
422
self.last_checked_ok = datetime.datetime.utcnow()
423
gobject.source_remove(self.disable_initiator_tag)
424
self.disable_initiator_tag = (gobject.timeout_add
425
(self.timeout_milliseconds(),
428
def need_approval(self):
429
self.last_approval_request = datetime.datetime.utcnow()
310
logger.info(u"Checker for %(name)s failed",
431
312
def start_checker(self):
432
313
"""Start a new checker subprocess if one is not running.
434
314
If a checker already exists, leave it running and do
436
316
# The reason for not killing a running checker is that if we
513
360
logger.debug("Stopping checker for %(name)s", vars(self))
515
362
os.kill(self.checker.pid, signal.SIGTERM)
517
364
#if self.checker.poll() is None:
518
365
# os.kill(self.checker.pid, signal.SIGKILL)
519
366
except OSError, error:
520
367
if error.errno != errno.ESRCH: # No such process
522
369
self.checker = None
524
def dbus_service_property(dbus_interface, signature="v",
525
access="readwrite", byte_arrays=False):
526
"""Decorators for marking methods of a DBusObjectWithProperties to
527
become properties on the D-Bus.
529
The decorated method will be called with no arguments by "Get"
530
and with one argument by "Set".
532
The parameters, where they are supported, are the same as
533
dbus.service.method, except there is only "signature", since the
534
type from Get() and the type sent to Set() is the same.
536
# Encoding deeply encoded byte arrays is not supported yet by the
537
# "Set" method, so we fail early here:
538
if byte_arrays and signature != "ay":
539
raise ValueError("Byte arrays not supported for non-'ay'"
540
" signature %r" % signature)
542
func._dbus_is_property = True
543
func._dbus_interface = dbus_interface
544
func._dbus_signature = signature
545
func._dbus_access = access
546
func._dbus_name = func.__name__
547
if func._dbus_name.endswith("_dbus_property"):
548
func._dbus_name = func._dbus_name[:-14]
549
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
554
class DBusPropertyException(dbus.exceptions.DBusException):
555
"""A base class for D-Bus property-related exceptions
557
def __unicode__(self):
558
return unicode(str(self))
561
class DBusPropertyAccessException(DBusPropertyException):
562
"""A property's access permissions disallows an operation.
567
class DBusPropertyNotFound(DBusPropertyException):
568
"""An attempt was made to access a non-existing property.
573
class DBusObjectWithProperties(dbus.service.Object):
574
"""A D-Bus object with properties.
576
Classes inheriting from this can use the dbus_service_property
577
decorator to expose methods as D-Bus properties. It exposes the
578
standard Get(), Set(), and GetAll() methods on the D-Bus.
582
def _is_dbus_property(obj):
583
return getattr(obj, "_dbus_is_property", False)
585
def _get_all_dbus_properties(self):
586
"""Returns a generator of (name, attribute) pairs
588
return ((prop._dbus_name, prop)
590
inspect.getmembers(self, self._is_dbus_property))
592
def _get_dbus_property(self, interface_name, property_name):
593
"""Returns a bound method if one exists which is a D-Bus
594
property with the specified name and interface.
596
for name in (property_name,
597
property_name + "_dbus_property"):
598
prop = getattr(self, name, None)
600
or not self._is_dbus_property(prop)
601
or prop._dbus_name != property_name
602
or (interface_name and prop._dbus_interface
603
and interface_name != prop._dbus_interface)):
607
raise DBusPropertyNotFound(self.dbus_object_path + ":"
608
+ interface_name + "."
611
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
613
def Get(self, interface_name, property_name):
614
"""Standard D-Bus property Get() method, see D-Bus standard.
616
prop = self._get_dbus_property(interface_name, property_name)
617
if prop._dbus_access == "write":
618
raise DBusPropertyAccessException(property_name)
620
if not hasattr(value, "variant_level"):
622
return type(value)(value, variant_level=value.variant_level+1)
624
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
625
def Set(self, interface_name, property_name, value):
626
"""Standard D-Bus property Set() method, see D-Bus standard.
628
prop = self._get_dbus_property(interface_name, property_name)
629
if prop._dbus_access == "read":
630
raise DBusPropertyAccessException(property_name)
631
if prop._dbus_get_args_options["byte_arrays"]:
632
# The byte_arrays option is not supported yet on
633
# signatures other than "ay".
634
if prop._dbus_signature != "ay":
636
value = dbus.ByteArray(''.join(unichr(byte)
640
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
641
out_signature="a{sv}")
642
def GetAll(self, interface_name):
643
"""Standard D-Bus property GetAll() method, see D-Bus
646
Note: Will not include properties with access="write".
649
for name, prop in self._get_all_dbus_properties():
651
and interface_name != prop._dbus_interface):
652
# Interface non-empty but did not match
654
# Ignore write-only properties
655
if prop._dbus_access == "write":
658
if not hasattr(value, "variant_level"):
661
all[name] = type(value)(value, variant_level=
662
value.variant_level+1)
663
return dbus.Dictionary(all, signature="sv")
665
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
667
path_keyword='object_path',
668
connection_keyword='connection')
669
def Introspect(self, object_path, connection):
670
"""Standard D-Bus method, overloaded to insert property tags.
672
xmlstring = dbus.service.Object.Introspect(self, object_path,
675
document = xml.dom.minidom.parseString(xmlstring)
676
def make_tag(document, name, prop):
677
e = document.createElement("property")
678
e.setAttribute("name", name)
679
e.setAttribute("type", prop._dbus_signature)
680
e.setAttribute("access", prop._dbus_access)
682
for if_tag in document.getElementsByTagName("interface"):
683
for tag in (make_tag(document, name, prop)
685
in self._get_all_dbus_properties()
686
if prop._dbus_interface
687
== if_tag.getAttribute("name")):
688
if_tag.appendChild(tag)
689
# Add the names to the return values for the
690
# "org.freedesktop.DBus.Properties" methods
691
if (if_tag.getAttribute("name")
692
== "org.freedesktop.DBus.Properties"):
693
for cn in if_tag.getElementsByTagName("method"):
694
if cn.getAttribute("name") == "Get":
695
for arg in cn.getElementsByTagName("arg"):
696
if (arg.getAttribute("direction")
698
arg.setAttribute("name", "value")
699
elif cn.getAttribute("name") == "GetAll":
700
for arg in cn.getElementsByTagName("arg"):
701
if (arg.getAttribute("direction")
703
arg.setAttribute("name", "props")
704
xmlstring = document.toxml("utf-8")
706
except (AttributeError, xml.dom.DOMException,
707
xml.parsers.expat.ExpatError), error:
708
logger.error("Failed to override Introspection method",
713
class ClientDBus(Client, DBusObjectWithProperties):
714
"""A Client class using D-Bus
717
dbus_object_path: dbus.ObjectPath
718
bus: dbus.SystemBus()
721
runtime_expansions = (Client.runtime_expansions
722
+ ("dbus_object_path",))
724
# dbus.service.Object doesn't use super(), so we can't either.
726
def __init__(self, bus = None, *args, **kwargs):
727
self._approvals_pending = 0
729
Client.__init__(self, *args, **kwargs)
730
# Only now, when this client is initialized, can it show up on
732
client_object_name = unicode(self.name).translate(
735
self.dbus_object_path = (dbus.ObjectPath
736
("/clients/" + client_object_name))
737
DBusObjectWithProperties.__init__(self, self.bus,
738
self.dbus_object_path)
740
def _get_approvals_pending(self):
741
return self._approvals_pending
742
def _set_approvals_pending(self, value):
743
old_value = self._approvals_pending
744
self._approvals_pending = value
746
if (hasattr(self, "dbus_object_path")
747
and bval is not bool(old_value)):
748
dbus_bool = dbus.Boolean(bval, variant_level=1)
749
self.PropertyChanged(dbus.String("ApprovalPending"),
752
approvals_pending = property(_get_approvals_pending,
753
_set_approvals_pending)
754
del _get_approvals_pending, _set_approvals_pending
757
def _datetime_to_dbus(dt, variant_level=0):
758
"""Convert a UTC datetime.datetime() to a D-Bus type."""
759
return dbus.String(dt.isoformat(),
760
variant_level=variant_level)
763
oldstate = getattr(self, "enabled", False)
764
r = Client.enable(self)
765
if oldstate != self.enabled:
767
self.PropertyChanged(dbus.String("Enabled"),
768
dbus.Boolean(True, variant_level=1))
769
self.PropertyChanged(
770
dbus.String("LastEnabled"),
771
self._datetime_to_dbus(self.last_enabled,
775
def disable(self, quiet = False):
776
oldstate = getattr(self, "enabled", False)
777
r = Client.disable(self, quiet=quiet)
778
if not quiet and oldstate != self.enabled:
780
self.PropertyChanged(dbus.String("Enabled"),
781
dbus.Boolean(False, variant_level=1))
784
def __del__(self, *args, **kwargs):
786
self.remove_from_connection()
789
if hasattr(DBusObjectWithProperties, "__del__"):
790
DBusObjectWithProperties.__del__(self, *args, **kwargs)
791
Client.__del__(self, *args, **kwargs)
793
def checker_callback(self, pid, condition, command,
795
self.checker_callback_tag = None
798
self.PropertyChanged(dbus.String("CheckerRunning"),
799
dbus.Boolean(False, variant_level=1))
800
if os.WIFEXITED(condition):
801
exitstatus = os.WEXITSTATUS(condition)
803
self.CheckerCompleted(dbus.Int16(exitstatus),
804
dbus.Int64(condition),
805
dbus.String(command))
808
self.CheckerCompleted(dbus.Int16(-1),
809
dbus.Int64(condition),
810
dbus.String(command))
812
return Client.checker_callback(self, pid, condition, command,
815
def checked_ok(self, *args, **kwargs):
816
r = Client.checked_ok(self, *args, **kwargs)
818
self.PropertyChanged(
819
dbus.String("LastCheckedOK"),
820
(self._datetime_to_dbus(self.last_checked_ok,
824
def need_approval(self, *args, **kwargs):
825
r = Client.need_approval(self, *args, **kwargs)
827
self.PropertyChanged(
828
dbus.String("LastApprovalRequest"),
829
(self._datetime_to_dbus(self.last_approval_request,
833
def start_checker(self, *args, **kwargs):
834
old_checker = self.checker
835
if self.checker is not None:
836
old_checker_pid = self.checker.pid
838
old_checker_pid = None
839
r = Client.start_checker(self, *args, **kwargs)
840
# Only if new checker process was started
841
if (self.checker is not None
842
and old_checker_pid != self.checker.pid):
844
self.CheckerStarted(self.current_checker_command)
845
self.PropertyChanged(
846
dbus.String("CheckerRunning"),
847
dbus.Boolean(True, variant_level=1))
850
def stop_checker(self, *args, **kwargs):
851
old_checker = getattr(self, "checker", None)
852
r = Client.stop_checker(self, *args, **kwargs)
853
if (old_checker is not None
854
and getattr(self, "checker", None) is None):
855
self.PropertyChanged(dbus.String("CheckerRunning"),
856
dbus.Boolean(False, variant_level=1))
859
def _reset_approved(self):
860
self._approved = None
863
def approve(self, value=True):
864
self.send_changedstate()
865
self._approved = value
866
gobject.timeout_add(self._timedelta_to_milliseconds
867
(self.approval_duration),
868
self._reset_approved)
871
## D-Bus methods, signals & properties
872
_interface = "se.bsnet.fukt.Mandos.Client"
876
# CheckerCompleted - signal
877
@dbus.service.signal(_interface, signature="nxs")
878
def CheckerCompleted(self, exitcode, waitstatus, command):
882
# CheckerStarted - signal
883
@dbus.service.signal(_interface, signature="s")
884
def CheckerStarted(self, command):
888
# PropertyChanged - signal
889
@dbus.service.signal(_interface, signature="sv")
890
def PropertyChanged(self, property, value):
895
@dbus.service.signal(_interface)
898
Is sent after a successful transfer of secret from the Mandos
899
server to mandos-client
904
@dbus.service.signal(_interface, signature="s")
905
def Rejected(self, reason):
909
# NeedApproval - signal
910
@dbus.service.signal(_interface, signature="tb")
911
def NeedApproval(self, timeout, default):
913
return self.need_approval()
918
@dbus.service.method(_interface, in_signature="b")
919
def Approve(self, value):
923
@dbus.service.method(_interface)
925
return self.checked_ok()
928
@dbus.service.method(_interface)
933
# StartChecker - method
934
@dbus.service.method(_interface)
935
def StartChecker(self):
940
@dbus.service.method(_interface)
945
# StopChecker - method
946
@dbus.service.method(_interface)
947
def StopChecker(self):
952
# ApprovalPending - property
953
@dbus_service_property(_interface, signature="b", access="read")
954
def ApprovalPending_dbus_property(self):
955
return dbus.Boolean(bool(self.approvals_pending))
957
# ApprovedByDefault - property
958
@dbus_service_property(_interface, signature="b",
960
def ApprovedByDefault_dbus_property(self, value=None):
961
if value is None: # get
962
return dbus.Boolean(self.approved_by_default)
963
self.approved_by_default = bool(value)
965
self.PropertyChanged(dbus.String("ApprovedByDefault"),
966
dbus.Boolean(value, variant_level=1))
968
# ApprovalDelay - property
969
@dbus_service_property(_interface, signature="t",
971
def ApprovalDelay_dbus_property(self, value=None):
972
if value is None: # get
973
return dbus.UInt64(self.approval_delay_milliseconds())
974
self.approval_delay = datetime.timedelta(0, 0, 0, value)
976
self.PropertyChanged(dbus.String("ApprovalDelay"),
977
dbus.UInt64(value, variant_level=1))
979
# ApprovalDuration - property
980
@dbus_service_property(_interface, signature="t",
982
def ApprovalDuration_dbus_property(self, value=None):
983
if value is None: # get
984
return dbus.UInt64(self._timedelta_to_milliseconds(
985
self.approval_duration))
986
self.approval_duration = datetime.timedelta(0, 0, 0, value)
988
self.PropertyChanged(dbus.String("ApprovalDuration"),
989
dbus.UInt64(value, variant_level=1))
992
@dbus_service_property(_interface, signature="s", access="read")
993
def Name_dbus_property(self):
994
return dbus.String(self.name)
996
# Fingerprint - property
997
@dbus_service_property(_interface, signature="s", access="read")
998
def Fingerprint_dbus_property(self):
999
return dbus.String(self.fingerprint)
1002
@dbus_service_property(_interface, signature="s",
1004
def Host_dbus_property(self, value=None):
1005
if value is None: # get
1006
return dbus.String(self.host)
1009
self.PropertyChanged(dbus.String("Host"),
1010
dbus.String(value, variant_level=1))
1012
# Created - property
1013
@dbus_service_property(_interface, signature="s", access="read")
1014
def Created_dbus_property(self):
1015
return dbus.String(self._datetime_to_dbus(self.created))
1017
# LastEnabled - property
1018
@dbus_service_property(_interface, signature="s", access="read")
1019
def LastEnabled_dbus_property(self):
1020
if self.last_enabled is None:
1021
return dbus.String("")
1022
return dbus.String(self._datetime_to_dbus(self.last_enabled))
1024
# Enabled - property
1025
@dbus_service_property(_interface, signature="b",
1027
def Enabled_dbus_property(self, value=None):
1028
if value is None: # get
1029
return dbus.Boolean(self.enabled)
1035
# LastCheckedOK - property
1036
@dbus_service_property(_interface, signature="s",
1038
def LastCheckedOK_dbus_property(self, value=None):
1039
if value is not None:
370
def still_valid(self):
371
"""Has the timeout not yet passed for this client?"""
372
now = datetime.datetime.now()
1042
373
if self.last_checked_ok is None:
1043
return dbus.String("")
1044
return dbus.String(self._datetime_to_dbus(self
1047
# LastApprovalRequest - property
1048
@dbus_service_property(_interface, signature="s", access="read")
1049
def LastApprovalRequest_dbus_property(self):
1050
if self.last_approval_request is None:
1051
return dbus.String("")
1052
return dbus.String(self.
1053
_datetime_to_dbus(self
1054
.last_approval_request))
1056
# Timeout - property
1057
@dbus_service_property(_interface, signature="t",
1059
def Timeout_dbus_property(self, value=None):
1060
if value is None: # get
1061
return dbus.UInt64(self.timeout_milliseconds())
1062
self.timeout = datetime.timedelta(0, 0, 0, value)
1064
self.PropertyChanged(dbus.String("Timeout"),
1065
dbus.UInt64(value, variant_level=1))
1066
if getattr(self, "disable_initiator_tag", None) is None:
1068
# Reschedule timeout
1069
gobject.source_remove(self.disable_initiator_tag)
1070
self.disable_initiator_tag = None
1071
time_to_die = (self.
1072
_timedelta_to_milliseconds((self
1077
if time_to_die <= 0:
1078
# The timeout has passed
1081
self.disable_initiator_tag = (gobject.timeout_add
1082
(time_to_die, self.disable))
1084
# Interval - property
1085
@dbus_service_property(_interface, signature="t",
1087
def Interval_dbus_property(self, value=None):
1088
if value is None: # get
1089
return dbus.UInt64(self.interval_milliseconds())
1090
self.interval = datetime.timedelta(0, 0, 0, value)
1092
self.PropertyChanged(dbus.String("Interval"),
1093
dbus.UInt64(value, variant_level=1))
1094
if getattr(self, "checker_initiator_tag", None) is None:
1096
# Reschedule checker run
1097
gobject.source_remove(self.checker_initiator_tag)
1098
self.checker_initiator_tag = (gobject.timeout_add
1099
(value, self.start_checker))
1100
self.start_checker() # Start one now, too
1102
# Checker - property
1103
@dbus_service_property(_interface, signature="s",
1105
def Checker_dbus_property(self, value=None):
1106
if value is None: # get
1107
return dbus.String(self.checker_command)
1108
self.checker_command = value
1110
self.PropertyChanged(dbus.String("Checker"),
1111
dbus.String(self.checker_command,
1114
# CheckerRunning - property
1115
@dbus_service_property(_interface, signature="b",
1117
def CheckerRunning_dbus_property(self, value=None):
1118
if value is None: # get
1119
return dbus.Boolean(self.checker is not None)
1121
self.start_checker()
1125
# ObjectPath - property
1126
@dbus_service_property(_interface, signature="o", access="read")
1127
def ObjectPath_dbus_property(self):
1128
return self.dbus_object_path # is already a dbus.ObjectPath
1131
@dbus_service_property(_interface, signature="ay",
1132
access="write", byte_arrays=True)
1133
def Secret_dbus_property(self, value):
1134
self.secret = str(value)
1139
class ProxyClient(object):
1140
def __init__(self, child_pipe, fpr, address):
1141
self._pipe = child_pipe
1142
self._pipe.send(('init', fpr, address))
1143
if not self._pipe.recv():
1146
def __getattribute__(self, name):
1147
if(name == '_pipe'):
1148
return super(ProxyClient, self).__getattribute__(name)
1149
self._pipe.send(('getattr', name))
1150
data = self._pipe.recv()
1151
if data[0] == 'data':
1153
if data[0] == 'function':
1154
def func(*args, **kwargs):
1155
self._pipe.send(('funcall', name, args, kwargs))
1156
return self._pipe.recv()[1]
1159
def __setattr__(self, name, value):
1160
if(name == '_pipe'):
1161
return super(ProxyClient, self).__setattr__(name, value)
1162
self._pipe.send(('setattr', name, value))
1165
class ClientHandler(socketserver.BaseRequestHandler, object):
1166
"""A class to handle client connections.
1168
Instantiated once for each connection to handle it.
374
return now < (self.created + self.timeout)
376
return now < (self.last_checked_ok + self.timeout)
379
def peer_certificate(session):
380
"Return the peer's OpenPGP certificate as a bytestring"
381
# If not an OpenPGP certificate...
382
if gnutls.library.functions.gnutls_certificate_type_get\
383
(session._c_object) \
384
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
385
# ...do the normal thing
386
return session.peer_certificate
387
list_size = ctypes.c_uint()
388
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
389
(session._c_object, ctypes.byref(list_size))
390
if list_size.value == 0:
393
return ctypes.string_at(cert.data, cert.size)
396
def fingerprint(openpgp):
397
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
398
# New GnuTLS "datum" with the OpenPGP public key
399
datum = gnutls.library.types.gnutls_datum_t\
400
(ctypes.cast(ctypes.c_char_p(openpgp),
401
ctypes.POINTER(ctypes.c_ubyte)),
402
ctypes.c_uint(len(openpgp)))
403
# New empty GnuTLS certificate
404
crt = gnutls.library.types.gnutls_openpgp_crt_t()
405
gnutls.library.functions.gnutls_openpgp_crt_init\
407
# Import the OpenPGP public key into the certificate
408
gnutls.library.functions.gnutls_openpgp_crt_import\
409
(crt, ctypes.byref(datum),
410
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
411
# New buffer for the fingerprint
412
buffer = ctypes.create_string_buffer(20)
413
buffer_length = ctypes.c_size_t()
414
# Get the fingerprint from the certificate into the buffer
415
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
416
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
417
# Deinit the certificate
418
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
419
# Convert the buffer to a Python bytestring
420
fpr = ctypes.string_at(buffer, buffer_length.value)
421
# Convert the bytestring to hexadecimal notation
422
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
426
class tcp_handler(SocketServer.BaseRequestHandler, object):
427
"""A TCP request handler class.
428
Instantiated by IPv6_TCPServer for each request to handle it.
1169
429
Note: This will run in its own forked process."""
1171
431
def handle(self):
1172
with contextlib.closing(self.server.child_pipe) as child_pipe:
1173
logger.info("TCP connection from: %s",
1174
unicode(self.client_address))
1175
logger.debug("Pipe FD: %d",
1176
self.server.child_pipe.fileno())
1178
session = (gnutls.connection
1179
.ClientSession(self.request,
1181
.X509Credentials()))
1183
# Note: gnutls.connection.X509Credentials is really a
1184
# generic GnuTLS certificate credentials object so long as
1185
# no X.509 keys are added to it. Therefore, we can use it
1186
# here despite using OpenPGP certificates.
1188
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1189
# "+AES-256-CBC", "+SHA1",
1190
# "+COMP-NULL", "+CTYPE-OPENPGP",
1192
# Use a fallback default, since this MUST be set.
1193
priority = self.server.gnutls_priority
1194
if priority is None:
1196
(gnutls.library.functions
1197
.gnutls_priority_set_direct(session._c_object,
1200
# Start communication using the Mandos protocol
1201
# Get protocol number
1202
line = self.request.makefile().readline()
1203
logger.debug("Protocol version: %r", line)
1205
if int(line.strip().split()[0]) > 1:
1207
except (ValueError, IndexError, RuntimeError), error:
1208
logger.error("Unknown protocol version: %s", error)
1211
# Start GnuTLS connection
1214
except gnutls.errors.GNUTLSError, error:
1215
logger.warning("Handshake failed: %s", error)
1216
# Do not run session.bye() here: the session is not
1217
# established. Just abandon the request.
1219
logger.debug("Handshake succeeded")
1221
approval_required = False
1224
fpr = self.fingerprint(self.peer_certificate
1226
except (TypeError, gnutls.errors.GNUTLSError), error:
1227
logger.warning("Bad certificate: %s", error)
1229
logger.debug("Fingerprint: %s", fpr)
1232
client = ProxyClient(child_pipe, fpr,
1233
self.client_address)
1237
if client.approval_delay:
1238
delay = client.approval_delay
1239
client.approvals_pending += 1
1240
approval_required = True
1243
if not client.enabled:
1244
logger.warning("Client %s is disabled",
1246
if self.server.use_dbus:
1248
client.Rejected("Disabled")
1251
if client._approved or not client.approval_delay:
1252
#We are approved or approval is disabled
1254
elif client._approved is None:
1255
logger.info("Client %s needs approval",
1257
if self.server.use_dbus:
1259
client.NeedApproval(
1260
client.approval_delay_milliseconds(),
1261
client.approved_by_default)
1263
logger.warning("Client %s was not approved",
1265
if self.server.use_dbus:
1267
client.Rejected("Denied")
1270
#wait until timeout or approved
1271
#x = float(client._timedelta_to_milliseconds(delay))
1272
time = datetime.datetime.now()
1273
client.changedstate.acquire()
1274
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1275
client.changedstate.release()
1276
time2 = datetime.datetime.now()
1277
if (time2 - time) >= delay:
1278
if not client.approved_by_default:
1279
logger.warning("Client %s timed out while"
1280
" waiting for approval",
1282
if self.server.use_dbus:
1284
client.Rejected("Approval timed out")
1289
delay -= time2 - time
1292
while sent_size < len(client.secret):
1294
sent = session.send(client.secret[sent_size:])
1295
except (gnutls.errors.GNUTLSError), error:
1296
logger.warning("gnutls send failed")
1298
logger.debug("Sent: %d, remaining: %d",
1299
sent, len(client.secret)
1300
- (sent_size + sent))
1303
logger.info("Sending secret to %s", client.name)
1304
# bump the timeout as if seen
1306
if self.server.use_dbus:
1311
if approval_required:
1312
client.approvals_pending -= 1
1315
except (gnutls.errors.GNUTLSError), error:
1316
logger.warning("GnuTLS bye failed")
1319
def peer_certificate(session):
1320
"Return the peer's OpenPGP certificate as a bytestring"
1321
# If not an OpenPGP certificate...
1322
if (gnutls.library.functions
1323
.gnutls_certificate_type_get(session._c_object)
1324
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1325
# ...do the normal thing
1326
return session.peer_certificate
1327
list_size = ctypes.c_uint(1)
1328
cert_list = (gnutls.library.functions
1329
.gnutls_certificate_get_peers
1330
(session._c_object, ctypes.byref(list_size)))
1331
if not bool(cert_list) and list_size.value != 0:
1332
raise gnutls.errors.GNUTLSError("error getting peer"
1334
if list_size.value == 0:
1337
return ctypes.string_at(cert.data, cert.size)
1340
def fingerprint(openpgp):
1341
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1342
# New GnuTLS "datum" with the OpenPGP public key
1343
datum = (gnutls.library.types
1344
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1347
ctypes.c_uint(len(openpgp))))
1348
# New empty GnuTLS certificate
1349
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1350
(gnutls.library.functions
1351
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1352
# Import the OpenPGP public key into the certificate
1353
(gnutls.library.functions
1354
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1355
gnutls.library.constants
1356
.GNUTLS_OPENPGP_FMT_RAW))
1357
# Verify the self signature in the key
1358
crtverify = ctypes.c_uint()
1359
(gnutls.library.functions
1360
.gnutls_openpgp_crt_verify_self(crt, 0,
1361
ctypes.byref(crtverify)))
1362
if crtverify.value != 0:
1363
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1364
raise (gnutls.errors.CertificateSecurityError
1366
# New buffer for the fingerprint
1367
buf = ctypes.create_string_buffer(20)
1368
buf_len = ctypes.c_size_t()
1369
# Get the fingerprint from the certificate into the buffer
1370
(gnutls.library.functions
1371
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1372
ctypes.byref(buf_len)))
1373
# Deinit the certificate
1374
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1375
# Convert the buffer to a Python bytestring
1376
fpr = ctypes.string_at(buf, buf_len.value)
1377
# Convert the bytestring to hexadecimal notation
1378
hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1382
class MultiprocessingMixIn(object):
1383
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1384
def sub_process_main(self, request, address):
1386
self.finish_request(request, address)
1388
self.handle_error(request, address)
1389
self.close_request(request)
1391
def process_request(self, request, address):
1392
"""Start a new process to process the request."""
1393
multiprocessing.Process(target = self.sub_process_main,
1394
args = (request, address)).start()
1396
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1397
""" adds a pipe to the MixIn """
1398
def process_request(self, request, client_address):
1399
"""Overrides and wraps the original process_request().
1401
This function creates a new pipe in self.pipe
1403
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1405
super(MultiprocessingMixInWithPipe,
1406
self).process_request(request, client_address)
1407
self.child_pipe.close()
1408
self.add_pipe(parent_pipe)
1410
def add_pipe(self, parent_pipe):
1411
"""Dummy function; override as necessary"""
1412
raise NotImplementedError
1414
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1415
socketserver.TCPServer, object):
1416
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
432
logger.info(u"TCP connection from: %s",
433
unicode(self.client_address))
434
session = gnutls.connection.ClientSession\
435
(self.request, gnutls.connection.X509Credentials())
437
line = self.request.makefile().readline()
438
logger.debug(u"Protocol version: %r", line)
440
if int(line.strip().split()[0]) > 1:
442
except (ValueError, IndexError, RuntimeError), error:
443
logger.error(u"Unknown protocol version: %s", error)
446
# Note: gnutls.connection.X509Credentials is really a generic
447
# GnuTLS certificate credentials object so long as no X.509
448
# keys are added to it. Therefore, we can use it here despite
449
# using OpenPGP certificates.
451
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
452
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
454
priority = "NORMAL" # Fallback default, since this
456
if self.server.settings["priority"]:
457
priority = self.server.settings["priority"]
458
gnutls.library.functions.gnutls_priority_set_direct\
459
(session._c_object, priority, None);
463
except gnutls.errors.GNUTLSError, error:
464
logger.warning(u"Handshake failed: %s", error)
465
# Do not run session.bye() here: the session is not
466
# established. Just abandon the request.
469
fpr = fingerprint(peer_certificate(session))
470
except (TypeError, gnutls.errors.GNUTLSError), error:
471
logger.warning(u"Bad certificate: %s", error)
474
logger.debug(u"Fingerprint: %s", fpr)
476
for c in self.server.clients:
477
if c.fingerprint == fpr:
481
logger.warning(u"Client not found for fingerprint: %s",
485
# Have to check if client.still_valid(), since it is possible
486
# that the client timed out while establishing the GnuTLS
488
if not client.still_valid():
489
logger.warning(u"Client %(name)s is invalid",
494
while sent_size < len(client.secret):
495
sent = session.send(client.secret[sent_size:])
496
logger.debug(u"Sent: %d, remaining: %d",
497
sent, len(client.secret)
498
- (sent_size + sent))
503
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
504
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1419
enabled: Boolean; whether this server is activated yet
1420
interface: None or a network interface name (string)
1421
use_ipv6: Boolean; to use IPv6 or not
506
settings: Server settings
507
clients: Set() of Client objects
1423
def __init__(self, server_address, RequestHandlerClass,
1424
interface=None, use_ipv6=True):
1425
self.interface = interface
1427
self.address_family = socket.AF_INET6
1428
socketserver.TCPServer.__init__(self, server_address,
1429
RequestHandlerClass)
509
address_family = socket.AF_INET6
510
def __init__(self, *args, **kwargs):
511
if "settings" in kwargs:
512
self.settings = kwargs["settings"]
513
del kwargs["settings"]
514
if "clients" in kwargs:
515
self.clients = kwargs["clients"]
516
del kwargs["clients"]
517
return super(type(self), self).__init__(*args, **kwargs)
1430
518
def server_bind(self):
1431
519
"""This overrides the normal server_bind() function
1432
520
to bind to an interface if one was specified, and also NOT to
1433
521
bind to an address or port if they were not specified."""
1434
if self.interface is not None:
1435
if SO_BINDTODEVICE is None:
1436
logger.error("SO_BINDTODEVICE does not exist;"
1437
" cannot bind to interface %s",
1441
self.socket.setsockopt(socket.SOL_SOCKET,
1445
except socket.error, error:
1446
if error[0] == errno.EPERM:
1447
logger.error("No permission to"
1448
" bind to interface %s",
1450
elif error[0] == errno.ENOPROTOOPT:
1451
logger.error("SO_BINDTODEVICE not available;"
1452
" cannot bind to interface %s",
522
if self.settings["interface"]:
523
# 25 is from /usr/include/asm-i486/socket.h
524
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
526
self.socket.setsockopt(socket.SOL_SOCKET,
528
self.settings["interface"])
529
except socket.error, error:
530
if error[0] == errno.EPERM:
531
logger.error(u"No permission to"
532
u" bind to interface %s",
533
self.settings["interface"])
1456
536
# Only bind(2) the socket if we really need to.
1457
537
if self.server_address[0] or self.server_address[1]:
1458
538
if not self.server_address[0]:
1459
if self.address_family == socket.AF_INET6:
1460
any_address = "::" # in6addr_any
1462
any_address = socket.INADDR_ANY
1463
self.server_address = (any_address,
540
self.server_address = (in6addr_any,
1464
541
self.server_address[1])
1465
elif not self.server_address[1]:
542
elif self.server_address[1] is None:
1466
543
self.server_address = (self.server_address[0],
1468
# if self.interface:
1469
# self.server_address = (self.server_address[0],
1474
return socketserver.TCPServer.server_bind(self)
1477
class MandosServer(IPv6_TCPServer):
1481
clients: set of Client objects
1482
gnutls_priority GnuTLS priority string
1483
use_dbus: Boolean; to emit D-Bus signals or not
1485
Assumes a gobject.MainLoop event loop.
1487
def __init__(self, server_address, RequestHandlerClass,
1488
interface=None, use_ipv6=True, clients=None,
1489
gnutls_priority=None, use_dbus=True):
1490
self.enabled = False
1491
self.clients = clients
1492
if self.clients is None:
1493
self.clients = set()
1494
self.use_dbus = use_dbus
1495
self.gnutls_priority = gnutls_priority
1496
IPv6_TCPServer.__init__(self, server_address,
1497
RequestHandlerClass,
1498
interface = interface,
1499
use_ipv6 = use_ipv6)
1500
def server_activate(self):
1502
return socketserver.TCPServer.server_activate(self)
1505
def add_pipe(self, parent_pipe):
1506
# Call "handle_ipc" for both data and EOF events
1507
gobject.io_add_watch(parent_pipe.fileno(),
1508
gobject.IO_IN | gobject.IO_HUP,
1509
functools.partial(self.handle_ipc,
1510
parent_pipe = parent_pipe))
1512
def handle_ipc(self, source, condition, parent_pipe=None,
1513
client_object=None):
1515
gobject.IO_IN: "IN", # There is data to read.
1516
gobject.IO_OUT: "OUT", # Data can be written (without
1518
gobject.IO_PRI: "PRI", # There is urgent data to read.
1519
gobject.IO_ERR: "ERR", # Error condition.
1520
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1521
# broken, usually for pipes and
1524
conditions_string = ' | '.join(name
1526
condition_names.iteritems()
1527
if cond & condition)
1528
# error or the other end of multiprocessing.Pipe has closed
1529
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1532
# Read a request from the child
1533
request = parent_pipe.recv()
1534
command = request[0]
1536
if command == 'init':
1538
address = request[2]
1540
for c in self.clients:
1541
if c.fingerprint == fpr:
1545
logger.warning("Client not found for fingerprint: %s, ad"
1546
"dress: %s", fpr, address)
1549
mandos_dbus_service.ClientNotFound(fpr, address[0])
1550
parent_pipe.send(False)
1553
gobject.io_add_watch(parent_pipe.fileno(),
1554
gobject.IO_IN | gobject.IO_HUP,
1555
functools.partial(self.handle_ipc,
1556
parent_pipe = parent_pipe,
1557
client_object = client))
1558
parent_pipe.send(True)
1559
# remove the old hook in favor of the new above hook on same fileno
1561
if command == 'funcall':
1562
funcname = request[1]
1566
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1568
if command == 'getattr':
1569
attrname = request[1]
1570
if callable(client_object.__getattribute__(attrname)):
1571
parent_pipe.send(('function',))
1573
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1575
if command == 'setattr':
1576
attrname = request[1]
1578
setattr(client_object, attrname, value)
545
return super(type(self), self).server_bind()
1583
548
def string_to_delta(interval):
1584
549
"""Parse a string and return a datetime.timedelta
1586
551
>>> string_to_delta('7d')
1587
552
datetime.timedelta(7)
1588
553
>>> string_to_delta('60s')
1719
688
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1720
689
"servicename": "Mandos",
1726
692
# Parse config file for server-global settings
1727
server_config = configparser.SafeConfigParser(server_defaults)
693
server_config = ConfigParser.SafeConfigParser(server_defaults)
1728
694
del server_defaults
1729
server_config.read(os.path.join(options.configdir,
695
server_config.read(os.path.join(options.configdir, "server.conf"))
696
server_section = "server"
1731
697
# Convert the SafeConfigParser object to a dict
1732
server_settings = server_config.defaults()
1733
# Use the appropriate methods on the non-string config options
1734
for option in ("debug", "use_dbus", "use_ipv6"):
1735
server_settings[option] = server_config.getboolean("DEFAULT",
1737
if server_settings["port"]:
1738
server_settings["port"] = server_config.getint("DEFAULT",
698
server_settings = dict(server_config.items(server_section))
699
# Use getboolean on the boolean config option
700
server_settings["debug"] = server_config.getboolean\
701
(server_section, "debug")
1740
702
del server_config
1742
704
# Override the settings from the config file with command line
1743
705
# options, if set.
1744
706
for option in ("interface", "address", "port", "debug",
1745
"priority", "servicename", "configdir",
1746
"use_dbus", "use_ipv6", "debuglevel"):
707
"priority", "servicename", "configdir"):
1747
708
value = getattr(options, option)
1748
709
if value is not None:
1749
710
server_settings[option] = value
1751
# Force all strings to be unicode
1752
for option in server_settings.keys():
1753
if type(server_settings[option]) is str:
1754
server_settings[option] = unicode(server_settings[option])
1755
712
# Now we have our good server settings in "server_settings"
1757
##################################################################
1760
debug = server_settings["debug"]
1761
debuglevel = server_settings["debuglevel"]
1762
use_dbus = server_settings["use_dbus"]
1763
use_ipv6 = server_settings["use_ipv6"]
1765
if server_settings["servicename"] != "Mandos":
1766
syslogger.setFormatter(logging.Formatter
1767
('Mandos (%s) [%%(process)d]:'
1768
' %%(levelname)s: %%(message)s'
1769
% server_settings["servicename"]))
1771
714
# Parse config file with clients
1772
715
client_defaults = { "timeout": "1h",
1773
716
"interval": "5m",
1774
"checker": "fping -q -- %%(host)s",
1776
"approval_delay": "0s",
1777
"approval_duration": "1s",
717
"checker": "fping -q -- %%(fqdn)s",
1779
client_config = configparser.SafeConfigParser(client_defaults)
719
client_config = ConfigParser.SafeConfigParser(client_defaults)
1780
720
client_config.read(os.path.join(server_settings["configdir"],
1781
721
"clients.conf"))
1783
global mandos_dbus_service
1784
mandos_dbus_service = None
1786
tcp_server = MandosServer((server_settings["address"],
1787
server_settings["port"]),
1789
interface=(server_settings["interface"]
1793
server_settings["priority"],
1796
pidfilename = "/var/run/mandos.pid"
1798
pidfile = open(pidfilename, "w")
1800
logger.error("Could not open file %r", pidfilename)
1803
uid = pwd.getpwnam("_mandos").pw_uid
1804
gid = pwd.getpwnam("_mandos").pw_gid
1807
uid = pwd.getpwnam("mandos").pw_uid
1808
gid = pwd.getpwnam("mandos").pw_gid
1811
uid = pwd.getpwnam("nobody").pw_uid
1812
gid = pwd.getpwnam("nobody").pw_gid
1819
except OSError, error:
1820
if error[0] != errno.EPERM:
1823
if not debug and not debuglevel:
1824
syslogger.setLevel(logging.WARNING)
1825
console.setLevel(logging.WARNING)
1827
level = getattr(logging, debuglevel.upper())
1828
syslogger.setLevel(level)
1829
console.setLevel(level)
1832
# Enable all possible GnuTLS debugging
1834
# "Use a log level over 10 to enable all debugging options."
1836
gnutls.library.functions.gnutls_global_set_log_level(11)
1838
@gnutls.library.types.gnutls_log_func
1839
def debug_gnutls(level, string):
1840
logger.debug("GnuTLS: %s", string[:-1])
1842
(gnutls.library.functions
1843
.gnutls_global_set_log_function(debug_gnutls))
1845
# Redirect stdin so all checkers get /dev/null
1846
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1847
os.dup2(null, sys.stdin.fileno())
1851
# No console logging
1852
logger.removeHandler(console)
1854
# Need to fork before connecting to D-Bus
1856
# Close all input and output, do double fork, etc.
724
service = AvahiService(name = server_settings["servicename"],
725
type = "_mandos._tcp", );
726
if server_settings["interface"]:
727
service.interface = if_nametoindex(server_settings["interface"])
1859
729
global main_loop
1860
732
# From the Avahi example code
1861
733
DBusGMainLoop(set_as_default=True )
1862
734
main_loop = gobject.MainLoop()
1863
735
bus = dbus.SystemBus()
736
server = dbus.Interface(
737
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
738
avahi.DBUS_INTERFACE_SERVER )
1864
739
# End of Avahi example code
1867
bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
1868
bus, do_not_queue=True)
1869
except dbus.exceptions.NameExistsException, e:
1870
logger.error(unicode(e) + ", disabling D-Bus")
1872
server_settings["use_dbus"] = False
1873
tcp_server.use_dbus = False
1874
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1875
service = AvahiService(name = server_settings["servicename"],
1876
servicetype = "_mandos._tcp",
1877
protocol = protocol, bus = bus)
1878
if server_settings["interface"]:
1879
service.interface = (if_nametoindex
1880
(str(server_settings["interface"])))
1882
global multiprocessing_manager
1883
multiprocessing_manager = multiprocessing.Manager()
1885
client_class = Client
1887
client_class = functools.partial(ClientDBus, bus = bus)
1888
def client_config_items(config, section):
1889
special_settings = {
1890
"approved_by_default":
1891
lambda: config.getboolean(section,
1892
"approved_by_default"),
1894
for name, value in config.items(section):
1896
yield (name, special_settings[name]())
1900
tcp_server.clients.update(set(
1901
client_class(name = section,
1902
config= dict(client_config_items(
1903
client_config, section)))
1904
for section in client_config.sections()))
1905
if not tcp_server.clients:
1906
logger.warning("No clients defined")
1912
pidfile.write(str(pid) + "\n".encode("utf-8"))
1915
logger.error("Could not write to file %r with PID %d",
1918
# "pidfile" was never created
741
debug = server_settings["debug"]
744
console = logging.StreamHandler()
745
# console.setLevel(logging.DEBUG)
746
console.setFormatter(logging.Formatter\
747
('%(levelname)s: %(message)s'))
748
logger.addHandler(console)
752
def remove_from_clients(client):
753
clients.remove(client)
755
logger.critical(u"No clients left, exiting")
758
clients.update(Set(Client(name = section,
759
stop_hook = remove_from_clients,
761
= dict(client_config.items(section)))
762
for section in client_config.sections()))
768
"Cleanup function; run on exit"
770
# From the Avahi example code
771
if not group is None:
774
# End of Avahi example code
777
client = clients.pop()
778
client.stop_hook = None
781
atexit.register(cleanup)
1922
784
signal.signal(signal.SIGINT, signal.SIG_IGN)
1924
785
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1925
786
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1928
class MandosDBusService(dbus.service.Object):
1929
"""A D-Bus proxy object"""
1931
dbus.service.Object.__init__(self, bus, "/")
1932
_interface = "se.bsnet.fukt.Mandos"
1934
@dbus.service.signal(_interface, signature="o")
1935
def ClientAdded(self, objpath):
1939
@dbus.service.signal(_interface, signature="ss")
1940
def ClientNotFound(self, fingerprint, address):
1944
@dbus.service.signal(_interface, signature="os")
1945
def ClientRemoved(self, objpath, name):
1949
@dbus.service.method(_interface, out_signature="ao")
1950
def GetAllClients(self):
1952
return dbus.Array(c.dbus_object_path
1953
for c in tcp_server.clients)
1955
@dbus.service.method(_interface,
1956
out_signature="a{oa{sv}}")
1957
def GetAllClientsWithProperties(self):
1959
return dbus.Dictionary(
1960
((c.dbus_object_path, c.GetAll(""))
1961
for c in tcp_server.clients),
1964
@dbus.service.method(_interface, in_signature="o")
1965
def RemoveClient(self, object_path):
1967
for c in tcp_server.clients:
1968
if c.dbus_object_path == object_path:
1969
tcp_server.clients.remove(c)
1970
c.remove_from_connection()
1971
# Don't signal anything except ClientRemoved
1972
c.disable(quiet=True)
1974
self.ClientRemoved(object_path, c.name)
1976
raise KeyError(object_path)
1980
mandos_dbus_service = MandosDBusService()
1983
"Cleanup function; run on exit"
1986
while tcp_server.clients:
1987
client = tcp_server.clients.pop()
1989
client.remove_from_connection()
1990
client.disable_hook = None
1991
# Don't signal anything except ClientRemoved
1992
client.disable(quiet=True)
1995
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
1998
atexit.register(cleanup)
2000
for client in tcp_server.clients:
2003
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2007
tcp_server.server_activate()
788
for client in clients:
791
tcp_server = IPv6_TCPServer((server_settings["address"],
792
server_settings["port"]),
794
settings=server_settings,
2009
796
# Find out what port we got
2010
797
service.port = tcp_server.socket.getsockname()[1]
2012
logger.info("Now listening on address %r, port %d,"
2013
" flowinfo %d, scope_id %d"
2014
% tcp_server.socket.getsockname())
2016
logger.info("Now listening on address %r, port %d"
2017
% tcp_server.socket.getsockname())
798
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
799
u" scope_id %d" % tcp_server.socket.getsockname())
2019
801
#service.interface = tcp_server.socket.getsockname()[3]
2022
804
# From the Avahi example code
805
server.connect_to_signal("StateChanged", server_state_changed)
807
server_state_changed(server.GetState())
2025
808
except dbus.exceptions.DBusException, error:
2026
logger.critical("DBusException: %s", error)
809
logger.critical(u"DBusException: %s", error)
2029
811
# End of Avahi example code
2031
813
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2032
814
lambda *args, **kwargs:
2033
(tcp_server.handle_request
2034
(*args[2:], **kwargs) or True))
815
tcp_server.handle_request\
816
(*args[2:], **kwargs) or True)
2036
818
logger.debug("Starting main loop")
819
main_loop_started = True
2038
821
except AvahiError, error:
2039
logger.critical("AvahiError: %s", error)
822
logger.critical(u"AvahiError: %s" + unicode(error))
2042
824
except KeyboardInterrupt:
2044
print("", file=sys.stderr)
2045
logger.debug("Server received KeyboardInterrupt")
2046
logger.debug("Server exiting")
2047
# Must run before the D-Bus bus name gets deregistered
2050
828
if __name__ == '__main__':