/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2010-09-12 03:00:40 UTC
  • Revision ID: teddy@fukt.bsnet.se-20100912030040-b0uopyennste9fdh
Documentation changes:

* DBUS-API: New file documenting the server D-Bus interface.

* clients.conf: Add examples of new approval settings.

* debian/mandos.docs: Added "DBUS-API".

* mandos-clients.conf.xml (OPTIONS): Added "approved_by_default",
                                     "approval_delay", and
                                     "approval_duration".
* mandos.xml (D-BUS INTERFACE): Refer to the "DBUS-API" file.
  (BUGS): Remove mention of lack of a remote query interface.

Show diffs side-by-side

added added

removed removed

Lines of Context:
11
11
# "AvahiService" class, and some lines in "main".
12
12
13
13
# Everything else is
14
 
# Copyright © 2008-2011 Teddy Hogeborn
15
 
# Copyright © 2008-2011 Björn Påhlsson
 
14
# Copyright © 2008,2009 Teddy Hogeborn
 
15
# Copyright © 2008,2009 Björn Påhlsson
16
16
17
17
# This program is free software: you can redistribute it and/or modify
18
18
# it under the terms of the GNU General Public License as published by
28
28
# along with this program.  If not, see
29
29
# <http://www.gnu.org/licenses/>.
30
30
31
 
# Contact the authors at <mandos@recompile.se>.
 
31
# Contact the authors at <mandos@fukt.bsnet.se>.
32
32
33
33
 
34
 
from __future__ import (division, absolute_import, print_function,
35
 
                        unicode_literals)
 
34
from __future__ import division, with_statement, absolute_import
36
35
 
37
36
import SocketServer as socketserver
38
37
import socket
39
 
import argparse
 
38
import optparse
40
39
import datetime
41
40
import errno
42
41
import gnutls.crypto
62
61
import functools
63
62
import cPickle as pickle
64
63
import multiprocessing
65
 
import types
66
 
import hashlib
67
64
 
68
65
import dbus
69
66
import dbus.service
74
71
import ctypes.util
75
72
import xml.dom.minidom
76
73
import inspect
77
 
import Crypto.Cipher.AES
78
74
 
79
75
try:
80
76
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
85
81
        SO_BINDTODEVICE = None
86
82
 
87
83
 
88
 
version = "1.4.1"
89
 
stored_state_path = "/var/lib/mandos/clients.pickle"
 
84
version = "1.0.14"
90
85
 
91
 
logger = logging.getLogger()
 
86
#logger = logging.getLogger(u'mandos')
 
87
logger = logging.Logger(u'mandos')
92
88
syslogger = (logging.handlers.SysLogHandler
93
89
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
94
 
              address = str("/dev/log")))
95
 
 
96
 
try:
97
 
    if_nametoindex = (ctypes.cdll.LoadLibrary
98
 
                      (ctypes.util.find_library("c"))
99
 
                      .if_nametoindex)
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"),
107
 
                                            interface))
108
 
        interface_index = struct.unpack(str("I"),
109
 
                                        ifreq[16:20])[0]
110
 
        return interface_index
111
 
 
112
 
 
113
 
def initlogger(level=logging.WARNING):
114
 
    """init logger and add loglevel"""
115
 
    
116
 
    syslogger.setFormatter(logging.Formatter
117
 
                           ('Mandos [%(process)d]: %(levelname)s:'
118
 
                            ' %(message)s'))
119
 
    logger.addHandler(syslogger)
120
 
    
121
 
    console = logging.StreamHandler()
122
 
    console.setFormatter(logging.Formatter('%(asctime)s %(name)s'
123
 
                                           ' [%(process)d]:'
124
 
                                           ' %(levelname)s:'
125
 
                                           ' %(message)s'))
126
 
    logger.addHandler(console)
127
 
    logger.setLevel(level)
128
 
 
 
90
              address = "/dev/log"))
 
91
syslogger.setFormatter(logging.Formatter
 
92
                       (u'Mandos [%(process)d]: %(levelname)s:'
 
93
                        u' %(message)s'))
 
94
logger.addHandler(syslogger)
 
95
 
 
96
console = logging.StreamHandler()
 
97
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
 
98
                                       u' %(levelname)s:'
 
99
                                       u' %(message)s'))
 
100
logger.addHandler(console)
129
101
 
130
102
class AvahiError(Exception):
131
103
    def __init__(self, value, *args, **kwargs):
147
119
    Attributes:
148
120
    interface: integer; avahi.IF_UNSPEC or an interface index.
149
121
               Used to optionally bind to the specified interface.
150
 
    name: string; Example: 'Mandos'
151
 
    type: string; Example: '_mandos._tcp'.
 
122
    name: string; Example: u'Mandos'
 
123
    type: string; Example: u'_mandos._tcp'.
152
124
                  See <http://www.dns-sd.org/ServiceTypes.html>
153
125
    port: integer; what port to announce
154
126
    TXT: list of strings; TXT record for the service
163
135
    """
164
136
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
165
137
                 servicetype = None, port = None, TXT = None,
166
 
                 domain = "", host = "", max_renames = 32768,
 
138
                 domain = u"", host = u"", max_renames = 32768,
167
139
                 protocol = avahi.PROTO_UNSPEC, bus = None):
168
140
        self.interface = interface
169
141
        self.name = name
178
150
        self.group = None       # our entry group
179
151
        self.server = None
180
152
        self.bus = bus
181
 
        self.entry_group_state_changed_match = None
182
153
    def rename(self):
183
154
        """Derived from the Avahi example code"""
184
155
        if self.rename_count >= self.max_renames:
185
 
            logger.critical("No suitable Zeroconf service name found"
186
 
                            " after %i retries, exiting.",
 
156
            logger.critical(u"No suitable Zeroconf service name found"
 
157
                            u" after %i retries, exiting.",
187
158
                            self.rename_count)
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 ...",
 
159
            raise AvahiServiceError(u"Too many renames")
 
160
        self.name = unicode(self.server.GetAlternativeServiceName(self.name))
 
161
        logger.info(u"Changing Zeroconf service name to %r ...",
192
162
                    self.name)
 
163
        syslogger.setFormatter(logging.Formatter
 
164
                               (u'Mandos (%s) [%%(process)d]:'
 
165
                                u' %%(levelname)s: %%(message)s'
 
166
                                % self.name))
193
167
        self.remove()
194
168
        try:
195
169
            self.add()
196
 
        except dbus.exceptions.DBusException as error:
197
 
            logger.critical("DBusException: %s", error)
 
170
        except dbus.exceptions.DBusException, error:
 
171
            logger.critical(u"DBusException: %s", error)
198
172
            self.cleanup()
199
173
            os._exit(1)
200
174
        self.rename_count += 1
201
175
    def remove(self):
202
176
        """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
177
        if self.group is not None:
207
178
            self.group.Reset()
208
179
    def add(self):
209
180
        """Derived from the Avahi example code"""
210
 
        self.remove()
211
181
        if self.group is None:
212
182
            self.group = dbus.Interface(
213
183
                self.bus.get_object(avahi.DBUS_NAME,
214
184
                                    self.server.EntryGroupNew()),
215
185
                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' ...",
 
186
            self.group.connect_to_signal('StateChanged',
 
187
                                         self
 
188
                                         .entry_group_state_changed)
 
189
        logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
220
190
                     self.name, self.type)
221
191
        self.group.AddService(
222
192
            self.interface,
229
199
        self.group.Commit()
230
200
    def entry_group_state_changed(self, state, error):
231
201
        """Derived from the Avahi example code"""
232
 
        logger.debug("Avahi entry group state change: %i", state)
 
202
        logger.debug(u"Avahi entry group state change: %i", state)
233
203
        
234
204
        if state == avahi.ENTRY_GROUP_ESTABLISHED:
235
 
            logger.debug("Zeroconf service established.")
 
205
            logger.debug(u"Zeroconf service established.")
236
206
        elif state == avahi.ENTRY_GROUP_COLLISION:
237
 
            logger.info("Zeroconf service name collision.")
 
207
            logger.warning(u"Zeroconf service name collision.")
238
208
            self.rename()
239
209
        elif state == avahi.ENTRY_GROUP_FAILURE:
240
 
            logger.critical("Avahi: Error in group state changed %s",
 
210
            logger.critical(u"Avahi: Error in group state changed %s",
241
211
                            unicode(error))
242
 
            raise AvahiGroupError("State changed: %s"
 
212
            raise AvahiGroupError(u"State changed: %s"
243
213
                                  % unicode(error))
244
214
    def cleanup(self):
245
215
        """Derived from the Avahi example code"""
246
216
        if self.group is not None:
247
 
            try:
248
 
                self.group.Free()
249
 
            except (dbus.exceptions.UnknownMethodException,
250
 
                    dbus.exceptions.DBusException):
251
 
                pass
 
217
            self.group.Free()
252
218
            self.group = None
253
 
        self.remove()
254
 
    def server_state_changed(self, state, error=None):
 
219
    def server_state_changed(self, state):
255
220
        """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:
266
 
                if error is None:
267
 
                    logger.error(bad_states[state])
268
 
                else:
269
 
                    logger.error(bad_states[state] + ": %r", error)
270
 
            self.cleanup()
 
221
        logger.debug(u"Avahi server state change: %i", state)
 
222
        if state == avahi.SERVER_COLLISION:
 
223
            logger.error(u"Zeroconf server name collision")
 
224
            self.remove()
271
225
        elif state == avahi.SERVER_RUNNING:
272
226
            self.add()
273
 
        else:
274
 
            if error is None:
275
 
                logger.debug("Unknown state: %r", state)
276
 
            else:
277
 
                logger.debug("Unknown state: %r: %r", state, error)
278
227
    def activate(self):
279
228
        """Derived from the Avahi example code"""
280
229
        if self.server is None:
281
230
            self.server = dbus.Interface(
282
231
                self.bus.get_object(avahi.DBUS_NAME,
283
 
                                    avahi.DBUS_PATH_SERVER,
284
 
                                    follow_name_owner_changes=True),
 
232
                                    avahi.DBUS_PATH_SERVER),
285
233
                avahi.DBUS_INTERFACE_SERVER)
286
 
        self.server.connect_to_signal("StateChanged",
 
234
        self.server.connect_to_signal(u"StateChanged",
287
235
                                 self.server_state_changed)
288
236
        self.server_state_changed(self.server.GetState())
289
237
 
290
 
class AvahiServiceToSyslog(AvahiService):
291
 
    def rename(self):
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'
297
 
                                % self.name))
298
 
        return ret
299
238
 
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))
305
 
        
306
239
class Client(object):
307
240
    """A representation of a client host served by this server.
308
241
    
309
242
    Attributes:
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
 
243
    name:       string; from the config file, used in log messages and
 
244
                        D-Bus identifiers
 
245
    fingerprint: string (40 or 32 hexadecimal digits); used to
 
246
                 uniquely identify the client
 
247
    secret:     bytestring; sent verbatim (over TLS) to client
 
248
    host:       string; available for use by the checker command
 
249
    created:    datetime.datetime(); (UTC) object creation
 
250
    last_enabled: datetime.datetime(); (UTC)
 
251
    enabled:    bool()
 
252
    last_checked_ok: datetime.datetime(); (UTC) or None
 
253
    timeout:    datetime.timedelta(); How long from last_checked_ok
 
254
                                      until this client is disabled
 
255
    interval:   datetime.timedelta(); How often to start a new checker
 
256
    disable_hook:  If set, called by disable() as disable_hook(self)
313
257
    checker:    subprocess.Popen(); a running checker process used
314
258
                                    to see if the client lives.
315
259
                                    '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
 
260
    checker_initiator_tag: a gobject event source tag, or None
 
261
    disable_initiator_tag: - '' -
 
262
    checker_callback_tag:  - '' -
 
263
    checker_command: string; External command which is run to check if
 
264
                     client lives.  %() expansions are done at
319
265
                     runtime with vars(self) as dict, so that for
320
266
                     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
267
    current_checker_command: string; current running checker_command
326
 
    disable_initiator_tag: a gobject event source tag, or None
327
 
    enabled:    bool()
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,
336
 
                         or None.
337
 
    last_enabled: datetime.datetime(); (UTC)
338
 
    name:       string; from the config file, used in log messages and
339
 
                        D-Bus identifiers
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
346
 
                disabled, or None
 
268
    approval_delay: datetime.timedelta(); Time to wait for approval
 
269
    _approved:   bool(); 'None' if not yet approved/disapproved
 
270
    approval_duration: datetime.timedelta(); Duration of one approval
347
271
    """
348
272
    
349
 
    runtime_expansions = ("approval_delay", "approval_duration",
350
 
                          "created", "enabled", "fingerprint",
351
 
                          "host", "interval", "last_checked_ok",
352
 
                          "last_enabled", "name", "timeout")
 
273
    @staticmethod
 
274
    def _timedelta_to_milliseconds(td):
 
275
        "Convert a datetime.timedelta() to milliseconds"
 
276
        return ((td.days * 24 * 60 * 60 * 1000)
 
277
                + (td.seconds * 1000)
 
278
                + (td.microseconds // 1000))
353
279
    
354
280
    def timeout_milliseconds(self):
355
281
        "Return the 'timeout' attribute in milliseconds"
356
 
        return _timedelta_to_milliseconds(self.timeout)
357
 
    
358
 
    def extended_timeout_milliseconds(self):
359
 
        "Return the 'extended_timeout' attribute in milliseconds"
360
 
        return _timedelta_to_milliseconds(self.extended_timeout)
 
282
        return self._timedelta_to_milliseconds(self.timeout)
361
283
    
362
284
    def interval_milliseconds(self):
363
285
        "Return the 'interval' attribute in milliseconds"
364
 
        return _timedelta_to_milliseconds(self.interval)
365
 
    
 
286
        return self._timedelta_to_milliseconds(self.interval)
 
287
 
366
288
    def approval_delay_milliseconds(self):
367
 
        return _timedelta_to_milliseconds(self.approval_delay)
 
289
        return self._timedelta_to_milliseconds(self.approval_delay)
368
290
    
369
 
    def __init__(self, name = None, config=None):
 
291
    def __init__(self, name = None, disable_hook=None, config=None):
370
292
        """Note: the 'checker' key in 'config' sets the
371
293
        'checker_command' attribute and *not* the 'checker'
372
294
        attribute."""
373
295
        self.name = name
374
296
        if config is None:
375
297
            config = {}
376
 
        logger.debug("Creating client %r", self.name)
 
298
        logger.debug(u"Creating client %r", self.name)
377
299
        # Uppercase and remove spaces from fingerprint for later
378
300
        # comparison purposes with return value from the fingerprint()
379
301
        # function
380
 
        self.fingerprint = (config["fingerprint"].upper()
381
 
                            .replace(" ", ""))
382
 
        logger.debug("  Fingerprint: %s", self.fingerprint)
383
 
        if "secret" in config:
384
 
            self.secret = config["secret"].decode("base64")
385
 
        elif "secfile" in config:
 
302
        self.fingerprint = (config[u"fingerprint"].upper()
 
303
                            .replace(u" ", u""))
 
304
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
 
305
        if u"secret" in config:
 
306
            self.secret = config[u"secret"].decode(u"base64")
 
307
        elif u"secfile" in config:
386
308
            with open(os.path.expanduser(os.path.expandvars
387
 
                                         (config["secfile"])),
 
309
                                         (config[u"secfile"])),
388
310
                      "rb") as secfile:
389
311
                self.secret = secfile.read()
390
312
        else:
391
 
            raise TypeError("No secret or secfile for client %s"
 
313
            raise TypeError(u"No secret or secfile for client %s"
392
314
                            % self.name)
393
 
        self.host = config.get("host", "")
 
315
        self.host = config.get(u"host", u"")
394
316
        self.created = datetime.datetime.utcnow()
395
 
        self.enabled = True
396
 
        self.last_approval_request = None
397
 
        self.last_enabled = datetime.datetime.utcnow()
 
317
        self.enabled = False
 
318
        self.last_enabled = None
398
319
        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"])
 
320
        self.timeout = string_to_delta(config[u"timeout"])
 
321
        self.interval = string_to_delta(config[u"interval"])
 
322
        self.disable_hook = disable_hook
404
323
        self.checker = None
405
324
        self.checker_initiator_tag = None
406
325
        self.disable_initiator_tag = None
407
 
        self.expires = datetime.datetime.utcnow() + self.timeout
408
326
        self.checker_callback_tag = None
409
 
        self.checker_command = config["checker"]
 
327
        self.checker_command = config[u"checker"]
410
328
        self.current_checker_command = None
 
329
        self.last_connect = None
411
330
        self._approved = None
412
 
        self.approved_by_default = config.get("approved_by_default",
 
331
        self.approved_by_default = config.get(u"approved_by_default",
413
332
                                              True)
414
333
        self.approvals_pending = 0
415
334
        self.approval_delay = string_to_delta(
416
 
            config["approval_delay"])
 
335
            config[u"approval_delay"])
417
336
        self.approval_duration = string_to_delta(
418
 
            config["approval_duration"])
419
 
        self.changedstate = (multiprocessing_manager
420
 
                             .Condition(multiprocessing_manager
421
 
                                        .Lock()))
422
 
        self.client_structure = [attr for attr in self.__dict__.iterkeys() if not attr.startswith("_")]
423
 
        self.client_structure.append("client_structure")
424
 
 
425
 
 
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)
 
337
            config[u"approval_duration"])
 
338
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
430
339
    
431
 
    # Send notice to process children that client state has changed
432
340
    def send_changedstate(self):
433
 
        with self.changedstate:
434
 
            self.changedstate.notify_all()
435
 
    
 
341
        self.changedstate.acquire()
 
342
        self.changedstate.notify_all()
 
343
        self.changedstate.release()
 
344
        
436
345
    def enable(self):
437
346
        """Start this client's checker and timeout hooks"""
438
 
        if getattr(self, "enabled", False):
 
347
        if getattr(self, u"enabled", False):
439
348
            # Already enabled
440
349
            return
441
350
        self.send_changedstate()
442
 
        self.expires = datetime.datetime.utcnow() + self.timeout
 
351
        self.last_enabled = datetime.datetime.utcnow()
 
352
        # Schedule a new checker to be started an 'interval' from now,
 
353
        # and every interval from then on.
 
354
        self.checker_initiator_tag = (gobject.timeout_add
 
355
                                      (self.interval_milliseconds(),
 
356
                                       self.start_checker))
 
357
        # Schedule a disable() when 'timeout' has passed
 
358
        self.disable_initiator_tag = (gobject.timeout_add
 
359
                                   (self.timeout_milliseconds(),
 
360
                                    self.disable))
443
361
        self.enabled = True
444
 
        self.last_enabled = datetime.datetime.utcnow()
445
 
        self.init_checker()
 
362
        # Also start a new checker *right now*.
 
363
        self.start_checker()
446
364
    
447
365
    def disable(self, quiet=True):
448
366
        """Disable this client."""
451
369
        if not quiet:
452
370
            self.send_changedstate()
453
371
        if not quiet:
454
 
            logger.info("Disabling client %s", self.name)
455
 
        if getattr(self, "disable_initiator_tag", False):
 
372
            logger.info(u"Disabling client %s", self.name)
 
373
        if getattr(self, u"disable_initiator_tag", False):
456
374
            gobject.source_remove(self.disable_initiator_tag)
457
375
            self.disable_initiator_tag = None
458
 
        self.expires = None
459
 
        if getattr(self, "checker_initiator_tag", False):
 
376
        if getattr(self, u"checker_initiator_tag", False):
460
377
            gobject.source_remove(self.checker_initiator_tag)
461
378
            self.checker_initiator_tag = None
462
379
        self.stop_checker()
 
380
        if self.disable_hook:
 
381
            self.disable_hook(self)
463
382
        self.enabled = False
464
383
        # Do not run this again if called by a gobject.timeout_add
465
384
        return False
466
385
    
467
386
    def __del__(self):
 
387
        self.disable_hook = None
468
388
        self.disable()
469
 
 
470
 
    def init_checker(self):
471
 
        # Schedule a new checker to be started an 'interval' from now,
472
 
        # and every interval from then on.
473
 
        self.checker_initiator_tag = (gobject.timeout_add
474
 
                                      (self.interval_milliseconds(),
475
 
                                       self.start_checker))
476
 
        # Schedule a disable() when 'timeout' has passed
477
 
        self.disable_initiator_tag = (gobject.timeout_add
478
 
                                   (self.timeout_milliseconds(),
479
 
                                    self.disable))
480
 
        # Also start a new checker *right now*.
481
 
        self.start_checker()
482
 
 
483
 
        
 
389
    
484
390
    def checker_callback(self, pid, condition, command):
485
391
        """The checker has completed, so take appropriate actions."""
486
392
        self.checker_callback_tag = None
487
393
        self.checker = None
488
394
        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",
 
395
            exitstatus = os.WEXITSTATUS(condition)
 
396
            if exitstatus == 0:
 
397
                logger.info(u"Checker for %(name)s succeeded",
492
398
                            vars(self))
493
399
                self.checked_ok()
494
400
            else:
495
 
                logger.info("Checker for %(name)s failed",
 
401
                logger.info(u"Checker for %(name)s failed",
496
402
                            vars(self))
497
403
        else:
498
 
            self.last_checker_status = -1
499
 
            logger.warning("Checker for %(name)s crashed?",
 
404
            logger.warning(u"Checker for %(name)s crashed?",
500
405
                           vars(self))
501
406
    
502
 
    def checked_ok(self, timeout=None):
 
407
    def checked_ok(self):
503
408
        """Bump up the timeout for this client.
504
409
        
505
410
        This should only be called when the client has been seen,
506
411
        alive and well.
507
412
        """
508
 
        if timeout is None:
509
 
            timeout = self.timeout
510
413
        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
518
 
    
519
 
    def need_approval(self):
520
 
        self.last_approval_request = datetime.datetime.utcnow()
 
414
        gobject.source_remove(self.disable_initiator_tag)
 
415
        self.disable_initiator_tag = (gobject.timeout_add
 
416
                                      (self.timeout_milliseconds(),
 
417
                                       self.disable))
521
418
    
522
419
    def start_checker(self):
523
420
        """Start a new checker subprocess if one is not running.
536
433
        # If a checker exists, make sure it is not a zombie
537
434
        try:
538
435
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
539
 
        except (AttributeError, OSError) as error:
 
436
        except (AttributeError, OSError), error:
540
437
            if (isinstance(error, OSError)
541
438
                and error.errno != errno.ECHILD):
542
439
                raise error
543
440
        else:
544
441
            if pid:
545
 
                logger.warning("Checker was a zombie")
 
442
                logger.warning(u"Checker was a zombie")
546
443
                gobject.source_remove(self.checker_callback_tag)
547
444
                self.checker_callback(pid, status,
548
445
                                      self.current_checker_command)
553
450
                command = self.checker_command % self.host
554
451
            except TypeError:
555
452
                # Escape attributes for the shell
556
 
                escaped_attrs = dict(
557
 
                    (attr,
558
 
                     re.escape(unicode(str(getattr(self, attr, "")),
559
 
                                       errors=
560
 
                                       'replace')))
561
 
                    for attr in
562
 
                    self.runtime_expansions)
563
 
                
 
453
                escaped_attrs = dict((key,
 
454
                                      re.escape(unicode(str(val),
 
455
                                                        errors=
 
456
                                                        u'replace')))
 
457
                                     for key, val in
 
458
                                     vars(self).iteritems())
564
459
                try:
565
460
                    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)
 
461
                except TypeError, error:
 
462
                    logger.error(u'Could not format string "%s":'
 
463
                                 u' %s', self.checker_command, error)
569
464
                    return True # Try again later
570
465
            self.current_checker_command = command
571
466
            try:
572
 
                logger.info("Starting checker %r for %s",
 
467
                logger.info(u"Starting checker %r for %s",
573
468
                            command, self.name)
574
469
                # We don't need to redirect stdout and stderr, since
575
470
                # in normal mode, that is already done by daemon(),
577
472
                # always replaced by /dev/null.)
578
473
                self.checker = subprocess.Popen(command,
579
474
                                                close_fds=True,
580
 
                                                shell=True, cwd="/")
 
475
                                                shell=True, cwd=u"/")
581
476
                self.checker_callback_tag = (gobject.child_watch_add
582
477
                                             (self.checker.pid,
583
478
                                              self.checker_callback,
588
483
                if pid:
589
484
                    gobject.source_remove(self.checker_callback_tag)
590
485
                    self.checker_callback(pid, status, command)
591
 
            except OSError as error:
592
 
                logger.error("Failed to start subprocess: %s",
 
486
            except OSError, error:
 
487
                logger.error(u"Failed to start subprocess: %s",
593
488
                             error)
594
489
        # Re-run this periodically if run by gobject.timeout_add
595
490
        return True
599
494
        if self.checker_callback_tag:
600
495
            gobject.source_remove(self.checker_callback_tag)
601
496
            self.checker_callback_tag = None
602
 
        if getattr(self, "checker", None) is None:
 
497
        if getattr(self, u"checker", None) is None:
603
498
            return
604
 
        logger.debug("Stopping checker for %(name)s", vars(self))
 
499
        logger.debug(u"Stopping checker for %(name)s", vars(self))
605
500
        try:
606
501
            os.kill(self.checker.pid, signal.SIGTERM)
607
502
            #time.sleep(0.5)
608
503
            #if self.checker.poll() is None:
609
504
            #    os.kill(self.checker.pid, signal.SIGKILL)
610
 
        except OSError as error:
 
505
        except OSError, error:
611
506
            if error.errno != errno.ESRCH: # No such process
612
507
                raise
613
508
        self.checker = None
614
509
 
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()
619
 
        hasheng.update(key)
620
 
        encryptionkey = hasheng.digest()
621
 
 
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()
626
 
 
627
 
        # Encrypt secret
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)
633
 
 
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()
638
 
        hasheng.update(key)
639
 
        encryptionkey = hasheng.digest()
640
 
 
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)
646
 
 
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)
652
 
 
653
 
        # if validation fails, we use key as new secret. Otherwhise, we use
654
 
        # the decrypted secret
655
 
        if hasheng.digest() == validationhash:
656
 
            self.secret = secret
657
 
        else:
658
 
            self.secret = key
659
 
        del self.encrypted_secret
660
 
 
661
 
 
662
 
def dbus_service_property(dbus_interface, signature="v",
663
 
                          access="readwrite", byte_arrays=False):
 
510
def dbus_service_property(dbus_interface, signature=u"v",
 
511
                          access=u"readwrite", byte_arrays=False):
664
512
    """Decorators for marking methods of a DBusObjectWithProperties to
665
513
    become properties on the D-Bus.
666
514
    
673
521
    """
674
522
    # Encoding deeply encoded byte arrays is not supported yet by the
675
523
    # "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)
 
524
    if byte_arrays and signature != u"ay":
 
525
        raise ValueError(u"Byte arrays not supported for non-'ay'"
 
526
                         u" signature %r" % signature)
679
527
    def decorator(func):
680
528
        func._dbus_is_property = True
681
529
        func._dbus_interface = dbus_interface
682
530
        func._dbus_signature = signature
683
531
        func._dbus_access = access
684
532
        func._dbus_name = func.__name__
685
 
        if func._dbus_name.endswith("_dbus_property"):
 
533
        if func._dbus_name.endswith(u"_dbus_property"):
686
534
            func._dbus_name = func._dbus_name[:-14]
687
 
        func._dbus_get_args_options = {'byte_arrays': byte_arrays }
 
535
        func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
688
536
        return func
689
537
    return decorator
690
538
 
710
558
 
711
559
class DBusObjectWithProperties(dbus.service.Object):
712
560
    """A D-Bus object with properties.
713
 
    
 
561
 
714
562
    Classes inheriting from this can use the dbus_service_property
715
563
    decorator to expose methods as D-Bus properties.  It exposes the
716
564
    standard Get(), Set(), and GetAll() methods on the D-Bus.
718
566
    
719
567
    @staticmethod
720
568
    def _is_dbus_property(obj):
721
 
        return getattr(obj, "_dbus_is_property", False)
 
569
        return getattr(obj, u"_dbus_is_property", False)
722
570
    
723
571
    def _get_all_dbus_properties(self):
724
572
        """Returns a generator of (name, attribute) pairs
725
573
        """
726
 
        return ((prop.__get__(self)._dbus_name, prop.__get__(self))
727
 
                for cls in self.__class__.__mro__
 
574
        return ((prop._dbus_name, prop)
728
575
                for name, prop in
729
 
                inspect.getmembers(cls, self._is_dbus_property))
 
576
                inspect.getmembers(self, self._is_dbus_property))
730
577
    
731
578
    def _get_dbus_property(self, interface_name, property_name):
732
579
        """Returns a bound method if one exists which is a D-Bus
733
580
        property with the specified name and interface.
734
581
        """
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)
741
 
        
 
582
        for name in (property_name,
 
583
                     property_name + u"_dbus_property"):
 
584
            prop = getattr(self, name, None)
 
585
            if (prop is None
 
586
                or not self._is_dbus_property(prop)
 
587
                or prop._dbus_name != property_name
 
588
                or (interface_name and prop._dbus_interface
 
589
                    and interface_name != prop._dbus_interface)):
 
590
                continue
 
591
            return prop
742
592
        # No such property
743
 
        raise DBusPropertyNotFound(self.dbus_object_path + ":"
744
 
                                   + interface_name + "."
 
593
        raise DBusPropertyNotFound(self.dbus_object_path + u":"
 
594
                                   + interface_name + u"."
745
595
                                   + property_name)
746
596
    
747
 
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ss",
748
 
                         out_signature="v")
 
597
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
 
598
                         out_signature=u"v")
749
599
    def Get(self, interface_name, property_name):
750
600
        """Standard D-Bus property Get() method, see D-Bus standard.
751
601
        """
752
602
        prop = self._get_dbus_property(interface_name, property_name)
753
 
        if prop._dbus_access == "write":
 
603
        if prop._dbus_access == u"write":
754
604
            raise DBusPropertyAccessException(property_name)
755
605
        value = prop()
756
 
        if not hasattr(value, "variant_level"):
 
606
        if not hasattr(value, u"variant_level"):
757
607
            return value
758
608
        return type(value)(value, variant_level=value.variant_level+1)
759
609
    
760
 
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="ssv")
 
610
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
761
611
    def Set(self, interface_name, property_name, value):
762
612
        """Standard D-Bus property Set() method, see D-Bus standard.
763
613
        """
764
614
        prop = self._get_dbus_property(interface_name, property_name)
765
 
        if prop._dbus_access == "read":
 
615
        if prop._dbus_access == u"read":
766
616
            raise DBusPropertyAccessException(property_name)
767
 
        if prop._dbus_get_args_options["byte_arrays"]:
 
617
        if prop._dbus_get_args_options[u"byte_arrays"]:
768
618
            # The byte_arrays option is not supported yet on
769
619
            # signatures other than "ay".
770
 
            if prop._dbus_signature != "ay":
 
620
            if prop._dbus_signature != u"ay":
771
621
                raise ValueError
772
622
            value = dbus.ByteArray(''.join(unichr(byte)
773
623
                                           for byte in value))
774
624
        prop(value)
775
625
    
776
 
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature="s",
777
 
                         out_signature="a{sv}")
 
626
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
 
627
                         out_signature=u"a{sv}")
778
628
    def GetAll(self, interface_name):
779
629
        """Standard D-Bus property GetAll() method, see D-Bus
780
630
        standard.
781
 
        
 
631
 
782
632
        Note: Will not include properties with access="write".
783
633
        """
784
 
        properties = {}
 
634
        all = {}
785
635
        for name, prop in self._get_all_dbus_properties():
786
636
            if (interface_name
787
637
                and interface_name != prop._dbus_interface):
788
638
                # Interface non-empty but did not match
789
639
                continue
790
640
            # Ignore write-only properties
791
 
            if prop._dbus_access == "write":
 
641
            if prop._dbus_access == u"write":
792
642
                continue
793
643
            value = prop()
794
 
            if not hasattr(value, "variant_level"):
795
 
                properties[name] = value
 
644
            if not hasattr(value, u"variant_level"):
 
645
                all[name] = value
796
646
                continue
797
 
            properties[name] = type(value)(value, variant_level=
798
 
                                           value.variant_level+1)
799
 
        return dbus.Dictionary(properties, signature="sv")
 
647
            all[name] = type(value)(value, variant_level=
 
648
                                    value.variant_level+1)
 
649
        return dbus.Dictionary(all, signature=u"sv")
800
650
    
801
651
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
802
 
                         out_signature="s",
 
652
                         out_signature=u"s",
803
653
                         path_keyword='object_path',
804
654
                         connection_keyword='connection')
805
655
    def Introspect(self, object_path, connection):
810
660
        try:
811
661
            document = xml.dom.minidom.parseString(xmlstring)
812
662
            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)
 
663
                e = document.createElement(u"property")
 
664
                e.setAttribute(u"name", name)
 
665
                e.setAttribute(u"type", prop._dbus_signature)
 
666
                e.setAttribute(u"access", prop._dbus_access)
817
667
                return e
818
 
            for if_tag in document.getElementsByTagName("interface"):
 
668
            for if_tag in document.getElementsByTagName(u"interface"):
819
669
                for tag in (make_tag(document, name, prop)
820
670
                            for name, prop
821
671
                            in self._get_all_dbus_properties()
822
672
                            if prop._dbus_interface
823
 
                            == if_tag.getAttribute("name")):
 
673
                            == if_tag.getAttribute(u"name")):
824
674
                    if_tag.appendChild(tag)
825
675
                # Add the names to the return values for the
826
676
                # "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")
833
 
                                    == "out"):
834
 
                                    arg.setAttribute("name", "value")
835
 
                        elif cn.getAttribute("name") == "GetAll":
836
 
                            for arg in cn.getElementsByTagName("arg"):
837
 
                                if (arg.getAttribute("direction")
838
 
                                    == "out"):
839
 
                                    arg.setAttribute("name", "props")
840
 
            xmlstring = document.toxml("utf-8")
 
677
                if (if_tag.getAttribute(u"name")
 
678
                    == u"org.freedesktop.DBus.Properties"):
 
679
                    for cn in if_tag.getElementsByTagName(u"method"):
 
680
                        if cn.getAttribute(u"name") == u"Get":
 
681
                            for arg in cn.getElementsByTagName(u"arg"):
 
682
                                if (arg.getAttribute(u"direction")
 
683
                                    == u"out"):
 
684
                                    arg.setAttribute(u"name", u"value")
 
685
                        elif cn.getAttribute(u"name") == u"GetAll":
 
686
                            for arg in cn.getElementsByTagName(u"arg"):
 
687
                                if (arg.getAttribute(u"direction")
 
688
                                    == u"out"):
 
689
                                    arg.setAttribute(u"name", u"props")
 
690
            xmlstring = document.toxml(u"utf-8")
841
691
            document.unlink()
842
692
        except (AttributeError, xml.dom.DOMException,
843
 
                xml.parsers.expat.ExpatError) as error:
844
 
            logger.error("Failed to override Introspection method",
 
693
                xml.parsers.expat.ExpatError), error:
 
694
            logger.error(u"Failed to override Introspection method",
845
695
                         error)
846
696
        return xmlstring
847
697
 
848
698
 
849
 
def datetime_to_dbus (dt, variant_level=0):
850
 
    """Convert a UTC datetime.datetime() to a D-Bus type."""
851
 
    if dt is None:
852
 
        return dbus.String("", variant_level = variant_level)
853
 
    return dbus.String(dt.isoformat(),
854
 
                       variant_level=variant_level)
855
 
 
856
 
class AlternateDBusNamesMetaclass(DBusObjectWithProperties
857
 
                                  .__metaclass__):
858
 
    """Applied to an empty subclass of a D-Bus object, this metaclass
859
 
    will add additional D-Bus attributes matching a certain pattern.
860
 
    """
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")):
873
 
                    continue
874
 
                # Create an alternate D-Bus interface name based on
875
 
                # the current name
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
882
 
                    # black magic
883
 
                    nonmethod_func = (dict(
884
 
                            zip(attribute.func_code.co_freevars,
885
 
                                attribute.__closure__))["func"]
886
 
                                      .cell_contents)
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
891
 
                                    (alt_interface,
892
 
                                     attribute._dbus_signature)
893
 
                                    (types.FunctionType(
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)
911
 
                        return call_both
912
 
                    # Create the "call_both" function and add it to
913
 
                    # the class
914
 
                    attr[attrname] = fixscope(attribute,
915
 
                                              new_function)
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
921
 
                    # to the class.
922
 
                    attr[attrname] = (dbus.service.method
923
 
                                      (alt_interface,
924
 
                                       attribute._dbus_in_signature,
925
 
                                       attribute._dbus_out_signature)
926
 
                                      (types.FunctionType
927
 
                                       (attribute.func_code,
928
 
                                        attribute.func_globals,
929
 
                                        attribute.func_name,
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
939
 
                                      (alt_interface,
940
 
                                       attribute._dbus_signature,
941
 
                                       attribute._dbus_access,
942
 
                                       attribute
943
 
                                       ._dbus_get_args_options
944
 
                                       ["byte_arrays"])
945
 
                                      (types.FunctionType
946
 
                                       (attribute.func_code,
947
 
                                        attribute.func_globals,
948
 
                                        attribute.func_name,
949
 
                                        attribute.func_defaults,
950
 
                                        attribute.func_closure)))
951
 
        return type.__new__(mcs, name, bases, attr)
952
 
 
953
699
class ClientDBus(Client, DBusObjectWithProperties):
954
700
    """A Client class using D-Bus
955
701
    
957
703
    dbus_object_path: dbus.ObjectPath
958
704
    bus: dbus.SystemBus()
959
705
    """
960
 
    
961
 
    runtime_expansions = (Client.runtime_expansions
962
 
                          + ("dbus_object_path",))
963
 
    
964
706
    # dbus.service.Object doesn't use super(), so we can't either.
965
707
    
966
708
    def __init__(self, bus = None, *args, **kwargs):
 
709
        self._approvals_pending = 0
967
710
        self.bus = bus
968
711
        Client.__init__(self, *args, **kwargs)
969
 
 
970
 
        self._approvals_pending = 0
971
712
        # Only now, when this client is initialized, can it show up on
972
713
        # the D-Bus
973
 
        client_object_name = unicode(self.name).translate(
974
 
            {ord("."): ord("_"),
975
 
             ord("-"): ord("_")})
976
714
        self.dbus_object_path = (dbus.ObjectPath
977
 
                                 ("/clients/" + client_object_name))
 
715
                                 (u"/clients/"
 
716
                                  + self.name.replace(u".", u"_")))
978
717
        DBusObjectWithProperties.__init__(self, self.bus,
979
718
                                          self.dbus_object_path)
980
719
        
981
 
    def notifychangeproperty(transform_func,
982
 
                             dbus_name, type_func=lambda x: x,
983
 
                             variant_level=1):
984
 
        """ Modify a variable so that it's a property which announces
985
 
        its changes to DBus.
 
720
    def _get_approvals_pending(self):
 
721
        return self._approvals_pending
 
722
    def _set_approvals_pending(self, value):
 
723
        old_value = self._approvals_pending
 
724
        self._approvals_pending = value
 
725
        bval = bool(value)
 
726
        if (hasattr(self, "dbus_object_path")
 
727
            and bval is not bool(old_value)):
 
728
            dbus_bool = dbus.Boolean(bval, variant_level=1)
 
729
            self.PropertyChanged(dbus.String(u"ApprovalPending"),
 
730
                                 dbus_bool)
986
731
 
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
993
 
        """
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),
1001
 
                                                variant_level
1002
 
                                                =variant_level)
1003
 
                    self.PropertyChanged(dbus.String(dbus_name),
1004
 
                                         dbus_value)
1005
 
            setattr(self, attrname, value)
1006
 
        
1007
 
        return property(lambda self: getattr(self, attrname), setter)
1008
 
    
1009
 
    
1010
 
    expires = notifychangeproperty(datetime_to_dbus, "Expires")
1011
 
    approvals_pending = notifychangeproperty(dbus.Boolean,
1012
 
                                             "ApprovalPending",
1013
 
                                             type_func = bool)
1014
 
    enabled = notifychangeproperty(dbus.Boolean, "Enabled")
1015
 
    last_enabled = notifychangeproperty(datetime_to_dbus,
1016
 
                                        "LastEnabled")
1017
 
    checker = notifychangeproperty(dbus.Boolean, "CheckerRunning",
1018
 
                                   type_func = lambda checker:
1019
 
                                       checker is not None)
1020
 
    last_checked_ok = notifychangeproperty(datetime_to_dbus,
1021
 
                                           "LastCheckedOK")
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,
1027
 
                                          "ApprovalDelay",
1028
 
                                          type_func =
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",
1035
 
                                   type_func =
1036
 
                                   _timedelta_to_milliseconds)
1037
 
    extended_timeout = notifychangeproperty(
1038
 
        dbus.UInt16, "ExtendedTimeout",
1039
 
        type_func = _timedelta_to_milliseconds)
1040
 
    interval = notifychangeproperty(dbus.UInt16,
1041
 
                                    "Interval",
1042
 
                                    type_func =
1043
 
                                    _timedelta_to_milliseconds)
1044
 
    checker_command = notifychangeproperty(dbus.String, "Checker")
1045
 
    
1046
 
    del notifychangeproperty
 
732
    approvals_pending = property(_get_approvals_pending,
 
733
                                 _set_approvals_pending)
 
734
    del _get_approvals_pending, _set_approvals_pending
 
735
    
 
736
    @staticmethod
 
737
    def _datetime_to_dbus(dt, variant_level=0):
 
738
        """Convert a UTC datetime.datetime() to a D-Bus type."""
 
739
        return dbus.String(dt.isoformat(),
 
740
                           variant_level=variant_level)
 
741
    
 
742
    def enable(self):
 
743
        oldstate = getattr(self, u"enabled", False)
 
744
        r = Client.enable(self)
 
745
        if oldstate != self.enabled:
 
746
            # Emit D-Bus signals
 
747
            self.PropertyChanged(dbus.String(u"Enabled"),
 
748
                                 dbus.Boolean(True, variant_level=1))
 
749
            self.PropertyChanged(
 
750
                dbus.String(u"LastEnabled"),
 
751
                self._datetime_to_dbus(self.last_enabled,
 
752
                                       variant_level=1))
 
753
        return r
 
754
    
 
755
    def disable(self, quiet = False):
 
756
        oldstate = getattr(self, u"enabled", False)
 
757
        r = Client.disable(self, quiet=quiet)
 
758
        if not quiet and oldstate != self.enabled:
 
759
            # Emit D-Bus signal
 
760
            self.PropertyChanged(dbus.String(u"Enabled"),
 
761
                                 dbus.Boolean(False, variant_level=1))
 
762
        return r
1047
763
    
1048
764
    def __del__(self, *args, **kwargs):
1049
765
        try:
1050
766
            self.remove_from_connection()
1051
767
        except LookupError:
1052
768
            pass
1053
 
        if hasattr(DBusObjectWithProperties, "__del__"):
 
769
        if hasattr(DBusObjectWithProperties, u"__del__"):
1054
770
            DBusObjectWithProperties.__del__(self, *args, **kwargs)
1055
771
        Client.__del__(self, *args, **kwargs)
1056
772
    
1058
774
                         *args, **kwargs):
1059
775
        self.checker_callback_tag = None
1060
776
        self.checker = None
 
777
        # Emit D-Bus signal
 
778
        self.PropertyChanged(dbus.String(u"CheckerRunning"),
 
779
                             dbus.Boolean(False, variant_level=1))
1061
780
        if os.WIFEXITED(condition):
1062
781
            exitstatus = os.WEXITSTATUS(condition)
1063
782
            # Emit D-Bus signal
1073
792
        return Client.checker_callback(self, pid, condition, command,
1074
793
                                       *args, **kwargs)
1075
794
    
 
795
    def checked_ok(self, *args, **kwargs):
 
796
        r = Client.checked_ok(self, *args, **kwargs)
 
797
        # Emit D-Bus signal
 
798
        self.PropertyChanged(
 
799
            dbus.String(u"LastCheckedOK"),
 
800
            (self._datetime_to_dbus(self.last_checked_ok,
 
801
                                    variant_level=1)))
 
802
        return r
 
803
    
1076
804
    def start_checker(self, *args, **kwargs):
1077
805
        old_checker = self.checker
1078
806
        if self.checker is not None:
1085
813
            and old_checker_pid != self.checker.pid):
1086
814
            # Emit D-Bus signal
1087
815
            self.CheckerStarted(self.current_checker_command)
 
816
            self.PropertyChanged(
 
817
                dbus.String(u"CheckerRunning"),
 
818
                dbus.Boolean(True, variant_level=1))
1088
819
        return r
1089
820
    
 
821
    def stop_checker(self, *args, **kwargs):
 
822
        old_checker = getattr(self, u"checker", None)
 
823
        r = Client.stop_checker(self, *args, **kwargs)
 
824
        if (old_checker is not None
 
825
            and getattr(self, u"checker", None) is None):
 
826
            self.PropertyChanged(dbus.String(u"CheckerRunning"),
 
827
                                 dbus.Boolean(False, variant_level=1))
 
828
        return r
 
829
 
1090
830
    def _reset_approved(self):
1091
831
        self._approved = None
1092
832
        return False
1094
834
    def approve(self, value=True):
1095
835
        self.send_changedstate()
1096
836
        self._approved = value
1097
 
        gobject.timeout_add(_timedelta_to_milliseconds
 
837
        gobject.timeout_add(self._timedelta_to_milliseconds
1098
838
                            (self.approval_duration),
1099
839
                            self._reset_approved)
1100
840
    
1101
841
    
1102
842
    ## D-Bus methods, signals & properties
1103
 
    _interface = "se.recompile.Mandos.Client"
 
843
    _interface = u"se.bsnet.fukt.Mandos.Client"
1104
844
    
1105
845
    ## Signals
1106
846
    
1107
847
    # CheckerCompleted - signal
1108
 
    @dbus.service.signal(_interface, signature="nxs")
 
848
    @dbus.service.signal(_interface, signature=u"nxs")
1109
849
    def CheckerCompleted(self, exitcode, waitstatus, command):
1110
850
        "D-Bus signal"
1111
851
        pass
1112
852
    
1113
853
    # CheckerStarted - signal
1114
 
    @dbus.service.signal(_interface, signature="s")
 
854
    @dbus.service.signal(_interface, signature=u"s")
1115
855
    def CheckerStarted(self, command):
1116
856
        "D-Bus signal"
1117
857
        pass
1118
858
    
1119
859
    # PropertyChanged - signal
1120
 
    @dbus.service.signal(_interface, signature="sv")
 
860
    @dbus.service.signal(_interface, signature=u"sv")
1121
861
    def PropertyChanged(self, property, value):
1122
862
        "D-Bus signal"
1123
863
        pass
1132
872
        pass
1133
873
    
1134
874
    # Rejected - signal
1135
 
    @dbus.service.signal(_interface, signature="s")
 
875
    @dbus.service.signal(_interface, signature=u"s")
1136
876
    def Rejected(self, reason):
1137
877
        "D-Bus signal"
1138
878
        pass
1139
879
    
1140
880
    # NeedApproval - signal
1141
 
    @dbus.service.signal(_interface, signature="tb")
 
881
    @dbus.service.signal(_interface, signature=u"tb")
1142
882
    def NeedApproval(self, timeout, default):
1143
883
        "D-Bus signal"
1144
 
        return self.need_approval()
 
884
        pass
1145
885
    
1146
 
    # NeRwequest - signal
1147
 
    @dbus.service.signal(_interface, signature="s")
1148
 
    def NewRequest(self, ip):
1149
 
        """D-Bus signal
1150
 
        Is sent after a client request a password.
1151
 
        """
1152
 
        pass
1153
 
 
1154
886
    ## Methods
1155
 
    
 
887
 
1156
888
    # Approve - method
1157
 
    @dbus.service.method(_interface, in_signature="b")
 
889
    @dbus.service.method(_interface, in_signature=u"b")
1158
890
    def Approve(self, value):
1159
891
        self.approve(value)
1160
 
    
 
892
 
1161
893
    # CheckedOK - method
1162
894
    @dbus.service.method(_interface)
1163
895
    def CheckedOK(self):
1164
 
        self.checked_ok()
 
896
        return self.checked_ok()
1165
897
    
1166
898
    # Enable - method
1167
899
    @dbus.service.method(_interface)
1189
921
    ## Properties
1190
922
    
1191
923
    # ApprovalPending - property
1192
 
    @dbus_service_property(_interface, signature="b", access="read")
 
924
    @dbus_service_property(_interface, signature=u"b", access=u"read")
1193
925
    def ApprovalPending_dbus_property(self):
1194
926
        return dbus.Boolean(bool(self.approvals_pending))
1195
927
    
1196
928
    # ApprovedByDefault - property
1197
 
    @dbus_service_property(_interface, signature="b",
1198
 
                           access="readwrite")
 
929
    @dbus_service_property(_interface, signature=u"b",
 
930
                           access=u"readwrite")
1199
931
    def ApprovedByDefault_dbus_property(self, value=None):
1200
932
        if value is None:       # get
1201
933
            return dbus.Boolean(self.approved_by_default)
1202
934
        self.approved_by_default = bool(value)
 
935
        # Emit D-Bus signal
 
936
        self.PropertyChanged(dbus.String(u"ApprovedByDefault"),
 
937
                             dbus.Boolean(value, variant_level=1))
1203
938
    
1204
939
    # ApprovalDelay - property
1205
 
    @dbus_service_property(_interface, signature="t",
1206
 
                           access="readwrite")
 
940
    @dbus_service_property(_interface, signature=u"t",
 
941
                           access=u"readwrite")
1207
942
    def ApprovalDelay_dbus_property(self, value=None):
1208
943
        if value is None:       # get
1209
944
            return dbus.UInt64(self.approval_delay_milliseconds())
1210
945
        self.approval_delay = datetime.timedelta(0, 0, 0, value)
 
946
        # Emit D-Bus signal
 
947
        self.PropertyChanged(dbus.String(u"ApprovalDelay"),
 
948
                             dbus.UInt64(value, variant_level=1))
1211
949
    
1212
950
    # ApprovalDuration - property
1213
 
    @dbus_service_property(_interface, signature="t",
1214
 
                           access="readwrite")
 
951
    @dbus_service_property(_interface, signature=u"t",
 
952
                           access=u"readwrite")
1215
953
    def ApprovalDuration_dbus_property(self, value=None):
1216
954
        if value is None:       # get
1217
 
            return dbus.UInt64(_timedelta_to_milliseconds(
 
955
            return dbus.UInt64(self._timedelta_to_milliseconds(
1218
956
                    self.approval_duration))
1219
957
        self.approval_duration = datetime.timedelta(0, 0, 0, value)
 
958
        # Emit D-Bus signal
 
959
        self.PropertyChanged(dbus.String(u"ApprovalDuration"),
 
960
                             dbus.UInt64(value, variant_level=1))
1220
961
    
1221
962
    # Name - property
1222
 
    @dbus_service_property(_interface, signature="s", access="read")
 
963
    @dbus_service_property(_interface, signature=u"s", access=u"read")
1223
964
    def Name_dbus_property(self):
1224
965
        return dbus.String(self.name)
1225
966
    
1226
967
    # Fingerprint - property
1227
 
    @dbus_service_property(_interface, signature="s", access="read")
 
968
    @dbus_service_property(_interface, signature=u"s", access=u"read")
1228
969
    def Fingerprint_dbus_property(self):
1229
970
        return dbus.String(self.fingerprint)
1230
971
    
1231
972
    # Host - property
1232
 
    @dbus_service_property(_interface, signature="s",
1233
 
                           access="readwrite")
 
973
    @dbus_service_property(_interface, signature=u"s",
 
974
                           access=u"readwrite")
1234
975
    def Host_dbus_property(self, value=None):
1235
976
        if value is None:       # get
1236
977
            return dbus.String(self.host)
1237
978
        self.host = value
 
979
        # Emit D-Bus signal
 
980
        self.PropertyChanged(dbus.String(u"Host"),
 
981
                             dbus.String(value, variant_level=1))
1238
982
    
1239
983
    # Created - property
1240
 
    @dbus_service_property(_interface, signature="s", access="read")
 
984
    @dbus_service_property(_interface, signature=u"s", access=u"read")
1241
985
    def Created_dbus_property(self):
1242
 
        return dbus.String(datetime_to_dbus(self.created))
 
986
        return dbus.String(self._datetime_to_dbus(self.created))
1243
987
    
1244
988
    # LastEnabled - property
1245
 
    @dbus_service_property(_interface, signature="s", access="read")
 
989
    @dbus_service_property(_interface, signature=u"s", access=u"read")
1246
990
    def LastEnabled_dbus_property(self):
1247
 
        return datetime_to_dbus(self.last_enabled)
 
991
        if self.last_enabled is None:
 
992
            return dbus.String(u"")
 
993
        return dbus.String(self._datetime_to_dbus(self.last_enabled))
1248
994
    
1249
995
    # Enabled - property
1250
 
    @dbus_service_property(_interface, signature="b",
1251
 
                           access="readwrite")
 
996
    @dbus_service_property(_interface, signature=u"b",
 
997
                           access=u"readwrite")
1252
998
    def Enabled_dbus_property(self, value=None):
1253
999
        if value is None:       # get
1254
1000
            return dbus.Boolean(self.enabled)
1258
1004
            self.disable()
1259
1005
    
1260
1006
    # LastCheckedOK - property
1261
 
    @dbus_service_property(_interface, signature="s",
1262
 
                           access="readwrite")
 
1007
    @dbus_service_property(_interface, signature=u"s",
 
1008
                           access=u"readwrite")
1263
1009
    def LastCheckedOK_dbus_property(self, value=None):
1264
1010
        if value is not None:
1265
1011
            self.checked_ok()
1266
1012
            return
1267
 
        return datetime_to_dbus(self.last_checked_ok)
1268
 
    
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)
1273
 
    
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)
 
1013
        if self.last_checked_ok is None:
 
1014
            return dbus.String(u"")
 
1015
        return dbus.String(self._datetime_to_dbus(self
 
1016
                                                  .last_checked_ok))
1278
1017
    
1279
1018
    # Timeout - property
1280
 
    @dbus_service_property(_interface, signature="t",
1281
 
                           access="readwrite")
 
1019
    @dbus_service_property(_interface, signature=u"t",
 
1020
                           access=u"readwrite")
1282
1021
    def Timeout_dbus_property(self, value=None):
1283
1022
        if value is None:       # get
1284
1023
            return dbus.UInt64(self.timeout_milliseconds())
1285
1024
        self.timeout = datetime.timedelta(0, 0, 0, value)
1286
 
        if getattr(self, "disable_initiator_tag", None) is None:
 
1025
        # Emit D-Bus signal
 
1026
        self.PropertyChanged(dbus.String(u"Timeout"),
 
1027
                             dbus.UInt64(value, variant_level=1))
 
1028
        if getattr(self, u"disable_initiator_tag", None) is None:
1287
1029
            return
1288
1030
        # Reschedule timeout
1289
1031
        gobject.source_remove(self.disable_initiator_tag)
1290
1032
        self.disable_initiator_tag = None
1291
 
        self.expires = None
1292
 
        time_to_die = _timedelta_to_milliseconds((self
1293
 
                                                  .last_checked_ok
1294
 
                                                  + self.timeout)
1295
 
                                                 - datetime.datetime
1296
 
                                                 .utcnow())
 
1033
        time_to_die = (self.
 
1034
                       _timedelta_to_milliseconds((self
 
1035
                                                   .last_checked_ok
 
1036
                                                   + self.timeout)
 
1037
                                                  - datetime.datetime
 
1038
                                                  .utcnow()))
1297
1039
        if time_to_die <= 0:
1298
1040
            # The timeout has passed
1299
1041
            self.disable()
1300
1042
        else:
1301
 
            self.expires = (datetime.datetime.utcnow()
1302
 
                            + datetime.timedelta(milliseconds =
1303
 
                                                 time_to_die))
1304
1043
            self.disable_initiator_tag = (gobject.timeout_add
1305
1044
                                          (time_to_die, self.disable))
1306
1045
    
1307
 
    # ExtendedTimeout - property
1308
 
    @dbus_service_property(_interface, signature="t",
1309
 
                           access="readwrite")
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)
1314
 
    
1315
1046
    # Interval - property
1316
 
    @dbus_service_property(_interface, signature="t",
1317
 
                           access="readwrite")
 
1047
    @dbus_service_property(_interface, signature=u"t",
 
1048
                           access=u"readwrite")
1318
1049
    def Interval_dbus_property(self, value=None):
1319
1050
        if value is None:       # get
1320
1051
            return dbus.UInt64(self.interval_milliseconds())
1321
1052
        self.interval = datetime.timedelta(0, 0, 0, value)
1322
 
        if getattr(self, "checker_initiator_tag", None) is None:
 
1053
        # Emit D-Bus signal
 
1054
        self.PropertyChanged(dbus.String(u"Interval"),
 
1055
                             dbus.UInt64(value, variant_level=1))
 
1056
        if getattr(self, u"checker_initiator_tag", None) is None:
1323
1057
            return
1324
1058
        # Reschedule checker run
1325
1059
        gobject.source_remove(self.checker_initiator_tag)
1326
1060
        self.checker_initiator_tag = (gobject.timeout_add
1327
1061
                                      (value, self.start_checker))
1328
1062
        self.start_checker()    # Start one now, too
1329
 
    
 
1063
 
1330
1064
    # Checker - property
1331
 
    @dbus_service_property(_interface, signature="s",
1332
 
                           access="readwrite")
 
1065
    @dbus_service_property(_interface, signature=u"s",
 
1066
                           access=u"readwrite")
1333
1067
    def Checker_dbus_property(self, value=None):
1334
1068
        if value is None:       # get
1335
1069
            return dbus.String(self.checker_command)
1336
1070
        self.checker_command = value
 
1071
        # Emit D-Bus signal
 
1072
        self.PropertyChanged(dbus.String(u"Checker"),
 
1073
                             dbus.String(self.checker_command,
 
1074
                                         variant_level=1))
1337
1075
    
1338
1076
    # CheckerRunning - property
1339
 
    @dbus_service_property(_interface, signature="b",
1340
 
                           access="readwrite")
 
1077
    @dbus_service_property(_interface, signature=u"b",
 
1078
                           access=u"readwrite")
1341
1079
    def CheckerRunning_dbus_property(self, value=None):
1342
1080
        if value is None:       # get
1343
1081
            return dbus.Boolean(self.checker is not None)
1347
1085
            self.stop_checker()
1348
1086
    
1349
1087
    # ObjectPath - property
1350
 
    @dbus_service_property(_interface, signature="o", access="read")
 
1088
    @dbus_service_property(_interface, signature=u"o", access=u"read")
1351
1089
    def ObjectPath_dbus_property(self):
1352
1090
        return self.dbus_object_path # is already a dbus.ObjectPath
1353
1091
    
1354
1092
    # Secret = property
1355
 
    @dbus_service_property(_interface, signature="ay",
1356
 
                           access="write", byte_arrays=True)
 
1093
    @dbus_service_property(_interface, signature=u"ay",
 
1094
                           access=u"write", byte_arrays=True)
1357
1095
    def Secret_dbus_property(self, value):
1358
1096
        self.secret = str(value)
1359
1097
    
1366
1104
        self._pipe.send(('init', fpr, address))
1367
1105
        if not self._pipe.recv():
1368
1106
            raise KeyError()
1369
 
    
 
1107
 
1370
1108
    def __getattribute__(self, name):
1371
1109
        if(name == '_pipe'):
1372
1110
            return super(ProxyClient, self).__getattribute__(name)
1379
1117
                self._pipe.send(('funcall', name, args, kwargs))
1380
1118
                return self._pipe.recv()[1]
1381
1119
            return func
1382
 
    
 
1120
 
1383
1121
    def __setattr__(self, name, value):
1384
1122
        if(name == '_pipe'):
1385
1123
            return super(ProxyClient, self).__setattr__(name, value)
1386
1124
        self._pipe.send(('setattr', name, value))
1387
1125
 
1388
 
class ClientDBusTransitional(ClientDBus):
1389
 
    __metaclass__ = AlternateDBusNamesMetaclass
1390
1126
 
1391
1127
class ClientHandler(socketserver.BaseRequestHandler, object):
1392
1128
    """A class to handle client connections.
1396
1132
    
1397
1133
    def handle(self):
1398
1134
        with contextlib.closing(self.server.child_pipe) as child_pipe:
1399
 
            logger.info("TCP connection from: %s",
 
1135
            logger.info(u"TCP connection from: %s",
1400
1136
                        unicode(self.client_address))
1401
 
            logger.debug("Pipe FD: %d",
 
1137
            logger.debug(u"Pipe FD: %d",
1402
1138
                         self.server.child_pipe.fileno())
1403
 
            
 
1139
 
1404
1140
            session = (gnutls.connection
1405
1141
                       .ClientSession(self.request,
1406
1142
                                      gnutls.connection
1407
1143
                                      .X509Credentials()))
1408
 
            
 
1144
 
1409
1145
            # Note: gnutls.connection.X509Credentials is really a
1410
1146
            # generic GnuTLS certificate credentials object so long as
1411
1147
            # no X.509 keys are added to it.  Therefore, we can use it
1412
1148
            # here despite using OpenPGP certificates.
1413
 
            
1414
 
            #priority = ':'.join(("NONE", "+VERS-TLS1.1",
1415
 
            #                      "+AES-256-CBC", "+SHA1",
1416
 
            #                      "+COMP-NULL", "+CTYPE-OPENPGP",
1417
 
            #                      "+DHE-DSS"))
 
1149
 
 
1150
            #priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
 
1151
            #                      u"+AES-256-CBC", u"+SHA1",
 
1152
            #                      u"+COMP-NULL", u"+CTYPE-OPENPGP",
 
1153
            #                      u"+DHE-DSS"))
1418
1154
            # Use a fallback default, since this MUST be set.
1419
1155
            priority = self.server.gnutls_priority
1420
1156
            if priority is None:
1421
 
                priority = "NORMAL"
 
1157
                priority = u"NORMAL"
1422
1158
            (gnutls.library.functions
1423
1159
             .gnutls_priority_set_direct(session._c_object,
1424
1160
                                         priority, None))
1425
 
            
 
1161
 
1426
1162
            # Start communication using the Mandos protocol
1427
1163
            # Get protocol number
1428
1164
            line = self.request.makefile().readline()
1429
 
            logger.debug("Protocol version: %r", line)
 
1165
            logger.debug(u"Protocol version: %r", line)
1430
1166
            try:
1431
1167
                if int(line.strip().split()[0]) > 1:
1432
1168
                    raise RuntimeError
1433
 
            except (ValueError, IndexError, RuntimeError) as error:
1434
 
                logger.error("Unknown protocol version: %s", error)
 
1169
            except (ValueError, IndexError, RuntimeError), error:
 
1170
                logger.error(u"Unknown protocol version: %s", error)
1435
1171
                return
1436
 
            
 
1172
 
1437
1173
            # Start GnuTLS connection
1438
1174
            try:
1439
1175
                session.handshake()
1440
 
            except gnutls.errors.GNUTLSError as error:
1441
 
                logger.warning("Handshake failed: %s", error)
 
1176
            except gnutls.errors.GNUTLSError, error:
 
1177
                logger.warning(u"Handshake failed: %s", error)
1442
1178
                # Do not run session.bye() here: the session is not
1443
1179
                # established.  Just abandon the request.
1444
1180
                return
1445
 
            logger.debug("Handshake succeeded")
1446
 
            
 
1181
            logger.debug(u"Handshake succeeded")
 
1182
 
1447
1183
            approval_required = False
1448
1184
            try:
1449
1185
                try:
1450
1186
                    fpr = self.fingerprint(self.peer_certificate
1451
1187
                                           (session))
1452
 
                except (TypeError,
1453
 
                        gnutls.errors.GNUTLSError) as error:
1454
 
                    logger.warning("Bad certificate: %s", error)
 
1188
                except (TypeError, gnutls.errors.GNUTLSError), error:
 
1189
                    logger.warning(u"Bad certificate: %s", error)
1455
1190
                    return
1456
 
                logger.debug("Fingerprint: %s", fpr)
1457
 
                if self.server.use_dbus:
1458
 
                    # Emit D-Bus signal
1459
 
                    client.NewRequest(str(self.client_address))
1460
 
                
 
1191
                logger.debug(u"Fingerprint: %s", fpr)
 
1192
 
1461
1193
                try:
1462
1194
                    client = ProxyClient(child_pipe, fpr,
1463
1195
                                         self.client_address)
1471
1203
                
1472
1204
                while True:
1473
1205
                    if not client.enabled:
1474
 
                        logger.info("Client %s is disabled",
 
1206
                        logger.warning(u"Client %s is disabled",
1475
1207
                                       client.name)
1476
1208
                        if self.server.use_dbus:
1477
1209
                            # Emit D-Bus signal
1478
 
                            client.Rejected("Disabled")
 
1210
                            client.Rejected("Disabled")                    
1479
1211
                        return
1480
1212
                    
1481
1213
                    if client._approved or not client.approval_delay:
1482
1214
                        #We are approved or approval is disabled
1483
1215
                        break
1484
1216
                    elif client._approved is None:
1485
 
                        logger.info("Client %s needs approval",
 
1217
                        logger.info(u"Client %s needs approval",
1486
1218
                                    client.name)
1487
1219
                        if self.server.use_dbus:
1488
1220
                            # Emit D-Bus signal
1490
1222
                                client.approval_delay_milliseconds(),
1491
1223
                                client.approved_by_default)
1492
1224
                    else:
1493
 
                        logger.warning("Client %s was not approved",
 
1225
                        logger.warning(u"Client %s was not approved",
1494
1226
                                       client.name)
1495
1227
                        if self.server.use_dbus:
1496
1228
                            # Emit D-Bus signal
1498
1230
                        return
1499
1231
                    
1500
1232
                    #wait until timeout or approved
 
1233
                    #x = float(client._timedelta_to_milliseconds(delay))
1501
1234
                    time = datetime.datetime.now()
1502
1235
                    client.changedstate.acquire()
1503
 
                    (client.changedstate.wait
1504
 
                     (float(client._timedelta_to_milliseconds(delay)
1505
 
                            / 1000)))
 
1236
                    client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1506
1237
                    client.changedstate.release()
1507
1238
                    time2 = datetime.datetime.now()
1508
1239
                    if (time2 - time) >= delay:
1512
1243
                                           client.name)
1513
1244
                            if self.server.use_dbus:
1514
1245
                                # Emit D-Bus signal
1515
 
                                client.Rejected("Approval timed out")
 
1246
                                client.Rejected("Timed out")
1516
1247
                            return
1517
1248
                        else:
1518
1249
                            break
1523
1254
                while sent_size < len(client.secret):
1524
1255
                    try:
1525
1256
                        sent = session.send(client.secret[sent_size:])
1526
 
                    except gnutls.errors.GNUTLSError as error:
 
1257
                    except (gnutls.errors.GNUTLSError), error:
1527
1258
                        logger.warning("gnutls send failed")
1528
1259
                        return
1529
 
                    logger.debug("Sent: %d, remaining: %d",
 
1260
                    logger.debug(u"Sent: %d, remaining: %d",
1530
1261
                                 sent, len(client.secret)
1531
1262
                                 - (sent_size + sent))
1532
1263
                    sent_size += sent
1533
 
                
1534
 
                logger.info("Sending secret to %s", client.name)
1535
 
                # bump the timeout using extended_timeout
1536
 
                client.checked_ok(client.extended_timeout)
 
1264
 
 
1265
                logger.info(u"Sending secret to %s", client.name)
 
1266
                # bump the timeout as if seen
 
1267
                client.checked_ok()
1537
1268
                if self.server.use_dbus:
1538
1269
                    # Emit D-Bus signal
1539
1270
                    client.GotSecret()
1543
1274
                    client.approvals_pending -= 1
1544
1275
                try:
1545
1276
                    session.bye()
1546
 
                except gnutls.errors.GNUTLSError as error:
 
1277
                except (gnutls.errors.GNUTLSError), error:
1547
1278
                    logger.warning("GnuTLS bye failed")
1548
1279
    
1549
1280
    @staticmethod
1560
1291
                     .gnutls_certificate_get_peers
1561
1292
                     (session._c_object, ctypes.byref(list_size)))
1562
1293
        if not bool(cert_list) and list_size.value != 0:
1563
 
            raise gnutls.errors.GNUTLSError("error getting peer"
1564
 
                                            " certificate")
 
1294
            raise gnutls.errors.GNUTLSError(u"error getting peer"
 
1295
                                            u" certificate")
1565
1296
        if list_size.value == 0:
1566
1297
            return None
1567
1298
        cert = cert_list[0]
1593
1324
        if crtverify.value != 0:
1594
1325
            gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1595
1326
            raise (gnutls.errors.CertificateSecurityError
1596
 
                   ("Verify failed"))
 
1327
                   (u"Verify failed"))
1597
1328
        # New buffer for the fingerprint
1598
1329
        buf = ctypes.create_string_buffer(20)
1599
1330
        buf_len = ctypes.c_size_t()
1606
1337
        # Convert the buffer to a Python bytestring
1607
1338
        fpr = ctypes.string_at(buf, buf_len.value)
1608
1339
        # Convert the bytestring to hexadecimal notation
1609
 
        hex_fpr = ''.join("%02X" % ord(char) for char in fpr)
 
1340
        hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1610
1341
        return hex_fpr
1611
1342
 
1612
1343
 
1618
1349
        except:
1619
1350
            self.handle_error(request, address)
1620
1351
        self.close_request(request)
1621
 
    
 
1352
            
1622
1353
    def process_request(self, request, address):
1623
1354
        """Start a new process to process the request."""
1624
 
        proc = multiprocessing.Process(target = self.sub_process_main,
1625
 
                                       args = (request,
1626
 
                                               address))
1627
 
        proc.start()
1628
 
        return proc
1629
 
 
 
1355
        multiprocessing.Process(target = self.sub_process_main,
 
1356
                                args = (request, address)).start()
1630
1357
 
1631
1358
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1632
1359
    """ adds a pipe to the MixIn """
1636
1363
        This function creates a new pipe in self.pipe
1637
1364
        """
1638
1365
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
1639
 
        
1640
 
        proc = MultiprocessingMixIn.process_request(self, request,
1641
 
                                                    client_address)
 
1366
 
 
1367
        super(MultiprocessingMixInWithPipe,
 
1368
              self).process_request(request, client_address)
1642
1369
        self.child_pipe.close()
1643
 
        self.add_pipe(parent_pipe, proc)
1644
 
    
1645
 
    def add_pipe(self, parent_pipe, proc):
 
1370
        self.add_pipe(parent_pipe)
 
1371
 
 
1372
    def add_pipe(self, parent_pipe):
1646
1373
        """Dummy function; override as necessary"""
1647
 
        raise NotImplementedError
1648
 
 
 
1374
        pass
1649
1375
 
1650
1376
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
1651
1377
                     socketserver.TCPServer, object):
1669
1395
        bind to an address or port if they were not specified."""
1670
1396
        if self.interface is not None:
1671
1397
            if SO_BINDTODEVICE is None:
1672
 
                logger.error("SO_BINDTODEVICE does not exist;"
1673
 
                             " cannot bind to interface %s",
 
1398
                logger.error(u"SO_BINDTODEVICE does not exist;"
 
1399
                             u" cannot bind to interface %s",
1674
1400
                             self.interface)
1675
1401
            else:
1676
1402
                try:
1677
1403
                    self.socket.setsockopt(socket.SOL_SOCKET,
1678
1404
                                           SO_BINDTODEVICE,
1679
1405
                                           str(self.interface
1680
 
                                               + '\0'))
1681
 
                except socket.error as error:
 
1406
                                               + u'\0'))
 
1407
                except socket.error, error:
1682
1408
                    if error[0] == errno.EPERM:
1683
 
                        logger.error("No permission to"
1684
 
                                     " bind to interface %s",
 
1409
                        logger.error(u"No permission to"
 
1410
                                     u" bind to interface %s",
1685
1411
                                     self.interface)
1686
1412
                    elif error[0] == errno.ENOPROTOOPT:
1687
 
                        logger.error("SO_BINDTODEVICE not available;"
1688
 
                                     " cannot bind to interface %s",
 
1413
                        logger.error(u"SO_BINDTODEVICE not available;"
 
1414
                                     u" cannot bind to interface %s",
1689
1415
                                     self.interface)
1690
1416
                    else:
1691
1417
                        raise
1693
1419
        if self.server_address[0] or self.server_address[1]:
1694
1420
            if not self.server_address[0]:
1695
1421
                if self.address_family == socket.AF_INET6:
1696
 
                    any_address = "::" # in6addr_any
 
1422
                    any_address = u"::" # in6addr_any
1697
1423
                else:
1698
1424
                    any_address = socket.INADDR_ANY
1699
1425
                self.server_address = (any_address,
1726
1452
        self.enabled = False
1727
1453
        self.clients = clients
1728
1454
        if self.clients is None:
1729
 
            self.clients = {}
 
1455
            self.clients = set()
1730
1456
        self.use_dbus = use_dbus
1731
1457
        self.gnutls_priority = gnutls_priority
1732
1458
        IPv6_TCPServer.__init__(self, server_address,
1736
1462
    def server_activate(self):
1737
1463
        if self.enabled:
1738
1464
            return socketserver.TCPServer.server_activate(self)
1739
 
    
1740
1465
    def enable(self):
1741
1466
        self.enabled = True
1742
 
    
1743
 
    def add_pipe(self, parent_pipe, proc):
 
1467
    def add_pipe(self, parent_pipe):
1744
1468
        # Call "handle_ipc" for both data and EOF events
1745
1469
        gobject.io_add_watch(parent_pipe.fileno(),
1746
1470
                             gobject.IO_IN | gobject.IO_HUP,
1747
1471
                             functools.partial(self.handle_ipc,
1748
 
                                               parent_pipe =
1749
 
                                               parent_pipe,
1750
 
                                               proc = proc))
1751
 
    
 
1472
                                               parent_pipe = parent_pipe))
 
1473
        
1752
1474
    def handle_ipc(self, source, condition, parent_pipe=None,
1753
 
                   proc = None, client_object=None):
 
1475
                   client_object=None):
1754
1476
        condition_names = {
1755
 
            gobject.IO_IN: "IN",   # There is data to read.
1756
 
            gobject.IO_OUT: "OUT", # Data can be written (without
 
1477
            gobject.IO_IN: u"IN",   # There is data to read.
 
1478
            gobject.IO_OUT: u"OUT", # Data can be written (without
1757
1479
                                    # blocking).
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
 
1480
            gobject.IO_PRI: u"PRI", # There is urgent data to read.
 
1481
            gobject.IO_ERR: u"ERR", # Error condition.
 
1482
            gobject.IO_HUP: u"HUP"  # Hung up (the connection has been
1761
1483
                                    # broken, usually for pipes and
1762
1484
                                    # sockets).
1763
1485
            }
1765
1487
                                       for cond, name in
1766
1488
                                       condition_names.iteritems()
1767
1489
                                       if cond & condition)
1768
 
        # error, or the other end of multiprocessing.Pipe has closed
 
1490
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
 
1491
                     conditions_string)
 
1492
 
 
1493
        # error or the other end of multiprocessing.Pipe has closed
1769
1494
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
1770
 
            # Wait for other process to exit
1771
 
            proc.join()
1772
1495
            return False
1773
1496
        
1774
1497
        # Read a request from the child
1775
1498
        request = parent_pipe.recv()
 
1499
        logger.debug(u"IPC request: %s", repr(request))
1776
1500
        command = request[0]
1777
1501
        
1778
1502
        if command == 'init':
1779
1503
            fpr = request[1]
1780
1504
            address = request[2]
1781
1505
            
1782
 
            for c in self.clients.itervalues():
 
1506
            for c in self.clients:
1783
1507
                if c.fingerprint == fpr:
1784
1508
                    client = c
1785
1509
                    break
1786
1510
            else:
1787
 
                logger.info("Client not found for fingerprint: %s, ad"
1788
 
                            "dress: %s", fpr, address)
 
1511
                logger.warning(u"Client not found for fingerprint: %s, ad"
 
1512
                               u"dress: %s", fpr, address)
1789
1513
                if self.use_dbus:
1790
1514
                    # Emit D-Bus signal
1791
 
                    mandos_dbus_service.ClientNotFound(fpr,
1792
 
                                                       address[0])
 
1515
                    mandos_dbus_service.ClientNotFound(fpr, address)
1793
1516
                parent_pipe.send(False)
1794
1517
                return False
1795
1518
            
1796
1519
            gobject.io_add_watch(parent_pipe.fileno(),
1797
1520
                                 gobject.IO_IN | gobject.IO_HUP,
1798
1521
                                 functools.partial(self.handle_ipc,
1799
 
                                                   parent_pipe =
1800
 
                                                   parent_pipe,
1801
 
                                                   proc = proc,
1802
 
                                                   client_object =
1803
 
                                                   client))
 
1522
                                                   parent_pipe = parent_pipe,
 
1523
                                                   client_object = client))
1804
1524
            parent_pipe.send(True)
1805
 
            # remove the old hook in favor of the new above hook on
1806
 
            # same fileno
 
1525
            # remove the old hook in favor of the new above hook on same fileno
1807
1526
            return False
1808
1527
        if command == 'funcall':
1809
1528
            funcname = request[1]
1810
1529
            args = request[2]
1811
1530
            kwargs = request[3]
1812
1531
            
1813
 
            parent_pipe.send(('data', getattr(client_object,
1814
 
                                              funcname)(*args,
1815
 
                                                         **kwargs)))
1816
 
        
 
1532
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
 
1533
 
1817
1534
        if command == 'getattr':
1818
1535
            attrname = request[1]
1819
1536
            if callable(client_object.__getattribute__(attrname)):
1820
1537
                parent_pipe.send(('function',))
1821
1538
            else:
1822
 
                parent_pipe.send(('data', client_object
1823
 
                                  .__getattribute__(attrname)))
 
1539
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
1824
1540
        
1825
1541
        if command == 'setattr':
1826
1542
            attrname = request[1]
1827
1543
            value = request[2]
1828
1544
            setattr(client_object, attrname, value)
1829
 
        
 
1545
 
1830
1546
        return True
1831
1547
 
1832
1548
 
1833
1549
def string_to_delta(interval):
1834
1550
    """Parse a string and return a datetime.timedelta
1835
1551
    
1836
 
    >>> string_to_delta('7d')
 
1552
    >>> string_to_delta(u'7d')
1837
1553
    datetime.timedelta(7)
1838
 
    >>> string_to_delta('60s')
 
1554
    >>> string_to_delta(u'60s')
1839
1555
    datetime.timedelta(0, 60)
1840
 
    >>> string_to_delta('60m')
 
1556
    >>> string_to_delta(u'60m')
1841
1557
    datetime.timedelta(0, 3600)
1842
 
    >>> string_to_delta('24h')
 
1558
    >>> string_to_delta(u'24h')
1843
1559
    datetime.timedelta(1)
1844
 
    >>> string_to_delta('1w')
 
1560
    >>> string_to_delta(u'1w')
1845
1561
    datetime.timedelta(7)
1846
 
    >>> string_to_delta('5m 30s')
 
1562
    >>> string_to_delta(u'5m 30s')
1847
1563
    datetime.timedelta(0, 330)
1848
1564
    """
1849
1565
    timevalue = datetime.timedelta(0)
1851
1567
        try:
1852
1568
            suffix = unicode(s[-1])
1853
1569
            value = int(s[:-1])
1854
 
            if suffix == "d":
 
1570
            if suffix == u"d":
1855
1571
                delta = datetime.timedelta(value)
1856
 
            elif suffix == "s":
 
1572
            elif suffix == u"s":
1857
1573
                delta = datetime.timedelta(0, value)
1858
 
            elif suffix == "m":
 
1574
            elif suffix == u"m":
1859
1575
                delta = datetime.timedelta(0, 0, 0, 0, value)
1860
 
            elif suffix == "h":
 
1576
            elif suffix == u"h":
1861
1577
                delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1862
 
            elif suffix == "w":
 
1578
            elif suffix == u"w":
1863
1579
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1864
1580
            else:
1865
 
                raise ValueError("Unknown suffix %r" % suffix)
1866
 
        except (ValueError, IndexError) as e:
1867
 
            raise ValueError(*(e.args))
 
1581
                raise ValueError(u"Unknown suffix %r" % suffix)
 
1582
        except (ValueError, IndexError), e:
 
1583
            raise ValueError(e.message)
1868
1584
        timevalue += delta
1869
1585
    return timevalue
1870
1586
 
1871
1587
 
 
1588
def if_nametoindex(interface):
 
1589
    """Call the C function if_nametoindex(), or equivalent
 
1590
    
 
1591
    Note: This function cannot accept a unicode string."""
 
1592
    global if_nametoindex
 
1593
    try:
 
1594
        if_nametoindex = (ctypes.cdll.LoadLibrary
 
1595
                          (ctypes.util.find_library(u"c"))
 
1596
                          .if_nametoindex)
 
1597
    except (OSError, AttributeError):
 
1598
        logger.warning(u"Doing if_nametoindex the hard way")
 
1599
        def if_nametoindex(interface):
 
1600
            "Get an interface index the hard way, i.e. using fcntl()"
 
1601
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
 
1602
            with contextlib.closing(socket.socket()) as s:
 
1603
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
 
1604
                                    struct.pack(str(u"16s16x"),
 
1605
                                                interface))
 
1606
            interface_index = struct.unpack(str(u"I"),
 
1607
                                            ifreq[16:20])[0]
 
1608
            return interface_index
 
1609
    return if_nametoindex(interface)
 
1610
 
 
1611
 
1872
1612
def daemon(nochdir = False, noclose = False):
1873
1613
    """See daemon(3).  Standard BSD Unix function.
1874
1614
    
1877
1617
        sys.exit()
1878
1618
    os.setsid()
1879
1619
    if not nochdir:
1880
 
        os.chdir("/")
 
1620
        os.chdir(u"/")
1881
1621
    if os.fork():
1882
1622
        sys.exit()
1883
1623
    if not noclose:
1885
1625
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1886
1626
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1887
1627
            raise OSError(errno.ENODEV,
1888
 
                          "%s not a character device"
 
1628
                          u"%s not a character device"
1889
1629
                          % os.path.devnull)
1890
1630
        os.dup2(null, sys.stdin.fileno())
1891
1631
        os.dup2(null, sys.stdout.fileno())
1899
1639
    ##################################################################
1900
1640
    # Parsing of options, both command line and config file
1901
1641
    
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"
1916
 
                        " to terminal")
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"
1926
 
                        " files")
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",
1934
 
                        default=True)
1935
 
 
1936
 
    options = parser.parse_args()
 
1642
    parser = optparse.OptionParser(version = "%%prog %s" % version)
 
1643
    parser.add_option("-i", u"--interface", type=u"string",
 
1644
                      metavar="IF", help=u"Bind to interface IF")
 
1645
    parser.add_option("-a", u"--address", type=u"string",
 
1646
                      help=u"Address to listen for requests on")
 
1647
    parser.add_option("-p", u"--port", type=u"int",
 
1648
                      help=u"Port number to receive requests on")
 
1649
    parser.add_option("--check", action=u"store_true",
 
1650
                      help=u"Run self-test")
 
1651
    parser.add_option("--debug", action=u"store_true",
 
1652
                      help=u"Debug mode; run in foreground and log to"
 
1653
                      u" terminal")
 
1654
    parser.add_option("--debuglevel", type=u"string", metavar="Level",
 
1655
                      help=u"Debug level for stdout output")
 
1656
    parser.add_option("--priority", type=u"string", help=u"GnuTLS"
 
1657
                      u" priority string (see GnuTLS documentation)")
 
1658
    parser.add_option("--servicename", type=u"string",
 
1659
                      metavar=u"NAME", help=u"Zeroconf service name")
 
1660
    parser.add_option("--configdir", type=u"string",
 
1661
                      default=u"/etc/mandos", metavar=u"DIR",
 
1662
                      help=u"Directory to search for configuration"
 
1663
                      u" files")
 
1664
    parser.add_option("--no-dbus", action=u"store_false",
 
1665
                      dest=u"use_dbus", help=u"Do not provide D-Bus"
 
1666
                      u" system bus interface")
 
1667
    parser.add_option("--no-ipv6", action=u"store_false",
 
1668
                      dest=u"use_ipv6", help=u"Do not use IPv6")
 
1669
    options = parser.parse_args()[0]
1937
1670
    
1938
1671
    if options.check:
1939
1672
        import doctest
1941
1674
        sys.exit()
1942
1675
    
1943
1676
    # Default values for config file for server-global settings
1944
 
    server_defaults = { "interface": "",
1945
 
                        "address": "",
1946
 
                        "port": "",
1947
 
                        "debug": "False",
1948
 
                        "priority":
1949
 
                        "SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1950
 
                        "servicename": "Mandos",
1951
 
                        "use_dbus": "True",
1952
 
                        "use_ipv6": "True",
1953
 
                        "debuglevel": "",
 
1677
    server_defaults = { u"interface": u"",
 
1678
                        u"address": u"",
 
1679
                        u"port": u"",
 
1680
                        u"debug": u"False",
 
1681
                        u"priority":
 
1682
                        u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
 
1683
                        u"servicename": u"Mandos",
 
1684
                        u"use_dbus": u"True",
 
1685
                        u"use_ipv6": u"True",
 
1686
                        u"debuglevel": u"",
1954
1687
                        }
1955
1688
    
1956
1689
    # Parse config file for server-global settings
1957
1690
    server_config = configparser.SafeConfigParser(server_defaults)
1958
1691
    del server_defaults
1959
1692
    server_config.read(os.path.join(options.configdir,
1960
 
                                    "mandos.conf"))
 
1693
                                    u"mandos.conf"))
1961
1694
    # Convert the SafeConfigParser object to a dict
1962
1695
    server_settings = server_config.defaults()
1963
1696
    # 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",
 
1697
    for option in (u"debug", u"use_dbus", u"use_ipv6"):
 
1698
        server_settings[option] = server_config.getboolean(u"DEFAULT",
1966
1699
                                                           option)
1967
1700
    if server_settings["port"]:
1968
 
        server_settings["port"] = server_config.getint("DEFAULT",
1969
 
                                                       "port")
 
1701
        server_settings["port"] = server_config.getint(u"DEFAULT",
 
1702
                                                       u"port")
1970
1703
    del server_config
1971
1704
    
1972
1705
    # Override the settings from the config file with command line
1973
1706
    # options, if set.
1974
 
    for option in ("interface", "address", "port", "debug",
1975
 
                   "priority", "servicename", "configdir",
1976
 
                   "use_dbus", "use_ipv6", "debuglevel", "restore"):
 
1707
    for option in (u"interface", u"address", u"port", u"debug",
 
1708
                   u"priority", u"servicename", u"configdir",
 
1709
                   u"use_dbus", u"use_ipv6", u"debuglevel"):
1977
1710
        value = getattr(options, option)
1978
1711
        if value is not None:
1979
1712
            server_settings[option] = value
1987
1720
    ##################################################################
1988
1721
    
1989
1722
    # For convenience
1990
 
    debug = server_settings["debug"]
1991
 
    debuglevel = server_settings["debuglevel"]
1992
 
    use_dbus = server_settings["use_dbus"]
1993
 
    use_ipv6 = server_settings["use_ipv6"]
1994
 
    
1995
 
    if debug:
1996
 
        initlogger(logging.DEBUG)
1997
 
    else:
1998
 
        if not debuglevel:
1999
 
            initlogger()
2000
 
        else:
2001
 
            level = getattr(logging, debuglevel.upper())
2002
 
            initlogger(level)    
2003
 
    
2004
 
    if server_settings["servicename"] != "Mandos":
 
1723
    debug = server_settings[u"debug"]
 
1724
    debuglevel = server_settings[u"debuglevel"]
 
1725
    use_dbus = server_settings[u"use_dbus"]
 
1726
    use_ipv6 = server_settings[u"use_ipv6"]
 
1727
 
 
1728
    if server_settings[u"servicename"] != u"Mandos":
2005
1729
        syslogger.setFormatter(logging.Formatter
2006
 
                               ('Mandos (%s) [%%(process)d]:'
2007
 
                                ' %%(levelname)s: %%(message)s'
2008
 
                                % server_settings["servicename"]))
 
1730
                               (u'Mandos (%s) [%%(process)d]:'
 
1731
                                u' %%(levelname)s: %%(message)s'
 
1732
                                % server_settings[u"servicename"]))
2009
1733
    
2010
1734
    # Parse config file with clients
2011
 
    client_defaults = { "timeout": "5m",
2012
 
                        "extended_timeout": "15m",
2013
 
                        "interval": "2m",
2014
 
                        "checker": "fping -q -- %%(host)s",
2015
 
                        "host": "",
2016
 
                        "approval_delay": "0s",
2017
 
                        "approval_duration": "1s",
 
1735
    client_defaults = { u"timeout": u"1h",
 
1736
                        u"interval": u"5m",
 
1737
                        u"checker": u"fping -q -- %%(host)s",
 
1738
                        u"host": u"",
 
1739
                        u"approval_delay": u"0s",
 
1740
                        u"approval_duration": u"1s",
2018
1741
                        }
2019
1742
    client_config = configparser.SafeConfigParser(client_defaults)
2020
 
    client_config.read(os.path.join(server_settings["configdir"],
2021
 
                                    "clients.conf"))
 
1743
    client_config.read(os.path.join(server_settings[u"configdir"],
 
1744
                                    u"clients.conf"))
2022
1745
    
2023
1746
    global mandos_dbus_service
2024
1747
    mandos_dbus_service = None
2025
1748
    
2026
 
    tcp_server = MandosServer((server_settings["address"],
2027
 
                               server_settings["port"]),
 
1749
    tcp_server = MandosServer((server_settings[u"address"],
 
1750
                               server_settings[u"port"]),
2028
1751
                              ClientHandler,
2029
 
                              interface=(server_settings["interface"]
 
1752
                              interface=(server_settings[u"interface"]
2030
1753
                                         or None),
2031
1754
                              use_ipv6=use_ipv6,
2032
1755
                              gnutls_priority=
2033
 
                              server_settings["priority"],
 
1756
                              server_settings[u"priority"],
2034
1757
                              use_dbus=use_dbus)
2035
 
    if not debug:
2036
 
        pidfilename = "/var/run/mandos.pid"
2037
 
        try:
2038
 
            pidfile = open(pidfilename, "w")
2039
 
        except IOError:
2040
 
            logger.error("Could not open file %r", pidfilename)
 
1758
    pidfilename = u"/var/run/mandos.pid"
 
1759
    try:
 
1760
        pidfile = open(pidfilename, u"w")
 
1761
    except IOError:
 
1762
        logger.error(u"Could not open file %r", pidfilename)
2041
1763
    
2042
1764
    try:
2043
 
        uid = pwd.getpwnam("_mandos").pw_uid
2044
 
        gid = pwd.getpwnam("_mandos").pw_gid
 
1765
        uid = pwd.getpwnam(u"_mandos").pw_uid
 
1766
        gid = pwd.getpwnam(u"_mandos").pw_gid
2045
1767
    except KeyError:
2046
1768
        try:
2047
 
            uid = pwd.getpwnam("mandos").pw_uid
2048
 
            gid = pwd.getpwnam("mandos").pw_gid
 
1769
            uid = pwd.getpwnam(u"mandos").pw_uid
 
1770
            gid = pwd.getpwnam(u"mandos").pw_gid
2049
1771
        except KeyError:
2050
1772
            try:
2051
 
                uid = pwd.getpwnam("nobody").pw_uid
2052
 
                gid = pwd.getpwnam("nobody").pw_gid
 
1773
                uid = pwd.getpwnam(u"nobody").pw_uid
 
1774
                gid = pwd.getpwnam(u"nobody").pw_gid
2053
1775
            except KeyError:
2054
1776
                uid = 65534
2055
1777
                gid = 65534
2056
1778
    try:
2057
1779
        os.setgid(gid)
2058
1780
        os.setuid(uid)
2059
 
    except OSError as error:
 
1781
    except OSError, error:
2060
1782
        if error[0] != errno.EPERM:
2061
1783
            raise error
2062
1784
    
 
1785
    if not debug and not debuglevel:
 
1786
        syslogger.setLevel(logging.WARNING)
 
1787
        console.setLevel(logging.WARNING)
 
1788
    if debuglevel:
 
1789
        level = getattr(logging, debuglevel.upper())
 
1790
        syslogger.setLevel(level)
 
1791
        console.setLevel(level)
 
1792
 
2063
1793
    if debug:
2064
1794
        # Enable all possible GnuTLS debugging
2065
1795
        
2069
1799
        
2070
1800
        @gnutls.library.types.gnutls_log_func
2071
1801
        def debug_gnutls(level, string):
2072
 
            logger.debug("GnuTLS: %s", string[:-1])
 
1802
            logger.debug(u"GnuTLS: %s", string[:-1])
2073
1803
        
2074
1804
        (gnutls.library.functions
2075
1805
         .gnutls_global_set_log_function(debug_gnutls))
2083
1813
        # No console logging
2084
1814
        logger.removeHandler(console)
2085
1815
    
2086
 
    # Need to fork before connecting to D-Bus
2087
 
    if not debug:
2088
 
        # Close all input and output, do double fork, etc.
2089
 
        daemon()
2090
1816
    
2091
1817
    global main_loop
2092
1818
    # From the Avahi example code
2096
1822
    # End of Avahi example code
2097
1823
    if use_dbus:
2098
1824
        try:
2099
 
            bus_name = dbus.service.BusName("se.recompile.Mandos",
 
1825
            bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
2100
1826
                                            bus, do_not_queue=True)
2101
 
            old_bus_name = (dbus.service.BusName
2102
 
                            ("se.bsnet.fukt.Mandos", bus,
2103
 
                             do_not_queue=True))
2104
 
        except dbus.exceptions.NameExistsException as e:
2105
 
            logger.error(unicode(e) + ", disabling D-Bus")
 
1827
        except dbus.exceptions.NameExistsException, e:
 
1828
            logger.error(unicode(e) + u", disabling D-Bus")
2106
1829
            use_dbus = False
2107
 
            server_settings["use_dbus"] = False
 
1830
            server_settings[u"use_dbus"] = False
2108
1831
            tcp_server.use_dbus = False
2109
1832
    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)
 
1833
    service = AvahiService(name = server_settings[u"servicename"],
 
1834
                           servicetype = u"_mandos._tcp",
 
1835
                           protocol = protocol, bus = bus)
2114
1836
    if server_settings["interface"]:
2115
1837
        service.interface = (if_nametoindex
2116
 
                             (str(server_settings["interface"])))
2117
 
    
 
1838
                             (str(server_settings[u"interface"])))
 
1839
 
 
1840
    if not debug:
 
1841
        # Close all input and output, do double fork, etc.
 
1842
        daemon()
 
1843
        
2118
1844
    global multiprocessing_manager
2119
1845
    multiprocessing_manager = multiprocessing.Manager()
2120
1846
    
2121
1847
    client_class = Client
2122
1848
    if use_dbus:
2123
 
        client_class = functools.partial(ClientDBusTransitional,
2124
 
                                         bus = bus)
2125
 
    
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":
2130
 
            lambda section:
2131
 
            client_config.getboolean(section, "approved_by_default"),
2132
 
        }
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,
2137
 
                           dict((setting,
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())
2142
 
    
2143
 
    old_client_settings = {}
2144
 
    clients_data = []
2145
 
 
2146
 
    # Get client data and settings from last running state. 
2147
 
    if server_settings["restore"]:
2148
 
        try:
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:
2155
 
                raise
2156
 
 
2157
 
    for client in clients_data:
2158
 
        client_name = client["name"]
2159
 
        
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():
 
1849
        client_class = functools.partial(ClientDBus, bus = bus)
 
1850
    def client_config_items(config, section):
 
1851
        special_settings = {
 
1852
            "approved_by_default":
 
1853
                lambda: config.getboolean(section,
 
1854
                                          "approved_by_default"),
 
1855
            }
 
1856
        for name, value in config.items(section):
2166
1857
            try:
2167
 
                # For each value in new config, check if it differs
2168
 
                # from the old config value (Except for the "secret"
2169
 
                # attribute)
2170
 
                if name != "secret" and value != old_client_settings[client_name][name]:
2171
 
                    setattr(client, name, value)
 
1858
                yield (name, special_settings[name]())
2172
1859
            except KeyError:
2173
 
                pass
2174
 
 
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
2183
 
                else:
2184
 
                    client["expires"] = datetime.datetime.utcnow() + client["timeout"]
2185
 
 
2186
 
        client["changedstate"] = (multiprocessing_manager
2187
 
                                  .Condition(multiprocessing_manager
2188
 
                                             .Lock()))
2189
 
        if use_dbus:
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,
2201
 
                                              new_client.bus,
2202
 
                                              new_client.dbus_object_path)
2203
 
        else:
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)
2207
 
                
2208
 
        tcp_server.clients[client_name].decrypt_secret(
2209
 
            client_settings[client_name]["secret"])            
2210
 
        
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,
2216
 
                                                       config =
2217
 
                                                       client_settings
2218
 
                                                       [clientname]))
 
1860
                yield (name, value)
2219
1861
    
2220
 
 
 
1862
    tcp_server.clients.update(set(
 
1863
            client_class(name = section,
 
1864
                         config= dict(client_config_items(
 
1865
                        client_config, section)))
 
1866
            for section in client_config.sections()))
2221
1867
    if not tcp_server.clients:
2222
 
        logger.warning("No clients defined")
 
1868
        logger.warning(u"No clients defined")
2223
1869
        
 
1870
    try:
 
1871
        with pidfile:
 
1872
            pid = os.getpid()
 
1873
            pidfile.write(str(pid) + "\n")
 
1874
        del pidfile
 
1875
    except IOError:
 
1876
        logger.error(u"Could not write to file %r with PID %d",
 
1877
                     pidfilename, pid)
 
1878
    except NameError:
 
1879
        # "pidfile" was never created
 
1880
        pass
 
1881
    del pidfilename
 
1882
    
2224
1883
    if not debug:
2225
 
        try:
2226
 
            with pidfile:
2227
 
                pid = os.getpid()
2228
 
                pidfile.write(str(pid) + "\n".encode("utf-8"))
2229
 
            del pidfile
2230
 
        except IOError:
2231
 
            logger.error("Could not write to file %r with PID %d",
2232
 
                         pidfilename, pid)
2233
 
        except NameError:
2234
 
            # "pidfile" was never created
2235
 
            pass
2236
 
        del pidfilename
2237
 
        
2238
1884
        signal.signal(signal.SIGINT, signal.SIG_IGN)
2239
 
    
2240
1885
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
2241
1886
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
2242
1887
    
2244
1889
        class MandosDBusService(dbus.service.Object):
2245
1890
            """A D-Bus proxy object"""
2246
1891
            def __init__(self):
2247
 
                dbus.service.Object.__init__(self, bus, "/")
2248
 
            _interface = "se.recompile.Mandos"
 
1892
                dbus.service.Object.__init__(self, bus, u"/")
 
1893
            _interface = u"se.bsnet.fukt.Mandos"
2249
1894
            
2250
 
            @dbus.service.signal(_interface, signature="o")
 
1895
            @dbus.service.signal(_interface, signature=u"o")
2251
1896
            def ClientAdded(self, objpath):
2252
1897
                "D-Bus signal"
2253
1898
                pass
2254
1899
            
2255
 
            @dbus.service.signal(_interface, signature="ss")
 
1900
            @dbus.service.signal(_interface, signature=u"ss")
2256
1901
            def ClientNotFound(self, fingerprint, address):
2257
1902
                "D-Bus signal"
2258
1903
                pass
2259
1904
            
2260
 
            @dbus.service.signal(_interface, signature="os")
 
1905
            @dbus.service.signal(_interface, signature=u"os")
2261
1906
            def ClientRemoved(self, objpath, name):
2262
1907
                "D-Bus signal"
2263
1908
                pass
2264
1909
            
2265
 
            @dbus.service.method(_interface, out_signature="ao")
 
1910
            @dbus.service.method(_interface, out_signature=u"ao")
2266
1911
            def GetAllClients(self):
2267
1912
                "D-Bus method"
2268
1913
                return dbus.Array(c.dbus_object_path
2269
 
                                  for c in
2270
 
                                  tcp_server.clients.itervalues())
 
1914
                                  for c in tcp_server.clients)
2271
1915
            
2272
1916
            @dbus.service.method(_interface,
2273
 
                                 out_signature="a{oa{sv}}")
 
1917
                                 out_signature=u"a{oa{sv}}")
2274
1918
            def GetAllClientsWithProperties(self):
2275
1919
                "D-Bus method"
2276
1920
                return dbus.Dictionary(
2277
 
                    ((c.dbus_object_path, c.GetAll(""))
2278
 
                     for c in tcp_server.clients.itervalues()),
2279
 
                    signature="oa{sv}")
 
1921
                    ((c.dbus_object_path, c.GetAll(u""))
 
1922
                     for c in tcp_server.clients),
 
1923
                    signature=u"oa{sv}")
2280
1924
            
2281
 
            @dbus.service.method(_interface, in_signature="o")
 
1925
            @dbus.service.method(_interface, in_signature=u"o")
2282
1926
            def RemoveClient(self, object_path):
2283
1927
                "D-Bus method"
2284
 
                for c in tcp_server.clients.itervalues():
 
1928
                for c in tcp_server.clients:
2285
1929
                    if c.dbus_object_path == object_path:
2286
 
                        del tcp_server.clients[c.name]
 
1930
                        tcp_server.clients.remove(c)
2287
1931
                        c.remove_from_connection()
2288
1932
                        # Don't signal anything except ClientRemoved
2289
1933
                        c.disable(quiet=True)
2294
1938
            
2295
1939
            del _interface
2296
1940
        
2297
 
        class MandosDBusServiceTransitional(MandosDBusService):
2298
 
            __metaclass__ = AlternateDBusNamesMetaclass
2299
 
        mandos_dbus_service = MandosDBusServiceTransitional()
 
1941
        mandos_dbus_service = MandosDBusService()
2300
1942
    
2301
1943
    def cleanup():
2302
1944
        "Cleanup function; run on exit"
2303
1945
        service.cleanup()
2304
1946
        
2305
 
        multiprocessing.active_children()
2306
 
        if not (tcp_server.clients or client_settings):
2307
 
            return
2308
 
 
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.
2312
 
        clients = []
2313
 
        for client in tcp_server.clients.itervalues():
2314
 
            client.encrypt_secret(client_settings[client.name]["secret"])
2315
 
 
2316
 
            client_dict = {}
2317
 
 
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):
2321
 
                exclude.add(name)
2322
 
                
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)
2327
 
 
2328
 
            clients.append(client_dict) 
2329
 
            del client_settings[client.name]["secret"]
2330
 
            
2331
 
        try:
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:
2337
 
                raise
2338
 
 
2339
 
        # Delete all clients, and settings from config
2340
1947
        while tcp_server.clients:
2341
 
            name, client = tcp_server.clients.popitem()
 
1948
            client = tcp_server.clients.pop()
2342
1949
            if use_dbus:
2343
1950
                client.remove_from_connection()
 
1951
            client.disable_hook = None
2344
1952
            # Don't signal anything except ClientRemoved
2345
1953
            client.disable(quiet=True)
2346
1954
            if use_dbus:
2347
1955
                # Emit D-Bus signal
2348
 
                mandos_dbus_service.ClientRemoved(client
2349
 
                                                  .dbus_object_path,
 
1956
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
2350
1957
                                                  client.name)
2351
 
        client_settings.clear()
2352
1958
    
2353
1959
    atexit.register(cleanup)
2354
1960
    
2355
 
    for client in tcp_server.clients.itervalues():
 
1961
    for client in tcp_server.clients:
2356
1962
        if use_dbus:
2357
1963
            # Emit D-Bus signal
2358
1964
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
2359
 
        # Need to initiate checking of clients
2360
 
        if client.enabled:
2361
 
            client.init_checker()
2362
 
 
 
1965
        client.enable()
2363
1966
    
2364
1967
    tcp_server.enable()
2365
1968
    tcp_server.server_activate()
2367
1970
    # Find out what port we got
2368
1971
    service.port = tcp_server.socket.getsockname()[1]
2369
1972
    if use_ipv6:
2370
 
        logger.info("Now listening on address %r, port %d,"
 
1973
        logger.info(u"Now listening on address %r, port %d,"
2371
1974
                    " flowinfo %d, scope_id %d"
2372
1975
                    % tcp_server.socket.getsockname())
2373
1976
    else:                       # IPv4
2374
 
        logger.info("Now listening on address %r, port %d"
 
1977
        logger.info(u"Now listening on address %r, port %d"
2375
1978
                    % tcp_server.socket.getsockname())
2376
1979
    
2377
1980
    #service.interface = tcp_server.socket.getsockname()[3]
2380
1983
        # From the Avahi example code
2381
1984
        try:
2382
1985
            service.activate()
2383
 
        except dbus.exceptions.DBusException as error:
2384
 
            logger.critical("DBusException: %s", error)
 
1986
        except dbus.exceptions.DBusException, error:
 
1987
            logger.critical(u"DBusException: %s", error)
2385
1988
            cleanup()
2386
1989
            sys.exit(1)
2387
1990
        # End of Avahi example code
2391
1994
                             (tcp_server.handle_request
2392
1995
                              (*args[2:], **kwargs) or True))
2393
1996
        
2394
 
        logger.debug("Starting main loop")
 
1997
        logger.debug(u"Starting main loop")
2395
1998
        main_loop.run()
2396
 
    except AvahiError as error:
2397
 
        logger.critical("AvahiError: %s", error)
 
1999
    except AvahiError, error:
 
2000
        logger.critical(u"AvahiError: %s", error)
2398
2001
        cleanup()
2399
2002
        sys.exit(1)
2400
2003
    except KeyboardInterrupt:
2401
2004
        if debug:
2402
 
            print("", file=sys.stderr)
2403
 
        logger.debug("Server received KeyboardInterrupt")
2404
 
    logger.debug("Server exiting")
 
2005
            print >> sys.stderr
 
2006
        logger.debug(u"Server received KeyboardInterrupt")
 
2007
    logger.debug(u"Server exiting")
2405
2008
    # Must run before the D-Bus bus name gets deregistered
2406
2009
    cleanup()
2407
2010
 
2408
 
 
2409
2011
if __name__ == '__main__':
2410
2012
    main()