2
# -*- mode: python; coding: utf-8 -*-
4
# Mandos server - give out binary blobs to connecting clients.
6
# This program is partly derived from an example program for an Avahi
7
# service publisher, downloaded from
8
# <http://avahi.org/wiki/PythonPublishExample>. This includes the
9
# methods "add", "remove", "server_state_changed",
10
# "entry_group_state_changed", "cleanup", and "activate" in the
11
# "AvahiService" class, and some lines in "main".
14
# Copyright © 2008,2009 Teddy Hogeborn
15
# Copyright © 2008,2009 Björn Påhlsson
17
# This program is free software: you can redistribute it and/or modify
18
# it under the terms of the GNU General Public License as published by
19
# the Free Software Foundation, either version 3 of the License, or
20
# (at your option) any later version.
22
# This program is distributed in the hope that it will be useful,
23
# but WITHOUT ANY WARRANTY; without even the implied warranty of
24
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
# GNU General Public License for more details.
27
# You should have received a copy of the GNU General Public License
28
# along with this program. If not, see
29
# <http://www.gnu.org/licenses/>.
31
# Contact the authors at <mandos@fukt.bsnet.se>.
34
from __future__ import division, with_statement, absolute_import
36
import SocketServer as socketserver
6
from optparse import OptionParser
41
9
import gnutls.crypto
42
10
import gnutls.connection
43
11
import gnutls.errors
44
import gnutls.library.functions
45
import gnutls.library.constants
46
import gnutls.library.types
47
import ConfigParser as configparser
56
import logging.handlers
58
from contextlib import closing
67
from dbus.mainloop.glib import DBusGMainLoop
70
import xml.dom.minidom
74
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
75
except AttributeError:
77
from IN import SO_BINDTODEVICE
79
SO_BINDTODEVICE = None
84
logger = logging.Logger(u'mandos')
85
syslogger = (logging.handlers.SysLogHandler
86
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
87
address = "/dev/log"))
88
syslogger.setFormatter(logging.Formatter
89
(u'Mandos [%(process)d]: %(levelname)s:'
91
logger.addHandler(syslogger)
93
console = logging.StreamHandler()
94
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
97
logger.addHandler(console)
99
class AvahiError(Exception):
100
def __init__(self, value, *args, **kwargs):
102
super(AvahiError, self).__init__(value, *args, **kwargs)
103
def __unicode__(self):
104
return unicode(repr(self.value))
106
class AvahiServiceError(AvahiError):
109
class AvahiGroupError(AvahiError):
113
class AvahiService(object):
114
"""An Avahi (Zeroconf) service.
117
interface: integer; avahi.IF_UNSPEC or an interface index.
118
Used to optionally bind to the specified interface.
119
name: string; Example: u'Mandos'
120
type: string; Example: u'_mandos._tcp'.
121
See <http://www.dns-sd.org/ServiceTypes.html>
122
port: integer; what port to announce
123
TXT: list of strings; TXT record for the service
124
domain: string; Domain to publish on, default to .local if empty.
125
host: string; Host to publish records for, default is localhost
126
max_renames: integer; maximum number of renames
127
rename_count: integer; counter so we only rename after collisions
128
a sensible number of times
129
group: D-Bus Entry Group
131
bus: dbus.SystemBus()
133
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
134
servicetype = None, port = None, TXT = None,
135
domain = u"", host = u"", max_renames = 32768,
136
protocol = avahi.PROTO_UNSPEC, bus = None):
137
self.interface = interface
139
self.type = servicetype
141
self.TXT = TXT if TXT is not None else []
144
self.rename_count = 0
145
self.max_renames = max_renames
146
self.protocol = protocol
147
self.group = None # our entry group
151
"""Derived from the Avahi example code"""
152
if self.rename_count >= self.max_renames:
153
logger.critical(u"No suitable Zeroconf service name found"
154
u" after %i retries, exiting.",
156
raise AvahiServiceError(u"Too many renames")
157
self.name = self.server.GetAlternativeServiceName(self.name)
158
logger.info(u"Changing Zeroconf service name to %r ...",
160
syslogger.setFormatter(logging.Formatter
161
(u'Mandos (%s) [%%(process)d]:'
162
u' %%(levelname)s: %%(message)s'
166
self.rename_count += 1
168
"""Derived from the Avahi example code"""
169
if self.group is not None:
172
"""Derived from the Avahi example code"""
173
if self.group is None:
174
self.group = dbus.Interface(
175
self.bus.get_object(avahi.DBUS_NAME,
176
self.server.EntryGroupNew()),
177
avahi.DBUS_INTERFACE_ENTRY_GROUP)
178
self.group.connect_to_signal('StateChanged',
180
.entry_group_state_changed)
181
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
182
self.name, self.type)
183
self.group.AddService(
186
dbus.UInt32(0), # flags
187
self.name, self.type,
188
self.domain, self.host,
189
dbus.UInt16(self.port),
190
avahi.string_array_to_txt_array(self.TXT))
192
def entry_group_state_changed(self, state, error):
193
"""Derived from the Avahi example code"""
194
logger.debug(u"Avahi state change: %i", state)
196
if state == avahi.ENTRY_GROUP_ESTABLISHED:
197
logger.debug(u"Zeroconf service established.")
198
elif state == avahi.ENTRY_GROUP_COLLISION:
199
logger.warning(u"Zeroconf service name collision.")
201
elif state == avahi.ENTRY_GROUP_FAILURE:
202
logger.critical(u"Avahi: Error in group state changed %s",
204
raise AvahiGroupError(u"State changed: %s"
207
"""Derived from the Avahi example code"""
208
if self.group is not None:
211
def server_state_changed(self, state):
212
"""Derived from the Avahi example code"""
213
if state == avahi.SERVER_COLLISION:
214
logger.error(u"Zeroconf server name collision")
216
elif state == avahi.SERVER_RUNNING:
219
"""Derived from the Avahi example code"""
220
if self.server is None:
221
self.server = dbus.Interface(
222
self.bus.get_object(avahi.DBUS_NAME,
223
avahi.DBUS_PATH_SERVER),
224
avahi.DBUS_INTERFACE_SERVER)
225
self.server.connect_to_signal(u"StateChanged",
226
self.server_state_changed)
227
self.server_state_changed(self.server.GetState())
230
16
class Client(object):
231
"""A representation of a client host served by this server.
234
name: string; from the config file, used in log messages and
236
fingerprint: string (40 or 32 hexadecimal digits); used to
237
uniquely identify the client
238
secret: bytestring; sent verbatim (over TLS) to client
239
host: string; available for use by the checker command
240
created: datetime.datetime(); (UTC) object creation
241
last_enabled: datetime.datetime(); (UTC)
243
last_checked_ok: datetime.datetime(); (UTC) or None
244
timeout: datetime.timedelta(); How long from last_checked_ok
245
until this client is invalid
246
interval: datetime.timedelta(); How often to start a new checker
247
disable_hook: If set, called by disable() as disable_hook(self)
248
checker: subprocess.Popen(); a running checker process used
249
to see if the client lives.
250
'None' if no process is running.
251
checker_initiator_tag: a gobject event source tag, or None
252
disable_initiator_tag: - '' -
253
checker_callback_tag: - '' -
254
checker_command: string; External command which is run to check if
255
client lives. %() expansions are done at
256
runtime with vars(self) as dict, so that for
257
instance %(name)s can be used in the command.
258
current_checker_command: string; current running checker_command
262
def _timedelta_to_milliseconds(td):
263
"Convert a datetime.timedelta() to milliseconds"
264
return ((td.days * 24 * 60 * 60 * 1000)
265
+ (td.seconds * 1000)
266
+ (td.microseconds // 1000))
268
def timeout_milliseconds(self):
269
"Return the 'timeout' attribute in milliseconds"
270
return self._timedelta_to_milliseconds(self.timeout)
272
def interval_milliseconds(self):
273
"Return the 'interval' attribute in milliseconds"
274
return self._timedelta_to_milliseconds(self.interval)
276
def __init__(self, name = None, disable_hook=None, config=None):
277
"""Note: the 'checker' key in 'config' sets the
278
'checker_command' attribute and *not* the 'checker'
17
def __init__(self, name=None, options=None, dn=None,
18
password=None, passfile=None, fqdn=None,
19
timeout=None, interval=-1):
283
logger.debug(u"Creating client %r", self.name)
284
# Uppercase and remove spaces from fingerprint for later
285
# comparison purposes with return value from the fingerprint()
287
self.fingerprint = (config[u"fingerprint"].upper()
289
logger.debug(u" Fingerprint: %s", self.fingerprint)
290
if u"secret" in config:
291
self.secret = config[u"secret"].decode(u"base64")
292
elif u"secfile" in config:
293
with closing(open(os.path.expanduser
295
(config[u"secfile"])))) as secfile:
296
self.secret = secfile.read()
298
raise TypeError(u"No secret or secfile for client %s"
300
self.host = config.get(u"host", u"")
301
self.created = datetime.datetime.utcnow()
303
self.last_enabled = None
304
self.last_checked_ok = None
305
self.timeout = string_to_delta(config[u"timeout"])
306
self.interval = string_to_delta(config[u"interval"])
307
self.disable_hook = disable_hook
309
self.checker_initiator_tag = None
310
self.disable_initiator_tag = None
311
self.checker_callback_tag = None
312
self.checker_command = config[u"checker"]
313
self.current_checker_command = None
314
self.last_connect = None
317
"""Start this client's checker and timeout hooks"""
318
if getattr(self, u"enabled", False):
321
self.last_enabled = datetime.datetime.utcnow()
322
# Schedule a new checker to be started an 'interval' from now,
323
# and every interval from then on.
324
self.checker_initiator_tag = (gobject.timeout_add
325
(self.interval_milliseconds(),
327
# Also start a new checker *right now*.
329
# Schedule a disable() when 'timeout' has passed
330
self.disable_initiator_tag = (gobject.timeout_add
331
(self.timeout_milliseconds(),
336
"""Disable this client."""
337
if not getattr(self, "enabled", False):
339
logger.info(u"Disabling client %s", self.name)
340
if getattr(self, u"disable_initiator_tag", False):
341
gobject.source_remove(self.disable_initiator_tag)
342
self.disable_initiator_tag = None
343
if getattr(self, u"checker_initiator_tag", False):
344
gobject.source_remove(self.checker_initiator_tag)
345
self.checker_initiator_tag = None
347
if self.disable_hook:
348
self.disable_hook(self)
350
# Do not run this again if called by a gobject.timeout_add
354
self.disable_hook = None
357
def checker_callback(self, pid, condition, command):
358
"""The checker has completed, so take appropriate actions."""
359
self.checker_callback_tag = None
361
if os.WIFEXITED(condition):
362
exitstatus = os.WEXITSTATUS(condition)
364
logger.info(u"Checker for %(name)s succeeded",
368
logger.info(u"Checker for %(name)s failed",
371
logger.warning(u"Checker for %(name)s crashed?",
374
def checked_ok(self):
375
"""Bump up the timeout for this client.
377
This should only be called when the client has been seen,
380
self.last_checked_ok = datetime.datetime.utcnow()
381
gobject.source_remove(self.disable_initiator_tag)
382
self.disable_initiator_tag = (gobject.timeout_add
383
(self.timeout_milliseconds(),
386
def start_checker(self):
387
"""Start a new checker subprocess if one is not running.
389
If a checker already exists, leave it running and do
391
# The reason for not killing a running checker is that if we
392
# did that, then if a checker (for some reason) started
393
# running slowly and taking more than 'interval' time, the
394
# client would inevitably timeout, since no checker would get
395
# a chance to run to completion. If we instead leave running
396
# checkers alone, the checker would have to take more time
397
# than 'timeout' for the client to be declared invalid, which
398
# is as it should be.
400
# If a checker exists, make sure it is not a zombie
402
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
403
except (AttributeError, OSError), error:
404
if (isinstance(error, OSError)
405
and error.errno != errno.ECHILD):
409
logger.warning(u"Checker was a zombie")
410
gobject.source_remove(self.checker_callback_tag)
411
self.checker_callback(pid, status,
412
self.current_checker_command)
413
# Start a new checker if needed
414
if self.checker is None:
416
# In case checker_command has exactly one % operator
417
command = self.checker_command % self.host
419
# Escape attributes for the shell
420
escaped_attrs = dict((key,
421
re.escape(unicode(str(val),
425
vars(self).iteritems())
427
command = self.checker_command % escaped_attrs
428
except TypeError, error:
429
logger.error(u'Could not format string "%s":'
430
u' %s', self.checker_command, error)
431
return True # Try again later
432
self.current_checker_command = command
434
logger.info(u"Starting checker %r for %s",
436
# We don't need to redirect stdout and stderr, since
437
# in normal mode, that is already done by daemon(),
438
# and in debug mode we don't want to. (Stdin is
439
# always replaced by /dev/null.)
440
self.checker = subprocess.Popen(command,
442
shell=True, cwd=u"/")
443
self.checker_callback_tag = (gobject.child_watch_add
445
self.checker_callback,
447
# The checker may have completed before the gobject
448
# watch was added. Check for this.
449
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
451
gobject.source_remove(self.checker_callback_tag)
452
self.checker_callback(pid, status, command)
453
except OSError, error:
454
logger.error(u"Failed to start subprocess: %s",
456
# Re-run this periodically if run by gobject.timeout_add
459
def stop_checker(self):
460
"""Force the checker process, if any, to stop."""
461
if self.checker_callback_tag:
462
gobject.source_remove(self.checker_callback_tag)
463
self.checker_callback_tag = None
464
if getattr(self, u"checker", None) is None:
466
logger.debug(u"Stopping checker for %(name)s", vars(self))
468
os.kill(self.checker.pid, signal.SIGTERM)
470
#if self.checker.poll() is None:
471
# os.kill(self.checker.pid, signal.SIGKILL)
472
except OSError, error:
473
if error.errno != errno.ESRCH: # No such process
477
def still_valid(self):
478
"""Has the timeout not yet passed for this client?"""
479
if not getattr(self, u"enabled", False):
481
now = datetime.datetime.utcnow()
482
if self.last_checked_ok is None:
483
return now < (self.created + self.timeout)
485
return now < (self.last_checked_ok + self.timeout)
488
def dbus_service_property(dbus_interface, signature=u"v",
489
access=u"readwrite", byte_arrays=False):
490
"""Decorators for marking methods of a DBusObjectWithProperties to
491
become properties on the D-Bus.
493
The decorated method will be called with no arguments by "Get"
494
and with one argument by "Set".
496
The parameters, where they are supported, are the same as
497
dbus.service.method, except there is only "signature", since the
498
type from Get() and the type sent to Set() is the same.
501
func._dbus_is_property = True
502
func._dbus_interface = dbus_interface
503
func._dbus_signature = signature
504
func._dbus_access = access
505
func._dbus_name = func.__name__
506
if func._dbus_name.endswith(u"_dbus_property"):
507
func._dbus_name = func._dbus_name[:-14]
508
func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
513
class DBusPropertyException(dbus.exceptions.DBusException):
514
"""A base class for D-Bus property-related exceptions
516
def __unicode__(self):
517
return unicode(str(self))
520
class DBusPropertyAccessException(DBusPropertyException):
521
"""A property's access permissions disallows an operation.
526
class DBusPropertyNotFound(DBusPropertyException):
527
"""An attempt was made to access a non-existing property.
532
class DBusObjectWithProperties(dbus.service.Object):
533
"""A D-Bus object with properties.
535
Classes inheriting from this can use the dbus_service_property
536
decorator to expose methods as D-Bus properties. It exposes the
537
standard Get(), Set(), and GetAll() methods on the D-Bus.
541
def _is_dbus_property(obj):
542
return getattr(obj, u"_dbus_is_property", False)
544
def _get_all_dbus_properties(self):
545
"""Returns a generator of (name, attribute) pairs
547
return ((prop._dbus_name, prop)
549
inspect.getmembers(self, self._is_dbus_property))
551
def _get_dbus_property(self, interface_name, property_name):
552
"""Returns a bound method if one exists which is a D-Bus
553
property with the specified name and interface.
555
for name in (property_name,
556
property_name + u"_dbus_property"):
557
prop = getattr(self, name, None)
559
or not self._is_dbus_property(prop)
560
or prop._dbus_name != property_name
561
or (interface_name and prop._dbus_interface
562
and interface_name != prop._dbus_interface)):
566
raise DBusPropertyNotFound(self.dbus_object_path + u":"
567
+ interface_name + u"."
570
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
572
def Get(self, interface_name, property_name):
573
"""Standard D-Bus property Get() method, see D-Bus standard.
575
prop = self._get_dbus_property(interface_name, property_name)
576
if prop._dbus_access == u"write":
577
raise DBusPropertyAccessException(property_name)
579
if not hasattr(value, u"variant_level"):
581
return type(value)(value, variant_level=value.variant_level+1)
583
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
584
def Set(self, interface_name, property_name, value):
585
"""Standard D-Bus property Set() method, see D-Bus standard.
587
prop = self._get_dbus_property(interface_name, property_name)
588
if prop._dbus_access == u"read":
589
raise DBusPropertyAccessException(property_name)
590
if prop._dbus_get_args_options[u"byte_arrays"]:
591
value = dbus.ByteArray(''.join(unichr(byte)
595
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
596
out_signature=u"a{sv}")
597
def GetAll(self, interface_name):
598
"""Standard D-Bus property GetAll() method, see D-Bus
601
Note: Will not include properties with access="write".
604
for name, prop in self._get_all_dbus_properties():
606
and interface_name != prop._dbus_interface):
607
# Interface non-empty but did not match
609
# Ignore write-only properties
610
if prop._dbus_access == u"write":
613
if not hasattr(value, u"variant_level"):
616
all[name] = type(value)(value, variant_level=
617
value.variant_level+1)
618
return dbus.Dictionary(all, signature=u"sv")
620
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
622
path_keyword='object_path',
623
connection_keyword='connection')
624
def Introspect(self, object_path, connection):
625
"""Standard D-Bus method, overloaded to insert property tags.
627
xmlstring = dbus.service.Object.Introspect(self, object_path,
629
document = xml.dom.minidom.parseString(xmlstring)
631
def make_tag(document, name, prop):
632
e = document.createElement(u"property")
633
e.setAttribute(u"name", name)
634
e.setAttribute(u"type", prop._dbus_signature)
635
e.setAttribute(u"access", prop._dbus_access)
637
for if_tag in document.getElementsByTagName(u"interface"):
638
for tag in (make_tag(document, name, prop)
640
in self._get_all_dbus_properties()
641
if prop._dbus_interface
642
== if_tag.getAttribute(u"name")):
643
if_tag.appendChild(tag)
644
xmlstring = document.toxml(u"utf-8")
649
class ClientDBus(Client, DBusObjectWithProperties):
650
"""A Client class using D-Bus
653
dbus_object_path: dbus.ObjectPath
654
bus: dbus.SystemBus()
656
# dbus.service.Object doesn't use super(), so we can't either.
658
def __init__(self, bus = None, *args, **kwargs):
660
Client.__init__(self, *args, **kwargs)
661
# Only now, when this client is initialized, can it show up on
663
self.dbus_object_path = (dbus.ObjectPath
665
+ self.name.replace(u".", u"_")))
666
DBusObjectWithProperties.__init__(self, self.bus,
667
self.dbus_object_path)
670
def _datetime_to_dbus(dt, variant_level=0):
671
"""Convert a UTC datetime.datetime() to a D-Bus type."""
672
return dbus.String(dt.isoformat(),
673
variant_level=variant_level)
676
oldstate = getattr(self, u"enabled", False)
677
r = Client.enable(self)
678
if oldstate != self.enabled:
680
self.PropertyChanged(dbus.String(u"enabled"),
681
dbus.Boolean(True, variant_level=1))
682
self.PropertyChanged(
683
dbus.String(u"last_enabled"),
684
self._datetime_to_dbus(self.last_enabled,
688
def disable(self, signal = True):
689
oldstate = getattr(self, u"enabled", False)
690
r = Client.disable(self)
691
if signal and oldstate != self.enabled:
693
self.PropertyChanged(dbus.String(u"enabled"),
694
dbus.Boolean(False, variant_level=1))
697
def __del__(self, *args, **kwargs):
699
self.remove_from_connection()
702
if hasattr(DBusObjectWithProperties, u"__del__"):
703
DBusObjectWithProperties.__del__(self, *args, **kwargs)
704
Client.__del__(self, *args, **kwargs)
706
def checker_callback(self, pid, condition, command,
708
self.checker_callback_tag = None
711
self.PropertyChanged(dbus.String(u"checker_running"),
712
dbus.Boolean(False, variant_level=1))
713
if os.WIFEXITED(condition):
714
exitstatus = os.WEXITSTATUS(condition)
716
self.CheckerCompleted(dbus.Int16(exitstatus),
717
dbus.Int64(condition),
718
dbus.String(command))
721
self.CheckerCompleted(dbus.Int16(-1),
722
dbus.Int64(condition),
723
dbus.String(command))
725
return Client.checker_callback(self, pid, condition, command,
728
def checked_ok(self, *args, **kwargs):
729
r = Client.checked_ok(self, *args, **kwargs)
731
self.PropertyChanged(
732
dbus.String(u"last_checked_ok"),
733
(self._datetime_to_dbus(self.last_checked_ok,
737
def start_checker(self, *args, **kwargs):
738
old_checker = self.checker
739
if self.checker is not None:
740
old_checker_pid = self.checker.pid
742
old_checker_pid = None
743
r = Client.start_checker(self, *args, **kwargs)
744
# Only if new checker process was started
745
if (self.checker is not None
746
and old_checker_pid != self.checker.pid):
748
self.CheckerStarted(self.current_checker_command)
749
self.PropertyChanged(
750
dbus.String(u"checker_running"),
751
dbus.Boolean(True, variant_level=1))
754
def stop_checker(self, *args, **kwargs):
755
old_checker = getattr(self, u"checker", None)
756
r = Client.stop_checker(self, *args, **kwargs)
757
if (old_checker is not None
758
and getattr(self, u"checker", None) is None):
759
self.PropertyChanged(dbus.String(u"checker_running"),
760
dbus.Boolean(False, variant_level=1))
763
## D-Bus methods & signals
764
_interface = u"se.bsnet.fukt.Mandos.Client"
767
@dbus.service.method(_interface)
769
return self.checked_ok()
771
# CheckerCompleted - signal
772
@dbus.service.signal(_interface, signature=u"nxs")
773
def CheckerCompleted(self, exitcode, waitstatus, command):
777
# CheckerStarted - signal
778
@dbus.service.signal(_interface, signature=u"s")
779
def CheckerStarted(self, command):
783
# PropertyChanged - signal
784
@dbus.service.signal(_interface, signature=u"sv")
785
def PropertyChanged(self, property, value):
789
# ReceivedSecret - signal
790
@dbus.service.signal(_interface)
791
def ReceivedSecret(self):
796
@dbus.service.signal(_interface)
802
@dbus.service.method(_interface)
807
# StartChecker - method
808
@dbus.service.method(_interface)
809
def StartChecker(self):
814
@dbus.service.method(_interface)
819
# StopChecker - method
820
@dbus.service.method(_interface)
821
def StopChecker(self):
825
@dbus_service_property(_interface, signature=u"s", access=u"read")
826
def name_dbus_property(self):
827
return dbus.String(self.name)
829
# fingerprint - property
830
@dbus_service_property(_interface, signature=u"s", access=u"read")
831
def fingerprint_dbus_property(self):
832
return dbus.String(self.fingerprint)
835
@dbus_service_property(_interface, signature=u"s",
837
def host_dbus_property(self, value=None):
838
if value is None: # get
839
return dbus.String(self.host)
842
self.PropertyChanged(dbus.String(u"host"),
843
dbus.String(value, variant_level=1))
846
@dbus_service_property(_interface, signature=u"s", access=u"read")
847
def created_dbus_property(self):
848
return dbus.String(self._datetime_to_dbus(self.created))
850
# last_enabled - property
851
@dbus_service_property(_interface, signature=u"s", access=u"read")
852
def last_enabled_dbus_property(self):
853
if self.last_enabled is None:
854
return dbus.String(u"")
855
return dbus.String(self._datetime_to_dbus(self.last_enabled))
858
@dbus_service_property(_interface, signature=u"b",
860
def enabled_dbus_property(self, value=None):
861
if value is None: # get
862
return dbus.Boolean(self.enabled)
868
# last_checked_ok - property
869
@dbus_service_property(_interface, signature=u"s",
871
def last_checked_ok_dbus_property(self, value=None):
872
if value is not None:
875
if self.last_checked_ok is None:
876
return dbus.String(u"")
877
return dbus.String(self._datetime_to_dbus(self
881
@dbus_service_property(_interface, signature=u"t",
883
def timeout_dbus_property(self, value=None):
884
if value is None: # get
885
return dbus.UInt64(self.timeout_milliseconds())
886
self.timeout = datetime.timedelta(0, 0, 0, value)
888
self.PropertyChanged(dbus.String(u"timeout"),
889
dbus.UInt64(value, variant_level=1))
890
if getattr(self, u"disable_initiator_tag", None) is None:
893
gobject.source_remove(self.disable_initiator_tag)
894
self.disable_initiator_tag = None
896
_timedelta_to_milliseconds((self
902
# The timeout has passed
905
self.disable_initiator_tag = (gobject.timeout_add
906
(time_to_die, self.disable))
908
# interval - property
909
@dbus_service_property(_interface, signature=u"t",
911
def interval_dbus_property(self, value=None):
912
if value is None: # get
913
return dbus.UInt64(self.interval_milliseconds())
914
self.interval = datetime.timedelta(0, 0, 0, value)
916
self.PropertyChanged(dbus.String(u"interval"),
917
dbus.UInt64(value, variant_level=1))
918
if getattr(self, u"checker_initiator_tag", None) is None:
920
# Reschedule checker run
921
gobject.source_remove(self.checker_initiator_tag)
922
self.checker_initiator_tag = (gobject.timeout_add
923
(value, self.start_checker))
924
self.start_checker() # Start one now, too
927
@dbus_service_property(_interface, signature=u"s",
929
def checker_dbus_property(self, value=None):
930
if value is None: # get
931
return dbus.String(self.checker_command)
932
self.checker_command = value
934
self.PropertyChanged(dbus.String(u"checker"),
935
dbus.String(self.checker_command,
938
# checker_running - property
939
@dbus_service_property(_interface, signature=u"b",
941
def checker_running_dbus_property(self, value=None):
942
if value is None: # get
943
return dbus.Boolean(self.checker is not None)
949
# object_path - property
950
@dbus_service_property(_interface, signature=u"o", access=u"read")
951
def object_path_dbus_property(self):
952
return self.dbus_object_path # is already a dbus.ObjectPath
955
@dbus_service_property(_interface, signature=u"ay",
956
access=u"write", byte_arrays=True)
957
def secret_dbus_property(self, value):
958
self.secret = str(value)
963
class ClientHandler(socketserver.BaseRequestHandler, object):
964
"""A class to handle client connections.
966
Instantiated once for each connection to handle it.
967
Note: This will run in its own forked process."""
970
logger.info(u"TCP connection from: %s",
971
unicode(self.client_address))
972
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
973
# Open IPC pipe to parent process
974
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
975
session = (gnutls.connection
976
.ClientSession(self.request,
980
line = self.request.makefile().readline()
981
logger.debug(u"Protocol version: %r", line)
983
if int(line.strip().split()[0]) > 1:
985
except (ValueError, IndexError, RuntimeError), error:
986
logger.error(u"Unknown protocol version: %s", error)
989
# Note: gnutls.connection.X509Credentials is really a
990
# generic GnuTLS certificate credentials object so long as
991
# no X.509 keys are added to it. Therefore, we can use it
992
# here despite using OpenPGP certificates.
994
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
995
# u"+AES-256-CBC", u"+SHA1",
996
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
998
# Use a fallback default, since this MUST be set.
999
priority = self.server.gnutls_priority
1000
if priority is None:
1001
priority = u"NORMAL"
1002
(gnutls.library.functions
1003
.gnutls_priority_set_direct(session._c_object,
1008
except gnutls.errors.GNUTLSError, error:
1009
logger.warning(u"Handshake failed: %s", error)
1010
# Do not run session.bye() here: the session is not
1011
# established. Just abandon the request.
1013
logger.debug(u"Handshake succeeded")
1015
fpr = self.fingerprint(self.peer_certificate(session))
1016
except (TypeError, gnutls.errors.GNUTLSError), error:
1017
logger.warning(u"Bad certificate: %s", error)
1020
logger.debug(u"Fingerprint: %s", fpr)
1022
for c in self.server.clients:
1023
if c.fingerprint == fpr:
1027
ipc.write(u"NOTFOUND %s %s\n"
1028
% (fpr, unicode(self.client_address)))
1031
# Have to check if client.still_valid(), since it is
1032
# possible that the client timed out while establishing
1033
# the GnuTLS session.
1034
if not client.still_valid():
1035
ipc.write(u"INVALID %s\n" % client.name)
1038
ipc.write(u"SENDING %s\n" % client.name)
1040
while sent_size < len(client.secret):
1041
sent = session.send(client.secret[sent_size:])
1042
logger.debug(u"Sent: %d, remaining: %d",
1043
sent, len(client.secret)
1044
- (sent_size + sent))
1049
def peer_certificate(session):
1050
"Return the peer's OpenPGP certificate as a bytestring"
1051
# If not an OpenPGP certificate...
1052
if (gnutls.library.functions
1053
.gnutls_certificate_type_get(session._c_object)
1054
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1055
# ...do the normal thing
1056
return session.peer_certificate
1057
list_size = ctypes.c_uint(1)
1058
cert_list = (gnutls.library.functions
1059
.gnutls_certificate_get_peers
1060
(session._c_object, ctypes.byref(list_size)))
1061
if not bool(cert_list) and list_size.value != 0:
1062
raise gnutls.errors.GNUTLSError(u"error getting peer"
1064
if list_size.value == 0:
1067
return ctypes.string_at(cert.data, cert.size)
1070
def fingerprint(openpgp):
1071
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1072
# New GnuTLS "datum" with the OpenPGP public key
1073
datum = (gnutls.library.types
1074
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1077
ctypes.c_uint(len(openpgp))))
1078
# New empty GnuTLS certificate
1079
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1080
(gnutls.library.functions
1081
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1082
# Import the OpenPGP public key into the certificate
1083
(gnutls.library.functions
1084
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1085
gnutls.library.constants
1086
.GNUTLS_OPENPGP_FMT_RAW))
1087
# Verify the self signature in the key
1088
crtverify = ctypes.c_uint()
1089
(gnutls.library.functions
1090
.gnutls_openpgp_crt_verify_self(crt, 0,
1091
ctypes.byref(crtverify)))
1092
if crtverify.value != 0:
1093
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1094
raise (gnutls.errors.CertificateSecurityError
1096
# New buffer for the fingerprint
1097
buf = ctypes.create_string_buffer(20)
1098
buf_len = ctypes.c_size_t()
1099
# Get the fingerprint from the certificate into the buffer
1100
(gnutls.library.functions
1101
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1102
ctypes.byref(buf_len)))
1103
# Deinit the certificate
1104
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1105
# Convert the buffer to a Python bytestring
1106
fpr = ctypes.string_at(buf, buf_len.value)
1107
# Convert the bytestring to hexadecimal notation
1108
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1112
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
1113
"""Like socketserver.ForkingMixIn, but also pass a pipe."""
1114
def process_request(self, request, client_address):
1115
"""Overrides and wraps the original process_request().
1117
This function creates a new pipe in self.pipe
1119
self.pipe = os.pipe()
1120
super(ForkingMixInWithPipe,
1121
self).process_request(request, client_address)
1122
os.close(self.pipe[1]) # close write end
1123
self.add_pipe(self.pipe[0])
1124
def add_pipe(self, pipe):
1125
"""Dummy function; override as necessary"""
1129
class IPv6_TCPServer(ForkingMixInWithPipe,
1130
socketserver.TCPServer, object):
1131
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
1134
enabled: Boolean; whether this server is activated yet
1135
interface: None or a network interface name (string)
1136
use_ipv6: Boolean; to use IPv6 or not
1138
def __init__(self, server_address, RequestHandlerClass,
1139
interface=None, use_ipv6=True):
1140
self.interface = interface
1142
self.address_family = socket.AF_INET6
1143
socketserver.TCPServer.__init__(self, server_address,
1144
RequestHandlerClass)
1145
def server_bind(self):
1146
"""This overrides the normal server_bind() function
1147
to bind to an interface if one was specified, and also NOT to
1148
bind to an address or port if they were not specified."""
1149
if self.interface is not None:
1150
if SO_BINDTODEVICE is None:
1151
logger.error(u"SO_BINDTODEVICE does not exist;"
1152
u" cannot bind to interface %s",
23
self.password = password
25
self.password = open(passfile).readall()
27
print "No Password or Passfile in client config file"
28
# raise RuntimeError XXX
29
self.password = "gazonk"
31
self.created = datetime.datetime.now()
34
timeout = options.timeout
35
self.timeout = timeout
37
interval = options.interval
38
self.interval = interval
39
self.next_check = datetime.datetime.now()
42
class server_metaclass(type):
43
"Common behavior for the UDP and TCP server classes"
44
def __new__(cls, name, bases, attrs):
45
attrs["address_family"] = socket.AF_INET6
46
attrs["allow_reuse_address"] = True
47
def server_bind(self):
48
if self.options.interface:
49
if not hasattr(socket, "SO_BINDTODEVICE"):
50
# From /usr/include/asm-i486/socket.h
51
socket.SO_BINDTODEVICE = 25
1156
53
self.socket.setsockopt(socket.SOL_SOCKET,
54
socket.SO_BINDTODEVICE,
55
self.options.interface)
1160
56
except socket.error, error:
1161
57
if error[0] == errno.EPERM:
1162
logger.error(u"No permission to"
1163
u" bind to interface %s",
1165
elif error[0] == errno.ENOPROTOOPT:
1166
logger.error(u"SO_BINDTODEVICE not available;"
1167
u" cannot bind to interface %s",
58
print "Warning: No permission to bind to interface", \
59
self.options.interface
1171
# Only bind(2) the socket if we really need to.
1172
if self.server_address[0] or self.server_address[1]:
1173
if not self.server_address[0]:
1174
if self.address_family == socket.AF_INET6:
1175
any_address = u"::" # in6addr_any
1177
any_address = socket.INADDR_ANY
1178
self.server_address = (any_address,
1179
self.server_address[1])
1180
elif not self.server_address[1]:
1181
self.server_address = (self.server_address[0],
1183
# if self.interface:
1184
# self.server_address = (self.server_address[0],
1189
return socketserver.TCPServer.server_bind(self)
1192
class MandosServer(IPv6_TCPServer):
1196
clients: set of Client objects
1197
gnutls_priority GnuTLS priority string
1198
use_dbus: Boolean; to emit D-Bus signals or not
1200
Assumes a gobject.MainLoop event loop.
1202
def __init__(self, server_address, RequestHandlerClass,
1203
interface=None, use_ipv6=True, clients=None,
1204
gnutls_priority=None, use_dbus=True):
1205
self.enabled = False
1206
self.clients = clients
1207
if self.clients is None:
1208
self.clients = set()
1209
self.use_dbus = use_dbus
1210
self.gnutls_priority = gnutls_priority
1211
IPv6_TCPServer.__init__(self, server_address,
1212
RequestHandlerClass,
1213
interface = interface,
1214
use_ipv6 = use_ipv6)
1215
def server_activate(self):
1217
return socketserver.TCPServer.server_activate(self)
1220
def add_pipe(self, pipe):
1221
# Call "handle_ipc" for both data and EOF events
1222
gobject.io_add_watch(pipe, gobject.IO_IN | gobject.IO_HUP,
1224
def handle_ipc(self, source, condition, file_objects={}):
1226
gobject.IO_IN: u"IN", # There is data to read.
1227
gobject.IO_OUT: u"OUT", # Data can be written (without
1229
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1230
gobject.IO_ERR: u"ERR", # Error condition.
1231
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1232
# broken, usually for pipes and
1235
conditions_string = ' | '.join(name
1237
condition_names.iteritems()
1238
if cond & condition)
1239
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1242
# Turn the pipe file descriptor into a Python file object
1243
if source not in file_objects:
1244
file_objects[source] = os.fdopen(source, u"r", 1)
1246
# Read a line from the file object
1247
cmdline = file_objects[source].readline()
1248
if not cmdline: # Empty line means end of file
1249
# close the IPC pipe
1250
file_objects[source].close()
1251
del file_objects[source]
1253
# Stop calling this function
1256
logger.debug(u"IPC command: %r", cmdline)
1258
# Parse and act on command
1259
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1261
if cmd == u"NOTFOUND":
1262
logger.warning(u"Client not found for fingerprint: %s",
1266
mandos_dbus_service.ClientNotFound(args)
1267
elif cmd == u"INVALID":
1268
for client in self.clients:
1269
if client.name == args:
1270
logger.warning(u"Client %s is invalid", args)
1276
logger.error(u"Unknown client %s is invalid", args)
1277
elif cmd == u"SENDING":
1278
for client in self.clients:
1279
if client.name == args:
1280
logger.info(u"Sending secret to %s", client.name)
1284
client.ReceivedSecret()
1287
logger.error(u"Sending secret to unknown client %s",
1290
logger.error(u"Unknown IPC command: %r", cmdline)
1292
# Keep calling this function
62
return super(type(self), self).server_bind()
63
attrs["server_bind"] = server_bind
64
def init(self, *args, **kwargs):
65
if "options" in kwargs:
66
self.options = kwargs["options"]
68
if "clients" in kwargs:
69
self.clients = kwargs["clients"]
71
if "credentials" in kwargs:
72
self.credentials = kwargs["credentials"]
73
del kwargs["credentials"]
74
return super(type(self), self).__init__(*args, **kwargs)
75
attrs["__init__"] = init
76
return type.__new__(cls, name, bases, attrs)
79
class udp_handler(SocketServer.DatagramRequestHandler, object):
81
self.wfile.write("Polo")
82
print "UDP request answered"
85
class IPv6_UDPServer(SocketServer.UDPServer, object):
86
__metaclass__ = server_metaclass
87
def verify_request(self, request, client_address):
88
print "UDP request came"
89
return request[0] == "Marco"
92
class tcp_handler(SocketServer.BaseRequestHandler, object):
94
print "TCP request came"
95
print "Request:", self.request
96
print "Client Address:", self.client_address
97
print "Server:", self.server
98
session = gnutls.connection.ServerSession(self.request,
99
self.server.credentials)
101
if session.peer_certificate:
102
print "DN:", session.peer_certificate.subject
104
session.verify_peer()
105
except gnutls.errors.CertificateError, error:
106
print "Verify failed", error
110
session.send(dict((client.dn, client.password)
111
for client in self.server.clients)
112
[session.peer_certificate.subject])
114
session.send("gazonk")
119
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
120
__metaclass__ = server_metaclass
121
request_queue_size = 1024
1296
128
def string_to_delta(interval):
1297
129
"""Parse a string and return a datetime.timedelta
1299
>>> string_to_delta(u'7d')
131
>>> string_to_delta('7d')
1300
132
datetime.timedelta(7)
1301
>>> string_to_delta(u'60s')
133
>>> string_to_delta('60s')
1302
134
datetime.timedelta(0, 60)
1303
>>> string_to_delta(u'60m')
135
>>> string_to_delta('60m')
1304
136
datetime.timedelta(0, 3600)
1305
>>> string_to_delta(u'24h')
137
>>> string_to_delta('24h')
1306
138
datetime.timedelta(1)
1307
139
>>> string_to_delta(u'1w')
1308
140
datetime.timedelta(7)
1309
>>> string_to_delta(u'5m 30s')
1310
datetime.timedelta(0, 330)
1312
timevalue = datetime.timedelta(0)
1313
for s in interval.split():
1315
suffix = unicode(s[-1])
1318
delta = datetime.timedelta(value)
1319
elif suffix == u"s":
1320
delta = datetime.timedelta(0, value)
1321
elif suffix == u"m":
1322
delta = datetime.timedelta(0, 0, 0, 0, value)
1323
elif suffix == u"h":
1324
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1325
elif suffix == u"w":
1326
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1329
except (ValueError, IndexError):
143
suffix=unicode(interval[-1])
144
value=int(interval[:-1])
146
delta = datetime.timedelta(value)
148
delta = datetime.timedelta(0, value)
150
delta = datetime.timedelta(0, 0, 0, 0, value)
152
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
154
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1330
156
raise ValueError
1335
def if_nametoindex(interface):
1336
"""Call the C function if_nametoindex(), or equivalent
1338
Note: This function cannot accept a unicode string."""
1339
global if_nametoindex
1341
if_nametoindex = (ctypes.cdll.LoadLibrary
1342
(ctypes.util.find_library(u"c"))
1344
except (OSError, AttributeError):
1345
logger.warning(u"Doing if_nametoindex the hard way")
1346
def if_nametoindex(interface):
1347
"Get an interface index the hard way, i.e. using fcntl()"
1348
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1349
with closing(socket.socket()) as s:
1350
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1351
struct.pack(str(u"16s16x"),
1353
interface_index = struct.unpack(str(u"I"),
1355
return interface_index
1356
return if_nametoindex(interface)
1359
def daemon(nochdir = False, noclose = False):
1360
"""See daemon(3). Standard BSD Unix function.
1362
This should really exist as os.daemon, but it doesn't (yet)."""
1371
# Close all standard open file descriptors
1372
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1373
if not stat.S_ISCHR(os.fstat(null).st_mode):
1374
raise OSError(errno.ENODEV,
1375
u"/dev/null not a character device")
1376
os.dup2(null, sys.stdin.fileno())
1377
os.dup2(null, sys.stdout.fileno())
1378
os.dup2(null, sys.stderr.fileno())
157
except (ValueError, IndexError):
1385
##################################################################
1386
# Parsing of options, both command line and config file
1388
parser = optparse.OptionParser(version = "%%prog %s" % version)
1389
parser.add_option("-i", u"--interface", type=u"string",
1390
metavar="IF", help=u"Bind to interface IF")
1391
parser.add_option("-a", u"--address", type=u"string",
1392
help=u"Address to listen for requests on")
1393
parser.add_option("-p", u"--port", type=u"int",
1394
help=u"Port number to receive requests on")
1395
parser.add_option("--check", action=u"store_true",
1396
help=u"Run self-test")
1397
parser.add_option("--debug", action=u"store_true",
1398
help=u"Debug mode; run in foreground and log to"
1400
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1401
u" priority string (see GnuTLS documentation)")
1402
parser.add_option("--servicename", type=u"string",
1403
metavar=u"NAME", help=u"Zeroconf service name")
1404
parser.add_option("--configdir", type=u"string",
1405
default=u"/etc/mandos", metavar=u"DIR",
1406
help=u"Directory to search for configuration"
1408
parser.add_option("--no-dbus", action=u"store_false",
1409
dest=u"use_dbus", help=u"Do not provide D-Bus"
1410
u" system bus interface")
1411
parser.add_option("--no-ipv6", action=u"store_false",
1412
dest=u"use_ipv6", help=u"Do not use IPv6")
1413
options = parser.parse_args()[0]
162
parser = OptionParser()
163
parser.add_option("-i", "--interface", type="string",
164
default="eth0", metavar="IF",
165
help="Interface to bind to")
166
parser.add_option("--cert", type="string", default="cert.pem",
168
help="Public key certificate to use")
169
parser.add_option("--key", type="string", default="key.pem",
171
help="Private key to use")
172
parser.add_option("--ca", type="string", default="ca.pem",
174
help="Certificate Authority certificate to use")
175
parser.add_option("--crl", type="string", default="crl.pem",
177
help="Certificate Revokation List to use")
178
parser.add_option("-p", "--port", type="int", default=49001,
179
help="Port number to receive requests on")
180
parser.add_option("--dh", type="int", metavar="BITS",
181
help="DH group to use")
182
parser.add_option("-t", "--timeout", type="string", # Parsed later
184
help="Amount of downtime allowed for clients")
185
parser.add_option("--interval", type="string", # Parsed later
187
help="How often to check that a client is up")
188
parser.add_option("--check", action="store_true", default=False,
189
help="Run self-test")
190
(options, args) = parser.parse_args()
1415
192
if options.check:
1417
194
doctest.testmod()
1420
# Default values for config file for server-global settings
1421
server_defaults = { u"interface": u"",
1426
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1427
u"servicename": u"Mandos",
1428
u"use_dbus": u"True",
1429
u"use_ipv6": u"True",
1432
# Parse config file for server-global settings
1433
server_config = configparser.SafeConfigParser(server_defaults)
1435
server_config.read(os.path.join(options.configdir,
1437
# Convert the SafeConfigParser object to a dict
1438
server_settings = server_config.defaults()
1439
# Use the appropriate methods on the non-string config options
1440
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1441
server_settings[option] = server_config.getboolean(u"DEFAULT",
1443
if server_settings["port"]:
1444
server_settings["port"] = server_config.getint(u"DEFAULT",
1448
# Override the settings from the config file with command line
1450
for option in (u"interface", u"address", u"port", u"debug",
1451
u"priority", u"servicename", u"configdir",
1452
u"use_dbus", u"use_ipv6"):
1453
value = getattr(options, option)
1454
if value is not None:
1455
server_settings[option] = value
1457
# Force all strings to be unicode
1458
for option in server_settings.keys():
1459
if type(server_settings[option]) is str:
1460
server_settings[option] = unicode(server_settings[option])
1461
# Now we have our good server settings in "server_settings"
1463
##################################################################
1466
debug = server_settings[u"debug"]
1467
use_dbus = server_settings[u"use_dbus"]
1468
use_ipv6 = server_settings[u"use_ipv6"]
1471
syslogger.setLevel(logging.WARNING)
1472
console.setLevel(logging.WARNING)
1474
if server_settings[u"servicename"] != u"Mandos":
1475
syslogger.setFormatter(logging.Formatter
1476
(u'Mandos (%s) [%%(process)d]:'
1477
u' %%(levelname)s: %%(message)s'
1478
% server_settings[u"servicename"]))
1480
# Parse config file with clients
1481
client_defaults = { u"timeout": u"1h",
1483
u"checker": u"fping -q -- %%(host)s",
1486
client_config = configparser.SafeConfigParser(client_defaults)
1487
client_config.read(os.path.join(server_settings[u"configdir"],
1490
global mandos_dbus_service
1491
mandos_dbus_service = None
1493
tcp_server = MandosServer((server_settings[u"address"],
1494
server_settings[u"port"]),
1496
interface=server_settings[u"interface"],
1499
server_settings[u"priority"],
1501
pidfilename = u"/var/run/mandos.pid"
1503
pidfile = open(pidfilename, u"w")
1505
logger.error(u"Could not open file %r", pidfilename)
1508
uid = pwd.getpwnam(u"_mandos").pw_uid
1509
gid = pwd.getpwnam(u"_mandos").pw_gid
1512
uid = pwd.getpwnam(u"mandos").pw_uid
1513
gid = pwd.getpwnam(u"mandos").pw_gid
1516
uid = pwd.getpwnam(u"nobody").pw_uid
1517
gid = pwd.getpwnam(u"nobody").pw_gid
1524
except OSError, error:
1525
if error[0] != errno.EPERM:
1528
# Enable all possible GnuTLS debugging
1530
# "Use a log level over 10 to enable all debugging options."
1532
gnutls.library.functions.gnutls_global_set_log_level(11)
1534
@gnutls.library.types.gnutls_log_func
1535
def debug_gnutls(level, string):
1536
logger.debug(u"GnuTLS: %s", string[:-1])
1538
(gnutls.library.functions
1539
.gnutls_global_set_log_function(debug_gnutls))
1542
# From the Avahi example code
1543
DBusGMainLoop(set_as_default=True )
1544
main_loop = gobject.MainLoop()
1545
bus = dbus.SystemBus()
1546
# End of Avahi example code
1548
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1549
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1550
service = AvahiService(name = server_settings[u"servicename"],
1551
servicetype = u"_mandos._tcp",
1552
protocol = protocol, bus = bus)
1553
if server_settings["interface"]:
1554
service.interface = (if_nametoindex
1555
(str(server_settings[u"interface"])))
1557
client_class = Client
1559
client_class = functools.partial(ClientDBus, bus = bus)
1560
tcp_server.clients.update(set(
1561
client_class(name = section,
1562
config= dict(client_config.items(section)))
1563
for section in client_config.sections()))
1564
if not tcp_server.clients:
1565
logger.warning(u"No clients defined")
1568
# Redirect stdin so all checkers get /dev/null
1569
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1570
os.dup2(null, sys.stdin.fileno())
1574
# No console logging
1575
logger.removeHandler(console)
1576
# Close all input and output, do double fork, etc.
1580
with closing(pidfile):
1582
pidfile.write(str(pid) + "\n")
1585
logger.error(u"Could not write to file %r with PID %d",
1588
# "pidfile" was never created
1593
"Cleanup function; run on exit"
1596
while tcp_server.clients:
1597
client = tcp_server.clients.pop()
1598
client.disable_hook = None
1601
atexit.register(cleanup)
1604
signal.signal(signal.SIGINT, signal.SIG_IGN)
1605
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1606
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1609
class MandosDBusService(dbus.service.Object):
1610
"""A D-Bus proxy object"""
1612
dbus.service.Object.__init__(self, bus, u"/")
1613
_interface = u"se.bsnet.fukt.Mandos"
1615
@dbus.service.signal(_interface, signature=u"oa{sv}")
1616
def ClientAdded(self, objpath, properties):
1620
@dbus.service.signal(_interface, signature=u"s")
1621
def ClientNotFound(self, fingerprint):
1625
@dbus.service.signal(_interface, signature=u"os")
1626
def ClientRemoved(self, objpath, name):
1630
@dbus.service.method(_interface, out_signature=u"ao")
1631
def GetAllClients(self):
1633
return dbus.Array(c.dbus_object_path
1634
for c in tcp_server.clients)
1636
@dbus.service.method(_interface,
1637
out_signature=u"a{oa{sv}}")
1638
def GetAllClientsWithProperties(self):
1640
return dbus.Dictionary(
1641
((c.dbus_object_path, c.GetAll(u""))
1642
for c in tcp_server.clients),
1643
signature=u"oa{sv}")
1645
@dbus.service.method(_interface, in_signature=u"o")
1646
def RemoveClient(self, object_path):
1648
for c in tcp_server.clients:
1649
if c.dbus_object_path == object_path:
1650
tcp_server.clients.remove(c)
1651
c.remove_from_connection()
1652
# Don't signal anything except ClientRemoved
1653
c.disable(signal=False)
1655
self.ClientRemoved(object_path, c.name)
1661
mandos_dbus_service = MandosDBusService()
1663
for client in tcp_server.clients:
1666
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1671
tcp_server.server_activate()
1673
# Find out what port we got
1674
service.port = tcp_server.socket.getsockname()[1]
1676
logger.info(u"Now listening on address %r, port %d,"
1677
" flowinfo %d, scope_id %d"
1678
% tcp_server.socket.getsockname())
1680
logger.info(u"Now listening on address %r, port %d"
1681
% tcp_server.socket.getsockname())
1683
#service.interface = tcp_server.socket.getsockname()[3]
1686
# From the Avahi example code
1689
except dbus.exceptions.DBusException, error:
1690
logger.critical(u"DBusException: %s", error)
1692
# End of Avahi example code
1694
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
1695
lambda *args, **kwargs:
1696
(tcp_server.handle_request
1697
(*args[2:], **kwargs) or True))
1699
logger.debug(u"Starting main loop")
1701
except AvahiError, error:
1702
logger.critical(u"AvahiError: %s", error)
1704
except KeyboardInterrupt:
1707
logger.debug(u"Server received KeyboardInterrupt")
1708
logger.debug(u"Server exiting")
1710
if __name__ == '__main__':
197
# Parse the time arguments
199
options.timeout = string_to_delta(options.timeout)
201
parser.error("option --timeout: Unparseable time")
204
options.interval = string_to_delta(options.interval)
206
parser.error("option --interval: Unparseable time")
208
cert = gnutls.crypto.X509Certificate(open(options.cert).read())
209
key = gnutls.crypto.X509PrivateKey(open(options.key).read())
210
ca = gnutls.crypto.X509Certificate(open(options.ca).read())
211
crl = gnutls.crypto.X509CRL(open(options.crl).read())
212
cred = gnutls.connection.X509Credentials(cert, key, [ca], [crl])
216
client_config_object = ConfigParser.SafeConfigParser(defaults)
217
client_config_object.read("mandos-clients.conf")
218
clients = [Client(name=section, options=options,
219
**(dict(client_config_object.items(section))))
220
for section in client_config_object.sections()]
222
udp_server = IPv6_UDPServer((in6addr_any, options.port),
226
tcp_server = IPv6_TCPServer((in6addr_any, options.port),
233
in_, out, err = select.select((udp_server,
236
server.handle_request()
239
if __name__ == "__main__":