137
148
self.rename_count = 0
138
149
self.max_renames = max_renames
139
150
self.protocol = protocol
151
self.group = None # our entry group
154
self.entry_group_state_changed_match = None
140
155
def rename(self):
141
156
"""Derived from the Avahi example code"""
142
157
if self.rename_count >= self.max_renames:
143
logger.critical(u"No suitable Zeroconf service name found"
144
u" after %i retries, exiting.",
158
logger.critical("No suitable Zeroconf service name found"
159
" after %i retries, exiting.",
145
160
self.rename_count)
146
raise AvahiServiceError(u"Too many renames")
147
self.name = server.GetAlternativeServiceName(self.name)
148
logger.info(u"Changing Zeroconf service name to %r ...",
161
raise AvahiServiceError("Too many renames")
162
self.name = unicode(self.server.GetAlternativeServiceName(self.name))
163
logger.info("Changing Zeroconf service name to %r ...",
150
165
syslogger.setFormatter(logging.Formatter
151
166
('Mandos (%s) [%%(process)d]:'
152
167
' %%(levelname)s: %%(message)s'
172
except dbus.exceptions.DBusException as error:
173
logger.critical("DBusException: %s", error)
156
176
self.rename_count += 1
157
177
def remove(self):
158
178
"""Derived from the Avahi example code"""
159
if group is not None:
179
if self.entry_group_state_changed_match is not None:
180
self.entry_group_state_changed_match.remove()
181
self.entry_group_state_changed_match = None
182
if self.group is not None:
162
185
"""Derived from the Avahi example code"""
165
group = dbus.Interface(bus.get_object
167
server.EntryGroupNew()),
168
avahi.DBUS_INTERFACE_ENTRY_GROUP)
169
group.connect_to_signal('StateChanged',
170
entry_group_state_changed)
171
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
172
service.name, service.type)
174
self.interface, # interface
175
self.protocol, # protocol
176
dbus.UInt32(0), # flags
177
self.name, self.type,
178
self.domain, self.host,
179
dbus.UInt16(self.port),
180
avahi.string_array_to_txt_array(self.TXT))
183
# From the Avahi example code:
184
group = None # our entry group
185
# End of Avahi example code
188
def _datetime_to_dbus(dt, variant_level=0):
189
"""Convert a UTC datetime.datetime() to a D-Bus type."""
190
return dbus.String(dt.isoformat(), variant_level=variant_level)
187
if self.group is None:
188
self.group = dbus.Interface(
189
self.bus.get_object(avahi.DBUS_NAME,
190
self.server.EntryGroupNew()),
191
avahi.DBUS_INTERFACE_ENTRY_GROUP)
192
self.entry_group_state_changed_match = (
193
self.group.connect_to_signal(
194
'StateChanged', self .entry_group_state_changed))
195
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
196
self.name, self.type)
197
self.group.AddService(
200
dbus.UInt32(0), # flags
201
self.name, self.type,
202
self.domain, self.host,
203
dbus.UInt16(self.port),
204
avahi.string_array_to_txt_array(self.TXT))
206
def entry_group_state_changed(self, state, error):
207
"""Derived from the Avahi example code"""
208
logger.debug("Avahi entry group state change: %i", state)
210
if state == avahi.ENTRY_GROUP_ESTABLISHED:
211
logger.debug("Zeroconf service established.")
212
elif state == avahi.ENTRY_GROUP_COLLISION:
213
logger.info("Zeroconf service name collision.")
215
elif state == avahi.ENTRY_GROUP_FAILURE:
216
logger.critical("Avahi: Error in group state changed %s",
218
raise AvahiGroupError("State changed: %s"
221
"""Derived from the Avahi example code"""
222
if self.group is not None:
225
except (dbus.exceptions.UnknownMethodException,
226
dbus.exceptions.DBusException) as e:
230
def server_state_changed(self, state, error=None):
231
"""Derived from the Avahi example code"""
232
logger.debug("Avahi server state change: %i", state)
233
bad_states = { avahi.SERVER_INVALID:
234
"Zeroconf server invalid",
235
avahi.SERVER_REGISTERING: None,
236
avahi.SERVER_COLLISION:
237
"Zeroconf server name collision",
238
avahi.SERVER_FAILURE:
239
"Zeroconf server failure" }
240
if state in bad_states:
241
if bad_states[state] is not None:
243
logger.error(bad_states[state])
245
logger.error(bad_states[state] + ": %r", error)
247
elif state == avahi.SERVER_RUNNING:
251
logger.debug("Unknown state: %r", state)
253
logger.debug("Unknown state: %r: %r", state, error)
255
"""Derived from the Avahi example code"""
256
if self.server is None:
257
self.server = dbus.Interface(
258
self.bus.get_object(avahi.DBUS_NAME,
259
avahi.DBUS_PATH_SERVER,
260
follow_name_owner_changes=True),
261
avahi.DBUS_INTERFACE_SERVER)
262
self.server.connect_to_signal("StateChanged",
263
self.server_state_changed)
264
self.server_state_changed(self.server.GetState())
267
def _timedelta_to_milliseconds(td):
268
"Convert a datetime.timedelta() to milliseconds"
269
return ((td.days * 24 * 60 * 60 * 1000)
270
+ (td.seconds * 1000)
271
+ (td.microseconds // 1000))
193
273
class Client(object):
194
274
"""A representation of a client host served by this server.
197
name: string; from the config file, used in log messages and
199
fingerprint: string (40 or 32 hexadecimal digits); used to
200
uniquely identify the client
201
secret: bytestring; sent verbatim (over TLS) to client
202
host: string; available for use by the checker command
203
created: datetime.datetime(); (UTC) object creation
204
last_enabled: datetime.datetime(); (UTC)
206
last_checked_ok: datetime.datetime(); (UTC) or None
207
timeout: datetime.timedelta(); How long from last_checked_ok
208
until this client is invalid
209
interval: datetime.timedelta(); How often to start a new checker
210
disable_hook: If set, called by disable() as disable_hook(self)
277
_approved: bool(); 'None' if not yet approved/disapproved
278
approval_delay: datetime.timedelta(); Time to wait for approval
279
approval_duration: datetime.timedelta(); Duration of one approval
211
280
checker: subprocess.Popen(); a running checker process used
212
281
to see if the client lives.
213
282
'None' if no process is running.
214
checker_initiator_tag: a gobject event source tag, or None
215
disable_initiator_tag: - '' -
216
checker_callback_tag: - '' -
217
checker_command: string; External command which is run to check if
218
client lives. %() expansions are done at
283
checker_callback_tag: a gobject event source tag, or None
284
checker_command: string; External command which is run to check
285
if client lives. %() expansions are done at
219
286
runtime with vars(self) as dict, so that for
220
287
instance %(name)s can be used in the command.
288
checker_initiator_tag: a gobject event source tag, or None
289
created: datetime.datetime(); (UTC) object creation
221
290
current_checker_command: string; current running checker_command
291
disable_hook: If set, called by disable() as disable_hook(self)
292
disable_initiator_tag: a gobject event source tag, or None
294
fingerprint: string (40 or 32 hexadecimal digits); used to
295
uniquely identify the client
296
host: string; available for use by the checker command
297
interval: datetime.timedelta(); How often to start a new checker
298
last_approval_request: datetime.datetime(); (UTC) or None
299
last_checked_ok: datetime.datetime(); (UTC) or None
300
last_enabled: datetime.datetime(); (UTC)
301
name: string; from the config file, used in log messages and
303
secret: bytestring; sent verbatim (over TLS) to client
304
timeout: datetime.timedelta(); How long from last_checked_ok
305
until this client is disabled
306
extended_timeout: extra long timeout when password has been sent
307
runtime_expansions: Allowed attributes for runtime expansion.
308
expires: datetime.datetime(); time (UTC) when a client will be
312
runtime_expansions = ("approval_delay", "approval_duration",
313
"created", "enabled", "fingerprint",
314
"host", "interval", "last_checked_ok",
315
"last_enabled", "name", "timeout")
223
317
def timeout_milliseconds(self):
224
318
"Return the 'timeout' attribute in milliseconds"
225
return ((self.timeout.days * 24 * 60 * 60 * 1000)
226
+ (self.timeout.seconds * 1000)
227
+ (self.timeout.microseconds // 1000))
319
return _timedelta_to_milliseconds(self.timeout)
321
def extended_timeout_milliseconds(self):
322
"Return the 'extended_timeout' attribute in milliseconds"
323
return _timedelta_to_milliseconds(self.extended_timeout)
229
325
def interval_milliseconds(self):
230
326
"Return the 'interval' attribute in milliseconds"
231
return ((self.interval.days * 24 * 60 * 60 * 1000)
232
+ (self.interval.seconds * 1000)
233
+ (self.interval.microseconds // 1000))
327
return _timedelta_to_milliseconds(self.interval)
329
def approval_delay_milliseconds(self):
330
return _timedelta_to_milliseconds(self.approval_delay)
235
332
def __init__(self, name = None, disable_hook=None, config=None):
236
333
"""Note: the 'checker' key in 'config' sets the
240
337
if config is None:
242
logger.debug(u"Creating client %r", self.name)
339
logger.debug("Creating client %r", self.name)
243
340
# Uppercase and remove spaces from fingerprint for later
244
341
# comparison purposes with return value from the fingerprint()
246
343
self.fingerprint = (config["fingerprint"].upper()
248
logger.debug(u" Fingerprint: %s", self.fingerprint)
345
logger.debug(" Fingerprint: %s", self.fingerprint)
249
346
if "secret" in config:
250
self.secret = config["secret"].decode(u"base64")
347
self.secret = config["secret"].decode("base64")
251
348
elif "secfile" in config:
252
with closing(open(os.path.expanduser
254
(config["secfile"])))) as secfile:
349
with open(os.path.expanduser(os.path.expandvars
350
(config["secfile"])),
255
352
self.secret = secfile.read()
257
raise TypeError(u"No secret or secfile for client %s"
354
raise TypeError("No secret or secfile for client %s"
259
356
self.host = config.get("host", "")
260
357
self.created = datetime.datetime.utcnow()
261
358
self.enabled = False
359
self.last_approval_request = None
262
360
self.last_enabled = None
263
361
self.last_checked_ok = None
264
362
self.timeout = string_to_delta(config["timeout"])
363
self.extended_timeout = string_to_delta(config["extended_timeout"])
265
364
self.interval = string_to_delta(config["interval"])
266
365
self.disable_hook = disable_hook
267
366
self.checker = None
268
367
self.checker_initiator_tag = None
269
368
self.disable_initiator_tag = None
270
370
self.checker_callback_tag = None
271
371
self.checker_command = config["checker"]
272
372
self.current_checker_command = None
273
373
self.last_connect = None
374
self._approved = None
375
self.approved_by_default = config.get("approved_by_default",
377
self.approvals_pending = 0
378
self.approval_delay = string_to_delta(
379
config["approval_delay"])
380
self.approval_duration = string_to_delta(
381
config["approval_duration"])
382
self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
384
def send_changedstate(self):
385
self.changedstate.acquire()
386
self.changedstate.notify_all()
387
self.changedstate.release()
275
389
def enable(self):
276
390
"""Start this client's checker and timeout hooks"""
277
self.last_enabled = datetime.datetime.utcnow()
391
if getattr(self, "enabled", False):
394
self.send_changedstate()
278
395
# Schedule a new checker to be started an 'interval' from now,
279
396
# and every interval from then on.
280
397
self.checker_initiator_tag = (gobject.timeout_add
281
398
(self.interval_milliseconds(),
282
399
self.start_checker))
283
# Also start a new checker *right now*.
285
400
# Schedule a disable() when 'timeout' has passed
401
self.expires = datetime.datetime.utcnow() + self.timeout
286
402
self.disable_initiator_tag = (gobject.timeout_add
287
403
(self.timeout_milliseconds(),
289
405
self.enabled = True
406
self.last_enabled = datetime.datetime.utcnow()
407
# Also start a new checker *right now*.
410
def disable(self, quiet=True):
292
411
"""Disable this client."""
293
412
if not getattr(self, "enabled", False):
295
logger.info(u"Disabling client %s", self.name)
415
self.send_changedstate()
417
logger.info("Disabling client %s", self.name)
296
418
if getattr(self, "disable_initiator_tag", False):
297
419
gobject.source_remove(self.disable_initiator_tag)
298
420
self.disable_initiator_tag = None
299
422
if getattr(self, "checker_initiator_tag", False):
300
423
gobject.source_remove(self.checker_initiator_tag)
301
424
self.checker_initiator_tag = None
411
550
self.checker_callback_tag = None
412
551
if getattr(self, "checker", None) is None:
414
logger.debug(u"Stopping checker for %(name)s", vars(self))
553
logger.debug("Stopping checker for %(name)s", vars(self))
416
555
os.kill(self.checker.pid, signal.SIGTERM)
418
557
#if self.checker.poll() is None:
419
558
# os.kill(self.checker.pid, signal.SIGKILL)
420
except OSError, error:
559
except OSError as error:
421
560
if error.errno != errno.ESRCH: # No such process
423
562
self.checker = None
425
def still_valid(self):
426
"""Has the timeout not yet passed for this client?"""
427
if not getattr(self, "enabled", False):
429
now = datetime.datetime.utcnow()
430
if self.last_checked_ok is None:
431
return now < (self.created + self.timeout)
433
return now < (self.last_checked_ok + self.timeout)
436
class ClientDBus(Client, dbus.service.Object):
565
def dbus_service_property(dbus_interface, signature="v",
566
access="readwrite", byte_arrays=False):
567
"""Decorators for marking methods of a DBusObjectWithProperties to
568
become properties on the D-Bus.
570
The decorated method will be called with no arguments by "Get"
571
and with one argument by "Set".
573
The parameters, where they are supported, are the same as
574
dbus.service.method, except there is only "signature", since the
575
type from Get() and the type sent to Set() is the same.
577
# Encoding deeply encoded byte arrays is not supported yet by the
578
# "Set" method, so we fail early here:
579
if byte_arrays and signature != "ay":
580
raise ValueError("Byte arrays not supported for non-'ay'"
581
" signature %r" % signature)
583
func._dbus_is_property = True
584
func._dbus_interface = dbus_interface
585
func._dbus_signature = signature
586
func._dbus_access = access
587
func._dbus_name = func.__name__
588
if func._dbus_name.endswith("_dbus_property"):
589
func._dbus_name = func._dbus_name[:-14]
590
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
595
class DBusPropertyException(dbus.exceptions.DBusException):
596
"""A base class for D-Bus property-related exceptions
598
def __unicode__(self):
599
return unicode(str(self))
602
class DBusPropertyAccessException(DBusPropertyException):
603
"""A property's access permissions disallows an operation.
608
class DBusPropertyNotFound(DBusPropertyException):
609
"""An attempt was made to access a non-existing property.
614
class DBusObjectWithProperties(dbus.service.Object):
615
"""A D-Bus object with properties.
617
Classes inheriting from this can use the dbus_service_property
618
decorator to expose methods as D-Bus properties. It exposes the
619
standard Get(), Set(), and GetAll() methods on the D-Bus.
623
def _is_dbus_property(obj):
624
return getattr(obj, "_dbus_is_property", False)
626
def _get_all_dbus_properties(self):
627
"""Returns a generator of (name, attribute) pairs
629
return ((prop._dbus_name, prop)
631
inspect.getmembers(self, self._is_dbus_property))
633
def _get_dbus_property(self, interface_name, property_name):
634
"""Returns a bound method if one exists which is a D-Bus
635
property with the specified name and interface.
637
for name in (property_name,
638
property_name + "_dbus_property"):
639
prop = getattr(self, name, None)
641
or not self._is_dbus_property(prop)
642
or prop._dbus_name != property_name
643
or (interface_name and prop._dbus_interface
644
and interface_name != prop._dbus_interface)):
648
raise DBusPropertyNotFound(self.dbus_object_path + ":"
649
+ interface_name + "."
652
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
654
def Get(self, interface_name, property_name):
655
"""Standard D-Bus property Get() method, see D-Bus standard.
657
prop = self._get_dbus_property(interface_name, property_name)
658
if prop._dbus_access == "write":
659
raise DBusPropertyAccessException(property_name)
661
if not hasattr(value, "variant_level"):
663
return type(value)(value, variant_level=value.variant_level+1)
665
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
666
def Set(self, interface_name, property_name, value):
667
"""Standard D-Bus property Set() method, see D-Bus standard.
669
prop = self._get_dbus_property(interface_name, property_name)
670
if prop._dbus_access == "read":
671
raise DBusPropertyAccessException(property_name)
672
if prop._dbus_get_args_options["byte_arrays"]:
673
# The byte_arrays option is not supported yet on
674
# signatures other than "ay".
675
if prop._dbus_signature != "ay":
677
value = dbus.ByteArray(''.join(unichr(byte)
681
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
682
out_signature="a{sv}")
683
def GetAll(self, interface_name):
684
"""Standard D-Bus property GetAll() method, see D-Bus
687
Note: Will not include properties with access="write".
690
for name, prop in self._get_all_dbus_properties():
692
and interface_name != prop._dbus_interface):
693
# Interface non-empty but did not match
695
# Ignore write-only properties
696
if prop._dbus_access == "write":
699
if not hasattr(value, "variant_level"):
702
all[name] = type(value)(value, variant_level=
703
value.variant_level+1)
704
return dbus.Dictionary(all, signature="sv")
706
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
708
path_keyword='object_path',
709
connection_keyword='connection')
710
def Introspect(self, object_path, connection):
711
"""Standard D-Bus method, overloaded to insert property tags.
713
xmlstring = dbus.service.Object.Introspect(self, object_path,
716
document = xml.dom.minidom.parseString(xmlstring)
717
def make_tag(document, name, prop):
718
e = document.createElement("property")
719
e.setAttribute("name", name)
720
e.setAttribute("type", prop._dbus_signature)
721
e.setAttribute("access", prop._dbus_access)
723
for if_tag in document.getElementsByTagName("interface"):
724
for tag in (make_tag(document, name, prop)
726
in self._get_all_dbus_properties()
727
if prop._dbus_interface
728
== if_tag.getAttribute("name")):
729
if_tag.appendChild(tag)
730
# Add the names to the return values for the
731
# "org.freedesktop.DBus.Properties" methods
732
if (if_tag.getAttribute("name")
733
== "org.freedesktop.DBus.Properties"):
734
for cn in if_tag.getElementsByTagName("method"):
735
if cn.getAttribute("name") == "Get":
736
for arg in cn.getElementsByTagName("arg"):
737
if (arg.getAttribute("direction")
739
arg.setAttribute("name", "value")
740
elif cn.getAttribute("name") == "GetAll":
741
for arg in cn.getElementsByTagName("arg"):
742
if (arg.getAttribute("direction")
744
arg.setAttribute("name", "props")
745
xmlstring = document.toxml("utf-8")
747
except (AttributeError, xml.dom.DOMException,
748
xml.parsers.expat.ExpatError) as error:
749
logger.error("Failed to override Introspection method",
754
def datetime_to_dbus (dt, variant_level=0):
755
"""Convert a UTC datetime.datetime() to a D-Bus type."""
757
return dbus.String("", variant_level = variant_level)
758
return dbus.String(dt.isoformat(),
759
variant_level=variant_level)
762
class ClientDBus(Client, DBusObjectWithProperties):
437
763
"""A Client class using D-Bus
440
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
766
dbus_object_path: dbus.ObjectPath
767
bus: dbus.SystemBus()
770
runtime_expansions = (Client.runtime_expansions
771
+ ("dbus_object_path",))
442
773
# dbus.service.Object doesn't use super(), so we can't either.
444
def __init__(self, *args, **kwargs):
775
def __init__(self, bus = None, *args, **kwargs):
776
self._approvals_pending = 0
445
778
Client.__init__(self, *args, **kwargs)
446
779
# Only now, when this client is initialized, can it show up on
781
client_object_name = unicode(self.name).translate(
448
784
self.dbus_object_path = (dbus.ObjectPath
450
+ self.name.replace(".", "_")))
451
dbus.service.Object.__init__(self, bus,
452
self.dbus_object_path)
454
oldstate = getattr(self, "enabled", False)
455
r = Client.enable(self)
456
if oldstate != self.enabled:
458
self.PropertyChanged(dbus.String(u"enabled"),
459
dbus.Boolean(True, variant_level=1))
460
self.PropertyChanged(dbus.String(u"last_enabled"),
461
(_datetime_to_dbus(self.last_enabled,
465
def disable(self, signal = True):
466
oldstate = getattr(self, "enabled", False)
467
r = Client.disable(self)
468
if signal and oldstate != self.enabled:
470
self.PropertyChanged(dbus.String(u"enabled"),
471
dbus.Boolean(False, variant_level=1))
785
("/clients/" + client_object_name))
786
DBusObjectWithProperties.__init__(self, self.bus,
787
self.dbus_object_path)
789
def notifychangeproperty(transform_func,
790
dbus_name, type_func=lambda x: x,
792
""" Modify a variable so that its a property that announce its
794
transform_fun: Function that takes a value and transform it to
796
dbus_name: DBus name of the variable
797
type_func: Function that transform the value before sending it
799
variant_level: DBus variant level. default: 1
802
def setter(self, value):
803
old_value = real_value[0]
804
real_value[0] = value
805
if hasattr(self, "dbus_object_path"):
806
if type_func(old_value) != type_func(real_value[0]):
807
dbus_value = transform_func(type_func(real_value[0]),
809
self.PropertyChanged(dbus.String(dbus_name),
812
return property(lambda self: real_value[0], setter)
815
expires = notifychangeproperty(datetime_to_dbus, "Expires")
816
approvals_pending = notifychangeproperty(dbus.Boolean,
819
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
820
last_enabled = notifychangeproperty(datetime_to_dbus,
822
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
823
type_func = lambda checker: checker is not None)
824
last_checked_ok = notifychangeproperty(datetime_to_dbus,
826
last_approval_request = notifychangeproperty(datetime_to_dbus,
827
"LastApprovalRequest")
828
approved_by_default = notifychangeproperty(dbus.Boolean,
830
approval_delay = notifychangeproperty(dbus.UInt16, "ApprovalDelay",
831
type_func = _timedelta_to_milliseconds)
832
approval_duration = notifychangeproperty(dbus.UInt16, "ApprovalDuration",
833
type_func = _timedelta_to_milliseconds)
834
host = notifychangeproperty(dbus.String, "Host")
835
timeout = notifychangeproperty(dbus.UInt16, "Timeout",
836
type_func = _timedelta_to_milliseconds)
837
extended_timeout = notifychangeproperty(dbus.UInt16, "ExtendedTimeout",
838
type_func = _timedelta_to_milliseconds)
839
interval = notifychangeproperty(dbus.UInt16, "Interval",
840
type_func = _timedelta_to_milliseconds)
841
checker_command = notifychangeproperty(dbus.String, "Checker")
843
del notifychangeproperty
474
845
def __del__(self, *args, **kwargs):
476
847
self.remove_from_connection()
477
848
except LookupError:
479
if hasattr(dbus.service.Object, "__del__"):
480
dbus.service.Object.__del__(self, *args, **kwargs)
850
if hasattr(DBusObjectWithProperties, "__del__"):
851
DBusObjectWithProperties.__del__(self, *args, **kwargs)
481
852
Client.__del__(self, *args, **kwargs)
483
854
def checker_callback(self, pid, condition, command,
484
855
*args, **kwargs):
485
856
self.checker_callback_tag = None
486
857
self.checker = None
488
self.PropertyChanged(dbus.String(u"checker_running"),
489
dbus.Boolean(False, variant_level=1))
490
858
if os.WIFEXITED(condition):
491
859
exitstatus = os.WEXITSTATUS(condition)
492
860
# Emit D-Bus signal
559
# GetAllProperties - method
560
@dbus.service.method(_interface, out_signature="a{sv}")
561
def GetAllProperties(self):
563
return dbus.Dictionary({
565
dbus.String(self.name, variant_level=1),
566
dbus.String("fingerprint"):
567
dbus.String(self.fingerprint, variant_level=1),
569
dbus.String(self.host, variant_level=1),
570
dbus.String("created"):
571
_datetime_to_dbus(self.created, variant_level=1),
572
dbus.String("last_enabled"):
573
(_datetime_to_dbus(self.last_enabled,
575
if self.last_enabled is not None
576
else dbus.Boolean(False, variant_level=1)),
577
dbus.String("enabled"):
578
dbus.Boolean(self.enabled, variant_level=1),
579
dbus.String("last_checked_ok"):
580
(_datetime_to_dbus(self.last_checked_ok,
582
if self.last_checked_ok is not None
583
else dbus.Boolean (False, variant_level=1)),
584
dbus.String("timeout"):
585
dbus.UInt64(self.timeout_milliseconds(),
587
dbus.String("interval"):
588
dbus.UInt64(self.interval_milliseconds(),
590
dbus.String("checker"):
591
dbus.String(self.checker_command,
593
dbus.String("checker_running"):
594
dbus.Boolean(self.checker is not None,
596
dbus.String("object_path"):
597
dbus.ObjectPath(self.dbus_object_path,
601
# IsStillValid - method
602
@dbus.service.method(_interface, out_signature="b")
603
def IsStillValid(self):
604
return self.still_valid()
606
916
# PropertyChanged - signal
607
917
@dbus.service.signal(_interface, signature="sv")
608
918
def PropertyChanged(self, property, value):
612
# ReceivedSecret - signal
613
923
@dbus.service.signal(_interface)
614
def ReceivedSecret(self):
926
Is sent after a successful transfer of secret from the Mandos
927
server to mandos-client
618
931
# Rejected - signal
619
@dbus.service.signal(_interface)
932
@dbus.service.signal(_interface, signature="s")
933
def Rejected(self, reason):
624
# SetChecker - method
625
@dbus.service.method(_interface, in_signature="s")
626
def SetChecker(self, checker):
627
"D-Bus setter method"
628
self.checker_command = checker
630
self.PropertyChanged(dbus.String(u"checker"),
631
dbus.String(self.checker_command,
635
@dbus.service.method(_interface, in_signature="s")
636
def SetHost(self, host):
637
"D-Bus setter method"
640
self.PropertyChanged(dbus.String(u"host"),
641
dbus.String(self.host, variant_level=1))
643
# SetInterval - method
644
@dbus.service.method(_interface, in_signature="t")
645
def SetInterval(self, milliseconds):
646
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
648
self.PropertyChanged(dbus.String(u"interval"),
649
(dbus.UInt64(self.interval_milliseconds(),
653
@dbus.service.method(_interface, in_signature="ay",
655
def SetSecret(self, secret):
656
"D-Bus setter method"
657
self.secret = str(secret)
659
# SetTimeout - method
660
@dbus.service.method(_interface, in_signature="t")
661
def SetTimeout(self, milliseconds):
662
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
664
self.PropertyChanged(dbus.String(u"timeout"),
665
(dbus.UInt64(self.timeout_milliseconds(),
937
# NeedApproval - signal
938
@dbus.service.signal(_interface, signature="tb")
939
def NeedApproval(self, timeout, default):
941
return self.need_approval()
946
@dbus.service.method(_interface, in_signature="b")
947
def Approve(self, value):
951
@dbus.service.method(_interface)
668
955
# Enable - method
669
Enable = dbus.service.method(_interface)(enable)
670
Enable.__name__ = "Enable"
956
@dbus.service.method(_interface)
672
961
# StartChecker - method
673
962
@dbus.service.method(_interface)
684
973
# StopChecker - method
685
StopChecker = dbus.service.method(_interface)(stop_checker)
686
StopChecker.__name__ = "StopChecker"
974
@dbus.service.method(_interface)
975
def StopChecker(self):
980
# ApprovalPending - property
981
@dbus_service_property(_interface, signature="b", access="read")
982
def ApprovalPending_dbus_property(self):
983
return dbus.Boolean(bool(self.approvals_pending))
985
# ApprovedByDefault - property
986
@dbus_service_property(_interface, signature="b",
988
def ApprovedByDefault_dbus_property(self, value=None):
989
if value is None: # get
990
return dbus.Boolean(self.approved_by_default)
991
self.approved_by_default = bool(value)
993
# ApprovalDelay - property
994
@dbus_service_property(_interface, signature="t",
996
def ApprovalDelay_dbus_property(self, value=None):
997
if value is None: # get
998
return dbus.UInt64(self.approval_delay_milliseconds())
999
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1001
# ApprovalDuration - property
1002
@dbus_service_property(_interface, signature="t",
1004
def ApprovalDuration_dbus_property(self, value=None):
1005
if value is None: # get
1006
return dbus.UInt64(_timedelta_to_milliseconds(
1007
self.approval_duration))
1008
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1011
@dbus_service_property(_interface, signature="s", access="read")
1012
def Name_dbus_property(self):
1013
return dbus.String(self.name)
1015
# Fingerprint - property
1016
@dbus_service_property(_interface, signature="s", access="read")
1017
def Fingerprint_dbus_property(self):
1018
return dbus.String(self.fingerprint)
1021
@dbus_service_property(_interface, signature="s",
1023
def Host_dbus_property(self, value=None):
1024
if value is None: # get
1025
return dbus.String(self.host)
1028
# Created - property
1029
@dbus_service_property(_interface, signature="s", access="read")
1030
def Created_dbus_property(self):
1031
return dbus.String(datetime_to_dbus(self.created))
1033
# LastEnabled - property
1034
@dbus_service_property(_interface, signature="s", access="read")
1035
def LastEnabled_dbus_property(self):
1036
return datetime_to_dbus(self.last_enabled)
1038
# Enabled - property
1039
@dbus_service_property(_interface, signature="b",
1041
def Enabled_dbus_property(self, value=None):
1042
if value is None: # get
1043
return dbus.Boolean(self.enabled)
1049
# LastCheckedOK - property
1050
@dbus_service_property(_interface, signature="s",
1052
def LastCheckedOK_dbus_property(self, value=None):
1053
if value is not None:
1056
return datetime_to_dbus(self.last_checked_ok)
1058
# Expires - property
1059
@dbus_service_property(_interface, signature="s", access="read")
1060
def Expires_dbus_property(self):
1061
return datetime_to_dbus(self.expires)
1063
# LastApprovalRequest - property
1064
@dbus_service_property(_interface, signature="s", access="read")
1065
def LastApprovalRequest_dbus_property(self):
1066
return datetime_to_dbus(self.last_approval_request)
1068
# Timeout - property
1069
@dbus_service_property(_interface, signature="t",
1071
def Timeout_dbus_property(self, value=None):
1072
if value is None: # get
1073
return dbus.UInt64(self.timeout_milliseconds())
1074
self.timeout = datetime.timedelta(0, 0, 0, value)
1075
if getattr(self, "disable_initiator_tag", None) is None:
1077
# Reschedule timeout
1078
gobject.source_remove(self.disable_initiator_tag)
1079
self.disable_initiator_tag = None
1081
time_to_die = (self.
1082
_timedelta_to_milliseconds((self
1087
if time_to_die <= 0:
1088
# The timeout has passed
1091
self.expires = (datetime.datetime.utcnow()
1092
+ datetime.timedelta(milliseconds = time_to_die))
1093
self.disable_initiator_tag = (gobject.timeout_add
1094
(time_to_die, self.disable))
1096
# ExtendedTimeout - property
1097
@dbus_service_property(_interface, signature="t",
1099
def ExtendedTimeout_dbus_property(self, value=None):
1100
if value is None: # get
1101
return dbus.UInt64(self.extended_timeout_milliseconds())
1102
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1104
# Interval - property
1105
@dbus_service_property(_interface, signature="t",
1107
def Interval_dbus_property(self, value=None):
1108
if value is None: # get
1109
return dbus.UInt64(self.interval_milliseconds())
1110
self.interval = datetime.timedelta(0, 0, 0, value)
1111
if getattr(self, "checker_initiator_tag", None) is None:
1113
# Reschedule checker run
1114
gobject.source_remove(self.checker_initiator_tag)
1115
self.checker_initiator_tag = (gobject.timeout_add
1116
(value, self.start_checker))
1117
self.start_checker() # Start one now, too
1119
# Checker - property
1120
@dbus_service_property(_interface, signature="s",
1122
def Checker_dbus_property(self, value=None):
1123
if value is None: # get
1124
return dbus.String(self.checker_command)
1125
self.checker_command = value
1127
# CheckerRunning - property
1128
@dbus_service_property(_interface, signature="b",
1130
def CheckerRunning_dbus_property(self, value=None):
1131
if value is None: # get
1132
return dbus.Boolean(self.checker is not None)
1134
self.start_checker()
1138
# ObjectPath - property
1139
@dbus_service_property(_interface, signature="o", access="read")
1140
def ObjectPath_dbus_property(self):
1141
return self.dbus_object_path # is already a dbus.ObjectPath
1144
@dbus_service_property(_interface, signature="ay",
1145
access="write", byte_arrays=True)
1146
def Secret_dbus_property(self, value):
1147
self.secret = str(value)
691
class ClientHandler(SocketServer.BaseRequestHandler, object):
1152
class ProxyClient(object):
1153
def __init__(self, child_pipe, fpr, address):
1154
self._pipe = child_pipe
1155
self._pipe.send(('init', fpr, address))
1156
if not self._pipe.recv():
1159
def __getattribute__(self, name):
1160
if(name == '_pipe'):
1161
return super(ProxyClient, self).__getattribute__(name)
1162
self._pipe.send(('getattr', name))
1163
data = self._pipe.recv()
1164
if data[0] == 'data':
1166
if data[0] == 'function':
1167
def func(*args, **kwargs):
1168
self._pipe.send(('funcall', name, args, kwargs))
1169
return self._pipe.recv()[1]
1172
def __setattr__(self, name, value):
1173
if(name == '_pipe'):
1174
return super(ProxyClient, self).__setattr__(name, value)
1175
self._pipe.send(('setattr', name, value))
1178
class ClientHandler(socketserver.BaseRequestHandler, object):
692
1179
"""A class to handle client connections.
694
1181
Instantiated once for each connection to handle it.
695
1182
Note: This will run in its own forked process."""
697
1184
def handle(self):
698
logger.info(u"TCP connection from: %s",
699
unicode(self.client_address))
700
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
701
# Open IPC pipe to parent process
702
with closing(os.fdopen(self.server.pipe[1], "w", 1)) as ipc:
1185
with contextlib.closing(self.server.child_pipe) as child_pipe:
1186
logger.info("TCP connection from: %s",
1187
unicode(self.client_address))
1188
logger.debug("Pipe FD: %d",
1189
self.server.child_pipe.fileno())
703
1191
session = (gnutls.connection
704
1192
.ClientSession(self.request,
705
1193
gnutls.connection
706
1194
.X509Credentials()))
708
line = self.request.makefile().readline()
709
logger.debug(u"Protocol version: %r", line)
711
if int(line.strip().split()[0]) > 1:
713
except (ValueError, IndexError, RuntimeError), error:
714
logger.error(u"Unknown protocol version: %s", error)
717
1196
# Note: gnutls.connection.X509Credentials is really a
718
1197
# generic GnuTLS certificate credentials object so long as
719
1198
# no X.509 keys are added to it. Therefore, we can use it
720
1199
# here despite using OpenPGP certificates.
722
1201
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
723
# "+AES-256-CBC", "+SHA1",
724
# "+COMP-NULL", "+CTYPE-OPENPGP",
1202
# "+AES-256-CBC", "+SHA1",
1203
# "+COMP-NULL", "+CTYPE-OPENPGP",
726
1205
# Use a fallback default, since this MUST be set.
727
1206
priority = self.server.gnutls_priority
728
1207
if priority is None:
731
1210
.gnutls_priority_set_direct(session._c_object,
732
1211
priority, None))
1213
# Start communication using the Mandos protocol
1214
# Get protocol number
1215
line = self.request.makefile().readline()
1216
logger.debug("Protocol version: %r", line)
1218
if int(line.strip().split()[0]) > 1:
1220
except (ValueError, IndexError, RuntimeError) as error:
1221
logger.error("Unknown protocol version: %s", error)
1224
# Start GnuTLS connection
735
1226
session.handshake()
736
except gnutls.errors.GNUTLSError, error:
737
logger.warning(u"Handshake failed: %s", error)
1227
except gnutls.errors.GNUTLSError as error:
1228
logger.warning("Handshake failed: %s", error)
738
1229
# Do not run session.bye() here: the session is not
739
1230
# established. Just abandon the request.
741
logger.debug(u"Handshake succeeded")
1232
logger.debug("Handshake succeeded")
1234
approval_required = False
743
fpr = self.fingerprint(self.peer_certificate(session))
744
except (TypeError, gnutls.errors.GNUTLSError), error:
745
logger.warning(u"Bad certificate: %s", error)
748
logger.debug(u"Fingerprint: %s", fpr)
1237
fpr = self.fingerprint(self.peer_certificate
1240
gnutls.errors.GNUTLSError) as error:
1241
logger.warning("Bad certificate: %s", error)
1243
logger.debug("Fingerprint: %s", fpr)
1246
client = ProxyClient(child_pipe, fpr,
1247
self.client_address)
1251
if client.approval_delay:
1252
delay = client.approval_delay
1253
client.approvals_pending += 1
1254
approval_required = True
1257
if not client.enabled:
1258
logger.info("Client %s is disabled",
1260
if self.server.use_dbus:
1262
client.Rejected("Disabled")
1265
if client._approved or not client.approval_delay:
1266
#We are approved or approval is disabled
1268
elif client._approved is None:
1269
logger.info("Client %s needs approval",
1271
if self.server.use_dbus:
1273
client.NeedApproval(
1274
client.approval_delay_milliseconds(),
1275
client.approved_by_default)
1277
logger.warning("Client %s was not approved",
1279
if self.server.use_dbus:
1281
client.Rejected("Denied")
1284
#wait until timeout or approved
1285
#x = float(client._timedelta_to_milliseconds(delay))
1286
time = datetime.datetime.now()
1287
client.changedstate.acquire()
1288
client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1289
client.changedstate.release()
1290
time2 = datetime.datetime.now()
1291
if (time2 - time) >= delay:
1292
if not client.approved_by_default:
1293
logger.warning("Client %s timed out while"
1294
" waiting for approval",
1296
if self.server.use_dbus:
1298
client.Rejected("Approval timed out")
1303
delay -= time2 - time
1306
while sent_size < len(client.secret):
1308
sent = session.send(client.secret[sent_size:])
1309
except gnutls.errors.GNUTLSError as error:
1310
logger.warning("gnutls send failed")
1312
logger.debug("Sent: %d, remaining: %d",
1313
sent, len(client.secret)
1314
- (sent_size + sent))
1317
logger.info("Sending secret to %s", client.name)
1318
# bump the timeout as if seen
1319
client.checked_ok(client.extended_timeout)
1320
if self.server.use_dbus:
750
for c in self.server.clients:
751
if c.fingerprint == fpr:
755
ipc.write("NOTFOUND %s\n" % fpr)
758
# Have to check if client.still_valid(), since it is
759
# possible that the client timed out while establishing
760
# the GnuTLS session.
761
if not client.still_valid():
762
ipc.write("INVALID %s\n" % client.name)
765
ipc.write("SENDING %s\n" % client.name)
767
while sent_size < len(client.secret):
768
sent = session.send(client.secret[sent_size:])
769
logger.debug(u"Sent: %d, remaining: %d",
770
sent, len(client.secret)
771
- (sent_size + sent))
1325
if approval_required:
1326
client.approvals_pending -= 1
1329
except gnutls.errors.GNUTLSError as error:
1330
logger.warning("GnuTLS bye failed")
776
1333
def peer_certificate(session):
832
1389
# Convert the buffer to a Python bytestring
833
1390
fpr = ctypes.string_at(buf, buf_len.value)
834
1391
# Convert the bytestring to hexadecimal notation
835
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1392
hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
839
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
840
"""Like SocketServer.ForkingMixIn, but also pass a pipe.
842
Assumes a gobject.MainLoop event loop.
1396
class MultiprocessingMixIn(object):
1397
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1398
def sub_process_main(self, request, address):
1400
self.finish_request(request, address)
1402
self.handle_error(request, address)
1403
self.close_request(request)
1405
def process_request(self, request, address):
1406
"""Start a new process to process the request."""
1407
multiprocessing.Process(target = self.sub_process_main,
1408
args = (request, address)).start()
1411
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1412
""" adds a pipe to the MixIn """
844
1413
def process_request(self, request, client_address):
845
1414
"""Overrides and wraps the original process_request().
847
This function creates a new pipe in self.pipe
1416
This function creates a new pipe in self.pipe
849
self.pipe = os.pipe()
850
super(ForkingMixInWithPipe,
1418
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1420
super(MultiprocessingMixInWithPipe,
851
1421
self).process_request(request, client_address)
852
os.close(self.pipe[1]) # close write end
853
# Call "handle_ipc" for both data and EOF events
854
gobject.io_add_watch(self.pipe[0],
855
gobject.IO_IN | gobject.IO_HUP,
857
def handle_ipc(source, condition):
1422
self.child_pipe.close()
1423
self.add_pipe(parent_pipe)
1425
def add_pipe(self, parent_pipe):
858
1426
"""Dummy function; override as necessary"""
863
class IPv6_TCPServer(ForkingMixInWithPipe,
864
SocketServer.TCPServer, object):
1427
raise NotImplementedError
1430
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1431
socketserver.TCPServer, object):
865
1432
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
868
1435
enabled: Boolean; whether this server is activated yet
869
1436
interface: None or a network interface name (string)
870
1437
use_ipv6: Boolean; to use IPv6 or not
872
clients: Set() of Client objects
873
gnutls_priority GnuTLS priority string
874
use_dbus: Boolean; to emit D-Bus signals or not
876
1439
def __init__(self, server_address, RequestHandlerClass,
877
interface=None, use_ipv6=True, clients=None,
878
gnutls_priority=None, use_dbus=True):
1440
interface=None, use_ipv6=True):
880
1441
self.interface = interface
882
1443
self.address_family = socket.AF_INET6
883
self.clients = clients
884
self.use_dbus = use_dbus
885
self.gnutls_priority = gnutls_priority
886
SocketServer.TCPServer.__init__(self, server_address,
1444
socketserver.TCPServer.__init__(self, server_address,
887
1445
RequestHandlerClass)
888
1446
def server_bind(self):
889
1447
"""This overrides the normal server_bind() function
890
1448
to bind to an interface if one was specified, and also NOT to
891
1449
bind to an address or port if they were not specified."""
892
1450
if self.interface is not None:
894
self.socket.setsockopt(socket.SOL_SOCKET,
896
self.interface + '\0')
897
except socket.error, error:
898
if error[0] == errno.EPERM:
899
logger.error(u"No permission to"
900
u" bind to interface %s",
1451
if SO_BINDTODEVICE is None:
1452
logger.error("SO_BINDTODEVICE does not exist;"
1453
" cannot bind to interface %s",
1457
self.socket.setsockopt(socket.SOL_SOCKET,
1461
except socket.error as error:
1462
if error[0] == errno.EPERM:
1463
logger.error("No permission to"
1464
" bind to interface %s",
1466
elif error[0] == errno.ENOPROTOOPT:
1467
logger.error("SO_BINDTODEVICE not available;"
1468
" cannot bind to interface %s",
904
1472
# Only bind(2) the socket if we really need to.
905
1473
if self.server_address[0] or self.server_address[1]:
906
1474
if not self.server_address[0]:
920
1488
# if_nametoindex
921
1489
# (self.interface))
922
return SocketServer.TCPServer.server_bind(self)
1490
return socketserver.TCPServer.server_bind(self)
1493
class MandosServer(IPv6_TCPServer):
1497
clients: set of Client objects
1498
gnutls_priority GnuTLS priority string
1499
use_dbus: Boolean; to emit D-Bus signals or not
1501
Assumes a gobject.MainLoop event loop.
1503
def __init__(self, server_address, RequestHandlerClass,
1504
interface=None, use_ipv6=True, clients=None,
1505
gnutls_priority=None, use_dbus=True):
1506
self.enabled = False
1507
self.clients = clients
1508
if self.clients is None:
1509
self.clients = set()
1510
self.use_dbus = use_dbus
1511
self.gnutls_priority = gnutls_priority
1512
IPv6_TCPServer.__init__(self, server_address,
1513
RequestHandlerClass,
1514
interface = interface,
1515
use_ipv6 = use_ipv6)
923
1516
def server_activate(self):
924
1517
if self.enabled:
925
return SocketServer.TCPServer.server_activate(self)
1518
return socketserver.TCPServer.server_activate(self)
926
1519
def enable(self):
927
1520
self.enabled = True
928
def handle_ipc(self, source, condition, file_objects={}):
1521
def add_pipe(self, parent_pipe):
1522
# Call "handle_ipc" for both data and EOF events
1523
gobject.io_add_watch(parent_pipe.fileno(),
1524
gobject.IO_IN | gobject.IO_HUP,
1525
functools.partial(self.handle_ipc,
1526
parent_pipe = parent_pipe))
1528
def handle_ipc(self, source, condition, parent_pipe=None,
1529
client_object=None):
929
1530
condition_names = {
930
gobject.IO_IN: "IN", # There is data to read.
1531
gobject.IO_IN: "IN", # There is data to read.
931
1532
gobject.IO_OUT: "OUT", # Data can be written (without
933
1534
gobject.IO_PRI: "PRI", # There is urgent data to read.
934
1535
gobject.IO_ERR: "ERR", # Error condition.
935
1536
gobject.IO_HUP: "HUP" # Hung up (the connection has been
936
# broken, usually for pipes and
1537
# broken, usually for pipes and
939
1540
conditions_string = ' | '.join(name
940
1541
for cond, name in
941
1542
condition_names.iteritems()
942
1543
if cond & condition)
943
logger.debug("Handling IPC: FD = %d, condition = %s", source,
946
# Turn the pipe file descriptor into a Python file object
947
if source not in file_objects:
948
file_objects[source] = os.fdopen(source, "r", 1)
950
# Read a line from the file object
951
cmdline = file_objects[source].readline()
952
if not cmdline: # Empty line means end of file
954
file_objects[source].close()
955
del file_objects[source]
957
# Stop calling this function
960
logger.debug("IPC command: %r", cmdline)
962
# Parse and act on command
963
cmd, args = cmdline.rstrip("\r\n").split(None, 1)
965
if cmd == "NOTFOUND":
966
logger.warning(u"Client not found for fingerprint: %s",
970
mandos_dbus_service.ClientNotFound(args)
971
elif cmd == "INVALID":
972
for client in self.clients:
973
if client.name == args:
974
logger.warning(u"Client %s is invalid", args)
980
logger.error(u"Unknown client %s is invalid", args)
981
elif cmd == "SENDING":
982
for client in self.clients:
983
if client.name == args:
984
logger.info(u"Sending secret to %s", client.name)
988
client.ReceivedSecret()
991
logger.error(u"Sending secret to unknown client %s",
994
logger.error("Unknown IPC command: %r", cmdline)
996
# Keep calling this function
1544
# error or the other end of multiprocessing.Pipe has closed
1545
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1548
# Read a request from the child
1549
request = parent_pipe.recv()
1550
command = request[0]
1552
if command == 'init':
1554
address = request[2]
1556
for c in self.clients:
1557
if c.fingerprint == fpr:
1561
logger.info("Client not found for fingerprint: %s, ad"
1562
"dress: %s", fpr, address)
1565
mandos_dbus_service.ClientNotFound(fpr, address[0])
1566
parent_pipe.send(False)
1569
gobject.io_add_watch(parent_pipe.fileno(),
1570
gobject.IO_IN | gobject.IO_HUP,
1571
functools.partial(self.handle_ipc,
1572
parent_pipe = parent_pipe,
1573
client_object = client))
1574
parent_pipe.send(True)
1575
# remove the old hook in favor of the new above hook on same fileno
1577
if command == 'funcall':
1578
funcname = request[1]
1582
parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1584
if command == 'getattr':
1585
attrname = request[1]
1586
if callable(client_object.__getattribute__(attrname)):
1587
parent_pipe.send(('function',))
1589
parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1591
if command == 'setattr':
1592
attrname = request[1]
1594
setattr(client_object, attrname, value)
1019
1618
suffix = unicode(s[-1])
1020
1619
value = int(s[:-1])
1022
1621
delta = datetime.timedelta(value)
1023
elif suffix == u"s":
1024
1623
delta = datetime.timedelta(0, value)
1025
elif suffix == u"m":
1026
1625
delta = datetime.timedelta(0, 0, 0, 0, value)
1027
elif suffix == u"h":
1028
1627
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1029
elif suffix == u"w":
1030
1629
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1033
except (ValueError, IndexError):
1631
raise ValueError("Unknown suffix %r" % suffix)
1632
except (ValueError, IndexError) as e:
1633
raise ValueError(*(e.args))
1035
1634
timevalue += delta
1036
1635
return timevalue
1039
def server_state_changed(state):
1040
"""Derived from the Avahi example code"""
1041
if state == avahi.SERVER_COLLISION:
1042
logger.error(u"Zeroconf server name collision")
1044
elif state == avahi.SERVER_RUNNING:
1048
def entry_group_state_changed(state, error):
1049
"""Derived from the Avahi example code"""
1050
logger.debug(u"Avahi state change: %i", state)
1052
if state == avahi.ENTRY_GROUP_ESTABLISHED:
1053
logger.debug(u"Zeroconf service established.")
1054
elif state == avahi.ENTRY_GROUP_COLLISION:
1055
logger.warning(u"Zeroconf service name collision.")
1057
elif state == avahi.ENTRY_GROUP_FAILURE:
1058
logger.critical(u"Avahi: Error in group state changed %s",
1060
raise AvahiGroupError(u"State changed: %s" % unicode(error))
1062
1638
def if_nametoindex(interface):
1063
"""Call the C function if_nametoindex(), or equivalent"""
1639
"""Call the C function if_nametoindex(), or equivalent
1641
Note: This function cannot accept a unicode string."""
1064
1642
global if_nametoindex
1066
1644
if_nametoindex = (ctypes.cdll.LoadLibrary
1067
1645
(ctypes.util.find_library("c"))
1068
1646
.if_nametoindex)
1069
1647
except (OSError, AttributeError):
1070
if "struct" not in sys.modules:
1072
if "fcntl" not in sys.modules:
1648
logger.warning("Doing if_nametoindex the hard way")
1074
1649
def if_nametoindex(interface):
1075
1650
"Get an interface index the hard way, i.e. using fcntl()"
1076
1651
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1077
with closing(socket.socket()) as s:
1652
with contextlib.closing(socket.socket()) as s:
1078
1653
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1079
struct.pack("16s16x", interface))
1080
interface_index = struct.unpack("I", ifreq[16:20])[0]
1654
struct.pack(str("16s16x"),
1656
interface_index = struct.unpack(str("I"),
1081
1658
return interface_index
1082
1659
return if_nametoindex(interface)
1111
######################################################################
1689
##################################################################
1112
1690
# Parsing of options, both command line and config file
1114
parser = optparse.OptionParser(version = "%%prog %s" % version)
1115
parser.add_option("-i", "--interface", type="string",
1116
metavar="IF", help="Bind to interface IF")
1117
parser.add_option("-a", "--address", type="string",
1118
help="Address to listen for requests on")
1119
parser.add_option("-p", "--port", type="int",
1120
help="Port number to receive requests on")
1121
parser.add_option("--check", action="store_true",
1122
help="Run self-test")
1123
parser.add_option("--debug", action="store_true",
1124
help="Debug mode; run in foreground and log to"
1126
parser.add_option("--priority", type="string", help="GnuTLS"
1127
" priority string (see GnuTLS documentation)")
1128
parser.add_option("--servicename", type="string", metavar="NAME",
1129
help="Zeroconf service name")
1130
parser.add_option("--configdir", type="string",
1131
default="/etc/mandos", metavar="DIR",
1132
help="Directory to search for configuration"
1134
parser.add_option("--no-dbus", action="store_false",
1136
help="Do not provide D-Bus system bus"
1138
parser.add_option("--no-ipv6", action="store_false",
1139
dest="use_ipv6", help="Do not use IPv6")
1140
options = parser.parse_args()[0]
1692
parser = argparse.ArgumentParser()
1693
parser.add_argument("-v", "--version", action="version",
1694
version = "%%(prog)s %s" % version,
1695
help="show version number and exit")
1696
parser.add_argument("-i", "--interface", metavar="IF",
1697
help="Bind to interface IF")
1698
parser.add_argument("-a", "--address",
1699
help="Address to listen for requests on")
1700
parser.add_argument("-p", "--port", type=int,
1701
help="Port number to receive requests on")
1702
parser.add_argument("--check", action="store_true",
1703
help="Run self-test")
1704
parser.add_argument("--debug", action="store_true",
1705
help="Debug mode; run in foreground and log"
1707
parser.add_argument("--debuglevel", metavar="LEVEL",
1708
help="Debug level for stdout output")
1709
parser.add_argument("--priority", help="GnuTLS"
1710
" priority string (see GnuTLS documentation)")
1711
parser.add_argument("--servicename",
1712
metavar="NAME", help="Zeroconf service name")
1713
parser.add_argument("--configdir",
1714
default="/etc/mandos", metavar="DIR",
1715
help="Directory to search for configuration"
1717
parser.add_argument("--no-dbus", action="store_false",
1718
dest="use_dbus", help="Do not provide D-Bus"
1719
" system bus interface")
1720
parser.add_argument("--no-ipv6", action="store_false",
1721
dest="use_ipv6", help="Do not use IPv6")
1722
options = parser.parse_args()
1142
1724
if options.check:
1266
1859
(gnutls.library.functions
1267
1860
.gnutls_global_set_log_function(debug_gnutls))
1862
# Redirect stdin so all checkers get /dev/null
1863
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1864
os.dup2(null, sys.stdin.fileno())
1868
# No console logging
1869
logger.removeHandler(console)
1270
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1271
service = AvahiService(name = server_settings["servicename"],
1272
servicetype = "_mandos._tcp",
1273
protocol = protocol)
1274
if server_settings["interface"]:
1275
service.interface = (if_nametoindex
1276
(server_settings["interface"]))
1871
# Need to fork before connecting to D-Bus
1873
# Close all input and output, do double fork, etc.
1278
1876
global main_loop
1281
1877
# From the Avahi example code
1282
1878
DBusGMainLoop(set_as_default=True )
1283
1879
main_loop = gobject.MainLoop()
1284
1880
bus = dbus.SystemBus()
1285
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1286
avahi.DBUS_PATH_SERVER),
1287
avahi.DBUS_INTERFACE_SERVER)
1288
1881
# End of Avahi example code
1290
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1884
bus_name = dbus.service.BusName("se.bsnet.fukt.Mandos",
1885
bus, do_not_queue=True)
1886
except dbus.exceptions.NameExistsException as e:
1887
logger.error(unicode(e) + ", disabling D-Bus")
1889
server_settings["use_dbus"] = False
1890
tcp_server.use_dbus = False
1891
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1892
service = AvahiService(name = server_settings["servicename"],
1893
servicetype = "_mandos._tcp",
1894
protocol = protocol, bus = bus)
1895
if server_settings["interface"]:
1896
service.interface = (if_nametoindex
1897
(str(server_settings["interface"])))
1899
global multiprocessing_manager
1900
multiprocessing_manager = multiprocessing.Manager()
1292
1902
client_class = Client
1294
client_class = ClientDBus
1904
client_class = functools.partial(ClientDBus, bus = bus)
1905
def client_config_items(config, section):
1906
special_settings = {
1907
"approved_by_default":
1908
lambda: config.getboolean(section,
1909
"approved_by_default"),
1911
for name, value in config.items(section):
1913
yield (name, special_settings[name]())
1917
tcp_server.clients.update(set(
1296
1918
client_class(name = section,
1297
config= dict(client_config.items(section)))
1919
config= dict(client_config_items(
1920
client_config, section)))
1298
1921
for section in client_config.sections()))
1300
logger.warning(u"No clients defined")
1303
# Redirect stdin so all checkers get /dev/null
1304
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1305
os.dup2(null, sys.stdin.fileno())
1309
# No console logging
1310
logger.removeHandler(console)
1311
# Close all input and output, do double fork, etc.
1315
with closing(pidfile):
1317
pidfile.write(str(pid) + "\n")
1320
logger.error(u"Could not write to file %r with PID %d",
1323
# "pidfile" was never created
1328
"Cleanup function; run on exit"
1330
# From the Avahi example code
1331
if not group is None:
1334
# End of Avahi example code
1922
if not tcp_server.clients:
1923
logger.warning("No clients defined")
1337
client = clients.pop()
1338
client.disable_hook = None
1341
atexit.register(cleanup)
1929
pidfile.write(str(pid) + "\n".encode("utf-8"))
1932
logger.error("Could not write to file %r with PID %d",
1935
# "pidfile" was never created
1344
1939
signal.signal(signal.SIGINT, signal.SIG_IGN)
1345
1941
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1346
1942
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())