45
14
import gnutls.library.functions
46
15
import gnutls.library.constants
47
16
import gnutls.library.types
48
import ConfigParser as configparser
57
import logging.handlers
63
import cPickle as pickle
64
import multiprocessing
72
28
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))
32
import logging.handlers
34
# logghandler.setFormatter(logging.Formatter('%(levelname)s %(message)s')
36
logger = logging.Logger('mandos')
37
logger.addHandler(logging.handlers.SysLogHandler(facility = logging.handlers.SysLogHandler.LOG_DAEMON))
39
# This variable is used to optionally bind to a specified interface.
40
# It is a global variable to fit in with the other variables from the
41
# Avahi server example code.
42
serviceInterface = avahi.IF_UNSPEC
43
# From the Avahi server example code:
44
serviceName = "Mandos"
45
serviceType = "_mandos._tcp" # http://www.dns-sd.org/ServiceTypes.html
46
servicePort = None # Not known at startup
47
serviceTXT = [] # TXT record for the service
48
domain = "" # Domain to publish on, default to .local
49
host = "" # Host to publish records for, default to localhost
50
group = None #our entry group
51
rename_count = 12 # Counter so we only rename after collisions a
52
# sensible number of times
53
# End of Avahi example code
306
56
class Client(object):
307
57
"""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
59
name: string; from the config file, used in log messages
60
fingerprint: string (40 or 32 hexadecimal digits); used to
61
uniquely identify the client
62
secret: bytestring; sent verbatim (over TLS) to client
63
fqdn: string (FQDN); available for use by the checker command
64
created: datetime.datetime()
65
last_seen: datetime.datetime() or None if not yet seen
66
timeout: datetime.timedelta(); How long from last_seen until
67
this client is invalid
68
interval: datetime.timedelta(); How often to start a new checker
69
stop_hook: If set, called by stop() as stop_hook(self)
70
checker: subprocess.Popen(); a running checker process used
71
to see if the client lives.
72
Is None if no process is running.
73
checker_initiator_tag: a gobject event source tag, or None
74
stop_initiator_tag: - '' -
75
checker_callback_tag: - '' -
76
checker_command: string; External command which is run to check if
77
client lives. %()s expansions are done at
319
78
runtime with vars(self) as dict, so that for
320
79
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
81
_timeout: Real variable for 'timeout'
82
_interval: Real variable for 'interval'
83
_timeout_milliseconds: Used by gobject.timeout_add()
84
_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'
86
def _set_timeout(self, timeout):
87
"Setter function for 'timeout' attribute"
88
self._timeout = timeout
89
self._timeout_milliseconds = ((self.timeout.days
90
* 24 * 60 * 60 * 1000)
91
+ (self.timeout.seconds * 1000)
92
+ (self.timeout.microseconds
94
timeout = property(lambda self: self._timeout,
97
def _set_interval(self, interval):
98
"Setter function for 'interval' attribute"
99
self._interval = interval
100
self._interval_milliseconds = ((self.interval.days
101
* 24 * 60 * 60 * 1000)
102
+ (self.interval.seconds
104
+ (self.interval.microseconds
106
interval = property(lambda self: self._interval,
109
def __init__(self, name=None, options=None, stop_hook=None,
110
fingerprint=None, secret=None, secfile=None, fqdn=None,
111
timeout=None, interval=-1, checker=None):
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"])
113
# Uppercase and remove spaces from fingerprint
114
# for later comparison purposes with return value of
115
# the fingerprint() function
116
self.fingerprint = fingerprint.upper().replace(u" ", u"")
118
self.secret = secret.decode(u"base64")
121
self.secret = sf.read()
124
raise RuntimeError(u"No secret or secfile for client %s"
126
self.fqdn = fqdn # string
127
self.created = datetime.datetime.now()
128
self.last_seen = None
130
timeout = options.timeout
131
self.timeout = timeout
133
interval = options.interval
135
interval = string_to_delta(interval)
136
self.interval = interval
137
self.stop_hook = stop_hook
404
138
self.checker = None
405
139
self.checker_initiator_tag = None
406
self.disable_initiator_tag = None
407
self.expires = datetime.datetime.utcnow() + self.timeout
140
self.stop_initiator_tag = None
408
141
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")
426
for name, t in inspect.getmembers(type(self),
427
lambda obj: isinstance(obj, property)):
428
if not name.startswith("_"):
429
self.client_structure.append(name)
431
# Send notice to process children that client state has changed
432
def send_changedstate(self):
433
with self.changedstate:
434
self.changedstate.notify_all()
437
"""Start this client's checker and timeout hooks"""
438
if getattr(self, "enabled", False):
441
self.send_changedstate()
442
self.expires = datetime.datetime.utcnow() + self.timeout
444
self.last_enabled = datetime.datetime.utcnow()
447
def disable(self, quiet=True):
448
"""Disable this client."""
449
if not getattr(self, "enabled", False):
452
self.send_changedstate()
454
logger.info("Disabling client %s", self.name)
455
if getattr(self, "disable_initiator_tag", False):
456
gobject.source_remove(self.disable_initiator_tag)
457
self.disable_initiator_tag = None
459
if getattr(self, "checker_initiator_tag", False):
460
gobject.source_remove(self.checker_initiator_tag)
461
self.checker_initiator_tag = None
464
# Do not run this again if called by a gobject.timeout_add
470
def init_checker(self):
142
self.check_command = checker
144
"""Start this clients checker and timeout hooks"""
471
145
# Schedule a new checker to be started an 'interval' from now,
472
146
# and every interval from then on.
473
self.checker_initiator_tag = (gobject.timeout_add
474
(self.interval_milliseconds(),
476
# Schedule a disable() when 'timeout' has passed
477
self.disable_initiator_tag = (gobject.timeout_add
478
(self.timeout_milliseconds(),
147
self.checker_initiator_tag = gobject.timeout_add\
148
(self._interval_milliseconds,
480
150
# Also start a new checker *right now*.
481
151
self.start_checker()
484
def checker_callback(self, pid, condition, command):
152
# Schedule a stop() when 'timeout' has passed
153
self.stop_initiator_tag = gobject.timeout_add\
154
(self._timeout_milliseconds,
158
The possibility that this client might be restarted is left
159
open, but not currently used."""
160
logger.debug(u"Stopping client %s", self.name)
162
if self.stop_initiator_tag:
163
gobject.source_remove(self.stop_initiator_tag)
164
self.stop_initiator_tag = None
165
if self.checker_initiator_tag:
166
gobject.source_remove(self.checker_initiator_tag)
167
self.checker_initiator_tag = None
171
# Do not run this again if called by a gobject.timeout_add
174
# Some code duplication here and in stop()
175
if hasattr(self, "stop_initiator_tag") \
176
and self.stop_initiator_tag:
177
gobject.source_remove(self.stop_initiator_tag)
178
self.stop_initiator_tag = None
179
if hasattr(self, "checker_initiator_tag") \
180
and self.checker_initiator_tag:
181
gobject.source_remove(self.checker_initiator_tag)
182
self.checker_initiator_tag = None
184
def checker_callback(self, pid, condition):
485
185
"""The checker has completed, so take appropriate actions."""
486
self.checker_callback_tag = None
488
if os.WIFEXITED(condition):
489
self.last_checker_status = os.WEXITSTATUS(condition)
490
if self.last_checker_status == 0:
491
logger.info("Checker for %(name)s succeeded",
495
logger.info("Checker for %(name)s failed",
498
self.last_checker_status = -1
499
logger.warning("Checker for %(name)s crashed?",
186
now = datetime.datetime.now()
187
if os.WIFEXITED(condition) \
188
and (os.WEXITSTATUS(condition) == 0):
189
logger.debug(u"Checker for %(name)s succeeded",
192
gobject.source_remove(self.stop_initiator_tag)
193
self.stop_initiator_tag = gobject.timeout_add\
194
(self._timeout_milliseconds,
196
if not os.WIFEXITED(condition):
197
logger.warning(u"Checker for %(name)s crashed?",
502
def checked_ok(self, timeout=None):
503
"""Bump up the timeout for this client.
505
This should only be called when the client has been seen,
509
timeout = self.timeout
510
self.last_checked_ok = datetime.datetime.utcnow()
511
if self.disable_initiator_tag is not None:
512
gobject.source_remove(self.disable_initiator_tag)
513
if getattr(self, "enabled", False):
514
self.disable_initiator_tag = (gobject.timeout_add
515
(_timedelta_to_milliseconds
516
(timeout), self.disable))
517
self.expires = datetime.datetime.utcnow() + timeout
519
def need_approval(self):
520
self.last_approval_request = datetime.datetime.utcnow()
200
logger.debug(u"Checker for %(name)s failed",
203
self.checker_callback_tag = None
522
204
def start_checker(self):
523
205
"""Start a new checker subprocess if one is not running.
525
206
If a checker already exists, leave it running and do
527
# The reason for not killing a running checker is that if we
528
# did that, then if a checker (for some reason) started
529
# running slowly and taking more than 'interval' time, the
530
# client would inevitably timeout, since no checker would get
531
# a chance to run to completion. If we instead leave running
532
# checkers alone, the checker would have to take more time
533
# than 'timeout' for the client to be disabled, which is as it
536
# If a checker exists, make sure it is not a zombie
538
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
539
except (AttributeError, OSError) as error:
540
if (isinstance(error, OSError)
541
and error.errno != errno.ECHILD):
545
logger.warning("Checker was a zombie")
546
gobject.source_remove(self.checker_callback_tag)
547
self.checker_callback(pid, status,
548
self.current_checker_command)
549
# Start a new checker if needed
550
208
if self.checker is None:
209
logger.debug(u"Starting checker for %s",
552
# In case checker_command has exactly one % operator
553
command = self.checker_command % self.host
212
command = self.check_command % self.fqdn
554
213
except TypeError:
555
# Escape attributes for the shell
556
escaped_attrs = dict(
558
re.escape(unicode(str(getattr(self, attr, "")),
562
self.runtime_expansions)
214
escaped_attrs = dict((key, re.escape(str(val)))
216
vars(self).iteritems())
565
command = self.checker_command % escaped_attrs
566
except TypeError as error:
567
logger.error('Could not format string "%s":'
568
' %s', self.checker_command, error)
218
command = self.check_command % escaped_attrs
219
except TypeError, error:
220
logger.critical(u'Could not format string "%s": %s',
221
self.check_command, error)
569
222
return True # Try again later
570
self.current_checker_command = command
572
logger.info("Starting checker %r for %s",
574
# We don't need to redirect stdout and stderr, since
575
# in normal mode, that is already done by daemon(),
576
# and in debug mode we don't want to. (Stdin is
577
# always replaced by /dev/null.)
578
self.checker = subprocess.Popen(command,
581
self.checker_callback_tag = (gobject.child_watch_add
583
self.checker_callback,
585
# The checker may have completed before the gobject
586
# watch was added. Check for this.
587
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
589
gobject.source_remove(self.checker_callback_tag)
590
self.checker_callback(pid, status, command)
591
except OSError as error:
592
logger.error("Failed to start subprocess: %s",
224
self.checker = subprocess.\
226
stdout=subprocess.PIPE,
227
close_fds=True, shell=True,
229
self.checker_callback_tag = gobject.\
230
child_watch_add(self.checker.pid,
233
except subprocess.OSError, error:
234
logger.error(u"Failed to start subprocess: %s",
594
236
# Re-run this periodically if run by gobject.timeout_add
597
238
def stop_checker(self):
598
239
"""Force the checker process, if any, to stop."""
599
if self.checker_callback_tag:
600
gobject.source_remove(self.checker_callback_tag)
601
self.checker_callback_tag = None
602
if getattr(self, "checker", None) is None:
240
if not hasattr(self, "checker") or self.checker is None:
604
logger.debug("Stopping checker for %(name)s", vars(self))
606
os.kill(self.checker.pid, signal.SIGTERM)
608
#if self.checker.poll() is None:
609
# os.kill(self.checker.pid, signal.SIGKILL)
610
except OSError as error:
611
if error.errno != errno.ESRCH: # No such process
615
# Encrypts a client secret and stores it in a varible encrypted_secret
616
def encrypt_secret(self, key):
617
# Encryption-key need to be of a specific size, so we hash inputed key
618
hasheng = hashlib.sha256()
620
encryptionkey = hasheng.digest()
622
# Create validation hash so we know at decryption if it was sucessful
623
hasheng = hashlib.sha256()
624
hasheng.update(self.secret)
625
validationhash = hasheng.digest()
628
iv = os.urandom(Crypto.Cipher.AES.block_size)
629
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
630
Crypto.Cipher.AES.MODE_CFB, iv)
631
ciphertext = ciphereng.encrypt(validationhash+self.secret)
632
self.encrypted_secret = (ciphertext, iv)
634
# Decrypt a encrypted client secret
635
def decrypt_secret(self, key):
636
# Decryption-key need to be of a specific size, so we hash inputed key
637
hasheng = hashlib.sha256()
639
encryptionkey = hasheng.digest()
641
# Decrypt encrypted secret
642
ciphertext, iv = self.encrypted_secret
643
ciphereng = Crypto.Cipher.AES.new(encryptionkey,
644
Crypto.Cipher.AES.MODE_CFB, iv)
645
plain = ciphereng.decrypt(ciphertext)
647
# Validate decrypted secret to know if it was succesful
648
hasheng = hashlib.sha256()
649
validationhash = plain[:hasheng.digest_size]
650
secret = plain[hasheng.digest_size:]
651
hasheng.update(secret)
653
# if validation fails, we use key as new secret. Otherwhise, we use
654
# the decrypted secret
655
if hasheng.digest() == validationhash:
659
del self.encrypted_secret
662
def dbus_service_property(dbus_interface, signature="v",
663
access="readwrite", byte_arrays=False):
664
"""Decorators for marking methods of a DBusObjectWithProperties to
665
become properties on the D-Bus.
667
The decorated method will be called with no arguments by "Get"
668
and with one argument by "Set".
670
The parameters, where they are supported, are the same as
671
dbus.service.method, except there is only "signature", since the
672
type from Get() and the type sent to Set() is the same.
674
# Encoding deeply encoded byte arrays is not supported yet by the
675
# "Set" method, so we fail early here:
676
if byte_arrays and signature != "ay":
677
raise ValueError("Byte arrays not supported for non-'ay'"
678
" signature %r" % signature)
680
func._dbus_is_property = True
681
func._dbus_interface = dbus_interface
682
func._dbus_signature = signature
683
func._dbus_access = access
684
func._dbus_name = func.__name__
685
if func._dbus_name.endswith("_dbus_property"):
686
func._dbus_name = func._dbus_name[:-14]
687
func._dbus_get_args_options = {'byte_arrays': byte_arrays }
692
class DBusPropertyException(dbus.exceptions.DBusException):
693
"""A base class for D-Bus property-related exceptions
695
def __unicode__(self):
696
return unicode(str(self))
699
class DBusPropertyAccessException(DBusPropertyException):
700
"""A property's access permissions disallows an operation.
705
class DBusPropertyNotFound(DBusPropertyException):
706
"""An attempt was made to access a non-existing property.
711
class DBusObjectWithProperties(dbus.service.Object):
712
"""A D-Bus object with properties.
714
Classes inheriting from this can use the dbus_service_property
715
decorator to expose methods as D-Bus properties. It exposes the
716
standard Get(), Set(), and GetAll() methods on the D-Bus.
720
def _is_dbus_property(obj):
721
return getattr(obj, "_dbus_is_property", False)
723
def _get_all_dbus_properties(self):
724
"""Returns a generator of (name, attribute) pairs
726
return ((prop.__get__(self)._dbus_name, prop.__get__(self))
727
for cls in self.__class__.__mro__
729
inspect.getmembers(cls, self._is_dbus_property))
731
def _get_dbus_property(self, interface_name, property_name):
732
"""Returns a bound method if one exists which is a D-Bus
733
property with the specified name and interface.
735
for cls in self.__class__.__mro__:
736
for name, value in (inspect.getmembers
737
(cls, self._is_dbus_property)):
738
if (value._dbus_name == property_name
739
and value._dbus_interface == interface_name):
740
return value.__get__(self)
743
raise DBusPropertyNotFound(self.dbus_object_path + ":"
744
+ interface_name + "."
747
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
749
def Get(self, interface_name, property_name):
750
"""Standard D-Bus property Get() method, see D-Bus standard.
752
prop = self._get_dbus_property(interface_name, property_name)
753
if prop._dbus_access == "write":
754
raise DBusPropertyAccessException(property_name)
756
if not hasattr(value, "variant_level"):
758
return type(value)(value, variant_level=value.variant_level+1)
760
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
761
def Set(self, interface_name, property_name, value):
762
"""Standard D-Bus property Set() method, see D-Bus standard.
764
prop = self._get_dbus_property(interface_name, property_name)
765
if prop._dbus_access == "read":
766
raise DBusPropertyAccessException(property_name)
767
if prop._dbus_get_args_options["byte_arrays"]:
768
# The byte_arrays option is not supported yet on
769
# signatures other than "ay".
770
if prop._dbus_signature != "ay":
772
value = dbus.ByteArray(''.join(unichr(byte)
776
@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
777
out_signature="a{sv}")
778
def GetAll(self, interface_name):
779
"""Standard D-Bus property GetAll() method, see D-Bus
782
Note: Will not include properties with access="write".
785
for name, prop in self._get_all_dbus_properties():
787
and interface_name != prop._dbus_interface):
788
# Interface non-empty but did not match
790
# Ignore write-only properties
791
if prop._dbus_access == "write":
794
if not hasattr(value, "variant_level"):
795
properties[name] = value
797
properties[name] = type(value)(value, variant_level=
798
value.variant_level+1)
799
return dbus.Dictionary(properties, signature="sv")
801
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
803
path_keyword='object_path',
804
connection_keyword='connection')
805
def Introspect(self, object_path, connection):
806
"""Standard D-Bus method, overloaded to insert property tags.
808
xmlstring = dbus.service.Object.Introspect(self, object_path,
811
document = xml.dom.minidom.parseString(xmlstring)
812
def make_tag(document, name, prop):
813
e = document.createElement("property")
814
e.setAttribute("name", name)
815
e.setAttribute("type", prop._dbus_signature)
816
e.setAttribute("access", prop._dbus_access)
818
for if_tag in document.getElementsByTagName("interface"):
819
for tag in (make_tag(document, name, prop)
821
in self._get_all_dbus_properties()
822
if prop._dbus_interface
823
== if_tag.getAttribute("name")):
824
if_tag.appendChild(tag)
825
# Add the names to the return values for the
826
# "org.freedesktop.DBus.Properties" methods
827
if (if_tag.getAttribute("name")
828
== "org.freedesktop.DBus.Properties"):
829
for cn in if_tag.getElementsByTagName("method"):
830
if cn.getAttribute("name") == "Get":
831
for arg in cn.getElementsByTagName("arg"):
832
if (arg.getAttribute("direction")
834
arg.setAttribute("name", "value")
835
elif cn.getAttribute("name") == "GetAll":
836
for arg in cn.getElementsByTagName("arg"):
837
if (arg.getAttribute("direction")
839
arg.setAttribute("name", "props")
840
xmlstring = document.toxml("utf-8")
842
except (AttributeError, xml.dom.DOMException,
843
xml.parsers.expat.ExpatError) as error:
844
logger.error("Failed to override Introspection method",
849
def datetime_to_dbus (dt, variant_level=0):
850
"""Convert a UTC datetime.datetime() to a D-Bus type."""
852
return dbus.String("", variant_level = variant_level)
853
return dbus.String(dt.isoformat(),
854
variant_level=variant_level)
856
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
858
"""Applied to an empty subclass of a D-Bus object, this metaclass
859
will add additional D-Bus attributes matching a certain pattern.
861
def __new__(mcs, name, bases, attr):
862
# Go through all the base classes which could have D-Bus
863
# methods, signals, or properties in them
864
for base in (b for b in bases
865
if issubclass(b, dbus.service.Object)):
866
# Go though all attributes of the base class
867
for attrname, attribute in inspect.getmembers(base):
868
# Ignore non-D-Bus attributes, and D-Bus attributes
869
# with the wrong interface name
870
if (not hasattr(attribute, "_dbus_interface")
871
or not attribute._dbus_interface
872
.startswith("se.recompile.Mandos")):
874
# Create an alternate D-Bus interface name based on
876
alt_interface = (attribute._dbus_interface
877
.replace("se.recompile.Mandos",
878
"se.bsnet.fukt.Mandos"))
879
# Is this a D-Bus signal?
880
if getattr(attribute, "_dbus_is_signal", False):
881
# Extract the original non-method function by
883
nonmethod_func = (dict(
884
zip(attribute.func_code.co_freevars,
885
attribute.__closure__))["func"]
887
# Create a new, but exactly alike, function
888
# object, and decorate it to be a new D-Bus signal
889
# with the alternate D-Bus interface name
890
new_function = (dbus.service.signal
892
attribute._dbus_signature)
894
nonmethod_func.func_code,
895
nonmethod_func.func_globals,
896
nonmethod_func.func_name,
897
nonmethod_func.func_defaults,
898
nonmethod_func.func_closure)))
899
# Define a creator of a function to call both the
900
# old and new functions, so both the old and new
901
# signals gets sent when the function is called
902
def fixscope(func1, func2):
903
"""This function is a scope container to pass
904
func1 and func2 to the "call_both" function
905
outside of its arguments"""
906
def call_both(*args, **kwargs):
907
"""This function will emit two D-Bus
908
signals by calling func1 and func2"""
909
func1(*args, **kwargs)
910
func2(*args, **kwargs)
912
# Create the "call_both" function and add it to
914
attr[attrname] = fixscope(attribute,
916
# Is this a D-Bus method?
917
elif getattr(attribute, "_dbus_is_method", False):
918
# Create a new, but exactly alike, function
919
# object. Decorate it to be a new D-Bus method
920
# with the alternate D-Bus interface name. Add it
922
attr[attrname] = (dbus.service.method
924
attribute._dbus_in_signature,
925
attribute._dbus_out_signature)
927
(attribute.func_code,
928
attribute.func_globals,
930
attribute.func_defaults,
931
attribute.func_closure)))
932
# Is this a D-Bus property?
933
elif getattr(attribute, "_dbus_is_property", False):
934
# Create a new, but exactly alike, function
935
# object, and decorate it to be a new D-Bus
936
# property with the alternate D-Bus interface
937
# name. Add it to the class.
938
attr[attrname] = (dbus_service_property
940
attribute._dbus_signature,
941
attribute._dbus_access,
943
._dbus_get_args_options
946
(attribute.func_code,
947
attribute.func_globals,
949
attribute.func_defaults,
950
attribute.func_closure)))
951
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,
242
gobject.source_remove(self.checker_callback_tag)
1059
243
self.checker_callback_tag = None
244
os.kill(self.checker.pid, signal.SIGTERM)
245
if self.checker.poll() is None:
246
os.kill(self.checker.pid, signal.SIGKILL)
1060
247
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))
1388
class ClientDBusTransitional(ClientDBus):
1389
__metaclass__ = AlternateDBusNamesMetaclass
1391
class ClientHandler(socketserver.BaseRequestHandler, object):
1392
"""A class to handle client connections.
1394
Instantiated once for each connection to handle it.
248
def still_valid(self, now=None):
249
"""Has the timeout not yet passed for this client?"""
251
now = datetime.datetime.now()
252
if self.last_seen is None:
253
return now < (self.created + self.timeout)
255
return now < (self.last_seen + self.timeout)
258
def peer_certificate(session):
259
# If not an OpenPGP certificate...
260
if gnutls.library.functions.gnutls_certificate_type_get\
261
(session._c_object) \
262
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP:
263
# ...do the normal thing
264
return session.peer_certificate
265
list_size = ctypes.c_uint()
266
cert_list = gnutls.library.functions.gnutls_certificate_get_peers\
267
(session._c_object, ctypes.byref(list_size))
268
if list_size.value == 0:
271
return ctypes.string_at(cert.data, cert.size)
274
def fingerprint(openpgp):
275
# New empty GnuTLS certificate
276
crt = gnutls.library.types.gnutls_openpgp_crt_t()
277
gnutls.library.functions.gnutls_openpgp_crt_init\
279
# New GnuTLS "datum" with the OpenPGP public key
280
datum = gnutls.library.types.gnutls_datum_t\
281
(ctypes.cast(ctypes.c_char_p(openpgp),
282
ctypes.POINTER(ctypes.c_ubyte)),
283
ctypes.c_uint(len(openpgp)))
284
# Import the OpenPGP public key into the certificate
285
ret = gnutls.library.functions.gnutls_openpgp_crt_import\
288
gnutls.library.constants.GNUTLS_OPENPGP_FMT_RAW)
289
# New buffer for the fingerprint
290
buffer = ctypes.create_string_buffer(20)
291
buffer_length = ctypes.c_size_t()
292
# Get the fingerprint from the certificate into the buffer
293
gnutls.library.functions.gnutls_openpgp_crt_get_fingerprint\
294
(crt, ctypes.byref(buffer), ctypes.byref(buffer_length))
295
# Deinit the certificate
296
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
297
# Convert the buffer to a Python bytestring
298
fpr = ctypes.string_at(buffer, buffer_length.value)
299
# Convert the bytestring to hexadecimal notation
300
hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
304
class tcp_handler(SocketServer.BaseRequestHandler, object):
305
"""A TCP request handler class.
306
Instantiated by IPv6_TCPServer for each request to handle it.
1395
307
Note: This will run in its own forked process."""
1397
309
def handle(self):
1398
with contextlib.closing(self.server.child_pipe) as child_pipe:
1399
logger.info("TCP connection from: %s",
1400
unicode(self.client_address))
1401
logger.debug("Pipe FD: %d",
1402
self.server.child_pipe.fileno())
1404
session = (gnutls.connection
1405
.ClientSession(self.request,
1407
.X509Credentials()))
1409
# Note: gnutls.connection.X509Credentials is really a
1410
# generic GnuTLS certificate credentials object so long as
1411
# no X.509 keys are added to it. Therefore, we can use it
1412
# here despite using OpenPGP certificates.
1414
#priority = ':'.join(("NONE", "+VERS-TLS1.1",
1415
# "+AES-256-CBC", "+SHA1",
1416
# "+COMP-NULL", "+CTYPE-OPENPGP",
1418
# Use a fallback default, since this MUST be set.
1419
priority = self.server.gnutls_priority
1420
if priority is None:
1422
(gnutls.library.functions
1423
.gnutls_priority_set_direct(session._c_object,
1426
# Start communication using the Mandos protocol
1427
# Get protocol number
1428
line = self.request.makefile().readline()
1429
logger.debug("Protocol version: %r", line)
1431
if int(line.strip().split()[0]) > 1:
1433
except (ValueError, IndexError, RuntimeError) as error:
1434
logger.error("Unknown protocol version: %s", error)
1437
# Start GnuTLS connection
1440
except gnutls.errors.GNUTLSError as error:
1441
logger.warning("Handshake failed: %s", error)
1442
# Do not run session.bye() here: the session is not
1443
# established. Just abandon the request.
1445
logger.debug("Handshake succeeded")
1447
approval_required = False
1450
fpr = self.fingerprint(self.peer_certificate
1453
gnutls.errors.GNUTLSError) as error:
1454
logger.warning("Bad certificate: %s", error)
1456
logger.debug("Fingerprint: %s", fpr)
1457
if self.server.use_dbus:
1459
client.NewRequest(str(self.client_address))
1462
client = ProxyClient(child_pipe, fpr,
1463
self.client_address)
1467
if client.approval_delay:
1468
delay = client.approval_delay
1469
client.approvals_pending += 1
1470
approval_required = True
1473
if not client.enabled:
1474
logger.info("Client %s is disabled",
1476
if self.server.use_dbus:
1478
client.Rejected("Disabled")
1481
if client._approved or not client.approval_delay:
1482
#We are approved or approval is disabled
1484
elif client._approved is None:
1485
logger.info("Client %s needs approval",
1487
if self.server.use_dbus:
1489
client.NeedApproval(
1490
client.approval_delay_milliseconds(),
1491
client.approved_by_default)
1493
logger.warning("Client %s was not approved",
1495
if self.server.use_dbus:
1497
client.Rejected("Denied")
1500
#wait until timeout or approved
1501
time = datetime.datetime.now()
1502
client.changedstate.acquire()
1503
(client.changedstate.wait
1504
(float(client._timedelta_to_milliseconds(delay)
1506
client.changedstate.release()
1507
time2 = datetime.datetime.now()
1508
if (time2 - time) >= delay:
1509
if not client.approved_by_default:
1510
logger.warning("Client %s timed out while"
1511
" waiting for approval",
1513
if self.server.use_dbus:
1515
client.Rejected("Approval timed out")
1520
delay -= time2 - time
1523
while sent_size < len(client.secret):
1525
sent = session.send(client.secret[sent_size:])
1526
except gnutls.errors.GNUTLSError as error:
1527
logger.warning("gnutls send failed")
1529
logger.debug("Sent: %d, remaining: %d",
1530
sent, len(client.secret)
1531
- (sent_size + sent))
1534
logger.info("Sending secret to %s", client.name)
1535
# bump the timeout using extended_timeout
1536
client.checked_ok(client.extended_timeout)
1537
if self.server.use_dbus:
1542
if approval_required:
1543
client.approvals_pending -= 1
1546
except gnutls.errors.GNUTLSError as error:
1547
logger.warning("GnuTLS bye failed")
1550
def peer_certificate(session):
1551
"Return the peer's OpenPGP certificate as a bytestring"
1552
# If not an OpenPGP certificate...
1553
if (gnutls.library.functions
1554
.gnutls_certificate_type_get(session._c_object)
1555
!= gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1556
# ...do the normal thing
1557
return session.peer_certificate
1558
list_size = ctypes.c_uint(1)
1559
cert_list = (gnutls.library.functions
1560
.gnutls_certificate_get_peers
1561
(session._c_object, ctypes.byref(list_size)))
1562
if not bool(cert_list) and list_size.value != 0:
1563
raise gnutls.errors.GNUTLSError("error getting peer"
1565
if list_size.value == 0:
1568
return ctypes.string_at(cert.data, cert.size)
1571
def fingerprint(openpgp):
1572
"Convert an OpenPGP bytestring to a hexdigit fingerprint"
1573
# New GnuTLS "datum" with the OpenPGP public key
1574
datum = (gnutls.library.types
1575
.gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1578
ctypes.c_uint(len(openpgp))))
1579
# New empty GnuTLS certificate
1580
crt = gnutls.library.types.gnutls_openpgp_crt_t()
1581
(gnutls.library.functions
1582
.gnutls_openpgp_crt_init(ctypes.byref(crt)))
1583
# Import the OpenPGP public key into the certificate
1584
(gnutls.library.functions
1585
.gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1586
gnutls.library.constants
1587
.GNUTLS_OPENPGP_FMT_RAW))
1588
# Verify the self signature in the key
1589
crtverify = ctypes.c_uint()
1590
(gnutls.library.functions
1591
.gnutls_openpgp_crt_verify_self(crt, 0,
1592
ctypes.byref(crtverify)))
1593
if crtverify.value != 0:
1594
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1595
raise (gnutls.errors.CertificateSecurityError
1597
# New buffer for the fingerprint
1598
buf = ctypes.create_string_buffer(20)
1599
buf_len = ctypes.c_size_t()
1600
# Get the fingerprint from the certificate into the buffer
1601
(gnutls.library.functions
1602
.gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1603
ctypes.byref(buf_len)))
1604
# Deinit the certificate
1605
gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1606
# Convert the buffer to a Python bytestring
1607
fpr = ctypes.string_at(buf, buf_len.value)
1608
# Convert the bytestring to hexadecimal notation
1609
hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
1613
class MultiprocessingMixIn(object):
1614
"""Like socketserver.ThreadingMixIn, but with multiprocessing"""
1615
def sub_process_main(self, request, address):
1617
self.finish_request(request, address)
1619
self.handle_error(request, address)
1620
self.close_request(request)
1622
def process_request(self, request, address):
1623
"""Start a new process to process the request."""
1624
proc = multiprocessing.Process(target = self.sub_process_main,
1631
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1632
""" adds a pipe to the MixIn """
1633
def process_request(self, request, client_address):
1634
"""Overrides and wraps the original process_request().
1636
This function creates a new pipe in self.pipe
1638
parent_pipe, self.child_pipe = multiprocessing.Pipe()
1640
proc = MultiprocessingMixIn.process_request(self, request,
1642
self.child_pipe.close()
1643
self.add_pipe(parent_pipe, proc)
1645
def add_pipe(self, parent_pipe, proc):
1646
"""Dummy function; override as necessary"""
1647
raise NotImplementedError
1650
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1651
socketserver.TCPServer, object):
1652
"""IPv6-capable TCP server. Accepts 'None' as address and/or port
310
logger.debug(u"TCP connection from: %s",
311
unicode(self.client_address))
312
session = gnutls.connection.ClientSession(self.request,
316
#priority = ':'.join(("NONE", "+VERS-TLS1.1", "+AES-256-CBC",
317
# "+SHA1", "+COMP-NULL", "+CTYPE-OPENPGP",
319
priority = "SECURE256"
321
gnutls.library.functions.gnutls_priority_set_direct\
322
(session._c_object, priority, None);
326
except gnutls.errors.GNUTLSError, error:
327
logger.debug(u"Handshake failed: %s", error)
328
# Do not run session.bye() here: the session is not
329
# established. Just abandon the request.
332
fpr = fingerprint(peer_certificate(session))
333
except (TypeError, gnutls.errors.GNUTLSError), error:
334
logger.debug(u"Bad certificate: %s", error)
337
logger.debug(u"Fingerprint: %s", fpr)
340
if c.fingerprint == fpr:
343
# Have to check if client.still_valid(), since it is possible
344
# that the client timed out while establishing the GnuTLS
346
if (not client) or (not client.still_valid()):
348
logger.debug(u"Client %(name)s is invalid",
351
logger.debug(u"Client not found for fingerprint: %s",
356
while sent_size < len(client.secret):
357
sent = session.send(client.secret[sent_size:])
358
logger.debug(u"Sent: %d, remaining: %d",
359
sent, len(client.secret)
360
- (sent_size + sent))
365
class IPv6_TCPServer(SocketServer.ForkingTCPServer, object):
366
"""IPv6 TCP server. Accepts 'None' as address and/or port.
1655
enabled: Boolean; whether this server is activated yet
1656
interface: None or a network interface name (string)
1657
use_ipv6: Boolean; to use IPv6 or not
368
options: Command line options
369
clients: Set() of Client objects
1659
def __init__(self, server_address, RequestHandlerClass,
1660
interface=None, use_ipv6=True):
1661
self.interface = interface
1663
self.address_family = socket.AF_INET6
1664
socketserver.TCPServer.__init__(self, server_address,
1665
RequestHandlerClass)
371
address_family = socket.AF_INET6
372
def __init__(self, *args, **kwargs):
373
if "options" in kwargs:
374
self.options = kwargs["options"]
375
del kwargs["options"]
376
if "clients" in kwargs:
377
self.clients = kwargs["clients"]
378
del kwargs["clients"]
379
return super(type(self), self).__init__(*args, **kwargs)
1666
380
def server_bind(self):
1667
381
"""This overrides the normal server_bind() function
1668
382
to bind to an interface if one was specified, and also NOT to
1669
383
bind to an address or port if they were not specified."""
1670
if self.interface is not None:
1671
if SO_BINDTODEVICE is None:
1672
logger.error("SO_BINDTODEVICE does not exist;"
1673
" cannot bind to interface %s",
1677
self.socket.setsockopt(socket.SOL_SOCKET,
1681
except socket.error as error:
1682
if error[0] == errno.EPERM:
1683
logger.error("No permission to"
1684
" bind to interface %s",
1686
elif error[0] == errno.ENOPROTOOPT:
1687
logger.error("SO_BINDTODEVICE not available;"
1688
" cannot bind to interface %s",
384
if self.options.interface:
385
if not hasattr(socket, "SO_BINDTODEVICE"):
386
# From /usr/include/asm-i486/socket.h
387
socket.SO_BINDTODEVICE = 25
389
self.socket.setsockopt(socket.SOL_SOCKET,
390
socket.SO_BINDTODEVICE,
391
self.options.interface)
392
except socket.error, error:
393
if error[0] == errno.EPERM:
394
logger.warning(u"No permission to"
395
u" bind to interface %s",
396
self.options.interface)
1692
399
# Only bind(2) the socket if we really need to.
1693
400
if self.server_address[0] or self.server_address[1]:
1694
401
if not self.server_address[0]:
1695
if self.address_family == socket.AF_INET6:
1696
any_address = "::" # in6addr_any
1698
any_address = socket.INADDR_ANY
1699
self.server_address = (any_address,
403
self.server_address = (in6addr_any,
1700
404
self.server_address[1])
1701
elif not self.server_address[1]:
405
elif self.server_address[1] is None:
1702
406
self.server_address = (self.server_address[0],
1704
# if self.interface:
1705
# self.server_address = (self.server_address[0],
1710
return socketserver.TCPServer.server_bind(self)
1713
class MandosServer(IPv6_TCPServer):
1717
clients: set of Client objects
1718
gnutls_priority GnuTLS priority string
1719
use_dbus: Boolean; to emit D-Bus signals or not
1721
Assumes a gobject.MainLoop event loop.
1723
def __init__(self, server_address, RequestHandlerClass,
1724
interface=None, use_ipv6=True, clients=None,
1725
gnutls_priority=None, use_dbus=True):
1726
self.enabled = False
1727
self.clients = clients
1728
if self.clients is None:
1730
self.use_dbus = use_dbus
1731
self.gnutls_priority = gnutls_priority
1732
IPv6_TCPServer.__init__(self, server_address,
1733
RequestHandlerClass,
1734
interface = interface,
1735
use_ipv6 = use_ipv6)
1736
def server_activate(self):
1738
return socketserver.TCPServer.server_activate(self)
1743
def add_pipe(self, parent_pipe, proc):
1744
# Call "handle_ipc" for both data and EOF events
1745
gobject.io_add_watch(parent_pipe.fileno(),
1746
gobject.IO_IN | gobject.IO_HUP,
1747
functools.partial(self.handle_ipc,
1752
def handle_ipc(self, source, condition, parent_pipe=None,
1753
proc = None, client_object=None):
1755
gobject.IO_IN: "IN", # There is data to read.
1756
gobject.IO_OUT: "OUT", # Data can be written (without
1758
gobject.IO_PRI: "PRI", # There is urgent data to read.
1759
gobject.IO_ERR: "ERR", # Error condition.
1760
gobject.IO_HUP: "HUP" # Hung up (the connection has been
1761
# broken, usually for pipes and
1764
conditions_string = ' | '.join(name
1766
condition_names.iteritems()
1767
if cond & condition)
1768
# error, or the other end of multiprocessing.Pipe has closed
1769
if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1770
# Wait for other process to exit
1774
# Read a request from the child
1775
request = parent_pipe.recv()
1776
command = request[0]
1778
if command == 'init':
1780
address = request[2]
1782
for c in self.clients.itervalues():
1783
if c.fingerprint == fpr:
1787
logger.info("Client not found for fingerprint: %s, ad"
1788
"dress: %s", fpr, address)
1791
mandos_dbus_service.ClientNotFound(fpr,
1793
parent_pipe.send(False)
1796
gobject.io_add_watch(parent_pipe.fileno(),
1797
gobject.IO_IN | gobject.IO_HUP,
1798
functools.partial(self.handle_ipc,
1804
parent_pipe.send(True)
1805
# remove the old hook in favor of the new above hook on
1808
if command == 'funcall':
1809
funcname = request[1]
1813
parent_pipe.send(('data', getattr(client_object,
1817
if command == 'getattr':
1818
attrname = request[1]
1819
if callable(client_object.__getattribute__(attrname)):
1820
parent_pipe.send(('function',))
1822
parent_pipe.send(('data', client_object
1823
.__getattribute__(attrname)))
1825
if command == 'setattr':
1826
attrname = request[1]
1828
setattr(client_object, attrname, value)
408
return super(type(self), self).server_bind()
1833
411
def string_to_delta(interval):
1834
412
"""Parse a string and return a datetime.timedelta
1836
414
>>> string_to_delta('7d')
1837
415
datetime.timedelta(7)
1838
416
>>> string_to_delta('60s')
1841
419
datetime.timedelta(0, 3600)
1842
420
>>> string_to_delta('24h')
1843
421
datetime.timedelta(1)
1844
>>> string_to_delta('1w')
422
>>> string_to_delta(u'1w')
1845
423
datetime.timedelta(7)
1846
>>> string_to_delta('5m 30s')
1847
datetime.timedelta(0, 330)
1849
timevalue = datetime.timedelta(0)
1850
for s in interval.split():
1852
suffix = unicode(s[-1])
1855
delta = datetime.timedelta(value)
1857
delta = datetime.timedelta(0, value)
1859
delta = datetime.timedelta(0, 0, 0, 0, value)
1861
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1863
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1865
raise ValueError("Unknown suffix %r" % suffix)
1866
except (ValueError, IndexError) as e:
1867
raise ValueError(*(e.args))
1872
def daemon(nochdir = False, noclose = False):
1873
"""See daemon(3). Standard BSD Unix function.
1875
This should really exist as os.daemon, but it doesn't (yet)."""
1884
# Close all standard open file descriptors
1885
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1886
if not stat.S_ISCHR(os.fstat(null).st_mode):
1887
raise OSError(errno.ENODEV,
1888
"%s not a character device"
1890
os.dup2(null, sys.stdin.fileno())
1891
os.dup2(null, sys.stdout.fileno())
1892
os.dup2(null, sys.stderr.fileno())
1899
##################################################################
1900
# Parsing of options, both command line and config file
1902
parser = argparse.ArgumentParser()
1903
parser.add_argument("-v", "--version", action="version",
1904
version = "%%(prog)s %s" % version,
1905
help="show version number and exit")
1906
parser.add_argument("-i", "--interface", metavar="IF",
1907
help="Bind to interface IF")
1908
parser.add_argument("-a", "--address",
1909
help="Address to listen for requests on")
1910
parser.add_argument("-p", "--port", type=int,
1911
help="Port number to receive requests on")
1912
parser.add_argument("--check", action="store_true",
1913
help="Run self-test")
1914
parser.add_argument("--debug", action="store_true",
1915
help="Debug mode; run in foreground and log"
1917
parser.add_argument("--debuglevel", metavar="LEVEL",
1918
help="Debug level for stdout output")
1919
parser.add_argument("--priority", help="GnuTLS"
1920
" priority string (see GnuTLS documentation)")
1921
parser.add_argument("--servicename",
1922
metavar="NAME", help="Zeroconf service name")
1923
parser.add_argument("--configdir",
1924
default="/etc/mandos", metavar="DIR",
1925
help="Directory to search for configuration"
1927
parser.add_argument("--no-dbus", action="store_false",
1928
dest="use_dbus", help="Do not provide D-Bus"
1929
" system bus interface")
1930
parser.add_argument("--no-ipv6", action="store_false",
1931
dest="use_ipv6", help="Do not use IPv6")
1932
parser.add_argument("--no-restore", action="store_false",
1933
dest="restore", help="Do not restore stored state",
1936
options = parser.parse_args()
426
suffix=unicode(interval[-1])
427
value=int(interval[:-1])
429
delta = datetime.timedelta(value)
431
delta = datetime.timedelta(0, value)
433
delta = datetime.timedelta(0, 0, 0, 0, value)
435
delta = datetime.timedelta(0, 0, 0, 0, 0, value)
437
delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
440
except (ValueError, IndexError):
446
"""From the Avahi server example code"""
447
global group, serviceName, serviceType, servicePort, serviceTXT, \
450
group = dbus.Interface(
451
bus.get_object( avahi.DBUS_NAME,
452
server.EntryGroupNew()),
453
avahi.DBUS_INTERFACE_ENTRY_GROUP)
454
group.connect_to_signal('StateChanged',
455
entry_group_state_changed)
456
logger.debug(u"Adding service '%s' of type '%s' ...",
457
serviceName, serviceType)
460
serviceInterface, # interface
461
avahi.PROTO_INET6, # protocol
462
dbus.UInt32(0), # flags
463
serviceName, serviceType,
465
dbus.UInt16(servicePort),
466
avahi.string_array_to_txt_array(serviceTXT))
470
def remove_service():
471
"""From the Avahi server example code"""
474
if not group is None:
478
def server_state_changed(state):
479
"""From the Avahi server example code"""
480
if state == avahi.SERVER_COLLISION:
481
logger.warning(u"Server name collision")
483
elif state == avahi.SERVER_RUNNING:
487
def entry_group_state_changed(state, error):
488
"""From the Avahi server example code"""
489
global serviceName, server, rename_count
491
logger.debug(u"state change: %i", state)
493
if state == avahi.ENTRY_GROUP_ESTABLISHED:
494
logger.debug(u"Service established.")
495
elif state == avahi.ENTRY_GROUP_COLLISION:
497
rename_count = rename_count - 1
499
name = server.GetAlternativeServiceName(name)
500
logger.warning(u"Service name collision, "
501
u"changing name to '%s' ...", name)
506
logger.error(u"No suitable service name found "
507
u"after %i retries, exiting.",
510
elif state == avahi.ENTRY_GROUP_FAILURE:
511
logger.error(u"Error in group state changed %s",
517
def if_nametoindex(interface):
518
"""Call the C function if_nametoindex()"""
520
libc = ctypes.cdll.LoadLibrary("libc.so.6")
521
return libc.if_nametoindex(interface)
522
except (OSError, AttributeError):
523
if "struct" not in sys.modules:
525
if "fcntl" not in sys.modules:
527
SIOCGIFINDEX = 0x8933 # From /usr/include/linux/sockios.h
529
ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
530
struct.pack("16s16x", interface))
532
interface_index = struct.unpack("I", ifreq[16:20])[0]
533
return interface_index
536
if __name__ == '__main__':
537
parser = OptionParser()
538
parser.add_option("-i", "--interface", type="string",
539
default=None, metavar="IF",
540
help="Bind to interface IF")
541
parser.add_option("--cert", type="string", default="cert.pem",
543
help="Public key certificate PEM file to use")
544
parser.add_option("--key", type="string", default="key.pem",
546
help="Private key PEM file to use")
547
parser.add_option("--ca", type="string", default="ca.pem",
549
help="Certificate Authority certificate PEM file to use")
550
parser.add_option("--crl", type="string", default="crl.pem",
552
help="Certificate Revokation List PEM file to use")
553
parser.add_option("-p", "--port", type="int", default=None,
554
help="Port number to receive requests on")
555
parser.add_option("--timeout", type="string", # Parsed later
557
help="Amount of downtime allowed for clients")
558
parser.add_option("--interval", type="string", # Parsed later
560
help="How often to check that a client is up")
561
parser.add_option("--check", action="store_true", default=False,
562
help="Run self-test")
563
parser.add_option("--debug", action="store_true", default=False,
565
(options, args) = parser.parse_args()
1938
567
if options.check:
1940
569
doctest.testmod()
1943
# Default values for config file for server-global settings
1944
server_defaults = { "interface": "",
1949
"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1950
"servicename": "Mandos",
1956
# Parse config file for server-global settings
1957
server_config = configparser.SafeConfigParser(server_defaults)
1959
server_config.read(os.path.join(options.configdir,
1961
# Convert the SafeConfigParser object to a dict
1962
server_settings = server_config.defaults()
1963
# Use the appropriate methods on the non-string config options
1964
for option in ("debug", "use_dbus", "use_ipv6"):
1965
server_settings[option] = server_config.getboolean("DEFAULT",
1967
if server_settings["port"]:
1968
server_settings["port"] = server_config.getint("DEFAULT",
1972
# Override the settings from the config file with command line
1974
for option in ("interface", "address", "port", "debug",
1975
"priority", "servicename", "configdir",
1976
"use_dbus", "use_ipv6", "debuglevel", "restore"):
1977
value = getattr(options, option)
1978
if value is not None:
1979
server_settings[option] = value
1981
# Force all strings to be unicode
1982
for option in server_settings.keys():
1983
if type(server_settings[option]) is str:
1984
server_settings[option] = unicode(server_settings[option])
1985
# Now we have our good server settings in "server_settings"
1987
##################################################################
1990
debug = server_settings["debug"]
1991
debuglevel = server_settings["debuglevel"]
1992
use_dbus = server_settings["use_dbus"]
1993
use_ipv6 = server_settings["use_ipv6"]
1996
initlogger(logging.DEBUG)
2001
level = getattr(logging, debuglevel.upper())
2004
if server_settings["servicename"] != "Mandos":
2005
syslogger.setFormatter(logging.Formatter
2006
('Mandos (%s) [%%(process)d]:'
2007
' %%(levelname)s: %%(message)s'
2008
% server_settings["servicename"]))
2010
# Parse config file with clients
2011
client_defaults = { "timeout": "5m",
2012
"extended_timeout": "15m",
2014
"checker": "fping -q -- %%(host)s",
2016
"approval_delay": "0s",
2017
"approval_duration": "1s",
2019
client_config = configparser.SafeConfigParser(client_defaults)
2020
client_config.read(os.path.join(server_settings["configdir"],
2023
global mandos_dbus_service
2024
mandos_dbus_service = None
2026
tcp_server = MandosServer((server_settings["address"],
2027
server_settings["port"]),
2029
interface=(server_settings["interface"]
2033
server_settings["priority"],
2036
pidfilename = "/var/run/mandos.pid"
2038
pidfile = open(pidfilename, "w")
2040
logger.error("Could not open file %r", pidfilename)
2043
uid = pwd.getpwnam("_mandos").pw_uid
2044
gid = pwd.getpwnam("_mandos").pw_gid
2047
uid = pwd.getpwnam("mandos").pw_uid
2048
gid = pwd.getpwnam("mandos").pw_gid
2051
uid = pwd.getpwnam("nobody").pw_uid
2052
gid = pwd.getpwnam("nobody").pw_gid
2059
except OSError as error:
2060
if error[0] != errno.EPERM:
2064
# Enable all possible GnuTLS debugging
2066
# "Use a log level over 10 to enable all debugging options."
2068
gnutls.library.functions.gnutls_global_set_log_level(11)
2070
@gnutls.library.types.gnutls_log_func
2071
def debug_gnutls(level, string):
2072
logger.debug("GnuTLS: %s", string[:-1])
2074
(gnutls.library.functions
2075
.gnutls_global_set_log_function(debug_gnutls))
2077
# Redirect stdin so all checkers get /dev/null
2078
null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
2079
os.dup2(null, sys.stdin.fileno())
2083
# No console logging
2084
logger.removeHandler(console)
2086
# Need to fork before connecting to D-Bus
2088
# Close all input and output, do double fork, etc.
2092
# From the Avahi example code
572
# Parse the time arguments
574
options.timeout = string_to_delta(options.timeout)
576
parser.error("option --timeout: Unparseable time")
578
options.interval = string_to_delta(options.interval)
580
parser.error("option --interval: Unparseable time")
583
defaults = { "checker": "sleep 1; fping -q -- %%(fqdn)s" }
584
client_config = ConfigParser.SafeConfigParser(defaults)
585
#client_config.readfp(open("secrets.conf"), "secrets.conf")
586
client_config.read("mandos-clients.conf")
588
# From the Avahi server example code
2093
589
DBusGMainLoop(set_as_default=True )
2094
590
main_loop = gobject.MainLoop()
2095
591
bus = dbus.SystemBus()
2096
# End of Avahi example code
2099
bus_name = dbus.service.BusName("se.recompile.Mandos",
2100
bus, do_not_queue=True)
2101
old_bus_name = (dbus.service.BusName
2102
("se.bsnet.fukt.Mandos", bus,
2104
except dbus.exceptions.NameExistsException as e:
2105
logger.error(unicode(e) + ", disabling D-Bus")
2107
server_settings["use_dbus"] = False
2108
tcp_server.use_dbus = False
2109
protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
2110
service = AvahiServiceToSyslog(name =
2111
server_settings["servicename"],
2112
servicetype = "_mandos._tcp",
2113
protocol = protocol, bus = bus)
2114
if server_settings["interface"]:
2115
service.interface = (if_nametoindex
2116
(str(server_settings["interface"])))
2118
global multiprocessing_manager
2119
multiprocessing_manager = multiprocessing.Manager()
2121
client_class = Client
2123
client_class = functools.partial(ClientDBusTransitional,
2126
special_settings = {
2127
# Some settings need to be accessd by special methods;
2128
# booleans need .getboolean(), etc. Here is a list of them:
2129
"approved_by_default":
2131
client_config.getboolean(section, "approved_by_default"),
2133
# Construct a new dict of client settings of this form:
2134
# { client_name: {setting_name: value, ...}, ...}
2135
# with exceptions for any special settings as defined above
2136
client_settings = dict((clientname,
2138
(value if setting not in special_settings
2139
else special_settings[setting](clientname)))
2140
for setting, value in client_config.items(clientname)))
2141
for clientname in client_config.sections())
2143
old_client_settings = {}
2146
# Get client data and settings from last running state.
2147
if server_settings["restore"]:
2149
with open(stored_state_path, "rb") as stored_state:
2150
clients_data, old_client_settings = pickle.load(stored_state)
2151
os.remove(stored_state_path)
2152
except IOError as e:
2153
logger.warning("Could not load persistant state: {0}".format(e))
2154
if e.errno != errno.ENOENT:
2157
for client in clients_data:
2158
client_name = client["name"]
2160
# Decide which value to use after restoring saved state.
2161
# We have three different values: Old config file,
2162
# new config file, and saved state.
2163
# New config value takes precedence if it differs from old
2164
# config value, otherwise use saved state.
2165
for name, value in client_settings[client_name].items():
2167
# For each value in new config, check if it differs
2168
# from the old config value (Except for the "secret"
2170
if name != "secret" and value != old_client_settings[client_name][name]:
2171
setattr(client, name, value)
2175
# Clients who has passed its expire date, can still be enabled if its
2176
# last checker was sucessful. Clients who checkers failed before we
2177
# stored it state is asumed to had failed checker during downtime.
2178
if client["enabled"] and client["last_checked_ok"]:
2179
if ((datetime.datetime.utcnow() - client["last_checked_ok"])
2180
> client["interval"]):
2181
if client["last_checker_status"] != 0:
2182
client["enabled"] = False
2184
client["expires"] = datetime.datetime.utcnow() + client["timeout"]
2186
client["changedstate"] = (multiprocessing_manager
2187
.Condition(multiprocessing_manager
2190
new_client = ClientDBusTransitional.__new__(ClientDBusTransitional)
2191
tcp_server.clients[client_name] = new_client
2192
new_client.bus = bus
2193
for name, value in client.iteritems():
2194
setattr(new_client, name, value)
2195
client_object_name = unicode(client_name).translate(
2196
{ord("."): ord("_"),
2197
ord("-"): ord("_")})
2198
new_client.dbus_object_path = (dbus.ObjectPath
2199
("/clients/" + client_object_name))
2200
DBusObjectWithProperties.__init__(new_client,
2202
new_client.dbus_object_path)
2204
tcp_server.clients[client_name] = Client.__new__(Client)
2205
for name, value in client.iteritems():
2206
setattr(tcp_server.clients[client_name], name, value)
2208
tcp_server.clients[client_name].decrypt_secret(
2209
client_settings[client_name]["secret"])
2211
# Create/remove clients based on new changes made to config
2212
for clientname in set(old_client_settings) - set(client_settings):
2213
del tcp_server.clients[clientname]
2214
for clientname in set(client_settings) - set(old_client_settings):
2215
tcp_server.clients[clientname] = (client_class(name = clientname,
2221
if not tcp_server.clients:
2222
logger.warning("No clients defined")
2228
pidfile.write(str(pid) + "\n".encode("utf-8"))
2231
logger.error("Could not write to file %r with PID %d",
2234
# "pidfile" was never created
2238
signal.signal(signal.SIGINT, signal.SIG_IGN)
2240
signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2241
signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2244
class MandosDBusService(dbus.service.Object):
2245
"""A D-Bus proxy object"""
2247
dbus.service.Object.__init__(self, bus, "/")
2248
_interface = "se.recompile.Mandos"
2250
@dbus.service.signal(_interface, signature="o")
2251
def ClientAdded(self, objpath):
2255
@dbus.service.signal(_interface, signature="ss")
2256
def ClientNotFound(self, fingerprint, address):
2260
@dbus.service.signal(_interface, signature="os")
2261
def ClientRemoved(self, objpath, name):
2265
@dbus.service.method(_interface, out_signature="ao")
2266
def GetAllClients(self):
2268
return dbus.Array(c.dbus_object_path
2270
tcp_server.clients.itervalues())
2272
@dbus.service.method(_interface,
2273
out_signature="a{oa{sv}}")
2274
def GetAllClientsWithProperties(self):
2276
return dbus.Dictionary(
2277
((c.dbus_object_path, c.GetAll(""))
2278
for c in tcp_server.clients.itervalues()),
2281
@dbus.service.method(_interface, in_signature="o")
2282
def RemoveClient(self, object_path):
2284
for c in tcp_server.clients.itervalues():
2285
if c.dbus_object_path == object_path:
2286
del tcp_server.clients[c.name]
2287
c.remove_from_connection()
2288
# Don't signal anything except ClientRemoved
2289
c.disable(quiet=True)
2291
self.ClientRemoved(object_path, c.name)
2293
raise KeyError(object_path)
2297
class MandosDBusServiceTransitional(MandosDBusService):
2298
__metaclass__ = AlternateDBusNamesMetaclass
2299
mandos_dbus_service = MandosDBusServiceTransitional()
2302
"Cleanup function; run on exit"
2305
multiprocessing.active_children()
2306
if not (tcp_server.clients or client_settings):
2309
# Store client before exiting. Secrets are encrypted with key based
2310
# on what config file has. If config file is removed/edited, old
2311
# secret will thus be unrecovable.
2313
for client in tcp_server.clients.itervalues():
2314
client.encrypt_secret(client_settings[client.name]["secret"])
2318
# A list of attributes that will not be stored when shuting down.
2319
exclude = set(("bus", "changedstate", "secret"))
2320
for name, typ in inspect.getmembers(dbus.service.Object):
2323
client_dict["encrypted_secret"] = client.encrypted_secret
2324
for attr in client.client_structure:
2325
if attr not in exclude:
2326
client_dict[attr] = getattr(client, attr)
2328
clients.append(client_dict)
2329
del client_settings[client.name]["secret"]
2332
with os.fdopen(os.open(stored_state_path, os.O_CREAT|os.O_WRONLY|os.O_TRUNC, 0600), "wb") as stored_state:
2333
pickle.dump((clients, client_settings), stored_state)
2334
except IOError as e:
2335
logger.warning("Could not save persistant state: {0}".format(e))
2336
if e.errno != errno.ENOENT:
2339
# Delete all clients, and settings from config
2340
while tcp_server.clients:
2341
name, client = tcp_server.clients.popitem()
2343
client.remove_from_connection()
2344
# Don't signal anything except ClientRemoved
2345
client.disable(quiet=True)
2348
mandos_dbus_service.ClientRemoved(client
2351
client_settings.clear()
2353
atexit.register(cleanup)
2355
for client in tcp_server.clients.itervalues():
2358
mandos_dbus_service.ClientAdded(client.dbus_object_path)
2359
# Need to initiate checking of clients
2361
client.init_checker()
2365
tcp_server.server_activate()
2367
# Find out what port we got
2368
service.port = tcp_server.socket.getsockname()[1]
2370
logger.info("Now listening on address %r, port %d,"
2371
" flowinfo %d, scope_id %d"
2372
% tcp_server.socket.getsockname())
2374
logger.info("Now listening on address %r, port %d"
2375
% tcp_server.socket.getsockname())
2377
#service.interface = tcp_server.socket.getsockname()[3]
592
server = dbus.Interface(
593
bus.get_object( avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER ),
594
avahi.DBUS_INTERFACE_SERVER )
595
# End of Avahi example code
597
debug = options.debug
600
def remove_from_clients(client):
601
clients.remove(client)
603
logger.debug(u"No clients left, exiting")
606
clients.update(Set(Client(name=section, options=options,
607
stop_hook = remove_from_clients,
608
**(dict(client_config\
610
for section in client_config.sections()))
611
for client in clients:
614
tcp_server = IPv6_TCPServer((None, options.port),
618
# Find out what random port we got
619
servicePort = tcp_server.socket.getsockname()[1]
620
logger.debug(u"Now listening on port %d", servicePort)
622
if options.interface is not None:
623
serviceInterface = if_nametoindex(options.interface)
625
# From the Avahi server example code
626
server.connect_to_signal("StateChanged", server_state_changed)
627
server_state_changed(server.GetState())
628
# End of Avahi example code
630
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
631
lambda *args, **kwargs:
632
tcp_server.handle_request(*args[2:],
2380
# From the Avahi example code
2383
except dbus.exceptions.DBusException as error:
2384
logger.critical("DBusException: %s", error)
2387
# End of Avahi example code
2389
gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
2390
lambda *args, **kwargs:
2391
(tcp_server.handle_request
2392
(*args[2:], **kwargs) or True))
2394
logger.debug("Starting main loop")
2396
except AvahiError as error:
2397
logger.critical("AvahiError: %s", error)
2400
636
except KeyboardInterrupt:
2402
print("", file=sys.stderr)
2403
logger.debug("Server received KeyboardInterrupt")
2404
logger.debug("Server exiting")
2405
# Must run before the D-Bus bus name gets deregistered
2409
if __name__ == '__main__':
641
# From the Avahi server example code
642
if not group is None:
644
# End of Avahi example code
646
for client in clients:
647
client.stop_hook = None