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
66
62
from dbus.mainloop.glib import DBusGMainLoop
71
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
72
except AttributeError:
74
from IN import SO_BINDTODEVICE
76
# From /usr/include/asm/socket.h
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
130
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
131
servicetype = None, port = None, TXT = None,
132
domain = u"", host = u"", max_renames = 32768,
133
protocol = avahi.PROTO_UNSPEC):
134
self.interface = interface
136
self.type = servicetype
138
self.TXT = TXT if TXT is not None else []
141
self.rename_count = 0
142
self.max_renames = max_renames
143
self.protocol = protocol
144
self.group = None # our entry group
147
"""Derived from the Avahi example code"""
148
if self.rename_count >= self.max_renames:
149
logger.critical(u"No suitable Zeroconf service name found"
150
u" after %i retries, exiting.",
152
raise AvahiServiceError(u"Too many renames")
153
self.name = self.server.GetAlternativeServiceName(self.name)
154
logger.info(u"Changing Zeroconf service name to %r ...",
156
syslogger.setFormatter(logging.Formatter
157
(u'Mandos (%s) [%%(process)d]:'
158
u' %%(levelname)s: %%(message)s'
162
self.rename_count += 1
164
"""Derived from the Avahi example code"""
165
if self.group is not None:
168
"""Derived from the Avahi example code"""
169
if self.group is None:
170
self.group = dbus.Interface(
171
bus.get_object(avahi.DBUS_NAME,
172
self.server.EntryGroupNew()),
173
avahi.DBUS_INTERFACE_ENTRY_GROUP)
174
self.group.connect_to_signal('StateChanged',
175
self.entry_group_state_changed)
176
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
177
self.name, self.type)
178
self.group.AddService(
181
dbus.UInt32(0), # flags
182
self.name, self.type,
183
self.domain, self.host,
184
dbus.UInt16(self.port),
185
avahi.string_array_to_txt_array(self.TXT))
187
def entry_group_state_changed(self, state, error):
188
"""Derived from the Avahi example code"""
189
logger.debug(u"Avahi state change: %i", state)
191
if state == avahi.ENTRY_GROUP_ESTABLISHED:
192
logger.debug(u"Zeroconf service established.")
193
elif state == avahi.ENTRY_GROUP_COLLISION:
194
logger.warning(u"Zeroconf service name collision.")
196
elif state == avahi.ENTRY_GROUP_FAILURE:
197
logger.critical(u"Avahi: Error in group state changed %s",
199
raise AvahiGroupError(u"State changed: %s"
202
"""Derived from the Avahi example code"""
203
if self.group is not None:
206
def server_state_changed(self, state):
207
"""Derived from the Avahi example code"""
208
if state == avahi.SERVER_COLLISION:
209
logger.error(u"Zeroconf server name collision")
211
elif state == avahi.SERVER_RUNNING:
214
"""Derived from the Avahi example code"""
215
if self.server is None:
216
self.server = dbus.Interface(
217
bus.get_object(avahi.DBUS_NAME,
218
avahi.DBUS_PATH_SERVER),
219
avahi.DBUS_INTERFACE_SERVER)
220
self.server.connect_to_signal(u"StateChanged",
221
self.server_state_changed)
222
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
225
102
class Client(object):
226
103
"""A representation of a client host served by this server.
229
name: string; from the config file, used in log messages and
105
name: string; from the config file, used in log messages
231
106
fingerprint: string (40 or 32 hexadecimal digits); used to
232
107
uniquely identify the client
233
secret: bytestring; sent verbatim (over TLS) to client
234
host: string; available for use by the checker command
235
created: datetime.datetime(); (UTC) object creation
236
last_enabled: datetime.datetime(); (UTC)
238
last_checked_ok: datetime.datetime(); (UTC) or None
239
timeout: datetime.timedelta(); How long from last_checked_ok
240
until this client is invalid
241
interval: datetime.timedelta(); How often to start a new checker
242
disable_hook: If set, called by disable() as disable_hook(self)
243
checker: subprocess.Popen(); a running checker process used
244
to see if the client lives.
245
'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.
246
119
checker_initiator_tag: a gobject event source tag, or None
247
disable_initiator_tag: - '' -
120
stop_initiator_tag: - '' -
248
121
checker_callback_tag: - '' -
249
122
checker_command: string; External command which is run to check if
250
client lives. %() expansions are done at
123
client lives. %()s expansions are done at
251
124
runtime with vars(self) as dict, so that for
252
125
instance %(name)s can be used in the command.
253
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: - '' -
257
def _datetime_to_milliseconds(dt):
258
"Convert a datetime.datetime() to milliseconds"
259
return ((dt.days * 24 * 60 * 60 * 1000)
260
+ (dt.seconds * 1000)
261
+ (dt.microseconds // 1000))
263
def timeout_milliseconds(self):
264
"Return the 'timeout' attribute in milliseconds"
265
return self._datetime_to_milliseconds(self.timeout)
267
def interval_milliseconds(self):
268
"Return the 'interval' attribute in milliseconds"
269
return self._datetime_to_milliseconds(self.interval)
271
def __init__(self, name = None, disable_hook=None, config=None):
272
"""Note: the 'checker' key in 'config' sets the
273
'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.."""
278
logger.debug(u"Creating client %r", self.name)
279
# Uppercase and remove spaces from fingerprint for later
280
# comparison purposes with return value from the fingerprint()
282
self.fingerprint = (config[u"fingerprint"].upper()
284
logger.debug(u" Fingerprint: %s", self.fingerprint)
285
if u"secret" in config:
286
self.secret = config[u"secret"].decode(u"base64")
287
elif u"secfile" in config:
288
with closing(open(os.path.expanduser
290
(config[u"secfile"])))) as secfile:
291
self.secret = secfile.read()
293
raise TypeError(u"No secret or secfile for client %s"
295
self.host = config.get(u"host", u"")
296
self.created = datetime.datetime.utcnow()
298
self.last_enabled = None
299
self.last_checked_ok = None
300
self.timeout = string_to_delta(config[u"timeout"])
301
self.interval = string_to_delta(config[u"interval"])
302
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
303
186
self.checker = None
304
187
self.checker_initiator_tag = None
305
self.disable_initiator_tag = None
188
self.stop_initiator_tag = None
306
189
self.checker_callback_tag = None
307
self.checker_command = config[u"checker"]
308
self.current_checker_command = None
309
self.last_connect = None
190
self.check_command = checker
312
192
"""Start this client's checker and timeout hooks"""
313
self.last_enabled = datetime.datetime.utcnow()
314
193
# Schedule a new checker to be started an 'interval' from now,
315
194
# and every interval from then on.
316
self.checker_initiator_tag = (gobject.timeout_add
317
(self.interval_milliseconds(),
195
self.checker_initiator_tag = gobject.timeout_add\
196
(self._interval_milliseconds,
319
198
# Also start a new checker *right now*.
320
199
self.start_checker()
321
# Schedule a disable() when 'timeout' has passed
322
self.disable_initiator_tag = (gobject.timeout_add
323
(self.timeout_milliseconds(),
328
"""Disable this client."""
329
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)
331
logger.info(u"Disabling client %s", self.name)
332
if getattr(self, u"disable_initiator_tag", False):
333
gobject.source_remove(self.disable_initiator_tag)
334
self.disable_initiator_tag = None
335
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:
336
220
gobject.source_remove(self.checker_initiator_tag)
337
221
self.checker_initiator_tag = None
338
222
self.stop_checker()
339
if self.disable_hook:
340
self.disable_hook(self)
342
225
# Do not run this again if called by a gobject.timeout_add
345
227
def __del__(self):
346
self.disable_hook = None
349
def checker_callback(self, pid, condition, command):
228
self.stop_hook = None
230
def checker_callback(self, pid, condition):
350
231
"""The checker has completed, so take appropriate actions."""
232
now = datetime.datetime.now()
351
233
self.checker_callback_tag = None
352
234
self.checker = None
353
if os.WIFEXITED(condition):
354
exitstatus = os.WEXITSTATUS(condition)
356
logger.info(u"Checker for %(name)s succeeded",
360
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):
363
245
logger.warning(u"Checker for %(name)s crashed?",
366
def checked_ok(self):
367
"""Bump up the timeout for this client.
369
This should only be called when the client has been seen,
372
self.last_checked_ok = datetime.datetime.utcnow()
373
gobject.source_remove(self.disable_initiator_tag)
374
self.disable_initiator_tag = (gobject.timeout_add
375
(self.timeout_milliseconds(),
248
logger.debug(u"Checker for %(name)s failed",
378
250
def start_checker(self):
379
251
"""Start a new checker subprocess if one is not running.
381
252
If a checker already exists, leave it running and do
383
254
# The reason for not killing a running checker is that if we
388
259
# checkers alone, the checker would have to take more time
389
260
# than 'timeout' for the client to be declared invalid, which
390
261
# is as it should be.
392
# If a checker exists, make sure it is not a zombie
393
if self.checker is not None:
394
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
396
logger.warning(u"Checker was a zombie")
397
gobject.source_remove(self.checker_callback_tag)
398
self.checker_callback(pid, status,
399
self.current_checker_command)
400
# Start a new checker if needed
401
262
if self.checker is None:
403
# In case checker_command has exactly one % operator
404
command = self.checker_command % self.host
264
command = self.check_command % self.fqdn
405
265
except TypeError:
406
# Escape attributes for the shell
407
escaped_attrs = dict((key,
408
re.escape(unicode(str(val),
266
escaped_attrs = dict((key, re.escape(str(val)))
412
268
vars(self).iteritems())
414
command = self.checker_command % escaped_attrs
270
command = self.check_command % escaped_attrs
415
271
except TypeError, error:
416
logger.error(u'Could not format string "%s":'
417
u' %s', self.checker_command, error)
272
logger.critical(u'Could not format string "%s":'
273
u' %s', self.check_command, error)
418
274
return True # Try again later
419
self.current_checker_command = command
421
logger.info(u"Starting checker %r for %s",
423
# We don't need to redirect stdout and stderr, since
424
# in normal mode, that is already done by daemon(),
425
# and in debug mode we don't want to. (Stdin is
426
# always replaced by /dev/null.)
427
self.checker = subprocess.Popen(command,
429
shell=True, cwd=u"/")
430
self.checker_callback_tag = (gobject.child_watch_add
432
self.checker_callback,
434
# The checker may have completed before the gobject
435
# watch was added. Check for this.
436
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
438
gobject.source_remove(self.checker_callback_tag)
439
self.checker_callback(pid, status, command)
440
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:
441
286
logger.error(u"Failed to start subprocess: %s",
443
288
# Re-run this periodically if run by gobject.timeout_add
446
290
def stop_checker(self):
447
291
"""Force the checker process, if any, to stop."""
448
292
if self.checker_callback_tag:
449
293
gobject.source_remove(self.checker_callback_tag)
450
294
self.checker_callback_tag = None
451
if getattr(self, u"checker", None) is None:
295
if not hasattr(self, "checker") or self.checker is None:
453
logger.debug(u"Stopping checker for %(name)s", vars(self))
297
logger.debug("Stopping checker for %(name)s", vars(self))
455
299
os.kill(self.checker.pid, signal.SIGTERM)
457
301
#if self.checker.poll() is None:
458
302
# os.kill(self.checker.pid, signal.SIGKILL)
459
303
except OSError, error:
460
if error.errno != errno.ESRCH: # No such process
304
if error.errno != errno.ESRCH:
462
306
self.checker = None
464
def still_valid(self):
307
def still_valid(self, now=None):
465
308
"""Has the timeout not yet passed for this client?"""
466
if not getattr(self, u"enabled", False):
468
now = datetime.datetime.utcnow()
469
if self.last_checked_ok is None:
310
now = datetime.datetime.now()
311
if self.last_seen is None:
470
312
return now < (self.created + self.timeout)
472
return now < (self.last_checked_ok + self.timeout)
475
class ClientDBus(Client, dbus.service.Object):
476
"""A Client class using D-Bus
479
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
481
# dbus.service.Object doesn't use super(), so we can't either.
483
def __init__(self, *args, **kwargs):
484
Client.__init__(self, *args, **kwargs)
485
# Only now, when this client is initialized, can it show up on
487
self.dbus_object_path = (dbus.ObjectPath
489
+ self.name.replace(u".", u"_")))
490
dbus.service.Object.__init__(self, bus,
491
self.dbus_object_path)
494
def _datetime_to_dbus(dt, variant_level=0):
495
"""Convert a UTC datetime.datetime() to a D-Bus type."""
496
return dbus.String(dt.isoformat(),
497
variant_level=variant_level)
500
oldstate = getattr(self, u"enabled", False)
501
r = Client.enable(self)
502
if oldstate != self.enabled:
504
self.PropertyChanged(dbus.String(u"enabled"),
505
dbus.Boolean(True, variant_level=1))
506
self.PropertyChanged(
507
dbus.String(u"last_enabled"),
508
self._datetime_to_dbus(self.last_enabled,
512
def disable(self, signal = True):
513
oldstate = getattr(self, u"enabled", False)
514
r = Client.disable(self)
515
if signal and oldstate != self.enabled:
517
self.PropertyChanged(dbus.String(u"enabled"),
518
dbus.Boolean(False, variant_level=1))
521
def __del__(self, *args, **kwargs):
523
self.remove_from_connection()
526
if hasattr(dbus.service.Object, u"__del__"):
527
dbus.service.Object.__del__(self, *args, **kwargs)
528
Client.__del__(self, *args, **kwargs)
530
def checker_callback(self, pid, condition, command,
532
self.checker_callback_tag = None
535
self.PropertyChanged(dbus.String(u"checker_running"),
536
dbus.Boolean(False, variant_level=1))
537
if os.WIFEXITED(condition):
538
exitstatus = os.WEXITSTATUS(condition)
540
self.CheckerCompleted(dbus.Int16(exitstatus),
541
dbus.Int64(condition),
542
dbus.String(command))
545
self.CheckerCompleted(dbus.Int16(-1),
546
dbus.Int64(condition),
547
dbus.String(command))
549
return Client.checker_callback(self, pid, condition, command,
552
def checked_ok(self, *args, **kwargs):
553
r = Client.checked_ok(self, *args, **kwargs)
555
self.PropertyChanged(
556
dbus.String(u"last_checked_ok"),
557
(self._datetime_to_dbus(self.last_checked_ok,
561
def start_checker(self, *args, **kwargs):
562
old_checker = self.checker
563
if self.checker is not None:
564
old_checker_pid = self.checker.pid
566
old_checker_pid = None
567
r = Client.start_checker(self, *args, **kwargs)
568
# Only if new checker process was started
569
if (self.checker is not None
570
and old_checker_pid != self.checker.pid):
572
self.CheckerStarted(self.current_checker_command)
573
self.PropertyChanged(
574
dbus.String(u"checker_running"),
575
dbus.Boolean(True, variant_level=1))
578
def stop_checker(self, *args, **kwargs):
579
old_checker = getattr(self, u"checker", None)
580
r = Client.stop_checker(self, *args, **kwargs)
581
if (old_checker is not None
582
and getattr(self, u"checker", None) is None):
583
self.PropertyChanged(dbus.String(u"checker_running"),
584
dbus.Boolean(False, variant_level=1))
587
## D-Bus methods & signals
588
_interface = u"se.bsnet.fukt.Mandos.Client"
591
@dbus.service.method(_interface)
593
return self.checked_ok()
595
# CheckerCompleted - signal
596
@dbus.service.signal(_interface, signature=u"nxs")
597
def CheckerCompleted(self, exitcode, waitstatus, command):
601
# CheckerStarted - signal
602
@dbus.service.signal(_interface, signature=u"s")
603
def CheckerStarted(self, command):
607
# GetAllProperties - method
608
@dbus.service.method(_interface, out_signature=u"a{sv}")
609
def GetAllProperties(self):
611
return dbus.Dictionary({
612
dbus.String(u"name"):
613
dbus.String(self.name, variant_level=1),
614
dbus.String(u"fingerprint"):
615
dbus.String(self.fingerprint, variant_level=1),
616
dbus.String(u"host"):
617
dbus.String(self.host, variant_level=1),
618
dbus.String(u"created"):
619
self._datetime_to_dbus(self.created,
621
dbus.String(u"last_enabled"):
622
(self._datetime_to_dbus(self.last_enabled,
624
if self.last_enabled is not None
625
else dbus.Boolean(False, variant_level=1)),
626
dbus.String(u"enabled"):
627
dbus.Boolean(self.enabled, variant_level=1),
628
dbus.String(u"last_checked_ok"):
629
(self._datetime_to_dbus(self.last_checked_ok,
631
if self.last_checked_ok is not None
632
else dbus.Boolean (False, variant_level=1)),
633
dbus.String(u"timeout"):
634
dbus.UInt64(self.timeout_milliseconds(),
636
dbus.String(u"interval"):
637
dbus.UInt64(self.interval_milliseconds(),
639
dbus.String(u"checker"):
640
dbus.String(self.checker_command,
642
dbus.String(u"checker_running"):
643
dbus.Boolean(self.checker is not None,
645
dbus.String(u"object_path"):
646
dbus.ObjectPath(self.dbus_object_path,
650
# IsStillValid - method
651
@dbus.service.method(_interface, out_signature=u"b")
652
def IsStillValid(self):
653
return self.still_valid()
655
# PropertyChanged - signal
656
@dbus.service.signal(_interface, signature=u"sv")
657
def PropertyChanged(self, property, value):
661
# ReceivedSecret - signal
662
@dbus.service.signal(_interface)
663
def ReceivedSecret(self):
668
@dbus.service.signal(_interface)
673
# SetChecker - method
674
@dbus.service.method(_interface, in_signature=u"s")
675
def SetChecker(self, checker):
676
"D-Bus setter method"
677
self.checker_command = checker
679
self.PropertyChanged(dbus.String(u"checker"),
680
dbus.String(self.checker_command,
684
@dbus.service.method(_interface, in_signature=u"s")
685
def SetHost(self, host):
686
"D-Bus setter method"
689
self.PropertyChanged(dbus.String(u"host"),
690
dbus.String(self.host, variant_level=1))
692
# SetInterval - method
693
@dbus.service.method(_interface, in_signature=u"t")
694
def SetInterval(self, milliseconds):
695
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
697
self.PropertyChanged(dbus.String(u"interval"),
698
(dbus.UInt64(self.interval_milliseconds(),
702
@dbus.service.method(_interface, in_signature=u"ay",
704
def SetSecret(self, secret):
705
"D-Bus setter method"
706
self.secret = str(secret)
708
# SetTimeout - method
709
@dbus.service.method(_interface, in_signature=u"t")
710
def SetTimeout(self, milliseconds):
711
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
713
self.PropertyChanged(dbus.String(u"timeout"),
714
(dbus.UInt64(self.timeout_milliseconds(),
718
@dbus.service.method(_interface)
723
# StartChecker - method
724
@dbus.service.method(_interface)
725
def StartChecker(self):
730
@dbus.service.method(_interface)
735
# StopChecker - method
736
@dbus.service.method(_interface)
737
def StopChecker(self):
743
class ClientHandler(socketserver.BaseRequestHandler, object):
744
"""A class to handle client connections.
746
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.
747
368
Note: This will run in its own forked process."""
749
370
def handle(self):
750
logger.info(u"TCP connection from: %s",
751
unicode(self.client_address))
752
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
753
# Open IPC pipe to parent process
754
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
755
session = (gnutls.connection
756
.ClientSession(self.request,
760
line = self.request.makefile().readline()
761
logger.debug(u"Protocol version: %r", line)
763
if int(line.strip().split()[0]) > 1:
765
except (ValueError, IndexError, RuntimeError), error:
766
logger.error(u"Unknown protocol version: %s", error)
769
# Note: gnutls.connection.X509Credentials is really a
770
# generic GnuTLS certificate credentials object so long as
771
# no X.509 keys are added to it. Therefore, we can use it
772
# here despite using OpenPGP certificates.
774
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
775
# u"+AES-256-CBC", u"+SHA1",
776
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
778
# Use a fallback default, since this MUST be set.
779
priority = self.server.gnutls_priority
782
(gnutls.library.functions
783
.gnutls_priority_set_direct(session._c_object,
788
except gnutls.errors.GNUTLSError, error:
789
logger.warning(u"Handshake failed: %s", error)
790
# Do not run session.bye() here: the session is not
791
# established. Just abandon the request.
793
logger.debug(u"Handshake succeeded")
795
fpr = self.fingerprint(self.peer_certificate(session))
796
except (TypeError, gnutls.errors.GNUTLSError), error:
797
logger.warning(u"Bad certificate: %s", error)
800
logger.debug(u"Fingerprint: %s", fpr)
802
for c in self.server.clients:
803
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",
807
ipc.write(u"NOTFOUND %s\n" % fpr)
810
# Have to check if client.still_valid(), since it is
811
# possible that the client timed out while establishing
812
# the GnuTLS session.
813
if not client.still_valid():
814
ipc.write(u"INVALID %s\n" % client.name)
817
ipc.write(u"SENDING %s\n" % client.name)
819
while sent_size < len(client.secret):
820
sent = session.send(client.secret[sent_size:])
821
logger.debug(u"Sent: %d, remaining: %d",
822
sent, len(client.secret)
823
- (sent_size + sent))
412
logger.debug(u"Client not found for fingerprint: %s",
828
def peer_certificate(session):
829
"Return the peer's OpenPGP certificate as a bytestring"
830
# If not an OpenPGP certificate...
831
if (gnutls.library.functions
832
.gnutls_certificate_type_get(session._c_object)
833
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
834
# ...do the normal thing
835
return session.peer_certificate
836
list_size = ctypes.c_uint(1)
837
cert_list = (gnutls.library.functions
838
.gnutls_certificate_get_peers
839
(session._c_object, ctypes.byref(list_size)))
840
if not bool(cert_list) and list_size.value != 0:
841
raise gnutls.errors.GNUTLSError(u"error getting peer"
843
if list_size.value == 0:
846
return ctypes.string_at(cert.data, cert.size)
849
def fingerprint(openpgp):
850
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
851
# New GnuTLS "datum" with the OpenPGP public key
852
datum = (gnutls.library.types
853
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
856
ctypes.c_uint(len(openpgp))))
857
# New empty GnuTLS certificate
858
crt = gnutls.library.types.gnutls_openpgp_crt_t()
859
(gnutls.library.functions
860
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
861
# Import the OpenPGP public key into the certificate
862
(gnutls.library.functions
863
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
864
gnutls.library.constants
865
.GNUTLS_OPENPGP_FMT_RAW))
866
# Verify the self signature in the key
867
crtverify = ctypes.c_uint()
868
(gnutls.library.functions
869
.gnutls_openpgp_crt_verify_self(crt, 0,
870
ctypes.byref(crtverify)))
871
if crtverify.value != 0:
872
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
873
raise (gnutls.errors.CertificateSecurityError
875
# New buffer for the fingerprint
876
buf = ctypes.create_string_buffer(20)
877
buf_len = ctypes.c_size_t()
878
# Get the fingerprint from the certificate into the buffer
879
(gnutls.library.functions
880
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
881
ctypes.byref(buf_len)))
882
# Deinit the certificate
883
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
884
# Convert the buffer to a Python bytestring
885
fpr = ctypes.string_at(buf, buf_len.value)
886
# Convert the bytestring to hexadecimal notation
887
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
891
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
892
"""Like socketserver.ForkingMixIn, but also pass a pipe.
894
Assumes a gobject.MainLoop event loop.
896
def process_request(self, request, client_address):
897
"""Overrides and wraps the original process_request().
899
This function creates a new pipe in self.pipe
901
self.pipe = os.pipe()
902
super(ForkingMixInWithPipe,
903
self).process_request(request, client_address)
904
os.close(self.pipe[1]) # close write end
905
# Call "handle_ipc" for both data and EOF events
906
gobject.io_add_watch(self.pipe[0],
907
gobject.IO_IN | gobject.IO_HUP,
909
def handle_ipc(source, condition):
910
"""Dummy function; override as necessary"""
915
class IPv6_TCPServer(ForkingMixInWithPipe,
916
socketserver.TCPServer, object):
917
"""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.
920
enabled: Boolean; whether this server is activated yet
921
interface: None or a network interface name (string)
922
use_ipv6: Boolean; to use IPv6 or not
924
clients: set of Client objects
925
gnutls_priority GnuTLS priority string
926
use_dbus: Boolean; to emit D-Bus signals or not
429
options: Command line options
430
clients: Set() of Client objects
928
def __init__(self, server_address, RequestHandlerClass,
929
interface=None, use_ipv6=True, clients=None,
930
gnutls_priority=None, use_dbus=True):
932
self.interface = interface
934
self.address_family = socket.AF_INET6
935
self.clients = clients
936
self.use_dbus = use_dbus
937
self.gnutls_priority = gnutls_priority
938
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)
940
441
def server_bind(self):
941
442
"""This overrides the normal server_bind() function
942
443
to bind to an interface if one was specified, and also NOT to
943
444
bind to an address or port if they were not specified."""
944
if self.interface is not None:
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
946
450
self.socket.setsockopt(socket.SOL_SOCKET,
948
str(self.interface + u'\0'))
451
socket.SO_BINDTODEVICE,
452
self.options.interface)
949
453
except socket.error, error:
950
454
if error[0] == errno.EPERM:
951
logger.error(u"No permission to"
952
u" bind to interface %s",
455
logger.warning(u"No permission to"
456
u" bind to interface %s",
457
self.options.interface)
956
460
# Only bind(2) the socket if we really need to.
957
461
if self.server_address[0] or self.server_address[1]:
958
462
if not self.server_address[0]:
959
if self.address_family == socket.AF_INET6:
960
any_address = u"::" # in6addr_any
962
any_address = socket.INADDR_ANY
963
self.server_address = (any_address,
464
self.server_address = (in6addr_any,
964
465
self.server_address[1])
965
elif not self.server_address[1]:
466
elif self.server_address[1] is None:
966
467
self.server_address = (self.server_address[0],
969
# self.server_address = (self.server_address[0],
974
return socketserver.TCPServer.server_bind(self)
975
def server_activate(self):
977
return socketserver.TCPServer.server_activate(self)
980
def handle_ipc(self, source, condition, file_objects={}):
982
gobject.IO_IN: u"IN", # There is data to read.
983
gobject.IO_OUT: u"OUT", # Data can be written (without
985
gobject.IO_PRI: u"PRI", # There is urgent data to read.
986
gobject.IO_ERR: u"ERR", # Error condition.
987
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
988
# broken, usually for pipes and
991
conditions_string = ' | '.join(name
993
condition_names.iteritems()
995
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
998
# Turn the pipe file descriptor into a Python file object
999
if source not in file_objects:
1000
file_objects[source] = os.fdopen(source, u"r", 1)
1002
# Read a line from the file object
1003
cmdline = file_objects[source].readline()
1004
if not cmdline: # Empty line means end of file
1005
# close the IPC pipe
1006
file_objects[source].close()
1007
del file_objects[source]
1009
# Stop calling this function
1012
logger.debug(u"IPC command: %r", cmdline)
1014
# Parse and act on command
1015
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1017
if cmd == u"NOTFOUND":
1018
logger.warning(u"Client not found for fingerprint: %s",
1022
mandos_dbus_service.ClientNotFound(args)
1023
elif cmd == u"INVALID":
1024
for client in self.clients:
1025
if client.name == args:
1026
logger.warning(u"Client %s is invalid", args)
1032
logger.error(u"Unknown client %s is invalid", args)
1033
elif cmd == u"SENDING":
1034
for client in self.clients:
1035
if client.name == args:
1036
logger.info(u"Sending secret to %s", client.name)
1040
client.ReceivedSecret()
1043
logger.error(u"Sending secret to unknown client %s",
1046
logger.error(u"Unknown IPC command: %r", cmdline)
1048
# Keep calling this function
469
return super(type(self), self).server_bind()
1052
472
def string_to_delta(interval):
1053
473
"""Parse a string and return a datetime.timedelta
1055
>>> string_to_delta(u'7d')
475
>>> string_to_delta('7d')
1056
476
datetime.timedelta(7)
1057
>>> string_to_delta(u'60s')
477
>>> string_to_delta('60s')
1058
478
datetime.timedelta(0, 60)
1059
>>> string_to_delta(u'60m')
479
>>> string_to_delta('60m')
1060
480
datetime.timedelta(0, 3600)
1061
>>> string_to_delta(u'24h')
481
>>> string_to_delta('24h')
1062
482
datetime.timedelta(1)
1063
483
>>> string_to_delta(u'1w')
1064
484
datetime.timedelta(7)
1065
>>> string_to_delta(u'5m 30s')
1066
datetime.timedelta(0, 330)
1068
timevalue = datetime.timedelta(0)
1069
for s in interval.split():
1071
suffix = unicode(s[-1])
1074
delta = datetime.timedelta(value)
1075
elif suffix == u"s":
1076
delta = datetime.timedelta(0, value)
1077
elif suffix == u"m":
1078
delta = datetime.timedelta(0, 0, 0, 0, value)
1079
elif suffix == u"h":
1080
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1081
elif suffix == u"w":
1082
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1085
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)
1086
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",
1091
576
def if_nametoindex(interface):
1092
"""Call the C function if_nametoindex(), or equivalent
1094
Note: This function cannot accept a unicode string."""
1095
global if_nametoindex
577
"""Call the C function if_nametoindex()"""
1097
if_nametoindex = (ctypes.cdll.LoadLibrary
1098
(ctypes.util.find_library(u"c"))
579
libc = ctypes.cdll.LoadLibrary("libc.so.6")
580
return libc.if_nametoindex(interface)
1100
581
except (OSError, AttributeError):
1101
logger.warning(u"Doing if_nametoindex the hard way")
1102
def if_nametoindex(interface):
1103
"Get an interface index the hard way, i.e. using fcntl()"
1104
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1105
with closing(socket.socket()) as s:
1106
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1107
struct.pack(str(u"16s16x"),
1109
interface_index = struct.unpack(str(u"I"),
1111
return interface_index
1112
return if_nametoindex(interface)
1115
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):
1116
596
"""See daemon(3). Standard BSD Unix function.
1118
597
This should really exist as os.daemon, but it doesn't (yet)."""
1127
604
# Close all standard open file descriptors
1128
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
605
null = os.open("/dev/null", os.O_NOCTTY | os.O_RDWR)
1129
606
if not stat.S_ISCHR(os.fstat(null).st_mode):
1130
607
raise OSError(errno.ENODEV,
1131
u"/dev/null not a character device")
608
"/dev/null not a character device")
1132
609
os.dup2(null, sys.stdin.fileno())
1133
610
os.dup2(null, sys.stdout.fileno())
1134
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:
1141
######################################################################
1142
# Parsing of options, both command line and config file
1144
parser = optparse.OptionParser(version = "%%prog %s" % version)
1145
parser.add_option("-i", u"--interface", type=u"string",
1146
metavar="IF", help=u"Bind to interface IF")
1147
parser.add_option("-a", u"--address", type=u"string",
1148
help=u"Address to listen for requests on")
1149
parser.add_option("-p", u"--port", type=u"int",
1150
help=u"Port number to receive requests on")
1151
parser.add_option("--check", action=u"store_true",
1152
help=u"Run self-test")
1153
parser.add_option("--debug", action=u"store_true",
1154
help=u"Debug mode; run in foreground and log to"
1156
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1157
u" priority string (see GnuTLS documentation)")
1158
parser.add_option("--servicename", type=u"string",
1159
metavar=u"NAME", help=u"Zeroconf service name")
1160
parser.add_option("--configdir", type=u"string",
1161
default=u"/etc/mandos", metavar=u"DIR",
1162
help=u"Directory to search for configuration"
1164
parser.add_option("--no-dbus", action=u"store_false",
1165
dest=u"use_dbus", help=u"Do not provide D-Bus"
1166
u" system bus interface")
1167
parser.add_option("--no-ipv6", action=u"store_false",
1168
dest=u"use_ipv6", help=u"Do not use IPv6")
1169
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()
1171
651
if options.check:
1173
653
doctest.testmod()
1176
# Default values for config file for server-global settings
1177
server_defaults = { u"interface": u"",
1182
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1183
u"servicename": u"Mandos",
1184
u"use_dbus": u"True",
1185
u"use_ipv6": u"True",
1188
# Parse config file for server-global settings
1189
server_config = configparser.SafeConfigParser(server_defaults)
1191
server_config.read(os.path.join(options.configdir,
1193
# Convert the SafeConfigParser object to a dict
1194
server_settings = server_config.defaults()
1195
# Use the appropriate methods on the non-string config options
1196
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1197
server_settings[option] = server_config.getboolean(u"DEFAULT",
1199
if server_settings["port"]:
1200
server_settings["port"] = server_config.getint(u"DEFAULT",
1204
# Override the settings from the config file with command line
1206
for option in (u"interface", u"address", u"port", u"debug",
1207
u"priority", u"servicename", u"configdir",
1208
u"use_dbus", u"use_ipv6"):
1209
value = getattr(options, option)
1210
if value is not None:
1211
server_settings[option] = value
1213
# Force all strings to be unicode
1214
for option in server_settings.keys():
1215
if type(server_settings[option]) is str:
1216
server_settings[option] = unicode(server_settings[option])
1217
# Now we have our good server settings in "server_settings"
1219
##################################################################
1222
debug = server_settings[u"debug"]
1223
use_dbus = server_settings[u"use_dbus"]
1224
use_ipv6 = server_settings[u"use_ipv6"]
1227
syslogger.setLevel(logging.WARNING)
1228
console.setLevel(logging.WARNING)
1230
if server_settings[u"servicename"] != u"Mandos":
1231
syslogger.setFormatter(logging.Formatter
1232
(u'Mandos (%s) [%%(process)d]:'
1233
u' %%(levelname)s: %%(message)s'
1234
% server_settings[u"servicename"]))
1236
# Parse config file with clients
1237
client_defaults = { u"timeout": u"1h",
1239
u"checker": u"fping -q -- %%(host)s",
1242
client_config = configparser.SafeConfigParser(client_defaults)
1243
client_config.read(os.path.join(server_settings[u"configdir"],
1246
global mandos_dbus_service
1247
mandos_dbus_service = None
1250
tcp_server = IPv6_TCPServer((server_settings[u"address"],
1251
server_settings[u"port"]),
1254
server_settings[u"interface"],
1258
server_settings[u"priority"],
1260
pidfilename = u"/var/run/mandos.pid"
1262
pidfile = open(pidfilename, u"w")
1264
logger.error(u"Could not open file %r", pidfilename)
1267
uid = pwd.getpwnam(u"_mandos").pw_uid
1268
gid = pwd.getpwnam(u"_mandos").pw_gid
1271
uid = pwd.getpwnam(u"mandos").pw_uid
1272
gid = pwd.getpwnam(u"mandos").pw_gid
1275
uid = pwd.getpwnam(u"nobody").pw_uid
1276
gid = pwd.getpwnam(u"nobody").pw_gid
1283
except OSError, error:
1284
if error[0] != errno.EPERM:
1287
# Enable all possible GnuTLS debugging
1289
# "Use a log level over 10 to enable all debugging options."
1291
gnutls.library.functions.gnutls_global_set_log_level(11)
1293
@gnutls.library.types.gnutls_log_func
1294
def debug_gnutls(level, string):
1295
logger.debug(u"GnuTLS: %s", string[:-1])
1297
(gnutls.library.functions
1298
.gnutls_global_set_log_function(debug_gnutls))
1301
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1302
service = AvahiService(name = server_settings[u"servicename"],
1303
servicetype = u"_mandos._tcp",
1304
protocol = protocol)
1305
if server_settings["interface"]:
1306
service.interface = (if_nametoindex
1307
(str(server_settings[u"interface"])))
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")
1309
672
global main_loop
1311
675
# From the Avahi example code
1312
676
DBusGMainLoop(set_as_default=True )
1313
677
main_loop = gobject.MainLoop()
1314
678
bus = dbus.SystemBus()
679
server = dbus.Interface(
680
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
681
avahi.DBUS_INTERFACE_SERVER )
1315
682
# End of Avahi example code
1317
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1319
client_class = Client
1321
client_class = ClientDBus
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")
684
debug = options.debug
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
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()))
1355
711
"Cleanup function; run on exit"
713
# From the Avahi example code
714
if not group is None:
717
# End of Avahi example code
1359
720
client = clients.pop()
1360
client.disable_hook = None
721
client.stop_hook = None
1363
724
atexit.register(cleanup)
1366
727
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()
728
signal.signal(signal.SIGHUP, lambda signum, frame: killme())
729
signal.signal(signal.SIGTERM, lambda signum, frame: killme())
1424
731
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]
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")
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
1462
except AvahiError, error:
1463
logger.critical(u"AvahiError: %s", error)
1465
764
except KeyboardInterrupt:
1468
logger.debug(u"Server received KeyboardInterrupt")
1469
logger.debug(u"Server exiting")
1471
770
if __name__ == '__main__':