129
105
max_renames: integer; maximum number of renames
130
106
rename_count: integer; counter so we only rename after collisions
131
107
a sensible number of times
132
group: D-Bus Entry Group
134
bus: dbus.SystemBus()
136
109
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):
110
type = None, port = None, TXT = None, domain = "",
111
host = "", max_renames = 32768):
140
112
self.interface = interface
142
self.type = servicetype
144
self.TXT = TXT if TXT is not None else []
145
120
self.domain = domain
147
122
self.rename_count = 0
148
123
self.max_renames = max_renames
149
self.protocol = protocol
150
self.group = None # our entry group
153
124
def rename(self):
154
125
"""Derived from the Avahi example code"""
155
126
if self.rename_count >= self.max_renames:
156
127
logger.critical(u"No suitable Zeroconf service name found"
157
128
u" after %i retries, exiting.",
159
raise AvahiServiceError(u"Too many renames")
160
self.name = unicode(self.server.GetAlternativeServiceName(self.name))
130
raise AvahiServiceError("Too many renames")
131
self.name = server.GetAlternativeServiceName(self.name)
161
132
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'
134
syslogger.setFormatter(logging.Formatter\
135
('Mandos (%s): %%(levelname)s:'
136
' %%(message)s' % self.name))
169
139
self.rename_count += 1
170
140
def remove(self):
171
141
"""Derived from the Avahi example code"""
172
if self.group is not None:
142
if group is not None:
175
145
"""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)
148
group = dbus.Interface\
149
(bus.get_object(avahi.DBUS_NAME,
150
server.EntryGroupNew()),
151
avahi.DBUS_INTERFACE_ENTRY_GROUP)
152
group.connect_to_signal('StateChanged',
153
entry_group_state_changed)
184
154
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())
155
service.name, service.type)
157
self.interface, # interface
158
avahi.PROTO_INET6, # protocol
159
dbus.UInt32(0), # flags
160
self.name, self.type,
161
self.domain, self.host,
162
dbus.UInt16(self.port),
163
avahi.string_array_to_txt_array(self.TXT))
166
# From the Avahi example code:
167
group = None # our entry group
168
# End of Avahi example code
233
171
class Client(object):
234
172
"""A representation of a client host served by this server.
237
name: string; from the config file, used in log messages and
174
name: string; from the config file, used in log messages
239
175
fingerprint: string (40 or 32 hexadecimal digits); used to
240
176
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.
177
secret: bytestring; sent verbatim (over TLS) to client
178
host: string; available for use by the checker command
179
created: datetime.datetime(); object creation, not client host
180
last_checked_ok: datetime.datetime() or None if not yet checked OK
181
timeout: datetime.timedelta(); How long from last_checked_ok
182
until this client is invalid
183
interval: datetime.timedelta(); How often to start a new checker
184
stop_hook: If set, called by stop() as stop_hook(self)
185
checker: subprocess.Popen(); a running checker process used
186
to see if the client lives.
187
'None' if no process is running.
254
188
checker_initiator_tag: a gobject event source tag, or None
255
disable_initiator_tag: - '' -
189
stop_initiator_tag: - '' -
256
190
checker_callback_tag: - '' -
257
191
checker_command: string; External command which is run to check if
258
192
client lives. %() expansions are done at
259
193
runtime with vars(self) as dict, so that for
260
194
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
196
_timeout: Real variable for 'timeout'
197
_interval: Real variable for 'interval'
198
_timeout_milliseconds: Used when calling gobject.timeout_add()
199
_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):
201
def _set_timeout(self, timeout):
202
"Setter function for 'timeout' attribute"
203
self._timeout = timeout
204
self._timeout_milliseconds = ((self.timeout.days
205
* 24 * 60 * 60 * 1000)
206
+ (self.timeout.seconds * 1000)
207
+ (self.timeout.microseconds
209
timeout = property(lambda self: self._timeout,
212
def _set_interval(self, interval):
213
"Setter function for 'interval' attribute"
214
self._interval = interval
215
self._interval_milliseconds = ((self.interval.days
216
* 24 * 60 * 60 * 1000)
217
+ (self.interval.seconds
219
+ (self.interval.microseconds
221
interval = property(lambda self: self._interval,
224
def __init__(self, name = None, stop_hook=None, config={}):
286
225
"""Note: the 'checker' key in 'config' sets the
287
226
'checker_command' attribute and *not* the 'checker'
292
229
logger.debug(u"Creating client %r", self.name)
293
230
# Uppercase and remove spaces from fingerprint for later
294
231
# comparison purposes with return value from the fingerprint()
296
self.fingerprint = (config[u"fingerprint"].upper()
233
self.fingerprint = config["fingerprint"].upper()\
298
235
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()
236
if "secret" in config:
237
self.secret = config["secret"].decode(u"base64")
238
elif "secfile" in config:
239
sf = open(config["secfile"])
240
self.secret = sf.read()
307
243
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
245
self.host = config.get("host", "")
246
self.created = datetime.datetime.now()
313
247
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
248
self.timeout = string_to_delta(config["timeout"])
249
self.interval = string_to_delta(config["interval"])
250
self.stop_hook = stop_hook
317
251
self.checker = None
318
252
self.checker_initiator_tag = None
319
self.disable_initiator_tag = None
253
self.stop_initiator_tag = None
320
254
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()
255
self.check_command = config["checker"]
340
257
"""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
258
# Schedule a new checker to be started an 'interval' from now,
347
259
# 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(),
260
self.checker_initiator_tag = gobject.timeout_add\
261
(self._interval_milliseconds,
356
263
# Also start a new checker *right now*.
357
264
self.start_checker()
359
def disable(self, quiet=True):
360
"""Disable this client."""
361
if not getattr(self, "enabled", False):
265
# Schedule a stop() when 'timeout' has passed
266
self.stop_initiator_tag = gobject.timeout_add\
267
(self._timeout_milliseconds,
271
The possibility that a client might be restarted is left open,
272
but not currently used."""
273
# If this client doesn't have a secret, it is already stopped.
274
if hasattr(self, "secret") and self.secret:
275
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):
279
if getattr(self, "stop_initiator_tag", False):
280
gobject.source_remove(self.stop_initiator_tag)
281
self.stop_initiator_tag = None
282
if getattr(self, "checker_initiator_tag", False):
371
283
gobject.source_remove(self.checker_initiator_tag)
372
284
self.checker_initiator_tag = None
373
285
self.stop_checker()
374
if self.disable_hook:
375
self.disable_hook(self)
377
288
# Do not run this again if called by a gobject.timeout_add
380
290
def __del__(self):
381
self.disable_hook = None
384
def checker_callback(self, pid, condition, command):
291
self.stop_hook = None
293
def checker_callback(self, pid, condition):
385
294
"""The checker has completed, so take appropriate actions."""
295
now = datetime.datetime.now()
386
296
self.checker_callback_tag = None
387
297
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",
298
if os.WIFEXITED(condition) \
299
and (os.WEXITSTATUS(condition) == 0):
300
logger.info(u"Checker for %(name)s succeeded",
302
self.last_checked_ok = now
303
gobject.source_remove(self.stop_initiator_tag)
304
self.stop_initiator_tag = gobject.timeout_add\
305
(self._timeout_milliseconds,
307
elif not os.WIFEXITED(condition):
398
308
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(),
311
logger.info(u"Checker for %(name)s failed",
413
313
def start_checker(self):
414
314
"""Start a new checker subprocess if one is not running.
416
315
If a checker already exists, leave it running and do
418
317
# The reason for not killing a running checker is that if we
466
346
# always replaced by /dev/null.)
467
347
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)
350
self.checker_callback_tag = gobject.child_watch_add\
352
self.checker_callback)
480
353
except OSError, error:
481
354
logger.error(u"Failed to start subprocess: %s",
483
356
# Re-run this periodically if run by gobject.timeout_add
486
358
def stop_checker(self):
487
359
"""Force the checker process, if any, to stop."""
488
360
if self.checker_callback_tag:
489
361
gobject.source_remove(self.checker_callback_tag)
490
362
self.checker_callback_tag = None
491
if getattr(self, u"checker", None) is None:
363
if getattr(self, "checker", None) is None:
493
365
logger.debug(u"Stopping checker for %(name)s", vars(self))
495
367
os.kill(self.checker.pid, signal.SIGTERM)
497
369
#if self.checker.poll() is None:
498
370
# os.kill(self.checker.pid, signal.SIGKILL)
499
371
except OSError, error:
500
372
if error.errno != errno.ESRCH: # No such process
502
374
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:
375
def still_valid(self):
376
"""Has the timeout not yet passed for this client?"""
377
now = datetime.datetime.now()
991
378
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.
379
return now < (self.created + self.timeout)
381
return now < (self.last_checked_ok + self.timeout)
384
def peer_certificate(session):
385
"Return the peer's OpenPGP certificate as a bytestring"
386
# If not an OpenPGP certificate...
387
if gnutls.library.functions.gnutls_certificate_type_get\
388
(session._c_object) \
389
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
390
# ...do the normal thing
391
return session.peer_certificate
392
list_size = ctypes.c_uint()
393
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
394
(session._c_object, ctypes.byref(list_size))
395
if list_size.value == 0:
398
return ctypes.string_at(cert.data, cert.size)
401
def fingerprint(openpgp):
402
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
403
# New GnuTLS "datum" with the OpenPGP public key
404
datum = gnutls.library.types.gnutls_datum_t\
405
(ctypes.cast(ctypes.c_char_p(openpgp),
406
ctypes.POINTER(ctypes.c_ubyte)),
407
ctypes.c_uint(len(openpgp)))
408
# New empty GnuTLS certificate
409
crt = gnutls.library.types.gnutls_openpgp_crt_t()
410
gnutls.library.functions.gnutls_openpgp_crt_init\
412
# Import the OpenPGP public key into the certificate
413
gnutls.library.functions.gnutls_openpgp_crt_import\
414
(crt, ctypes.byref(datum),
415
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
416
# Verify the self signature in the key
417
crtverify = ctypes.c_uint();
418
gnutls.library.functions.gnutls_openpgp_crt_verify_self\
419
(crt, 0, ctypes.byref(crtverify))
420
if crtverify.value != 0:
421
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
422
raise gnutls.errors.CertificateSecurityError("Verify failed")
423
# New buffer for the fingerprint
424
buffer = ctypes.create_string_buffer(20)
425
buffer_length = ctypes.c_size_t()
426
# Get the fingerprint from the certificate into the buffer
427
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
428
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
429
# Deinit the certificate
430
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
431
# Convert the buffer to a Python bytestring
432
fpr = ctypes.string_at(buffer, buffer_length.value)
433
# Convert the bytestring to hexadecimal notation
434
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
438
class tcp_handler(SocketServer.BaseRequestHandler, object):
439
"""A TCP request handler class.
440
Instantiated by IPv6_TCPServer for each request to handle it.
1109
441
Note: This will run in its own forked process."""
1111
443
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
444
logger.info(u"TCP connection from: %s",
445
unicode(self.client_address))
446
session = gnutls.connection.ClientSession\
447
(self.request, gnutls.connection.X509Credentials())
449
line = self.request.makefile().readline()
450
logger.debug(u"Protocol version: %r", line)
452
if int(line.strip().split()[0]) > 1:
454
except (ValueError, IndexError, RuntimeError), error:
455
logger.error(u"Unknown protocol version: %s", error)
458
# Note: gnutls.connection.X509Credentials is really a generic
459
# GnuTLS certificate credentials object so long as no X.509
460
# keys are added to it. Therefore, we can use it here despite
461
# using OpenPGP certificates.
463
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
464
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
466
priority = "NORMAL" # Fallback default, since this
468
if self.server.settings["priority"]:
469
priority = self.server.settings["priority"]
470
gnutls.library.functions.gnutls_priority_set_direct\
471
(session._c_object, priority, None);
475
except gnutls.errors.GNUTLSError, error:
476
logger.warning(u"Handshake failed: %s", error)
477
# Do not run session.bye() here: the session is not
478
# established. Just abandon the request.
481
fpr = fingerprint(peer_certificate(session))
482
except (TypeError, gnutls.errors.GNUTLSError), error:
483
logger.warning(u"Bad certificate: %s", error)
486
logger.debug(u"Fingerprint: %s", fpr)
488
for c in self.server.clients:
489
if c.fingerprint == fpr:
493
logger.warning(u"Client not found for fingerprint: %s",
497
# Have to check if client.still_valid(), since it is possible
498
# that the client timed out while establishing the GnuTLS
500
if not client.still_valid():
501
logger.warning(u"Client %(name)s is invalid",
506
while sent_size < len(client.secret):
507
sent = session.send(client.secret[sent_size:])
508
logger.debug(u"Sent: %d, remaining: %d",
509
sent, len(client.secret)
510
- (sent_size + sent))
515
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
516
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1359
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
518
settings: Server settings
519
clients: Set() of Client objects
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)
521
address_family = socket.AF_INET6
522
def __init__(self, *args, **kwargs):
523
if "settings" in kwargs:
524
self.settings = kwargs["settings"]
525
del kwargs["settings"]
526
if "clients" in kwargs:
527
self.clients = kwargs["clients"]
528
del kwargs["clients"]
529
return super(type(self), self).__init__(*args, **kwargs)
1370
530
def server_bind(self):
1371
531
"""This overrides the normal server_bind() function
1372
532
to bind to an interface if one was specified, and also NOT to
1373
533
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",
534
if self.settings["interface"]:
535
# 25 is from /usr/include/asm-i486/socket.h
536
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
538
self.socket.setsockopt(socket.SOL_SOCKET,
540
self.settings["interface"])
541
except socket.error, error:
542
if error[0] == errno.EPERM:
543
logger.error(u"No permission to"
544
u" bind to interface %s",
545
self.settings["interface"])
1396
548
# Only bind(2) the socket if we really need to.
1397
549
if self.server_address[0] or self.server_address[1]:
1398
550
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,
552
self.server_address = (in6addr_any,
1404
553
self.server_address[1])
1405
554
elif not self.server_address[1]:
1406
555
self.server_address = (self.server_address[0],
1408
# if self.interface:
557
# if self.settings["interface"]:
1409
558
# self.server_address = (self.server_address[0],
1412
561
# 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)
1440
def server_activate(self):
1442
return socketserver.TCPServer.server_activate(self)
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)
564
return super(type(self), self).server_bind()
1527
567
def string_to_delta(interval):
1528
568
"""Parse a string and return a datetime.timedelta
1530
>>> string_to_delta(u'7d')
570
>>> string_to_delta('7d')
1531
571
datetime.timedelta(7)
1532
>>> string_to_delta(u'60s')
572
>>> string_to_delta('60s')
1533
573
datetime.timedelta(0, 60)
1534
>>> string_to_delta(u'60m')
574
>>> string_to_delta('60m')
1535
575
datetime.timedelta(0, 3600)
1536
>>> string_to_delta(u'24h')
576
>>> string_to_delta('24h')
1537
577
datetime.timedelta(1)
1538
578
>>> string_to_delta(u'1w')
1539
579
datetime.timedelta(7)
1540
>>> string_to_delta(u'5m 30s')
580
>>> string_to_delta('5m 30s')
1541
581
datetime.timedelta(0, 330)
1543
583
timevalue = datetime.timedelta(0)
1544
584
for s in interval.split():
1546
suffix = unicode(s[-1])
586
suffix=unicode(s[-1])
1548
588
if suffix == u"d":
1549
589
delta = datetime.timedelta(value)
1550
590
elif suffix == u"s":
1654
708
# 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",
709
server_defaults = { "interface": "",
714
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
715
"servicename": "Mandos",
1667
718
# Parse config file for server-global settings
1668
server_config = configparser.SafeConfigParser(server_defaults)
719
server_config = ConfigParser.SafeConfigParser(server_defaults)
1669
720
del server_defaults
1670
server_config.read(os.path.join(options.configdir,
721
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1672
722
# Convert the SafeConfigParser object to a dict
1673
723
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",
724
# Use getboolean on the boolean config option
725
server_settings["debug"] = server_config.getboolean\
1681
727
del server_config
1683
729
# Override the settings from the config file with command line
1684
730
# 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"):
731
for option in ("interface", "address", "port", "debug",
732
"priority", "servicename", "configdir"):
1688
733
value = getattr(options, option)
1689
734
if value is not None:
1690
735
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
737
# 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"]))
1712
# 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",
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
1758
except OSError, error:
1759
if error[0] != errno.EPERM:
1762
# Enable all possible GnuTLS debugging
1765
if not debug and not debuglevel:
739
debug = server_settings["debug"]
1766
742
syslogger.setLevel(logging.WARNING)
1767
743
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)
745
if server_settings["servicename"] != "Mandos":
746
syslogger.setFormatter(logging.Formatter\
747
('Mandos (%s): %%(levelname)s:'
749
% server_settings["servicename"]))
751
# Parse config file with clients
752
client_defaults = { "timeout": "1h",
754
"checker": "fping -q -- %(host)s",
757
client_config = ConfigParser.SafeConfigParser(client_defaults)
758
client_config.read(os.path.join(server_settings["configdir"],
762
service = AvahiService(name = server_settings["servicename"],
763
type = "_mandos._tcp", );
764
if server_settings["interface"]:
765
service.interface = if_nametoindex\
766
(server_settings["interface"])
1795
768
global main_loop
1796
771
# From the Avahi example code
1797
772
DBusGMainLoop(set_as_default=True )
1798
773
main_loop = gobject.MainLoop()
1799
774
bus = dbus.SystemBus()
775
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
776
avahi.DBUS_PATH_SERVER),
777
avahi.DBUS_INTERFACE_SERVER)
1800
778
# 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"])))
781
def remove_from_clients(client):
782
clients.remove(client)
784
logger.critical(u"No clients left, exiting")
787
clients.update(Set(Client(name = section,
788
stop_hook = remove_from_clients,
790
= dict(client_config.items(section)))
791
for section in client_config.sections()))
793
logger.critical(u"No clients defined")
797
# Redirect stdin so all checkers get /dev/null
798
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
799
os.dup2(null, sys.stdin.fileno())
804
logger.removeHandler(console)
1819
805
# 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")
808
pidfilename = "/var/run/mandos/mandos.pid"
1851
pidfile.write(str(pid) + "\n")
811
pidfile = open(pidfilename, "w")
812
pidfile.write(str(pid) + "\n")
1854
logger.error(u"Could not write to file %r with PID %d",
1857
# "pidfile" was never created
816
logger.error(u"Could not write %s file with PID %d",
817
pidfilename, os.getpid())
820
"Cleanup function; run on exit"
822
# From the Avahi example code
823
if not group is None:
826
# End of Avahi example code
829
client = clients.pop()
830
client.stop_hook = None
833
atexit.register(cleanup)
1862
836
signal.signal(signal.SIGINT, signal.SIG_IGN)
1863
837
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1864
838
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1867
class MandosDBusService(dbus.service.Object):
1868
"""A D-Bus proxy object"""
1870
dbus.service.Object.__init__(self, bus, u"/")
1871
_interface = u"se.bsnet.fukt.Mandos"
1873
@dbus.service.signal(_interface, signature=u"o")
1874
def ClientAdded(self, objpath):
1878
@dbus.service.signal(_interface, signature=u"ss")
1879
def ClientNotFound(self, fingerprint, address):
1883
@dbus.service.signal(_interface, signature=u"os")
1884
def ClientRemoved(self, objpath, name):
1888
@dbus.service.method(_interface, out_signature=u"ao")
1889
def GetAllClients(self):
1891
return dbus.Array(c.dbus_object_path
1892
for c in tcp_server.clients)
1894
@dbus.service.method(_interface,
1895
out_signature=u"a{oa{sv}}")
1896
def GetAllClientsWithProperties(self):
1898
return dbus.Dictionary(
1899
((c.dbus_object_path, c.GetAll(u""))
1900
for c in tcp_server.clients),
1901
signature=u"oa{sv}")
1903
@dbus.service.method(_interface, in_signature=u"o")
1904
def RemoveClient(self, object_path):
1906
for c in tcp_server.clients:
1907
if c.dbus_object_path == object_path:
1908
tcp_server.clients.remove(c)
1909
c.remove_from_connection()
1910
# Don't signal anything except ClientRemoved
1911
c.disable(quiet=True)
1913
self.ClientRemoved(object_path, c.name)
1915
raise KeyError(object_path)
1919
mandos_dbus_service = MandosDBusService()
1922
"Cleanup function; run on exit"
1925
while tcp_server.clients:
1926
client = tcp_server.clients.pop()
1928
client.remove_from_connection()
1929
client.disable_hook = None
1930
# Don't signal anything except ClientRemoved
1931
client.disable(quiet=True)
1934
mandos_dbus_service.ClientRemoved(client.dbus_object_path,
1937
atexit.register(cleanup)
1939
for client in tcp_server.clients:
1942
mandos_dbus_service.ClientAdded(client.dbus_object_path)
1946
tcp_server.server_activate()
840
for client in clients:
843
tcp_server = IPv6_TCPServer((server_settings["address"],
844
server_settings["port"]),
846
settings=server_settings,
1948
848
# Find out what port we got
1949
849
service.port = tcp_server.socket.getsockname()[1]
1951
logger.info(u"Now listening on address %r, port %d,"
1952
" flowinfo %d, scope_id %d"
1953
% tcp_server.socket.getsockname())
1955
logger.info(u"Now listening on address %r, port %d"
1956
% tcp_server.socket.getsockname())
850
logger.info(u"Now listening on address %r, port %d, flowinfo %d,"
851
u" scope_id %d" % tcp_server.socket.getsockname())
1958
853
#service.interface = tcp_server.socket.getsockname()[3]
1961
856
# From the Avahi example code
857
server.connect_to_signal("StateChanged", server_state_changed)
859
server_state_changed(server.GetState())
1964
860
except dbus.exceptions.DBusException, error:
1965
861
logger.critical(u"DBusException: %s", error)
1968
863
# End of Avahi example code
1970
865
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
1971
866
lambda *args, **kwargs:
1972
(tcp_server.handle_request
1973
(*args[2:], **kwargs) or True))
867
tcp_server.handle_request\
868
(*args[2:], **kwargs) or True)
1975
870
logger.debug(u"Starting main loop")
871
main_loop_started = True
1977
873
except AvahiError, error:
1978
logger.critical(u"AvahiError: %s", error)
874
logger.critical(u"AvahiError: %s" + unicode(error))
1981
876
except KeyboardInterrupt:
1984
logger.debug(u"Server received KeyboardInterrupt")
1985
logger.debug(u"Server exiting")
1986
# Must run before the D-Bus bus name gets deregistered
1989
880
if __name__ == '__main__':