44
14
import gnutls.library.functions
45
15
import gnutls.library.constants
46
16
import gnutls.library.types
47
import ConfigParser as configparser
56
import logging.handlers
58
from contextlib import closing
67
28
from dbus.mainloop.glib import DBusGMainLoop
72
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
73
except AttributeError:
75
from IN import SO_BINDTODEVICE
77
# From /usr/include/asm/socket.h
83
logger = logging.Logger(u'mandos')
84
syslogger = (logging.handlers.SysLogHandler
85
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
86
address = "/dev/log"))
87
syslogger.setFormatter(logging.Formatter
88
(u'Mandos [%(process)d]: %(levelname)s:'
90
logger.addHandler(syslogger)
92
console = logging.StreamHandler()
93
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
96
logger.addHandler(console)
98
class AvahiError(Exception):
99
def __init__(self, value, *args, **kwargs):
101
super(AvahiError, self).__init__(value, *args, **kwargs)
102
def __unicode__(self):
103
return unicode(repr(self.value))
105
class AvahiServiceError(AvahiError):
108
class AvahiGroupError(AvahiError):
112
class AvahiService(object):
113
"""An Avahi (Zeroconf) service.
116
interface: integer; avahi.IF_UNSPEC or an interface index.
117
Used to optionally bind to the specified interface.
118
name: string; Example: u'Mandos'
119
type: string; Example: u'_mandos._tcp'.
120
See <http://www.dns-sd.org/ServiceTypes.html>
121
port: integer; what port to announce
122
TXT: list of strings; TXT record for the service
123
domain: string; Domain to publish on, default to .local if empty.
124
host: string; Host to publish records for, default is localhost
125
max_renames: integer; maximum number of renames
126
rename_count: integer; counter so we only rename after collisions
127
a sensible number of times
128
group: D-Bus Entry Group
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())
32
import logging.handlers
34
# logghandler.setFormatter(logging.Formatter('%(levelname)s %(message)s')
36
logger = logging.Logger('mandos')
37
logger.addHandler(logging.handlers.SysLogHandler(facility = logging.handlers.SysLogHandler.LOG_DAEMON))
39
# This variable is used to optionally bind to a specified interface.
40
# It is a global variable to fit in with the other variables from the
41
# Avahi server example code.
42
serviceInterface = avahi.IF_UNSPEC
43
# From the Avahi server example code:
44
serviceName = "Mandos"
45
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
46
servicePort = None # Not known at startup
47
serviceTXT = [] # TXT record for the service
48
domain = "" # Domain to publish on, default to .local
49
host = "" # Host to publish records for, default to localhost
50
group = None #our entry group
51
rename_count = 12 # Counter so we only rename after collisions a
52
# sensible number of times
53
# End of Avahi example code
227
56
class Client(object):
228
57
"""A representation of a client host served by this server.
231
name: string; from the config file, used in log messages and
59
name: string; from the config file, used in log messages
233
60
fingerprint: string (40 or 32 hexadecimal digits); used to
234
61
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.
62
secret: bytestring; sent verbatim (over TLS) to client
63
fqdn: string (FQDN); available for use by the checker command
64
created: datetime.datetime()
65
last_seen: datetime.datetime() or None if not yet seen
66
timeout: datetime.timedelta(); How long from last_seen until
67
this client is invalid
68
interval: datetime.timedelta(); How often to start a new checker
69
stop_hook: If set, called by stop() as stop_hook(self)
70
checker: subprocess.Popen(); a running checker process used
71
to see if the client lives.
72
Is None if no process is running.
248
73
checker_initiator_tag: a gobject event source tag, or None
249
disable_initiator_tag: - '' -
74
stop_initiator_tag: - '' -
250
75
checker_callback_tag: - '' -
251
76
checker_command: string; External command which is run to check if
252
client lives. %() expansions are done at
77
client lives. %()s expansions are done at
253
78
runtime with vars(self) as dict, so that for
254
79
instance %(name)s can be used in the command.
255
current_checker_command: string; current running checker_command
81
_timeout: Real variable for 'timeout'
82
_interval: Real variable for 'interval'
83
_timeout_milliseconds: Used by gobject.timeout_add()
84
_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'
86
def _set_timeout(self, timeout):
87
"Setter function for 'timeout' attribute"
88
self._timeout = timeout
89
self._timeout_milliseconds = ((self.timeout.days
90
* 24 * 60 * 60 * 1000)
91
+ (self.timeout.seconds * 1000)
92
+ (self.timeout.microseconds
94
timeout = property(lambda self: self._timeout,
97
def _set_interval(self, interval):
98
"Setter function for 'interval' attribute"
99
self._interval = interval
100
self._interval_milliseconds = ((self.interval.days
101
* 24 * 60 * 60 * 1000)
102
+ (self.interval.seconds
104
+ (self.interval.microseconds
106
interval = property(lambda self: self._interval,
109
def __init__(self, name=None, options=None, stop_hook=None,
110
fingerprint=None, secret=None, secfile=None, fqdn=None,
111
timeout=None, interval=-1, checker=None):
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
113
# Uppercase and remove spaces from fingerprint
114
# for later comparison purposes with return value of
115
# the fingerprint() function
116
self.fingerprint = fingerprint.upper().replace(u" ", u"")
118
self.secret = secret.decode(u"base64")
121
self.secret = sf.read()
124
raise RuntimeError(u"No secret or secfile for client %s"
126
self.fqdn = fqdn # string
127
self.created = datetime.datetime.now()
128
self.last_seen = None
130
timeout = options.timeout
131
self.timeout = timeout
133
interval = options.interval
135
interval = string_to_delta(interval)
136
self.interval = interval
137
self.stop_hook = stop_hook
305
138
self.checker = None
306
139
self.checker_initiator_tag = None
307
self.disable_initiator_tag = None
140
self.stop_initiator_tag = None
308
141
self.checker_callback_tag = None
309
self.checker_command = config[u"checker"]
310
self.current_checker_command = None
311
self.last_connect = None
314
"""Start this client's checker and timeout hooks"""
315
self.last_enabled = datetime.datetime.utcnow()
142
self.check_command = checker
144
"""Start this clients checker and timeout hooks"""
316
145
# Schedule a new checker to be started an 'interval' from now,
317
146
# and every interval from then on.
318
self.checker_initiator_tag = (gobject.timeout_add
319
(self.interval_milliseconds(),
147
self.checker_initiator_tag = gobject.timeout_add\
148
(self._interval_milliseconds,
321
150
# Also start a new checker *right now*.
322
151
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):
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):
152
# Schedule a stop() when 'timeout' has passed
153
self.stop_initiator_tag = gobject.timeout_add\
154
(self._timeout_milliseconds,
158
The possibility that this client might be restarted is left
159
open, but not currently used."""
160
logger.debug(u"Stopping client %s", self.name)
162
if self.stop_initiator_tag:
163
gobject.source_remove(self.stop_initiator_tag)
164
self.stop_initiator_tag = None
165
if self.checker_initiator_tag:
338
166
gobject.source_remove(self.checker_initiator_tag)
339
167
self.checker_initiator_tag = None
340
168
self.stop_checker()
341
if self.disable_hook:
342
self.disable_hook(self)
344
171
# Do not run this again if called by a gobject.timeout_add
347
173
def __del__(self):
348
self.disable_hook = None
351
def checker_callback(self, pid, condition, command):
174
# Some code duplication here and in stop()
175
if hasattr(self, "stop_initiator_tag") \
176
and self.stop_initiator_tag:
177
gobject.source_remove(self.stop_initiator_tag)
178
self.stop_initiator_tag = None
179
if hasattr(self, "checker_initiator_tag") \
180
and self.checker_initiator_tag:
181
gobject.source_remove(self.checker_initiator_tag)
182
self.checker_initiator_tag = None
184
def checker_callback(self, pid, condition):
352
185
"""The checker has completed, so take appropriate actions."""
353
self.checker_callback_tag = 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",
186
now = datetime.datetime.now()
187
if os.WIFEXITED(condition) \
188
and (os.WEXITSTATUS(condition) == 0):
189
logger.debug(u"Checker for %(name)s succeeded",
192
gobject.source_remove(self.stop_initiator_tag)
193
self.stop_initiator_tag = gobject.timeout_add\
194
(self._timeout_milliseconds,
196
if not os.WIFEXITED(condition):
365
197
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(),
200
logger.debug(u"Checker for %(name)s failed",
203
self.checker_callback_tag = None
380
204
def start_checker(self):
381
205
"""Start a new checker subprocess if one is not running.
383
206
If a checker already exists, leave it running and do
385
# The reason for not killing a running checker is that if we
386
# did that, then if a checker (for some reason) started
387
# running slowly and taking more than 'interval' time, the
388
# client would inevitably timeout, since no checker would get
389
# a chance to run to completion. If we instead leave running
390
# checkers alone, the checker would have to take more time
391
# than 'timeout' for the client to be declared invalid, which
392
# 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
208
if self.checker is None:
209
logger.debug(u"Starting checker for %s",
405
# In case checker_command has exactly one % operator
406
command = self.checker_command % self.host
212
command = self.check_command % self.fqdn
407
213
except TypeError:
408
# Escape attributes for the shell
409
escaped_attrs = dict((key,
410
re.escape(unicode(str(val),
214
escaped_attrs = dict((key, re.escape(str(val)))
414
216
vars(self).iteritems())
416
command = self.checker_command % escaped_attrs
218
command = self.check_command % escaped_attrs
417
219
except TypeError, error:
418
logger.error(u'Could not format string "%s":'
419
u' %s', self.checker_command, error)
220
logger.critical(u'Could not format string "%s": %s',
221
self.check_command, error)
420
222
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:
224
self.checker = subprocess.\
226
stdout=subprocess.PIPE,
227
close_fds=True, shell=True,
229
self.checker_callback_tag = gobject.\
230
child_watch_add(self.checker.pid,
233
except subprocess.OSError, error:
443
234
logger.error(u"Failed to start subprocess: %s",
445
236
# Re-run this periodically if run by gobject.timeout_add
448
238
def stop_checker(self):
449
239
"""Force the checker process, if any, to stop."""
450
if self.checker_callback_tag:
451
gobject.source_remove(self.checker_callback_tag)
452
self.checker_callback_tag = None
453
if getattr(self, u"checker", None) is None:
240
if not hasattr(self, "checker") or self.checker is None:
455
logger.debug(u"Stopping checker for %(name)s", vars(self))
457
os.kill(self.checker.pid, signal.SIGTERM)
459
#if self.checker.poll() is None:
460
# os.kill(self.checker.pid, signal.SIGKILL)
461
except OSError, error:
462
if error.errno != errno.ESRCH: # No such process
242
gobject.source_remove(self.checker_callback_tag)
243
self.checker_callback_tag = None
244
os.kill(self.checker.pid, signal.SIGTERM)
245
if self.checker.poll() is None:
246
os.kill(self.checker.pid, signal.SIGKILL)
464
247
self.checker = None
466
def still_valid(self):
248
def still_valid(self, now=None):
467
249
"""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:
251
now = datetime.datetime.now()
252
if self.last_seen is None:
472
253
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 ; only set if self.use_dbus
483
# dbus.service.Object doesn't use super(), so we can't either.
485
def __init__(self, bus = None, *args, **kwargs):
487
Client.__init__(self, *args, **kwargs)
488
# Only now, when this client is initialized, can it show up on
490
self.dbus_object_path = (dbus.ObjectPath
492
+ self.name.replace(u".", u"_")))
493
dbus.service.Object.__init__(self, self.bus,
494
self.dbus_object_path)
497
def _datetime_to_dbus(dt, variant_level=0):
498
"""Convert a UTC datetime.datetime() to a D-Bus type."""
499
return dbus.String(dt.isoformat(),
500
variant_level=variant_level)
503
oldstate = getattr(self, u"enabled", False)
504
r = Client.enable(self)
505
if oldstate != self.enabled:
507
self.PropertyChanged(dbus.String(u"enabled"),
508
dbus.Boolean(True, variant_level=1))
509
self.PropertyChanged(
510
dbus.String(u"last_enabled"),
511
self._datetime_to_dbus(self.last_enabled,
515
def disable(self, signal = True):
516
oldstate = getattr(self, u"enabled", False)
517
r = Client.disable(self)
518
if signal and oldstate != self.enabled:
520
self.PropertyChanged(dbus.String(u"enabled"),
521
dbus.Boolean(False, variant_level=1))
524
def __del__(self, *args, **kwargs):
526
self.remove_from_connection()
529
if hasattr(dbus.service.Object, u"__del__"):
530
dbus.service.Object.__del__(self, *args, **kwargs)
531
Client.__del__(self, *args, **kwargs)
533
def checker_callback(self, pid, condition, command,
535
self.checker_callback_tag = None
538
self.PropertyChanged(dbus.String(u"checker_running"),
539
dbus.Boolean(False, variant_level=1))
540
if os.WIFEXITED(condition):
541
exitstatus = os.WEXITSTATUS(condition)
543
self.CheckerCompleted(dbus.Int16(exitstatus),
544
dbus.Int64(condition),
545
dbus.String(command))
548
self.CheckerCompleted(dbus.Int16(-1),
549
dbus.Int64(condition),
550
dbus.String(command))
552
return Client.checker_callback(self, pid, condition, command,
555
def checked_ok(self, *args, **kwargs):
556
r = Client.checked_ok(self, *args, **kwargs)
558
self.PropertyChanged(
559
dbus.String(u"last_checked_ok"),
560
(self._datetime_to_dbus(self.last_checked_ok,
564
def start_checker(self, *args, **kwargs):
565
old_checker = self.checker
566
if self.checker is not None:
567
old_checker_pid = self.checker.pid
569
old_checker_pid = None
570
r = Client.start_checker(self, *args, **kwargs)
571
# Only if new checker process was started
572
if (self.checker is not None
573
and old_checker_pid != self.checker.pid):
575
self.CheckerStarted(self.current_checker_command)
576
self.PropertyChanged(
577
dbus.String(u"checker_running"),
578
dbus.Boolean(True, variant_level=1))
581
def stop_checker(self, *args, **kwargs):
582
old_checker = getattr(self, u"checker", None)
583
r = Client.stop_checker(self, *args, **kwargs)
584
if (old_checker is not None
585
and getattr(self, u"checker", None) is None):
586
self.PropertyChanged(dbus.String(u"checker_running"),
587
dbus.Boolean(False, variant_level=1))
590
## D-Bus methods & signals
591
_interface = u"se.bsnet.fukt.Mandos.Client"
594
@dbus.service.method(_interface)
596
return self.checked_ok()
598
# CheckerCompleted - signal
599
@dbus.service.signal(_interface, signature=u"nxs")
600
def CheckerCompleted(self, exitcode, waitstatus, command):
604
# CheckerStarted - signal
605
@dbus.service.signal(_interface, signature=u"s")
606
def CheckerStarted(self, command):
610
# GetAllProperties - method
611
@dbus.service.method(_interface, out_signature=u"a{sv}")
612
def GetAllProperties(self):
614
return dbus.Dictionary({
615
dbus.String(u"name"):
616
dbus.String(self.name, variant_level=1),
617
dbus.String(u"fingerprint"):
618
dbus.String(self.fingerprint, variant_level=1),
619
dbus.String(u"host"):
620
dbus.String(self.host, variant_level=1),
621
dbus.String(u"created"):
622
self._datetime_to_dbus(self.created,
624
dbus.String(u"last_enabled"):
625
(self._datetime_to_dbus(self.last_enabled,
627
if self.last_enabled is not None
628
else dbus.Boolean(False, variant_level=1)),
629
dbus.String(u"enabled"):
630
dbus.Boolean(self.enabled, variant_level=1),
631
dbus.String(u"last_checked_ok"):
632
(self._datetime_to_dbus(self.last_checked_ok,
634
if self.last_checked_ok is not None
635
else dbus.Boolean (False, variant_level=1)),
636
dbus.String(u"timeout"):
637
dbus.UInt64(self.timeout_milliseconds(),
639
dbus.String(u"interval"):
640
dbus.UInt64(self.interval_milliseconds(),
642
dbus.String(u"checker"):
643
dbus.String(self.checker_command,
645
dbus.String(u"checker_running"):
646
dbus.Boolean(self.checker is not None,
648
dbus.String(u"object_path"):
649
dbus.ObjectPath(self.dbus_object_path,
653
# IsStillValid - method
654
@dbus.service.method(_interface, out_signature=u"b")
655
def IsStillValid(self):
656
return self.still_valid()
658
# PropertyChanged - signal
659
@dbus.service.signal(_interface, signature=u"sv")
660
def PropertyChanged(self, property, value):
664
# ReceivedSecret - signal
665
@dbus.service.signal(_interface)
666
def ReceivedSecret(self):
671
@dbus.service.signal(_interface)
676
# SetChecker - method
677
@dbus.service.method(_interface, in_signature=u"s")
678
def SetChecker(self, checker):
679
"D-Bus setter method"
680
self.checker_command = checker
682
self.PropertyChanged(dbus.String(u"checker"),
683
dbus.String(self.checker_command,
687
@dbus.service.method(_interface, in_signature=u"s")
688
def SetHost(self, host):
689
"D-Bus setter method"
692
self.PropertyChanged(dbus.String(u"host"),
693
dbus.String(self.host, variant_level=1))
695
# SetInterval - method
696
@dbus.service.method(_interface, in_signature=u"t")
697
def SetInterval(self, milliseconds):
698
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
700
self.PropertyChanged(dbus.String(u"interval"),
701
(dbus.UInt64(self.interval_milliseconds(),
705
@dbus.service.method(_interface, in_signature=u"ay",
707
def SetSecret(self, secret):
708
"D-Bus setter method"
709
self.secret = str(secret)
711
# SetTimeout - method
712
@dbus.service.method(_interface, in_signature=u"t")
713
def SetTimeout(self, milliseconds):
714
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
716
self.PropertyChanged(dbus.String(u"timeout"),
717
(dbus.UInt64(self.timeout_milliseconds(),
721
@dbus.service.method(_interface)
726
# StartChecker - method
727
@dbus.service.method(_interface)
728
def StartChecker(self):
733
@dbus.service.method(_interface)
738
# StopChecker - method
739
@dbus.service.method(_interface)
740
def StopChecker(self):
746
class ClientHandler(socketserver.BaseRequestHandler, object):
747
"""A class to handle client connections.
749
Instantiated once for each connection to handle it.
255
return now < (self.last_seen + self.timeout)
258
def peer_certificate(session):
259
# If not an OpenPGP certificate...
260
if gnutls.library.functions.gnutls_certificate_type_get\
261
(session._c_object) \
262
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
263
# ...do the normal thing
264
return session.peer_certificate
265
list_size = ctypes.c_uint()
266
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
267
(session._c_object, ctypes.byref(list_size))
268
if list_size.value == 0:
271
return ctypes.string_at(cert.data, cert.size)
274
def fingerprint(openpgp):
275
# New empty GnuTLS certificate
276
crt = gnutls.library.types.gnutls_openpgp_crt_t()
277
gnutls.library.functions.gnutls_openpgp_crt_init\
279
# New GnuTLS "datum" with the OpenPGP public key
280
datum = gnutls.library.types.gnutls_datum_t\
281
(ctypes.cast(ctypes.c_char_p(openpgp),
282
ctypes.POINTER(ctypes.c_ubyte)),
283
ctypes.c_uint(len(openpgp)))
284
# Import the OpenPGP public key into the certificate
285
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
288
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
289
# New buffer for the fingerprint
290
buffer = ctypes.create_string_buffer(20)
291
buffer_length = ctypes.c_size_t()
292
# Get the fingerprint from the certificate into the buffer
293
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
294
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
295
# Deinit the certificate
296
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
297
# Convert the buffer to a Python bytestring
298
fpr = ctypes.string_at(buffer, buffer_length.value)
299
# Convert the bytestring to hexadecimal notation
300
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
304
class tcp_handler(SocketServer.BaseRequestHandler, object):
305
"""A TCP request handler class.
306
Instantiated by IPv6_TCPServer for each request to handle it.
750
307
Note: This will run in its own forked process."""
752
309
def handle(self):
753
logger.info(u"TCP connection from: %s",
754
unicode(self.client_address))
755
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
756
# Open IPC pipe to parent process
757
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
758
session = (gnutls.connection
759
.ClientSession(self.request,
763
line = self.request.makefile().readline()
764
logger.debug(u"Protocol version: %r", line)
766
if int(line.strip().split()[0]) > 1:
768
except (ValueError, IndexError, RuntimeError), error:
769
logger.error(u"Unknown protocol version: %s", error)
772
# Note: gnutls.connection.X509Credentials is really a
773
# generic GnuTLS certificate credentials object so long as
774
# no X.509 keys are added to it. Therefore, we can use it
775
# here despite using OpenPGP certificates.
777
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
778
# u"+AES-256-CBC", u"+SHA1",
779
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
781
# Use a fallback default, since this MUST be set.
782
priority = self.server.gnutls_priority
785
(gnutls.library.functions
786
.gnutls_priority_set_direct(session._c_object,
791
except gnutls.errors.GNUTLSError, error:
792
logger.warning(u"Handshake failed: %s", error)
793
# Do not run session.bye() here: the session is not
794
# established. Just abandon the request.
796
logger.debug(u"Handshake succeeded")
798
fpr = self.fingerprint(self.peer_certificate(session))
799
except (TypeError, gnutls.errors.GNUTLSError), error:
800
logger.warning(u"Bad certificate: %s", error)
803
logger.debug(u"Fingerprint: %s", fpr)
805
for c in self.server.clients:
806
if c.fingerprint == fpr:
310
logger.debug(u"TCP connection from: %s",
311
unicode(self.client_address))
312
session = gnutls.connection.ClientSession(self.request,
316
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
317
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
319
priority = "SECURE256"
321
gnutls.library.functions.gnutls_priority_set_direct\
322
(session._c_object, priority, None);
326
except gnutls.errors.GNUTLSError, error:
327
logger.debug(u"Handshake failed: %s", error)
328
# Do not run session.bye() here: the session is not
329
# established. Just abandon the request.
332
fpr = fingerprint(peer_certificate(session))
333
except (TypeError, gnutls.errors.GNUTLSError), error:
334
logger.debug(u"Bad certificate: %s", error)
337
logger.debug(u"Fingerprint: %s", fpr)
340
if c.fingerprint == fpr:
343
# Have to check if client.still_valid(), since it is possible
344
# that the client timed out while establishing the GnuTLS
346
if (not client) or (not client.still_valid()):
348
logger.debug(u"Client %(name)s is invalid",
810
ipc.write(u"NOTFOUND %s\n" % fpr)
813
# Have to check if client.still_valid(), since it is
814
# possible that the client timed out while establishing
815
# the GnuTLS session.
816
if not client.still_valid():
817
ipc.write(u"INVALID %s\n" % client.name)
820
ipc.write(u"SENDING %s\n" % client.name)
822
while sent_size < len(client.secret):
823
sent = session.send(client.secret[sent_size:])
824
logger.debug(u"Sent: %d, remaining: %d",
825
sent, len(client.secret)
826
- (sent_size + sent))
351
logger.debug(u"Client not found for fingerprint: %s",
831
def peer_certificate(session):
832
"Return the peer's OpenPGP certificate as a bytestring"
833
# If not an OpenPGP certificate...
834
if (gnutls.library.functions
835
.gnutls_certificate_type_get(session._c_object)
836
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
837
# ...do the normal thing
838
return session.peer_certificate
839
list_size = ctypes.c_uint(1)
840
cert_list = (gnutls.library.functions
841
.gnutls_certificate_get_peers
842
(session._c_object, ctypes.byref(list_size)))
843
if not bool(cert_list) and list_size.value != 0:
844
raise gnutls.errors.GNUTLSError(u"error getting peer"
846
if list_size.value == 0:
849
return ctypes.string_at(cert.data, cert.size)
852
def fingerprint(openpgp):
853
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
854
# New GnuTLS "datum" with the OpenPGP public key
855
datum = (gnutls.library.types
856
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
859
ctypes.c_uint(len(openpgp))))
860
# New empty GnuTLS certificate
861
crt = gnutls.library.types.gnutls_openpgp_crt_t()
862
(gnutls.library.functions
863
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
864
# Import the OpenPGP public key into the certificate
865
(gnutls.library.functions
866
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
867
gnutls.library.constants
868
.GNUTLS_OPENPGP_FMT_RAW))
869
# Verify the self signature in the key
870
crtverify = ctypes.c_uint()
871
(gnutls.library.functions
872
.gnutls_openpgp_crt_verify_self(crt, 0,
873
ctypes.byref(crtverify)))
874
if crtverify.value != 0:
875
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
876
raise (gnutls.errors.CertificateSecurityError
878
# New buffer for the fingerprint
879
buf = ctypes.create_string_buffer(20)
880
buf_len = ctypes.c_size_t()
881
# Get the fingerprint from the certificate into the buffer
882
(gnutls.library.functions
883
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
884
ctypes.byref(buf_len)))
885
# Deinit the certificate
886
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
887
# Convert the buffer to a Python bytestring
888
fpr = ctypes.string_at(buf, buf_len.value)
889
# Convert the bytestring to hexadecimal notation
890
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
894
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
895
"""Like socketserver.ForkingMixIn, but also pass a pipe.
897
Assumes a gobject.MainLoop event loop.
899
def process_request(self, request, client_address):
900
"""Overrides and wraps the original process_request().
902
This function creates a new pipe in self.pipe
904
self.pipe = os.pipe()
905
super(ForkingMixInWithPipe,
906
self).process_request(request, client_address)
907
os.close(self.pipe[1]) # close write end
908
# Call "handle_ipc" for both data and EOF events
909
gobject.io_add_watch(self.pipe[0],
910
gobject.IO_IN | gobject.IO_HUP,
912
def handle_ipc(source, condition):
913
"""Dummy function; override as necessary"""
918
class IPv6_TCPServer(ForkingMixInWithPipe,
919
socketserver.TCPServer, object):
920
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
356
while sent_size < len(client.secret):
357
sent = session.send(client.secret[sent_size:])
358
logger.debug(u"Sent: %d, remaining: %d",
359
sent, len(client.secret)
360
- (sent_size + sent))
365
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
366
"""IPv6 TCP server. Accepts 'None' as address and/or port.
923
enabled: Boolean; whether this server is activated yet
924
interface: None or a network interface name (string)
925
use_ipv6: Boolean; to use IPv6 or not
927
clients: set of Client objects
928
gnutls_priority GnuTLS priority string
929
use_dbus: Boolean; to emit D-Bus signals or not
368
options: Command line options
369
clients: Set() of Client objects
931
def __init__(self, server_address, RequestHandlerClass,
932
interface=None, use_ipv6=True, clients=None,
933
gnutls_priority=None, use_dbus=True):
935
self.interface = interface
937
self.address_family = socket.AF_INET6
938
self.clients = clients
939
self.use_dbus = use_dbus
940
self.gnutls_priority = gnutls_priority
941
socketserver.TCPServer.__init__(self, server_address,
371
address_family = socket.AF_INET6
372
def __init__(self, *args, **kwargs):
373
if "options" in kwargs:
374
self.options = kwargs["options"]
375
del kwargs["options"]
376
if "clients" in kwargs:
377
self.clients = kwargs["clients"]
378
del kwargs["clients"]
379
return super(type(self), self).__init__(*args, **kwargs)
943
380
def server_bind(self):
944
381
"""This overrides the normal server_bind() function
945
382
to bind to an interface if one was specified, and also NOT to
946
383
bind to an address or port if they were not specified."""
947
if self.interface is not None:
384
if self.options.interface:
385
if not hasattr(socket, "SO_BINDTODEVICE"):
386
# From /usr/include/asm-i486/socket.h
387
socket.SO_BINDTODEVICE = 25
949
389
self.socket.setsockopt(socket.SOL_SOCKET,
951
str(self.interface + u'\0'))
390
socket.SO_BINDTODEVICE,
391
self.options.interface)
952
392
except socket.error, error:
953
393
if error[0] == errno.EPERM:
954
logger.error(u"No permission to"
955
u" bind to interface %s",
394
logger.warning(u"No permission to"
395
u" bind to interface %s",
396
self.options.interface)
959
399
# Only bind(2) the socket if we really need to.
960
400
if self.server_address[0] or self.server_address[1]:
961
401
if not self.server_address[0]:
962
if self.address_family == socket.AF_INET6:
963
any_address = u"::" # in6addr_any
965
any_address = socket.INADDR_ANY
966
self.server_address = (any_address,
403
self.server_address = (in6addr_any,
967
404
self.server_address[1])
968
elif not self.server_address[1]:
405
elif self.server_address[1] is None:
969
406
self.server_address = (self.server_address[0],
972
# self.server_address = (self.server_address[0],
977
return socketserver.TCPServer.server_bind(self)
978
def server_activate(self):
980
return socketserver.TCPServer.server_activate(self)
983
def handle_ipc(self, source, condition, file_objects={}):
985
gobject.IO_IN: u"IN", # There is data to read.
986
gobject.IO_OUT: u"OUT", # Data can be written (without
988
gobject.IO_PRI: u"PRI", # There is urgent data to read.
989
gobject.IO_ERR: u"ERR", # Error condition.
990
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
991
# broken, usually for pipes and
994
conditions_string = ' | '.join(name
996
condition_names.iteritems()
998
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1001
# Turn the pipe file descriptor into a Python file object
1002
if source not in file_objects:
1003
file_objects[source] = os.fdopen(source, u"r", 1)
1005
# Read a line from the file object
1006
cmdline = file_objects[source].readline()
1007
if not cmdline: # Empty line means end of file
1008
# close the IPC pipe
1009
file_objects[source].close()
1010
del file_objects[source]
1012
# Stop calling this function
1015
logger.debug(u"IPC command: %r", cmdline)
1017
# Parse and act on command
1018
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1020
if cmd == u"NOTFOUND":
1021
logger.warning(u"Client not found for fingerprint: %s",
1025
mandos_dbus_service.ClientNotFound(args)
1026
elif cmd == u"INVALID":
1027
for client in self.clients:
1028
if client.name == args:
1029
logger.warning(u"Client %s is invalid", args)
1035
logger.error(u"Unknown client %s is invalid", args)
1036
elif cmd == u"SENDING":
1037
for client in self.clients:
1038
if client.name == args:
1039
logger.info(u"Sending secret to %s", client.name)
1043
client.ReceivedSecret()
1046
logger.error(u"Sending secret to unknown client %s",
1049
logger.error(u"Unknown IPC command: %r", cmdline)
1051
# Keep calling this function
408
return super(type(self), self).server_bind()
1055
411
def string_to_delta(interval):
1056
412
"""Parse a string and return a datetime.timedelta
1058
>>> string_to_delta(u'7d')
414
>>> string_to_delta('7d')
1059
415
datetime.timedelta(7)
1060
>>> string_to_delta(u'60s')
416
>>> string_to_delta('60s')
1061
417
datetime.timedelta(0, 60)
1062
>>> string_to_delta(u'60m')
418
>>> string_to_delta('60m')
1063
419
datetime.timedelta(0, 3600)
1064
>>> string_to_delta(u'24h')
420
>>> string_to_delta('24h')
1065
421
datetime.timedelta(1)
1066
422
>>> string_to_delta(u'1w')
1067
423
datetime.timedelta(7)
1068
>>> string_to_delta(u'5m 30s')
1069
datetime.timedelta(0, 330)
1071
timevalue = datetime.timedelta(0)
1072
for s in interval.split():
1074
suffix = unicode(s[-1])
1077
delta = datetime.timedelta(value)
1078
elif suffix == u"s":
1079
delta = datetime.timedelta(0, value)
1080
elif suffix == u"m":
1081
delta = datetime.timedelta(0, 0, 0, 0, value)
1082
elif suffix == u"h":
1083
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1084
elif suffix == u"w":
1085
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1088
except (ValueError, IndexError):
426
suffix=unicode(interval[-1])
427
value=int(interval[:-1])
429
delta = datetime.timedelta(value)
431
delta = datetime.timedelta(0, value)
433
delta = datetime.timedelta(0, 0, 0, 0, value)
435
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
437
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1089
439
raise ValueError
440
except (ValueError, IndexError):
446
"""From the Avahi server example code"""
447
global group, serviceName, serviceType, servicePort, serviceTXT, \
450
group = dbus.Interface(
451
bus.get_object( avahi.DBUS_NAME,
452
server.EntryGroupNew()),
453
avahi.DBUS_INTERFACE_ENTRY_GROUP)
454
group.connect_to_signal('StateChanged',
455
entry_group_state_changed)
456
logger.debug(u"Adding service '%s' of type '%s' ...",
457
serviceName, serviceType)
460
serviceInterface, # interface
461
avahi.PROTO_INET6, # protocol
462
dbus.UInt32(0), # flags
463
serviceName, serviceType,
465
dbus.UInt16(servicePort),
466
avahi.string_array_to_txt_array(serviceTXT))
470
def remove_service():
471
"""From the Avahi server example code"""
474
if not group is None:
478
def server_state_changed(state):
479
"""From the Avahi server example code"""
480
if state == avahi.SERVER_COLLISION:
481
logger.warning(u"Server name collision")
483
elif state == avahi.SERVER_RUNNING:
487
def entry_group_state_changed(state, error):
488
"""From the Avahi server example code"""
489
global serviceName, server, rename_count
491
logger.debug(u"state change: %i", state)
493
if state == avahi.ENTRY_GROUP_ESTABLISHED:
494
logger.debug(u"Service established.")
495
elif state == avahi.ENTRY_GROUP_COLLISION:
497
rename_count = rename_count - 1
499
name = server.GetAlternativeServiceName(name)
500
logger.warning(u"Service name collision, "
501
u"changing name to '%s' ...", name)
506
logger.error(u"No suitable service name found "
507
u"after %i retries, exiting.",
510
elif state == avahi.ENTRY_GROUP_FAILURE:
511
logger.error(u"Error in group state changed %s",
1094
517
def if_nametoindex(interface):
1095
"""Call the C function if_nametoindex(), or equivalent
1097
Note: This function cannot accept a unicode string."""
1098
global if_nametoindex
518
"""Call the C function if_nametoindex()"""
1100
if_nametoindex = (ctypes.cdll.LoadLibrary
1101
(ctypes.util.find_library(u"c"))
520
libc = ctypes.cdll.LoadLibrary("libc.so.6")
521
return libc.if_nametoindex(interface)
1103
522
except (OSError, AttributeError):
1104
logger.warning(u"Doing if_nametoindex the hard way")
1105
def if_nametoindex(interface):
1106
"Get an interface index the hard way, i.e. using fcntl()"
1107
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1108
with closing(socket.socket()) as s:
1109
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1110
struct.pack(str(u"16s16x"),
1112
interface_index = struct.unpack(str(u"I"),
1114
return interface_index
1115
return if_nametoindex(interface)
1118
def daemon(nochdir = False, noclose = False):
1119
"""See daemon(3). Standard BSD Unix function.
1121
This should really exist as os.daemon, but it doesn't (yet)."""
1130
# Close all standard open file descriptors
1131
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1132
if not stat.S_ISCHR(os.fstat(null).st_mode):
1133
raise OSError(errno.ENODEV,
1134
u"/dev/null not a character device")
1135
os.dup2(null, sys.stdin.fileno())
1136
os.dup2(null, sys.stdout.fileno())
1137
os.dup2(null, sys.stderr.fileno())
1144
######################################################################
1145
# Parsing of options, both command line and config file
1147
parser = optparse.OptionParser(version = "%%prog %s" % version)
1148
parser.add_option("-i", u"--interface", type=u"string",
1149
metavar="IF", help=u"Bind to interface IF")
1150
parser.add_option("-a", u"--address", type=u"string",
1151
help=u"Address to listen for requests on")
1152
parser.add_option("-p", u"--port", type=u"int",
1153
help=u"Port number to receive requests on")
1154
parser.add_option("--check", action=u"store_true",
1155
help=u"Run self-test")
1156
parser.add_option("--debug", action=u"store_true",
1157
help=u"Debug mode; run in foreground and log to"
1159
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1160
u" priority string (see GnuTLS documentation)")
1161
parser.add_option("--servicename", type=u"string",
1162
metavar=u"NAME", help=u"Zeroconf service name")
1163
parser.add_option("--configdir", type=u"string",
1164
default=u"/etc/mandos", metavar=u"DIR",
1165
help=u"Directory to search for configuration"
1167
parser.add_option("--no-dbus", action=u"store_false",
1168
dest=u"use_dbus", help=u"Do not provide D-Bus"
1169
u" system bus interface")
1170
parser.add_option("--no-ipv6", action=u"store_false",
1171
dest=u"use_ipv6", help=u"Do not use IPv6")
1172
options = parser.parse_args()[0]
523
if "struct" not in sys.modules:
525
if "fcntl" not in sys.modules:
527
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
529
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
530
struct.pack("16s16x", interface))
532
interface_index = struct.unpack("I", ifreq[16:20])[0]
533
return interface_index
536
if __name__ == '__main__':
537
parser = OptionParser()
538
parser.add_option("-i", "--interface", type="string",
539
default=None, metavar="IF",
540
help="Bind to interface IF")
541
parser.add_option("--cert", type="string", default="cert.pem",
543
help="Public key certificate PEM file to use")
544
parser.add_option("--key", type="string", default="key.pem",
546
help="Private key PEM file to use")
547
parser.add_option("--ca", type="string", default="ca.pem",
549
help="Certificate Authority certificate PEM file to use")
550
parser.add_option("--crl", type="string", default="crl.pem",
552
help="Certificate Revokation List PEM file to use")
553
parser.add_option("-p", "--port", type="int", default=None,
554
help="Port number to receive requests on")
555
parser.add_option("--timeout", type="string", # Parsed later
557
help="Amount of downtime allowed for clients")
558
parser.add_option("--interval", type="string", # Parsed later
560
help="How often to check that a client is up")
561
parser.add_option("--check", action="store_true", default=False,
562
help="Run self-test")
563
parser.add_option("--debug", action="store_true", default=False,
565
(options, args) = parser.parse_args()
1174
567
if options.check:
1176
569
doctest.testmod()
1179
# Default values for config file for server-global settings
1180
server_defaults = { u"interface": u"",
1185
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1186
u"servicename": u"Mandos",
1187
u"use_dbus": u"True",
1188
u"use_ipv6": u"True",
1191
# Parse config file for server-global settings
1192
server_config = configparser.SafeConfigParser(server_defaults)
1194
server_config.read(os.path.join(options.configdir,
1196
# Convert the SafeConfigParser object to a dict
1197
server_settings = server_config.defaults()
1198
# Use the appropriate methods on the non-string config options
1199
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1200
server_settings[option] = server_config.getboolean(u"DEFAULT",
1202
if server_settings["port"]:
1203
server_settings["port"] = server_config.getint(u"DEFAULT",
1207
# Override the settings from the config file with command line
1209
for option in (u"interface", u"address", u"port", u"debug",
1210
u"priority", u"servicename", u"configdir",
1211
u"use_dbus", u"use_ipv6"):
1212
value = getattr(options, option)
1213
if value is not None:
1214
server_settings[option] = value
1216
# Force all strings to be unicode
1217
for option in server_settings.keys():
1218
if type(server_settings[option]) is str:
1219
server_settings[option] = unicode(server_settings[option])
1220
# Now we have our good server settings in "server_settings"
1222
##################################################################
1225
debug = server_settings[u"debug"]
1226
use_dbus = server_settings[u"use_dbus"]
1227
use_ipv6 = server_settings[u"use_ipv6"]
1230
syslogger.setLevel(logging.WARNING)
1231
console.setLevel(logging.WARNING)
1233
if server_settings[u"servicename"] != u"Mandos":
1234
syslogger.setFormatter(logging.Formatter
1235
(u'Mandos (%s) [%%(process)d]:'
1236
u' %%(levelname)s: %%(message)s'
1237
% server_settings[u"servicename"]))
1239
# Parse config file with clients
1240
client_defaults = { u"timeout": u"1h",
1242
u"checker": u"fping -q -- %%(host)s",
1245
client_config = configparser.SafeConfigParser(client_defaults)
1246
client_config.read(os.path.join(server_settings[u"configdir"],
1249
global mandos_dbus_service
1250
mandos_dbus_service = None
1253
tcp_server = IPv6_TCPServer((server_settings[u"address"],
1254
server_settings[u"port"]),
1257
server_settings[u"interface"],
1261
server_settings[u"priority"],
1263
pidfilename = u"/var/run/mandos.pid"
1265
pidfile = open(pidfilename, u"w")
1267
logger.error(u"Could not open file %r", pidfilename)
1270
uid = pwd.getpwnam(u"_mandos").pw_uid
1271
gid = pwd.getpwnam(u"_mandos").pw_gid
1274
uid = pwd.getpwnam(u"mandos").pw_uid
1275
gid = pwd.getpwnam(u"mandos").pw_gid
1278
uid = pwd.getpwnam(u"nobody").pw_uid
1279
gid = pwd.getpwnam(u"nobody").pw_gid
1286
except OSError, error:
1287
if error[0] != errno.EPERM:
1290
# Enable all possible GnuTLS debugging
1292
# "Use a log level over 10 to enable all debugging options."
1294
gnutls.library.functions.gnutls_global_set_log_level(11)
1296
@gnutls.library.types.gnutls_log_func
1297
def debug_gnutls(level, string):
1298
logger.debug(u"GnuTLS: %s", string[:-1])
1300
(gnutls.library.functions
1301
.gnutls_global_set_log_function(debug_gnutls))
1304
# From the Avahi example code
572
# Parse the time arguments
574
options.timeout = string_to_delta(options.timeout)
576
parser.error("option --timeout: Unparseable time")
578
options.interval = string_to_delta(options.interval)
580
parser.error("option --interval: Unparseable time")
583
defaults = { "checker": "sleep 1; fping -q -- %%(fqdn)s" }
584
client_config = ConfigParser.SafeConfigParser(defaults)
585
#client_config.readfp(open("secrets.conf"), "secrets.conf")
586
client_config.read("mandos-clients.conf")
588
# From the Avahi server example code
1305
589
DBusGMainLoop(set_as_default=True )
1306
590
main_loop = gobject.MainLoop()
1307
591
bus = dbus.SystemBus()
592
server = dbus.Interface(
593
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
594
avahi.DBUS_INTERFACE_SERVER )
1308
595
# End of Avahi example code
1310
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1311
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1312
service = AvahiService(name = server_settings[u"servicename"],
1313
servicetype = u"_mandos._tcp",
1314
protocol = protocol, bus = bus)
1315
if server_settings["interface"]:
1316
service.interface = (if_nametoindex
1317
(str(server_settings[u"interface"])))
1319
client_class = Client
1321
client_class = functools.partial(ClientDBus, bus = bus)
1323
client_class(name = section,
1324
config= dict(client_config.items(section)))
1325
for section in client_config.sections()))
1327
logger.warning(u"No clients defined")
1330
# Redirect stdin so all checkers get /dev/null
1331
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1332
os.dup2(null, sys.stdin.fileno())
1336
# No console logging
1337
logger.removeHandler(console)
1338
# Close all input and output, do double fork, etc.
1342
with closing(pidfile):
1344
pidfile.write(str(pid) + "\n")
1347
logger.error(u"Could not write to file %r with PID %d",
1350
# "pidfile" was never created
1355
"Cleanup function; run on exit"
1359
client = clients.pop()
1360
client.disable_hook = None
1363
atexit.register(cleanup)
1366
signal.signal(signal.SIGINT, signal.SIG_IGN)
1367
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1368
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1371
class MandosDBusService(dbus.service.Object):
1372
"""A D-Bus proxy object"""
1374
dbus.service.Object.__init__(self, bus, u"/")
1375
_interface = u"se.bsnet.fukt.Mandos"
1377
@dbus.service.signal(_interface, signature=u"oa{sv}")
1378
def ClientAdded(self, objpath, properties):
1382
@dbus.service.signal(_interface, signature=u"s")
1383
def ClientNotFound(self, fingerprint):
1387
@dbus.service.signal(_interface, signature=u"os")
1388
def ClientRemoved(self, objpath, name):
1392
@dbus.service.method(_interface, out_signature=u"ao")
1393
def GetAllClients(self):
1395
return dbus.Array(c.dbus_object_path for c in clients)
1397
@dbus.service.method(_interface,
1398
out_signature=u"a{oa{sv}}")
1399
def GetAllClientsWithProperties(self):
1401
return dbus.Dictionary(
1402
((c.dbus_object_path, c.GetAllProperties())
1404
signature=u"oa{sv}")
1406
@dbus.service.method(_interface, in_signature=u"o")
1407
def RemoveClient(self, object_path):
1410
if c.dbus_object_path == object_path:
1412
c.remove_from_connection()
1413
# Don't signal anything except ClientRemoved
1414
c.disable(signal=False)
1416
self.ClientRemoved(object_path, c.name)
1422
mandos_dbus_service = MandosDBusService()
597
debug = options.debug
600
def remove_from_clients(client):
601
clients.remove(client)
603
logger.debug(u"No clients left, exiting")
606
clients.update(Set(Client(name=section, options=options,
607
stop_hook = remove_from_clients,
608
**(dict(client_config\
610
for section in client_config.sections()))
1424
611
for client in clients:
1427
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1428
client.GetAllProperties())
1432
tcp_server.server_activate()
1434
# Find out what port we got
1435
service.port = tcp_server.socket.getsockname()[1]
1437
logger.info(u"Now listening on address %r, port %d,"
1438
" flowinfo %d, scope_id %d"
1439
% tcp_server.socket.getsockname())
1441
logger.info(u"Now listening on address %r, port %d"
1442
% tcp_server.socket.getsockname())
1444
#service.interface = tcp_server.socket.getsockname()[3]
614
tcp_server = IPv6_TCPServer((None, options.port),
618
# Find out what random port we got
619
servicePort = tcp_server.socket.getsockname()[1]
620
logger.debug(u"Now listening on port %d", servicePort)
622
if options.interface is not None:
623
serviceInterface = if_nametoindex(options.interface)
625
# From the Avahi server example code
626
server.connect_to_signal("StateChanged", server_state_changed)
627
server_state_changed(server.GetState())
628
# End of Avahi example code
630
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
631
lambda *args, **kwargs:
632
tcp_server.handle_request(*args[2:],
1447
# From the Avahi example code
1450
except dbus.exceptions.DBusException, error:
1451
logger.critical(u"DBusException: %s", error)
1453
# End of Avahi example code
1455
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
1456
lambda *args, **kwargs:
1457
(tcp_server.handle_request
1458
(*args[2:], **kwargs) or True))
1460
logger.debug(u"Starting main loop")
1462
except AvahiError, error:
1463
logger.critical(u"AvahiError: %s", error)
1465
636
except KeyboardInterrupt:
1468
logger.debug(u"Server received KeyboardInterrupt")
1469
logger.debug(u"Server exiting")
1471
if __name__ == '__main__':
641
# From the Avahi server example code
642
if not group is None:
644
# End of Avahi example code
646
for client in clients:
647
client.stop_hook = None