124
107
max_renames: integer; maximum number of renames
125
108
rename_count: integer; counter so we only rename after collisions
126
109
a sensible number of times
127
group: D-Bus Entry Group
129
bus: dbus.SystemBus()
131
111
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
132
servicetype = None, port = None, TXT = None,
133
domain = u"", host = u"", max_renames = 32768,
134
protocol = avahi.PROTO_UNSPEC, bus = None):
112
servicetype = None, port = None, TXT = None, domain = "",
113
host = "", max_renames = 32768):
135
114
self.interface = interface
137
116
self.type = servicetype
139
self.TXT = TXT if TXT is not None else []
140
122
self.domain = domain
142
124
self.rename_count = 0
143
125
self.max_renames = max_renames
144
self.protocol = protocol
145
self.group = None # our entry group
148
126
def rename(self):
149
127
"""Derived from the Avahi example code"""
150
128
if self.rename_count >= self.max_renames:
151
129
logger.critical(u"No suitable Zeroconf service name found"
152
130
u" after %i retries, exiting.",
153
131
self.rename_count)
154
raise AvahiServiceError(u"Too many renames")
155
self.name = self.server.GetAlternativeServiceName(self.name)
132
raise AvahiServiceError("Too many renames")
133
self.name = server.GetAlternativeServiceName(self.name)
156
134
logger.info(u"Changing Zeroconf service name to %r ...",
158
syslogger.setFormatter(logging.Formatter
159
(u'Mandos (%s) [%%(process)d]:'
160
u' %%(levelname)s: %%(message)s'
136
syslogger.setFormatter(logging.Formatter\
137
('Mandos (%s): %%(levelname)s:'
138
' %%(message)s' % self.name))
164
141
self.rename_count += 1
165
142
def remove(self):
166
143
"""Derived from the Avahi example code"""
167
if self.group is not None:
144
if group is not None:
170
147
"""Derived from the Avahi example code"""
171
if self.group is None:
172
self.group = dbus.Interface(
173
self.bus.get_object(avahi.DBUS_NAME,
174
self.server.EntryGroupNew()),
175
avahi.DBUS_INTERFACE_ENTRY_GROUP)
176
self.group.connect_to_signal('StateChanged',
177
self.entry_group_state_changed)
150
group = dbus.Interface\
151
(bus.get_object(avahi.DBUS_NAME,
152
server.EntryGroupNew()),
153
avahi.DBUS_INTERFACE_ENTRY_GROUP)
154
group.connect_to_signal('StateChanged',
155
entry_group_state_changed)
178
156
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
179
self.name, self.type)
180
self.group.AddService(
183
dbus.UInt32(0), # flags
184
self.name, self.type,
185
self.domain, self.host,
186
dbus.UInt16(self.port),
187
avahi.string_array_to_txt_array(self.TXT))
189
def entry_group_state_changed(self, state, error):
190
"""Derived from the Avahi example code"""
191
logger.debug(u"Avahi state change: %i", state)
193
if state == avahi.ENTRY_GROUP_ESTABLISHED:
194
logger.debug(u"Zeroconf service established.")
195
elif state == avahi.ENTRY_GROUP_COLLISION:
196
logger.warning(u"Zeroconf service name collision.")
198
elif state == avahi.ENTRY_GROUP_FAILURE:
199
logger.critical(u"Avahi: Error in group state changed %s",
201
raise AvahiGroupError(u"State changed: %s"
204
"""Derived from the Avahi example code"""
205
if self.group is not None:
208
def server_state_changed(self, state):
209
"""Derived from the Avahi example code"""
210
if state == avahi.SERVER_COLLISION:
211
logger.error(u"Zeroconf server name collision")
213
elif state == avahi.SERVER_RUNNING:
216
"""Derived from the Avahi example code"""
217
if self.server is None:
218
self.server = dbus.Interface(
219
self.bus.get_object(avahi.DBUS_NAME,
220
avahi.DBUS_PATH_SERVER),
221
avahi.DBUS_INTERFACE_SERVER)
222
self.server.connect_to_signal(u"StateChanged",
223
self.server_state_changed)
224
self.server_state_changed(self.server.GetState())
157
service.name, service.type)
159
self.interface, # interface
160
avahi.PROTO_INET6, # protocol
161
dbus.UInt32(0), # flags
162
self.name, self.type,
163
self.domain, self.host,
164
dbus.UInt16(self.port),
165
avahi.string_array_to_txt_array(self.TXT))
168
# From the Avahi example code:
169
group = None # our entry group
170
# End of Avahi example code
227
173
class Client(object):
228
174
"""A representation of a client host served by this server.
231
name: string; from the config file, used in log messages and
176
name: string; from the config file, used in log messages
233
177
fingerprint: string (40 or 32 hexadecimal digits); used to
234
178
uniquely identify the client
235
secret: bytestring; sent verbatim (over TLS) to client
236
host: string; available for use by the checker command
237
created: datetime.datetime(); (UTC) object creation
238
last_enabled: datetime.datetime(); (UTC)
240
last_checked_ok: datetime.datetime(); (UTC) or None
241
timeout: datetime.timedelta(); How long from last_checked_ok
242
until this client is invalid
243
interval: datetime.timedelta(); How often to start a new checker
244
disable_hook: If set, called by disable() as disable_hook(self)
245
checker: subprocess.Popen(); a running checker process used
246
to see if the client lives.
247
'None' if no process is running.
179
secret: bytestring; sent verbatim (over TLS) to client
180
host: string; available for use by the checker command
181
created: datetime.datetime(); object creation, not client host
182
last_checked_ok: datetime.datetime() or None if not yet checked OK
183
timeout: datetime.timedelta(); How long from last_checked_ok
184
until this client is invalid
185
interval: datetime.timedelta(); How often to start a new checker
186
stop_hook: If set, called by stop() as stop_hook(self)
187
checker: subprocess.Popen(); a running checker process used
188
to see if the client lives.
189
'None' if no process is running.
248
190
checker_initiator_tag: a gobject event source tag, or None
249
disable_initiator_tag: - '' -
191
stop_initiator_tag: - '' -
250
192
checker_callback_tag: - '' -
251
193
checker_command: string; External command which is run to check if
252
194
client lives. %() expansions are done at
253
195
runtime with vars(self) as dict, so that for
254
196
instance %(name)s can be used in the command.
255
current_checker_command: string; current running checker_command
198
_timeout: Real variable for 'timeout'
199
_interval: Real variable for 'interval'
200
_timeout_milliseconds: Used when calling gobject.timeout_add()
201
_interval_milliseconds: - '' -
259
def _datetime_to_milliseconds(dt):
260
"Convert a datetime.datetime() to milliseconds"
261
return ((dt.days * 24 * 60 * 60 * 1000)
262
+ (dt.seconds * 1000)
263
+ (dt.microseconds // 1000))
265
def timeout_milliseconds(self):
266
"Return the 'timeout' attribute in milliseconds"
267
return self._datetime_to_milliseconds(self.timeout)
269
def interval_milliseconds(self):
270
"Return the 'interval' attribute in milliseconds"
271
return self._datetime_to_milliseconds(self.interval)
273
def __init__(self, name = None, disable_hook=None, config=None):
203
def _set_timeout(self, timeout):
204
"Setter function for 'timeout' attribute"
205
self._timeout = timeout
206
self._timeout_milliseconds = ((self.timeout.days
207
* 24 * 60 * 60 * 1000)
208
+ (self.timeout.seconds * 1000)
209
+ (self.timeout.microseconds
211
timeout = property(lambda self: self._timeout,
214
def _set_interval(self, interval):
215
"Setter function for 'interval' attribute"
216
self._interval = interval
217
self._interval_milliseconds = ((self.interval.days
218
* 24 * 60 * 60 * 1000)
219
+ (self.interval.seconds
221
+ (self.interval.microseconds
223
interval = property(lambda self: self._interval,
226
def __init__(self, name = None, stop_hook=None, config=None):
274
227
"""Note: the 'checker' key in 'config' sets the
275
228
'checker_command' attribute and *not* the 'checker'
278
230
if config is None:
280
233
logger.debug(u"Creating client %r", self.name)
281
234
# Uppercase and remove spaces from fingerprint for later
282
235
# comparison purposes with return value from the fingerprint()
284
self.fingerprint = (config[u"fingerprint"].upper()
237
self.fingerprint = config["fingerprint"].upper()\
286
239
logger.debug(u" Fingerprint: %s", self.fingerprint)
287
if u"secret" in config:
288
self.secret = config[u"secret"].decode(u"base64")
289
elif u"secfile" in config:
290
with closing(open(os.path.expanduser
292
(config[u"secfile"])))) as secfile:
293
self.secret = secfile.read()
240
if "secret" in config:
241
self.secret = config["secret"].decode(u"base64")
242
elif "secfile" in config:
243
secfile = open(os.path.expanduser(os.path.expandvars
244
(config["secfile"])))
245
self.secret = secfile.read()
295
248
raise TypeError(u"No secret or secfile for client %s"
297
self.host = config.get(u"host", u"")
298
self.created = datetime.datetime.utcnow()
300
self.last_enabled = None
250
self.host = config.get("host", "")
251
self.created = datetime.datetime.now()
301
252
self.last_checked_ok = None
302
self.timeout = string_to_delta(config[u"timeout"])
303
self.interval = string_to_delta(config[u"interval"])
304
self.disable_hook = disable_hook
253
self.timeout = string_to_delta(config["timeout"])
254
self.interval = string_to_delta(config["interval"])
255
self.stop_hook = stop_hook
305
256
self.checker = None
306
257
self.checker_initiator_tag = None
307
self.disable_initiator_tag = None
258
self.stop_initiator_tag = None
308
259
self.checker_callback_tag = None
309
self.checker_command = config[u"checker"]
310
self.current_checker_command = None
311
self.last_connect = None
260
self.check_command = config["checker"]
314
262
"""Start this client's checker and timeout hooks"""
315
self.last_enabled = datetime.datetime.utcnow()
316
263
# Schedule a new checker to be started an 'interval' from now,
317
264
# and every interval from then on.
318
self.checker_initiator_tag = (gobject.timeout_add
319
(self.interval_milliseconds(),
265
self.checker_initiator_tag = gobject.timeout_add\
266
(self._interval_milliseconds,
321
268
# Also start a new checker *right now*.
322
269
self.start_checker()
323
# Schedule a disable() when 'timeout' has passed
324
self.disable_initiator_tag = (gobject.timeout_add
325
(self.timeout_milliseconds(),
330
"""Disable this client."""
331
if not getattr(self, "enabled", False):
270
# Schedule a stop() when 'timeout' has passed
271
self.stop_initiator_tag = gobject.timeout_add\
272
(self._timeout_milliseconds,
276
The possibility that a client might be restarted is left open,
277
but not currently used."""
278
# If this client doesn't have a secret, it is already stopped.
279
if hasattr(self, "secret") and self.secret:
280
logger.info(u"Stopping client %s", self.name)
333
logger.info(u"Disabling client %s", self.name)
334
if getattr(self, u"disable_initiator_tag", False):
335
gobject.source_remove(self.disable_initiator_tag)
336
self.disable_initiator_tag = None
337
if getattr(self, u"checker_initiator_tag", False):
284
if getattr(self, "stop_initiator_tag", False):
285
gobject.source_remove(self.stop_initiator_tag)
286
self.stop_initiator_tag = None
287
if getattr(self, "checker_initiator_tag", False):
338
288
gobject.source_remove(self.checker_initiator_tag)
339
289
self.checker_initiator_tag = None
340
290
self.stop_checker()
341
if self.disable_hook:
342
self.disable_hook(self)
344
293
# Do not run this again if called by a gobject.timeout_add
347
295
def __del__(self):
348
self.disable_hook = None
351
def checker_callback(self, pid, condition, command):
296
self.stop_hook = None
298
def checker_callback(self, pid, condition):
352
299
"""The checker has completed, so take appropriate actions."""
300
now = datetime.datetime.now()
353
301
self.checker_callback_tag = None
354
302
self.checker = None
355
if os.WIFEXITED(condition):
356
exitstatus = os.WEXITSTATUS(condition)
358
logger.info(u"Checker for %(name)s succeeded",
362
logger.info(u"Checker for %(name)s failed",
303
if os.WIFEXITED(condition) \
304
and (os.WEXITSTATUS(condition) == 0):
305
logger.info(u"Checker for %(name)s succeeded",
307
self.last_checked_ok = now
308
gobject.source_remove(self.stop_initiator_tag)
309
self.stop_initiator_tag = gobject.timeout_add\
310
(self._timeout_milliseconds,
312
elif not os.WIFEXITED(condition):
365
313
logger.warning(u"Checker for %(name)s crashed?",
368
def checked_ok(self):
369
"""Bump up the timeout for this client.
371
This should only be called when the client has been seen,
374
self.last_checked_ok = datetime.datetime.utcnow()
375
gobject.source_remove(self.disable_initiator_tag)
376
self.disable_initiator_tag = (gobject.timeout_add
377
(self.timeout_milliseconds(),
316
logger.info(u"Checker for %(name)s failed",
380
318
def start_checker(self):
381
319
"""Start a new checker subprocess if one is not running.
383
320
If a checker already exists, leave it running and do
385
322
# The reason for not killing a running checker is that if we
462
377
if error.errno != errno.ESRCH: # No such process
464
379
self.checker = None
466
380
def still_valid(self):
467
381
"""Has the timeout not yet passed for this client?"""
468
if not getattr(self, u"enabled", False):
470
now = datetime.datetime.utcnow()
382
now = datetime.datetime.now()
471
383
if self.last_checked_ok is None:
472
384
return now < (self.created + self.timeout)
474
386
return now < (self.last_checked_ok + self.timeout)
477
class ClientDBus(Client, dbus.service.Object):
478
"""A Client class using D-Bus
481
dbus_object_path: dbus.ObjectPath
482
bus: dbus.SystemBus()
484
# dbus.service.Object doesn't use super(), so we can't either.
486
def __init__(self, bus = None, *args, **kwargs):
488
Client.__init__(self, *args, **kwargs)
489
# Only now, when this client is initialized, can it show up on
491
self.dbus_object_path = (dbus.ObjectPath
493
+ self.name.replace(u".", u"_")))
494
dbus.service.Object.__init__(self, self.bus,
495
self.dbus_object_path)
498
def _datetime_to_dbus(dt, variant_level=0):
499
"""Convert a UTC datetime.datetime() to a D-Bus type."""
500
return dbus.String(dt.isoformat(),
501
variant_level=variant_level)
504
oldstate = getattr(self, u"enabled", False)
505
r = Client.enable(self)
506
if oldstate != self.enabled:
508
self.PropertyChanged(dbus.String(u"enabled"),
509
dbus.Boolean(True, variant_level=1))
510
self.PropertyChanged(
511
dbus.String(u"last_enabled"),
512
self._datetime_to_dbus(self.last_enabled,
516
def disable(self, signal = True):
517
oldstate = getattr(self, u"enabled", False)
518
r = Client.disable(self)
519
if signal and oldstate != self.enabled:
521
self.PropertyChanged(dbus.String(u"enabled"),
522
dbus.Boolean(False, variant_level=1))
525
def __del__(self, *args, **kwargs):
527
self.remove_from_connection()
530
if hasattr(dbus.service.Object, u"__del__"):
531
dbus.service.Object.__del__(self, *args, **kwargs)
532
Client.__del__(self, *args, **kwargs)
534
def checker_callback(self, pid, condition, command,
536
self.checker_callback_tag = None
539
self.PropertyChanged(dbus.String(u"checker_running"),
540
dbus.Boolean(False, variant_level=1))
541
if os.WIFEXITED(condition):
542
exitstatus = os.WEXITSTATUS(condition)
544
self.CheckerCompleted(dbus.Int16(exitstatus),
545
dbus.Int64(condition),
546
dbus.String(command))
549
self.CheckerCompleted(dbus.Int16(-1),
550
dbus.Int64(condition),
551
dbus.String(command))
553
return Client.checker_callback(self, pid, condition, command,
556
def checked_ok(self, *args, **kwargs):
557
r = Client.checked_ok(self, *args, **kwargs)
559
self.PropertyChanged(
560
dbus.String(u"last_checked_ok"),
561
(self._datetime_to_dbus(self.last_checked_ok,
565
def start_checker(self, *args, **kwargs):
566
old_checker = self.checker
567
if self.checker is not None:
568
old_checker_pid = self.checker.pid
570
old_checker_pid = None
571
r = Client.start_checker(self, *args, **kwargs)
572
# Only if new checker process was started
573
if (self.checker is not None
574
and old_checker_pid != self.checker.pid):
576
self.CheckerStarted(self.current_checker_command)
577
self.PropertyChanged(
578
dbus.String(u"checker_running"),
579
dbus.Boolean(True, variant_level=1))
582
def stop_checker(self, *args, **kwargs):
583
old_checker = getattr(self, u"checker", None)
584
r = Client.stop_checker(self, *args, **kwargs)
585
if (old_checker is not None
586
and getattr(self, u"checker", None) is None):
587
self.PropertyChanged(dbus.String(u"checker_running"),
588
dbus.Boolean(False, variant_level=1))
591
## D-Bus methods & signals
592
_interface = u"se.bsnet.fukt.Mandos.Client"
595
@dbus.service.method(_interface)
597
return self.checked_ok()
599
# CheckerCompleted - signal
600
@dbus.service.signal(_interface, signature=u"nxs")
601
def CheckerCompleted(self, exitcode, waitstatus, command):
605
# CheckerStarted - signal
606
@dbus.service.signal(_interface, signature=u"s")
607
def CheckerStarted(self, command):
611
# GetAllProperties - method
612
@dbus.service.method(_interface, out_signature=u"a{sv}")
613
def GetAllProperties(self):
615
return dbus.Dictionary({
616
dbus.String(u"name"):
617
dbus.String(self.name, variant_level=1),
618
dbus.String(u"fingerprint"):
619
dbus.String(self.fingerprint, variant_level=1),
620
dbus.String(u"host"):
621
dbus.String(self.host, variant_level=1),
622
dbus.String(u"created"):
623
self._datetime_to_dbus(self.created,
625
dbus.String(u"last_enabled"):
626
(self._datetime_to_dbus(self.last_enabled,
628
if self.last_enabled is not None
629
else dbus.Boolean(False, variant_level=1)),
630
dbus.String(u"enabled"):
631
dbus.Boolean(self.enabled, variant_level=1),
632
dbus.String(u"last_checked_ok"):
633
(self._datetime_to_dbus(self.last_checked_ok,
635
if self.last_checked_ok is not None
636
else dbus.Boolean (False, variant_level=1)),
637
dbus.String(u"timeout"):
638
dbus.UInt64(self.timeout_milliseconds(),
640
dbus.String(u"interval"):
641
dbus.UInt64(self.interval_milliseconds(),
643
dbus.String(u"checker"):
644
dbus.String(self.checker_command,
646
dbus.String(u"checker_running"):
647
dbus.Boolean(self.checker is not None,
649
dbus.String(u"object_path"):
650
dbus.ObjectPath(self.dbus_object_path,
654
# IsStillValid - method
655
@dbus.service.method(_interface, out_signature=u"b")
656
def IsStillValid(self):
657
return self.still_valid()
659
# PropertyChanged - signal
660
@dbus.service.signal(_interface, signature=u"sv")
661
def PropertyChanged(self, property, value):
665
# ReceivedSecret - signal
666
@dbus.service.signal(_interface)
667
def ReceivedSecret(self):
672
@dbus.service.signal(_interface)
677
# SetChecker - method
678
@dbus.service.method(_interface, in_signature=u"s")
679
def SetChecker(self, checker):
680
"D-Bus setter method"
681
self.checker_command = checker
683
self.PropertyChanged(dbus.String(u"checker"),
684
dbus.String(self.checker_command,
688
@dbus.service.method(_interface, in_signature=u"s")
689
def SetHost(self, host):
690
"D-Bus setter method"
693
self.PropertyChanged(dbus.String(u"host"),
694
dbus.String(self.host, variant_level=1))
696
# SetInterval - method
697
@dbus.service.method(_interface, in_signature=u"t")
698
def SetInterval(self, milliseconds):
699
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
701
self.PropertyChanged(dbus.String(u"interval"),
702
(dbus.UInt64(self.interval_milliseconds(),
706
@dbus.service.method(_interface, in_signature=u"ay",
708
def SetSecret(self, secret):
709
"D-Bus setter method"
710
self.secret = str(secret)
712
# SetTimeout - method
713
@dbus.service.method(_interface, in_signature=u"t")
714
def SetTimeout(self, milliseconds):
715
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
717
self.PropertyChanged(dbus.String(u"timeout"),
718
(dbus.UInt64(self.timeout_milliseconds(),
722
@dbus.service.method(_interface)
727
# StartChecker - method
728
@dbus.service.method(_interface)
729
def StartChecker(self):
734
@dbus.service.method(_interface)
739
# StopChecker - method
740
@dbus.service.method(_interface)
741
def StopChecker(self):
747
class ClientHandler(socketserver.BaseRequestHandler, object):
748
"""A class to handle client connections.
750
Instantiated once for each connection to handle it.
389
def peer_certificate(session):
390
"Return the peer's OpenPGP certificate as a bytestring"
391
# If not an OpenPGP certificate...
392
if gnutls.library.functions.gnutls_certificate_type_get\
393
(session._c_object) \
394
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
395
# ...do the normal thing
396
return session.peer_certificate
397
list_size = ctypes.c_uint()
398
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
399
(session._c_object, ctypes.byref(list_size))
400
if list_size.value == 0:
403
return ctypes.string_at(cert.data, cert.size)
406
def fingerprint(openpgp):
407
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
408
# New GnuTLS "datum" with the OpenPGP public key
409
datum = gnutls.library.types.gnutls_datum_t\
410
(ctypes.cast(ctypes.c_char_p(openpgp),
411
ctypes.POINTER(ctypes.c_ubyte)),
412
ctypes.c_uint(len(openpgp)))
413
# New empty GnuTLS certificate
414
crt = gnutls.library.types.gnutls_openpgp_crt_t()
415
gnutls.library.functions.gnutls_openpgp_crt_init\
417
# Import the OpenPGP public key into the certificate
418
gnutls.library.functions.gnutls_openpgp_crt_import\
419
(crt, ctypes.byref(datum),
420
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
421
# Verify the self signature in the key
422
crtverify = ctypes.c_uint()
423
gnutls.library.functions.gnutls_openpgp_crt_verify_self\
424
(crt, 0, ctypes.byref(crtverify))
425
if crtverify.value != 0:
426
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
427
raise gnutls.errors.CertificateSecurityError("Verify failed")
428
# New buffer for the fingerprint
429
buf = ctypes.create_string_buffer(20)
430
buf_len = ctypes.c_size_t()
431
# Get the fingerprint from the certificate into the buffer
432
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
433
(crt, ctypes.byref(buf), ctypes.byref(buf_len))
434
# Deinit the certificate
435
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
436
# Convert the buffer to a Python bytestring
437
fpr = ctypes.string_at(buf, buf_len.value)
438
# Convert the bytestring to hexadecimal notation
439
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
443
class TCP_handler(SocketServer.BaseRequestHandler, object):
444
"""A TCP request handler class.
445
Instantiated by IPv6_TCPServer for each request to handle it.
751
446
Note: This will run in its own forked process."""
753
448
def handle(self):
754
449
logger.info(u"TCP connection from: %s",
755
unicode(self.client_address))
756
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
757
# Open IPC pipe to parent process
758
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
759
session = (gnutls.connection
760
.ClientSession(self.request,
764
line = self.request.makefile().readline()
765
logger.debug(u"Protocol version: %r", line)
767
if int(line.strip().split()[0]) > 1:
769
except (ValueError, IndexError, RuntimeError), error:
770
logger.error(u"Unknown protocol version: %s", error)
773
# Note: gnutls.connection.X509Credentials is really a
774
# generic GnuTLS certificate credentials object so long as
775
# no X.509 keys are added to it. Therefore, we can use it
776
# here despite using OpenPGP certificates.
778
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
779
# u"+AES-256-CBC", u"+SHA1",
780
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
782
# Use a fallback default, since this MUST be set.
783
priority = self.server.gnutls_priority
786
(gnutls.library.functions
787
.gnutls_priority_set_direct(session._c_object,
792
except gnutls.errors.GNUTLSError, error:
793
logger.warning(u"Handshake failed: %s", error)
794
# Do not run session.bye() here: the session is not
795
# established. Just abandon the request.
797
logger.debug(u"Handshake succeeded")
799
fpr = self.fingerprint(self.peer_certificate(session))
800
except (TypeError, gnutls.errors.GNUTLSError), error:
801
logger.warning(u"Bad certificate: %s", error)
804
logger.debug(u"Fingerprint: %s", fpr)
806
for c in self.server.clients:
807
if c.fingerprint == fpr:
811
ipc.write(u"NOTFOUND %s\n" % fpr)
814
# Have to check if client.still_valid(), since it is
815
# possible that the client timed out while establishing
816
# the GnuTLS session.
817
if not client.still_valid():
818
ipc.write(u"INVALID %s\n" % client.name)
821
ipc.write(u"SENDING %s\n" % client.name)
823
while sent_size < len(client.secret):
824
sent = session.send(client.secret[sent_size:])
825
logger.debug(u"Sent: %d, remaining: %d",
826
sent, len(client.secret)
827
- (sent_size + sent))
832
def peer_certificate(session):
833
"Return the peer's OpenPGP certificate as a bytestring"
834
# If not an OpenPGP certificate...
835
if (gnutls.library.functions
836
.gnutls_certificate_type_get(session._c_object)
837
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
838
# ...do the normal thing
839
return session.peer_certificate
840
list_size = ctypes.c_uint(1)
841
cert_list = (gnutls.library.functions
842
.gnutls_certificate_get_peers
843
(session._c_object, ctypes.byref(list_size)))
844
if not bool(cert_list) and list_size.value != 0:
845
raise gnutls.errors.GNUTLSError(u"error getting peer"
847
if list_size.value == 0:
850
return ctypes.string_at(cert.data, cert.size)
853
def fingerprint(openpgp):
854
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
855
# New GnuTLS "datum" with the OpenPGP public key
856
datum = (gnutls.library.types
857
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
860
ctypes.c_uint(len(openpgp))))
861
# New empty GnuTLS certificate
862
crt = gnutls.library.types.gnutls_openpgp_crt_t()
863
(gnutls.library.functions
864
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
865
# Import the OpenPGP public key into the certificate
866
(gnutls.library.functions
867
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
868
gnutls.library.constants
869
.GNUTLS_OPENPGP_FMT_RAW))
870
# Verify the self signature in the key
871
crtverify = ctypes.c_uint()
872
(gnutls.library.functions
873
.gnutls_openpgp_crt_verify_self(crt, 0,
874
ctypes.byref(crtverify)))
875
if crtverify.value != 0:
876
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
877
raise (gnutls.errors.CertificateSecurityError
879
# New buffer for the fingerprint
880
buf = ctypes.create_string_buffer(20)
881
buf_len = ctypes.c_size_t()
882
# Get the fingerprint from the certificate into the buffer
883
(gnutls.library.functions
884
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
885
ctypes.byref(buf_len)))
886
# Deinit the certificate
887
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
888
# Convert the buffer to a Python bytestring
889
fpr = ctypes.string_at(buf, buf_len.value)
890
# Convert the bytestring to hexadecimal notation
891
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
895
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
896
"""Like socketserver.ForkingMixIn, but also pass a pipe.
898
Assumes a gobject.MainLoop event loop.
900
def process_request(self, request, client_address):
901
"""Overrides and wraps the original process_request().
903
This function creates a new pipe in self.pipe
905
self.pipe = os.pipe()
906
super(ForkingMixInWithPipe,
907
self).process_request(request, client_address)
908
os.close(self.pipe[1]) # close write end
909
# Call "handle_ipc" for both data and EOF events
910
gobject.io_add_watch(self.pipe[0],
911
gobject.IO_IN | gobject.IO_HUP,
913
def handle_ipc(self, source, condition):
914
"""Dummy function; override as necessary"""
919
class IPv6_TCPServer(ForkingMixInWithPipe,
920
socketserver.TCPServer, object):
921
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
450
unicode(self.client_address))
451
session = gnutls.connection.ClientSession\
452
(self.request, gnutls.connection.X509Credentials())
454
line = self.request.makefile().readline()
455
logger.debug(u"Protocol version: %r", line)
457
if int(line.strip().split()[0]) > 1:
459
except (ValueError, IndexError, RuntimeError), error:
460
logger.error(u"Unknown protocol version: %s", error)
463
# Note: gnutls.connection.X509Credentials is really a generic
464
# GnuTLS certificate credentials object so long as no X.509
465
# keys are added to it. Therefore, we can use it here despite
466
# using OpenPGP certificates.
468
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
469
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
471
priority = "NORMAL" # Fallback default, since this
473
if self.server.settings["priority"]:
474
priority = self.server.settings["priority"]
475
gnutls.library.functions.gnutls_priority_set_direct\
476
(session._c_object, priority, None)
480
except gnutls.errors.GNUTLSError, error:
481
logger.warning(u"Handshake failed: %s", error)
482
# Do not run session.bye() here: the session is not
483
# established. Just abandon the request.
486
fpr = fingerprint(peer_certificate(session))
487
except (TypeError, gnutls.errors.GNUTLSError), error:
488
logger.warning(u"Bad certificate: %s", error)
491
logger.debug(u"Fingerprint: %s", fpr)
493
for c in self.server.clients:
494
if c.fingerprint == fpr:
498
logger.warning(u"Client not found for fingerprint: %s",
502
# Have to check if client.still_valid(), since it is possible
503
# that the client timed out while establishing the GnuTLS
505
if not client.still_valid():
506
logger.warning(u"Client %(name)s is invalid",
511
while sent_size < len(client.secret):
512
sent = session.send(client.secret[sent_size:])
513
logger.debug(u"Sent: %d, remaining: %d",
514
sent, len(client.secret)
515
- (sent_size + sent))
520
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
521
"""IPv6 TCP server. Accepts 'None' as address and/or port.
523
settings: Server settings
524
clients: Set() of Client objects
924
525
enabled: Boolean; whether this server is activated yet
925
interface: None or a network interface name (string)
926
use_ipv6: Boolean; to use IPv6 or not
928
clients: set of Client objects
929
gnutls_priority GnuTLS priority string
930
use_dbus: Boolean; to emit D-Bus signals or not
932
def __init__(self, server_address, RequestHandlerClass,
933
interface=None, use_ipv6=True):
934
self.interface = interface
936
self.address_family = socket.AF_INET6
937
socketserver.TCPServer.__init__(self, server_address,
527
address_family = socket.AF_INET6
528
def __init__(self, *args, **kwargs):
529
if "settings" in kwargs:
530
self.settings = kwargs["settings"]
531
del kwargs["settings"]
532
if "clients" in kwargs:
533
self.clients = kwargs["clients"]
534
del kwargs["clients"]
536
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
939
537
def server_bind(self):
940
538
"""This overrides the normal server_bind() function
941
539
to bind to an interface if one was specified, and also NOT to
942
540
bind to an address or port if they were not specified."""
943
if self.interface is not None:
944
if SO_BINDTODEVICE is None:
945
logger.error(u"SO_BINDTODEVICE does not exist;"
946
u" cannot bind to interface %s",
950
self.socket.setsockopt(socket.SOL_SOCKET,
954
except socket.error, error:
955
if error[0] == errno.EPERM:
956
logger.error(u"No permission to"
957
u" bind to interface %s",
959
elif error[0] == errno.ENOPROTOOPT:
960
logger.error(u"SO_BINDTODEVICE not available;"
961
u" cannot bind to interface %s",
541
if self.settings["interface"]:
542
# 25 is from /usr/include/asm-i486/socket.h
543
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
545
self.socket.setsockopt(socket.SOL_SOCKET,
547
self.settings["interface"])
548
except socket.error, error:
549
if error[0] == errno.EPERM:
550
logger.error(u"No permission to"
551
u" bind to interface %s",
552
self.settings["interface"])
965
555
# Only bind(2) the socket if we really need to.
966
556
if self.server_address[0] or self.server_address[1]:
967
557
if not self.server_address[0]:
968
if self.address_family == socket.AF_INET6:
969
any_address = u"::" # in6addr_any
971
any_address = socket.INADDR_ANY
972
self.server_address = (any_address,
559
self.server_address = (in6addr_any,
973
560
self.server_address[1])
974
561
elif not self.server_address[1]:
975
562
self.server_address = (self.server_address[0],
564
# if self.settings["interface"]:
978
565
# self.server_address = (self.server_address[0],
983
return socketserver.TCPServer.server_bind(self)
986
class MandosServer(IPv6_TCPServer):
990
clients: set of Client objects
991
gnutls_priority GnuTLS priority string
992
use_dbus: Boolean; to emit D-Bus signals or not
994
def __init__(self, server_address, RequestHandlerClass,
995
interface=None, use_ipv6=True, clients=None,
996
gnutls_priority=None, use_dbus=True):
998
self.clients = clients
999
self.use_dbus = use_dbus
1000
self.gnutls_priority = gnutls_priority
1001
IPv6_TCPServer.__init__(self, server_address,
1002
RequestHandlerClass,
1003
interface = interface,
1004
use_ipv6 = use_ipv6)
571
return super(IPv6_TCPServer, self).server_bind()
1005
572
def server_activate(self):
1006
573
if self.enabled:
1007
return socketserver.TCPServer.server_activate(self)
574
return super(IPv6_TCPServer, self).server_activate()
1008
575
def enable(self):
1009
576
self.enabled = True
1010
def handle_ipc(self, source, condition, file_objects={}):
1012
gobject.IO_IN: u"IN", # There is data to read.
1013
gobject.IO_OUT: u"OUT", # Data can be written (without
1015
gobject.IO_PRI: u"PRI", # There is urgent data to read.
1016
gobject.IO_ERR: u"ERR", # Error condition.
1017
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
1018
# broken, usually for pipes and
1021
conditions_string = ' | '.join(name
1023
condition_names.iteritems()
1024
if cond & condition)
1025
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
1028
# Turn the pipe file descriptor into a Python file object
1029
if source not in file_objects:
1030
file_objects[source] = os.fdopen(source, u"r", 1)
1032
# Read a line from the file object
1033
cmdline = file_objects[source].readline()
1034
if not cmdline: # Empty line means end of file
1035
# close the IPC pipe
1036
file_objects[source].close()
1037
del file_objects[source]
1039
# Stop calling this function
1042
logger.debug(u"IPC command: %r", cmdline)
1044
# Parse and act on command
1045
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1047
if cmd == u"NOTFOUND":
1048
logger.warning(u"Client not found for fingerprint: %s",
1052
mandos_dbus_service.ClientNotFound(args)
1053
elif cmd == u"INVALID":
1054
for client in self.clients:
1055
if client.name == args:
1056
logger.warning(u"Client %s is invalid", args)
1062
logger.error(u"Unknown client %s is invalid", args)
1063
elif cmd == u"SENDING":
1064
for client in self.clients:
1065
if client.name == args:
1066
logger.info(u"Sending secret to %s", client.name)
1070
client.ReceivedSecret()
1073
logger.error(u"Sending secret to unknown client %s",
1076
logger.error(u"Unknown IPC command: %r", cmdline)
1078
# Keep calling this function
1082
579
def string_to_delta(interval):
1083
580
"""Parse a string and return a datetime.timedelta
1085
>>> string_to_delta(u'7d')
582
>>> string_to_delta('7d')
1086
583
datetime.timedelta(7)
1087
>>> string_to_delta(u'60s')
584
>>> string_to_delta('60s')
1088
585
datetime.timedelta(0, 60)
1089
>>> string_to_delta(u'60m')
586
>>> string_to_delta('60m')
1090
587
datetime.timedelta(0, 3600)
1091
>>> string_to_delta(u'24h')
588
>>> string_to_delta('24h')
1092
589
datetime.timedelta(1)
1093
590
>>> string_to_delta(u'1w')
1094
591
datetime.timedelta(7)
1095
>>> string_to_delta(u'5m 30s')
592
>>> string_to_delta('5m 30s')
1096
593
datetime.timedelta(0, 330)
1098
595
timevalue = datetime.timedelta(0)
1206
715
# Default values for config file for server-global settings
1207
server_defaults = { u"interface": u"",
1212
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1213
u"servicename": u"Mandos",
1214
u"use_dbus": u"True",
1215
u"use_ipv6": u"True",
716
server_defaults = { "interface": "",
721
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
722
"servicename": "Mandos",
1218
725
# Parse config file for server-global settings
1219
server_config = configparser.SafeConfigParser(server_defaults)
726
server_config = ConfigParser.SafeConfigParser(server_defaults)
1220
727
del server_defaults
1221
server_config.read(os.path.join(options.configdir,
728
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1223
729
# Convert the SafeConfigParser object to a dict
1224
730
server_settings = server_config.defaults()
1225
# Use the appropriate methods on the non-string config options
1226
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1227
server_settings[option] = server_config.getboolean(u"DEFAULT",
1229
if server_settings["port"]:
1230
server_settings["port"] = server_config.getint(u"DEFAULT",
731
# Use getboolean on the boolean config option
732
server_settings["debug"] = server_config.getboolean\
1232
734
del server_config
1234
736
# Override the settings from the config file with command line
1235
737
# options, if set.
1236
for option in (u"interface", u"address", u"port", u"debug",
1237
u"priority", u"servicename", u"configdir",
1238
u"use_dbus", u"use_ipv6"):
738
for option in ("interface", "address", "port", "debug",
739
"priority", "servicename", "configdir"):
1239
740
value = getattr(options, option)
1240
741
if value is not None:
1241
742
server_settings[option] = value
1243
# Force all strings to be unicode
1244
for option in server_settings.keys():
1245
if type(server_settings[option]) is str:
1246
server_settings[option] = unicode(server_settings[option])
1247
744
# Now we have our good server settings in "server_settings"
1249
##################################################################
1252
debug = server_settings[u"debug"]
1253
use_dbus = server_settings[u"use_dbus"]
1254
use_ipv6 = server_settings[u"use_ipv6"]
746
debug = server_settings["debug"]
1257
749
syslogger.setLevel(logging.WARNING)
1258
750
console.setLevel(logging.WARNING)
1260
if server_settings[u"servicename"] != u"Mandos":
1261
syslogger.setFormatter(logging.Formatter
1262
(u'Mandos (%s) [%%(process)d]:'
1263
u' %%(levelname)s: %%(message)s'
1264
% server_settings[u"servicename"]))
752
if server_settings["servicename"] != "Mandos":
753
syslogger.setFormatter(logging.Formatter\
754
('Mandos (%s): %%(levelname)s:'
756
% server_settings["servicename"]))
1266
758
# Parse config file with clients
1267
client_defaults = { u"timeout": u"1h",
1269
u"checker": u"fping -q -- %%(host)s",
759
client_defaults = { "timeout": "1h",
761
"checker": "fping -q -- %(host)s",
1272
client_config = configparser.SafeConfigParser(client_defaults)
1273
client_config.read(os.path.join(server_settings[u"configdir"],
1276
global mandos_dbus_service
1277
mandos_dbus_service = None
1280
tcp_server = MandosServer((server_settings[u"address"],
1281
server_settings[u"port"]),
1283
interface=server_settings[u"interface"],
1287
server_settings[u"priority"],
1289
pidfilename = u"/var/run/mandos.pid"
1291
pidfile = open(pidfilename, u"w")
1293
logger.error(u"Could not open file %r", pidfilename)
1296
uid = pwd.getpwnam(u"_mandos").pw_uid
1297
gid = pwd.getpwnam(u"_mandos").pw_gid
1300
uid = pwd.getpwnam(u"mandos").pw_uid
1301
gid = pwd.getpwnam(u"mandos").pw_gid
1304
uid = pwd.getpwnam(u"nobody").pw_uid
1305
gid = pwd.getpwnam(u"nobody").pw_gid
764
client_config = ConfigParser.SafeConfigParser(client_defaults)
765
client_config.read(os.path.join(server_settings["configdir"],
769
tcp_server = IPv6_TCPServer((server_settings["address"],
770
server_settings["port"]),
772
settings=server_settings,
774
pidfilename = "/var/run/mandos.pid"
776
pidfile = open(pidfilename, "w")
777
except IOError, error:
778
logger.error("Could not open file %r", pidfilename)
783
uid = pwd.getpwnam("mandos").pw_uid
786
uid = pwd.getpwnam("nobody").pw_uid
790
gid = pwd.getpwnam("mandos").pw_gid
793
gid = pwd.getpwnam("nogroup").pw_gid
1312
799
except OSError, error:
1313
800
if error[0] != errno.EPERM:
1316
# Enable all possible GnuTLS debugging
1318
# "Use a log level over 10 to enable all debugging options."
1320
gnutls.library.functions.gnutls_global_set_log_level(11)
1322
@gnutls.library.types.gnutls_log_func
1323
def debug_gnutls(level, string):
1324
logger.debug(u"GnuTLS: %s", string[:-1])
1326
(gnutls.library.functions
1327
.gnutls_global_set_log_function(debug_gnutls))
804
service = AvahiService(name = server_settings["servicename"],
805
servicetype = "_mandos._tcp", )
806
if server_settings["interface"]:
807
service.interface = if_nametoindex\
808
(server_settings["interface"])
1329
810
global main_loop
1330
813
# From the Avahi example code
1331
814
DBusGMainLoop(set_as_default=True )
1332
815
main_loop = gobject.MainLoop()
1333
816
bus = dbus.SystemBus()
817
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
818
avahi.DBUS_PATH_SERVER),
819
avahi.DBUS_INTERFACE_SERVER)
1334
820
# End of Avahi example code
1336
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1337
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1338
service = AvahiService(name = server_settings[u"servicename"],
1339
servicetype = u"_mandos._tcp",
1340
protocol = protocol, bus = bus)
1341
if server_settings["interface"]:
1342
service.interface = (if_nametoindex
1343
(str(server_settings[u"interface"])))
1345
client_class = Client
1347
client_class = functools.partial(ClientDBus, bus = bus)
1349
client_class(name = section,
1350
config= dict(client_config.items(section)))
1351
for section in client_config.sections()))
822
def remove_from_clients(client):
823
clients.remove(client)
825
logger.critical(u"No clients left, exiting")
828
clients.update(Set(Client(name = section,
829
stop_hook = remove_from_clients,
831
= dict(client_config.items(section)))
832
for section in client_config.sections()))
1353
logger.warning(u"No clients defined")
834
logger.critical(u"No clients defined")
1356
838
# Redirect stdin so all checkers get /dev/null