141
124
self.rename_count = 0
142
125
self.max_renames = max_renames
143
self.protocol = protocol
144
self.group = None # our entry group
146
126
def rename(self):
147
127
"""Derived from the Avahi example code"""
148
128
if self.rename_count >= self.max_renames:
149
129
logger.critical(u"No suitable Zeroconf service name found"
150
130
u" after %i retries, exiting.",
151
131
self.rename_count)
152
raise AvahiServiceError(u"Too many renames")
153
self.name = self.server.GetAlternativeServiceName(self.name)
132
raise AvahiServiceError("Too many renames")
133
self.name = server.GetAlternativeServiceName(self.name)
154
134
logger.info(u"Changing Zeroconf service name to %r ...",
156
136
syslogger.setFormatter(logging.Formatter
157
(u'Mandos (%s) [%%(process)d]:'
158
u' %%(levelname)s: %%(message)s'
137
('Mandos (%s): %%(levelname)s:'
138
' %%(message)s' % self.name))
162
141
self.rename_count += 1
163
142
def remove(self):
164
143
"""Derived from the Avahi example code"""
165
if self.group is not None:
144
if group is not None:
168
147
"""Derived from the Avahi example code"""
169
if self.group is None:
170
self.group = dbus.Interface(
171
bus.get_object(avahi.DBUS_NAME,
172
self.server.EntryGroupNew()),
173
avahi.DBUS_INTERFACE_ENTRY_GROUP)
174
self.group.connect_to_signal('StateChanged',
175
self.entry_group_state_changed)
150
group = dbus.Interface(bus.get_object
152
server.EntryGroupNew()),
153
avahi.DBUS_INTERFACE_ENTRY_GROUP)
154
group.connect_to_signal('StateChanged',
155
entry_group_state_changed)
176
156
logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
177
self.name, self.type)
178
self.group.AddService(
181
dbus.UInt32(0), # flags
182
self.name, self.type,
183
self.domain, self.host,
184
dbus.UInt16(self.port),
185
avahi.string_array_to_txt_array(self.TXT))
187
def entry_group_state_changed(self, state, error):
188
"""Derived from the Avahi example code"""
189
logger.debug(u"Avahi state change: %i", state)
191
if state == avahi.ENTRY_GROUP_ESTABLISHED:
192
logger.debug(u"Zeroconf service established.")
193
elif state == avahi.ENTRY_GROUP_COLLISION:
194
logger.warning(u"Zeroconf service name collision.")
196
elif state == avahi.ENTRY_GROUP_FAILURE:
197
logger.critical(u"Avahi: Error in group state changed %s",
199
raise AvahiGroupError(u"State changed: %s"
202
"""Derived from the Avahi example code"""
203
if self.group is not None:
206
def server_state_changed(self, state):
207
"""Derived from the Avahi example code"""
208
if state == avahi.SERVER_COLLISION:
209
logger.error(u"Zeroconf server name collision")
211
elif state == avahi.SERVER_RUNNING:
214
"""Derived from the Avahi example code"""
215
if self.server is None:
216
self.server = dbus.Interface(
217
bus.get_object(avahi.DBUS_NAME,
218
avahi.DBUS_PATH_SERVER),
219
avahi.DBUS_INTERFACE_SERVER)
220
self.server.connect_to_signal(u"StateChanged",
221
self.server_state_changed)
222
self.server_state_changed(self.server.GetState())
225
class Client(object):
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
173
class Client(dbus.service.Object):
226
174
"""A representation of a client host served by this server.
229
name: string; from the config file, used in log messages and
176
name: string; from the config file, used in log messages
231
177
fingerprint: string (40 or 32 hexadecimal digits); used to
232
178
uniquely identify the client
233
secret: bytestring; sent verbatim (over TLS) to client
234
host: string; available for use by the checker command
235
created: datetime.datetime(); (UTC) object creation
236
last_enabled: datetime.datetime(); (UTC)
179
secret: bytestring; sent verbatim (over TLS) to client
180
host: string; available for use by the checker command
181
created: datetime.datetime(); (UTC) object creation
182
started: datetime.datetime(); (UTC) last started
238
183
last_checked_ok: datetime.datetime(); (UTC) or None
239
timeout: datetime.timedelta(); How long from last_checked_ok
240
until this client is invalid
241
interval: datetime.timedelta(); How often to start a new checker
242
disable_hook: If set, called by disable() as disable_hook(self)
243
checker: subprocess.Popen(); a running checker process used
244
to see if the client lives.
245
'None' if no process is running.
184
timeout: datetime.timedelta(); How long from last_checked_ok
185
until this client is invalid
186
interval: datetime.timedelta(); How often to start a new checker
187
stop_hook: If set, called by stop() as stop_hook(self)
188
checker: subprocess.Popen(); a running checker process used
189
to see if the client lives.
190
'None' if no process is running.
246
191
checker_initiator_tag: a gobject event source tag, or None
247
disable_initiator_tag: - '' -
192
stop_initiator_tag: - '' -
248
193
checker_callback_tag: - '' -
249
194
checker_command: string; External command which is run to check if
250
195
client lives. %() expansions are done at
251
196
runtime with vars(self) as dict, so that for
252
197
instance %(name)s can be used in the command.
253
current_checker_command: string; current running checker_command
199
_timeout: Real variable for 'timeout'
200
_interval: Real variable for 'interval'
201
_timeout_milliseconds: Used when calling gobject.timeout_add()
202
_interval_milliseconds: - '' -
257
def _datetime_to_milliseconds(dt):
258
"Convert a datetime.datetime() to milliseconds"
259
return ((dt.days * 24 * 60 * 60 * 1000)
260
+ (dt.seconds * 1000)
261
+ (dt.microseconds // 1000))
263
def timeout_milliseconds(self):
264
"Return the 'timeout' attribute in milliseconds"
265
return self._datetime_to_milliseconds(self.timeout)
267
def interval_milliseconds(self):
268
"Return the 'interval' attribute in milliseconds"
269
return self._datetime_to_milliseconds(self.interval)
271
def __init__(self, name = None, disable_hook=None, config=None):
204
def _set_timeout(self, timeout):
205
"Setter function for the 'timeout' attribute"
206
self._timeout = timeout
207
self._timeout_milliseconds = ((self.timeout.days
208
* 24 * 60 * 60 * 1000)
209
+ (self.timeout.seconds * 1000)
210
+ (self.timeout.microseconds
213
self.TimeoutChanged(self._timeout_milliseconds)
214
timeout = property(lambda self: self._timeout, _set_timeout)
217
def _set_interval(self, interval):
218
"Setter function for the 'interval' attribute"
219
self._interval = interval
220
self._interval_milliseconds = ((self.interval.days
221
* 24 * 60 * 60 * 1000)
222
+ (self.interval.seconds
224
+ (self.interval.microseconds
227
self.IntervalChanged(self._interval_milliseconds)
228
interval = property(lambda self: self._interval, _set_interval)
231
def __init__(self, name = None, stop_hook=None, config=None):
272
232
"""Note: the 'checker' key in 'config' sets the
273
233
'checker_command' attribute and *not* the 'checker'
235
dbus.service.Object.__init__(self, bus,
237
% name.replace(".", "_"))
276
238
if config is None:
278
241
logger.debug(u"Creating client %r", self.name)
279
242
# Uppercase and remove spaces from fingerprint for later
280
243
# comparison purposes with return value from the fingerprint()
282
self.fingerprint = (config[u"fingerprint"].upper()
245
self.fingerprint = (config["fingerprint"].upper()
283
246
.replace(u" ", u""))
284
247
logger.debug(u" Fingerprint: %s", self.fingerprint)
285
if u"secret" in config:
286
self.secret = config[u"secret"].decode(u"base64")
287
elif u"secfile" in config:
248
if "secret" in config:
249
self.secret = config["secret"].decode(u"base64")
250
elif "secfile" in config:
288
251
with closing(open(os.path.expanduser
289
252
(os.path.expandvars
290
(config[u"secfile"])))) as secfile:
253
(config["secfile"])))) as secfile:
291
254
self.secret = secfile.read()
293
256
raise TypeError(u"No secret or secfile for client %s"
295
self.host = config.get(u"host", u"")
258
self.host = config.get("host", "")
296
259
self.created = datetime.datetime.utcnow()
298
self.last_enabled = None
299
261
self.last_checked_ok = None
300
self.timeout = string_to_delta(config[u"timeout"])
301
self.interval = string_to_delta(config[u"interval"])
302
self.disable_hook = disable_hook
262
self.timeout = string_to_delta(config["timeout"])
263
self.interval = string_to_delta(config["interval"])
264
self.stop_hook = stop_hook
303
265
self.checker = None
304
266
self.checker_initiator_tag = None
305
self.disable_initiator_tag = None
267
self.stop_initiator_tag = None
306
268
self.checker_callback_tag = None
307
self.checker_command = config[u"checker"]
308
self.current_checker_command = None
309
self.last_connect = None
269
self.check_command = config["checker"]
312
272
"""Start this client's checker and timeout hooks"""
313
self.last_enabled = datetime.datetime.utcnow()
273
self.started = datetime.datetime.utcnow()
314
274
# Schedule a new checker to be started an 'interval' from now,
315
275
# and every interval from then on.
316
276
self.checker_initiator_tag = (gobject.timeout_add
317
(self.interval_milliseconds(),
277
(self._interval_milliseconds,
318
278
self.start_checker))
319
279
# Also start a new checker *right now*.
320
280
self.start_checker()
321
# Schedule a disable() when 'timeout' has passed
322
self.disable_initiator_tag = (gobject.timeout_add
323
(self.timeout_milliseconds(),
281
# Schedule a stop() when 'timeout' has passed
282
self.stop_initiator_tag = (gobject.timeout_add
283
(self._timeout_milliseconds,
286
self.StateChanged(True)
328
"""Disable this client."""
329
if not getattr(self, "enabled", False):
289
"""Stop this client."""
290
if getattr(self, "started", None) is not None:
291
logger.info(u"Stopping client %s", self.name)
331
logger.info(u"Disabling client %s", self.name)
332
if getattr(self, u"disable_initiator_tag", False):
333
gobject.source_remove(self.disable_initiator_tag)
334
self.disable_initiator_tag = None
335
if getattr(self, u"checker_initiator_tag", False):
294
if getattr(self, "stop_initiator_tag", False):
295
gobject.source_remove(self.stop_initiator_tag)
296
self.stop_initiator_tag = None
297
if getattr(self, "checker_initiator_tag", False):
336
298
gobject.source_remove(self.checker_initiator_tag)
337
299
self.checker_initiator_tag = None
338
300
self.stop_checker()
339
if self.disable_hook:
340
self.disable_hook(self)
305
self.StateChanged(False)
342
306
# Do not run this again if called by a gobject.timeout_add
345
309
def __del__(self):
346
self.disable_hook = None
310
self.stop_hook = None
349
def checker_callback(self, pid, condition, command):
313
def checker_callback(self, pid, condition):
350
314
"""The checker has completed, so take appropriate actions."""
351
315
self.checker_callback_tag = None
352
316
self.checker = None
353
if os.WIFEXITED(condition):
354
exitstatus = os.WEXITSTATUS(condition)
356
logger.info(u"Checker for %(name)s succeeded",
360
logger.info(u"Checker for %(name)s failed",
317
if (os.WIFEXITED(condition)
318
and (os.WEXITSTATUS(condition) == 0)):
319
logger.info(u"Checker for %(name)s succeeded",
322
self.CheckerCompleted(True)
324
elif not os.WIFEXITED(condition):
363
325
logger.warning(u"Checker for %(name)s crashed?",
328
self.CheckerCompleted(False)
330
logger.info(u"Checker for %(name)s failed",
333
self.CheckerCompleted(False)
366
def checked_ok(self):
335
def bump_timeout(self):
367
336
"""Bump up the timeout for this client.
369
337
This should only be called when the client has been seen,
372
340
self.last_checked_ok = datetime.datetime.utcnow()
373
gobject.source_remove(self.disable_initiator_tag)
374
self.disable_initiator_tag = (gobject.timeout_add
375
(self.timeout_milliseconds(),
341
gobject.source_remove(self.stop_initiator_tag)
342
self.stop_initiator_tag = (gobject.timeout_add
343
(self._timeout_milliseconds,
378
346
def start_checker(self):
379
347
"""Start a new checker subprocess if one is not running.
381
348
If a checker already exists, leave it running and do
383
350
# The reason for not killing a running checker is that if we
464
412
def still_valid(self):
465
413
"""Has the timeout not yet passed for this client?"""
466
if not getattr(self, u"enabled", False):
468
416
now = datetime.datetime.utcnow()
469
417
if self.last_checked_ok is None:
470
418
return now < (self.created + self.timeout)
472
420
return now < (self.last_checked_ok + self.timeout)
475
class ClientDBus(Client, dbus.service.Object):
476
"""A Client class using D-Bus
479
dbus_object_path: dbus.ObjectPath ; only set if self.use_dbus
481
# dbus.service.Object doesn't use super(), so we can't either.
483
def __init__(self, *args, **kwargs):
484
Client.__init__(self, *args, **kwargs)
485
# Only now, when this client is initialized, can it show up on
487
self.dbus_object_path = (dbus.ObjectPath
489
+ self.name.replace(u".", u"_")))
490
dbus.service.Object.__init__(self, bus,
491
self.dbus_object_path)
494
def _datetime_to_dbus(dt, variant_level=0):
495
"""Convert a UTC datetime.datetime() to a D-Bus type."""
496
return dbus.String(dt.isoformat(),
497
variant_level=variant_level)
500
oldstate = getattr(self, u"enabled", False)
501
r = Client.enable(self)
502
if oldstate != self.enabled:
504
self.PropertyChanged(dbus.String(u"enabled"),
505
dbus.Boolean(True, variant_level=1))
506
self.PropertyChanged(
507
dbus.String(u"last_enabled"),
508
self._datetime_to_dbus(self.last_enabled,
512
def disable(self, signal = True):
513
oldstate = getattr(self, u"enabled", False)
514
r = Client.disable(self)
515
if signal and oldstate != self.enabled:
517
self.PropertyChanged(dbus.String(u"enabled"),
518
dbus.Boolean(False, variant_level=1))
521
def __del__(self, *args, **kwargs):
523
self.remove_from_connection()
526
if hasattr(dbus.service.Object, u"__del__"):
527
dbus.service.Object.__del__(self, *args, **kwargs)
528
Client.__del__(self, *args, **kwargs)
530
def checker_callback(self, pid, condition, command,
532
self.checker_callback_tag = None
535
self.PropertyChanged(dbus.String(u"checker_running"),
536
dbus.Boolean(False, variant_level=1))
537
if os.WIFEXITED(condition):
538
exitstatus = os.WEXITSTATUS(condition)
540
self.CheckerCompleted(dbus.Int16(exitstatus),
541
dbus.Int64(condition),
542
dbus.String(command))
545
self.CheckerCompleted(dbus.Int16(-1),
546
dbus.Int64(condition),
547
dbus.String(command))
549
return Client.checker_callback(self, pid, condition, command,
552
def checked_ok(self, *args, **kwargs):
553
r = Client.checked_ok(self, *args, **kwargs)
555
self.PropertyChanged(
556
dbus.String(u"last_checked_ok"),
557
(self._datetime_to_dbus(self.last_checked_ok,
561
def start_checker(self, *args, **kwargs):
562
old_checker = self.checker
563
if self.checker is not None:
564
old_checker_pid = self.checker.pid
566
old_checker_pid = None
567
r = Client.start_checker(self, *args, **kwargs)
568
# Only if new checker process was started
569
if (self.checker is not None
570
and old_checker_pid != self.checker.pid):
572
self.CheckerStarted(self.current_checker_command)
573
self.PropertyChanged(
574
dbus.String(u"checker_running"),
575
dbus.Boolean(True, variant_level=1))
578
def stop_checker(self, *args, **kwargs):
579
old_checker = getattr(self, u"checker", None)
580
r = Client.stop_checker(self, *args, **kwargs)
581
if (old_checker is not None
582
and getattr(self, u"checker", None) is None):
583
self.PropertyChanged(dbus.String(u"checker_running"),
584
dbus.Boolean(False, variant_level=1))
587
422
## D-Bus methods & signals
588
_interface = u"se.bsnet.fukt.Mandos.Client"
591
@dbus.service.method(_interface)
593
return self.checked_ok()
423
_interface = u"org.mandos_system.Mandos.Client"
425
def _datetime_to_dbus_struct(dt):
426
return dbus.Struct(dt.year, dt.month, dt.day, dt.hour,
427
dt.minute, dt.second, dt.microsecond,
430
# BumpTimeout - method
431
BumpTimeout = dbus.service.method(_interface)(bump_timeout)
432
BumpTimeout.__name__ = "BumpTimeout"
434
# IntervalChanged - signal
435
@dbus.service.signal(_interface, signature="t")
436
def IntervalChanged(self, t):
595
440
# CheckerCompleted - signal
596
@dbus.service.signal(_interface, signature=u"nxs")
597
def CheckerCompleted(self, exitcode, waitstatus, command):
441
@dbus.service.signal(_interface, signature="b")
442
def CheckerCompleted(self, success):
446
# CheckerIsRunning - method
447
@dbus.service.method(_interface, out_signature="b")
448
def CheckerIsRunning(self):
449
"D-Bus getter method"
450
return self.checker is not None
601
452
# CheckerStarted - signal
602
@dbus.service.signal(_interface, signature=u"s")
453
@dbus.service.signal(_interface, signature="s")
603
454
def CheckerStarted(self, command):
607
# GetAllProperties - method
608
@dbus.service.method(_interface, out_signature=u"a{sv}")
609
def GetAllProperties(self):
611
return dbus.Dictionary({
612
dbus.String(u"name"):
613
dbus.String(self.name, variant_level=1),
614
dbus.String(u"fingerprint"):
615
dbus.String(self.fingerprint, variant_level=1),
616
dbus.String(u"host"):
617
dbus.String(self.host, variant_level=1),
618
dbus.String(u"created"):
619
self._datetime_to_dbus(self.created,
621
dbus.String(u"last_enabled"):
622
(self._datetime_to_dbus(self.last_enabled,
624
if self.last_enabled is not None
625
else dbus.Boolean(False, variant_level=1)),
626
dbus.String(u"enabled"):
627
dbus.Boolean(self.enabled, variant_level=1),
628
dbus.String(u"last_checked_ok"):
629
(self._datetime_to_dbus(self.last_checked_ok,
631
if self.last_checked_ok is not None
632
else dbus.Boolean (False, variant_level=1)),
633
dbus.String(u"timeout"):
634
dbus.UInt64(self.timeout_milliseconds(),
636
dbus.String(u"interval"):
637
dbus.UInt64(self.interval_milliseconds(),
639
dbus.String(u"checker"):
640
dbus.String(self.checker_command,
642
dbus.String(u"checker_running"):
643
dbus.Boolean(self.checker is not None,
645
dbus.String(u"object_path"):
646
dbus.ObjectPath(self.dbus_object_path,
650
# IsStillValid - method
651
@dbus.service.method(_interface, out_signature=u"b")
652
def IsStillValid(self):
653
return self.still_valid()
655
# PropertyChanged - signal
656
@dbus.service.signal(_interface, signature=u"sv")
657
def PropertyChanged(self, property, value):
661
# ReceivedSecret - signal
662
@dbus.service.signal(_interface)
663
def ReceivedSecret(self):
668
@dbus.service.signal(_interface)
458
# GetChecker - method
459
@dbus.service.method(_interface, out_signature="s")
460
def GetChecker(self):
461
"D-Bus getter method"
462
return self.checker_command
464
# GetCreated - method
465
@dbus.service.method(_interface, out_signature="(nyyyyyu)")
466
def GetCreated(self):
467
"D-Bus getter method"
468
return datetime_to_dbus_struct(self.created)
470
# GetFingerprint - method
471
@dbus.service.method(_interface, out_signature="s")
472
def GetFingerprint(self):
473
"D-Bus getter method"
474
return self.fingerprint
477
@dbus.service.method(_interface, out_signature="s")
479
"D-Bus getter method"
482
# GetInterval - method
483
@dbus.service.method(_interface, out_signature="t")
484
def GetInterval(self):
485
"D-Bus getter method"
486
return self._interval_milliseconds
489
@dbus.service.method(_interface, out_signature="s")
491
"D-Bus getter method"
494
# GetStarted - method
495
@dbus.service.method(_interface, out_signature="(nyyyyyu)")
496
def GetStarted(self):
497
"D-Bus getter method"
498
if self.started is not None:
499
return datetime_to_dbus_struct(self.started)
501
return dbus.Struct(0, 0, 0, 0, 0, 0, 0,
504
# GetTimeout - method
505
@dbus.service.method(_interface, out_signature="t")
506
def GetTimeout(self):
507
"D-Bus getter method"
508
return self._timeout_milliseconds
673
510
# SetChecker - method
674
@dbus.service.method(_interface, in_signature=u"s")
511
@dbus.service.method(_interface, in_signature="s")
675
512
def SetChecker(self, checker):
676
513
"D-Bus setter method"
677
514
self.checker_command = checker
679
self.PropertyChanged(dbus.String(u"checker"),
680
dbus.String(self.checker_command,
683
516
# SetHost - method
684
@dbus.service.method(_interface, in_signature=u"s")
517
@dbus.service.method(_interface, in_signature="s")
685
518
def SetHost(self, host):
686
519
"D-Bus setter method"
689
self.PropertyChanged(dbus.String(u"host"),
690
dbus.String(self.host, variant_level=1))
692
522
# SetInterval - method
693
@dbus.service.method(_interface, in_signature=u"t")
523
@dbus.service.method(_interface, in_signature="t")
694
524
def SetInterval(self, milliseconds):
695
self.interval = datetime.timedelta(0, 0, 0, milliseconds)
697
self.PropertyChanged(dbus.String(u"interval"),
698
(dbus.UInt64(self.interval_milliseconds(),
525
self.interval = datetime.timdeelta(0, 0, 0, milliseconds)
527
# SetTimeout - method
528
@dbus.service.method(_interface, in_signature="t")
529
def SetTimeout(self, milliseconds):
530
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
701
532
# SetSecret - method
702
@dbus.service.method(_interface, in_signature=u"ay",
533
@dbus.service.method(_interface, in_signature="ay",
703
534
byte_arrays=True)
704
535
def SetSecret(self, secret):
705
536
"D-Bus setter method"
706
537
self.secret = str(secret)
708
# SetTimeout - method
709
@dbus.service.method(_interface, in_signature=u"t")
710
def SetTimeout(self, milliseconds):
711
self.timeout = datetime.timedelta(0, 0, 0, milliseconds)
713
self.PropertyChanged(dbus.String(u"timeout"),
714
(dbus.UInt64(self.timeout_milliseconds(),
718
@dbus.service.method(_interface)
540
Start = dbus.service.method(_interface)(start)
541
Start.__name__ = "Start"
723
543
# StartChecker - method
724
@dbus.service.method(_interface)
725
def StartChecker(self):
730
@dbus.service.method(_interface)
544
StartChecker = dbus.service.method(_interface)(start_checker)
545
StartChecker.__name__ = "StartChecker"
547
# StateChanged - signal
548
@dbus.service.signal(_interface, signature="b")
549
def StateChanged(self, started):
553
# StillValid - method
554
StillValid = (dbus.service.method(_interface, out_signature="b")
556
StillValid.__name__ = "StillValid"
559
Stop = dbus.service.method(_interface)(stop)
560
Stop.__name__ = "Stop"
735
562
# StopChecker - method
736
@dbus.service.method(_interface)
737
def StopChecker(self):
563
StopChecker = dbus.service.method(_interface)(stop_checker)
564
StopChecker.__name__ = "StopChecker"
566
# TimeoutChanged - signal
567
@dbus.service.signal(_interface, signature="t")
568
def TimeoutChanged(self, t):
572
del _datetime_to_dbus_struct
743
class ClientHandler(socketserver.BaseRequestHandler, object):
744
"""A class to handle client connections.
746
Instantiated once for each connection to handle it.
576
def peer_certificate(session):
577
"Return the peer's OpenPGP certificate as a bytestring"
578
# If not an OpenPGP certificate...
579
if (gnutls.library.functions
580
.gnutls_certificate_type_get(session._c_object)
581
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
582
# ...do the normal thing
583
return session.peer_certificate
584
list_size = ctypes.c_uint()
585
cert_list = (gnutls.library.functions
586
.gnutls_certificate_get_peers
587
(session._c_object, ctypes.byref(list_size)))
588
if list_size.value == 0:
591
return ctypes.string_at(cert.data, cert.size)
594
def fingerprint(openpgp):
595
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
596
# New GnuTLS "datum" with the OpenPGP public key
597
datum = (gnutls.library.types
598
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
601
ctypes.c_uint(len(openpgp))))
602
# New empty GnuTLS certificate
603
crt = gnutls.library.types.gnutls_openpgp_crt_t()
604
(gnutls.library.functions
605
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
606
# Import the OpenPGP public key into the certificate
607
(gnutls.library.functions
608
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
609
gnutls.library.constants
610
.GNUTLS_OPENPGP_FMT_RAW))
611
# Verify the self signature in the key
612
crtverify = ctypes.c_uint()
613
(gnutls.library.functions
614
.gnutls_openpgp_crt_verify_self(crt, 0, ctypes.byref(crtverify)))
615
if crtverify.value != 0:
616
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
617
raise gnutls.errors.CertificateSecurityError("Verify failed")
618
# New buffer for the fingerprint
619
buf = ctypes.create_string_buffer(20)
620
buf_len = ctypes.c_size_t()
621
# Get the fingerprint from the certificate into the buffer
622
(gnutls.library.functions
623
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
624
ctypes.byref(buf_len)))
625
# Deinit the certificate
626
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
627
# Convert the buffer to a Python bytestring
628
fpr = ctypes.string_at(buf, buf_len.value)
629
# Convert the bytestring to hexadecimal notation
630
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
634
class TCP_handler(SocketServer.BaseRequestHandler, object):
635
"""A TCP request handler class.
636
Instantiated by IPv6_TCPServer for each request to handle it.
747
637
Note: This will run in its own forked process."""
749
639
def handle(self):
750
640
logger.info(u"TCP connection from: %s",
751
641
unicode(self.client_address))
752
logger.debug(u"IPC Pipe FD: %d", self.server.pipe[1])
753
# Open IPC pipe to parent process
754
with closing(os.fdopen(self.server.pipe[1], u"w", 1)) as ipc:
755
session = (gnutls.connection
756
.ClientSession(self.request,
760
line = self.request.makefile().readline()
761
logger.debug(u"Protocol version: %r", line)
763
if int(line.strip().split()[0]) > 1:
765
except (ValueError, IndexError, RuntimeError), error:
766
logger.error(u"Unknown protocol version: %s", error)
769
# Note: gnutls.connection.X509Credentials is really a
770
# generic GnuTLS certificate credentials object so long as
771
# no X.509 keys are added to it. Therefore, we can use it
772
# here despite using OpenPGP certificates.
774
#priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
775
# u"+AES-256-CBC", u"+SHA1",
776
# u"+COMP-NULL", u"+CTYPE-OPENPGP",
778
# Use a fallback default, since this MUST be set.
779
priority = self.server.gnutls_priority
782
(gnutls.library.functions
783
.gnutls_priority_set_direct(session._c_object,
788
except gnutls.errors.GNUTLSError, error:
789
logger.warning(u"Handshake failed: %s", error)
790
# Do not run session.bye() here: the session is not
791
# established. Just abandon the request.
793
logger.debug(u"Handshake succeeded")
795
fpr = self.fingerprint(self.peer_certificate(session))
796
except (TypeError, gnutls.errors.GNUTLSError), error:
797
logger.warning(u"Bad certificate: %s", error)
800
logger.debug(u"Fingerprint: %s", fpr)
802
for c in self.server.clients:
803
if c.fingerprint == fpr:
807
ipc.write(u"NOTFOUND %s\n" % fpr)
810
# Have to check if client.still_valid(), since it is
811
# possible that the client timed out while establishing
812
# the GnuTLS session.
813
if not client.still_valid():
814
ipc.write(u"INVALID %s\n" % client.name)
817
ipc.write(u"SENDING %s\n" % client.name)
819
while sent_size < len(client.secret):
820
sent = session.send(client.secret[sent_size:])
821
logger.debug(u"Sent: %d, remaining: %d",
822
sent, len(client.secret)
823
- (sent_size + sent))
828
def peer_certificate(session):
829
"Return the peer's OpenPGP certificate as a bytestring"
830
# If not an OpenPGP certificate...
831
if (gnutls.library.functions
832
.gnutls_certificate_type_get(session._c_object)
833
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
834
# ...do the normal thing
835
return session.peer_certificate
836
list_size = ctypes.c_uint(1)
837
cert_list = (gnutls.library.functions
838
.gnutls_certificate_get_peers
839
(session._c_object, ctypes.byref(list_size)))
840
if not bool(cert_list) and list_size.value != 0:
841
raise gnutls.errors.GNUTLSError(u"error getting peer"
843
if list_size.value == 0:
846
return ctypes.string_at(cert.data, cert.size)
849
def fingerprint(openpgp):
850
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
851
# New GnuTLS "datum" with the OpenPGP public key
852
datum = (gnutls.library.types
853
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
856
ctypes.c_uint(len(openpgp))))
857
# New empty GnuTLS certificate
858
crt = gnutls.library.types.gnutls_openpgp_crt_t()
859
(gnutls.library.functions
860
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
861
# Import the OpenPGP public key into the certificate
862
(gnutls.library.functions
863
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
864
gnutls.library.constants
865
.GNUTLS_OPENPGP_FMT_RAW))
866
# Verify the self signature in the key
867
crtverify = ctypes.c_uint()
868
(gnutls.library.functions
869
.gnutls_openpgp_crt_verify_self(crt, 0,
870
ctypes.byref(crtverify)))
871
if crtverify.value != 0:
872
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
873
raise (gnutls.errors.CertificateSecurityError
875
# New buffer for the fingerprint
876
buf = ctypes.create_string_buffer(20)
877
buf_len = ctypes.c_size_t()
878
# Get the fingerprint from the certificate into the buffer
879
(gnutls.library.functions
880
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
881
ctypes.byref(buf_len)))
882
# Deinit the certificate
883
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
884
# Convert the buffer to a Python bytestring
885
fpr = ctypes.string_at(buf, buf_len.value)
886
# Convert the bytestring to hexadecimal notation
887
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
891
class ForkingMixInWithPipe(socketserver.ForkingMixIn, object):
892
"""Like socketserver.ForkingMixIn, but also pass a pipe.
894
Assumes a gobject.MainLoop event loop.
896
def process_request(self, request, client_address):
897
"""Overrides and wraps the original process_request().
899
This function creates a new pipe in self.pipe
901
self.pipe = os.pipe()
902
super(ForkingMixInWithPipe,
903
self).process_request(request, client_address)
904
os.close(self.pipe[1]) # close write end
905
# Call "handle_ipc" for both data and EOF events
906
gobject.io_add_watch(self.pipe[0],
907
gobject.IO_IN | gobject.IO_HUP,
909
def handle_ipc(source, condition):
910
"""Dummy function; override as necessary"""
915
class IPv6_TCPServer(ForkingMixInWithPipe,
916
socketserver.TCPServer, object):
917
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
642
session = (gnutls.connection
643
.ClientSession(self.request,
647
line = self.request.makefile().readline()
648
logger.debug(u"Protocol version: %r", line)
650
if int(line.strip().split()[0]) > 1:
652
except (ValueError, IndexError, RuntimeError), error:
653
logger.error(u"Unknown protocol version: %s", error)
656
# Note: gnutls.connection.X509Credentials is really a generic
657
# GnuTLS certificate credentials object so long as no X.509
658
# keys are added to it. Therefore, we can use it here despite
659
# using OpenPGP certificates.
661
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
662
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
664
# Use a fallback default, since this MUST be set.
665
priority = self.server.settings.get("priority", "NORMAL")
666
(gnutls.library.functions
667
.gnutls_priority_set_direct(session._c_object,
672
except gnutls.errors.GNUTLSError, error:
673
logger.warning(u"Handshake failed: %s", error)
674
# Do not run session.bye() here: the session is not
675
# established. Just abandon the request.
678
fpr = fingerprint(peer_certificate(session))
679
except (TypeError, gnutls.errors.GNUTLSError), error:
680
logger.warning(u"Bad certificate: %s", error)
683
logger.debug(u"Fingerprint: %s", fpr)
684
for c in self.server.clients:
685
if c.fingerprint == fpr:
689
logger.warning(u"Client not found for fingerprint: %s",
693
# Have to check if client.still_valid(), since it is possible
694
# that the client timed out while establishing the GnuTLS
696
if not client.still_valid():
697
logger.warning(u"Client %(name)s is invalid",
701
## This won't work here, since we're in a fork.
702
# client.bump_timeout()
704
while sent_size < len(client.secret):
705
sent = session.send(client.secret[sent_size:])
706
logger.debug(u"Sent: %d, remaining: %d",
707
sent, len(client.secret)
708
- (sent_size + sent))
713
class IPv6_TCPServer(SocketServer.ForkingMixIn,
714
SocketServer.TCPServer, object):
715
"""IPv6 TCP server. Accepts 'None' as address and/or port.
717
settings: Server settings
718
clients: Set() of Client objects
920
719
enabled: Boolean; whether this server is activated yet
921
interface: None or a network interface name (string)
922
use_ipv6: Boolean; to use IPv6 or not
924
clients: set of Client objects
925
gnutls_priority GnuTLS priority string
926
use_dbus: Boolean; to emit D-Bus signals or not
928
def __init__(self, server_address, RequestHandlerClass,
929
interface=None, use_ipv6=True, clients=None,
930
gnutls_priority=None, use_dbus=True):
721
address_family = socket.AF_INET6
722
def __init__(self, *args, **kwargs):
723
if "settings" in kwargs:
724
self.settings = kwargs["settings"]
725
del kwargs["settings"]
726
if "clients" in kwargs:
727
self.clients = kwargs["clients"]
728
del kwargs["clients"]
931
729
self.enabled = False
932
self.interface = interface
934
self.address_family = socket.AF_INET6
935
self.clients = clients
936
self.use_dbus = use_dbus
937
self.gnutls_priority = gnutls_priority
938
socketserver.TCPServer.__init__(self, server_address,
730
super(IPv6_TCPServer, self).__init__(*args, **kwargs)
940
731
def server_bind(self):
941
732
"""This overrides the normal server_bind() function
942
733
to bind to an interface if one was specified, and also NOT to
943
734
bind to an address or port if they were not specified."""
944
if self.interface is not None:
735
if self.settings["interface"]:
736
# 25 is from /usr/include/asm-i486/socket.h
737
SO_BINDTODEVICE = getattr(socket, "SO_BINDTODEVICE", 25)
946
739
self.socket.setsockopt(socket.SOL_SOCKET,
948
str(self.interface + u'\0'))
741
self.settings["interface"])
949
742
except socket.error, error:
950
743
if error[0] == errno.EPERM:
951
744
logger.error(u"No permission to"
952
745
u" bind to interface %s",
746
self.settings["interface"])
956
749
# Only bind(2) the socket if we really need to.
957
750
if self.server_address[0] or self.server_address[1]:
958
751
if not self.server_address[0]:
959
if self.address_family == socket.AF_INET6:
960
any_address = u"::" # in6addr_any
962
any_address = socket.INADDR_ANY
963
self.server_address = (any_address,
753
self.server_address = (in6addr_any,
964
754
self.server_address[1])
965
755
elif not self.server_address[1]:
966
756
self.server_address = (self.server_address[0],
758
# if self.settings["interface"]:
969
759
# self.server_address = (self.server_address[0],
974
return socketserver.TCPServer.server_bind(self)
765
return super(IPv6_TCPServer, self).server_bind()
975
766
def server_activate(self):
977
return socketserver.TCPServer.server_activate(self)
768
return super(IPv6_TCPServer, self).server_activate()
978
769
def enable(self):
979
770
self.enabled = True
980
def handle_ipc(self, source, condition, file_objects={}):
982
gobject.IO_IN: u"IN", # There is data to read.
983
gobject.IO_OUT: u"OUT", # Data can be written (without
985
gobject.IO_PRI: u"PRI", # There is urgent data to read.
986
gobject.IO_ERR: u"ERR", # Error condition.
987
gobject.IO_HUP: u"HUP" # Hung up (the connection has been
988
# broken, usually for pipes and
991
conditions_string = ' | '.join(name
993
condition_names.iteritems()
995
logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
998
# Turn the pipe file descriptor into a Python file object
999
if source not in file_objects:
1000
file_objects[source] = os.fdopen(source, u"r", 1)
1002
# Read a line from the file object
1003
cmdline = file_objects[source].readline()
1004
if not cmdline: # Empty line means end of file
1005
# close the IPC pipe
1006
file_objects[source].close()
1007
del file_objects[source]
1009
# Stop calling this function
1012
logger.debug(u"IPC command: %r", cmdline)
1014
# Parse and act on command
1015
cmd, args = cmdline.rstrip(u"\r\n").split(None, 1)
1017
if cmd == u"NOTFOUND":
1018
logger.warning(u"Client not found for fingerprint: %s",
1022
mandos_dbus_service.ClientNotFound(args)
1023
elif cmd == u"INVALID":
1024
for client in self.clients:
1025
if client.name == args:
1026
logger.warning(u"Client %s is invalid", args)
1032
logger.error(u"Unknown client %s is invalid", args)
1033
elif cmd == u"SENDING":
1034
for client in self.clients:
1035
if client.name == args:
1036
logger.info(u"Sending secret to %s", client.name)
1040
client.ReceivedSecret()
1043
logger.error(u"Sending secret to unknown client %s",
1046
logger.error(u"Unknown IPC command: %r", cmdline)
1048
# Keep calling this function
1052
773
def string_to_delta(interval):
1053
774
"""Parse a string and return a datetime.timedelta
1055
>>> string_to_delta(u'7d')
776
>>> string_to_delta('7d')
1056
777
datetime.timedelta(7)
1057
>>> string_to_delta(u'60s')
778
>>> string_to_delta('60s')
1058
779
datetime.timedelta(0, 60)
1059
>>> string_to_delta(u'60m')
780
>>> string_to_delta('60m')
1060
781
datetime.timedelta(0, 3600)
1061
>>> string_to_delta(u'24h')
782
>>> string_to_delta('24h')
1062
783
datetime.timedelta(1)
1063
784
>>> string_to_delta(u'1w')
1064
785
datetime.timedelta(7)
1065
>>> string_to_delta(u'5m 30s')
786
>>> string_to_delta('5m 30s')
1066
787
datetime.timedelta(0, 330)
1068
789
timevalue = datetime.timedelta(0)
1176
909
# Default values for config file for server-global settings
1177
server_defaults = { u"interface": u"",
1182
u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1183
u"servicename": u"Mandos",
1184
u"use_dbus": u"True",
1185
u"use_ipv6": u"True",
910
server_defaults = { "interface": "",
915
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
916
"servicename": "Mandos",
1188
919
# Parse config file for server-global settings
1189
server_config = configparser.SafeConfigParser(server_defaults)
920
server_config = ConfigParser.SafeConfigParser(server_defaults)
1190
921
del server_defaults
1191
server_config.read(os.path.join(options.configdir,
922
server_config.read(os.path.join(options.configdir, "mandos.conf"))
1193
923
# Convert the SafeConfigParser object to a dict
1194
924
server_settings = server_config.defaults()
1195
# Use the appropriate methods on the non-string config options
1196
for option in (u"debug", u"use_dbus", u"use_ipv6"):
1197
server_settings[option] = server_config.getboolean(u"DEFAULT",
1199
if server_settings["port"]:
1200
server_settings["port"] = server_config.getint(u"DEFAULT",
925
# Use getboolean on the boolean config option
926
server_settings["debug"] = (server_config.getboolean
927
("DEFAULT", "debug"))
1202
928
del server_config
1204
930
# Override the settings from the config file with command line
1205
931
# options, if set.
1206
for option in (u"interface", u"address", u"port", u"debug",
1207
u"priority", u"servicename", u"configdir",
1208
u"use_dbus", u"use_ipv6"):
932
for option in ("interface", "address", "port", "debug",
933
"priority", "servicename", "configdir"):
1209
934
value = getattr(options, option)
1210
935
if value is not None:
1211
936
server_settings[option] = value
1213
# Force all strings to be unicode
1214
for option in server_settings.keys():
1215
if type(server_settings[option]) is str:
1216
server_settings[option] = unicode(server_settings[option])
1217
938
# Now we have our good server settings in "server_settings"
1219
##################################################################
1222
debug = server_settings[u"debug"]
1223
use_dbus = server_settings[u"use_dbus"]
1224
use_ipv6 = server_settings[u"use_ipv6"]
940
debug = server_settings["debug"]
1227
943
syslogger.setLevel(logging.WARNING)
1228
944
console.setLevel(logging.WARNING)
1230
if server_settings[u"servicename"] != u"Mandos":
946
if server_settings["servicename"] != "Mandos":
1231
947
syslogger.setFormatter(logging.Formatter
1232
(u'Mandos (%s) [%%(process)d]:'
1233
u' %%(levelname)s: %%(message)s'
1234
% server_settings[u"servicename"]))
948
('Mandos (%s): %%(levelname)s:'
950
% server_settings["servicename"]))
1236
952
# Parse config file with clients
1237
client_defaults = { u"timeout": u"1h",
1239
u"checker": u"fping -q -- %%(host)s",
953
client_defaults = { "timeout": "1h",
955
"checker": "fping -q -- %(host)s",
1242
client_config = configparser.SafeConfigParser(client_defaults)
1243
client_config.read(os.path.join(server_settings[u"configdir"],
1246
global mandos_dbus_service
1247
mandos_dbus_service = None
1250
tcp_server = IPv6_TCPServer((server_settings[u"address"],
1251
server_settings[u"port"]),
1254
server_settings[u"interface"],
1258
server_settings[u"priority"],
1260
pidfilename = u"/var/run/mandos.pid"
1262
pidfile = open(pidfilename, u"w")
1264
logger.error(u"Could not open file %r", pidfilename)
1267
uid = pwd.getpwnam(u"_mandos").pw_uid
1268
gid = pwd.getpwnam(u"_mandos").pw_gid
1271
uid = pwd.getpwnam(u"mandos").pw_uid
1272
gid = pwd.getpwnam(u"mandos").pw_gid
1275
uid = pwd.getpwnam(u"nobody").pw_uid
1276
gid = pwd.getpwnam(u"nobody").pw_gid
958
client_config = ConfigParser.SafeConfigParser(client_defaults)
959
client_config.read(os.path.join(server_settings["configdir"],
963
tcp_server = IPv6_TCPServer((server_settings["address"],
964
server_settings["port"]),
966
settings=server_settings,
968
pidfilename = "/var/run/mandos.pid"
970
pidfile = open(pidfilename, "w")
971
except IOError, error:
972
logger.error("Could not open file %r", pidfilename)
977
uid = pwd.getpwnam("mandos").pw_uid
980
uid = pwd.getpwnam("nobody").pw_uid
984
gid = pwd.getpwnam("mandos").pw_gid
987
gid = pwd.getpwnam("nogroup").pw_gid
1283
993
except OSError, error:
1284
994
if error[0] != errno.EPERM:
1287
# Enable all possible GnuTLS debugging
1289
# "Use a log level over 10 to enable all debugging options."
1291
gnutls.library.functions.gnutls_global_set_log_level(11)
1293
@gnutls.library.types.gnutls_log_func
1294
def debug_gnutls(level, string):
1295
logger.debug(u"GnuTLS: %s", string[:-1])
1297
(gnutls.library.functions
1298
.gnutls_global_set_log_function(debug_gnutls))
1301
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1302
service = AvahiService(name = server_settings[u"servicename"],
1303
servicetype = u"_mandos._tcp",
1304
protocol = protocol)
998
service = AvahiService(name = server_settings["servicename"],
999
servicetype = "_mandos._tcp", )
1305
1000
if server_settings["interface"]:
1306
1001
service.interface = (if_nametoindex
1307
(str(server_settings[u"interface"])))
1002
(server_settings["interface"]))
1309
1004
global main_loop
1311
1007
# From the Avahi example code
1312
1008
DBusGMainLoop(set_as_default=True )
1313
1009
main_loop = gobject.MainLoop()
1314
1010
bus = dbus.SystemBus()
1011
server = dbus.Interface(bus.get_object(avahi.DBUS_NAME,
1012
avahi.DBUS_PATH_SERVER),
1013
avahi.DBUS_INTERFACE_SERVER)
1315
1014
# End of Avahi example code
1317
bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos", bus)
1319
client_class = Client
1321
client_class = ClientDBus
1323
client_class(name = section,
1324
config= dict(client_config.items(section)))
1325
for section in client_config.sections()))
1015
bus_name = dbus.service.BusName(u"org.mandos-system.Mandos", bus)
1017
def remove_from_clients(client):
1018
clients.remove(client)
1020
logger.critical(u"No clients left, exiting")
1023
clients.update(Set(Client(name = section,
1024
stop_hook = remove_from_clients,
1026
= dict(client_config.items(section)))
1027
for section in client_config.sections()))
1326
1028
if not clients:
1327
logger.warning(u"No clients defined")
1029
logger.critical(u"No clients defined")
1330
1033
# Redirect stdin so all checkers get /dev/null