107
129
max_renames: integer; maximum number of renames
108
130
rename_count: integer; counter so we only rename after collisions
109
131
a sensible number of times
132
group: D-Bus Entry Group
134
bus: dbus.SystemBus()
111
136
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
112
servicetype = None, port = None, TXT = None, domain = "",
113
host = "", max_renames = 32768):
137
servicetype = None, port = None, TXT = None,
138
domain = u"", host = u"", max_renames = 32768,
139
protocol = avahi.PROTO_UNSPEC, bus = None):
114
140
self.interface = interface
116
142
self.type = servicetype
144
self.TXT = TXT if TXT is not None else []
122
145
self.domain = domain
124
147
self.rename_count = 0
125
148
self.max_renames = max_renames
149
self.protocol = protocol
150
self.group = None # our entry group
126
153
def rename(self):
127
154
"""Derived from the Avahi example code"""
128
155
if self.rename_count >= self.max_renames:
129
156
logger.critical(u"No suitable Zeroconf service name found"
130
157
u" after %i retries, exiting.",
131
158
self.rename_count)
132
raise AvahiServiceError("Too many renames")
133
self.name = server.GetAlternativeServiceName(self.name)
159
raise AvahiServiceError(u"Too many renames")
160
self.name = unicode(self.server.GetAlternativeServiceName(self.name))
134
161
logger.info(u"Changing Zeroconf service name to %r ...",
136
syslogger.setFormatter(logging.Formatter\
137
('Mandos (%s): %%(levelname)s:'
138
' %%(message)s' % self.name))
163
syslogger.setFormatter(logging.Formatter
164
(u'Mandos (%s) [%%(process)d]:'
165
u' %%(levelname)s: %%(message)s'
170
except dbus.exceptions.DBusException, error:
171
logger.critical(u"DBusException: %s", error)
141
174
self.rename_count += 1
142
175
def remove(self):
143
176
"""Derived from the Avahi example code"""
144
if group is not None:
177
if self.group is not None:
147
180
"""Derived from the Avahi example code"""
150
group = dbus.Interface\
151
(bus.get_object(avahi.DBUS_NAME,
152
server.EntryGroupNew()),
153
avahi.DBUS_INTERFACE_ENTRY_GROUP)
154
group.connect_to_signal('StateChanged',
155
entry_group_state_changed)
181
if self.group is None:
182
self.group = dbus.Interface(
183
self.bus.get_object(avahi.DBUS_NAME,
184
self.server.EntryGroupNew()),
185
avahi.DBUS_INTERFACE_ENTRY_GROUP)
186
self.group.connect_to_signal('StateChanged',
188
.entry_group_state_changed)
156
189
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
157
service.name, service.type)
159
self.interface, # interface
160
avahi.PROTO_INET6, # protocol
161
dbus.UInt32(0), # flags
162
self.name, self.type,
163
self.domain, self.host,
164
dbus.UInt16(self.port),
165
avahi.string_array_to_txt_array(self.TXT))
168
# From the Avahi example code:
169
group = None # our entry group
170
# End of Avahi example code
190
self.name, self.type)
191
self.group.AddService(
194
dbus.UInt32(0), # flags
195
self.name, self.type,
196
self.domain, self.host,
197
dbus.UInt16(self.port),
198
avahi.string_array_to_txt_array(self.TXT))
200
def entry_group_state_changed(self, state, error):
201
"""Derived from the Avahi example code"""
202
logger.debug(u"Avahi entry group state change: %i", state)
204
if state == avahi.ENTRY_GROUP_ESTABLISHED:
205
logger.debug(u"Zeroconf service established.")
206
elif state == avahi.ENTRY_GROUP_COLLISION:
207
logger.warning(u"Zeroconf service name collision.")
209
elif state == avahi.ENTRY_GROUP_FAILURE:
210
logger.critical(u"Avahi: Error in group state changed %s",
212
raise AvahiGroupError(u"State changed: %s"
215
"""Derived from the Avahi example code"""
216
if self.group is not None:
219
def server_state_changed(self, state):
220
"""Derived from the Avahi example code"""
221
logger.debug(u"Avahi server state change: %i", state)
222
if state == avahi.SERVER_COLLISION:
223
logger.error(u"Zeroconf server name collision")
225
elif state == avahi.SERVER_RUNNING:
228
"""Derived from the Avahi example code"""
229
if self.server is None:
230
self.server = dbus.Interface(
231
self.bus.get_object(avahi.DBUS_NAME,
232
avahi.DBUS_PATH_SERVER),
233
avahi.DBUS_INTERFACE_SERVER)
234
self.server.connect_to_signal(u"StateChanged",
235
self.server_state_changed)
236
self.server_state_changed(self.server.GetState())
173
239
class Client(object):
174
240
"""A representation of a client host served by this server.
176
name: string; from the config file, used in log messages
243
name: string; from the config file, used in log messages and
177
245
fingerprint: string (40 or 32 hexadecimal digits); used to
178
246
uniquely identify the client
179
secret: bytestring; sent verbatim (over TLS) to client
180
host: string; available for use by the checker command
181
created: datetime.datetime(); object creation, not client host
182
last_checked_ok: datetime.datetime() or None if not yet checked OK
183
timeout: datetime.timedelta(); How long from last_checked_ok
184
until this client is invalid
185
interval: datetime.timedelta(); How often to start a new checker
186
stop_hook: If set, called by stop() as stop_hook(self)
187
checker: subprocess.Popen(); a running checker process used
188
to see if the client lives.
189
'None' if no process is running.
247
secret: bytestring; sent verbatim (over TLS) to client
248
host: string; available for use by the checker command
249
created: datetime.datetime(); (UTC) object creation
250
last_enabled: datetime.datetime(); (UTC)
252
last_checked_ok: datetime.datetime(); (UTC) or None
253
timeout: datetime.timedelta(); How long from last_checked_ok
254
until this client is disabled
255
interval: datetime.timedelta(); How often to start a new checker
256
disable_hook: If set, called by disable() as disable_hook(self)
257
checker: subprocess.Popen(); a running checker process used
258
to see if the client lives.
259
'None' if no process is running.
190
260
checker_initiator_tag: a gobject event source tag, or None
191
stop_initiator_tag: - '' -
261
disable_initiator_tag: - '' -
192
262
checker_callback_tag: - '' -
193
263
checker_command: string; External command which is run to check if
194
264
client lives. %() expansions are done at
195
265
runtime with vars(self) as dict, so that for
196
266
instance %(name)s can be used in the command.
198
_timeout: Real variable for 'timeout'
199
_interval: Real variable for 'interval'
200
_timeout_milliseconds: Used when calling gobject.timeout_add()
201
_interval_milliseconds: - '' -
267
current_checker_command: string; current running checker_command
268
approval_delay: datetime.timedelta(); Time to wait for approval
269
_approved: bool(); 'None' if not yet approved/disapproved
270
approval_duration: datetime.timedelta(); Duration of one approval
203
def _set_timeout(self, timeout):
204
"Setter function for 'timeout' attribute"
205
self._timeout = timeout
206
self._timeout_milliseconds = ((self.timeout.days
207
* 24 * 60 * 60 * 1000)
208
+ (self.timeout.seconds * 1000)
209
+ (self.timeout.microseconds
211
timeout = property(lambda self: self._timeout,
214
def _set_interval(self, interval):
215
"Setter function for 'interval' attribute"
216
self._interval = interval
217
self._interval_milliseconds = ((self.interval.days
218
* 24 * 60 * 60 * 1000)
219
+ (self.interval.seconds
221
+ (self.interval.microseconds
223
interval = property(lambda self: self._interval,
226
def __init__(self, name = None, stop_hook=None, config=None):
274
def _timedelta_to_milliseconds(td):
275
"Convert a datetime.timedelta() to milliseconds"
276
return ((td.days * 24 * 60 * 60 * 1000)
277
+ (td.seconds * 1000)
278
+ (td.microseconds // 1000))
280
def timeout_milliseconds(self):
281
"Return the 'timeout' attribute in milliseconds"
282
return self._timedelta_to_milliseconds(self.timeout)
284
def interval_milliseconds(self):
285
"Return the 'interval' attribute in milliseconds"
286
return self._timedelta_to_milliseconds(self.interval)
288
def approval_delay_milliseconds(self):
289
return self._timedelta_to_milliseconds(self.approval_delay)
291
def __init__(self, name = None, disable_hook=None, config=None):
227
292
"""Note: the 'checker' key in 'config' sets the
228
293
'checker_command' attribute and *not* the 'checker'
230
296
if config is None:
233
298
logger.debug(u"Creating client %r", self.name)
234
299
# Uppercase and remove spaces from fingerprint for later
235
300
# comparison purposes with return value from the fingerprint()
237
self.fingerprint = config["fingerprint"].upper()\
302
self.fingerprint = (config[u"fingerprint"].upper()
239
304
logger.debug(u" Fingerprint: %s", self.fingerprint)
240
if "secret" in config:
241
self.secret = config["secret"].decode(u"base64")
242
elif "secfile" in config:
243
secfile = open(config["secfile"])
244
self.secret = secfile.read()
305
if u"secret" in config:
306
self.secret = config[u"secret"].decode(u"base64")
307
elif u"secfile" in config:
308
with open(os.path.expanduser(os.path.expandvars
309
(config[u"secfile"])),
311
self.secret = secfile.read()
247
313
raise TypeError(u"No secret or secfile for client %s"
249
self.host = config.get("host", "")
250
self.created = datetime.datetime.now()
315
self.host = config.get(u"host", u"")
316
self.created = datetime.datetime.utcnow()
318
self.last_enabled = None
251
319
self.last_checked_ok = None
252
self.timeout = string_to_delta(config["timeout"])
253
self.interval = string_to_delta(config["interval"])
254
self.stop_hook = stop_hook
320
self.timeout = string_to_delta(config[u"timeout"])
321
self.interval = string_to_delta(config[u"interval"])
322
self.disable_hook = disable_hook
255
323
self.checker = None
256
324
self.checker_initiator_tag = None
257
self.stop_initiator_tag = None
325
self.disable_initiator_tag = None
258
326
self.checker_callback_tag = None
259
self.check_command = config["checker"]
327
self.checker_command = config[u"checker"]
328
self.current_checker_command = None
329
self.last_connect = None
330
self._approved = None
331
self.approved_by_default = config.get(u"approved_by_default",
333
self.approvals_pending = 0
334
self.approval_delay = string_to_delta(
335
config[u"approval_delay"])
336
self.approval_duration = string_to_delta(
337
config[u"approval_duration"])
338
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
340
def send_changedstate(self):
341
self.changedstate.acquire()
342
self.changedstate.notify_all()
343
self.changedstate.release()
261
346
"""Start this client's checker and timeout hooks"""
347
if getattr(self, u"enabled", False):
350
self.send_changedstate()
351
self.last_enabled = datetime.datetime.utcnow()
262
352
# Schedule a new checker to be started an 'interval' from now,
263
353
# and every interval from then on.
264
self.checker_initiator_tag = gobject.timeout_add\
265
(self._interval_milliseconds,
354
self.checker_initiator_tag = (gobject.timeout_add
355
(self.interval_milliseconds(),
357
# Schedule a disable() when 'timeout' has passed
358
self.disable_initiator_tag = (gobject.timeout_add
359
(self.timeout_milliseconds(),
267
362
# Also start a new checker *right now*.
268
363
self.start_checker()
269
# Schedule a stop() when 'timeout' has passed
270
self.stop_initiator_tag = gobject.timeout_add\
271
(self._timeout_milliseconds,
275
The possibility that a client might be restarted is left open,
276
but not currently used."""
277
# If this client doesn't have a secret, it is already stopped.
278
if hasattr(self, "secret") and self.secret:
279
logger.info(u"Stopping client %s", self.name)
365
def disable(self, quiet=True):
366
"""Disable this client."""
367
if not getattr(self, "enabled", False):
283
if getattr(self, "stop_initiator_tag", False):
284
gobject.source_remove(self.stop_initiator_tag)
285
self.stop_initiator_tag = None
286
if getattr(self, "checker_initiator_tag", False):
370
self.send_changedstate()
372
logger.info(u"Disabling client %s", self.name)
373
if getattr(self, u"disable_initiator_tag", False):
374
gobject.source_remove(self.disable_initiator_tag)
375
self.disable_initiator_tag = None
376
if getattr(self, u"checker_initiator_tag", False):
287
377
gobject.source_remove(self.checker_initiator_tag)
288
378
self.checker_initiator_tag = None
289
379
self.stop_checker()
380
if self.disable_hook:
381
self.disable_hook(self)
292
383
# Do not run this again if called by a gobject.timeout_add
294
386
def __del__(self):
295
self.stop_hook = None
297
def checker_callback(self, pid, condition):
387
self.disable_hook = None
390
def checker_callback(self, pid, condition, command):
298
391
"""The checker has completed, so take appropriate actions."""
299
now = datetime.datetime.now()
300
392
self.checker_callback_tag = None
301
393
self.checker = None
302
if os.WIFEXITED(condition) \
303
and (os.WEXITSTATUS(condition) == 0):
304
logger.info(u"Checker for %(name)s succeeded",
306
self.last_checked_ok = now
307
gobject.source_remove(self.stop_initiator_tag)
308
self.stop_initiator_tag = gobject.timeout_add\
309
(self._timeout_milliseconds,
311
elif not os.WIFEXITED(condition):
394
if os.WIFEXITED(condition):
395
exitstatus = os.WEXITSTATUS(condition)
397
logger.info(u"Checker for %(name)s succeeded",
401
logger.info(u"Checker for %(name)s failed",
312
404
logger.warning(u"Checker for %(name)s crashed?",
315
logger.info(u"Checker for %(name)s failed",
407
def checked_ok(self):
408
"""Bump up the timeout for this client.
410
This should only be called when the client has been seen,
413
self.last_checked_ok = datetime.datetime.utcnow()
414
gobject.source_remove(self.disable_initiator_tag)
415
self.disable_initiator_tag = (gobject.timeout_add
416
(self.timeout_milliseconds(),
317
419
def start_checker(self):
318
420
"""Start a new checker subprocess if one is not running.
319
422
If a checker already exists, leave it running and do
321
424
# The reason for not killing a running checker is that if we
350
472
# always replaced by /dev/null.)
351
473
self.checker = subprocess.Popen(command,
354
self.checker_callback_tag = gobject.child_watch_add\
356
self.checker_callback)
475
shell=True, cwd=u"/")
476
self.checker_callback_tag = (gobject.child_watch_add
478
self.checker_callback,
480
# The checker may have completed before the gobject
481
# watch was added. Check for this.
482
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
484
gobject.source_remove(self.checker_callback_tag)
485
self.checker_callback(pid, status, command)
357
486
except OSError, error:
358
487
logger.error(u"Failed to start subprocess: %s",
360
489
# Re-run this periodically if run by gobject.timeout_add
362
492
def stop_checker(self):
363
493
"""Force the checker process, if any, to stop."""
364
494
if self.checker_callback_tag:
365
495
gobject.source_remove(self.checker_callback_tag)
366
496
self.checker_callback_tag = None
367
if getattr(self, "checker", None) is None:
497
if getattr(self, u"checker", None) is None:
369
499
logger.debug(u"Stopping checker for %(name)s", vars(self))
371
501
os.kill(self.checker.pid, signal.SIGTERM)
373
503
#if self.checker.poll() is None:
374
504
# os.kill(self.checker.pid, signal.SIGKILL)
375
505
except OSError, error:
376
506
if error.errno != errno.ESRCH: # No such process
378
508
self.checker = None
379
def still_valid(self):
380
"""Has the timeout not yet passed for this client?"""
381
now = datetime.datetime.now()
510
def dbus_service_property(dbus_interface, signature=u"v",
511
access=u"readwrite", byte_arrays=False):
512
"""Decorators for marking methods of a DBusObjectWithProperties to
513
become properties on the D-Bus.
515
The decorated method will be called with no arguments by "Get"
516
and with one argument by "Set".
518
The parameters, where they are supported, are the same as
519
dbus.service.method, except there is only "signature", since the
520
type from Get() and the type sent to Set() is the same.
522
# Encoding deeply encoded byte arrays is not supported yet by the
523
# "Set" method, so we fail early here:
524
if byte_arrays and signature != u"ay":
525
raise ValueError(u"Byte arrays not supported for non-'ay'"
526
u" signature %r" % signature)
528
func._dbus_is_property = True
529
func._dbus_interface = dbus_interface
530
func._dbus_signature = signature
531
func._dbus_access = access
532
func._dbus_name = func.__name__
533
if func._dbus_name.endswith(u"_dbus_property"):
534
func._dbus_name = func._dbus_name[:-14]
535
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
540
class DBusPropertyException(dbus.exceptions.DBusException):
541
"""A base class for D-Bus property-related exceptions
543
def __unicode__(self):
544
return unicode(str(self))
547
class DBusPropertyAccessException(DBusPropertyException):
548
"""A property's access permissions disallows an operation.
553
class DBusPropertyNotFound(DBusPropertyException):
554
"""An attempt was made to access a non-existing property.
559
class DBusObjectWithProperties(dbus.service.Object):
560
"""A D-Bus object with properties.
562
Classes inheriting from this can use the dbus_service_property
563
decorator to expose methods as D-Bus properties. It exposes the
564
standard Get(), Set(), and GetAll() methods on the D-Bus.
568
def _is_dbus_property(obj):
569
return getattr(obj, u"_dbus_is_property", False)
571
def _get_all_dbus_properties(self):
572
"""Returns a generator of (name, attribute) pairs
574
return ((prop._dbus_name, prop)
576
inspect.getmembers(self, self._is_dbus_property))
578
def _get_dbus_property(self, interface_name, property_name):
579
"""Returns a bound method if one exists which is a D-Bus
580
property with the specified name and interface.
582
for name in (property_name,
583
property_name + u"_dbus_property"):
584
prop = getattr(self, name, None)
586
or not self._is_dbus_property(prop)
587
or prop._dbus_name != property_name
588
or (interface_name and prop._dbus_interface
589
and interface_name != prop._dbus_interface)):
593
raise DBusPropertyNotFound(self.dbus_object_path + u":"
594
+ interface_name + u"."
597
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
599
def Get(self, interface_name, property_name):
600
"""Standard D-Bus property Get() method, see D-Bus standard.
602
prop = self._get_dbus_property(interface_name, property_name)
603
if prop._dbus_access == u"write":
604
raise DBusPropertyAccessException(property_name)
606
if not hasattr(value, u"variant_level"):
608
return type(value)(value, variant_level=value.variant_level+1)
610
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
611
def Set(self, interface_name, property_name, value):
612
"""Standard D-Bus property Set() method, see D-Bus standard.
614
prop = self._get_dbus_property(interface_name, property_name)
615
if prop._dbus_access == u"read":
616
raise DBusPropertyAccessException(property_name)
617
if prop._dbus_get_args_options[u"byte_arrays"]:
618
# The byte_arrays option is not supported yet on
619
# signatures other than "ay".
620
if prop._dbus_signature != u"ay":
622
value = dbus.ByteArray(''.join(unichr(byte)
626
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
627
out_signature=u"a{sv}")
628
def GetAll(self, interface_name):
629
"""Standard D-Bus property GetAll() method, see D-Bus
632
Note: Will not include properties with access="write".
635
for name, prop in self._get_all_dbus_properties():
637
and interface_name != prop._dbus_interface):
638
# Interface non-empty but did not match
640
# Ignore write-only properties
641
if prop._dbus_access == u"write":
644
if not hasattr(value, u"variant_level"):
647
all[name] = type(value)(value, variant_level=
648
value.variant_level+1)
649
return dbus.Dictionary(all, signature=u"sv")
651
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
653
path_keyword='object_path',
654
connection_keyword='connection')
655
def Introspect(self, object_path, connection):
656
"""Standard D-Bus method, overloaded to insert property tags.
658
xmlstring = dbus.service.Object.Introspect(self, object_path,
661
document = xml.dom.minidom.parseString(xmlstring)
662
def make_tag(document, name, prop):
663
e = document.createElement(u"property")
664
e.setAttribute(u"name", name)
665
e.setAttribute(u"type", prop._dbus_signature)
666
e.setAttribute(u"access", prop._dbus_access)
668
for if_tag in document.getElementsByTagName(u"interface"):
669
for tag in (make_tag(document, name, prop)
671
in self._get_all_dbus_properties()
672
if prop._dbus_interface
673
== if_tag.getAttribute(u"name")):
674
if_tag.appendChild(tag)
675
# Add the names to the return values for the
676
# "org.freedesktop.DBus.Properties" methods
677
if (if_tag.getAttribute(u"name")
678
== u"org.freedesktop.DBus.Properties"):
679
for cn in if_tag.getElementsByTagName(u"method"):
680
if cn.getAttribute(u"name") == u"Get":
681
for arg in cn.getElementsByTagName(u"arg"):
682
if (arg.getAttribute(u"direction")
684
arg.setAttribute(u"name", u"value")
685
elif cn.getAttribute(u"name") == u"GetAll":
686
for arg in cn.getElementsByTagName(u"arg"):
687
if (arg.getAttribute(u"direction")
689
arg.setAttribute(u"name", u"props")
690
xmlstring = document.toxml(u"utf-8")
692
except (AttributeError, xml.dom.DOMException,
693
xml.parsers.expat.ExpatError), error:
694
logger.error(u"Failed to override Introspection method",
699
class ClientDBus(Client, DBusObjectWithProperties):
700
"""A Client class using D-Bus
703
dbus_object_path: dbus.ObjectPath
704
bus: dbus.SystemBus()
706
# dbus.service.Object doesn't use super(), so we can't either.
708
def __init__(self, bus = None, *args, **kwargs):
709
self._approvals_pending = 0
711
Client.__init__(self, *args, **kwargs)
712
# Only now, when this client is initialized, can it show up on
714
self.dbus_object_path = (dbus.ObjectPath
716
+ self.name.replace(u".", u"_")))
717
DBusObjectWithProperties.__init__(self, self.bus,
718
self.dbus_object_path)
720
def _get_approvals_pending(self):
721
return self._approvals_pending
722
def _set_approvals_pending(self, value):
723
old_value = self._approvals_pending
724
self._approvals_pending = value
726
if (hasattr(self, "dbus_object_path")
727
and bval is not bool(old_value)):
728
dbus_bool = dbus.Boolean(bval, variant_level=1)
729
self.PropertyChanged(dbus.String(u"ApprovalPending"),
732
approvals_pending = property(_get_approvals_pending,
733
_set_approvals_pending)
734
del _get_approvals_pending, _set_approvals_pending
737
def _datetime_to_dbus(dt, variant_level=0):
738
"""Convert a UTC datetime.datetime() to a D-Bus type."""
739
return dbus.String(dt.isoformat(),
740
variant_level=variant_level)
743
oldstate = getattr(self, u"enabled", False)
744
r = Client.enable(self)
745
if oldstate != self.enabled:
747
self.PropertyChanged(dbus.String(u"Enabled"),
748
dbus.Boolean(True, variant_level=1))
749
self.PropertyChanged(
750
dbus.String(u"LastEnabled"),
751
self._datetime_to_dbus(self.last_enabled,
755
def disable(self, quiet = False):
756
oldstate = getattr(self, u"enabled", False)
757
r = Client.disable(self, quiet=quiet)
758
if not quiet and oldstate != self.enabled:
760
self.PropertyChanged(dbus.String(u"Enabled"),
761
dbus.Boolean(False, variant_level=1))
764
def __del__(self, *args, **kwargs):
766
self.remove_from_connection()
769
if hasattr(DBusObjectWithProperties, u"__del__"):
770
DBusObjectWithProperties.__del__(self, *args, **kwargs)
771
Client.__del__(self, *args, **kwargs)
773
def checker_callback(self, pid, condition, command,
775
self.checker_callback_tag = None
778
self.PropertyChanged(dbus.String(u"CheckerRunning"),
779
dbus.Boolean(False, variant_level=1))
780
if os.WIFEXITED(condition):
781
exitstatus = os.WEXITSTATUS(condition)
783
self.CheckerCompleted(dbus.Int16(exitstatus),
784
dbus.Int64(condition),
785
dbus.String(command))
788
self.CheckerCompleted(dbus.Int16(-1),
789
dbus.Int64(condition),
790
dbus.String(command))
792
return Client.checker_callback(self, pid, condition, command,
795
def checked_ok(self, *args, **kwargs):
796
r = Client.checked_ok(self, *args, **kwargs)
798
self.PropertyChanged(
799
dbus.String(u"LastCheckedOK"),
800
(self._datetime_to_dbus(self.last_checked_ok,
804
def start_checker(self, *args, **kwargs):
805
old_checker = self.checker
806
if self.checker is not None:
807
old_checker_pid = self.checker.pid
809
old_checker_pid = None
810
r = Client.start_checker(self, *args, **kwargs)
811
# Only if new checker process was started
812
if (self.checker is not None
813
and old_checker_pid != self.checker.pid):
815
self.CheckerStarted(self.current_checker_command)
816
self.PropertyChanged(
817
dbus.String(u"CheckerRunning"),
818
dbus.Boolean(True, variant_level=1))
821
def stop_checker(self, *args, **kwargs):
822
old_checker = getattr(self, u"checker", None)
823
r = Client.stop_checker(self, *args, **kwargs)
824
if (old_checker is not None
825
and getattr(self, u"checker", None) is None):
826
self.PropertyChanged(dbus.String(u"CheckerRunning"),
827
dbus.Boolean(False, variant_level=1))
830
def _reset_approved(self):
831
self._approved = None
834
def approve(self, value=True):
835
self.send_changedstate()
836
self._approved = value
837
gobject.timeout_add(self._timedelta_to_milliseconds
838
(self.approval_duration),
839
self._reset_approved)
842
## D-Bus methods, signals & properties
843
_interface = u"se.bsnet.fukt.Mandos.Client"
847
# CheckerCompleted - signal
848
@dbus.service.signal(_interface, signature=u"nxs")
849
def CheckerCompleted(self, exitcode, waitstatus, command):
853
# CheckerStarted - signal
854
@dbus.service.signal(_interface, signature=u"s")
855
def CheckerStarted(self, command):
859
# PropertyChanged - signal
860
@dbus.service.signal(_interface, signature=u"sv")
861
def PropertyChanged(self, property, value):
866
@dbus.service.signal(_interface)
869
Is sent after a successful transfer of secret from the Mandos
870
server to mandos-client
875
@dbus.service.signal(_interface, signature=u"s")
876
def Rejected(self, reason):
880
# NeedApproval - signal
881
@dbus.service.signal(_interface, signature=u"tb")
882
def NeedApproval(self, timeout, default):
889
@dbus.service.method(_interface, in_signature=u"b")
890
def Approve(self, value):
894
@dbus.service.method(_interface)
896
return self.checked_ok()
899
@dbus.service.method(_interface)
904
# StartChecker - method
905
@dbus.service.method(_interface)
906
def StartChecker(self):
911
@dbus.service.method(_interface)
916
# StopChecker - method
917
@dbus.service.method(_interface)
918
def StopChecker(self):
923
# ApprovalPending - property
924
@dbus_service_property(_interface, signature=u"b", access=u"read")
925
def ApprovalPending_dbus_property(self):
926
return dbus.Boolean(bool(self.approvals_pending))
928
# ApprovedByDefault - property
929
@dbus_service_property(_interface, signature=u"b",
931
def ApprovedByDefault_dbus_property(self, value=None):
932
if value is None: # get
933
return dbus.Boolean(self.approved_by_default)
934
self.approved_by_default = bool(value)
936
self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
937
dbus.Boolean(value, variant_level=1))
939
# ApprovalDelay - property
940
@dbus_service_property(_interface, signature=u"t",
942
def ApprovalDelay_dbus_property(self, value=None):
943
if value is None: # get
944
return dbus.UInt64(self.approval_delay_milliseconds())
945
self.approval_delay = datetime.timedelta(0, 0, 0, value)
947
self.PropertyChanged(dbus.String(u"ApprovalDelay"),
948
dbus.UInt64(value, variant_level=1))
950
# ApprovalDuration - property
951
@dbus_service_property(_interface, signature=u"t",
953
def ApprovalDuration_dbus_property(self, value=None):
954
if value is None: # get
955
return dbus.UInt64(self._timedelta_to_milliseconds(
956
self.approval_duration))
957
self.approval_duration = datetime.timedelta(0, 0, 0, value)
959
self.PropertyChanged(dbus.String(u"ApprovalDuration"),
960
dbus.UInt64(value, variant_level=1))
963
@dbus_service_property(_interface, signature=u"s", access=u"read")
964
def Name_dbus_property(self):
965
return dbus.String(self.name)
967
# Fingerprint - property
968
@dbus_service_property(_interface, signature=u"s", access=u"read")
969
def Fingerprint_dbus_property(self):
970
return dbus.String(self.fingerprint)
973
@dbus_service_property(_interface, signature=u"s",
975
def Host_dbus_property(self, value=None):
976
if value is None: # get
977
return dbus.String(self.host)
980
self.PropertyChanged(dbus.String(u"Host"),
981
dbus.String(value, variant_level=1))
984
@dbus_service_property(_interface, signature=u"s", access=u"read")
985
def Created_dbus_property(self):
986
return dbus.String(self._datetime_to_dbus(self.created))
988
# LastEnabled - property
989
@dbus_service_property(_interface, signature=u"s", access=u"read")
990
def LastEnabled_dbus_property(self):
991
if self.last_enabled is None:
992
return dbus.String(u"")
993
return dbus.String(self._datetime_to_dbus(self.last_enabled))
996
@dbus_service_property(_interface, signature=u"b",
998
def Enabled_dbus_property(self, value=None):
999
if value is None: # get
1000
return dbus.Boolean(self.enabled)
1006
# LastCheckedOK - property
1007
@dbus_service_property(_interface, signature=u"s",
1008
access=u"readwrite")
1009
def LastCheckedOK_dbus_property(self, value=None):
1010
if value is not None:
382
1013
if self.last_checked_ok is None:
383
return now < (self.created + self.timeout)
385
return now < (self.last_checked_ok + self.timeout)
388
def peer_certificate(session):
389
"Return the peer's OpenPGP certificate as a bytestring"
390
# If not an OpenPGP certificate...
391
if gnutls.library.functions.gnutls_certificate_type_get\
392
(session._c_object) \
393
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
394
# ...do the normal thing
395
return session.peer_certificate
396
list_size = ctypes.c_uint()
397
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
398
(session._c_object, ctypes.byref(list_size))
399
if list_size.value == 0:
402
return ctypes.string_at(cert.data, cert.size)
405
def fingerprint(openpgp):
406
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
407
# New GnuTLS "datum" with the OpenPGP public key
408
datum = gnutls.library.types.gnutls_datum_t\
409
(ctypes.cast(ctypes.c_char_p(openpgp),
410
ctypes.POINTER(ctypes.c_ubyte)),
411
ctypes.c_uint(len(openpgp)))
412
# New empty GnuTLS certificate
413
crt = gnutls.library.types.gnutls_openpgp_crt_t()
414
gnutls.library.functions.gnutls_openpgp_crt_init\
416
# Import the OpenPGP public key into the certificate
417
gnutls.library.functions.gnutls_openpgp_crt_import\
418
(crt, ctypes.byref(datum),
419
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
420
# Verify the self signature in the key
421
crtverify = ctypes.c_uint()
422
gnutls.library.functions.gnutls_openpgp_crt_verify_self\
423
(crt, 0, ctypes.byref(crtverify))
424
if crtverify.value != 0:
425
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
426
raise gnutls.errors.CertificateSecurityError("Verify failed")
427
# New buffer for the fingerprint
428
buf = ctypes.create_string_buffer(20)
429
buf_len = ctypes.c_size_t()
430
# Get the fingerprint from the certificate into the buffer
431
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
432
(crt, ctypes.byref(buf), ctypes.byref(buf_len))
433
# Deinit the certificate
434
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
435
# Convert the buffer to a Python bytestring
436
fpr = ctypes.string_at(buf, buf_len.value)
437
# Convert the bytestring to hexadecimal notation
438
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
442
class TCP_handler(SocketServer.BaseRequestHandler, object):
443
"""A TCP request handler class.
444
Instantiated by IPv6_TCPServer for each request to handle it.
1014
return dbus.String(u"")
1015
return dbus.String(self._datetime_to_dbus(self
1018
# Timeout - property
1019
@dbus_service_property(_interface, signature=u"t",
1020
access=u"readwrite")
1021
def Timeout_dbus_property(self, value=None):
1022
if value is None: # get
1023
return dbus.UInt64(self.timeout_milliseconds())
1024
self.timeout = datetime.timedelta(0, 0, 0, value)
1026
self.PropertyChanged(dbus.String(u"Timeout"),
1027
dbus.UInt64(value, variant_level=1))
1028
if getattr(self, u"disable_initiator_tag", None) is None:
1030
# Reschedule timeout
1031
gobject.source_remove(self.disable_initiator_tag)
1032
self.disable_initiator_tag = None
1033
time_to_die = (self.
1034
_timedelta_to_milliseconds((self
1039
if time_to_die <= 0:
1040
# The timeout has passed
1043
self.disable_initiator_tag = (gobject.timeout_add
1044
(time_to_die, self.disable))
1046
# Interval - property
1047
@dbus_service_property(_interface, signature=u"t",
1048
access=u"readwrite")
1049
def Interval_dbus_property(self, value=None):
1050
if value is None: # get
1051
return dbus.UInt64(self.interval_milliseconds())
1052
self.interval = datetime.timedelta(0, 0, 0, value)
1054
self.PropertyChanged(dbus.String(u"Interval"),
1055
dbus.UInt64(value, variant_level=1))
1056
if getattr(self, u"checker_initiator_tag", None) is None:
1058
# Reschedule checker run
1059
gobject.source_remove(self.checker_initiator_tag)
1060
self.checker_initiator_tag = (gobject.timeout_add
1061
(value, self.start_checker))
1062
self.start_checker() # Start one now, too
1064
# Checker - property
1065
@dbus_service_property(_interface, signature=u"s",
1066
access=u"readwrite")
1067
def Checker_dbus_property(self, value=None):
1068
if value is None: # get
1069
return dbus.String(self.checker_command)
1070
self.checker_command = value
1072
self.PropertyChanged(dbus.String(u"Checker"),
1073
dbus.String(self.checker_command,
1076
# CheckerRunning - property
1077
@dbus_service_property(_interface, signature=u"b",
1078
access=u"readwrite")
1079
def CheckerRunning_dbus_property(self, value=None):
1080
if value is None: # get
1081
return dbus.Boolean(self.checker is not None)
1083
self.start_checker()
1087
# ObjectPath - property
1088
@dbus_service_property(_interface, signature=u"o", access=u"read")
1089
def ObjectPath_dbus_property(self):
1090
return self.dbus_object_path # is already a dbus.ObjectPath
1093
@dbus_service_property(_interface, signature=u"ay",
1094
access=u"write", byte_arrays=True)
1095
def Secret_dbus_property(self, value):
1096
self.secret = str(value)
1101
class ProxyClient(object):
1102
def __init__(self, child_pipe, fpr, address):
1103
self._pipe = child_pipe
1104
self._pipe.send(('init', fpr, address))
1105
if not self._pipe.recv():
1108
def __getattribute__(self, name):
1109
if(name == '_pipe'):
1110
return super(ProxyClient, self).__getattribute__(name)
1111
self._pipe.send(('getattr', name))
1112
data = self._pipe.recv()
1113
if data[0] == 'data':
1115
if data[0] == 'function':
1116
def func(*args, **kwargs):
1117
self._pipe.send(('funcall', name, args, kwargs))
1118
return self._pipe.recv()[1]
1121
def __setattr__(self, name, value):
1122
if(name == '_pipe'):
1123
return super(ProxyClient, self).__setattr__(name, value)
1124
self._pipe.send(('setattr', name, value))
1127
class ClientHandler(socketserver.BaseRequestHandler, object):
1128
"""A class to handle client connections.
1130
Instantiated once for each connection to handle it.
445
1131
Note: This will run in its own forked process."""
447
1133
def handle(self):
448
logger.info(u"TCP connection from: %s",
449
unicode(self.client_address))
450
session = gnutls.connection.ClientSession\
451
(self.request, gnutls.connection.X509Credentials())
453
line = self.request.makefile().readline()
454
logger.debug(u"Protocol version: %r", line)
456
if int(line.strip().split()[0]) > 1:
458
except (ValueError, IndexError, RuntimeError), error:
459
logger.error(u"Unknown protocol version: %s", error)
462
# Note: gnutls.connection.X509Credentials is really a generic
463
# GnuTLS certificate credentials object so long as no X.509
464
# keys are added to it. Therefore, we can use it here despite
465
# using OpenPGP certificates.
467
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
468
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
470
priority = "NORMAL" # Fallback default, since this
472
if self.server.settings["priority"]:
473
priority = self.server.settings["priority"]
474
gnutls.library.functions.gnutls_priority_set_direct\
475
(session._c_object, priority, None)
479
except gnutls.errors.GNUTLSError, error:
480
logger.warning(u"Handshake failed: %s", error)
481
# Do not run session.bye() here: the session is not
482
# established. Just abandon the request.
485
fpr = fingerprint(peer_certificate(session))
486
except (TypeError, gnutls.errors.GNUTLSError), error:
487
logger.warning(u"Bad certificate: %s", error)
490
logger.debug(u"Fingerprint: %s", fpr)
492
for c in self.server.clients:
493
if c.fingerprint == fpr:
497
logger.warning(u"Client not found for fingerprint: %s",
501
# Have to check if client.still_valid(), since it is possible
502
# that the client timed out while establishing the GnuTLS
504
if not client.still_valid():
505
logger.warning(u"Client %(name)s is invalid",
510
while sent_size < len(client.secret):
511
sent = session.send(client.secret[sent_size:])
512
logger.debug(u"Sent: %d, remaining: %d",
513
sent, len(client.secret)
514
- (sent_size + sent))
519
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
520
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1134
with contextlib.closing(self.server.child_pipe) as child_pipe:
1135
logger.info(u"TCP connection from: %s",
1136
unicode(self.client_address))
1137
logger.debug(u"Pipe FD: %d",
1138
self.server.child_pipe.fileno())
1140
session = (gnutls.connection
1141
.ClientSession(self.request,
1143
.X509Credentials()))
1145
# Note: gnutls.connection.X509Credentials is really a
1146
# generic GnuTLS certificate credentials object so long as
1147
# no X.509 keys are added to it. Therefore, we can use it
1148
# here despite using OpenPGP certificates.
1150
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1151
# u"+AES-256-CBC", u"+SHA1",
1152
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
1154
# Use a fallback default, since this MUST be set.
1155
priority = self.server.gnutls_priority
1156
if priority is None:
1157
priority = u"NORMAL"
1158
(gnutls.library.functions
1159
.gnutls_priority_set_direct(session._c_object,
1162
# Start communication using the Mandos protocol
1163
# Get protocol number
1164
line = self.request.makefile().readline()
1165
logger.debug(u"Protocol version: %r", line)
1167
if int(line.strip().split()[0]) > 1:
1169
except (ValueError, IndexError, RuntimeError), error:
1170
logger.error(u"Unknown protocol version: %s", error)
1173
# Start GnuTLS connection
1176
except gnutls.errors.GNUTLSError, error:
1177
logger.warning(u"Handshake failed: %s", error)
1178
# Do not run session.bye() here: the session is not
1179
# established. Just abandon the request.
1181
logger.debug(u"Handshake succeeded")
1183
approval_required = False
1186
fpr = self.fingerprint(self.peer_certificate
1188
except (TypeError, gnutls.errors.GNUTLSError), error:
1189
logger.warning(u"Bad certificate: %s", error)
1191
logger.debug(u"Fingerprint: %s", fpr)
1194
client = ProxyClient(child_pipe, fpr,
1195
self.client_address)
1199
if client.approval_delay:
1200
delay = client.approval_delay
1201
client.approvals_pending += 1
1202
approval_required = True
1205
if not client.enabled:
1206
logger.warning(u"Client %s is disabled",
1208
if self.server.use_dbus:
1210
client.Rejected("Disabled")
1213
if client._approved or not client.approval_delay:
1214
#We are approved or approval is disabled
1216
elif client._approved is None:
1217
logger.info(u"Client %s needs approval",
1219
if self.server.use_dbus:
1221
client.NeedApproval(
1222
client.approval_delay_milliseconds(),
1223
client.approved_by_default)
1225
logger.warning(u"Client %s was not approved",
1227
if self.server.use_dbus:
1229
client.Rejected("Denied")
1232
#wait until timeout or approved
1233
#x = float(client._timedelta_to_milliseconds(delay))
1234
time = datetime.datetime.now()
1235
client.changedstate.acquire()
1236
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1237
client.changedstate.release()
1238
time2 = datetime.datetime.now()
1239
if (time2 - time) >= delay:
1240
if not client.approved_by_default:
1241
logger.warning("Client %s timed out while"
1242
" waiting for approval",
1244
if self.server.use_dbus:
1246
client.Rejected("Timed out")
1251
delay -= time2 - time
1254
while sent_size < len(client.secret):
1256
sent = session.send(client.secret[sent_size:])
1257
except (gnutls.errors.GNUTLSError), error:
1258
logger.warning("gnutls send failed")
1260
logger.debug(u"Sent: %d, remaining: %d",
1261
sent, len(client.secret)
1262
- (sent_size + sent))
1265
logger.info(u"Sending secret to %s", client.name)
1266
# bump the timeout as if seen
1268
if self.server.use_dbus:
1273
if approval_required:
1274
client.approvals_pending -= 1
1277
except (gnutls.errors.GNUTLSError), error:
1278
logger.warning("GnuTLS bye failed")
1281
def peer_certificate(session):
1282
"Return the peer's OpenPGP certificate as a bytestring"
1283
# If not an OpenPGP certificate...
1284
if (gnutls.library.functions
1285
.gnutls_certificate_type_get(session._c_object)
1286
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1287
# ...do the normal thing
1288
return session.peer_certificate
1289
list_size = ctypes.c_uint(1)
1290
cert_list = (gnutls.library.functions
1291
.gnutls_certificate_get_peers
1292
(session._c_object, ctypes.byref(list_size)))
1293
if not bool(cert_list) and list_size.value != 0:
1294
raise gnutls.errors.GNUTLSError(u"error getting peer"
1296
if list_size.value == 0:
1299
return ctypes.string_at(cert.data, cert.size)
1302
def fingerprint(openpgp):
1303
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1304
# New GnuTLS "datum" with the OpenPGP public key
1305
datum = (gnutls.library.types
1306
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1309
ctypes.c_uint(len(openpgp))))
1310
# New empty GnuTLS certificate
1311
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1312
(gnutls.library.functions
1313
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1314
# Import the OpenPGP public key into the certificate
1315
(gnutls.library.functions
1316
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1317
gnutls.library.constants
1318
.GNUTLS_OPENPGP_FMT_RAW))
1319
# Verify the self signature in the key
1320
crtverify = ctypes.c_uint()
1321
(gnutls.library.functions
1322
.gnutls_openpgp_crt_verify_self(crt, 0,
1323
ctypes.byref(crtverify)))
1324
if crtverify.value != 0:
1325
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1326
raise (gnutls.errors.CertificateSecurityError
1328
# New buffer for the fingerprint
1329
buf = ctypes.create_string_buffer(20)
1330
buf_len = ctypes.c_size_t()
1331
# Get the fingerprint from the certificate into the buffer
1332
(gnutls.library.functions
1333
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1334
ctypes.byref(buf_len)))
1335
# Deinit the certificate
1336
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1337
# Convert the buffer to a Python bytestring
1338
fpr = ctypes.string_at(buf, buf_len.value)
1339
# Convert the bytestring to hexadecimal notation
1340
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1344
class MultiprocessingMixIn(object):
1345
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1346
def sub_process_main(self, request, address):
1348
self.finish_request(request, address)
1350
self.handle_error(request, address)
1351
self.close_request(request)
1353
def process_request(self, request, address):
1354
"""Start a new process to process the request."""
1355
multiprocessing.Process(target = self.sub_process_main,
1356
args = (request, address)).start()
1358
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1359
""" adds a pipe to the MixIn """
1360
def process_request(self, request, client_address):
1361
"""Overrides and wraps the original process_request().
1363
This function creates a new pipe in self.pipe
1365
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1367
super(MultiprocessingMixInWithPipe,
1368
self).process_request(request, client_address)
1369
self.child_pipe.close()
1370
self.add_pipe(parent_pipe)
1372
def add_pipe(self, parent_pipe):
1373
"""Dummy function; override as necessary"""
1376
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1377
socketserver.TCPServer, object):
1378
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
522
settings: Server settings
523
clients: Set() of Client objects
524
1381
enabled: Boolean; whether this server is activated yet
1382
interface: None or a network interface name (string)
1383
use_ipv6: Boolean; to use IPv6 or not
526
address_family = socket.AF_INET6
527
def __init__(self, *args, **kwargs):
528
if "settings" in kwargs:
529
self.settings = kwargs["settings"]
530
del kwargs["settings"]
531
if "clients" in kwargs:
532
self.clients = kwargs["clients"]
533
del kwargs["clients"]
535
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
1385
def __init__(self, server_address, RequestHandlerClass,
1386
interface=None, use_ipv6=True):
1387
self.interface = interface
1389
self.address_family = socket.AF_INET6
1390
socketserver.TCPServer.__init__(self, server_address,
1391
RequestHandlerClass)
536
1392
def server_bind(self):
537
1393
"""This overrides the normal server_bind() function
538
1394
to bind to an interface if one was specified, and also NOT to
539
1395
bind to an address or port if they were not specified."""
540
if self.settings["interface"]:
541
# 25 is from /usr/include/asm-i486/socket.h
542
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
544
self.socket.setsockopt(socket.SOL_SOCKET,
546
self.settings["interface"])
547
except socket.error, error:
548
if error[0] == errno.EPERM:
549
logger.error(u"No permission to"
550
u" bind to interface %s",
551
self.settings["interface"])
1396
if self.interface is not None:
1397
if SO_BINDTODEVICE is None:
1398
logger.error(u"SO_BINDTODEVICE does not exist;"
1399
u" cannot bind to interface %s",
1403
self.socket.setsockopt(socket.SOL_SOCKET,
1407
except socket.error, error:
1408
if error[0] == errno.EPERM:
1409
logger.error(u"No permission to"
1410
u" bind to interface %s",
1412
elif error[0] == errno.ENOPROTOOPT:
1413
logger.error(u"SO_BINDTODEVICE not available;"
1414
u" cannot bind to interface %s",
554
1418
# Only bind(2) the socket if we really need to.
555
1419
if self.server_address[0] or self.server_address[1]:
556
1420
if not self.server_address[0]:
558
self.server_address = (in6addr_any,
1421
if self.address_family == socket.AF_INET6:
1422
any_address = u"::" # in6addr_any
1424
any_address = socket.INADDR_ANY
1425
self.server_address = (any_address,
559
1426
self.server_address[1])
560
1427
elif not self.server_address[1]:
561
1428
self.server_address = (self.server_address[0],
563
# if self.settings["interface"]:
1430
# if self.interface:
564
1431
# self.server_address = (self.server_address[0],
567
1434
# if_nametoindex
570
return super(IPv6_TCPServer, self).server_bind()
1436
return socketserver.TCPServer.server_bind(self)
1439
class MandosServer(IPv6_TCPServer):
1443
clients: set of Client objects
1444
gnutls_priority GnuTLS priority string
1445
use_dbus: Boolean; to emit D-Bus signals or not
1447
Assumes a gobject.MainLoop event loop.
1449
def __init__(self, server_address, RequestHandlerClass,
1450
interface=None, use_ipv6=True, clients=None,
1451
gnutls_priority=None, use_dbus=True):
1452
self.enabled = False
1453
self.clients = clients
1454
if self.clients is None:
1455
self.clients = set()
1456
self.use_dbus = use_dbus
1457
self.gnutls_priority = gnutls_priority
1458
IPv6_TCPServer.__init__(self, server_address,
1459
RequestHandlerClass,
1460
interface = interface,
1461
use_ipv6 = use_ipv6)
571
1462
def server_activate(self):
572
1463
if self.enabled:
573
return super(IPv6_TCPServer, self).server_activate()
1464
return socketserver.TCPServer.server_activate(self)
574
1465
def enable(self):
575
1466
self.enabled = True
1467
def add_pipe(self, parent_pipe):
1468
# Call "handle_ipc" for both data and EOF events
1469
gobject.io_add_watch(parent_pipe.fileno(),
1470
gobject.IO_IN | gobject.IO_HUP,
1471
functools.partial(self.handle_ipc,
1472
parent_pipe = parent_pipe))
1474
def handle_ipc(self, source, condition, parent_pipe=None,
1475
client_object=None):
1477
gobject.IO_IN: u"IN", # There is data to read.
1478
gobject.IO_OUT: u"OUT", # Data can be written (without
1480
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1481
gobject.IO_ERR: u"ERR", # Error condition.
1482
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1483
# broken, usually for pipes and
1486
conditions_string = ' | '.join(name
1488
condition_names.iteritems()
1489
if cond & condition)
1490
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1493
# error or the other end of multiprocessing.Pipe has closed
1494
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1497
# Read a request from the child
1498
request = parent_pipe.recv()
1499
logger.debug(u"IPC request: %s", repr(request))
1500
command = request[0]
1502
if command == 'init':
1504
address = request[2]
1506
for c in self.clients:
1507
if c.fingerprint == fpr:
1511
logger.warning(u"Client not found for fingerprint: %s, ad"
1512
u"dress: %s", fpr, address)
1515
mandos_dbus_service.ClientNotFound(fpr, address)
1516
parent_pipe.send(False)
1519
gobject.io_add_watch(parent_pipe.fileno(),
1520
gobject.IO_IN | gobject.IO_HUP,
1521
functools.partial(self.handle_ipc,
1522
parent_pipe = parent_pipe,
1523
client_object = client))
1524
parent_pipe.send(True)
1525
# remove the old hook in favor of the new above hook on same fileno
1527
if command == 'funcall':
1528
funcname = request[1]
1532
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1534
if command == 'getattr':
1535
attrname = request[1]
1536
if callable(client_object.__getattribute__(attrname)):
1537
parent_pipe.send(('function',))
1539
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1541
if command == 'setattr':
1542
attrname = request[1]
1544
setattr(client_object, attrname, value)
578
1549
def string_to_delta(interval):
579
1550
"""Parse a string and return a datetime.timedelta
581
>>> string_to_delta('7d')
1552
>>> string_to_delta(u'7d')
582
1553
datetime.timedelta(7)
583
>>> string_to_delta('60s')
1554
>>> string_to_delta(u'60s')
584
1555
datetime.timedelta(0, 60)
585
>>> string_to_delta('60m')
1556
>>> string_to_delta(u'60m')
586
1557
datetime.timedelta(0, 3600)
587
>>> string_to_delta('24h')
1558
>>> string_to_delta(u'24h')
588
1559
datetime.timedelta(1)
589
1560
>>> string_to_delta(u'1w')
590
1561
datetime.timedelta(7)
591
>>> string_to_delta('5m 30s')
1562
>>> string_to_delta(u'5m 30s')
592
1563
datetime.timedelta(0, 330)
594
1565
timevalue = datetime.timedelta(0)
714
1676
# Default values for config file for server-global settings
715
server_defaults = { "interface": "",
720
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
721
"servicename": "Mandos",
1677
server_defaults = { u"interface": u"",
1682
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1683
u"servicename": u"Mandos",
1684
u"use_dbus": u"True",
1685
u"use_ipv6": u"True",
724
1689
# Parse config file for server-global settings
725
server_config = ConfigParser.SafeConfigParser(server_defaults)
1690
server_config = configparser.SafeConfigParser(server_defaults)
726
1691
del server_defaults
727
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1692
server_config.read(os.path.join(options.configdir,
728
1694
# Convert the SafeConfigParser object to a dict
729
1695
server_settings = server_config.defaults()
730
# Use getboolean on the boolean config option
731
server_settings["debug"] = server_config.getboolean\
1696
# Use the appropriate methods on the non-string config options
1697
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1698
server_settings[option] = server_config.getboolean(u"DEFAULT",
1700
if server_settings["port"]:
1701
server_settings["port"] = server_config.getint(u"DEFAULT",
733
1703
del server_config
735
1705
# Override the settings from the config file with command line
736
1706
# options, if set.
737
for option in ("interface", "address", "port", "debug",
738
"priority", "servicename", "configdir"):
1707
for option in (u"interface", u"address", u"port", u"debug",
1708
u"priority", u"servicename", u"configdir",
1709
u"use_dbus", u"use_ipv6", u"debuglevel"):
739
1710
value = getattr(options, option)
740
1711
if value is not None:
741
1712
server_settings[option] = value
1714
# Force all strings to be unicode
1715
for option in server_settings.keys():
1716
if type(server_settings[option]) is str:
1717
server_settings[option] = unicode(server_settings[option])
743
1718
# Now we have our good server settings in "server_settings"
745
debug = server_settings["debug"]
748
syslogger.setLevel(logging.WARNING)
749
console.setLevel(logging.WARNING)
751
if server_settings["servicename"] != "Mandos":
752
syslogger.setFormatter(logging.Formatter\
753
('Mandos (%s): %%(levelname)s:'
755
% server_settings["servicename"]))
1720
##################################################################
1723
debug = server_settings[u"debug"]
1724
debuglevel = server_settings[u"debuglevel"]
1725
use_dbus = server_settings[u"use_dbus"]
1726
use_ipv6 = server_settings[u"use_ipv6"]
1728
if server_settings[u"servicename"] != u"Mandos":
1729
syslogger.setFormatter(logging.Formatter
1730
(u'Mandos (%s) [%%(process)d]:'
1731
u' %%(levelname)s: %%(message)s'
1732
% server_settings[u"servicename"]))
757
1734
# Parse config file with clients
758
client_defaults = { "timeout": "1h",
760
"checker": "fping -q -- %(host)s",
1735
client_defaults = { u"timeout": u"1h",
1737
u"checker": u"fping -q -- %%(host)s",
1739
u"approval_delay": u"0s",
1740
u"approval_duration": u"1s",
763
client_config = ConfigParser.SafeConfigParser(client_defaults)
764
client_config.read(os.path.join(server_settings["configdir"],
768
tcp_server = IPv6_TCPServer((server_settings["address"],
769
server_settings["port"]),
771
settings=server_settings,
773
pidfilename = "/var/run/mandos.pid"
775
pidfile = open(pidfilename, "w")
776
except IOError, error:
777
logger.error("Could not open file %r", pidfilename)
782
uid = pwd.getpwnam("mandos").pw_uid
785
uid = pwd.getpwnam("nobody").pw_uid
789
gid = pwd.getpwnam("mandos").pw_gid
792
gid = pwd.getpwnam("nogroup").pw_gid
1742
client_config = configparser.SafeConfigParser(client_defaults)
1743
client_config.read(os.path.join(server_settings[u"configdir"],
1746
global mandos_dbus_service
1747
mandos_dbus_service = None
1749
tcp_server = MandosServer((server_settings[u"address"],
1750
server_settings[u"port"]),
1752
interface=(server_settings[u"interface"]
1756
server_settings[u"priority"],
1758
pidfilename = u"/var/run/mandos.pid"
1760
pidfile = open(pidfilename, u"w")
1762
logger.error(u"Could not open file %r", pidfilename)
1765
uid = pwd.getpwnam(u"_mandos").pw_uid
1766
gid = pwd.getpwnam(u"_mandos").pw_gid
1769
uid = pwd.getpwnam(u"mandos").pw_uid
1770
gid = pwd.getpwnam(u"mandos").pw_gid
1773
uid = pwd.getpwnam(u"nobody").pw_uid
1774
gid = pwd.getpwnam(u"nobody").pw_gid
798
1781
except OSError, error:
799
1782
if error[0] != errno.EPERM:
803
service = AvahiService(name = server_settings["servicename"],
804
servicetype = "_mandos._tcp", )
805
if server_settings["interface"]:
806
service.interface = if_nametoindex\
807
(server_settings["interface"])
1785
if not debug and not debuglevel:
1786
syslogger.setLevel(logging.WARNING)
1787
console.setLevel(logging.WARNING)
1789
level = getattr(logging, debuglevel.upper())
1790
syslogger.setLevel(level)
1791
console.setLevel(level)
1794
# Enable all possible GnuTLS debugging
1796
# "Use a log level over 10 to enable all debugging options."
1798
gnutls.library.functions.gnutls_global_set_log_level(11)
1800
@gnutls.library.types.gnutls_log_func
1801
def debug_gnutls(level, string):
1802
logger.debug(u"GnuTLS: %s", string[:-1])
1804
(gnutls.library.functions
1805
.gnutls_global_set_log_function(debug_gnutls))
1807
# Redirect stdin so all checkers get /dev/null
1808
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1809
os.dup2(null, sys.stdin.fileno())
1813
# No console logging
1814
logger.removeHandler(console)
809
1817
global main_loop
812
1818
# From the Avahi example code
813
1819
DBusGMainLoop(set_as_default=True )
814
1820
main_loop = gobject.MainLoop()
815
1821
bus = dbus.SystemBus()
816
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
817
avahi.DBUS_PATH_SERVER),
818
avahi.DBUS_INTERFACE_SERVER)
819
1822
# End of Avahi example code
821
def remove_from_clients(client):
822
clients.remove(client)
824
logger.critical(u"No clients left, exiting")
827
clients.update(Set(Client(name = section,
828
stop_hook = remove_from_clients,
830
= dict(client_config.items(section)))
831
for section in client_config.sections()))
833
logger.critical(u"No clients defined")
837
# Redirect stdin so all checkers get /dev/null
838
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
839
os.dup2(null, sys.stdin.fileno())
844
logger.removeHandler(console)
1825
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1826
bus, do_not_queue=True)
1827
except dbus.exceptions.NameExistsException, e:
1828
logger.error(unicode(e) + u", disabling D-Bus")
1830
server_settings[u"use_dbus"] = False
1831
tcp_server.use_dbus = False
1832
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1833
service = AvahiService(name = server_settings[u"servicename"],
1834
servicetype = u"_mandos._tcp",
1835
protocol = protocol, bus = bus)
1836
if server_settings["interface"]:
1837
service.interface = (if_nametoindex
1838
(str(server_settings[u"interface"])))
845
1841
# Close all input and output, do double fork, etc.
1844
global multiprocessing_manager
1845
multiprocessing_manager = multiprocessing.Manager()
1847
client_class = Client
1849
client_class = functools.partial(ClientDBus, bus = bus)
1850
def client_config_items(config, section):
1851
special_settings = {
1852
"approved_by_default":
1853
lambda: config.getboolean(section,
1854
"approved_by_default"),
1856
for name, value in config.items(section):
1858
yield (name, special_settings[name]())
1862
tcp_server.clients.update(set(
1863
client_class(name = section,
1864
config= dict(client_config_items(
1865
client_config, section)))
1866
for section in client_config.sections()))
1867
if not tcp_server.clients:
1868
logger.warning(u"No clients defined")
850
pidfile.write(str(pid) + "\n")
1873
pidfile.write(str(pid) + "\n")
854
1876
logger.error(u"Could not write to file %r with PID %d",