44
44
import gnutls.library.functions
45
45
import gnutls.library.constants
46
46
import gnutls.library.types
47
import ConfigParser as configparser
56
57
import logging.handlers
58
from contextlib import closing
67
62
from dbus.mainloop.glib import DBusGMainLoop
72
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
73
except AttributeError:
75
from IN import SO_BINDTODEVICE
77
SO_BINDTODEVICE = None
82
logger = logging.Logger(u'mandos')
83
syslogger = (logging.handlers.SysLogHandler
84
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
85
address = "/dev/log"))
86
syslogger.setFormatter(logging.Formatter
87
(u'Mandos [%(process)d]: %(levelname)s:'
65
# Brief description of the operation of this program:
67
# This server announces itself as a Zeroconf service. Connecting
68
# clients use the TLS protocol, with the unusual quirk that this
69
# server program acts as a TLS "client" while the connecting clients
70
# acts as a TLS "server". The clients (acting as a TLS "server") must
71
# supply an OpenPGP certificate, and the fingerprint of this
72
# certificate is used by this server to look up (in a list read from a
73
# file at start time) which binary blob to give the client. No other
74
# authentication or authorization is done by this server.
77
logger = logging.Logger('mandos')
78
syslogger = logging.handlers.SysLogHandler\
79
(facility = logging.handlers.SysLogHandler.LOG_DAEMON)
80
syslogger.setFormatter(logging.Formatter\
81
('%(levelname)s: %(message)s'))
89
82
logger.addHandler(syslogger)
91
console = logging.StreamHandler()
92
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
95
logger.addHandler(console)
97
class AvahiError(Exception):
98
def __init__(self, value, *args, **kwargs):
100
super(AvahiError, self).__init__(value, *args, **kwargs)
101
def __unicode__(self):
102
return unicode(repr(self.value))
104
class AvahiServiceError(AvahiError):
107
class AvahiGroupError(AvahiError):
111
class AvahiService(object):
112
"""An Avahi (Zeroconf) service.
115
interface: integer; avahi.IF_UNSPEC or an interface index.
116
Used to optionally bind to the specified interface.
117
name: string; Example: u'Mandos'
118
type: string; Example: u'_mandos._tcp'.
119
See <http://www.dns-sd.org/ServiceTypes.html>
120
port: integer; what port to announce
121
TXT: list of strings; TXT record for the service
122
domain: string; Domain to publish on, default to .local if empty.
123
host: string; Host to publish records for, default is localhost
124
max_renames: integer; maximum number of renames
125
rename_count: integer; counter so we only rename after collisions
126
a sensible number of times
127
group: D-Bus Entry Group
129
bus: dbus.SystemBus()
131
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
132
servicetype = None, port = None, TXT = None,
133
domain = u"", host = u"", max_renames = 32768,
134
protocol = avahi.PROTO_UNSPEC, bus = None):
135
self.interface = interface
137
self.type = servicetype
139
self.TXT = TXT if TXT is not None else []
142
self.rename_count = 0
143
self.max_renames = max_renames
144
self.protocol = protocol
145
self.group = None # our entry group
149
"""Derived from the Avahi example code"""
150
if self.rename_count >= self.max_renames:
151
logger.critical(u"No suitable Zeroconf service name found"
152
u" after %i retries, exiting.",
154
raise AvahiServiceError(u"Too many renames")
155
self.name = self.server.GetAlternativeServiceName(self.name)
156
logger.info(u"Changing Zeroconf service name to %r ...",
158
syslogger.setFormatter(logging.Formatter
159
(u'Mandos (%s) [%%(process)d]:'
160
u' %%(levelname)s: %%(message)s'
164
self.rename_count += 1
166
"""Derived from the Avahi example code"""
167
if self.group is not None:
170
"""Derived from the Avahi example code"""
171
if self.group is None:
172
self.group = dbus.Interface(
173
self.bus.get_object(avahi.DBUS_NAME,
174
self.server.EntryGroupNew()),
175
avahi.DBUS_INTERFACE_ENTRY_GROUP)
176
self.group.connect_to_signal('StateChanged',
177
self.entry_group_state_changed)
178
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
179
self.name, self.type)
180
self.group.AddService(
183
dbus.UInt32(0), # flags
184
self.name, self.type,
185
self.domain, self.host,
186
dbus.UInt16(self.port),
187
avahi.string_array_to_txt_array(self.TXT))
189
def entry_group_state_changed(self, state, error):
190
"""Derived from the Avahi example code"""
191
logger.debug(u"Avahi state change: %i", state)
193
if state == avahi.ENTRY_GROUP_ESTABLISHED:
194
logger.debug(u"Zeroconf service established.")
195
elif state == avahi.ENTRY_GROUP_COLLISION:
196
logger.warning(u"Zeroconf service name collision.")
198
elif state == avahi.ENTRY_GROUP_FAILURE:
199
logger.critical(u"Avahi: Error in group state changed %s",
201
raise AvahiGroupError(u"State changed: %s"
204
"""Derived from the Avahi example code"""
205
if self.group is not None:
208
def server_state_changed(self, state):
209
"""Derived from the Avahi example code"""
210
if state == avahi.SERVER_COLLISION:
211
logger.error(u"Zeroconf server name collision")
213
elif state == avahi.SERVER_RUNNING:
216
"""Derived from the Avahi example code"""
217
if self.server is None:
218
self.server = dbus.Interface(
219
self.bus.get_object(avahi.DBUS_NAME,
220
avahi.DBUS_PATH_SERVER),
221
avahi.DBUS_INTERFACE_SERVER)
222
self.server.connect_to_signal(u"StateChanged",
223
self.server_state_changed)
224
self.server_state_changed(self.server.GetState())
85
# This variable is used to optionally bind to a specified interface.
86
# It is a global variable to fit in with the other variables from the
88
serviceInterface = avahi.IF_UNSPEC
89
# From the Avahi example code:
90
serviceName = "Mandos"
91
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
92
servicePort = None # Not known at startup
93
serviceTXT = [] # TXT record for the service
94
domain = "" # Domain to publish on, default to .local
95
host = "" # Host to publish records for, default to localhost
96
group = None #our entry group
97
rename_count = 12 # Counter so we only rename after collisions a
98
# sensible number of times
99
# End of Avahi example code
227
102
class Client(object):
228
103
"""A representation of a client host served by this server.
231
name: string; from the config file, used in log messages and
105
name: string; from the config file, used in log messages
233
106
fingerprint: string (40 or 32 hexadecimal digits); used to
234
107
uniquely identify the client
235
secret: bytestring; sent verbatim (over TLS) to client
236
host: string; available for use by the checker command
237
created: datetime.datetime(); (UTC) object creation
238
last_enabled: datetime.datetime(); (UTC)
240
last_checked_ok: datetime.datetime(); (UTC) or None
241
timeout: datetime.timedelta(); How long from last_checked_ok
242
until this client is invalid
243
interval: datetime.timedelta(); How often to start a new checker
244
disable_hook: If set, called by disable() as disable_hook(self)
245
checker: subprocess.Popen(); a running checker process used
246
to see if the client lives.
247
'None' if no process is running.
108
secret: bytestring; sent verbatim (over TLS) to client
109
fqdn: string (FQDN); available for use by the checker command
110
created: datetime.datetime()
111
last_seen: datetime.datetime() or None if not yet seen
112
timeout: datetime.timedelta(); How long from last_seen until
113
this client is invalid
114
interval: datetime.timedelta(); How often to start a new checker
115
stop_hook: If set, called by stop() as stop_hook(self)
116
checker: subprocess.Popen(); a running checker process used
117
to see if the client lives.
118
Is None if no process is running.
248
119
checker_initiator_tag: a gobject event source tag, or None
249
disable_initiator_tag: - '' -
120
stop_initiator_tag: - '' -
250
121
checker_callback_tag: - '' -
251
122
checker_command: string; External command which is run to check if
252
client lives. %() expansions are done at
123
client lives. %()s expansions are done at
253
124
runtime with vars(self) as dict, so that for
254
125
instance %(name)s can be used in the command.
255
current_checker_command: string; current running checker_command
127
_timeout: Real variable for 'timeout'
128
_interval: Real variable for 'interval'
129
_timeout_milliseconds: Used by gobject.timeout_add()
130
_interval_milliseconds: - '' -
259
def _datetime_to_milliseconds(dt):
260
"Convert a datetime.datetime() to milliseconds"
261
return ((dt.days * 24 * 60 * 60 * 1000)
262
+ (dt.seconds * 1000)
263
+ (dt.microseconds // 1000))
265
def timeout_milliseconds(self):
266
"Return the 'timeout' attribute in milliseconds"
267
return self._datetime_to_milliseconds(self.timeout)
269
def interval_milliseconds(self):
270
"Return the 'interval' attribute in milliseconds"
271
return self._datetime_to_milliseconds(self.interval)
273
def __init__(self, name = None, disable_hook=None, config=None):
274
"""Note: the 'checker' key in 'config' sets the
275
'checker_command' attribute and *not* the 'checker'
132
def _set_timeout(self, timeout):
133
"Setter function for 'timeout' attribute"
134
self._timeout = timeout
135
self._timeout_milliseconds = ((self.timeout.days
136
* 24 * 60 * 60 * 1000)
137
+ (self.timeout.seconds * 1000)
138
+ (self.timeout.microseconds
140
timeout = property(lambda self: self._timeout,
143
def _set_interval(self, interval):
144
"Setter function for 'interval' attribute"
145
self._interval = interval
146
self._interval_milliseconds = ((self.interval.days
147
* 24 * 60 * 60 * 1000)
148
+ (self.interval.seconds
150
+ (self.interval.microseconds
152
interval = property(lambda self: self._interval,
155
def __init__(self, name=None, options=None, stop_hook=None,
156
fingerprint=None, secret=None, secfile=None,
157
fqdn=None, timeout=None, interval=-1, checker=None):
158
"""Note: the 'checker' argument sets the 'checker_command'
159
attribute and not the 'checker' attribute.."""
280
logger.debug(u"Creating client %r", self.name)
281
# Uppercase and remove spaces from fingerprint for later
282
# comparison purposes with return value from the fingerprint()
284
self.fingerprint = (config[u"fingerprint"].upper()
286
logger.debug(u" Fingerprint: %s", self.fingerprint)
287
if u"secret" in config:
288
self.secret = config[u"secret"].decode(u"base64")
289
elif u"secfile" in config:
290
with closing(open(os.path.expanduser
292
(config[u"secfile"])))) as secfile:
293
self.secret = secfile.read()
295
raise TypeError(u"No secret or secfile for client %s"
297
self.host = config.get(u"host", u"")
298
self.created = datetime.datetime.utcnow()
300
self.last_enabled = None
301
self.last_checked_ok = None
302
self.timeout = string_to_delta(config[u"timeout"])
303
self.interval = string_to_delta(config[u"interval"])
304
self.disable_hook = disable_hook
161
# Uppercase and remove spaces from fingerprint
162
# for later comparison purposes with return value of
163
# the fingerprint() function
164
self.fingerprint = fingerprint.upper().replace(u" ", u"")
166
self.secret = secret.decode(u"base64")
169
self.secret = sf.read()
172
raise RuntimeError(u"No secret or secfile for client %s"
174
self.fqdn = fqdn # string
175
self.created = datetime.datetime.now()
176
self.last_seen = None
178
self.timeout = options.timeout
180
self.timeout = string_to_delta(timeout)
182
self.interval = options.interval
184
self.interval = string_to_delta(interval)
185
self.stop_hook = stop_hook
305
186
self.checker = None
306
187
self.checker_initiator_tag = None
307
self.disable_initiator_tag = None
188
self.stop_initiator_tag = None
308
189
self.checker_callback_tag = None
309
self.checker_command = config[u"checker"]
310
self.current_checker_command = None
311
self.last_connect = None
190
self.check_command = checker
314
192
"""Start this client's checker and timeout hooks"""
315
self.last_enabled = datetime.datetime.utcnow()
316
193
# Schedule a new checker to be started an 'interval' from now,
317
194
# and every interval from then on.
318
self.checker_initiator_tag = (gobject.timeout_add
319
(self.interval_milliseconds(),
195
self.checker_initiator_tag = gobject.timeout_add\
196
(self._interval_milliseconds,
321
198
# Also start a new checker *right now*.
322
199
self.start_checker()
323
# Schedule a disable() when 'timeout' has passed
324
self.disable_initiator_tag = (gobject.timeout_add
325
(self.timeout_milliseconds(),
330
"""Disable this client."""
331
if not getattr(self, "enabled", False):
200
# Schedule a stop() when 'timeout' has passed
201
self.stop_initiator_tag = gobject.timeout_add\
202
(self._timeout_milliseconds,
206
The possibility that this client might be restarted is left
207
open, but not currently used."""
208
# If this client doesn't have a secret, it is already stopped.
210
logger.debug(u"Stopping client %s", self.name)
333
logger.info(u"Disabling client %s", self.name)
334
if getattr(self, u"disable_initiator_tag", False):
335
gobject.source_remove(self.disable_initiator_tag)
336
self.disable_initiator_tag = None
337
if getattr(self, u"checker_initiator_tag", False):
214
if hasattr(self, "stop_initiator_tag") \
215
and self.stop_initiator_tag:
216
gobject.source_remove(self.stop_initiator_tag)
217
self.stop_initiator_tag = None
218
if hasattr(self, "checker_initiator_tag") \
219
and self.checker_initiator_tag:
338
220
gobject.source_remove(self.checker_initiator_tag)
339
221
self.checker_initiator_tag = None
340
222
self.stop_checker()
341
if self.disable_hook:
342
self.disable_hook(self)
344
225
# Do not run this again if called by a gobject.timeout_add
347
227
def __del__(self):
348
self.disable_hook = None
351
def checker_callback(self, pid, condition, command):
228
self.stop_hook = None
230
def checker_callback(self, pid, condition):
352
231
"""The checker has completed, so take appropriate actions."""
232
now = datetime.datetime.now()
353
233
self.checker_callback_tag = None
354
234
self.checker = None
355
if os.WIFEXITED(condition):
356
exitstatus = os.WEXITSTATUS(condition)
358
logger.info(u"Checker for %(name)s succeeded",
362
logger.info(u"Checker for %(name)s failed",
235
if os.WIFEXITED(condition) \
236
and (os.WEXITSTATUS(condition) == 0):
237
logger.debug(u"Checker for %(name)s succeeded",
240
gobject.source_remove(self.stop_initiator_tag)
241
self.stop_initiator_tag = gobject.timeout_add\
242
(self._timeout_milliseconds,
244
elif not os.WIFEXITED(condition):
365
245
logger.warning(u"Checker for %(name)s crashed?",
368
def checked_ok(self):
369
"""Bump up the timeout for this client.
371
This should only be called when the client has been seen,
374
self.last_checked_ok = datetime.datetime.utcnow()
375
gobject.source_remove(self.disable_initiator_tag)
376
self.disable_initiator_tag = (gobject.timeout_add
377
(self.timeout_milliseconds(),
248
logger.debug(u"Checker for %(name)s failed",
380
250
def start_checker(self):
381
251
"""Start a new checker subprocess if one is not running.
383
252
If a checker already exists, leave it running and do
385
254
# The reason for not killing a running checker is that if we
390
259
# checkers alone, the checker would have to take more time
391
260
# than 'timeout' for the client to be declared invalid, which
392
261
# is as it should be.
394
# If a checker exists, make sure it is not a zombie
395
if self.checker is not None:
396
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
398
logger.warning(u"Checker was a zombie")
399
gobject.source_remove(self.checker_callback_tag)
400
self.checker_callback(pid, status,
401
self.current_checker_command)
402
# Start a new checker if needed
403
262
if self.checker is None:
405
# In case checker_command has exactly one % operator
406
command = self.checker_command % self.host
264
command = self.check_command % self.fqdn
407
265
except TypeError:
408
# Escape attributes for the shell
409
escaped_attrs = dict((key,
410
re.escape(unicode(str(val),
266
escaped_attrs = dict((key, re.escape(str(val)))
414
268
vars(self).iteritems())
416
command = self.checker_command % escaped_attrs
270
command = self.check_command % escaped_attrs
417
271
except TypeError, error:
418
logger.error(u'Could not format string "%s":'
419
u' %s', self.checker_command, error)
272
logger.critical(u'Could not format string "%s":'
273
u' %s', self.check_command, error)
420
274
return True # Try again later
421
self.current_checker_command = command
423
logger.info(u"Starting checker %r for %s",
425
# We don't need to redirect stdout and stderr, since
426
# in normal mode, that is already done by daemon(),
427
# and in debug mode we don't want to. (Stdin is
428
# always replaced by /dev/null.)
429
self.checker = subprocess.Popen(command,
431
shell=True, cwd=u"/")
432
self.checker_callback_tag = (gobject.child_watch_add
434
self.checker_callback,
436
# The checker may have completed before the gobject
437
# watch was added. Check for this.
438
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
440
gobject.source_remove(self.checker_callback_tag)
441
self.checker_callback(pid, status, command)
442
except OSError, error:
276
logger.debug(u"Starting checker %r for %s",
278
self.checker = subprocess.\
280
close_fds=True, shell=True,
282
self.checker_callback_tag = gobject.child_watch_add\
284
self.checker_callback)
285
except subprocess.OSError, error:
443
286
logger.error(u"Failed to start subprocess: %s",
445
288
# Re-run this periodically if run by gobject.timeout_add
448
290
def stop_checker(self):
449
291
"""Force the checker process, if any, to stop."""
450
292
if self.checker_callback_tag:
451
293
gobject.source_remove(self.checker_callback_tag)
452
294
self.checker_callback_tag = None
453
if getattr(self, u"checker", None) is None:
295
if not hasattr(self, "checker") or self.checker is None:
455
logger.debug(u"Stopping checker for %(name)s", vars(self))
297
logger.debug("Stopping checker for %(name)s", vars(self))
457
299
os.kill(self.checker.pid, signal.SIGTERM)
459
301
#if self.checker.poll() is None:
460
302
# os.kill(self.checker.pid, signal.SIGKILL)
461
303
except OSError, error:
462
if error.errno != errno.ESRCH: # No such process
304
if error.errno != errno.ESRCH:
464
306
self.checker = None
466
def still_valid(self):
307
def still_valid(self, now=None):
467
308
"""Has the timeout not yet passed for this client?"""
468
if not getattr(self, u"enabled", False):
470
now = datetime.datetime.utcnow()
471
if self.last_checked_ok is None:
310
now = datetime.datetime.now()
311
if self.last_seen is None:
472
312
return now < (self.created + self.timeout)
474
return now < (self.last_checked_ok + self.timeout)
477
class ClientDBus(Client, dbus.service.Object):
478
"""A Client class using D-Bus
481
dbus_object_path: dbus.ObjectPath
482
bus: dbus.SystemBus()
484
# dbus.service.Object doesn't use super(), so we can't either.
486
def __init__(self, bus = None, *args, **kwargs):
488
Client.__init__(self, *args, **kwargs)
489
# Only now, when this client is initialized, can it show up on
491
self.dbus_object_path = (dbus.ObjectPath
493
+ self.name.replace(u".", u"_")))
494
dbus.service.Object.__init__(self, self.bus,
495
self.dbus_object_path)
498
def _datetime_to_dbus(dt, variant_level=0):
499
"""Convert a UTC datetime.datetime() to a D-Bus type."""
500
return dbus.String(dt.isoformat(),
501
variant_level=variant_level)
504
oldstate = getattr(self, u"enabled", False)
505
r = Client.enable(self)
506
if oldstate != self.enabled:
508
self.PropertyChanged(dbus.String(u"enabled"),
509
dbus.Boolean(True, variant_level=1))
510
self.PropertyChanged(
511
dbus.String(u"last_enabled"),
512
self._datetime_to_dbus(self.last_enabled,
516
def disable(self, signal = True):
517
oldstate = getattr(self, u"enabled", False)
518
r = Client.disable(self)
519
if signal and oldstate != self.enabled:
521
self.PropertyChanged(dbus.String(u"enabled"),
522
dbus.Boolean(False, variant_level=1))
525
def __del__(self, *args, **kwargs):
527
self.remove_from_connection()
530
if hasattr(dbus.service.Object, u"__del__"):
531
dbus.service.Object.__del__(self, *args, **kwargs)
532
Client.__del__(self, *args, **kwargs)
534
def checker_callback(self, pid, condition, command,
536
self.checker_callback_tag = None
539
self.PropertyChanged(dbus.String(u"checker_running"),
540
dbus.Boolean(False, variant_level=1))
541
if os.WIFEXITED(condition):
542
exitstatus = os.WEXITSTATUS(condition)
544
self.CheckerCompleted(dbus.Int16(exitstatus),
545
dbus.Int64(condition),
546
dbus.String(command))
549
self.CheckerCompleted(dbus.Int16(-1),
550
dbus.Int64(condition),
551
dbus.String(command))
553
return Client.checker_callback(self, pid, condition, command,
556
def checked_ok(self, *args, **kwargs):
557
r = Client.checked_ok(self, *args, **kwargs)
559
self.PropertyChanged(
560
dbus.String(u"last_checked_ok"),
561
(self._datetime_to_dbus(self.last_checked_ok,
565
def start_checker(self, *args, **kwargs):
566
old_checker = self.checker
567
if self.checker is not None:
568
old_checker_pid = self.checker.pid
570
old_checker_pid = None
571
r = Client.start_checker(self, *args, **kwargs)
572
# Only if new checker process was started
573
if (self.checker is not None
574
and old_checker_pid != self.checker.pid):
576
self.CheckerStarted(self.current_checker_command)
577
self.PropertyChanged(
578
dbus.String(u"checker_running"),
579
dbus.Boolean(True, variant_level=1))
582
def stop_checker(self, *args, **kwargs):
583
old_checker = getattr(self, u"checker", None)
584
r = Client.stop_checker(self, *args, **kwargs)
585
if (old_checker is not None
586
and getattr(self, u"checker", None) is None):
587
self.PropertyChanged(dbus.String(u"checker_running"),
588
dbus.Boolean(False, variant_level=1))
591
## D-Bus methods & signals
592
_interface = u"se.bsnet.fukt.Mandos.Client"
595
@dbus.service.method(_interface)
597
return self.checked_ok()
599
# CheckerCompleted - signal
600
@dbus.service.signal(_interface, signature=u"nxs")
601
def CheckerCompleted(self, exitcode, waitstatus, command):
605
# CheckerStarted - signal
606
@dbus.service.signal(_interface, signature=u"s")
607
def CheckerStarted(self, command):
611
# GetAllProperties - method
612
@dbus.service.method(_interface, out_signature=u"a{sv}")
613
def GetAllProperties(self):
615
return dbus.Dictionary({
616
dbus.String(u"name"):
617
dbus.String(self.name, variant_level=1),
618
dbus.String(u"fingerprint"):
619
dbus.String(self.fingerprint, variant_level=1),
620
dbus.String(u"host"):
621
dbus.String(self.host, variant_level=1),
622
dbus.String(u"created"):
623
self._datetime_to_dbus(self.created,
625
dbus.String(u"last_enabled"):
626
(self._datetime_to_dbus(self.last_enabled,
628
if self.last_enabled is not None
629
else dbus.Boolean(False, variant_level=1)),
630
dbus.String(u"enabled"):
631
dbus.Boolean(self.enabled, variant_level=1),
632
dbus.String(u"last_checked_ok"):
633
(self._datetime_to_dbus(self.last_checked_ok,
635
if self.last_checked_ok is not None
636
else dbus.Boolean (False, variant_level=1)),
637
dbus.String(u"timeout"):
638
dbus.UInt64(self.timeout_milliseconds(),
640
dbus.String(u"interval"):
641
dbus.UInt64(self.interval_milliseconds(),
643
dbus.String(u"checker"):
644
dbus.String(self.checker_command,
646
dbus.String(u"checker_running"):
647
dbus.Boolean(self.checker is not None,
649
dbus.String(u"object_path"):
650
dbus.ObjectPath(self.dbus_object_path,
654
# IsStillValid - method
655
@dbus.service.method(_interface, out_signature=u"b")
656
def IsStillValid(self):
657
return self.still_valid()
659
# PropertyChanged - signal
660
@dbus.service.signal(_interface, signature=u"sv")
661
def PropertyChanged(self, property, value):
665
# ReceivedSecret - signal
666
@dbus.service.signal(_interface)
667
def ReceivedSecret(self):
672
@dbus.service.signal(_interface)
677
# SetChecker - method
678
@dbus.service.method(_interface, in_signature=u"s")
679
def SetChecker(self, checker):
680
"D-Bus setter method"
681
self.checker_command = checker
683
self.PropertyChanged(dbus.String(u"checker"),
684
dbus.String(self.checker_command,
688
@dbus.service.method(_interface, in_signature=u"s")
689
def SetHost(self, host):
690
"D-Bus setter method"
693
self.PropertyChanged(dbus.String(u"host"),
694
dbus.String(self.host, variant_level=1))
696
# SetInterval - method
697
@dbus.service.method(_interface, in_signature=u"t")
698
def SetInterval(self, milliseconds):
699
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
701
self.PropertyChanged(dbus.String(u"interval"),
702
(dbus.UInt64(self.interval_milliseconds(),
706
@dbus.service.method(_interface, in_signature=u"ay",
708
def SetSecret(self, secret):
709
"D-Bus setter method"
710
self.secret = str(secret)
712
# SetTimeout - method
713
@dbus.service.method(_interface, in_signature=u"t")
714
def SetTimeout(self, milliseconds):
715
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
717
self.PropertyChanged(dbus.String(u"timeout"),
718
(dbus.UInt64(self.timeout_milliseconds(),
722
@dbus.service.method(_interface)
727
# StartChecker - method
728
@dbus.service.method(_interface)
729
def StartChecker(self):
734
@dbus.service.method(_interface)
739
# StopChecker - method
740
@dbus.service.method(_interface)
741
def StopChecker(self):
747
class ClientHandler(socketserver.BaseRequestHandler, object):
748
"""A class to handle client connections.
750
Instantiated once for each connection to handle it.
314
return now < (self.last_seen + self.timeout)
317
def peer_certificate(session):
318
"Return the peer's OpenPGP certificate as a bytestring"
319
# If not an OpenPGP certificate...
320
if gnutls.library.functions.gnutls_certificate_type_get\
321
(session._c_object) \
322
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
323
# ...do the normal thing
324
return session.peer_certificate
325
list_size = ctypes.c_uint()
326
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
327
(session._c_object, ctypes.byref(list_size))
328
if list_size.value == 0:
331
return ctypes.string_at(cert.data, cert.size)
334
def fingerprint(openpgp):
335
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
336
# New empty GnuTLS certificate
337
crt = gnutls.library.types.gnutls_openpgp_crt_t()
338
gnutls.library.functions.gnutls_openpgp_crt_init\
340
# New GnuTLS "datum" with the OpenPGP public key
341
datum = gnutls.library.types.gnutls_datum_t\
342
(ctypes.cast(ctypes.c_char_p(openpgp),
343
ctypes.POINTER(ctypes.c_ubyte)),
344
ctypes.c_uint(len(openpgp)))
345
# Import the OpenPGP public key into the certificate
346
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
349
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
350
# New buffer for the fingerprint
351
buffer = ctypes.create_string_buffer(20)
352
buffer_length = ctypes.c_size_t()
353
# Get the fingerprint from the certificate into the buffer
354
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
355
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
356
# Deinit the certificate
357
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
358
# Convert the buffer to a Python bytestring
359
fpr = ctypes.string_at(buffer, buffer_length.value)
360
# Convert the bytestring to hexadecimal notation
361
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
365
class tcp_handler(SocketServer.BaseRequestHandler, object):
366
"""A TCP request handler class.
367
Instantiated by IPv6_TCPServer for each request to handle it.
751
368
Note: This will run in its own forked process."""
753
370
def handle(self):
754
logger.info(u"TCP connection from: %s",
755
unicode(self.client_address))
756
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
757
# Open IPC pipe to parent process
758
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
759
session = (gnutls.connection
760
.ClientSession(self.request,
764
line = self.request.makefile().readline()
765
logger.debug(u"Protocol version: %r", line)
767
if int(line.strip().split()[0]) > 1:
769
except (ValueError, IndexError, RuntimeError), error:
770
logger.error(u"Unknown protocol version: %s", error)
773
# Note: gnutls.connection.X509Credentials is really a
774
# generic GnuTLS certificate credentials object so long as
775
# no X.509 keys are added to it. Therefore, we can use it
776
# here despite using OpenPGP certificates.
778
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
779
# u"+AES-256-CBC", u"+SHA1",
780
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
782
# Use a fallback default, since this MUST be set.
783
priority = self.server.gnutls_priority
786
(gnutls.library.functions
787
.gnutls_priority_set_direct(session._c_object,
792
except gnutls.errors.GNUTLSError, error:
793
logger.warning(u"Handshake failed: %s", error)
794
# Do not run session.bye() here: the session is not
795
# established. Just abandon the request.
797
logger.debug(u"Handshake succeeded")
799
fpr = self.fingerprint(self.peer_certificate(session))
800
except (TypeError, gnutls.errors.GNUTLSError), error:
801
logger.warning(u"Bad certificate: %s", error)
804
logger.debug(u"Fingerprint: %s", fpr)
806
for c in self.server.clients:
807
if c.fingerprint == fpr:
371
logger.debug(u"TCP connection from: %s",
372
unicode(self.client_address))
373
session = gnutls.connection.ClientSession(self.request,
377
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
378
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
380
priority = "SECURE256"
382
gnutls.library.functions.gnutls_priority_set_direct\
383
(session._c_object, priority, None);
387
except gnutls.errors.GNUTLSError, error:
388
logger.debug(u"Handshake failed: %s", error)
389
# Do not run session.bye() here: the session is not
390
# established. Just abandon the request.
393
fpr = fingerprint(peer_certificate(session))
394
except (TypeError, gnutls.errors.GNUTLSError), error:
395
logger.debug(u"Bad certificate: %s", error)
398
logger.debug(u"Fingerprint: %s", fpr)
400
for c in self.server.clients:
401
if c.fingerprint == fpr:
404
# Have to check if client.still_valid(), since it is possible
405
# that the client timed out while establishing the GnuTLS
407
if (not client) or (not client.still_valid()):
409
logger.debug(u"Client %(name)s is invalid",
811
ipc.write(u"NOTFOUND %s\n" % fpr)
814
# Have to check if client.still_valid(), since it is
815
# possible that the client timed out while establishing
816
# the GnuTLS session.
817
if not client.still_valid():
818
ipc.write(u"INVALID %s\n" % client.name)
821
ipc.write(u"SENDING %s\n" % client.name)
823
while sent_size < len(client.secret):
824
sent = session.send(client.secret[sent_size:])
825
logger.debug(u"Sent: %d, remaining: %d",
826
sent, len(client.secret)
827
- (sent_size + sent))
412
logger.debug(u"Client not found for fingerprint: %s",
832
def peer_certificate(session):
833
"Return the peer's OpenPGP certificate as a bytestring"
834
# If not an OpenPGP certificate...
835
if (gnutls.library.functions
836
.gnutls_certificate_type_get(session._c_object)
837
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
838
# ...do the normal thing
839
return session.peer_certificate
840
list_size = ctypes.c_uint(1)
841
cert_list = (gnutls.library.functions
842
.gnutls_certificate_get_peers
843
(session._c_object, ctypes.byref(list_size)))
844
if not bool(cert_list) and list_size.value != 0:
845
raise gnutls.errors.GNUTLSError(u"error getting peer"
847
if list_size.value == 0:
850
return ctypes.string_at(cert.data, cert.size)
853
def fingerprint(openpgp):
854
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
855
# New GnuTLS "datum" with the OpenPGP public key
856
datum = (gnutls.library.types
857
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
860
ctypes.c_uint(len(openpgp))))
861
# New empty GnuTLS certificate
862
crt = gnutls.library.types.gnutls_openpgp_crt_t()
863
(gnutls.library.functions
864
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
865
# Import the OpenPGP public key into the certificate
866
(gnutls.library.functions
867
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
868
gnutls.library.constants
869
.GNUTLS_OPENPGP_FMT_RAW))
870
# Verify the self signature in the key
871
crtverify = ctypes.c_uint()
872
(gnutls.library.functions
873
.gnutls_openpgp_crt_verify_self(crt, 0,
874
ctypes.byref(crtverify)))
875
if crtverify.value != 0:
876
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
877
raise (gnutls.errors.CertificateSecurityError
879
# New buffer for the fingerprint
880
buf = ctypes.create_string_buffer(20)
881
buf_len = ctypes.c_size_t()
882
# Get the fingerprint from the certificate into the buffer
883
(gnutls.library.functions
884
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
885
ctypes.byref(buf_len)))
886
# Deinit the certificate
887
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
888
# Convert the buffer to a Python bytestring
889
fpr = ctypes.string_at(buf, buf_len.value)
890
# Convert the bytestring to hexadecimal notation
891
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
895
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
896
"""Like socketserver.ForkingMixIn, but also pass a pipe.
898
Assumes a gobject.MainLoop event loop.
900
def process_request(self, request, client_address):
901
"""Overrides and wraps the original process_request().
903
This function creates a new pipe in self.pipe
905
self.pipe = os.pipe()
906
super(ForkingMixInWithPipe,
907
self).process_request(request, client_address)
908
os.close(self.pipe[1]) # close write end
909
# Call "handle_ipc" for both data and EOF events
910
gobject.io_add_watch(self.pipe[0],
911
gobject.IO_IN | gobject.IO_HUP,
913
def handle_ipc(source, condition):
914
"""Dummy function; override as necessary"""
919
class IPv6_TCPServer(ForkingMixInWithPipe,
920
socketserver.TCPServer, object):
921
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
417
while sent_size < len(client.secret):
418
sent = session.send(client.secret[sent_size:])
419
logger.debug(u"Sent: %d, remaining: %d",
420
sent, len(client.secret)
421
- (sent_size + sent))
426
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
427
"""IPv6 TCP server. Accepts 'None' as address and/or port.
924
enabled: Boolean; whether this server is activated yet
925
interface: None or a network interface name (string)
926
use_ipv6: Boolean; to use IPv6 or not
928
clients: set of Client objects
929
gnutls_priority GnuTLS priority string
930
use_dbus: Boolean; to emit D-Bus signals or not
429
options: Command line options
430
clients: Set() of Client objects
932
def __init__(self, server_address, RequestHandlerClass,
933
interface=None, use_ipv6=True, clients=None,
934
gnutls_priority=None, use_dbus=True):
936
self.interface = interface
938
self.address_family = socket.AF_INET6
939
self.clients = clients
940
self.use_dbus = use_dbus
941
self.gnutls_priority = gnutls_priority
942
socketserver.TCPServer.__init__(self, server_address,
432
address_family = socket.AF_INET6
433
def __init__(self, *args, **kwargs):
434
if "options" in kwargs:
435
self.options = kwargs["options"]
436
del kwargs["options"]
437
if "clients" in kwargs:
438
self.clients = kwargs["clients"]
439
del kwargs["clients"]
440
return super(type(self), self).__init__(*args, **kwargs)
944
441
def server_bind(self):
945
442
"""This overrides the normal server_bind() function
946
443
to bind to an interface if one was specified, and also NOT to
947
444
bind to an address or port if they were not specified."""
948
if self.interface is not None:
949
if SO_BINDTODEVICE is None:
950
logger.error(u"SO_BINDTODEVICE does not exist;"
951
u" cannot bind to interface %s",
955
self.socket.setsockopt(socket.SOL_SOCKET,
959
except socket.error, error:
960
if error[0] == errno.EPERM:
961
logger.error(u"No permission to"
962
u" bind to interface %s",
964
elif error[0] == errno.ENOPROTOOPT:
965
logger.error(u"SO_BINDTODEVICE not available;"
966
u" cannot bind to interface %s",
445
if self.options.interface:
446
if not hasattr(socket, "SO_BINDTODEVICE"):
447
# From /usr/include/asm-i486/socket.h
448
socket.SO_BINDTODEVICE = 25
450
self.socket.setsockopt(socket.SOL_SOCKET,
451
socket.SO_BINDTODEVICE,
452
self.options.interface)
453
except socket.error, error:
454
if error[0] == errno.EPERM:
455
logger.warning(u"No permission to"
456
u" bind to interface %s",
457
self.options.interface)
970
460
# Only bind(2) the socket if we really need to.
971
461
if self.server_address[0] or self.server_address[1]:
972
462
if not self.server_address[0]:
973
if self.address_family == socket.AF_INET6:
974
any_address = u"::" # in6addr_any
976
any_address = socket.INADDR_ANY
977
self.server_address = (any_address,
464
self.server_address = (in6addr_any,
978
465
self.server_address[1])
979
elif not self.server_address[1]:
466
elif self.server_address[1] is None:
980
467
self.server_address = (self.server_address[0],
983
# self.server_address = (self.server_address[0],
988
return socketserver.TCPServer.server_bind(self)
989
def server_activate(self):
991
return socketserver.TCPServer.server_activate(self)
994
def handle_ipc(self, source, condition, file_objects={}):
996
gobject.IO_IN: u"IN", # There is data to read.
997
gobject.IO_OUT: u"OUT", # Data can be written (without
999
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1000
gobject.IO_ERR: u"ERR", # Error condition.
1001
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1002
# broken, usually for pipes and
1005
conditions_string = ' | '.join(name
1007
condition_names.iteritems()
1008
if cond & condition)
1009
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1012
# Turn the pipe file descriptor into a Python file object
1013
if source not in file_objects:
1014
file_objects[source] = os.fdopen(source, u"r", 1)
1016
# Read a line from the file object
1017
cmdline = file_objects[source].readline()
1018
if not cmdline: # Empty line means end of file
1019
# close the IPC pipe
1020
file_objects[source].close()
1021
del file_objects[source]
1023
# Stop calling this function
1026
logger.debug(u"IPC command: %r", cmdline)
1028
# Parse and act on command
1029
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1031
if cmd == u"NOTFOUND":
1032
logger.warning(u"Client not found for fingerprint: %s",
1036
mandos_dbus_service.ClientNotFound(args)
1037
elif cmd == u"INVALID":
1038
for client in self.clients:
1039
if client.name == args:
1040
logger.warning(u"Client %s is invalid", args)
1046
logger.error(u"Unknown client %s is invalid", args)
1047
elif cmd == u"SENDING":
1048
for client in self.clients:
1049
if client.name == args:
1050
logger.info(u"Sending secret to %s", client.name)
1054
client.ReceivedSecret()
1057
logger.error(u"Sending secret to unknown client %s",
1060
logger.error(u"Unknown IPC command: %r", cmdline)
1062
# Keep calling this function
469
return super(type(self), self).server_bind()
1066
472
def string_to_delta(interval):
1067
473
"""Parse a string and return a datetime.timedelta
1069
>>> string_to_delta(u'7d')
475
>>> string_to_delta('7d')
1070
476
datetime.timedelta(7)
1071
>>> string_to_delta(u'60s')
477
>>> string_to_delta('60s')
1072
478
datetime.timedelta(0, 60)
1073
>>> string_to_delta(u'60m')
479
>>> string_to_delta('60m')
1074
480
datetime.timedelta(0, 3600)
1075
>>> string_to_delta(u'24h')
481
>>> string_to_delta('24h')
1076
482
datetime.timedelta(1)
1077
483
>>> string_to_delta(u'1w')
1078
484
datetime.timedelta(7)
1079
>>> string_to_delta(u'5m 30s')
1080
datetime.timedelta(0, 330)
1082
timevalue = datetime.timedelta(0)
1083
for s in interval.split():
1085
suffix = unicode(s[-1])
1088
delta = datetime.timedelta(value)
1089
elif suffix == u"s":
1090
delta = datetime.timedelta(0, value)
1091
elif suffix == u"m":
1092
delta = datetime.timedelta(0, 0, 0, 0, value)
1093
elif suffix == u"h":
1094
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1095
elif suffix == u"w":
1096
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1099
except (ValueError, IndexError):
487
suffix=unicode(interval[-1])
488
value=int(interval[:-1])
490
delta = datetime.timedelta(value)
492
delta = datetime.timedelta(0, value)
494
delta = datetime.timedelta(0, 0, 0, 0, value)
496
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
498
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1100
500
raise ValueError
501
except (ValueError, IndexError):
507
"""Derived from the Avahi example code"""
508
global group, serviceName, serviceType, servicePort, serviceTXT, \
511
group = dbus.Interface(
512
bus.get_object( avahi.DBUS_NAME,
513
server.EntryGroupNew()),
514
avahi.DBUS_INTERFACE_ENTRY_GROUP)
515
group.connect_to_signal('StateChanged',
516
entry_group_state_changed)
517
logger.debug(u"Adding service '%s' of type '%s' ...",
518
serviceName, serviceType)
521
serviceInterface, # interface
522
avahi.PROTO_INET6, # protocol
523
dbus.UInt32(0), # flags
524
serviceName, serviceType,
526
dbus.UInt16(servicePort),
527
avahi.string_array_to_txt_array(serviceTXT))
531
def remove_service():
532
"""From the Avahi example code"""
535
if not group is None:
539
def server_state_changed(state):
540
"""Derived from the Avahi example code"""
541
if state == avahi.SERVER_COLLISION:
542
logger.warning(u"Server name collision")
544
elif state == avahi.SERVER_RUNNING:
548
def entry_group_state_changed(state, error):
549
"""Derived from the Avahi example code"""
550
global serviceName, server, rename_count
552
logger.debug(u"state change: %i", state)
554
if state == avahi.ENTRY_GROUP_ESTABLISHED:
555
logger.debug(u"Service established.")
556
elif state == avahi.ENTRY_GROUP_COLLISION:
558
rename_count = rename_count - 1
560
name = server.GetAlternativeServiceName(name)
561
logger.warning(u"Service name collision, "
562
u"changing name to '%s' ...", name)
567
logger.error(u"No suitable service name found after %i"
568
u" retries, exiting.", n_rename)
570
elif state == avahi.ENTRY_GROUP_FAILURE:
571
logger.error(u"Error in group state changed %s",
1105
576
def if_nametoindex(interface):
1106
"""Call the C function if_nametoindex(), or equivalent
1108
Note: This function cannot accept a unicode string."""
1109
global if_nametoindex
577
"""Call the C function if_nametoindex()"""
1111
if_nametoindex = (ctypes.cdll.LoadLibrary
1112
(ctypes.util.find_library(u"c"))
579
libc = ctypes.cdll.LoadLibrary("libc.so.6")
580
return libc.if_nametoindex(interface)
1114
581
except (OSError, AttributeError):
1115
logger.warning(u"Doing if_nametoindex the hard way")
1116
def if_nametoindex(interface):
1117
"Get an interface index the hard way, i.e. using fcntl()"
1118
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1119
with closing(socket.socket()) as s:
1120
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1121
struct.pack(str(u"16s16x"),
1123
interface_index = struct.unpack(str(u"I"),
1125
return interface_index
1126
return if_nametoindex(interface)
1129
def daemon(nochdir = False, noclose = False):
582
if "struct" not in sys.modules:
584
if "fcntl" not in sys.modules:
586
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
588
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
589
struct.pack("16s16x", interface))
591
interface_index = struct.unpack("I", ifreq[16:20])[0]
592
return interface_index
595
def daemon(nochdir, noclose):
1130
596
"""See daemon(3). Standard BSD Unix function.
1132
597
This should really exist as os.daemon, but it doesn't (yet)."""
1141
604
# Close all standard open file descriptors
1142
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
605
null = os.open("/dev/null", os.O_NOCTTY | os.O_RDWR)
1143
606
if not stat.S_ISCHR(os.fstat(null).st_mode):
1144
607
raise OSError(errno.ENODEV,
1145
u"/dev/null not a character device")
608
"/dev/null not a character device")
1146
609
os.dup2(null, sys.stdin.fileno())
1147
610
os.dup2(null, sys.stdout.fileno())
1148
611
os.dup2(null, sys.stderr.fileno())
616
def killme(status = 0):
617
logger.debug("Stopping server with exit status %d", status)
619
if main_loop_started:
1155
######################################################################
1156
# Parsing of options, both command line and config file
1158
parser = optparse.OptionParser(version = "%%prog %s" % version)
1159
parser.add_option("-i", u"--interface", type=u"string",
1160
metavar="IF", help=u"Bind to interface IF")
1161
parser.add_option("-a", u"--address", type=u"string",
1162
help=u"Address to listen for requests on")
1163
parser.add_option("-p", u"--port", type=u"int",
1164
help=u"Port number to receive requests on")
1165
parser.add_option("--check", action=u"store_true",
1166
help=u"Run self-test")
1167
parser.add_option("--debug", action=u"store_true",
1168
help=u"Debug mode; run in foreground and log to"
1170
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1171
u" priority string (see GnuTLS documentation)")
1172
parser.add_option("--servicename", type=u"string",
1173
metavar=u"NAME", help=u"Zeroconf service name")
1174
parser.add_option("--configdir", type=u"string",
1175
default=u"/etc/mandos", metavar=u"DIR",
1176
help=u"Directory to search for configuration"
1178
parser.add_option("--no-dbus", action=u"store_false",
1179
dest=u"use_dbus", help=u"Do not provide D-Bus"
1180
u" system bus interface")
1181
parser.add_option("--no-ipv6", action=u"store_false",
1182
dest=u"use_ipv6", help=u"Do not use IPv6")
1183
options = parser.parse_args()[0]
628
global main_loop_started
629
main_loop_started = False
631
parser = OptionParser()
632
parser.add_option("-i", "--interface", type="string",
633
default=None, metavar="IF",
634
help="Bind to interface IF")
635
parser.add_option("-a", "--address", type="string", default=None,
636
help="Address to listen for requests on")
637
parser.add_option("-p", "--port", type="int", default=None,
638
help="Port number to receive requests on")
639
parser.add_option("--timeout", type="string", # Parsed later
641
help="Amount of downtime allowed for clients")
642
parser.add_option("--interval", type="string", # Parsed later
644
help="How often to check that a client is up")
645
parser.add_option("--check", action="store_true", default=False,
646
help="Run self-test")
647
parser.add_option("--debug", action="store_true", default=False,
649
(options, args) = parser.parse_args()
1185
651
if options.check:
1187
653
doctest.testmod()
1190
# Default values for config file for server-global settings
1191
server_defaults = { u"interface": u"",
1196
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1197
u"servicename": u"Mandos",
1198
u"use_dbus": u"True",
1199
u"use_ipv6": u"True",
1202
# Parse config file for server-global settings
1203
server_config = configparser.SafeConfigParser(server_defaults)
1205
server_config.read(os.path.join(options.configdir,
1207
# Convert the SafeConfigParser object to a dict
1208
server_settings = server_config.defaults()
1209
# Use the appropriate methods on the non-string config options
1210
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1211
server_settings[option] = server_config.getboolean(u"DEFAULT",
1213
if server_settings["port"]:
1214
server_settings["port"] = server_config.getint(u"DEFAULT",
1218
# Override the settings from the config file with command line
1220
for option in (u"interface", u"address", u"port", u"debug",
1221
u"priority", u"servicename", u"configdir",
1222
u"use_dbus", u"use_ipv6"):
1223
value = getattr(options, option)
1224
if value is not None:
1225
server_settings[option] = value
1227
# Force all strings to be unicode
1228
for option in server_settings.keys():
1229
if type(server_settings[option]) is str:
1230
server_settings[option] = unicode(server_settings[option])
1231
# Now we have our good server settings in "server_settings"
1233
##################################################################
1236
debug = server_settings[u"debug"]
1237
use_dbus = server_settings[u"use_dbus"]
1238
use_ipv6 = server_settings[u"use_ipv6"]
1241
syslogger.setLevel(logging.WARNING)
1242
console.setLevel(logging.WARNING)
1244
if server_settings[u"servicename"] != u"Mandos":
1245
syslogger.setFormatter(logging.Formatter
1246
(u'Mandos (%s) [%%(process)d]:'
1247
u' %%(levelname)s: %%(message)s'
1248
% server_settings[u"servicename"]))
1250
# Parse config file with clients
1251
client_defaults = { u"timeout": u"1h",
1253
u"checker": u"fping -q -- %%(host)s",
1256
client_config = configparser.SafeConfigParser(client_defaults)
1257
client_config.read(os.path.join(server_settings[u"configdir"],
1260
global mandos_dbus_service
1261
mandos_dbus_service = None
1264
tcp_server = IPv6_TCPServer((server_settings[u"address"],
1265
server_settings[u"port"]),
1268
server_settings[u"interface"],
1272
server_settings[u"priority"],
1274
pidfilename = u"/var/run/mandos.pid"
1276
pidfile = open(pidfilename, u"w")
1278
logger.error(u"Could not open file %r", pidfilename)
1281
uid = pwd.getpwnam(u"_mandos").pw_uid
1282
gid = pwd.getpwnam(u"_mandos").pw_gid
1285
uid = pwd.getpwnam(u"mandos").pw_uid
1286
gid = pwd.getpwnam(u"mandos").pw_gid
1289
uid = pwd.getpwnam(u"nobody").pw_uid
1290
gid = pwd.getpwnam(u"nobody").pw_gid
1297
except OSError, error:
1298
if error[0] != errno.EPERM:
1301
# Enable all possible GnuTLS debugging
1303
# "Use a log level over 10 to enable all debugging options."
1305
gnutls.library.functions.gnutls_global_set_log_level(11)
1307
@gnutls.library.types.gnutls_log_func
1308
def debug_gnutls(level, string):
1309
logger.debug(u"GnuTLS: %s", string[:-1])
1311
(gnutls.library.functions
1312
.gnutls_global_set_log_function(debug_gnutls))
656
# Parse the time arguments
658
options.timeout = string_to_delta(options.timeout)
660
parser.error("option --timeout: Unparseable time")
662
options.interval = string_to_delta(options.interval)
664
parser.error("option --interval: Unparseable time")
667
defaults = { "checker": "fping -q -- %%(fqdn)s" }
668
client_config = ConfigParser.SafeConfigParser(defaults)
669
#client_config.readfp(open("global.conf"), "global.conf")
670
client_config.read("mandos-clients.conf")
1314
672
global main_loop
1315
675
# From the Avahi example code
1316
676
DBusGMainLoop(set_as_default=True )
1317
677
main_loop = gobject.MainLoop()
1318
678
bus = dbus.SystemBus()
679
server = dbus.Interface(
680
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
681
avahi.DBUS_INTERFACE_SERVER )
1319
682
# End of Avahi example code
1321
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1322
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1323
service = AvahiService(name = server_settings[u"servicename"],
1324
servicetype = u"_mandos._tcp",
1325
protocol = protocol, bus = bus)
1326
if server_settings["interface"]:
1327
service.interface = (if_nametoindex
1328
(str(server_settings[u"interface"])))
1330
client_class = Client
1332
client_class = functools.partial(ClientDBus, bus = bus)
1334
client_class(name = section,
1335
config= dict(client_config.items(section)))
1336
for section in client_config.sections()))
1338
logger.warning(u"No clients defined")
684
debug = options.debug
1341
# Redirect stdin so all checkers get /dev/null
1342
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1343
os.dup2(null, sys.stdin.fileno())
1347
# No console logging
1348
logger.removeHandler(console)
1349
# Close all input and output, do double fork, etc.
1353
with closing(pidfile):
1355
pidfile.write(str(pid) + "\n")
1358
logger.error(u"Could not write to file %r with PID %d",
1361
# "pidfile" was never created
687
console = logging.StreamHandler()
688
# console.setLevel(logging.DEBUG)
689
console.setFormatter(logging.Formatter\
690
('%(levelname)s: %(message)s'))
691
logger.addHandler(console)
695
def remove_from_clients(client):
696
clients.remove(client)
698
logger.debug(u"No clients left, exiting")
701
clients.update(Set(Client(name=section, options=options,
702
stop_hook = remove_from_clients,
703
**(dict(client_config\
705
for section in client_config.sections()))
1366
711
"Cleanup function; run on exit"
713
# From the Avahi example code
714
if not group is None:
717
# End of Avahi example code
1370
720
client = clients.pop()
1371
client.disable_hook = None
721
client.stop_hook = None
1374
724
atexit.register(cleanup)
1377
727
signal.signal(signal.SIGINT, signal.SIG_IGN)
1378
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1379
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1382
class MandosDBusService(dbus.service.Object):
1383
"""A D-Bus proxy object"""
1385
dbus.service.Object.__init__(self, bus, u"/")
1386
_interface = u"se.bsnet.fukt.Mandos"
1388
@dbus.service.signal(_interface, signature=u"oa{sv}")
1389
def ClientAdded(self, objpath, properties):
1393
@dbus.service.signal(_interface, signature=u"s")
1394
def ClientNotFound(self, fingerprint):
1398
@dbus.service.signal(_interface, signature=u"os")
1399
def ClientRemoved(self, objpath, name):
1403
@dbus.service.method(_interface, out_signature=u"ao")
1404
def GetAllClients(self):
1406
return dbus.Array(c.dbus_object_path for c in clients)
1408
@dbus.service.method(_interface,
1409
out_signature=u"a{oa{sv}}")
1410
def GetAllClientsWithProperties(self):
1412
return dbus.Dictionary(
1413
((c.dbus_object_path, c.GetAllProperties())
1415
signature=u"oa{sv}")
1417
@dbus.service.method(_interface, in_signature=u"o")
1418
def RemoveClient(self, object_path):
1421
if c.dbus_object_path == object_path:
1423
c.remove_from_connection()
1424
# Don't signal anything except ClientRemoved
1425
c.disable(signal=False)
1427
self.ClientRemoved(object_path, c.name)
1433
mandos_dbus_service = MandosDBusService()
728
signal.signal(signal.SIGHUP, lambda signum, frame: killme())
729
signal.signal(signal.SIGTERM, lambda signum, frame: killme())
1435
731
for client in clients:
1438
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1439
client.GetAllProperties())
1443
tcp_server.server_activate()
1445
# Find out what port we got
1446
service.port = tcp_server.socket.getsockname()[1]
1448
logger.info(u"Now listening on address %r, port %d,"
1449
" flowinfo %d, scope_id %d"
1450
% tcp_server.socket.getsockname())
1452
logger.info(u"Now listening on address %r, port %d"
1453
% tcp_server.socket.getsockname())
1455
#service.interface = tcp_server.socket.getsockname()[3]
1458
# From the Avahi example code
1461
except dbus.exceptions.DBusException, error:
1462
logger.critical(u"DBusException: %s", error)
1464
# End of Avahi example code
1466
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
1467
lambda *args, **kwargs:
1468
(tcp_server.handle_request
1469
(*args[2:], **kwargs) or True))
1471
logger.debug(u"Starting main loop")
734
tcp_server = IPv6_TCPServer((options.address, options.port),
738
# Find out what random port we got
740
servicePort = tcp_server.socket.getsockname()[1]
741
logger.debug(u"Now listening on port %d", servicePort)
743
if options.interface is not None:
744
global serviceInterface
745
serviceInterface = if_nametoindex(options.interface)
747
# From the Avahi example code
748
server.connect_to_signal("StateChanged", server_state_changed)
750
server_state_changed(server.GetState())
751
except dbus.exceptions.DBusException, error:
752
logger.critical(u"DBusException: %s", error)
754
# End of Avahi example code
756
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
757
lambda *args, **kwargs:
758
tcp_server.handle_request(*args[2:],
761
logger.debug("Starting main loop")
762
main_loop_started = True
1473
except AvahiError, error:
1474
logger.critical(u"AvahiError: %s", error)
1476
764
except KeyboardInterrupt:
1479
logger.debug(u"Server received KeyboardInterrupt")
1480
logger.debug(u"Server exiting")
1482
770
if __name__ == '__main__':