45
44
import gnutls.library.functions
46
45
import gnutls.library.constants
47
46
import gnutls.library.types
48
import ConfigParser as configparser
57
57
import logging.handlers
63
import cPickle as pickle
64
import multiprocessing
72
62
from dbus.mainloop.glib import DBusGMainLoop
75
import xml.dom.minidom
77
import Crypto.Cipher.AES
80
SO_BINDTODEVICE = socket.SO_BINDTODEVICE
81
except AttributeError:
83
from IN import SO_BINDTODEVICE
85
SO_BINDTODEVICE = None
89
stored_state_path = "/var/lib/mandos/clients.pickle"
91
logger = logging.getLogger()
92
syslogger = (logging.handlers.SysLogHandler
93
(facility = logging.handlers.SysLogHandler.LOG_DAEMON,
94
address = str("/dev/log")))
97
if_nametoindex = (ctypes.cdll.LoadLibrary
98
(ctypes.util.find_library("c"))
100
except (OSError, AttributeError):
101
def if_nametoindex(interface):
102
"Get an interface index the hard way, i.e. using fcntl()"
103
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
104
with contextlib.closing(socket.socket()) as s:
105
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
106
struct.pack(str("16s16x"),
108
interface_index = struct.unpack(str("I"),
110
return interface_index
113
def initlogger(level=logging.WARNING):
114
"""init logger and add loglevel"""
116
syslogger.setFormatter(logging.Formatter
117
('Mandos [%(process)d]: %(levelname)s:'
119
logger.addHandler(syslogger)
121
console = logging.StreamHandler()
122
console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
126
logger.addHandler(console)
127
logger.setLevel(level)
130
class AvahiError(Exception):
131
def __init__(self, value, *args, **kwargs):
133
super(AvahiError, self).__init__(value, *args, **kwargs)
134
def __unicode__(self):
135
return unicode(repr(self.value))
137
class AvahiServiceError(AvahiError):
140
class AvahiGroupError(AvahiError):
144
class AvahiService(object):
145
"""An Avahi (Zeroconf) service.
148
interface: integer; avahi.IF_UNSPEC or an interface index.
149
Used to optionally bind to the specified interface.
150
name: string; Example: 'Mandos'
151
type: string; Example: '_mandos._tcp'.
152
See <http://www.dns-sd.org/ServiceTypes.html>
153
port: integer; what port to announce
154
TXT: list of strings; TXT record for the service
155
domain: string; Domain to publish on, default to .local if empty.
156
host: string; Host to publish records for, default is localhost
157
max_renames: integer; maximum number of renames
158
rename_count: integer; counter so we only rename after collisions
159
a sensible number of times
160
group: D-Bus Entry Group
162
bus: dbus.SystemBus()
164
def __init__(self, interface = avahi.IF_UNSPEC, name = None,
165
servicetype = None, port = None, TXT = None,
166
domain = "", host = "", max_renames = 32768,
167
protocol = avahi.PROTO_UNSPEC, bus = None):
168
self.interface = interface
170
self.type = servicetype
172
self.TXT = TXT if TXT is not None else []
175
self.rename_count = 0
176
self.max_renames = max_renames
177
self.protocol = protocol
178
self.group = None # our entry group
181
self.entry_group_state_changed_match = None
183
"""Derived from the Avahi example code"""
184
if self.rename_count >= self.max_renames:
185
logger.critical("No suitable Zeroconf service name found"
186
" after %i retries, exiting.",
188
raise AvahiServiceError("Too many renames")
189
self.name = unicode(self.server
190
.GetAlternativeServiceName(self.name))
191
logger.info("Changing Zeroconf service name to %r ...",
196
except dbus.exceptions.DBusException as error:
197
logger.critical("DBusException: %s", error)
200
self.rename_count += 1
202
"""Derived from the Avahi example code"""
203
if self.entry_group_state_changed_match is not None:
204
self.entry_group_state_changed_match.remove()
205
self.entry_group_state_changed_match = None
206
if self.group is not None:
209
"""Derived from the Avahi example code"""
211
if self.group is None:
212
self.group = dbus.Interface(
213
self.bus.get_object(avahi.DBUS_NAME,
214
self.server.EntryGroupNew()),
215
avahi.DBUS_INTERFACE_ENTRY_GROUP)
216
self.entry_group_state_changed_match = (
217
self.group.connect_to_signal(
218
'StateChanged', self.entry_group_state_changed))
219
logger.debug("Adding Zeroconf service '%s' of type '%s' ...",
220
self.name, self.type)
221
self.group.AddService(
224
dbus.UInt32(0), # flags
225
self.name, self.type,
226
self.domain, self.host,
227
dbus.UInt16(self.port),
228
avahi.string_array_to_txt_array(self.TXT))
230
def entry_group_state_changed(self, state, error):
231
"""Derived from the Avahi example code"""
232
logger.debug("Avahi entry group state change: %i", state)
234
if state == avahi.ENTRY_GROUP_ESTABLISHED:
235
logger.debug("Zeroconf service established.")
236
elif state == avahi.ENTRY_GROUP_COLLISION:
237
logger.info("Zeroconf service name collision.")
239
elif state == avahi.ENTRY_GROUP_FAILURE:
240
logger.critical("Avahi: Error in group state changed %s",
242
raise AvahiGroupError("State changed: %s"
245
"""Derived from the Avahi example code"""
246
if self.group is not None:
249
except (dbus.exceptions.UnknownMethodException,
250
dbus.exceptions.DBusException):
254
def server_state_changed(self, state, error=None):
255
"""Derived from the Avahi example code"""
256
logger.debug("Avahi server state change: %i", state)
257
bad_states = { avahi.SERVER_INVALID:
258
"Zeroconf server invalid",
259
avahi.SERVER_REGISTERING: None,
260
avahi.SERVER_COLLISION:
261
"Zeroconf server name collision",
262
avahi.SERVER_FAILURE:
263
"Zeroconf server failure" }
264
if state in bad_states:
265
if bad_states[state] is not None:
267
logger.error(bad_states[state])
269
logger.error(bad_states[state] + ": %r", error)
271
elif state == avahi.SERVER_RUNNING:
275
logger.debug("Unknown state: %r", state)
277
logger.debug("Unknown state: %r: %r", state, error)
279
"""Derived from the Avahi example code"""
280
if self.server is None:
281
self.server = dbus.Interface(
282
self.bus.get_object(avahi.DBUS_NAME,
283
avahi.DBUS_PATH_SERVER,
284
follow_name_owner_changes=True),
285
avahi.DBUS_INTERFACE_SERVER)
286
self.server.connect_to_signal("StateChanged",
287
self.server_state_changed)
288
self.server_state_changed(self.server.GetState())
290
class AvahiServiceToSyslog(AvahiService):
292
"""Add the new name to the syslog messages"""
293
ret = AvahiService.rename(self)
294
syslogger.setFormatter(logging.Formatter
295
('Mandos (%s) [%%(process)d]:'
296
' %%(levelname)s: %%(message)s'
300
def _timedelta_to_milliseconds(td):
301
"Convert a datetime.timedelta() to milliseconds"
302
return ((td.days * 24 * 60 * 60 * 1000)
303
+ (td.seconds * 1000)
304
+ (td.microseconds // 1000))
65
# Brief description of the operation of this program:
67
# This server announces itself as a Zeroconf service. Connecting
68
# clients use the TLS protocol, with the unusual quirk that this
69
# server program acts as a TLS "client" while the connecting clients
70
# acts as a TLS "server". The clients (acting as a TLS "server") must
71
# supply an OpenPGP certificate, and the fingerprint of this
72
# certificate is used by this server to look up (in a list read from a
73
# file at start time) which binary blob to give the client. No other
74
# authentication or authorization is done by this server.
77
logger = logging.Logger('mandos')
78
syslogger = logging.handlers.SysLogHandler\
79
(facility = logging.handlers.SysLogHandler.LOG_DAEMON)
80
syslogger.setFormatter(logging.Formatter\
81
('%(levelname)s: %(message)s'))
82
logger.addHandler(syslogger)
85
# This variable is used to optionally bind to a specified interface.
86
# It is a global variable to fit in with the other variables from the
88
serviceInterface = avahi.IF_UNSPEC
89
# From the Avahi example code:
90
serviceName = "Mandos"
91
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
92
servicePort = None # Not known at startup
93
serviceTXT = [] # TXT record for the service
94
domain = "" # Domain to publish on, default to .local
95
host = "" # Host to publish records for, default to localhost
96
group = None #our entry group
97
rename_count = 12 # Counter so we only rename after collisions a
98
# sensible number of times
99
# End of Avahi example code
306
102
class Client(object):
307
103
"""A representation of a client host served by this server.
310
_approved: bool(); 'None' if not yet approved/disapproved
311
approval_delay: datetime.timedelta(); Time to wait for approval
312
approval_duration: datetime.timedelta(); Duration of one approval
313
checker: subprocess.Popen(); a running checker process used
314
to see if the client lives.
315
'None' if no process is running.
316
checker_callback_tag: a gobject event source tag, or None
317
checker_command: string; External command which is run to check
318
if client lives. %() expansions are done at
105
name: string; from the config file, used in log messages
106
fingerprint: string (40 or 32 hexadecimal digits); used to
107
uniquely identify the client
108
secret: bytestring; sent verbatim (over TLS) to client
109
fqdn: string (FQDN); available for use by the checker command
110
created: datetime.datetime()
111
last_seen: datetime.datetime() or None if not yet seen
112
timeout: datetime.timedelta(); How long from last_seen until
113
this client is invalid
114
interval: datetime.timedelta(); How often to start a new checker
115
stop_hook: If set, called by stop() as stop_hook(self)
116
checker: subprocess.Popen(); a running checker process used
117
to see if the client lives.
118
Is None if no process is running.
119
checker_initiator_tag: a gobject event source tag, or None
120
stop_initiator_tag: - '' -
121
checker_callback_tag: - '' -
122
checker_command: string; External command which is run to check if
123
client lives. %()s expansions are done at
319
124
runtime with vars(self) as dict, so that for
320
125
instance %(name)s can be used in the command.
321
checker_initiator_tag: a gobject event source tag, or None
322
created: datetime.datetime(); (UTC) object creation
323
client_structure: Object describing what attributes a client has
324
and is used for storing the client at exit
325
current_checker_command: string; current running checker_command
326
disable_initiator_tag: a gobject event source tag, or None
328
fingerprint: string (40 or 32 hexadecimal digits); used to
329
uniquely identify the client
330
host: string; available for use by the checker command
331
interval: datetime.timedelta(); How often to start a new checker
332
last_approval_request: datetime.datetime(); (UTC) or None
333
last_checked_ok: datetime.datetime(); (UTC) or None
334
last_checker_status: integer between 0 and 255 reflecting exit status
335
of last checker. -1 reflect crashed checker,
337
last_enabled: datetime.datetime(); (UTC)
338
name: string; from the config file, used in log messages and
340
secret: bytestring; sent verbatim (over TLS) to client
341
timeout: datetime.timedelta(); How long from last_checked_ok
342
until this client is disabled
343
extended_timeout: extra long timeout when password has been sent
344
runtime_expansions: Allowed attributes for runtime expansion.
345
expires: datetime.datetime(); time (UTC) when a client will be
127
_timeout: Real variable for 'timeout'
128
_interval: Real variable for 'interval'
129
_timeout_milliseconds: Used by gobject.timeout_add()
130
_interval_milliseconds: - '' -
349
runtime_expansions = ("approval_delay", "approval_duration",
350
"created", "enabled", "fingerprint",
351
"host", "interval", "last_checked_ok",
352
"last_enabled", "name", "timeout")
354
def timeout_milliseconds(self):
355
"Return the 'timeout' attribute in milliseconds"
356
return _timedelta_to_milliseconds(self.timeout)
358
def extended_timeout_milliseconds(self):
359
"Return the 'extended_timeout' attribute in milliseconds"
360
return _timedelta_to_milliseconds(self.extended_timeout)
362
def interval_milliseconds(self):
363
"Return the 'interval' attribute in milliseconds"
364
return _timedelta_to_milliseconds(self.interval)
366
def approval_delay_milliseconds(self):
367
return _timedelta_to_milliseconds(self.approval_delay)
369
def __init__(self, name = None, config=None):
370
"""Note: the 'checker' key in 'config' sets the
371
'checker_command' attribute and *not* the 'checker'
132
def _set_timeout(self, timeout):
133
"Setter function for 'timeout' attribute"
134
self._timeout = timeout
135
self._timeout_milliseconds = ((self.timeout.days
136
* 24 * 60 * 60 * 1000)
137
+ (self.timeout.seconds * 1000)
138
+ (self.timeout.microseconds
140
timeout = property(lambda self: self._timeout,
143
def _set_interval(self, interval):
144
"Setter function for 'interval' attribute"
145
self._interval = interval
146
self._interval_milliseconds = ((self.interval.days
147
* 24 * 60 * 60 * 1000)
148
+ (self.interval.seconds
150
+ (self.interval.microseconds
152
interval = property(lambda self: self._interval,
155
def __init__(self, name=None, options=None, stop_hook=None,
156
fingerprint=None, secret=None, secfile=None,
157
fqdn=None, timeout=None, interval=-1, checker=None):
158
"""Note: the 'checker' argument sets the 'checker_command'
159
attribute and not the 'checker' attribute.."""
376
logger.debug("Creating client %r", self.name)
377
# Uppercase and remove spaces from fingerprint for later
378
# comparison purposes with return value from the fingerprint()
380
self.fingerprint = (config["fingerprint"].upper()
382
logger.debug(" Fingerprint: %s", self.fingerprint)
383
if "secret" in config:
384
self.secret = config["secret"].decode("base64")
385
elif "secfile" in config:
386
with open(os.path.expanduser(os.path.expandvars
387
(config["secfile"])),
389
self.secret = secfile.read()
391
raise TypeError("No secret or secfile for client %s"
393
self.host = config.get("host", "")
394
self.created = datetime.datetime.utcnow()
396
self.last_approval_request = None
397
self.last_enabled = datetime.datetime.utcnow()
398
self.last_checked_ok = None
399
self.last_checker_status = None
400
self.timeout = string_to_delta(config["timeout"])
401
self.extended_timeout = string_to_delta(config
402
["extended_timeout"])
403
self.interval = string_to_delta(config["interval"])
161
# Uppercase and remove spaces from fingerprint
162
# for later comparison purposes with return value of
163
# the fingerprint() function
164
self.fingerprint = fingerprint.upper().replace(u" ", u"")
166
self.secret = secret.decode(u"base64")
169
self.secret = sf.read()
172
raise RuntimeError(u"No secret or secfile for client %s"
174
self.fqdn = fqdn # string
175
self.created = datetime.datetime.now()
176
self.last_seen = None
178
self.timeout = options.timeout
180
self.timeout = string_to_delta(timeout)
182
self.interval = options.interval
184
self.interval = string_to_delta(interval)
185
self.stop_hook = stop_hook
404
186
self.checker = None
405
187
self.checker_initiator_tag = None
406
self.disable_initiator_tag = None
407
self.expires = datetime.datetime.utcnow() + self.timeout
188
self.stop_initiator_tag = None
408
189
self.checker_callback_tag = None
409
self.checker_command = config["checker"]
410
self.current_checker_command = None
411
self._approved = None
412
self.approved_by_default = config.get("approved_by_default",
414
self.approvals_pending = 0
415
self.approval_delay = string_to_delta(
416
config["approval_delay"])
417
self.approval_duration = string_to_delta(
418
config["approval_duration"])
419
self.changedstate = (multiprocessing_manager
420
.Condition(multiprocessing_manager
422
self.client_structure = [attr for attr in self.__dict__.iterkeys() if not attr.startswith("_")]
423
self.client_structure.append("client_structure")
425
for name, t in inspect.getmembers(type(self),
426
lambda obj: isinstance(obj, property)):
427
if not name.startswith("_"):
428
self.client_structure.append(name)
430
# Send notice to process children that client state has changed
431
def send_changedstate(self):
432
with self.changedstate:
433
self.changedstate.notify_all()
190
self.check_command = checker
436
192
"""Start this client's checker and timeout hooks"""
437
if getattr(self, "enabled", False):
440
self.send_changedstate()
441
self.expires = datetime.datetime.utcnow() + self.timeout
443
self.last_enabled = datetime.datetime.utcnow()
446
def disable(self, quiet=True):
447
"""Disable this client."""
448
if not getattr(self, "enabled", False):
193
# Schedule a new checker to be started an 'interval' from now,
194
# and every interval from then on.
195
self.checker_initiator_tag = gobject.timeout_add\
196
(self._interval_milliseconds,
198
# Also start a new checker *right now*.
200
# Schedule a stop() when 'timeout' has passed
201
self.stop_initiator_tag = gobject.timeout_add\
202
(self._timeout_milliseconds,
206
The possibility that this client might be restarted is left
207
open, but not currently used."""
208
# If this client doesn't have a secret, it is already stopped.
210
logger.debug(u"Stopping client %s", self.name)
451
self.send_changedstate()
453
logger.info("Disabling client %s", self.name)
454
if getattr(self, "disable_initiator_tag", False):
455
gobject.source_remove(self.disable_initiator_tag)
456
self.disable_initiator_tag = None
458
if getattr(self, "checker_initiator_tag", False):
214
if hasattr(self, "stop_initiator_tag") \
215
and self.stop_initiator_tag:
216
gobject.source_remove(self.stop_initiator_tag)
217
self.stop_initiator_tag = None
218
if hasattr(self, "checker_initiator_tag") \
219
and self.checker_initiator_tag:
459
220
gobject.source_remove(self.checker_initiator_tag)
460
221
self.checker_initiator_tag = None
461
222
self.stop_checker()
463
225
# Do not run this again if called by a gobject.timeout_add
466
227
def __del__(self):
469
def init_checker(self):
470
# Schedule a new checker to be started an 'interval' from now,
471
# and every interval from then on.
472
self.checker_initiator_tag = (gobject.timeout_add
473
(self.interval_milliseconds(),
475
# Schedule a disable() when 'timeout' has passed
476
self.disable_initiator_tag = (gobject.timeout_add
477
(self.timeout_milliseconds(),
479
# Also start a new checker *right now*.
482
def checker_callback(self, pid, condition, command):
228
self.stop_hook = None
230
def checker_callback(self, pid, condition):
483
231
"""The checker has completed, so take appropriate actions."""
232
now = datetime.datetime.now()
484
233
self.checker_callback_tag = None
485
234
self.checker = None
486
if os.WIFEXITED(condition):
487
self.last_checker_status = os.WEXITSTATUS(condition)
488
if self.last_checker_status == 0:
489
logger.info("Checker for %(name)s succeeded",
493
logger.info("Checker for %(name)s failed",
496
self.last_checker_status = -1
497
logger.warning("Checker for %(name)s crashed?",
235
if os.WIFEXITED(condition) \
236
and (os.WEXITSTATUS(condition) == 0):
237
logger.debug(u"Checker for %(name)s succeeded",
240
gobject.source_remove(self.stop_initiator_tag)
241
self.stop_initiator_tag = gobject.timeout_add\
242
(self._timeout_milliseconds,
244
elif not os.WIFEXITED(condition):
245
logger.warning(u"Checker for %(name)s crashed?",
500
def checked_ok(self, timeout=None):
501
"""Bump up the timeout for this client.
503
This should only be called when the client has been seen,
507
timeout = self.timeout
508
self.last_checked_ok = datetime.datetime.utcnow()
509
if self.disable_initiator_tag is not None:
510
gobject.source_remove(self.disable_initiator_tag)
511
if getattr(self, "enabled", False):
512
self.disable_initiator_tag = (gobject.timeout_add
513
(_timedelta_to_milliseconds
514
(timeout), self.disable))
515
self.expires = datetime.datetime.utcnow() + timeout
517
def need_approval(self):
518
self.last_approval_request = datetime.datetime.utcnow()
248
logger.debug(u"Checker for %(name)s failed",
520
250
def start_checker(self):
521
251
"""Start a new checker subprocess if one is not running.
523
252
If a checker already exists, leave it running and do
525
254
# The reason for not killing a running checker is that if we
528
257
# client would inevitably timeout, since no checker would get
529
258
# a chance to run to completion. If we instead leave running
530
259
# checkers alone, the checker would have to take more time
531
# than 'timeout' for the client to be disabled, which is as it
534
# If a checker exists, make sure it is not a zombie
536
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
537
except (AttributeError, OSError) as error:
538
if (isinstance(error, OSError)
539
and error.errno != errno.ECHILD):
543
logger.warning("Checker was a zombie")
544
gobject.source_remove(self.checker_callback_tag)
545
self.checker_callback(pid, status,
546
self.current_checker_command)
547
# Start a new checker if needed
260
# than 'timeout' for the client to be declared invalid, which
261
# is as it should be.
548
262
if self.checker is None:
550
# In case checker_command has exactly one % operator
551
command = self.checker_command % self.host
264
command = self.check_command % self.fqdn
552
265
except TypeError:
553
# Escape attributes for the shell
554
escaped_attrs = dict(
556
re.escape(unicode(str(getattr(self, attr, "")),
560
self.runtime_expansions)
266
escaped_attrs = dict((key, re.escape(str(val)))
268
vars(self).iteritems())
563
command = self.checker_command % escaped_attrs
564
except TypeError as error:
565
logger.error('Could not format string "%s":'
566
' %s', self.checker_command, error)
270
command = self.check_command % escaped_attrs
271
except TypeError, error:
272
logger.critical(u'Could not format string "%s":'
273
u' %s', self.check_command, error)
567
274
return True # Try again later
568
self.current_checker_command = command
570
logger.info("Starting checker %r for %s",
572
# We don't need to redirect stdout and stderr, since
573
# in normal mode, that is already done by daemon(),
574
# and in debug mode we don't want to. (Stdin is
575
# always replaced by /dev/null.)
576
self.checker = subprocess.Popen(command,
579
self.checker_callback_tag = (gobject.child_watch_add
581
self.checker_callback,
583
# The checker may have completed before the gobject
584
# watch was added. Check for this.
585
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
587
gobject.source_remove(self.checker_callback_tag)
588
self.checker_callback(pid, status, command)
589
except OSError as error:
590
logger.error("Failed to start subprocess: %s",
276
logger.debug(u"Starting checker %r for %s",
278
self.checker = subprocess.\
280
close_fds=True, shell=True,
282
self.checker_callback_tag = gobject.child_watch_add\
284
self.checker_callback)
285
except subprocess.OSError, error:
286
logger.error(u"Failed to start subprocess: %s",
592
288
# Re-run this periodically if run by gobject.timeout_add
595
290
def stop_checker(self):
596
291
"""Force the checker process, if any, to stop."""
597
292
if self.checker_callback_tag:
598
293
gobject.source_remove(self.checker_callback_tag)
599
294
self.checker_callback_tag = None
600
if getattr(self, "checker", None) is None:
295
if not hasattr(self, "checker") or self.checker is None:
602
297
logger.debug("Stopping checker for %(name)s", vars(self))
604
299
os.kill(self.checker.pid, signal.SIGTERM)
606
301
#if self.checker.poll() is None:
607
302
# os.kill(self.checker.pid, signal.SIGKILL)
608
except OSError as error:
609
if error.errno != errno.ESRCH: # No such process
303
except OSError, error:
304
if error.errno != errno.ESRCH:
611
306
self.checker = None
613
# Encrypts a client secret and stores it in a varible encrypted_secret
614
def encrypt_secret(self, key):
615
# Encryption-key need to be of a specific size, so we hash inputed key
616
hasheng = hashlib.sha256()
618
encryptionkey = hasheng.digest()
620
# Create validation hash so we know at decryption if it was sucessful
621
hasheng = hashlib.sha256()
622
hasheng.update(self.secret)
623
validationhash = hasheng.digest()
626
iv = os.urandom(Crypto.Cipher.AES.block_size)
627
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
628
Crypto.Cipher.AES.MODE_CFB, iv)
629
ciphertext = ciphereng.encrypt(validationhash+self.secret)
630
self.encrypted_secret = (ciphertext, iv)
632
# Decrypt a encrypted client secret
633
def decrypt_secret(self, key):
634
# Decryption-key need to be of a specific size, so we hash inputed key
635
hasheng = hashlib.sha256()
637
encryptionkey = hasheng.digest()
639
# Decrypt encrypted secret
640
ciphertext, iv = self.encrypted_secret
641
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
642
Crypto.Cipher.AES.MODE_CFB, iv)
643
plain = ciphereng.decrypt(ciphertext)
645
# Validate decrypted secret to know if it was succesful
646
hasheng = hashlib.sha256()
647
validationhash = plain[:hasheng.digest_size]
648
secret = plain[hasheng.digest_size:]
649
hasheng.update(secret)
651
# if validation fails, we use key as new secret. Otherwhise, we use
652
# the decrypted secret
653
if hasheng.digest() == validationhash:
657
del self.encrypted_secret
660
def dbus_service_property(dbus_interface, signature="v",
661
access="readwrite", byte_arrays=False):
662
"""Decorators for marking methods of a DBusObjectWithProperties to
663
become properties on the D-Bus.
665
The decorated method will be called with no arguments by "Get"
666
and with one argument by "Set".
668
The parameters, where they are supported, are the same as
669
dbus.service.method, except there is only "signature", since the
670
type from Get() and the type sent to Set() is the same.
672
# Encoding deeply encoded byte arrays is not supported yet by the
673
# "Set" method, so we fail early here:
674
if byte_arrays and signature != "ay":
675
raise ValueError("Byte arrays not supported for non-'ay'"
676
" signature %r" % signature)
678
func._dbus_is_property = True
679
func._dbus_interface = dbus_interface
680
func._dbus_signature = signature
681
func._dbus_access = access
682
func._dbus_name = func.__name__
683
if func._dbus_name.endswith("_dbus_property"):
684
func._dbus_name = func._dbus_name[:-14]
685
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
690
class DBusPropertyException(dbus.exceptions.DBusException):
691
"""A base class for D-Bus property-related exceptions
693
def __unicode__(self):
694
return unicode(str(self))
697
class DBusPropertyAccessException(DBusPropertyException):
698
"""A property's access permissions disallows an operation.
703
class DBusPropertyNotFound(DBusPropertyException):
704
"""An attempt was made to access a non-existing property.
709
class DBusObjectWithProperties(dbus.service.Object):
710
"""A D-Bus object with properties.
712
Classes inheriting from this can use the dbus_service_property
713
decorator to expose methods as D-Bus properties. It exposes the
714
standard Get(), Set(), and GetAll() methods on the D-Bus.
718
def _is_dbus_property(obj):
719
return getattr(obj, "_dbus_is_property", False)
721
def _get_all_dbus_properties(self):
722
"""Returns a generator of (name, attribute) pairs
724
return ((prop.__get__(self)._dbus_name, prop.__get__(self))
725
for cls in self.__class__.__mro__
727
inspect.getmembers(cls, self._is_dbus_property))
729
def _get_dbus_property(self, interface_name, property_name):
730
"""Returns a bound method if one exists which is a D-Bus
731
property with the specified name and interface.
733
for cls in self.__class__.__mro__:
734
for name, value in (inspect.getmembers
735
(cls, self._is_dbus_property)):
736
if (value._dbus_name == property_name
737
and value._dbus_interface == interface_name):
738
return value.__get__(self)
741
raise DBusPropertyNotFound(self.dbus_object_path + ":"
742
+ interface_name + "."
745
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
747
def Get(self, interface_name, property_name):
748
"""Standard D-Bus property Get() method, see D-Bus standard.
750
prop = self._get_dbus_property(interface_name, property_name)
751
if prop._dbus_access == "write":
752
raise DBusPropertyAccessException(property_name)
754
if not hasattr(value, "variant_level"):
756
return type(value)(value, variant_level=value.variant_level+1)
758
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
759
def Set(self, interface_name, property_name, value):
760
"""Standard D-Bus property Set() method, see D-Bus standard.
762
prop = self._get_dbus_property(interface_name, property_name)
763
if prop._dbus_access == "read":
764
raise DBusPropertyAccessException(property_name)
765
if prop._dbus_get_args_options["byte_arrays"]:
766
# The byte_arrays option is not supported yet on
767
# signatures other than "ay".
768
if prop._dbus_signature != "ay":
770
value = dbus.ByteArray(''.join(unichr(byte)
774
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
775
out_signature="a{sv}")
776
def GetAll(self, interface_name):
777
"""Standard D-Bus property GetAll() method, see D-Bus
780
Note: Will not include properties with access="write".
783
for name, prop in self._get_all_dbus_properties():
785
and interface_name != prop._dbus_interface):
786
# Interface non-empty but did not match
788
# Ignore write-only properties
789
if prop._dbus_access == "write":
792
if not hasattr(value, "variant_level"):
793
properties[name] = value
795
properties[name] = type(value)(value, variant_level=
796
value.variant_level+1)
797
return dbus.Dictionary(properties, signature="sv")
799
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
801
path_keyword='object_path',
802
connection_keyword='connection')
803
def Introspect(self, object_path, connection):
804
"""Standard D-Bus method, overloaded to insert property tags.
806
xmlstring = dbus.service.Object.Introspect(self, object_path,
809
document = xml.dom.minidom.parseString(xmlstring)
810
def make_tag(document, name, prop):
811
e = document.createElement("property")
812
e.setAttribute("name", name)
813
e.setAttribute("type", prop._dbus_signature)
814
e.setAttribute("access", prop._dbus_access)
816
for if_tag in document.getElementsByTagName("interface"):
817
for tag in (make_tag(document, name, prop)
819
in self._get_all_dbus_properties()
820
if prop._dbus_interface
821
== if_tag.getAttribute("name")):
822
if_tag.appendChild(tag)
823
# Add the names to the return values for the
824
# "org.freedesktop.DBus.Properties" methods
825
if (if_tag.getAttribute("name")
826
== "org.freedesktop.DBus.Properties"):
827
for cn in if_tag.getElementsByTagName("method"):
828
if cn.getAttribute("name") == "Get":
829
for arg in cn.getElementsByTagName("arg"):
830
if (arg.getAttribute("direction")
832
arg.setAttribute("name", "value")
833
elif cn.getAttribute("name") == "GetAll":
834
for arg in cn.getElementsByTagName("arg"):
835
if (arg.getAttribute("direction")
837
arg.setAttribute("name", "props")
838
xmlstring = document.toxml("utf-8")
840
except (AttributeError, xml.dom.DOMException,
841
xml.parsers.expat.ExpatError) as error:
842
logger.error("Failed to override Introspection method",
847
def datetime_to_dbus (dt, variant_level=0):
848
"""Convert a UTC datetime.datetime() to a D-Bus type."""
850
return dbus.String("", variant_level = variant_level)
851
return dbus.String(dt.isoformat(),
852
variant_level=variant_level)
855
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
857
"""Applied to an empty subclass of a D-Bus object, this metaclass
858
will add additional D-Bus attributes matching a certain pattern.
860
def __new__(mcs, name, bases, attr):
861
# Go through all the base classes which could have D-Bus
862
# methods, signals, or properties in them
863
for base in (b for b in bases
864
if issubclass(b, dbus.service.Object)):
865
# Go though all attributes of the base class
866
for attrname, attribute in inspect.getmembers(base):
867
# Ignore non-D-Bus attributes, and D-Bus attributes
868
# with the wrong interface name
869
if (not hasattr(attribute, "_dbus_interface")
870
or not attribute._dbus_interface
871
.startswith("se.recompile.Mandos")):
873
# Create an alternate D-Bus interface name based on
875
alt_interface = (attribute._dbus_interface
876
.replace("se.recompile.Mandos",
877
"se.bsnet.fukt.Mandos"))
878
# Is this a D-Bus signal?
879
if getattr(attribute, "_dbus_is_signal", False):
880
# Extract the original non-method function by
882
nonmethod_func = (dict(
883
zip(attribute.func_code.co_freevars,
884
attribute.__closure__))["func"]
886
# Create a new, but exactly alike, function
887
# object, and decorate it to be a new D-Bus signal
888
# with the alternate D-Bus interface name
889
new_function = (dbus.service.signal
891
attribute._dbus_signature)
893
nonmethod_func.func_code,
894
nonmethod_func.func_globals,
895
nonmethod_func.func_name,
896
nonmethod_func.func_defaults,
897
nonmethod_func.func_closure)))
898
# Define a creator of a function to call both the
899
# old and new functions, so both the old and new
900
# signals gets sent when the function is called
901
def fixscope(func1, func2):
902
"""This function is a scope container to pass
903
func1 and func2 to the "call_both" function
904
outside of its arguments"""
905
def call_both(*args, **kwargs):
906
"""This function will emit two D-Bus
907
signals by calling func1 and func2"""
908
func1(*args, **kwargs)
909
func2(*args, **kwargs)
911
# Create the "call_both" function and add it to
913
attr[attrname] = fixscope(attribute,
915
# Is this a D-Bus method?
916
elif getattr(attribute, "_dbus_is_method", False):
917
# Create a new, but exactly alike, function
918
# object. Decorate it to be a new D-Bus method
919
# with the alternate D-Bus interface name. Add it
921
attr[attrname] = (dbus.service.method
923
attribute._dbus_in_signature,
924
attribute._dbus_out_signature)
926
(attribute.func_code,
927
attribute.func_globals,
929
attribute.func_defaults,
930
attribute.func_closure)))
931
# Is this a D-Bus property?
932
elif getattr(attribute, "_dbus_is_property", False):
933
# Create a new, but exactly alike, function
934
# object, and decorate it to be a new D-Bus
935
# property with the alternate D-Bus interface
936
# name. Add it to the class.
937
attr[attrname] = (dbus_service_property
939
attribute._dbus_signature,
940
attribute._dbus_access,
942
._dbus_get_args_options
945
(attribute.func_code,
946
attribute.func_globals,
948
attribute.func_defaults,
949
attribute.func_closure)))
950
return type.__new__(mcs, name, bases, attr)
953
class ClientDBus(Client, DBusObjectWithProperties):
954
"""A Client class using D-Bus
957
dbus_object_path: dbus.ObjectPath
958
bus: dbus.SystemBus()
961
runtime_expansions = (Client.runtime_expansions
962
+ ("dbus_object_path",))
964
# dbus.service.Object doesn't use super(), so we can't either.
966
def __init__(self, bus = None, *args, **kwargs):
968
Client.__init__(self, *args, **kwargs)
970
self._approvals_pending = 0
971
# Only now, when this client is initialized, can it show up on
973
client_object_name = unicode(self.name).translate(
976
self.dbus_object_path = (dbus.ObjectPath
977
("/clients/" + client_object_name))
978
DBusObjectWithProperties.__init__(self, self.bus,
979
self.dbus_object_path)
981
def notifychangeproperty(transform_func,
982
dbus_name, type_func=lambda x: x,
984
""" Modify a variable so that it's a property which announces
987
transform_fun: Function that takes a value and a variant_level
988
and transforms it to a D-Bus type.
989
dbus_name: D-Bus name of the variable
990
type_func: Function that transform the value before sending it
991
to the D-Bus. Default: no transform
992
variant_level: D-Bus variant level. Default: 1
994
attrname = "_{0}".format(dbus_name)
995
def setter(self, value):
996
if hasattr(self, "dbus_object_path"):
997
if (not hasattr(self, attrname) or
998
type_func(getattr(self, attrname, None))
999
!= type_func(value)):
1000
dbus_value = transform_func(type_func(value),
1003
self.PropertyChanged(dbus.String(dbus_name),
1005
setattr(self, attrname, value)
1007
return property(lambda self: getattr(self, attrname), setter)
1010
expires = notifychangeproperty(datetime_to_dbus, "Expires")
1011
approvals_pending = notifychangeproperty(dbus.Boolean,
1014
enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1015
last_enabled = notifychangeproperty(datetime_to_dbus,
1017
checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1018
type_func = lambda checker:
1019
checker is not None)
1020
last_checked_ok = notifychangeproperty(datetime_to_dbus,
1022
last_approval_request = notifychangeproperty(
1023
datetime_to_dbus, "LastApprovalRequest")
1024
approved_by_default = notifychangeproperty(dbus.Boolean,
1025
"ApprovedByDefault")
1026
approval_delay = notifychangeproperty(dbus.UInt16,
1029
_timedelta_to_milliseconds)
1030
approval_duration = notifychangeproperty(
1031
dbus.UInt16, "ApprovalDuration",
1032
type_func = _timedelta_to_milliseconds)
1033
host = notifychangeproperty(dbus.String, "Host")
1034
timeout = notifychangeproperty(dbus.UInt16, "Timeout",
1036
_timedelta_to_milliseconds)
1037
extended_timeout = notifychangeproperty(
1038
dbus.UInt16, "ExtendedTimeout",
1039
type_func = _timedelta_to_milliseconds)
1040
interval = notifychangeproperty(dbus.UInt16,
1043
_timedelta_to_milliseconds)
1044
checker_command = notifychangeproperty(dbus.String, "Checker")
1046
del notifychangeproperty
1048
def __del__(self, *args, **kwargs):
1050
self.remove_from_connection()
1053
if hasattr(DBusObjectWithProperties, "__del__"):
1054
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1055
Client.__del__(self, *args, **kwargs)
1057
def checker_callback(self, pid, condition, command,
1059
self.checker_callback_tag = None
1061
if os.WIFEXITED(condition):
1062
exitstatus = os.WEXITSTATUS(condition)
1064
self.CheckerCompleted(dbus.Int16(exitstatus),
1065
dbus.Int64(condition),
1066
dbus.String(command))
1069
self.CheckerCompleted(dbus.Int16(-1),
1070
dbus.Int64(condition),
1071
dbus.String(command))
1073
return Client.checker_callback(self, pid, condition, command,
1076
def start_checker(self, *args, **kwargs):
1077
old_checker = self.checker
1078
if self.checker is not None:
1079
old_checker_pid = self.checker.pid
1081
old_checker_pid = None
1082
r = Client.start_checker(self, *args, **kwargs)
1083
# Only if new checker process was started
1084
if (self.checker is not None
1085
and old_checker_pid != self.checker.pid):
1087
self.CheckerStarted(self.current_checker_command)
1090
def _reset_approved(self):
1091
self._approved = None
1094
def approve(self, value=True):
1095
self.send_changedstate()
1096
self._approved = value
1097
gobject.timeout_add(_timedelta_to_milliseconds
1098
(self.approval_duration),
1099
self._reset_approved)
1102
## D-Bus methods, signals & properties
1103
_interface = "se.recompile.Mandos.Client"
1107
# CheckerCompleted - signal
1108
@dbus.service.signal(_interface, signature="nxs")
1109
def CheckerCompleted(self, exitcode, waitstatus, command):
1113
# CheckerStarted - signal
1114
@dbus.service.signal(_interface, signature="s")
1115
def CheckerStarted(self, command):
1119
# PropertyChanged - signal
1120
@dbus.service.signal(_interface, signature="sv")
1121
def PropertyChanged(self, property, value):
1125
# GotSecret - signal
1126
@dbus.service.signal(_interface)
1127
def GotSecret(self):
1129
Is sent after a successful transfer of secret from the Mandos
1130
server to mandos-client
1135
@dbus.service.signal(_interface, signature="s")
1136
def Rejected(self, reason):
1140
# NeedApproval - signal
1141
@dbus.service.signal(_interface, signature="tb")
1142
def NeedApproval(self, timeout, default):
1144
return self.need_approval()
1146
# NeRwequest - signal
1147
@dbus.service.signal(_interface, signature="s")
1148
def NewRequest(self, ip):
1150
Is sent after a client request a password.
1157
@dbus.service.method(_interface, in_signature="b")
1158
def Approve(self, value):
1161
# CheckedOK - method
1162
@dbus.service.method(_interface)
1163
def CheckedOK(self):
1167
@dbus.service.method(_interface)
1172
# StartChecker - method
1173
@dbus.service.method(_interface)
1174
def StartChecker(self):
1176
self.start_checker()
1179
@dbus.service.method(_interface)
1184
# StopChecker - method
1185
@dbus.service.method(_interface)
1186
def StopChecker(self):
1191
# ApprovalPending - property
1192
@dbus_service_property(_interface, signature="b", access="read")
1193
def ApprovalPending_dbus_property(self):
1194
return dbus.Boolean(bool(self.approvals_pending))
1196
# ApprovedByDefault - property
1197
@dbus_service_property(_interface, signature="b",
1199
def ApprovedByDefault_dbus_property(self, value=None):
1200
if value is None: # get
1201
return dbus.Boolean(self.approved_by_default)
1202
self.approved_by_default = bool(value)
1204
# ApprovalDelay - property
1205
@dbus_service_property(_interface, signature="t",
1207
def ApprovalDelay_dbus_property(self, value=None):
1208
if value is None: # get
1209
return dbus.UInt64(self.approval_delay_milliseconds())
1210
self.approval_delay = datetime.timedelta(0, 0, 0, value)
1212
# ApprovalDuration - property
1213
@dbus_service_property(_interface, signature="t",
1215
def ApprovalDuration_dbus_property(self, value=None):
1216
if value is None: # get
1217
return dbus.UInt64(_timedelta_to_milliseconds(
1218
self.approval_duration))
1219
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1222
@dbus_service_property(_interface, signature="s", access="read")
1223
def Name_dbus_property(self):
1224
return dbus.String(self.name)
1226
# Fingerprint - property
1227
@dbus_service_property(_interface, signature="s", access="read")
1228
def Fingerprint_dbus_property(self):
1229
return dbus.String(self.fingerprint)
1232
@dbus_service_property(_interface, signature="s",
1234
def Host_dbus_property(self, value=None):
1235
if value is None: # get
1236
return dbus.String(self.host)
1239
# Created - property
1240
@dbus_service_property(_interface, signature="s", access="read")
1241
def Created_dbus_property(self):
1242
return dbus.String(datetime_to_dbus(self.created))
1244
# LastEnabled - property
1245
@dbus_service_property(_interface, signature="s", access="read")
1246
def LastEnabled_dbus_property(self):
1247
return datetime_to_dbus(self.last_enabled)
1249
# Enabled - property
1250
@dbus_service_property(_interface, signature="b",
1252
def Enabled_dbus_property(self, value=None):
1253
if value is None: # get
1254
return dbus.Boolean(self.enabled)
1260
# LastCheckedOK - property
1261
@dbus_service_property(_interface, signature="s",
1263
def LastCheckedOK_dbus_property(self, value=None):
1264
if value is not None:
1267
return datetime_to_dbus(self.last_checked_ok)
1269
# Expires - property
1270
@dbus_service_property(_interface, signature="s", access="read")
1271
def Expires_dbus_property(self):
1272
return datetime_to_dbus(self.expires)
1274
# LastApprovalRequest - property
1275
@dbus_service_property(_interface, signature="s", access="read")
1276
def LastApprovalRequest_dbus_property(self):
1277
return datetime_to_dbus(self.last_approval_request)
1279
# Timeout - property
1280
@dbus_service_property(_interface, signature="t",
1282
def Timeout_dbus_property(self, value=None):
1283
if value is None: # get
1284
return dbus.UInt64(self.timeout_milliseconds())
1285
self.timeout = datetime.timedelta(0, 0, 0, value)
1286
if getattr(self, "disable_initiator_tag", None) is None:
1288
# Reschedule timeout
1289
gobject.source_remove(self.disable_initiator_tag)
1290
self.disable_initiator_tag = None
1292
time_to_die = _timedelta_to_milliseconds((self
1297
if time_to_die <= 0:
1298
# The timeout has passed
1301
self.expires = (datetime.datetime.utcnow()
1302
+ datetime.timedelta(milliseconds =
1304
self.disable_initiator_tag = (gobject.timeout_add
1305
(time_to_die, self.disable))
1307
# ExtendedTimeout - property
1308
@dbus_service_property(_interface, signature="t",
1310
def ExtendedTimeout_dbus_property(self, value=None):
1311
if value is None: # get
1312
return dbus.UInt64(self.extended_timeout_milliseconds())
1313
self.extended_timeout = datetime.timedelta(0, 0, 0, value)
1315
# Interval - property
1316
@dbus_service_property(_interface, signature="t",
1318
def Interval_dbus_property(self, value=None):
1319
if value is None: # get
1320
return dbus.UInt64(self.interval_milliseconds())
1321
self.interval = datetime.timedelta(0, 0, 0, value)
1322
if getattr(self, "checker_initiator_tag", None) is None:
1324
# Reschedule checker run
1325
gobject.source_remove(self.checker_initiator_tag)
1326
self.checker_initiator_tag = (gobject.timeout_add
1327
(value, self.start_checker))
1328
self.start_checker() # Start one now, too
1330
# Checker - property
1331
@dbus_service_property(_interface, signature="s",
1333
def Checker_dbus_property(self, value=None):
1334
if value is None: # get
1335
return dbus.String(self.checker_command)
1336
self.checker_command = value
1338
# CheckerRunning - property
1339
@dbus_service_property(_interface, signature="b",
1341
def CheckerRunning_dbus_property(self, value=None):
1342
if value is None: # get
1343
return dbus.Boolean(self.checker is not None)
1345
self.start_checker()
1349
# ObjectPath - property
1350
@dbus_service_property(_interface, signature="o", access="read")
1351
def ObjectPath_dbus_property(self):
1352
return self.dbus_object_path # is already a dbus.ObjectPath
1355
@dbus_service_property(_interface, signature="ay",
1356
access="write", byte_arrays=True)
1357
def Secret_dbus_property(self, value):
1358
self.secret = str(value)
1363
class ProxyClient(object):
1364
def __init__(self, child_pipe, fpr, address):
1365
self._pipe = child_pipe
1366
self._pipe.send(('init', fpr, address))
1367
if not self._pipe.recv():
1370
def __getattribute__(self, name):
1371
if(name == '_pipe'):
1372
return super(ProxyClient, self).__getattribute__(name)
1373
self._pipe.send(('getattr', name))
1374
data = self._pipe.recv()
1375
if data[0] == 'data':
1377
if data[0] == 'function':
1378
def func(*args, **kwargs):
1379
self._pipe.send(('funcall', name, args, kwargs))
1380
return self._pipe.recv()[1]
1383
def __setattr__(self, name, value):
1384
if(name == '_pipe'):
1385
return super(ProxyClient, self).__setattr__(name, value)
1386
self._pipe.send(('setattr', name, value))
1389
class ClientDBusTransitional(ClientDBus):
1390
__metaclass__ = AlternateDBusNamesMetaclass
1393
class ClientHandler(socketserver.BaseRequestHandler, object):
1394
"""A class to handle client connections.
1396
Instantiated once for each connection to handle it.
307
def still_valid(self, now=None):
308
"""Has the timeout not yet passed for this client?"""
310
now = datetime.datetime.now()
311
if self.last_seen is None:
312
return now < (self.created + self.timeout)
314
return now < (self.last_seen + self.timeout)
317
def peer_certificate(session):
318
"Return the peer's OpenPGP certificate as a bytestring"
319
# If not an OpenPGP certificate...
320
if gnutls.library.functions.gnutls_certificate_type_get\
321
(session._c_object) \
322
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
323
# ...do the normal thing
324
return session.peer_certificate
325
list_size = ctypes.c_uint()
326
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
327
(session._c_object, ctypes.byref(list_size))
328
if list_size.value == 0:
331
return ctypes.string_at(cert.data, cert.size)
334
def fingerprint(openpgp):
335
"Convert an OpenPGP bytestring to a hexdigit fingerprint string"
336
# New empty GnuTLS certificate
337
crt = gnutls.library.types.gnutls_openpgp_crt_t()
338
gnutls.library.functions.gnutls_openpgp_crt_init\
340
# New GnuTLS "datum" with the OpenPGP public key
341
datum = gnutls.library.types.gnutls_datum_t\
342
(ctypes.cast(ctypes.c_char_p(openpgp),
343
ctypes.POINTER(ctypes.c_ubyte)),
344
ctypes.c_uint(len(openpgp)))
345
# Import the OpenPGP public key into the certificate
346
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
349
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
350
# New buffer for the fingerprint
351
buffer = ctypes.create_string_buffer(20)
352
buffer_length = ctypes.c_size_t()
353
# Get the fingerprint from the certificate into the buffer
354
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
355
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
356
# Deinit the certificate
357
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
358
# Convert the buffer to a Python bytestring
359
fpr = ctypes.string_at(buffer, buffer_length.value)
360
# Convert the bytestring to hexadecimal notation
361
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
365
class tcp_handler(SocketServer.BaseRequestHandler, object):
366
"""A TCP request handler class.
367
Instantiated by IPv6_TCPServer for each request to handle it.
1397
368
Note: This will run in its own forked process."""
1399
370
def handle(self):
1400
with contextlib.closing(self.server.child_pipe) as child_pipe:
1401
logger.info("TCP connection from: %s",
1402
unicode(self.client_address))
1403
logger.debug("Pipe FD: %d",
1404
self.server.child_pipe.fileno())
1406
session = (gnutls.connection
1407
.ClientSession(self.request,
1409
.X509Credentials()))
1411
# Note: gnutls.connection.X509Credentials is really a
1412
# generic GnuTLS certificate credentials object so long as
1413
# no X.509 keys are added to it. Therefore, we can use it
1414
# here despite using OpenPGP certificates.
1416
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1417
# "+AES-256-CBC", "+SHA1",
1418
# "+COMP-NULL", "+CTYPE-OPENPGP",
1420
# Use a fallback default, since this MUST be set.
1421
priority = self.server.gnutls_priority
1422
if priority is None:
1424
(gnutls.library.functions
1425
.gnutls_priority_set_direct(session._c_object,
1428
# Start communication using the Mandos protocol
1429
# Get protocol number
1430
line = self.request.makefile().readline()
1431
logger.debug("Protocol version: %r", line)
1433
if int(line.strip().split()[0]) > 1:
1435
except (ValueError, IndexError, RuntimeError) as error:
1436
logger.error("Unknown protocol version: %s", error)
1439
# Start GnuTLS connection
1442
except gnutls.errors.GNUTLSError as error:
1443
logger.warning("Handshake failed: %s", error)
1444
# Do not run session.bye() here: the session is not
1445
# established. Just abandon the request.
1447
logger.debug("Handshake succeeded")
1449
approval_required = False
1452
fpr = self.fingerprint(self.peer_certificate
1455
gnutls.errors.GNUTLSError) as error:
1456
logger.warning("Bad certificate: %s", error)
1458
logger.debug("Fingerprint: %s", fpr)
1459
if self.server.use_dbus:
1461
client.NewRequest(str(self.client_address))
1464
client = ProxyClient(child_pipe, fpr,
1465
self.client_address)
1469
if client.approval_delay:
1470
delay = client.approval_delay
1471
client.approvals_pending += 1
1472
approval_required = True
1475
if not client.enabled:
1476
logger.info("Client %s is disabled",
1478
if self.server.use_dbus:
1480
client.Rejected("Disabled")
1483
if client._approved or not client.approval_delay:
1484
#We are approved or approval is disabled
1486
elif client._approved is None:
1487
logger.info("Client %s needs approval",
1489
if self.server.use_dbus:
1491
client.NeedApproval(
1492
client.approval_delay_milliseconds(),
1493
client.approved_by_default)
1495
logger.warning("Client %s was not approved",
1497
if self.server.use_dbus:
1499
client.Rejected("Denied")
1502
#wait until timeout or approved
1503
time = datetime.datetime.now()
1504
client.changedstate.acquire()
1505
(client.changedstate.wait
1506
(float(client._timedelta_to_milliseconds(delay)
1508
client.changedstate.release()
1509
time2 = datetime.datetime.now()
1510
if (time2 - time) >= delay:
1511
if not client.approved_by_default:
1512
logger.warning("Client %s timed out while"
1513
" waiting for approval",
1515
if self.server.use_dbus:
1517
client.Rejected("Approval timed out")
1522
delay -= time2 - time
1525
while sent_size < len(client.secret):
1527
sent = session.send(client.secret[sent_size:])
1528
except gnutls.errors.GNUTLSError as error:
1529
logger.warning("gnutls send failed")
1531
logger.debug("Sent: %d, remaining: %d",
1532
sent, len(client.secret)
1533
- (sent_size + sent))
1536
logger.info("Sending secret to %s", client.name)
1537
# bump the timeout using extended_timeout
1538
client.checked_ok(client.extended_timeout)
1539
if self.server.use_dbus:
1544
if approval_required:
1545
client.approvals_pending -= 1
1548
except gnutls.errors.GNUTLSError as error:
1549
logger.warning("GnuTLS bye failed")
1552
def peer_certificate(session):
1553
"Return the peer's OpenPGP certificate as a bytestring"
1554
# If not an OpenPGP certificate...
1555
if (gnutls.library.functions
1556
.gnutls_certificate_type_get(session._c_object)
1557
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1558
# ...do the normal thing
1559
return session.peer_certificate
1560
list_size = ctypes.c_uint(1)
1561
cert_list = (gnutls.library.functions
1562
.gnutls_certificate_get_peers
1563
(session._c_object, ctypes.byref(list_size)))
1564
if not bool(cert_list) and list_size.value != 0:
1565
raise gnutls.errors.GNUTLSError("error getting peer"
1567
if list_size.value == 0:
1570
return ctypes.string_at(cert.data, cert.size)
1573
def fingerprint(openpgp):
1574
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1575
# New GnuTLS "datum" with the OpenPGP public key
1576
datum = (gnutls.library.types
1577
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1580
ctypes.c_uint(len(openpgp))))
1581
# New empty GnuTLS certificate
1582
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1583
(gnutls.library.functions
1584
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1585
# Import the OpenPGP public key into the certificate
1586
(gnutls.library.functions
1587
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1588
gnutls.library.constants
1589
.GNUTLS_OPENPGP_FMT_RAW))
1590
# Verify the self signature in the key
1591
crtverify = ctypes.c_uint()
1592
(gnutls.library.functions
1593
.gnutls_openpgp_crt_verify_self(crt, 0,
1594
ctypes.byref(crtverify)))
1595
if crtverify.value != 0:
1596
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1597
raise (gnutls.errors.CertificateSecurityError
1599
# New buffer for the fingerprint
1600
buf = ctypes.create_string_buffer(20)
1601
buf_len = ctypes.c_size_t()
1602
# Get the fingerprint from the certificate into the buffer
1603
(gnutls.library.functions
1604
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1605
ctypes.byref(buf_len)))
1606
# Deinit the certificate
1607
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1608
# Convert the buffer to a Python bytestring
1609
fpr = ctypes.string_at(buf, buf_len.value)
1610
# Convert the bytestring to hexadecimal notation
1611
hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1615
class MultiprocessingMixIn(object):
1616
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1617
def sub_process_main(self, request, address):
1619
self.finish_request(request, address)
1621
self.handle_error(request, address)
1622
self.close_request(request)
1624
def process_request(self, request, address):
1625
"""Start a new process to process the request."""
1626
proc = multiprocessing.Process(target = self.sub_process_main,
1633
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1634
""" adds a pipe to the MixIn """
1635
def process_request(self, request, client_address):
1636
"""Overrides and wraps the original process_request().
1638
This function creates a new pipe in self.pipe
1640
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1642
proc = MultiprocessingMixIn.process_request(self, request,
1644
self.child_pipe.close()
1645
self.add_pipe(parent_pipe, proc)
1647
def add_pipe(self, parent_pipe, proc):
1648
"""Dummy function; override as necessary"""
1649
raise NotImplementedError
1652
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1653
socketserver.TCPServer, object):
1654
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
371
logger.debug(u"TCP connection from: %s",
372
unicode(self.client_address))
373
session = gnutls.connection.ClientSession(self.request,
377
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
378
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
380
priority = "SECURE256"
382
gnutls.library.functions.gnutls_priority_set_direct\
383
(session._c_object, priority, None);
387
except gnutls.errors.GNUTLSError, error:
388
logger.debug(u"Handshake failed: %s", error)
389
# Do not run session.bye() here: the session is not
390
# established. Just abandon the request.
393
fpr = fingerprint(peer_certificate(session))
394
except (TypeError, gnutls.errors.GNUTLSError), error:
395
logger.debug(u"Bad certificate: %s", error)
398
logger.debug(u"Fingerprint: %s", fpr)
400
for c in self.server.clients:
401
if c.fingerprint == fpr:
404
# Have to check if client.still_valid(), since it is possible
405
# that the client timed out while establishing the GnuTLS
407
if (not client) or (not client.still_valid()):
409
logger.debug(u"Client %(name)s is invalid",
412
logger.debug(u"Client not found for fingerprint: %s",
417
while sent_size < len(client.secret):
418
sent = session.send(client.secret[sent_size:])
419
logger.debug(u"Sent: %d, remaining: %d",
420
sent, len(client.secret)
421
- (sent_size + sent))
426
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
427
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1657
enabled: Boolean; whether this server is activated yet
1658
interface: None or a network interface name (string)
1659
use_ipv6: Boolean; to use IPv6 or not
429
options: Command line options
430
clients: Set() of Client objects
1661
def __init__(self, server_address, RequestHandlerClass,
1662
interface=None, use_ipv6=True):
1663
self.interface = interface
1665
self.address_family = socket.AF_INET6
1666
socketserver.TCPServer.__init__(self, server_address,
1667
RequestHandlerClass)
432
address_family = socket.AF_INET6
433
def __init__(self, *args, **kwargs):
434
if "options" in kwargs:
435
self.options = kwargs["options"]
436
del kwargs["options"]
437
if "clients" in kwargs:
438
self.clients = kwargs["clients"]
439
del kwargs["clients"]
440
return super(type(self), self).__init__(*args, **kwargs)
1668
441
def server_bind(self):
1669
442
"""This overrides the normal server_bind() function
1670
443
to bind to an interface if one was specified, and also NOT to
1671
444
bind to an address or port if they were not specified."""
1672
if self.interface is not None:
1673
if SO_BINDTODEVICE is None:
1674
logger.error("SO_BINDTODEVICE does not exist;"
1675
" cannot bind to interface %s",
1679
self.socket.setsockopt(socket.SOL_SOCKET,
1683
except socket.error as error:
1684
if error[0] == errno.EPERM:
1685
logger.error("No permission to"
1686
" bind to interface %s",
1688
elif error[0] == errno.ENOPROTOOPT:
1689
logger.error("SO_BINDTODEVICE not available;"
1690
" cannot bind to interface %s",
445
if self.options.interface:
446
if not hasattr(socket, "SO_BINDTODEVICE"):
447
# From /usr/include/asm-i486/socket.h
448
socket.SO_BINDTODEVICE = 25
450
self.socket.setsockopt(socket.SOL_SOCKET,
451
socket.SO_BINDTODEVICE,
452
self.options.interface)
453
except socket.error, error:
454
if error[0] == errno.EPERM:
455
logger.warning(u"No permission to"
456
u" bind to interface %s",
457
self.options.interface)
1694
460
# Only bind(2) the socket if we really need to.
1695
461
if self.server_address[0] or self.server_address[1]:
1696
462
if not self.server_address[0]:
1697
if self.address_family == socket.AF_INET6:
1698
any_address = "::" # in6addr_any
1700
any_address = socket.INADDR_ANY
1701
self.server_address = (any_address,
464
self.server_address = (in6addr_any,
1702
465
self.server_address[1])
1703
elif not self.server_address[1]:
466
elif self.server_address[1] is None:
1704
467
self.server_address = (self.server_address[0],
1706
# if self.interface:
1707
# self.server_address = (self.server_address[0],
1712
return socketserver.TCPServer.server_bind(self)
1715
class MandosServer(IPv6_TCPServer):
1719
clients: set of Client objects
1720
gnutls_priority GnuTLS priority string
1721
use_dbus: Boolean; to emit D-Bus signals or not
1723
Assumes a gobject.MainLoop event loop.
1725
def __init__(self, server_address, RequestHandlerClass,
1726
interface=None, use_ipv6=True, clients=None,
1727
gnutls_priority=None, use_dbus=True):
1728
self.enabled = False
1729
self.clients = clients
1730
if self.clients is None:
1732
self.use_dbus = use_dbus
1733
self.gnutls_priority = gnutls_priority
1734
IPv6_TCPServer.__init__(self, server_address,
1735
RequestHandlerClass,
1736
interface = interface,
1737
use_ipv6 = use_ipv6)
1738
def server_activate(self):
1740
return socketserver.TCPServer.server_activate(self)
1745
def add_pipe(self, parent_pipe, proc):
1746
# Call "handle_ipc" for both data and EOF events
1747
gobject.io_add_watch(parent_pipe.fileno(),
1748
gobject.IO_IN | gobject.IO_HUP,
1749
functools.partial(self.handle_ipc,
1754
def handle_ipc(self, source, condition, parent_pipe=None,
1755
proc = None, client_object=None):
1757
gobject.IO_IN: "IN", # There is data to read.
1758
gobject.IO_OUT: "OUT", # Data can be written (without
1760
gobject.IO_PRI: "PRI", # There is urgent data to read.
1761
gobject.IO_ERR: "ERR", # Error condition.
1762
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1763
# broken, usually for pipes and
1766
conditions_string = ' | '.join(name
1768
condition_names.iteritems()
1769
if cond & condition)
1770
# error, or the other end of multiprocessing.Pipe has closed
1771
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1772
# Wait for other process to exit
1776
# Read a request from the child
1777
request = parent_pipe.recv()
1778
command = request[0]
1780
if command == 'init':
1782
address = request[2]
1784
for c in self.clients.itervalues():
1785
if c.fingerprint == fpr:
1789
logger.info("Client not found for fingerprint: %s, ad"
1790
"dress: %s", fpr, address)
1793
mandos_dbus_service.ClientNotFound(fpr,
1795
parent_pipe.send(False)
1798
gobject.io_add_watch(parent_pipe.fileno(),
1799
gobject.IO_IN | gobject.IO_HUP,
1800
functools.partial(self.handle_ipc,
1806
parent_pipe.send(True)
1807
# remove the old hook in favor of the new above hook on
1810
if command == 'funcall':
1811
funcname = request[1]
1815
parent_pipe.send(('data', getattr(client_object,
1819
if command == 'getattr':
1820
attrname = request[1]
1821
if callable(client_object.__getattribute__(attrname)):
1822
parent_pipe.send(('function',))
1824
parent_pipe.send(('data', client_object
1825
.__getattribute__(attrname)))
1827
if command == 'setattr':
1828
attrname = request[1]
1830
setattr(client_object, attrname, value)
469
return super(type(self), self).server_bind()
1835
472
def string_to_delta(interval):
1836
473
"""Parse a string and return a datetime.timedelta
1838
475
>>> string_to_delta('7d')
1839
476
datetime.timedelta(7)
1840
477
>>> string_to_delta('60s')
616
def killme(status = 0):
617
logger.debug("Stopping server with exit status %d", status)
619
if main_loop_started:
1901
##################################################################
1902
# Parsing of options, both command line and config file
1904
parser = argparse.ArgumentParser()
1905
parser.add_argument("-v", "--version", action="version",
1906
version = "%%(prog)s %s" % version,
1907
help="show version number and exit")
1908
parser.add_argument("-i", "--interface", metavar="IF",
1909
help="Bind to interface IF")
1910
parser.add_argument("-a", "--address",
1911
help="Address to listen for requests on")
1912
parser.add_argument("-p", "--port", type=int,
1913
help="Port number to receive requests on")
1914
parser.add_argument("--check", action="store_true",
1915
help="Run self-test")
1916
parser.add_argument("--debug", action="store_true",
1917
help="Debug mode; run in foreground and log"
1919
parser.add_argument("--debuglevel", metavar="LEVEL",
1920
help="Debug level for stdout output")
1921
parser.add_argument("--priority", help="GnuTLS"
1922
" priority string (see GnuTLS documentation)")
1923
parser.add_argument("--servicename",
1924
metavar="NAME", help="Zeroconf service name")
1925
parser.add_argument("--configdir",
1926
default="/etc/mandos", metavar="DIR",
1927
help="Directory to search for configuration"
1929
parser.add_argument("--no-dbus", action="store_false",
1930
dest="use_dbus", help="Do not provide D-Bus"
1931
" system bus interface")
1932
parser.add_argument("--no-ipv6", action="store_false",
1933
dest="use_ipv6", help="Do not use IPv6")
1934
parser.add_argument("--no-restore", action="store_false",
1935
dest="restore", help="Do not restore stored state",
1938
options = parser.parse_args()
628
global main_loop_started
629
main_loop_started = False
631
parser = OptionParser()
632
parser.add_option("-i", "--interface", type="string",
633
default=None, metavar="IF",
634
help="Bind to interface IF")
635
parser.add_option("-a", "--address", type="string", default=None,
636
help="Address to listen for requests on")
637
parser.add_option("-p", "--port", type="int", default=None,
638
help="Port number to receive requests on")
639
parser.add_option("--timeout", type="string", # Parsed later
641
help="Amount of downtime allowed for clients")
642
parser.add_option("--interval", type="string", # Parsed later
644
help="How often to check that a client is up")
645
parser.add_option("--check", action="store_true", default=False,
646
help="Run self-test")
647
parser.add_option("--debug", action="store_true", default=False,
649
(options, args) = parser.parse_args()
1940
651
if options.check:
1942
653
doctest.testmod()
1945
# Default values for config file for server-global settings
1946
server_defaults = { "interface": "",
1951
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1952
"servicename": "Mandos",
1958
# Parse config file for server-global settings
1959
server_config = configparser.SafeConfigParser(server_defaults)
1961
server_config.read(os.path.join(options.configdir,
1963
# Convert the SafeConfigParser object to a dict
1964
server_settings = server_config.defaults()
1965
# Use the appropriate methods on the non-string config options
1966
for option in ("debug", "use_dbus", "use_ipv6"):
1967
server_settings[option] = server_config.getboolean("DEFAULT",
1969
if server_settings["port"]:
1970
server_settings["port"] = server_config.getint("DEFAULT",
1974
# Override the settings from the config file with command line
1976
for option in ("interface", "address", "port", "debug",
1977
"priority", "servicename", "configdir",
1978
"use_dbus", "use_ipv6", "debuglevel", "restore"):
1979
value = getattr(options, option)
1980
if value is not None:
1981
server_settings[option] = value
1983
# Force all strings to be unicode
1984
for option in server_settings.keys():
1985
if type(server_settings[option]) is str:
1986
server_settings[option] = unicode(server_settings[option])
1987
# Now we have our good server settings in "server_settings"
1989
##################################################################
1992
debug = server_settings["debug"]
1993
debuglevel = server_settings["debuglevel"]
1994
use_dbus = server_settings["use_dbus"]
1995
use_ipv6 = server_settings["use_ipv6"]
1998
initlogger(logging.DEBUG)
2003
level = getattr(logging, debuglevel.upper())
2006
if server_settings["servicename"] != "Mandos":
2007
syslogger.setFormatter(logging.Formatter
2008
('Mandos (%s) [%%(process)d]:'
2009
' %%(levelname)s: %%(message)s'
2010
% server_settings["servicename"]))
2012
# Parse config file with clients
2013
client_defaults = { "timeout": "5m",
2014
"extended_timeout": "15m",
2016
"checker": "fping -q -- %%(host)s",
2018
"approval_delay": "0s",
2019
"approval_duration": "1s",
2021
client_config = configparser.SafeConfigParser(client_defaults)
2022
client_config.read(os.path.join(server_settings["configdir"],
2025
global mandos_dbus_service
2026
mandos_dbus_service = None
2028
tcp_server = MandosServer((server_settings["address"],
2029
server_settings["port"]),
2031
interface=(server_settings["interface"]
2035
server_settings["priority"],
2038
pidfilename = "/var/run/mandos.pid"
2040
pidfile = open(pidfilename, "w")
2042
logger.error("Could not open file %r", pidfilename)
2045
uid = pwd.getpwnam("_mandos").pw_uid
2046
gid = pwd.getpwnam("_mandos").pw_gid
2049
uid = pwd.getpwnam("mandos").pw_uid
2050
gid = pwd.getpwnam("mandos").pw_gid
2053
uid = pwd.getpwnam("nobody").pw_uid
2054
gid = pwd.getpwnam("nobody").pw_gid
2061
except OSError as error:
2062
if error[0] != errno.EPERM:
2066
# Enable all possible GnuTLS debugging
2068
# "Use a log level over 10 to enable all debugging options."
2070
gnutls.library.functions.gnutls_global_set_log_level(11)
2072
@gnutls.library.types.gnutls_log_func
2073
def debug_gnutls(level, string):
2074
logger.debug("GnuTLS: %s", string[:-1])
2076
(gnutls.library.functions
2077
.gnutls_global_set_log_function(debug_gnutls))
2079
# Redirect stdin so all checkers get /dev/null
2080
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2081
os.dup2(null, sys.stdin.fileno())
2085
# No console logging
2086
logger.removeHandler(console)
2088
# Need to fork before connecting to D-Bus
2090
# Close all input and output, do double fork, etc.
656
# Parse the time arguments
658
options.timeout = string_to_delta(options.timeout)
660
parser.error("option --timeout: Unparseable time")
662
options.interval = string_to_delta(options.interval)
664
parser.error("option --interval: Unparseable time")
667
defaults = { "checker": "fping -q -- %%(fqdn)s" }
668
client_config = ConfigParser.SafeConfigParser(defaults)
669
#client_config.readfp(open("global.conf"), "global.conf")
670
client_config.read("mandos-clients.conf")
2093
672
global main_loop
2094
675
# From the Avahi example code
2095
676
DBusGMainLoop(set_as_default=True )
2096
677
main_loop = gobject.MainLoop()
2097
678
bus = dbus.SystemBus()
679
server = dbus.Interface(
680
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
681
avahi.DBUS_INTERFACE_SERVER )
2098
682
# End of Avahi example code
2101
bus_name = dbus.service.BusName("se.recompile.Mandos",
2102
bus, do_not_queue=True)
2103
old_bus_name = (dbus.service.BusName
2104
("se.bsnet.fukt.Mandos", bus,
2106
except dbus.exceptions.NameExistsException as e:
2107
logger.error(unicode(e) + ", disabling D-Bus")
2109
server_settings["use_dbus"] = False
2110
tcp_server.use_dbus = False
2111
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2112
service = AvahiServiceToSyslog(name =
2113
server_settings["servicename"],
2114
servicetype = "_mandos._tcp",
2115
protocol = protocol, bus = bus)
2116
if server_settings["interface"]:
2117
service.interface = (if_nametoindex
2118
(str(server_settings["interface"])))
2120
global multiprocessing_manager
2121
multiprocessing_manager = multiprocessing.Manager()
2123
client_class = Client
2125
client_class = functools.partial(ClientDBusTransitional,
2128
special_settings = {
2129
# Some settings need to be accessd by special methods;
2130
# booleans need .getboolean(), etc. Here is a list of them:
2131
"approved_by_default":
2133
client_config.getboolean(section, "approved_by_default"),
2135
# Construct a new dict of client settings of this form:
2136
# { client_name: {setting_name: value, ...}, ...}
2137
# with exceptions for any special settings as defined above
2138
client_settings = dict((clientname,
2140
(value if setting not in special_settings
2141
else special_settings[setting](clientname)))
2142
for setting, value in client_config.items(clientname)))
2143
for clientname in client_config.sections())
2145
old_client_settings = {}
2148
# Get client data and settings from last running state.
2149
if server_settings["restore"]:
2151
with open(stored_state_path, "rb") as stored_state:
2152
clients_data, old_client_settings = pickle.load(stored_state)
2153
os.remove(stored_state_path)
2154
except IOError as e:
2155
logger.warning("Could not load persistant state: {0}".format(e))
2156
if e.errno != errno.ENOENT:
2159
for client in clients_data:
2160
client_name = client["name"]
2162
# Decide which value to use after restoring saved state.
2163
# We have three different values: Old config file,
2164
# new config file, and saved state.
2165
# New config value takes precedence if it differs from old
2166
# config value, otherwise use saved state.
2167
for name, value in client_settings[client_name].items():
2169
# For each value in new config, check if it differs
2170
# from the old config value (Except for the "secret"
2172
if name != "secret" and value != old_client_settings[client_name][name]:
2173
setattr(client, name, value)
2177
# Clients who has passed its expire date, can still be enabled if its
2178
# last checker was sucessful. Clients who checkers failed before we
2179
# stored it state is asumed to had failed checker during downtime.
2180
if client["enabled"] and client["last_checked_ok"]:
2181
if ((datetime.datetime.utcnow() - client["last_checked_ok"])
2182
> client["interval"]):
2183
if client["last_checker_status"] != 0:
2184
client["enabled"] = False
2186
client["expires"] = datetime.datetime.utcnow() + client["timeout"]
2188
client["changedstate"] = (multiprocessing_manager
2189
.Condition(multiprocessing_manager
2192
new_client = ClientDBusTransitional.__new__(ClientDBusTransitional)
2193
tcp_server.clients[client_name] = new_client
2194
new_client.bus = bus
2195
for name, value in client.iteritems():
2196
setattr(new_client, name, value)
2197
client_object_name = unicode(client_name).translate(
2198
{ord("."): ord("_"),
2199
ord("-"): ord("_")})
2200
new_client.dbus_object_path = (dbus.ObjectPath
2201
("/clients/" + client_object_name))
2202
DBusObjectWithProperties.__init__(new_client,
2204
new_client.dbus_object_path)
2206
tcp_server.clients[client_name] = Client.__new__(Client)
2207
for name, value in client.iteritems():
2208
setattr(tcp_server.clients[client_name], name, value)
2210
tcp_server.clients[client_name].decrypt_secret(
2211
client_settings[client_name]["secret"])
2213
# Create/remove clients based on new changes made to config
2214
for clientname in set(old_client_settings) - set(client_settings):
2215
del tcp_server.clients[clientname]
2216
for clientname in set(client_settings) - set(old_client_settings):
2217
tcp_server.clients[clientname] = (client_class(name = clientname,
2223
if not tcp_server.clients:
2224
logger.warning("No clients defined")
684
debug = options.debug
687
console = logging.StreamHandler()
688
# console.setLevel(logging.DEBUG)
689
console.setFormatter(logging.Formatter\
690
('%(levelname)s: %(message)s'))
691
logger.addHandler(console)
695
def remove_from_clients(client):
696
clients.remove(client)
698
logger.debug(u"No clients left, exiting")
701
clients.update(Set(Client(name=section, options=options,
702
stop_hook = remove_from_clients,
703
**(dict(client_config\
705
for section in client_config.sections()))
2230
pidfile.write(str(pid) + "\n".encode("utf-8"))
2233
logger.error("Could not write to file %r with PID %d",
2236
# "pidfile" was never created
2240
signal.signal(signal.SIGINT, signal.SIG_IGN)
2242
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2243
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2246
class MandosDBusService(dbus.service.Object):
2247
"""A D-Bus proxy object"""
2249
dbus.service.Object.__init__(self, bus, "/")
2250
_interface = "se.recompile.Mandos"
2252
@dbus.service.signal(_interface, signature="o")
2253
def ClientAdded(self, objpath):
2257
@dbus.service.signal(_interface, signature="ss")
2258
def ClientNotFound(self, fingerprint, address):
2262
@dbus.service.signal(_interface, signature="os")
2263
def ClientRemoved(self, objpath, name):
2267
@dbus.service.method(_interface, out_signature="ao")
2268
def GetAllClients(self):
2270
return dbus.Array(c.dbus_object_path
2272
tcp_server.clients.itervalues())
2274
@dbus.service.method(_interface,
2275
out_signature="a{oa{sv}}")
2276
def GetAllClientsWithProperties(self):
2278
return dbus.Dictionary(
2279
((c.dbus_object_path, c.GetAll(""))
2280
for c in tcp_server.clients.itervalues()),
2283
@dbus.service.method(_interface, in_signature="o")
2284
def RemoveClient(self, object_path):
2286
for c in tcp_server.clients.itervalues():
2287
if c.dbus_object_path == object_path:
2288
del tcp_server.clients[c.name]
2289
c.remove_from_connection()
2290
# Don't signal anything except ClientRemoved
2291
c.disable(quiet=True)
2293
self.ClientRemoved(object_path, c.name)
2295
raise KeyError(object_path)
2299
class MandosDBusServiceTransitional(MandosDBusService):
2300
__metaclass__ = AlternateDBusNamesMetaclass
2301
mandos_dbus_service = MandosDBusServiceTransitional()
2304
711
"Cleanup function; run on exit"
2307
multiprocessing.active_children()
2308
if not (tcp_server.clients or client_settings):
2311
# Store client before exiting. Secrets are encrypted with key based
2312
# on what config file has. If config file is removed/edited, old
2313
# secret will thus be unrecovable.
2315
for client in tcp_server.clients.itervalues():
2316
client.encrypt_secret(client_settings[client.name]["secret"])
2320
# A list of attributes that will not be stored when shuting down.
2321
exclude = set(("bus", "changedstate", "secret"))
2322
for name, typ in inspect.getmembers(dbus.service.Object):
2325
client_dict["encrypted_secret"] = client.encrypted_secret
2326
for attr in client.client_structure:
2327
if attr not in exclude:
2328
client_dict[attr] = getattr(client, attr)
2330
clients.append(client_dict)
2331
del client_settings[client.name]["secret"]
2334
with os.fdopen(os.open(stored_state_path, os.O_CREAT|os.O_WRONLY|os.O_TRUNC, 0600), "wb") as stored_state:
2335
pickle.dump((clients, client_settings), stored_state)
2336
except IOError as e:
2337
logger.warning("Could not save persistant state: {0}".format(e))
2338
if e.errno != errno.ENOENT:
2341
# Delete all clients, and settings from config
2342
while tcp_server.clients:
2343
name, client = tcp_server.clients.popitem()
2345
client.remove_from_connection()
2346
# Don't signal anything except ClientRemoved
2347
client.disable(quiet=True)
2350
mandos_dbus_service.ClientRemoved(client
2353
client_settings.clear()
2355
atexit.register(cleanup)
2357
for client in tcp_server.clients.itervalues():
2360
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2361
# Need to initiate checking of clients
2363
client.init_checker()
2367
tcp_server.server_activate()
2369
# Find out what port we got
2370
service.port = tcp_server.socket.getsockname()[1]
2372
logger.info("Now listening on address %r, port %d,"
2373
" flowinfo %d, scope_id %d"
2374
% tcp_server.socket.getsockname())
2376
logger.info("Now listening on address %r, port %d"
2377
% tcp_server.socket.getsockname())
2379
#service.interface = tcp_server.socket.getsockname()[3]
2382
713
# From the Avahi example code
2385
except dbus.exceptions.DBusException as error:
2386
logger.critical("DBusException: %s", error)
714
if not group is None:
2389
717
# End of Avahi example code
2391
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2392
lambda *args, **kwargs:
2393
(tcp_server.handle_request
2394
(*args[2:], **kwargs) or True))
720
client = clients.pop()
721
client.stop_hook = None
724
atexit.register(cleanup)
727
signal.signal(signal.SIGINT, signal.SIG_IGN)
728
signal.signal(signal.SIGHUP, lambda signum, frame: killme())
729
signal.signal(signal.SIGTERM, lambda signum, frame: killme())
731
for client in clients:
734
tcp_server = IPv6_TCPServer((options.address, options.port),
738
# Find out what random port we got
740
servicePort = tcp_server.socket.getsockname()[1]
741
logger.debug(u"Now listening on port %d", servicePort)
743
if options.interface is not None:
744
global serviceInterface
745
serviceInterface = if_nametoindex(options.interface)
747
# From the Avahi example code
748
server.connect_to_signal("StateChanged", server_state_changed)
750
server_state_changed(server.GetState())
751
except dbus.exceptions.DBusException, error:
752
logger.critical(u"DBusException: %s", error)
754
# End of Avahi example code
756
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
757
lambda *args, **kwargs:
758
tcp_server.handle_request(*args[2:],
2396
761
logger.debug("Starting main loop")
762
main_loop_started = True
2398
except AvahiError as error:
2399
logger.critical("AvahiError: %s", error)
2402
764
except KeyboardInterrupt:
2404
print("", file=sys.stderr)
2405
logger.debug("Server received KeyboardInterrupt")
2406
logger.debug("Server exiting")
2407
# Must run before the D-Bus bus name gets deregistered
2411
770
if __name__ == '__main__':