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