45
15
import gnutls.library.functions
46
16
import gnutls.library.constants
47
17
import gnutls.library.types
48
import ConfigParser as configparser
57
import logging.handlers
63
import cPickle as pickle
64
import multiprocessing
72
31
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))
35
import logging.handlers
37
logger = logging.Logger('mandos')
38
syslogger = logging.handlers.SysLogHandler\
39
(facility = logging.handlers.SysLogHandler.LOG_DAEMON)
40
syslogger.setFormatter(logging.Formatter\
41
('%(levelname)s: %(message)s'))
42
logger.addHandler(syslogger)
45
# This variable is used to optionally bind to a specified interface.
46
# It is a global variable to fit in with the other variables from the
47
# Avahi server example code.
48
serviceInterface = avahi.IF_UNSPEC
49
# From the Avahi server example code:
50
serviceName = "Mandos"
51
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
52
servicePort = None # Not known at startup
53
serviceTXT = [] # TXT record for the service
54
domain = "" # Domain to publish on, default to .local
55
host = "" # Host to publish records for, default to localhost
56
group = None #our entry group
57
rename_count = 12 # Counter so we only rename after collisions a
58
# sensible number of times
59
# End of Avahi example code
306
62
class Client(object):
307
63
"""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
65
name: string; from the config file, used in log messages
66
fingerprint: string (40 or 32 hexadecimal digits); used to
67
uniquely identify the client
68
secret: bytestring; sent verbatim (over TLS) to client
69
fqdn: string (FQDN); available for use by the checker command
70
created: datetime.datetime()
71
last_seen: datetime.datetime() or None if not yet seen
72
timeout: datetime.timedelta(); How long from last_seen until
73
this client is invalid
74
interval: datetime.timedelta(); How often to start a new checker
75
stop_hook: If set, called by stop() as stop_hook(self)
76
checker: subprocess.Popen(); a running checker process used
77
to see if the client lives.
78
Is None if no process is running.
79
checker_initiator_tag: a gobject event source tag, or None
80
stop_initiator_tag: - '' -
81
checker_callback_tag: - '' -
82
checker_command: string; External command which is run to check if
83
client lives. %()s expansions are done at
319
84
runtime with vars(self) as dict, so that for
320
85
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
87
_timeout: Real variable for 'timeout'
88
_interval: Real variable for 'interval'
89
_timeout_milliseconds: Used by gobject.timeout_add()
90
_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'
92
def _set_timeout(self, timeout):
93
"Setter function for 'timeout' attribute"
94
self._timeout = timeout
95
self._timeout_milliseconds = ((self.timeout.days
96
* 24 * 60 * 60 * 1000)
97
+ (self.timeout.seconds * 1000)
98
+ (self.timeout.microseconds
100
timeout = property(lambda self: self._timeout,
103
def _set_interval(self, interval):
104
"Setter function for 'interval' attribute"
105
self._interval = interval
106
self._interval_milliseconds = ((self.interval.days
107
* 24 * 60 * 60 * 1000)
108
+ (self.interval.seconds
110
+ (self.interval.microseconds
112
interval = property(lambda self: self._interval,
115
def __init__(self, name=None, options=None, stop_hook=None,
116
fingerprint=None, secret=None, secfile=None,
117
fqdn=None, timeout=None, interval=-1, checker=None):
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"])
119
# Uppercase and remove spaces from fingerprint
120
# for later comparison purposes with return value of
121
# the fingerprint() function
122
self.fingerprint = fingerprint.upper().replace(u" ", u"")
124
self.secret = secret.decode(u"base64")
127
self.secret = sf.read()
130
raise RuntimeError(u"No secret or secfile for client %s"
132
self.fqdn = fqdn # string
133
self.created = datetime.datetime.now()
134
self.last_seen = None
136
self.timeout = options.timeout
138
self.timeout = string_to_delta(timeout)
140
self.interval = options.interval
142
self.interval = string_to_delta(interval)
143
self.stop_hook = stop_hook
404
144
self.checker = None
405
145
self.checker_initiator_tag = None
406
self.disable_initiator_tag = None
407
self.expires = datetime.datetime.utcnow() + self.timeout
146
self.stop_initiator_tag = None
408
147
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()
436
"""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):
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):
459
gobject.source_remove(self.checker_initiator_tag)
460
self.checker_initiator_tag = None
463
# Do not run this again if called by a gobject.timeout_add
469
def init_checker(self):
148
self.check_command = checker
150
"""Start this clients checker and timeout hooks"""
470
151
# Schedule a new checker to be started an 'interval' from now,
471
152
# 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(),
153
self.checker_initiator_tag = gobject.timeout_add\
154
(self._interval_milliseconds,
479
156
# Also start a new checker *right now*.
480
157
self.start_checker()
482
def checker_callback(self, pid, condition, command):
158
# Schedule a stop() when 'timeout' has passed
159
self.stop_initiator_tag = gobject.timeout_add\
160
(self._timeout_milliseconds,
164
The possibility that this client might be restarted is left
165
open, but not currently used."""
166
logger.debug(u"Stopping client %s", self.name)
168
if self.stop_initiator_tag:
169
gobject.source_remove(self.stop_initiator_tag)
170
self.stop_initiator_tag = None
171
if self.checker_initiator_tag:
172
gobject.source_remove(self.checker_initiator_tag)
173
self.checker_initiator_tag = None
177
# Do not run this again if called by a gobject.timeout_add
180
# Some code duplication here and in stop()
181
if hasattr(self, "stop_initiator_tag") \
182
and self.stop_initiator_tag:
183
gobject.source_remove(self.stop_initiator_tag)
184
self.stop_initiator_tag = None
185
if hasattr(self, "checker_initiator_tag") \
186
and self.checker_initiator_tag:
187
gobject.source_remove(self.checker_initiator_tag)
188
self.checker_initiator_tag = None
190
def checker_callback(self, pid, condition):
483
191
"""The checker has completed, so take appropriate actions."""
484
self.checker_callback_tag = 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?",
192
now = datetime.datetime.now()
193
if os.WIFEXITED(condition) \
194
and (os.WEXITSTATUS(condition) == 0):
195
logger.debug(u"Checker for %(name)s succeeded",
198
gobject.source_remove(self.stop_initiator_tag)
199
self.stop_initiator_tag = gobject.timeout_add\
200
(self._timeout_milliseconds,
202
elif not os.WIFEXITED(condition):
203
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()
206
logger.debug(u"Checker for %(name)s failed",
209
self.checker_callback_tag = None
520
210
def start_checker(self):
521
211
"""Start a new checker subprocess if one is not running.
523
212
If a checker already exists, leave it running and do
525
# The reason for not killing a running checker is that if we
526
# did that, then if a checker (for some reason) started
527
# running slowly and taking more than 'interval' time, the
528
# client would inevitably timeout, since no checker would get
529
# a chance to run to completion. If we instead leave running
530
# 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
548
214
if self.checker is None:
550
# In case checker_command has exactly one % operator
551
command = self.checker_command % self.host
216
command = self.check_command % self.fqdn
552
217
except TypeError:
553
# Escape attributes for the shell
554
escaped_attrs = dict(
556
re.escape(unicode(str(getattr(self, attr, "")),
560
self.runtime_expansions)
218
escaped_attrs = dict((key, re.escape(str(val)))
220
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)
222
command = self.check_command % escaped_attrs
223
except TypeError, error:
224
logger.critical(u'Could not format string "%s":'
225
u' %s', self.check_command, error)
567
226
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",
228
logger.debug(u"Starting checker %r for %s",
230
self.checker = subprocess.\
232
close_fds=True, shell=True,
234
self.checker_callback_tag = gobject.child_watch_add\
236
self.checker_callback)
237
except subprocess.OSError, error:
238
logger.error(u"Failed to start subprocess: %s",
592
240
# Re-run this periodically if run by gobject.timeout_add
595
242
def stop_checker(self):
596
243
"""Force the checker process, if any, to stop."""
597
if self.checker_callback_tag:
598
gobject.source_remove(self.checker_callback_tag)
599
self.checker_callback_tag = None
600
if getattr(self, "checker", None) is None:
244
if not hasattr(self, "checker") or self.checker is None:
602
logger.debug("Stopping checker for %(name)s", vars(self))
604
os.kill(self.checker.pid, signal.SIGTERM)
606
#if self.checker.poll() is None:
607
# os.kill(self.checker.pid, signal.SIGKILL)
608
except OSError as error:
609
if error.errno != errno.ESRCH: # No such process
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,
246
gobject.source_remove(self.checker_callback_tag)
1059
247
self.checker_callback_tag = None
248
os.kill(self.checker.pid, signal.SIGTERM)
249
if self.checker.poll() is None:
250
os.kill(self.checker.pid, signal.SIGKILL)
1060
251
self.checker = 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.
252
def still_valid(self, now=None):
253
"""Has the timeout not yet passed for this client?"""
255
now = datetime.datetime.now()
256
if self.last_seen is None:
257
return now < (self.created + self.timeout)
259
return now < (self.last_seen + self.timeout)
262
def peer_certificate(session):
263
"Return an OpenPGP data packet string for the peer's certificate"
264
# If not an OpenPGP certificate...
265
if gnutls.library.functions.gnutls_certificate_type_get\
266
(session._c_object) \
267
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
268
# ...do the normal thing
269
return session.peer_certificate
270
list_size = ctypes.c_uint()
271
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
272
(session._c_object, ctypes.byref(list_size))
273
if list_size.value == 0:
276
return ctypes.string_at(cert.data, cert.size)
279
def fingerprint(openpgp):
280
"Convert an OpenPGP data string to a hexdigit fingerprint string"
281
# New empty GnuTLS certificate
282
crt = gnutls.library.types.gnutls_openpgp_crt_t()
283
gnutls.library.functions.gnutls_openpgp_crt_init\
285
# New GnuTLS "datum" with the OpenPGP public key
286
datum = gnutls.library.types.gnutls_datum_t\
287
(ctypes.cast(ctypes.c_char_p(openpgp),
288
ctypes.POINTER(ctypes.c_ubyte)),
289
ctypes.c_uint(len(openpgp)))
290
# Import the OpenPGP public key into the certificate
291
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
294
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
295
# New buffer for the fingerprint
296
buffer = ctypes.create_string_buffer(20)
297
buffer_length = ctypes.c_size_t()
298
# Get the fingerprint from the certificate into the buffer
299
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
300
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
301
# Deinit the certificate
302
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
303
# Convert the buffer to a Python bytestring
304
fpr = ctypes.string_at(buffer, buffer_length.value)
305
# Convert the bytestring to hexadecimal notation
306
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
310
class tcp_handler(SocketServer.BaseRequestHandler, object):
311
"""A TCP request handler class.
312
Instantiated by IPv6_TCPServer for each request to handle it.
1397
313
Note: This will run in its own forked process."""
1399
315
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
316
logger.debug(u"TCP connection from: %s",
317
unicode(self.client_address))
318
session = gnutls.connection.ClientSession(self.request,
322
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
323
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
325
priority = "SECURE256"
327
gnutls.library.functions.gnutls_priority_set_direct\
328
(session._c_object, priority, None);
332
except gnutls.errors.GNUTLSError, error:
333
logger.debug(u"Handshake failed: %s", error)
334
# Do not run session.bye() here: the session is not
335
# established. Just abandon the request.
338
fpr = fingerprint(peer_certificate(session))
339
except (TypeError, gnutls.errors.GNUTLSError), error:
340
logger.debug(u"Bad certificate: %s", error)
343
logger.debug(u"Fingerprint: %s", fpr)
346
if c.fingerprint == fpr:
349
# Have to check if client.still_valid(), since it is possible
350
# that the client timed out while establishing the GnuTLS
352
if (not client) or (not client.still_valid()):
354
logger.debug(u"Client %(name)s is invalid",
357
logger.debug(u"Client not found for fingerprint: %s",
362
while sent_size < len(client.secret):
363
sent = session.send(client.secret[sent_size:])
364
logger.debug(u"Sent: %d, remaining: %d",
365
sent, len(client.secret)
366
- (sent_size + sent))
371
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
372
"""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
374
options: Command line options
375
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)
377
address_family = socket.AF_INET6
378
def __init__(self, *args, **kwargs):
379
if "options" in kwargs:
380
self.options = kwargs["options"]
381
del kwargs["options"]
382
if "clients" in kwargs:
383
self.clients = kwargs["clients"]
384
del kwargs["clients"]
385
return super(type(self), self).__init__(*args, **kwargs)
1668
386
def server_bind(self):
1669
387
"""This overrides the normal server_bind() function
1670
388
to bind to an interface if one was specified, and also NOT to
1671
389
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",
390
if self.options.interface:
391
if not hasattr(socket, "SO_BINDTODEVICE"):
392
# From /usr/include/asm-i486/socket.h
393
socket.SO_BINDTODEVICE = 25
395
self.socket.setsockopt(socket.SOL_SOCKET,
396
socket.SO_BINDTODEVICE,
397
self.options.interface)
398
except socket.error, error:
399
if error[0] == errno.EPERM:
400
logger.warning(u"No permission to"
401
u" bind to interface %s",
402
self.options.interface)
1694
405
# Only bind(2) the socket if we really need to.
1695
406
if self.server_address[0] or self.server_address[1]:
1696
407
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,
409
self.server_address = (in6addr_any,
1702
410
self.server_address[1])
1703
elif not self.server_address[1]:
411
elif self.server_address[1] is None:
1704
412
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)
414
return super(type(self), self).server_bind()
1835
417
def string_to_delta(interval):
1836
418
"""Parse a string and return a datetime.timedelta
1838
420
>>> string_to_delta('7d')
1839
421
datetime.timedelta(7)
1840
422
>>> string_to_delta('60s')
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()
561
def killme(status = 0):
562
logger.debug("Stopping server with exit status %d", status)
564
if main_loop_started:
570
if __name__ == '__main__':
572
main_loop_started = False
573
parser = OptionParser()
574
parser.add_option("-i", "--interface", type="string",
575
default=None, metavar="IF",
576
help="Bind to interface IF")
577
parser.add_option("-p", "--port", type="int", default=None,
578
help="Port number to receive requests on")
579
parser.add_option("--timeout", type="string", # Parsed later
581
help="Amount of downtime allowed for clients")
582
parser.add_option("--interval", type="string", # Parsed later
584
help="How often to check that a client is up")
585
parser.add_option("--check", action="store_true", default=False,
586
help="Run self-test")
587
parser.add_option("--debug", action="store_true", default=False,
589
(options, args) = parser.parse_args()
1940
591
if options.check:
1942
593
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.
2094
# From the Avahi example code
596
# Parse the time arguments
598
options.timeout = string_to_delta(options.timeout)
600
parser.error("option --timeout: Unparseable time")
602
options.interval = string_to_delta(options.interval)
604
parser.error("option --interval: Unparseable time")
607
defaults = { "checker": "fping -q -- %%(fqdn)s" }
608
client_config = ConfigParser.SafeConfigParser(defaults)
609
#client_config.readfp(open("secrets.conf"), "secrets.conf")
610
client_config.read("mandos-clients.conf")
612
# From the Avahi server example code
2095
613
DBusGMainLoop(set_as_default=True )
2096
614
main_loop = gobject.MainLoop()
2097
615
bus = dbus.SystemBus()
616
server = dbus.Interface(
617
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
618
avahi.DBUS_INTERFACE_SERVER )
2098
619
# 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")
621
debug = options.debug
624
console = logging.StreamHandler()
625
# console.setLevel(logging.DEBUG)
626
console.setFormatter(logging.Formatter\
627
('%(levelname)s: %(message)s'))
628
logger.addHandler(console)
632
def remove_from_clients(client):
633
clients.remove(client)
635
logger.debug(u"No clients left, exiting")
638
clients.update(Set(Client(name=section, options=options,
639
stop_hook = remove_from_clients,
640
**(dict(client_config\
642
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
648
"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()
650
# From the Avahi server example code
651
if not group is None:
654
# End of Avahi example code
656
for client in clients:
657
client.stop_hook = None
2355
660
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
# From the Avahi example code
2385
except dbus.exceptions.DBusException as error:
2386
logger.critical("DBusException: %s", error)
2389
# 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))
2396
logger.debug("Starting main loop")
663
signal.signal(signal.SIGINT, signal.SIG_IGN)
664
signal.signal(signal.SIGHUP, lambda signum, frame: killme())
665
signal.signal(signal.SIGTERM, lambda signum, frame: killme())
667
for client in clients:
670
tcp_server = IPv6_TCPServer((None, options.port),
674
# Find out what random port we got
675
servicePort = tcp_server.socket.getsockname()[1]
676
logger.debug(u"Now listening on port %d", servicePort)
678
if options.interface is not None:
679
serviceInterface = if_nametoindex(options.interface)
681
# From the Avahi server example code
682
server.connect_to_signal("StateChanged", server_state_changed)
684
server_state_changed(server.GetState())
685
except dbus.exceptions.DBusException, error:
686
logger.critical(u"DBusException: %s", error)
688
# End of Avahi example code
690
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
691
lambda *args, **kwargs:
692
tcp_server.handle_request(*args[2:],
695
main_loop_started = True
2398
except AvahiError as error:
2399
logger.critical("AvahiError: %s", error)
2402
697
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
if __name__ == '__main__':