52
22
from sets import Set
57
import logging.handlers
59
from contextlib import closing
65
28
from dbus.mainloop.glib import DBusGMainLoop
70
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
71
except AttributeError:
73
from IN import SO_BINDTODEVICE
75
# From /usr/include/asm/socket.h
32
import logging.handlers
34
# logghandler.setFormatter(logging.Formatter('%(levelname)s %(message)s')
81
36
logger = logging.Logger('mandos')
82
syslogger = (logging.handlers.SysLogHandler
83
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
84
address = "/dev/log"))
85
syslogger.setFormatter(logging.Formatter
86
('Mandos [%(process)d]: %(levelname)s:'
88
logger.addHandler(syslogger)
90
console = logging.StreamHandler()
91
console.setFormatter(logging.Formatter('%(name)s [%(process)d]:'
92
' %(levelname)s: %(message)s'))
93
logger.addHandler(console)
95
class AvahiError(Exception):
96
def __init__(self, value, *args, **kwargs):
98
super(AvahiError, self).__init__(value, *args, **kwargs)
99
def __unicode__(self):
100
return unicode(repr(self.value))
102
class AvahiServiceError(AvahiError):
105
class AvahiGroupError(AvahiError):
109
class AvahiService(object):
110
"""An Avahi (Zeroconf) service.
113
interface: integer; avahi.IF_UNSPEC or an interface index.
114
Used to optionally bind to the specified interface.
115
name: string; Example: 'Mandos'
116
type: string; Example: '_mandos._tcp'.
117
See <http://www.dns-sd.org/ServiceTypes.html>
118
port: integer; what port to announce
119
TXT: list of strings; TXT record for the service
120
domain: string; Domain to publish on, default to .local if empty.
121
host: string; Host to publish records for, default is localhost
122
max_renames: integer; maximum number of renames
123
rename_count: integer; counter so we only rename after collisions
124
a sensible number of times
126
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
127
servicetype = None, port = None, TXT = None,
128
domain = "", host = "", max_renames = 32768,
129
protocol = avahi.PROTO_UNSPEC):
130
self.interface = interface
132
self.type = servicetype
134
self.TXT = TXT if TXT is not None else []
137
self.rename_count = 0
138
self.max_renames = max_renames
139
self.protocol = protocol
141
"""Derived from the Avahi example code"""
142
if self.rename_count >= self.max_renames:
143
logger.critical(u"No suitable Zeroconf service name found"
144
u" after %i retries, exiting.",
146
raise AvahiServiceError(u"Too many renames")
147
self.name = server.GetAlternativeServiceName(self.name)
148
logger.info(u"Changing Zeroconf service name to %r ...",
150
syslogger.setFormatter(logging.Formatter
151
('Mandos (%s) [%%(process)d]:'
152
' %%(levelname)s: %%(message)s'
156
self.rename_count += 1
158
"""Derived from the Avahi example code"""
159
if group is not None:
162
"""Derived from the Avahi example code"""
165
group = dbus.Interface(bus.get_object
167
server.EntryGroupNew()),
168
avahi.DBUS_INTERFACE_ENTRY_GROUP)
169
group.connect_to_signal('StateChanged',
170
entry_group_state_changed)
171
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
172
service.name, service.type)
174
self.interface, # interface
175
self.protocol, # protocol
176
dbus.UInt32(0), # flags
177
self.name, self.type,
178
self.domain, self.host,
179
dbus.UInt16(self.port),
180
avahi.string_array_to_txt_array(self.TXT))
183
# From the Avahi example code:
184
group = None # our entry group
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
185
53
# End of Avahi example code
188
def _datetime_to_dbus(dt, variant_level=0):
189
"""Convert a UTC datetime.datetime() to a D-Bus type."""
190
return dbus.String(dt.isoformat(), variant_level=variant_level)
193
56
class Client(object):
194
57
"""A representation of a client host served by this server.
197
name: string; from the config file, used in log messages and
59
name: string; from the config file, used in log messages
199
60
fingerprint: string (40 or 32 hexadecimal digits); used to
200
61
uniquely identify the client
201
secret: bytestring; sent verbatim (over TLS) to client
202
host: string; available for use by the checker command
203
created: datetime.datetime(); (UTC) object creation
204
last_enabled: datetime.datetime(); (UTC)
206
last_checked_ok: datetime.datetime(); (UTC) or None
207
timeout: datetime.timedelta(); How long from last_checked_ok
208
until this client is invalid
209
interval: datetime.timedelta(); How often to start a new checker
210
disable_hook: If set, called by disable() as disable_hook(self)
211
checker: subprocess.Popen(); a running checker process used
212
to see if the client lives.
213
'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.
214
73
checker_initiator_tag: a gobject event source tag, or None
215
disable_initiator_tag: - '' -
74
stop_initiator_tag: - '' -
216
75
checker_callback_tag: - '' -
217
76
checker_command: string; External command which is run to check if
218
client lives. %() expansions are done at
77
client lives. %()s expansions are done at
219
78
runtime with vars(self) as dict, so that for
220
79
instance %(name)s can be used in the command.
221
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: - '' -
223
def timeout_milliseconds(self):
224
"Return the 'timeout' attribute in milliseconds"
225
return ((self.timeout.days * 24 * 60 * 60 * 1000)
226
+ (self.timeout.seconds * 1000)
227
+ (self.timeout.microseconds // 1000))
229
def interval_milliseconds(self):
230
"Return the 'interval' attribute in milliseconds"
231
return ((self.interval.days * 24 * 60 * 60 * 1000)
232
+ (self.interval.seconds * 1000)
233
+ (self.interval.microseconds // 1000))
235
def __init__(self, name = None, disable_hook=None, config=None):
236
"""Note: the 'checker' key in 'config' sets the
237
'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):
242
logger.debug(u"Creating client %r", self.name)
243
# Uppercase and remove spaces from fingerprint for later
244
# comparison purposes with return value from the fingerprint()
246
self.fingerprint = (config["fingerprint"].upper()
248
logger.debug(u" Fingerprint: %s", self.fingerprint)
249
if "secret" in config:
250
self.secret = config["secret"].decode(u"base64")
251
elif "secfile" in config:
252
with closing(open(os.path.expanduser
254
(config["secfile"])))) as secfile:
255
self.secret = secfile.read()
257
raise TypeError(u"No secret or secfile for client %s"
259
self.host = config.get("host", "")
260
self.created = datetime.datetime.utcnow()
262
self.last_enabled = None
263
self.last_checked_ok = None
264
self.timeout = string_to_delta(config["timeout"])
265
self.interval = string_to_delta(config["interval"])
266
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
267
138
self.checker = None
268
139
self.checker_initiator_tag = None
269
self.disable_initiator_tag = None
140
self.stop_initiator_tag = None
270
141
self.checker_callback_tag = None
271
self.checker_command = config["checker"]
272
self.current_checker_command = None
273
self.last_connect = None
276
"""Start this client's checker and timeout hooks"""
277
self.last_enabled = datetime.datetime.utcnow()
142
self.check_command = checker
144
"""Start this clients checker and timeout hooks"""
278
145
# Schedule a new checker to be started an 'interval' from now,
279
146
# and every interval from then on.
280
self.checker_initiator_tag = (gobject.timeout_add
281
(self.interval_milliseconds(),
147
self.checker_initiator_tag = gobject.timeout_add\
148
(self._interval_milliseconds,
283
150
# Also start a new checker *right now*.
284
151
self.start_checker()
285
# Schedule a disable() when 'timeout' has passed
286
self.disable_initiator_tag = (gobject.timeout_add
287
(self.timeout_milliseconds(),
292
"""Disable this client."""
293
if not getattr(self, "enabled", False):
295
logger.info(u"Disabling client %s", self.name)
296
if getattr(self, "disable_initiator_tag", False):
297
gobject.source_remove(self.disable_initiator_tag)
298
self.disable_initiator_tag = None
299
if getattr(self, "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:
300
166
gobject.source_remove(self.checker_initiator_tag)
301
167
self.checker_initiator_tag = None
302
168
self.stop_checker()
303
if self.disable_hook:
304
self.disable_hook(self)
306
171
# Do not run this again if called by a gobject.timeout_add
309
173
def __del__(self):
310
self.disable_hook = None
313
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):
314
185
"""The checker has completed, so take appropriate actions."""
315
self.checker_callback_tag = None
317
if os.WIFEXITED(condition):
318
exitstatus = os.WEXITSTATUS(condition)
320
logger.info(u"Checker for %(name)s succeeded",
324
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):
327
197
logger.warning(u"Checker for %(name)s crashed?",
330
def checked_ok(self):
331
"""Bump up the timeout for this client.
333
This should only be called when the client has been seen,
336
self.last_checked_ok = datetime.datetime.utcnow()
337
gobject.source_remove(self.disable_initiator_tag)
338
self.disable_initiator_tag = (gobject.timeout_add
339
(self.timeout_milliseconds(),
200
logger.debug(u"Checker for %(name)s failed",
203
self.checker_callback_tag = None
342
204
def start_checker(self):
343
205
"""Start a new checker subprocess if one is not running.
345
206
If a checker already exists, leave it running and do
347
# The reason for not killing a running checker is that if we
348
# did that, then if a checker (for some reason) started
349
# running slowly and taking more than 'interval' time, the
350
# client would inevitably timeout, since no checker would get
351
# a chance to run to completion. If we instead leave running
352
# checkers alone, the checker would have to take more time
353
# than 'timeout' for the client to be declared invalid, which
354
# is as it should be.
356
# If a checker exists, make sure it is not a zombie
357
if self.checker is not None:
358
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
360
logger.warning("Checker was a zombie")
361
gobject.source_remove(self.checker_callback_tag)
362
self.checker_callback(pid, status,
363
self.current_checker_command)
364
# Start a new checker if needed
365
208
if self.checker is None:
209
logger.debug(u"Starting checker for %s",
367
# In case checker_command has exactly one % operator
368
command = self.checker_command % self.host
212
command = self.check_command % self.fqdn
369
213
except TypeError:
370
# Escape attributes for the shell
371
214
escaped_attrs = dict((key, re.escape(str(val)))
373
216
vars(self).iteritems())
375
command = self.checker_command % escaped_attrs
218
command = self.check_command % escaped_attrs
376
219
except TypeError, error:
377
logger.error(u'Could not format string "%s":'
378
u' %s', self.checker_command, error)
220
logger.critical(u'Could not format string "%s": %s',
221
self.check_command, error)
379
222
return True # Try again later
380
self.current_checker_command = command
382
logger.info(u"Starting checker %r for %s",
384
# We don't need to redirect stdout and stderr, since
385
# in normal mode, that is already done by daemon(),
386
# and in debug mode we don't want to. (Stdin is
387
# always replaced by /dev/null.)
388
self.checker = subprocess.Popen(command,
391
self.checker_callback_tag = (gobject.child_watch_add
393
self.checker_callback,
395
# The checker may have completed before the gobject
396
# watch was added. Check for this.
397
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
399
gobject.source_remove(self.checker_callback_tag)
400
self.checker_callback(pid, status, command)
401
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:
402
234
logger.error(u"Failed to start subprocess: %s",
404
236
# Re-run this periodically if run by gobject.timeout_add
407
238
def stop_checker(self):
408
239
"""Force the checker process, if any, to stop."""
409
if self.checker_callback_tag:
410
gobject.source_remove(self.checker_callback_tag)
411
self.checker_callback_tag = None
412
if getattr(self, "checker", None) is None:
240
if not hasattr(self, "checker") or self.checker is None:
414
logger.debug(u"Stopping checker for %(name)s", vars(self))
416
os.kill(self.checker.pid, signal.SIGTERM)
418
#if self.checker.poll() is None:
419
# os.kill(self.checker.pid, signal.SIGKILL)
420
except OSError, error:
421
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)
423
247
self.checker = None
425
def still_valid(self):
248
def still_valid(self, now=None):
426
249
"""Has the timeout not yet passed for this client?"""
427
if not getattr(self, "enabled", False):
429
now = datetime.datetime.utcnow()
430
if self.last_checked_ok is None:
251
now = datetime.datetime.now()
252
if self.last_seen is None:
431
253
return now < (self.created + self.timeout)
433
return now < (self.last_checked_ok + self.timeout)
436
class ClientDBus(Client, dbus.service.Object):
437
"""A Client class using D-Bus
440
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
442
# dbus.service.Object doesn't use super(), so we can't either.
444
def __init__(self, *args, **kwargs):
445
Client.__init__(self, *args, **kwargs)
446
# Only now, when this client is initialized, can it show up on
448
self.dbus_object_path = (dbus.ObjectPath
450
+ self.name.replace(".", "_")))
451
dbus.service.Object.__init__(self, bus,
452
self.dbus_object_path)
454
oldstate = getattr(self, "enabled", False)
455
r = Client.enable(self)
456
if oldstate != self.enabled:
458
self.PropertyChanged(dbus.String(u"enabled"),
459
dbus.Boolean(True, variant_level=1))
460
self.PropertyChanged(dbus.String(u"last_enabled"),
461
(_datetime_to_dbus(self.last_enabled,
465
def disable(self, signal = True):
466
oldstate = getattr(self, "enabled", False)
467
r = Client.disable(self)
468
if signal and oldstate != self.enabled:
470
self.PropertyChanged(dbus.String(u"enabled"),
471
dbus.Boolean(False, variant_level=1))
474
def __del__(self, *args, **kwargs):
476
self.remove_from_connection()
479
if hasattr(dbus.service.Object, "__del__"):
480
dbus.service.Object.__del__(self, *args, **kwargs)
481
Client.__del__(self, *args, **kwargs)
483
def checker_callback(self, pid, condition, command,
485
self.checker_callback_tag = None
488
self.PropertyChanged(dbus.String(u"checker_running"),
489
dbus.Boolean(False, variant_level=1))
490
if os.WIFEXITED(condition):
491
exitstatus = os.WEXITSTATUS(condition)
493
self.CheckerCompleted(dbus.Int16(exitstatus),
494
dbus.Int64(condition),
495
dbus.String(command))
498
self.CheckerCompleted(dbus.Int16(-1),
499
dbus.Int64(condition),
500
dbus.String(command))
502
return Client.checker_callback(self, pid, condition, command,
505
def checked_ok(self, *args, **kwargs):
506
r = Client.checked_ok(self, *args, **kwargs)
508
self.PropertyChanged(
509
dbus.String(u"last_checked_ok"),
510
(_datetime_to_dbus(self.last_checked_ok,
514
def start_checker(self, *args, **kwargs):
515
old_checker = self.checker
516
if self.checker is not None:
517
old_checker_pid = self.checker.pid
519
old_checker_pid = None
520
r = Client.start_checker(self, *args, **kwargs)
521
# Only if new checker process was started
522
if (self.checker is not None
523
and old_checker_pid != self.checker.pid):
525
self.CheckerStarted(self.current_checker_command)
526
self.PropertyChanged(
527
dbus.String("checker_running"),
528
dbus.Boolean(True, variant_level=1))
531
def stop_checker(self, *args, **kwargs):
532
old_checker = getattr(self, "checker", None)
533
r = Client.stop_checker(self, *args, **kwargs)
534
if (old_checker is not None
535
and getattr(self, "checker", None) is None):
536
self.PropertyChanged(dbus.String(u"checker_running"),
537
dbus.Boolean(False, variant_level=1))
540
## D-Bus methods & signals
541
_interface = u"se.bsnet.fukt.Mandos.Client"
544
CheckedOK = dbus.service.method(_interface)(checked_ok)
545
CheckedOK.__name__ = "CheckedOK"
547
# CheckerCompleted - signal
548
@dbus.service.signal(_interface, signature="nxs")
549
def CheckerCompleted(self, exitcode, waitstatus, command):
553
# CheckerStarted - signal
554
@dbus.service.signal(_interface, signature="s")
555
def CheckerStarted(self, command):
559
# GetAllProperties - method
560
@dbus.service.method(_interface, out_signature="a{sv}")
561
def GetAllProperties(self):
563
return dbus.Dictionary({
565
dbus.String(self.name, variant_level=1),
566
dbus.String("fingerprint"):
567
dbus.String(self.fingerprint, variant_level=1),
569
dbus.String(self.host, variant_level=1),
570
dbus.String("created"):
571
_datetime_to_dbus(self.created, variant_level=1),
572
dbus.String("last_enabled"):
573
(_datetime_to_dbus(self.last_enabled,
575
if self.last_enabled is not None
576
else dbus.Boolean(False, variant_level=1)),
577
dbus.String("enabled"):
578
dbus.Boolean(self.enabled, variant_level=1),
579
dbus.String("last_checked_ok"):
580
(_datetime_to_dbus(self.last_checked_ok,
582
if self.last_checked_ok is not None
583
else dbus.Boolean (False, variant_level=1)),
584
dbus.String("timeout"):
585
dbus.UInt64(self.timeout_milliseconds(),
587
dbus.String("interval"):
588
dbus.UInt64(self.interval_milliseconds(),
590
dbus.String("checker"):
591
dbus.String(self.checker_command,
593
dbus.String("checker_running"):
594
dbus.Boolean(self.checker is not None,
596
dbus.String("object_path"):
597
dbus.ObjectPath(self.dbus_object_path,
601
# IsStillValid - method
602
@dbus.service.method(_interface, out_signature="b")
603
def IsStillValid(self):
604
return self.still_valid()
606
# PropertyChanged - signal
607
@dbus.service.signal(_interface, signature="sv")
608
def PropertyChanged(self, property, value):
612
# ReceivedSecret - signal
613
@dbus.service.signal(_interface)
614
def ReceivedSecret(self):
619
@dbus.service.signal(_interface)
624
# SetChecker - method
625
@dbus.service.method(_interface, in_signature="s")
626
def SetChecker(self, checker):
627
"D-Bus setter method"
628
self.checker_command = checker
630
self.PropertyChanged(dbus.String(u"checker"),
631
dbus.String(self.checker_command,
635
@dbus.service.method(_interface, in_signature="s")
636
def SetHost(self, host):
637
"D-Bus setter method"
640
self.PropertyChanged(dbus.String(u"host"),
641
dbus.String(self.host, variant_level=1))
643
# SetInterval - method
644
@dbus.service.method(_interface, in_signature="t")
645
def SetInterval(self, milliseconds):
646
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
648
self.PropertyChanged(dbus.String(u"interval"),
649
(dbus.UInt64(self.interval_milliseconds(),
653
@dbus.service.method(_interface, in_signature="ay",
655
def SetSecret(self, secret):
656
"D-Bus setter method"
657
self.secret = str(secret)
659
# SetTimeout - method
660
@dbus.service.method(_interface, in_signature="t")
661
def SetTimeout(self, milliseconds):
662
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
664
self.PropertyChanged(dbus.String(u"timeout"),
665
(dbus.UInt64(self.timeout_milliseconds(),
669
Enable = dbus.service.method(_interface)(enable)
670
Enable.__name__ = "Enable"
672
# StartChecker - method
673
@dbus.service.method(_interface)
674
def StartChecker(self):
679
@dbus.service.method(_interface)
684
# StopChecker - method
685
StopChecker = dbus.service.method(_interface)(stop_checker)
686
StopChecker.__name__ = "StopChecker"
691
class ClientHandler(SocketServer.BaseRequestHandler, object):
692
"""A class to handle client connections.
694
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.
695
307
Note: This will run in its own forked process."""
697
309
def handle(self):
698
logger.info(u"TCP connection from: %s",
699
unicode(self.client_address))
700
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
701
# Open IPC pipe to parent process
702
with closing(os.fdopen(self.server.pipe[1], "w", 1)) as ipc:
703
session = (gnutls.connection
704
.ClientSession(self.request,
708
line = self.request.makefile().readline()
709
logger.debug(u"Protocol version: %r", line)
711
if int(line.strip().split()[0]) > 1:
713
except (ValueError, IndexError, RuntimeError), error:
714
logger.error(u"Unknown protocol version: %s", error)
717
# Note: gnutls.connection.X509Credentials is really a
718
# generic GnuTLS certificate credentials object so long as
719
# no X.509 keys are added to it. Therefore, we can use it
720
# here despite using OpenPGP certificates.
722
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
723
# "+AES-256-CBC", "+SHA1",
724
# "+COMP-NULL", "+CTYPE-OPENPGP",
726
# Use a fallback default, since this MUST be set.
727
priority = self.server.gnutls_priority
730
(gnutls.library.functions
731
.gnutls_priority_set_direct(session._c_object,
736
except gnutls.errors.GNUTLSError, error:
737
logger.warning(u"Handshake failed: %s", error)
738
# Do not run session.bye() here: the session is not
739
# established. Just abandon the request.
741
logger.debug(u"Handshake succeeded")
743
fpr = self.fingerprint(self.peer_certificate(session))
744
except (TypeError, gnutls.errors.GNUTLSError), error:
745
logger.warning(u"Bad certificate: %s", error)
748
logger.debug(u"Fingerprint: %s", fpr)
750
for c in self.server.clients:
751
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",
755
ipc.write("NOTFOUND %s\n" % fpr)
758
# Have to check if client.still_valid(), since it is
759
# possible that the client timed out while establishing
760
# the GnuTLS session.
761
if not client.still_valid():
762
ipc.write("INVALID %s\n" % client.name)
765
ipc.write("SENDING %s\n" % client.name)
767
while sent_size < len(client.secret):
768
sent = session.send(client.secret[sent_size:])
769
logger.debug(u"Sent: %d, remaining: %d",
770
sent, len(client.secret)
771
- (sent_size + sent))
351
logger.debug(u"Client not found for fingerprint: %s",
776
def peer_certificate(session):
777
"Return the peer's OpenPGP certificate as a bytestring"
778
# If not an OpenPGP certificate...
779
if (gnutls.library.functions
780
.gnutls_certificate_type_get(session._c_object)
781
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
782
# ...do the normal thing
783
return session.peer_certificate
784
list_size = ctypes.c_uint(1)
785
cert_list = (gnutls.library.functions
786
.gnutls_certificate_get_peers
787
(session._c_object, ctypes.byref(list_size)))
788
if not bool(cert_list) and list_size.value != 0:
789
raise gnutls.errors.GNUTLSError("error getting peer"
791
if list_size.value == 0:
794
return ctypes.string_at(cert.data, cert.size)
797
def fingerprint(openpgp):
798
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
799
# New GnuTLS "datum" with the OpenPGP public key
800
datum = (gnutls.library.types
801
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
804
ctypes.c_uint(len(openpgp))))
805
# New empty GnuTLS certificate
806
crt = gnutls.library.types.gnutls_openpgp_crt_t()
807
(gnutls.library.functions
808
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
809
# Import the OpenPGP public key into the certificate
810
(gnutls.library.functions
811
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
812
gnutls.library.constants
813
.GNUTLS_OPENPGP_FMT_RAW))
814
# Verify the self signature in the key
815
crtverify = ctypes.c_uint()
816
(gnutls.library.functions
817
.gnutls_openpgp_crt_verify_self(crt, 0,
818
ctypes.byref(crtverify)))
819
if crtverify.value != 0:
820
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
821
raise (gnutls.errors.CertificateSecurityError
823
# New buffer for the fingerprint
824
buf = ctypes.create_string_buffer(20)
825
buf_len = ctypes.c_size_t()
826
# Get the fingerprint from the certificate into the buffer
827
(gnutls.library.functions
828
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
829
ctypes.byref(buf_len)))
830
# Deinit the certificate
831
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
832
# Convert the buffer to a Python bytestring
833
fpr = ctypes.string_at(buf, buf_len.value)
834
# Convert the bytestring to hexadecimal notation
835
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
839
class ForkingMixInWithPipe(SocketServer.ForkingMixIn, object):
840
"""Like SocketServer.ForkingMixIn, but also pass a pipe.
842
Assumes a gobject.MainLoop event loop.
844
def process_request(self, request, client_address):
845
"""Overrides and wraps the original process_request().
847
This function creates a new pipe in self.pipe
849
self.pipe = os.pipe()
850
super(ForkingMixInWithPipe,
851
self).process_request(request, client_address)
852
os.close(self.pipe[1]) # close write end
853
# Call "handle_ipc" for both data and EOF events
854
gobject.io_add_watch(self.pipe[0],
855
gobject.IO_IN | gobject.IO_HUP,
857
def handle_ipc(source, condition):
858
"""Dummy function; override as necessary"""
863
class IPv6_TCPServer(ForkingMixInWithPipe,
864
SocketServer.TCPServer, object):
865
"""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.
868
enabled: Boolean; whether this server is activated yet
869
interface: None or a network interface name (string)
870
use_ipv6: Boolean; to use IPv6 or not
368
options: Command line options
872
369
clients: Set() of Client objects
873
gnutls_priority GnuTLS priority string
874
use_dbus: Boolean; to emit D-Bus signals or not
876
def __init__(self, server_address, RequestHandlerClass,
877
interface=None, use_ipv6=True, clients=None,
878
gnutls_priority=None, use_dbus=True):
880
self.interface = interface
882
self.address_family = socket.AF_INET6
883
self.clients = clients
884
self.use_dbus = use_dbus
885
self.gnutls_priority = gnutls_priority
886
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)
888
380
def server_bind(self):
889
381
"""This overrides the normal server_bind() function
890
382
to bind to an interface if one was specified, and also NOT to
891
383
bind to an address or port if they were not specified."""
892
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
894
389
self.socket.setsockopt(socket.SOL_SOCKET,
896
self.interface + '\0')
390
socket.SO_BINDTODEVICE,
391
self.options.interface)
897
392
except socket.error, error:
898
393
if error[0] == errno.EPERM:
899
logger.error(u"No permission to"
900
u" bind to interface %s",
394
logger.warning(u"No permission to"
395
u" bind to interface %s",
396
self.options.interface)
904
399
# Only bind(2) the socket if we really need to.
905
400
if self.server_address[0] or self.server_address[1]:
906
401
if not self.server_address[0]:
907
if self.address_family == socket.AF_INET6:
908
any_address = "::" # in6addr_any
910
any_address = socket.INADDR_ANY
911
self.server_address = (any_address,
403
self.server_address = (in6addr_any,
912
404
self.server_address[1])
913
elif not self.server_address[1]:
405
elif self.server_address[1] is None:
914
406
self.server_address = (self.server_address[0],
917
# self.server_address = (self.server_address[0],
922
return SocketServer.TCPServer.server_bind(self)
923
def server_activate(self):
925
return SocketServer.TCPServer.server_activate(self)
928
def handle_ipc(self, source, condition, file_objects={}):
930
gobject.IO_IN: "IN", # There is data to read.
931
gobject.IO_OUT: "OUT", # Data can be written (without
933
gobject.IO_PRI: "PRI", # There is urgent data to read.
934
gobject.IO_ERR: "ERR", # Error condition.
935
gobject.IO_HUP: "HUP" # Hung up (the connection has been
936
# broken, usually for pipes and
939
conditions_string = ' | '.join(name
941
condition_names.iteritems()
943
logger.debug("Handling IPC: FD = %d, condition = %s", source,
946
# Turn the pipe file descriptor into a Python file object
947
if source not in file_objects:
948
file_objects[source] = os.fdopen(source, "r", 1)
950
# Read a line from the file object
951
cmdline = file_objects[source].readline()
952
if not cmdline: # Empty line means end of file
954
file_objects[source].close()
955
del file_objects[source]
957
# Stop calling this function
960
logger.debug("IPC command: %r", cmdline)
962
# Parse and act on command
963
cmd, args = cmdline.rstrip("\r\n").split(None, 1)
965
if cmd == "NOTFOUND":
966
logger.warning(u"Client not found for fingerprint: %s",
970
mandos_dbus_service.ClientNotFound(args)
971
elif cmd == "INVALID":
972
for client in self.clients:
973
if client.name == args:
974
logger.warning(u"Client %s is invalid", args)
980
logger.error(u"Unknown client %s is invalid", args)
981
elif cmd == "SENDING":
982
for client in self.clients:
983
if client.name == args:
984
logger.info(u"Sending secret to %s", client.name)
988
client.ReceivedSecret()
991
logger.error(u"Sending secret to unknown client %s",
994
logger.error("Unknown IPC command: %r", cmdline)
996
# Keep calling this function
408
return super(type(self), self).server_bind()
1000
411
def string_to_delta(interval):
1001
412
"""Parse a string and return a datetime.timedelta
1003
414
>>> string_to_delta('7d')
1004
415
datetime.timedelta(7)
1005
416
>>> string_to_delta('60s')
1010
421
datetime.timedelta(1)
1011
422
>>> string_to_delta(u'1w')
1012
423
datetime.timedelta(7)
1013
>>> string_to_delta('5m 30s')
1014
datetime.timedelta(0, 330)
1016
timevalue = datetime.timedelta(0)
1017
for s in interval.split():
1019
suffix = unicode(s[-1])
1022
delta = datetime.timedelta(value)
1023
elif suffix == u"s":
1024
delta = datetime.timedelta(0, value)
1025
elif suffix == u"m":
1026
delta = datetime.timedelta(0, 0, 0, 0, value)
1027
elif suffix == u"h":
1028
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1029
elif suffix == u"w":
1030
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1033
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)
1034
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:
1039
478
def server_state_changed(state):
1040
"""Derived from the Avahi example code"""
479
"""From the Avahi server example code"""
1041
480
if state == avahi.SERVER_COLLISION:
1042
logger.error(u"Zeroconf server name collision")
481
logger.warning(u"Server name collision")
1044
483
elif state == avahi.SERVER_RUNNING:
1048
487
def entry_group_state_changed(state, error):
1049
"""Derived from the Avahi example code"""
1050
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)
1052
493
if state == avahi.ENTRY_GROUP_ESTABLISHED:
1053
logger.debug(u"Zeroconf service established.")
494
logger.debug(u"Service established.")
1054
495
elif state == avahi.ENTRY_GROUP_COLLISION:
1055
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.",
1057
510
elif state == avahi.ENTRY_GROUP_FAILURE:
1058
logger.critical(u"Avahi: Error in group state changed %s",
1060
raise AvahiGroupError(u"State changed: %s" % unicode(error))
511
logger.error(u"Error in group state changed %s",
1062
517
def if_nametoindex(interface):
1063
"""Call the C function if_nametoindex(), or equivalent"""
1064
global if_nametoindex
518
"""Call the C function if_nametoindex()"""
1066
if_nametoindex = (ctypes.cdll.LoadLibrary
1067
(ctypes.util.find_library("c"))
520
libc = ctypes.cdll.LoadLibrary("libc.so.6")
521
return libc.if_nametoindex(interface)
1069
522
except (OSError, AttributeError):
1070
523
if "struct" not in sys.modules:
1072
525
if "fcntl" not in sys.modules:
1074
def if_nametoindex(interface):
1075
"Get an interface index the hard way, i.e. using fcntl()"
1076
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
1077
with closing(socket.socket()) as s:
1078
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
1079
struct.pack("16s16x", interface))
1080
interface_index = struct.unpack("I", ifreq[16:20])[0]
1081
return interface_index
1082
return if_nametoindex(interface)
1085
def daemon(nochdir = False, noclose = False):
1086
"""See daemon(3). Standard BSD Unix function.
1088
This should really exist as os.daemon, but it doesn't (yet)."""
1097
# Close all standard open file descriptors
1098
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1099
if not stat.S_ISCHR(os.fstat(null).st_mode):
1100
raise OSError(errno.ENODEV,
1101
"/dev/null not a character device")
1102
os.dup2(null, sys.stdin.fileno())
1103
os.dup2(null, sys.stdout.fileno())
1104
os.dup2(null, sys.stderr.fileno())
1111
######################################################################
1112
# Parsing of options, both command line and config file
1114
parser = optparse.OptionParser(version = "%%prog %s" % version)
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()
1115
538
parser.add_option("-i", "--interface", type="string",
1116
metavar="IF", help="Bind to interface IF")
1117
parser.add_option("-a", "--address", type="string",
1118
help="Address to listen for requests on")
1119
parser.add_option("-p", "--port", type="int",
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,
1120
554
help="Port number to receive requests on")
1121
parser.add_option("--check", action="store_true",
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,
1122
562
help="Run self-test")
1123
parser.add_option("--debug", action="store_true",
1124
help="Debug mode; run in foreground and log to"
1126
parser.add_option("--priority", type="string", help="GnuTLS"
1127
" priority string (see GnuTLS documentation)")
1128
parser.add_option("--servicename", type="string", metavar="NAME",
1129
help="Zeroconf service name")
1130
parser.add_option("--configdir", type="string",
1131
default="/etc/mandos", metavar="DIR",
1132
help="Directory to search for configuration"
1134
parser.add_option("--no-dbus", action="store_false",
1136
help="Do not provide D-Bus system bus"
1138
parser.add_option("--no-ipv6", action="store_false",
1139
dest="use_ipv6", help="Do not use IPv6")
1140
options = parser.parse_args()[0]
563
parser.add_option("--debug", action="store_true", default=False,
565
(options, args) = parser.parse_args()
1142
567
if options.check:
1144
569
doctest.testmod()
1147
# Default values for config file for server-global settings
1148
server_defaults = { "interface": "",
1153
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1154
"servicename": "Mandos",
1159
# Parse config file for server-global settings
1160
server_config = ConfigParser.SafeConfigParser(server_defaults)
1162
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1163
# Convert the SafeConfigParser object to a dict
1164
server_settings = server_config.defaults()
1165
# Use the appropriate methods on the non-string config options
1166
server_settings["debug"] = server_config.getboolean("DEFAULT",
1168
server_settings["use_dbus"] = server_config.getboolean("DEFAULT",
1170
server_settings["use_ipv6"] = server_config.getboolean("DEFAULT",
1172
if server_settings["port"]:
1173
server_settings["port"] = server_config.getint("DEFAULT",
1177
# Override the settings from the config file with command line
1179
for option in ("interface", "address", "port", "debug",
1180
"priority", "servicename", "configdir",
1181
"use_dbus", "use_ipv6"):
1182
value = getattr(options, option)
1183
if value is not None:
1184
server_settings[option] = value
1186
# Now we have our good server settings in "server_settings"
1188
##################################################################
1191
debug = server_settings["debug"]
1192
use_dbus = server_settings["use_dbus"]
1193
use_ipv6 = server_settings["use_ipv6"]
1196
syslogger.setLevel(logging.WARNING)
1197
console.setLevel(logging.WARNING)
1199
if server_settings["servicename"] != "Mandos":
1200
syslogger.setFormatter(logging.Formatter
1201
('Mandos (%s) [%%(process)d]:'
1202
' %%(levelname)s: %%(message)s'
1203
% server_settings["servicename"]))
1205
# Parse config file with clients
1206
client_defaults = { "timeout": "1h",
1208
"checker": "fping -q -- %%(host)s",
1211
client_config = ConfigParser.SafeConfigParser(client_defaults)
1212
client_config.read(os.path.join(server_settings["configdir"],
1215
global mandos_dbus_service
1216
mandos_dbus_service = None
1219
tcp_server = IPv6_TCPServer((server_settings["address"],
1220
server_settings["port"]),
1223
server_settings["interface"],
1227
server_settings["priority"],
1229
pidfilename = "/var/run/mandos.pid"
1231
pidfile = open(pidfilename, "w")
1233
logger.error("Could not open file %r", pidfilename)
1236
uid = pwd.getpwnam("_mandos").pw_uid
1237
gid = pwd.getpwnam("_mandos").pw_gid
1240
uid = pwd.getpwnam("mandos").pw_uid
1241
gid = pwd.getpwnam("mandos").pw_gid
1244
uid = pwd.getpwnam("nobody").pw_uid
1245
gid = pwd.getpwnam("nogroup").pw_gid
1252
except OSError, error:
1253
if error[0] != errno.EPERM:
1256
# Enable all possible GnuTLS debugging
1258
# "Use a log level over 10 to enable all debugging options."
1260
gnutls.library.functions.gnutls_global_set_log_level(11)
1262
@gnutls.library.types.gnutls_log_func
1263
def debug_gnutls(level, string):
1264
logger.debug("GnuTLS: %s", string[:-1])
1266
(gnutls.library.functions
1267
.gnutls_global_set_log_function(debug_gnutls))
1270
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1271
service = AvahiService(name = server_settings["servicename"],
1272
servicetype = "_mandos._tcp",
1273
protocol = protocol)
1274
if server_settings["interface"]:
1275
service.interface = (if_nametoindex
1276
(server_settings["interface"]))
1281
# 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
1282
589
DBusGMainLoop(set_as_default=True )
1283
590
main_loop = gobject.MainLoop()
1284
591
bus = dbus.SystemBus()
1285
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1286
avahi.DBUS_PATH_SERVER),
1287
avahi.DBUS_INTERFACE_SERVER)
592
server = dbus.Interface(
593
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
594
avahi.DBUS_INTERFACE_SERVER )
1288
595
# End of Avahi example code
1290
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1292
client_class = Client
1294
client_class = ClientDBus
1296
client_class(name = section,
1297
config= dict(client_config.items(section)))
1298
for section in client_config.sections()))
1300
logger.warning(u"No clients defined")
1303
# Redirect stdin so all checkers get /dev/null
1304
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1305
os.dup2(null, sys.stdin.fileno())
1309
# No console logging
1310
logger.removeHandler(console)
1311
# Close all input and output, do double fork, etc.
1315
with closing(pidfile):
1317
pidfile.write(str(pid) + "\n")
1320
logger.error(u"Could not write to file %r with PID %d",
1323
# "pidfile" was never created
1328
"Cleanup function; run on exit"
1330
# From the Avahi example code
1331
if not group is None:
1334
# End of Avahi example code
1337
client = clients.pop()
1338
client.disable_hook = None
1341
atexit.register(cleanup)
1344
signal.signal(signal.SIGINT, signal.SIG_IGN)
1345
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1346
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
1349
class MandosDBusService(dbus.service.Object):
1350
"""A D-Bus proxy object"""
1352
dbus.service.Object.__init__(self, bus, "/")
1353
_interface = u"se.bsnet.fukt.Mandos"
1355
@dbus.service.signal(_interface, signature="oa{sv}")
1356
def ClientAdded(self, objpath, properties):
1360
@dbus.service.signal(_interface, signature="s")
1361
def ClientNotFound(self, fingerprint):
1365
@dbus.service.signal(_interface, signature="os")
1366
def ClientRemoved(self, objpath, name):
1370
@dbus.service.method(_interface, out_signature="ao")
1371
def GetAllClients(self):
1373
return dbus.Array(c.dbus_object_path for c in clients)
1375
@dbus.service.method(_interface, out_signature="a{oa{sv}}")
1376
def GetAllClientsWithProperties(self):
1378
return dbus.Dictionary(
1379
((c.dbus_object_path, c.GetAllProperties())
1383
@dbus.service.method(_interface, in_signature="o")
1384
def RemoveClient(self, object_path):
1387
if c.dbus_object_path == object_path:
1389
c.remove_from_connection()
1390
# Don't signal anything except ClientRemoved
1391
c.disable(signal=False)
1393
self.ClientRemoved(object_path, c.name)
1399
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()))
1401
611
for client in clients:
1404
mandos_dbus_service.ClientAdded(client.dbus_object_path,
1405
client.GetAllProperties())
1409
tcp_server.server_activate()
1411
# Find out what port we got
1412
service.port = tcp_server.socket.getsockname()[1]
1414
logger.info(u"Now listening on address %r, port %d,"
1415
" flowinfo %d, scope_id %d"
1416
% tcp_server.socket.getsockname())
1418
logger.info(u"Now listening on address %r, port %d"
1419
% tcp_server.socket.getsockname())
1421
#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:],
1424
# From the Avahi example code
1425
server.connect_to_signal("StateChanged", server_state_changed)
1427
server_state_changed(server.GetState())
1428
except dbus.exceptions.DBusException, error:
1429
logger.critical(u"DBusException: %s", error)
1431
# End of Avahi example code
1433
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
1434
lambda *args, **kwargs:
1435
(tcp_server.handle_request
1436
(*args[2:], **kwargs) or True))
1438
logger.debug(u"Starting main loop")
1440
except AvahiError, error:
1441
logger.critical(u"AvahiError: %s", error)
1443
636
except KeyboardInterrupt:
1446
logger.debug("Server received KeyboardInterrupt")
1447
logger.debug("Server exiting")
1449
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