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
66
28
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:'
89
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
128
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
129
servicetype = None, port = None, TXT = None,
130
domain = u"", host = u"", max_renames = 32768,
131
protocol = avahi.PROTO_UNSPEC):
132
self.interface = interface
134
self.type = servicetype
136
self.TXT = TXT if TXT is not None else []
139
self.rename_count = 0
140
self.max_renames = max_renames
141
self.protocol = protocol
143
"""Derived from the Avahi example code"""
144
if self.rename_count >= self.max_renames:
145
logger.critical(u"No suitable Zeroconf service name found"
146
u" after %i retries, exiting.",
148
raise AvahiServiceError(u"Too many renames")
149
self.name = server.GetAlternativeServiceName(self.name)
150
logger.info(u"Changing Zeroconf service name to %r ...",
152
syslogger.setFormatter(logging.Formatter
153
(u'Mandos (%s) [%%(process)d]:'
154
u' %%(levelname)s: %%(message)s'
158
self.rename_count += 1
160
"""Derived from the Avahi example code"""
161
if group is not None:
164
"""Derived from the Avahi example code"""
167
group = dbus.Interface(bus.get_object
169
server.EntryGroupNew()),
170
avahi.DBUS_INTERFACE_ENTRY_GROUP)
171
group.connect_to_signal('StateChanged',
172
entry_group_state_changed)
173
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
174
service.name, service.type)
176
self.interface, # interface
177
self.protocol, # protocol
178
dbus.UInt32(0), # flags
179
self.name, self.type,
180
self.domain, self.host,
181
dbus.UInt16(self.port),
182
avahi.string_array_to_txt_array(self.TXT))
185
# From the Avahi example code:
186
group = None # our entry group
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
187
53
# End of Avahi example code
190
def _datetime_to_dbus(dt, variant_level=0):
191
"""Convert a UTC datetime.datetime() to a D-Bus type."""
192
return dbus.String(dt.isoformat(), variant_level=variant_level)
195
56
class Client(object):
196
57
"""A representation of a client host served by this server.
199
name: string; from the config file, used in log messages and
59
name: string; from the config file, used in log messages
201
60
fingerprint: string (40 or 32 hexadecimal digits); used to
202
61
uniquely identify the client
203
secret: bytestring; sent verbatim (over TLS) to client
204
host: string; available for use by the checker command
205
created: datetime.datetime(); (UTC) object creation
206
last_enabled: datetime.datetime(); (UTC)
208
last_checked_ok: datetime.datetime(); (UTC) or None
209
timeout: datetime.timedelta(); How long from last_checked_ok
210
until this client is invalid
211
interval: datetime.timedelta(); How often to start a new checker
212
disable_hook: If set, called by disable() as disable_hook(self)
213
checker: subprocess.Popen(); a running checker process used
214
to see if the client lives.
215
'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.
216
73
checker_initiator_tag: a gobject event source tag, or None
217
disable_initiator_tag: - '' -
74
stop_initiator_tag: - '' -
218
75
checker_callback_tag: - '' -
219
76
checker_command: string; External command which is run to check if
220
client lives. %() expansions are done at
77
client lives. %()s expansions are done at
221
78
runtime with vars(self) as dict, so that for
222
79
instance %(name)s can be used in the command.
223
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: - '' -
227
def _datetime_to_milliseconds(dt):
228
"Convert a datetime.datetime() to milliseconds"
229
return ((dt.days * 24 * 60 * 60 * 1000)
230
+ (dt.seconds * 1000)
231
+ (dt.microseconds // 1000))
233
def timeout_milliseconds(self):
234
"Return the 'timeout' attribute in milliseconds"
235
return self._datetime_to_milliseconds(self.timeout)
237
def interval_milliseconds(self):
238
"Return the 'interval' attribute in milliseconds"
239
return self._datetime_to_milliseconds(self.interval)
241
def __init__(self, name = None, disable_hook=None, config=None):
242
"""Note: the 'checker' key in 'config' sets the
243
'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):
248
logger.debug(u"Creating client %r", self.name)
249
# Uppercase and remove spaces from fingerprint for later
250
# comparison purposes with return value from the fingerprint()
252
self.fingerprint = (config[u"fingerprint"].upper()
254
logger.debug(u" Fingerprint: %s", self.fingerprint)
255
if u"secret" in config:
256
self.secret = config[u"secret"].decode(u"base64")
257
elif u"secfile" in config:
258
with closing(open(os.path.expanduser
260
(config[u"secfile"])))) as secfile:
261
self.secret = secfile.read()
263
raise TypeError(u"No secret or secfile for client %s"
265
self.host = config.get(u"host", u"")
266
self.created = datetime.datetime.utcnow()
268
self.last_enabled = None
269
self.last_checked_ok = None
270
self.timeout = string_to_delta(config[u"timeout"])
271
self.interval = string_to_delta(config[u"interval"])
272
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
273
138
self.checker = None
274
139
self.checker_initiator_tag = None
275
self.disable_initiator_tag = None
140
self.stop_initiator_tag = None
276
141
self.checker_callback_tag = None
277
self.checker_command = config[u"checker"]
278
self.current_checker_command = None
279
self.last_connect = None
282
"""Start this client's checker and timeout hooks"""
283
self.last_enabled = datetime.datetime.utcnow()
142
self.check_command = checker
144
"""Start this clients checker and timeout hooks"""
284
145
# Schedule a new checker to be started an 'interval' from now,
285
146
# and every interval from then on.
286
self.checker_initiator_tag = (gobject.timeout_add
287
(self.interval_milliseconds(),
147
self.checker_initiator_tag = gobject.timeout_add\
148
(self._interval_milliseconds,
289
150
# Also start a new checker *right now*.
290
151
self.start_checker()
291
# Schedule a disable() when 'timeout' has passed
292
self.disable_initiator_tag = (gobject.timeout_add
293
(self.timeout_milliseconds(),
298
"""Disable this client."""
299
if not getattr(self, "enabled", False):
301
logger.info(u"Disabling client %s", self.name)
302
if getattr(self, u"disable_initiator_tag", False):
303
gobject.source_remove(self.disable_initiator_tag)
304
self.disable_initiator_tag = None
305
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:
306
166
gobject.source_remove(self.checker_initiator_tag)
307
167
self.checker_initiator_tag = None
308
168
self.stop_checker()
309
if self.disable_hook:
310
self.disable_hook(self)
312
171
# Do not run this again if called by a gobject.timeout_add
315
173
def __del__(self):
316
self.disable_hook = None
319
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):
320
185
"""The checker has completed, so take appropriate actions."""
321
self.checker_callback_tag = None
323
if os.WIFEXITED(condition):
324
exitstatus = os.WEXITSTATUS(condition)
326
logger.info(u"Checker for %(name)s succeeded",
330
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):
333
197
logger.warning(u"Checker for %(name)s crashed?",
336
def checked_ok(self):
337
"""Bump up the timeout for this client.
339
This should only be called when the client has been seen,
342
self.last_checked_ok = datetime.datetime.utcnow()
343
gobject.source_remove(self.disable_initiator_tag)
344
self.disable_initiator_tag = (gobject.timeout_add
345
(self.timeout_milliseconds(),
200
logger.debug(u"Checker for %(name)s failed",
203
self.checker_callback_tag = None
348
204
def start_checker(self):
349
205
"""Start a new checker subprocess if one is not running.
351
206
If a checker already exists, leave it running and do
353
# The reason for not killing a running checker is that if we
354
# did that, then if a checker (for some reason) started
355
# running slowly and taking more than 'interval' time, the
356
# client would inevitably timeout, since no checker would get
357
# a chance to run to completion. If we instead leave running
358
# checkers alone, the checker would have to take more time
359
# than 'timeout' for the client to be declared invalid, which
360
# is as it should be.
362
# If a checker exists, make sure it is not a zombie
363
if self.checker is not None:
364
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
366
logger.warning(u"Checker was a zombie")
367
gobject.source_remove(self.checker_callback_tag)
368
self.checker_callback(pid, status,
369
self.current_checker_command)
370
# Start a new checker if needed
371
208
if self.checker is None:
209
logger.debug(u"Starting checker for %s",
373
# In case checker_command has exactly one % operator
374
command = self.checker_command % self.host
212
command = self.check_command % self.fqdn
375
213
except TypeError:
376
# Escape attributes for the shell
377
escaped_attrs = dict((key,
378
re.escape(unicode(str(val),
214
escaped_attrs = dict((key, re.escape(str(val)))
382
216
vars(self).iteritems())
384
command = self.checker_command % escaped_attrs
218
command = self.check_command % escaped_attrs
385
219
except TypeError, error:
386
logger.error(u'Could not format string "%s":'
387
u' %s', self.checker_command, error)
220
logger.critical(u'Could not format string "%s": %s',
221
self.check_command, error)
388
222
return True # Try again later
389
self.current_checker_command = command
391
logger.info(u"Starting checker %r for %s",
393
# We don't need to redirect stdout and stderr, since
394
# in normal mode, that is already done by daemon(),
395
# and in debug mode we don't want to. (Stdin is
396
# always replaced by /dev/null.)
397
self.checker = subprocess.Popen(command,
399
shell=True, cwd=u"/")
400
self.checker_callback_tag = (gobject.child_watch_add
402
self.checker_callback,
404
# The checker may have completed before the gobject
405
# watch was added. Check for this.
406
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
408
gobject.source_remove(self.checker_callback_tag)
409
self.checker_callback(pid, status, command)
410
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:
411
234
logger.error(u"Failed to start subprocess: %s",
413
236
# Re-run this periodically if run by gobject.timeout_add
416
238
def stop_checker(self):
417
239
"""Force the checker process, if any, to stop."""
418
if self.checker_callback_tag:
419
gobject.source_remove(self.checker_callback_tag)
420
self.checker_callback_tag = None
421
if getattr(self, u"checker", None) is None:
240
if not hasattr(self, "checker") or self.checker is None:
423
logger.debug(u"Stopping checker for %(name)s", vars(self))
425
os.kill(self.checker.pid, signal.SIGTERM)
427
#if self.checker.poll() is None:
428
# os.kill(self.checker.pid, signal.SIGKILL)
429
except OSError, error:
430
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)
432
247
self.checker = None
434
def still_valid(self):
248
def still_valid(self, now=None):
435
249
"""Has the timeout not yet passed for this client?"""
436
if not getattr(self, u"enabled", False):
438
now = datetime.datetime.utcnow()
439
if self.last_checked_ok is None:
251
now = datetime.datetime.now()
252
if self.last_seen is None:
440
253
return now < (self.created + self.timeout)
442
return now < (self.last_checked_ok + self.timeout)
445
class ClientDBus(Client, dbus.service.Object):
446
"""A Client class using D-Bus
449
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
451
# dbus.service.Object doesn't use super(), so we can't either.
453
def __init__(self, *args, **kwargs):
454
Client.__init__(self, *args, **kwargs)
455
# Only now, when this client is initialized, can it show up on
457
self.dbus_object_path = (dbus.ObjectPath
459
+ self.name.replace(u".", u"_")))
460
dbus.service.Object.__init__(self, bus,
461
self.dbus_object_path)
463
oldstate = getattr(self, u"enabled", False)
464
r = Client.enable(self)
465
if oldstate != self.enabled:
467
self.PropertyChanged(dbus.String(u"enabled"),
468
dbus.Boolean(True, variant_level=1))
469
self.PropertyChanged(dbus.String(u"last_enabled"),
470
(_datetime_to_dbus(self.last_enabled,
474
def disable(self, signal = True):
475
oldstate = getattr(self, u"enabled", False)
476
r = Client.disable(self)
477
if signal and oldstate != self.enabled:
479
self.PropertyChanged(dbus.String(u"enabled"),
480
dbus.Boolean(False, variant_level=1))
483
def __del__(self, *args, **kwargs):
485
self.remove_from_connection()
488
if hasattr(dbus.service.Object, u"__del__"):
489
dbus.service.Object.__del__(self, *args, **kwargs)
490
Client.__del__(self, *args, **kwargs)
492
def checker_callback(self, pid, condition, command,
494
self.checker_callback_tag = None
497
self.PropertyChanged(dbus.String(u"checker_running"),
498
dbus.Boolean(False, variant_level=1))
499
if os.WIFEXITED(condition):
500
exitstatus = os.WEXITSTATUS(condition)
502
self.CheckerCompleted(dbus.Int16(exitstatus),
503
dbus.Int64(condition),
504
dbus.String(command))
507
self.CheckerCompleted(dbus.Int16(-1),
508
dbus.Int64(condition),
509
dbus.String(command))
511
return Client.checker_callback(self, pid, condition, command,
514
def checked_ok(self, *args, **kwargs):
515
r = Client.checked_ok(self, *args, **kwargs)
517
self.PropertyChanged(
518
dbus.String(u"last_checked_ok"),
519
(_datetime_to_dbus(self.last_checked_ok,
523
def start_checker(self, *args, **kwargs):
524
old_checker = self.checker
525
if self.checker is not None:
526
old_checker_pid = self.checker.pid
528
old_checker_pid = None
529
r = Client.start_checker(self, *args, **kwargs)
530
# Only if new checker process was started
531
if (self.checker is not None
532
and old_checker_pid != self.checker.pid):
534
self.CheckerStarted(self.current_checker_command)
535
self.PropertyChanged(
536
dbus.String(u"checker_running"),
537
dbus.Boolean(True, variant_level=1))
540
def stop_checker(self, *args, **kwargs):
541
old_checker = getattr(self, u"checker", None)
542
r = Client.stop_checker(self, *args, **kwargs)
543
if (old_checker is not None
544
and getattr(self, u"checker", None) is None):
545
self.PropertyChanged(dbus.String(u"checker_running"),
546
dbus.Boolean(False, variant_level=1))
549
## D-Bus methods & signals
550
_interface = u"se.bsnet.fukt.Mandos.Client"
553
@dbus.service.method(_interface)
555
return self.checked_ok()
557
# CheckerCompleted - signal
558
@dbus.service.signal(_interface, signature=u"nxs")
559
def CheckerCompleted(self, exitcode, waitstatus, command):
563
# CheckerStarted - signal
564
@dbus.service.signal(_interface, signature=u"s")
565
def CheckerStarted(self, command):
569
# GetAllProperties - method
570
@dbus.service.method(_interface, out_signature=u"a{sv}")
571
def GetAllProperties(self):
573
return dbus.Dictionary({
574
dbus.String(u"name"):
575
dbus.String(self.name, variant_level=1),
576
dbus.String(u"fingerprint"):
577
dbus.String(self.fingerprint, variant_level=1),
578
dbus.String(u"host"):
579
dbus.String(self.host, variant_level=1),
580
dbus.String(u"created"):
581
_datetime_to_dbus(self.created, variant_level=1),
582
dbus.String(u"last_enabled"):
583
(_datetime_to_dbus(self.last_enabled,
585
if self.last_enabled is not None
586
else dbus.Boolean(False, variant_level=1)),
587
dbus.String(u"enabled"):
588
dbus.Boolean(self.enabled, variant_level=1),
589
dbus.String(u"last_checked_ok"):
590
(_datetime_to_dbus(self.last_checked_ok,
592
if self.last_checked_ok is not None
593
else dbus.Boolean (False, variant_level=1)),
594
dbus.String(u"timeout"):
595
dbus.UInt64(self.timeout_milliseconds(),
597
dbus.String(u"interval"):
598
dbus.UInt64(self.interval_milliseconds(),
600
dbus.String(u"checker"):
601
dbus.String(self.checker_command,
603
dbus.String(u"checker_running"):
604
dbus.Boolean(self.checker is not None,
606
dbus.String(u"object_path"):
607
dbus.ObjectPath(self.dbus_object_path,
611
# IsStillValid - method
612
@dbus.service.method(_interface, out_signature=u"b")
613
def IsStillValid(self):
614
return self.still_valid()
616
# PropertyChanged - signal
617
@dbus.service.signal(_interface, signature=u"sv")
618
def PropertyChanged(self, property, value):
622
# ReceivedSecret - signal
623
@dbus.service.signal(_interface)
624
def ReceivedSecret(self):
629
@dbus.service.signal(_interface)
634
# SetChecker - method
635
@dbus.service.method(_interface, in_signature=u"s")
636
def SetChecker(self, checker):
637
"D-Bus setter method"
638
self.checker_command = checker
640
self.PropertyChanged(dbus.String(u"checker"),
641
dbus.String(self.checker_command,
645
@dbus.service.method(_interface, in_signature=u"s")
646
def SetHost(self, host):
647
"D-Bus setter method"
650
self.PropertyChanged(dbus.String(u"host"),
651
dbus.String(self.host, variant_level=1))
653
# SetInterval - method
654
@dbus.service.method(_interface, in_signature=u"t")
655
def SetInterval(self, milliseconds):
656
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
658
self.PropertyChanged(dbus.String(u"interval"),
659
(dbus.UInt64(self.interval_milliseconds(),
663
@dbus.service.method(_interface, in_signature=u"ay",
665
def SetSecret(self, secret):
666
"D-Bus setter method"
667
self.secret = str(secret)
669
# SetTimeout - method
670
@dbus.service.method(_interface, in_signature=u"t")
671
def SetTimeout(self, milliseconds):
672
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
674
self.PropertyChanged(dbus.String(u"timeout"),
675
(dbus.UInt64(self.timeout_milliseconds(),
679
@dbus.service.method(_interface)
684
# StartChecker - method
685
@dbus.service.method(_interface)
686
def StartChecker(self):
691
@dbus.service.method(_interface)
696
# StopChecker - method
697
@dbus.service.method(_interface)
698
def StopChecker(self):
704
class ClientHandler(socketserver.BaseRequestHandler, object):
705
"""A class to handle client connections.
707
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.
708
307
Note: This will run in its own forked process."""
710
309
def handle(self):
711
logger.info(u"TCP connection from: %s",
712
unicode(self.client_address))
713
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
714
# Open IPC pipe to parent process
715
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
716
session = (gnutls.connection
717
.ClientSession(self.request,
721
line = self.request.makefile().readline()
722
logger.debug(u"Protocol version: %r", line)
724
if int(line.strip().split()[0]) > 1:
726
except (ValueError, IndexError, RuntimeError), error:
727
logger.error(u"Unknown protocol version: %s", error)
730
# Note: gnutls.connection.X509Credentials is really a
731
# generic GnuTLS certificate credentials object so long as
732
# no X.509 keys are added to it. Therefore, we can use it
733
# here despite using OpenPGP certificates.
735
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
736
# u"+AES-256-CBC", u"+SHA1",
737
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
739
# Use a fallback default, since this MUST be set.
740
priority = self.server.gnutls_priority
743
(gnutls.library.functions
744
.gnutls_priority_set_direct(session._c_object,
749
except gnutls.errors.GNUTLSError, error:
750
logger.warning(u"Handshake failed: %s", error)
751
# Do not run session.bye() here: the session is not
752
# established. Just abandon the request.
754
logger.debug(u"Handshake succeeded")
756
fpr = self.fingerprint(self.peer_certificate(session))
757
except (TypeError, gnutls.errors.GNUTLSError), error:
758
logger.warning(u"Bad certificate: %s", error)
761
logger.debug(u"Fingerprint: %s", fpr)
763
for c in self.server.clients:
764
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",
768
ipc.write(u"NOTFOUND %s\n" % fpr)
771
# Have to check if client.still_valid(), since it is
772
# possible that the client timed out while establishing
773
# the GnuTLS session.
774
if not client.still_valid():
775
ipc.write(u"INVALID %s\n" % client.name)
778
ipc.write(u"SENDING %s\n" % client.name)
780
while sent_size < len(client.secret):
781
sent = session.send(client.secret[sent_size:])
782
logger.debug(u"Sent: %d, remaining: %d",
783
sent, len(client.secret)
784
- (sent_size + sent))
351
logger.debug(u"Client not found for fingerprint: %s",
789
def peer_certificate(session):
790
"Return the peer's OpenPGP certificate as a bytestring"
791
# If not an OpenPGP certificate...
792
if (gnutls.library.functions
793
.gnutls_certificate_type_get(session._c_object)
794
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
795
# ...do the normal thing
796
return session.peer_certificate
797
list_size = ctypes.c_uint(1)
798
cert_list = (gnutls.library.functions
799
.gnutls_certificate_get_peers
800
(session._c_object, ctypes.byref(list_size)))
801
if not bool(cert_list) and list_size.value != 0:
802
raise gnutls.errors.GNUTLSError(u"error getting peer"
804
if list_size.value == 0:
807
return ctypes.string_at(cert.data, cert.size)
810
def fingerprint(openpgp):
811
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
812
# New GnuTLS "datum" with the OpenPGP public key
813
datum = (gnutls.library.types
814
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
817
ctypes.c_uint(len(openpgp))))
818
# New empty GnuTLS certificate
819
crt = gnutls.library.types.gnutls_openpgp_crt_t()
820
(gnutls.library.functions
821
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
822
# Import the OpenPGP public key into the certificate
823
(gnutls.library.functions
824
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
825
gnutls.library.constants
826
.GNUTLS_OPENPGP_FMT_RAW))
827
# Verify the self signature in the key
828
crtverify = ctypes.c_uint()
829
(gnutls.library.functions
830
.gnutls_openpgp_crt_verify_self(crt, 0,
831
ctypes.byref(crtverify)))
832
if crtverify.value != 0:
833
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
834
raise (gnutls.errors.CertificateSecurityError
836
# New buffer for the fingerprint
837
buf = ctypes.create_string_buffer(20)
838
buf_len = ctypes.c_size_t()
839
# Get the fingerprint from the certificate into the buffer
840
(gnutls.library.functions
841
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
842
ctypes.byref(buf_len)))
843
# Deinit the certificate
844
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
845
# Convert the buffer to a Python bytestring
846
fpr = ctypes.string_at(buf, buf_len.value)
847
# Convert the bytestring to hexadecimal notation
848
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
852
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
853
"""Like socketserver.ForkingMixIn, but also pass a pipe.
855
Assumes a gobject.MainLoop event loop.
857
def process_request(self, request, client_address):
858
"""Overrides and wraps the original process_request().
860
This function creates a new pipe in self.pipe
862
self.pipe = os.pipe()
863
super(ForkingMixInWithPipe,
864
self).process_request(request, client_address)
865
os.close(self.pipe[1]) # close write end
866
# Call "handle_ipc" for both data and EOF events
867
gobject.io_add_watch(self.pipe[0],
868
gobject.IO_IN | gobject.IO_HUP,
870
def handle_ipc(source, condition):
871
"""Dummy function; override as necessary"""
876
class IPv6_TCPServer(ForkingMixInWithPipe,
877
socketserver.TCPServer, object):
878
"""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.
881
enabled: Boolean; whether this server is activated yet
882
interface: None or a network interface name (string)
883
use_ipv6: Boolean; to use IPv6 or not
885
clients: set of Client objects
886
gnutls_priority GnuTLS priority string
887
use_dbus: Boolean; to emit D-Bus signals or not
368
options: Command line options
369
clients: Set() of Client objects
889
def __init__(self, server_address, RequestHandlerClass,
890
interface=None, use_ipv6=True, clients=None,
891
gnutls_priority=None, use_dbus=True):
893
self.interface = interface
895
self.address_family = socket.AF_INET6
896
self.clients = clients
897
self.use_dbus = use_dbus
898
self.gnutls_priority = gnutls_priority
899
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)
901
380
def server_bind(self):
902
381
"""This overrides the normal server_bind() function
903
382
to bind to an interface if one was specified, and also NOT to
904
383
bind to an address or port if they were not specified."""
905
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
907
389
self.socket.setsockopt(socket.SOL_SOCKET,
909
str(self.interface + u'\0'))
390
socket.SO_BINDTODEVICE,
391
self.options.interface)
910
392
except socket.error, error:
911
393
if error[0] == errno.EPERM:
912
logger.error(u"No permission to"
913
u" bind to interface %s",
394
logger.warning(u"No permission to"
395
u" bind to interface %s",
396
self.options.interface)
917
399
# Only bind(2) the socket if we really need to.
918
400
if self.server_address[0] or self.server_address[1]:
919
401
if not self.server_address[0]:
920
if self.address_family == socket.AF_INET6:
921
any_address = u"::" # in6addr_any
923
any_address = socket.INADDR_ANY
924
self.server_address = (any_address,
403
self.server_address = (in6addr_any,
925
404
self.server_address[1])
926
elif not self.server_address[1]:
405
elif self.server_address[1] is None:
927
406
self.server_address = (self.server_address[0],
930
# self.server_address = (self.server_address[0],
935
return socketserver.TCPServer.server_bind(self)
936
def server_activate(self):
938
return socketserver.TCPServer.server_activate(self)
941
def handle_ipc(self, source, condition, file_objects={}):
943
gobject.IO_IN: u"IN", # There is data to read.
944
gobject.IO_OUT: u"OUT", # Data can be written (without
946
gobject.IO_PRI: u"PRI", # There is urgent data to read.
947
gobject.IO_ERR: u"ERR", # Error condition.
948
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
949
# broken, usually for pipes and
952
conditions_string = ' | '.join(name
954
condition_names.iteritems()
956
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
959
# Turn the pipe file descriptor into a Python file object
960
if source not in file_objects:
961
file_objects[source] = os.fdopen(source, u"r", 1)
963
# Read a line from the file object
964
cmdline = file_objects[source].readline()
965
if not cmdline: # Empty line means end of file
967
file_objects[source].close()
968
del file_objects[source]
970
# Stop calling this function
973
logger.debug(u"IPC command: %r", cmdline)
975
# Parse and act on command
976
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
978
if cmd == u"NOTFOUND":
979
logger.warning(u"Client not found for fingerprint: %s",
983
mandos_dbus_service.ClientNotFound(args)
984
elif cmd == u"INVALID":
985
for client in self.clients:
986
if client.name == args:
987
logger.warning(u"Client %s is invalid", args)
993
logger.error(u"Unknown client %s is invalid", args)
994
elif cmd == u"SENDING":
995
for client in self.clients:
996
if client.name == args:
997
logger.info(u"Sending secret to %s", client.name)
1001
client.ReceivedSecret()
1004
logger.error(u"Sending secret to unknown client %s",
1007
logger.error(u"Unknown IPC command: %r", cmdline)
1009
# Keep calling this function
408
return super(type(self), self).server_bind()
1013
411
def string_to_delta(interval):
1014
412
"""Parse a string and return a datetime.timedelta
1016
>>> string_to_delta(u'7d')
414
>>> string_to_delta('7d')
1017
415
datetime.timedelta(7)
1018
>>> string_to_delta(u'60s')
416
>>> string_to_delta('60s')
1019
417
datetime.timedelta(0, 60)
1020
>>> string_to_delta(u'60m')
418
>>> string_to_delta('60m')
1021
419
datetime.timedelta(0, 3600)
1022
>>> string_to_delta(u'24h')
420
>>> string_to_delta('24h')
1023
421
datetime.timedelta(1)
1024
422
>>> string_to_delta(u'1w')
1025
423
datetime.timedelta(7)
1026
>>> string_to_delta(u'5m 30s')
1027
datetime.timedelta(0, 330)
1029
timevalue = datetime.timedelta(0)
1030
for s in interval.split():
1032
suffix = unicode(s[-1])
1035
delta = datetime.timedelta(value)
1036
elif suffix == u"s":
1037
delta = datetime.timedelta(0, value)
1038
elif suffix == u"m":
1039
delta = datetime.timedelta(0, 0, 0, 0, value)
1040
elif suffix == u"h":
1041
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1042
elif suffix == u"w":
1043
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1046
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)
1047
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:
1052
478
def server_state_changed(state):
1053
"""Derived from the Avahi example code"""
479
"""From the Avahi server example code"""
1054
480
if state == avahi.SERVER_COLLISION:
1055
logger.error(u"Zeroconf server name collision")
481
logger.warning(u"Server name collision")
1057
483
elif state == avahi.SERVER_RUNNING:
1061
487
def entry_group_state_changed(state, error):
1062
"""Derived from the Avahi example code"""
1063
logger.debug(u"Avahi state change: %i", state)
488
"""From the Avahi server example code"""
489
global serviceName, server, rename_count
491
logger.debug(u"state change: %i", state)
1065
493
if state == avahi.ENTRY_GROUP_ESTABLISHED:
1066
logger.debug(u"Zeroconf service established.")
494
logger.debug(u"Service established.")
1067
495
elif state == avahi.ENTRY_GROUP_COLLISION:
1068
logger.warning(u"Zeroconf service name 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.",
1070
510
elif state == avahi.ENTRY_GROUP_FAILURE:
1071
logger.critical(u"Avahi: Error in group state changed %s",
1073
raise AvahiGroupError(u"State changed: %s" % unicode(error))
511
logger.error(u"Error in group state changed %s",
1075
517
def if_nametoindex(interface):
1076
"""Call the C function if_nametoindex(), or equivalent
1078
Note: This function cannot accept a unicode string."""
1079
global if_nametoindex
518
"""Call the C function if_nametoindex()"""
1081
if_nametoindex = (ctypes.cdll.LoadLibrary
1082
(ctypes.util.find_library(u"c"))
520
libc = ctypes.cdll.LoadLibrary("libc.so.6")
521
return libc.if_nametoindex(interface)
1084
522
except (OSError, AttributeError):
1085
logger.warning(u"Doing if_nametoindex the hard way")
1086
def if_nametoindex(interface):
1087
"Get an interface index the hard way, i.e. using fcntl()"
1088
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1089
with closing(socket.socket()) as s:
1090
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1091
struct.pack(str(u"16s16x"),
1093
interface_index = struct.unpack(str(u"I"),
1095
return interface_index
1096
return if_nametoindex(interface)
1099
def daemon(nochdir = False, noclose = False):
1100
"""See daemon(3). Standard BSD Unix function.
1102
This should really exist as os.daemon, but it doesn't (yet)."""
1111
# Close all standard open file descriptors
1112
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1113
if not stat.S_ISCHR(os.fstat(null).st_mode):
1114
raise OSError(errno.ENODEV,
1115
u"/dev/null not a character device")
1116
os.dup2(null, sys.stdin.fileno())
1117
os.dup2(null, sys.stdout.fileno())
1118
os.dup2(null, sys.stderr.fileno())
1125
######################################################################
1126
# Parsing of options, both command line and config file
1128
parser = optparse.OptionParser(version = "%%prog %s" % version)
1129
parser.add_option("-i", u"--interface", type=u"string",
1130
metavar="IF", help=u"Bind to interface IF")
1131
parser.add_option("-a", u"--address", type=u"string",
1132
help=u"Address to listen for requests on")
1133
parser.add_option("-p", u"--port", type=u"int",
1134
help=u"Port number to receive requests on")
1135
parser.add_option("--check", action=u"store_true",
1136
help=u"Run self-test")
1137
parser.add_option("--debug", action=u"store_true",
1138
help=u"Debug mode; run in foreground and log to"
1140
parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1141
u" priority string (see GnuTLS documentation)")
1142
parser.add_option("--servicename", type=u"string",
1143
metavar=u"NAME", help=u"Zeroconf service name")
1144
parser.add_option("--configdir", type=u"string",
1145
default=u"/etc/mandos", metavar=u"DIR",
1146
help=u"Directory to search for configuration"
1148
parser.add_option("--no-dbus", action=u"store_false",
1149
dest=u"use_dbus", help=u"Do not provide D-Bus"
1150
u" system bus interface")
1151
parser.add_option("--no-ipv6", action=u"store_false",
1152
dest=u"use_ipv6", help=u"Do not use IPv6")
1153
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()
1155
567
if options.check:
1157
569
doctest.testmod()
1160
# Default values for config file for server-global settings
1161
server_defaults = { u"interface": u"",
1166
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1167
u"servicename": u"Mandos",
1168
u"use_dbus": u"True",
1169
u"use_ipv6": u"True",
1172
# Parse config file for server-global settings
1173
server_config = configparser.SafeConfigParser(server_defaults)
1175
server_config.read(os.path.join(options.configdir,
1177
# Convert the SafeConfigParser object to a dict
1178
server_settings = server_config.defaults()
1179
# Use the appropriate methods on the non-string config options
1180
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1181
server_settings[option] = server_config.getboolean(u"DEFAULT",
1183
if server_settings["port"]:
1184
server_settings["port"] = server_config.getint(u"DEFAULT",
1188
# Override the settings from the config file with command line
1190
for option in (u"interface", u"address", u"port", u"debug",
1191
u"priority", u"servicename", u"configdir",
1192
u"use_dbus", u"use_ipv6"):
1193
value = getattr(options, option)
1194
if value is not None:
1195
server_settings[option] = value
1197
# Force all strings to be unicode
1198
for option in server_settings.keys():
1199
if type(server_settings[option]) is str:
1200
server_settings[option] = unicode(server_settings[option])
1201
# Now we have our good server settings in "server_settings"
1203
##################################################################
1206
debug = server_settings[u"debug"]
1207
use_dbus = server_settings[u"use_dbus"]
1208
use_ipv6 = server_settings[u"use_ipv6"]
1211
syslogger.setLevel(logging.WARNING)
1212
console.setLevel(logging.WARNING)
1214
if server_settings[u"servicename"] != u"Mandos":
1215
syslogger.setFormatter(logging.Formatter
1216
(u'Mandos (%s) [%%(process)d]:'
1217
u' %%(levelname)s: %%(message)s'
1218
% server_settings[u"servicename"]))
1220
# Parse config file with clients
1221
client_defaults = { u"timeout": u"1h",
1223
u"checker": u"fping -q -- %%(host)s",
1226
client_config = configparser.SafeConfigParser(client_defaults)
1227
client_config.read(os.path.join(server_settings[u"configdir"],
1230
global mandos_dbus_service
1231
mandos_dbus_service = None
1234
tcp_server = IPv6_TCPServer((server_settings[u"address"],
1235
server_settings[u"port"]),
1238
server_settings[u"interface"],
1242
server_settings[u"priority"],
1244
pidfilename = u"/var/run/mandos.pid"
1246
pidfile = open(pidfilename, u"w")
1248
logger.error(u"Could not open file %r", pidfilename)
1251
uid = pwd.getpwnam(u"_mandos").pw_uid
1252
gid = pwd.getpwnam(u"_mandos").pw_gid
1255
uid = pwd.getpwnam(u"mandos").pw_uid
1256
gid = pwd.getpwnam(u"mandos").pw_gid
1259
uid = pwd.getpwnam(u"nobody").pw_uid
1260
gid = pwd.getpwnam(u"nobody").pw_gid
1267
except OSError, error:
1268
if error[0] != errno.EPERM:
1271
# Enable all possible GnuTLS debugging
1273
# "Use a log level over 10 to enable all debugging options."
1275
gnutls.library.functions.gnutls_global_set_log_level(11)
1277
@gnutls.library.types.gnutls_log_func
1278
def debug_gnutls(level, string):
1279
logger.debug(u"GnuTLS: %s", string[:-1])
1281
(gnutls.library.functions
1282
.gnutls_global_set_log_function(debug_gnutls))
1285
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1286
service = AvahiService(name = server_settings[u"servicename"],
1287
servicetype = u"_mandos._tcp",
1288
protocol = protocol)
1289
if server_settings["interface"]:
1290
service.interface = (if_nametoindex
1291
(str(server_settings[u"interface"])))
1296
# 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
1297
589
DBusGMainLoop(set_as_default=True )
1298
590
main_loop = gobject.MainLoop()
1299
591
bus = dbus.SystemBus()
1300
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1301
avahi.DBUS_PATH_SERVER),
1302
avahi.DBUS_INTERFACE_SERVER)
592
server = dbus.Interface(
593
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
594
avahi.DBUS_INTERFACE_SERVER )
1303
595
# End of Avahi example code
1305
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1307
client_class = Client
1309
client_class = ClientDBus
1311
client_class(name = section,
1312
config= dict(client_config.items(section)))
1313
for section in client_config.sections()))
1315
logger.warning(u"No clients defined")
1318
# Redirect stdin so all checkers get /dev/null
1319
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1320
os.dup2(null, sys.stdin.fileno())
1324
# No console logging
1325
logger.removeHandler(console)
1326
# Close all input and output, do double fork, etc.
1330
with closing(pidfile):
1332
pidfile.write(str(pid) + "\n")
1335
logger.error(u"Could not write to file %r with PID %d",
1338
# "pidfile" was never created
1343
"Cleanup function; run on exit"
1345
# From the Avahi example code
1346
if not group is None:
1349
# End of Avahi example code
1352
client = clients.pop()
1353
client.disable_hook = None
1356
atexit.register(cleanup)
1359
signal.signal(signal.SIGINT, signal.SIG_IGN)
1360
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1361
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1364
class MandosDBusService(dbus.service.Object):
1365
"""A D-Bus proxy object"""
1367
dbus.service.Object.__init__(self, bus, u"/")
1368
_interface = u"se.bsnet.fukt.Mandos"
1370
@dbus.service.signal(_interface, signature=u"oa{sv}")
1371
def ClientAdded(self, objpath, properties):
1375
@dbus.service.signal(_interface, signature=u"s")
1376
def ClientNotFound(self, fingerprint):
1380
@dbus.service.signal(_interface, signature=u"os")
1381
def ClientRemoved(self, objpath, name):
1385
@dbus.service.method(_interface, out_signature=u"ao")
1386
def GetAllClients(self):
1388
return dbus.Array(c.dbus_object_path for c in clients)
1390
@dbus.service.method(_interface,
1391
out_signature=u"a{oa{sv}}")
1392
def GetAllClientsWithProperties(self):
1394
return dbus.Dictionary(
1395
((c.dbus_object_path, c.GetAllProperties())
1397
signature=u"oa{sv}")
1399
@dbus.service.method(_interface, in_signature=u"o")
1400
def RemoveClient(self, object_path):
1403
if c.dbus_object_path == object_path:
1405
c.remove_from_connection()
1406
# Don't signal anything except ClientRemoved
1407
c.disable(signal=False)
1409
self.ClientRemoved(object_path, c.name)
1415
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()))
1417
611
for client in clients:
1420
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1421
client.GetAllProperties())
1425
tcp_server.server_activate()
1427
# Find out what port we got
1428
service.port = tcp_server.socket.getsockname()[1]
1430
logger.info(u"Now listening on address %r, port %d,"
1431
" flowinfo %d, scope_id %d"
1432
% tcp_server.socket.getsockname())
1434
logger.info(u"Now listening on address %r, port %d"
1435
% tcp_server.socket.getsockname())
1437
#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:],
1440
# From the Avahi example code
1441
server.connect_to_signal(u"StateChanged", server_state_changed)
1443
server_state_changed(server.GetState())
1444
except dbus.exceptions.DBusException, error:
1445
logger.critical(u"DBusException: %s", error)
1447
# End of Avahi example code
1449
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
1450
lambda *args, **kwargs:
1451
(tcp_server.handle_request
1452
(*args[2:], **kwargs) or True))
1454
logger.debug(u"Starting main loop")
1456
except AvahiError, error:
1457
logger.critical(u"AvahiError: %s", error)
1459
636
except KeyboardInterrupt:
1462
logger.debug(u"Server received KeyboardInterrupt")
1463
logger.debug(u"Server exiting")
1465
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