/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk
3 by Björn Påhlsson
Python based server
1
#!/usr/bin/python
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
2
# -*- mode: python; coding: utf-8 -*-
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
3
# 
4
# Mandos server - give out binary blobs to connecting clients.
5
# 
6
# This program is partly derived from an example program for an Avahi
7
# service publisher, downloaded from
8
# <http://avahi.org/wiki/PythonPublishExample>.  This includes the
336 by Teddy Hogeborn
Code cleanup.
9
# methods "add", "remove", "server_state_changed",
10
# "entry_group_state_changed", "cleanup", and "activate" in the
11
# "AvahiService" class, and some lines in "main".
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
12
# 
28 by Teddy Hogeborn
* server.conf: New file.
13
# Everything else is
246 by Teddy Hogeborn
* README: Update copyright year; add "2009".
14
# Copyright © 2008,2009 Teddy Hogeborn
15
# Copyright © 2008,2009 Björn Påhlsson
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
16
# 
17
# This program is free software: you can redistribute it and/or modify
18
# it under the terms of the GNU General Public License as published by
19
# the Free Software Foundation, either version 3 of the License, or
20
# (at your option) any later version.
21
#
22
#     This program is distributed in the hope that it will be useful,
23
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
24
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25
#     GNU General Public License for more details.
26
# 
27
# You should have received a copy of the GNU General Public License
109 by Teddy Hogeborn
* .bzrignore: New.
28
# along with this program.  If not, see
29
# <http://www.gnu.org/licenses/>.
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
30
# 
28 by Teddy Hogeborn
* server.conf: New file.
31
# Contact the authors at <mandos@fukt.bsnet.se>.
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
32
# 
3 by Björn Påhlsson
Python based server
33
237 by Teddy Hogeborn
* mandos: Also import "with_statement" and "absolute_import" from
34
from __future__ import division, with_statement, absolute_import
10 by Teddy Hogeborn
* server.py: Bug fix: Do "from __future__ import division".
35
335 by Teddy Hogeborn
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
36
import SocketServer as socketserver
3 by Björn Påhlsson
Python based server
37
import socket
237.2.1 by Teddy Hogeborn
Merge from trunk, but disable the unfinished D-Bus feature:
38
import optparse
3 by Björn Påhlsson
Python based server
39
import datetime
40
import errno
41
import gnutls.crypto
42
import gnutls.connection
43
import gnutls.errors
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
44
import gnutls.library.functions
45
import gnutls.library.constants
46
import gnutls.library.types
335 by Teddy Hogeborn
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
47
import ConfigParser as configparser
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
48
import sys
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
49
import re
50
import os
51
import signal
10 by Teddy Hogeborn
* server.py: Bug fix: Do "from __future__ import division".
52
import subprocess
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
53
import atexit
54
import stat
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
55
import logging
56
import logging.handlers
163 by Teddy Hogeborn
* Makefile (PIDDIR, USER, GROUP): Removed.
57
import pwd
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
58
import contextlib
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
59
import struct
60
import fcntl
337 by Teddy Hogeborn
Code cleanup. Move some global stuff into main.
61
import functools
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
62
import cPickle as pickle
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
63
import multiprocessing
5 by Teddy Hogeborn
* server.py (server_metaclass): New.
64
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
65
import dbus
237.1.1 by Teddy Hogeborn
First steps of a D-Bus interface to the server.
66
import dbus.service
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
67
import gobject
68
import avahi
69
from dbus.mainloop.glib import DBusGMainLoop
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
70
import ctypes
215 by Teddy Hogeborn
* mandos: Remove unused "select" module. Import "ctypes.util".
71
import ctypes.util
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
72
import xml.dom.minidom
73
import inspect
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
74
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
75
try:
76
    SO_BINDTODEVICE = socket.SO_BINDTODEVICE
77
except AttributeError:
78
    try:
79
        from IN import SO_BINDTODEVICE
80
    except ImportError:
338 by Teddy Hogeborn
* mandos: Minor doc string fixes.
81
        SO_BINDTODEVICE = None
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
82
83
237.2.32 by Teddy Hogeborn
* Makefile (version): Changed to "1.0.14".
84
version = "1.0.14"
13 by Björn Påhlsson
Added following support:
85
24.1.154 by Björn Påhlsson
merge
86
#logger = logging.getLogger(u'mandos')
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
87
logger = logging.Logger(u'mandos')
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
88
syslogger = (logging.handlers.SysLogHandler
89
             (facility = logging.handlers.SysLogHandler.LOG_DAEMON,
90
              address = "/dev/log"))
91
syslogger.setFormatter(logging.Formatter
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
92
                       (u'Mandos [%(process)d]: %(levelname)s:'
93
                        u' %(message)s'))
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
94
logger.addHandler(syslogger)
13 by Björn Påhlsson
Added following support:
95
61 by Teddy Hogeborn
* mandos (console): Define handler globally.
96
console = logging.StreamHandler()
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
97
console.setFormatter(logging.Formatter(u'%(name)s [%(process)d]:'
98
                                       u' %(levelname)s:'
99
                                       u' %(message)s'))
61 by Teddy Hogeborn
* mandos (console): Define handler globally.
100
logger.addHandler(console)
28 by Teddy Hogeborn
* server.conf: New file.
101
102
class AvahiError(Exception):
242 by Teddy Hogeborn
* mandos (AvahiError): Converted to use unicode. All users changed.
103
    def __init__(self, value, *args, **kwargs):
28 by Teddy Hogeborn
* server.conf: New file.
104
        self.value = value
242 by Teddy Hogeborn
* mandos (AvahiError): Converted to use unicode. All users changed.
105
        super(AvahiError, self).__init__(value, *args, **kwargs)
106
    def __unicode__(self):
107
        return unicode(repr(self.value))
28 by Teddy Hogeborn
* server.conf: New file.
108
109
class AvahiServiceError(AvahiError):
110
    pass
111
112
class AvahiGroupError(AvahiError):
113
    pass
114
115
116
class AvahiService(object):
45 by Teddy Hogeborn
* server.py: Cosmetic changes.
117
    """An Avahi (Zeroconf) service.
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
118
    
45 by Teddy Hogeborn
* server.py: Cosmetic changes.
119
    Attributes:
28 by Teddy Hogeborn
* server.conf: New file.
120
    interface: integer; avahi.IF_UNSPEC or an interface index.
121
               Used to optionally bind to the specified interface.
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
122
    name: string; Example: u'Mandos'
123
    type: string; Example: u'_mandos._tcp'.
45 by Teddy Hogeborn
* server.py: Cosmetic changes.
124
                  See <http://www.dns-sd.org/ServiceTypes.html>
125
    port: integer; what port to announce
126
    TXT: list of strings; TXT record for the service
127
    domain: string; Domain to publish on, default to .local if empty.
128
    host: string; Host to publish records for, default is localhost
129
    max_renames: integer; maximum number of renames
130
    rename_count: integer; counter so we only rename after collisions
131
                  a sensible number of times
336 by Teddy Hogeborn
Code cleanup.
132
    group: D-Bus Entry Group
133
    server: D-Bus Server
338 by Teddy Hogeborn
* mandos: Minor doc string fixes.
134
    bus: dbus.SystemBus()
28 by Teddy Hogeborn
* server.conf: New file.
135
    """
136
    def __init__(self, interface = avahi.IF_UNSPEC, name = None,
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
137
                 servicetype = None, port = None, TXT = None,
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
138
                 domain = u"", host = u"", max_renames = 32768,
337 by Teddy Hogeborn
Code cleanup. Move some global stuff into main.
139
                 protocol = avahi.PROTO_UNSPEC, bus = None):
28 by Teddy Hogeborn
* server.conf: New file.
140
        self.interface = interface
141
        self.name = name
215 by Teddy Hogeborn
* mandos: Remove unused "select" module. Import "ctypes.util".
142
        self.type = servicetype
28 by Teddy Hogeborn
* server.conf: New file.
143
        self.port = port
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
144
        self.TXT = TXT if TXT is not None else []
28 by Teddy Hogeborn
* server.conf: New file.
145
        self.domain = domain
146
        self.host = host
147
        self.rename_count = 0
76 by Teddy Hogeborn
* plugins.d/password-request.c (init_gnutls_global): Renamed
148
        self.max_renames = max_renames
314 by Teddy Hogeborn
Support not using IPv6 in server:
149
        self.protocol = protocol
336 by Teddy Hogeborn
Code cleanup.
150
        self.group = None       # our entry group
151
        self.server = None
337 by Teddy Hogeborn
Code cleanup. Move some global stuff into main.
152
        self.bus = bus
28 by Teddy Hogeborn
* server.conf: New file.
153
    def rename(self):
154
        """Derived from the Avahi example code"""
155
        if self.rename_count >= self.max_renames:
109 by Teddy Hogeborn
* .bzrignore: New.
156
            logger.critical(u"No suitable Zeroconf service name found"
157
                            u" after %i retries, exiting.",
215 by Teddy Hogeborn
* mandos: Remove unused "select" module. Import "ctypes.util".
158
                            self.rename_count)
242 by Teddy Hogeborn
* mandos (AvahiError): Converted to use unicode. All users changed.
159
            raise AvahiServiceError(u"Too many renames")
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
160
        self.name = unicode(self.server.GetAlternativeServiceName(self.name))
109 by Teddy Hogeborn
* .bzrignore: New.
161
        logger.info(u"Changing Zeroconf service name to %r ...",
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
162
                    self.name)
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
163
        syslogger.setFormatter(logging.Formatter
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
164
                               (u'Mandos (%s) [%%(process)d]:'
165
                                u' %%(levelname)s: %%(message)s'
327 by Teddy Hogeborn
Merge from pipe IPC branch.
166
                                % self.name))
28 by Teddy Hogeborn
* server.conf: New file.
167
        self.remove()
24.1.160 by Björn Påhlsson
fixed bug with local name collisions after a non-local name collision
168
        try:
169
            self.add()
170
        except dbus.exceptions.DBusException, error:
171
            logger.critical(u"DBusException: %s", error)
172
            self.cleanup()
173
            os._exit(1)
28 by Teddy Hogeborn
* server.conf: New file.
174
        self.rename_count += 1
175
    def remove(self):
176
        """Derived from the Avahi example code"""
336 by Teddy Hogeborn
Code cleanup.
177
        if self.group is not None:
178
            self.group.Reset()
28 by Teddy Hogeborn
* server.conf: New file.
179
    def add(self):
180
        """Derived from the Avahi example code"""
336 by Teddy Hogeborn
Code cleanup.
181
        if self.group is None:
182
            self.group = dbus.Interface(
337 by Teddy Hogeborn
Code cleanup. Move some global stuff into main.
183
                self.bus.get_object(avahi.DBUS_NAME,
184
                                    self.server.EntryGroupNew()),
336 by Teddy Hogeborn
Code cleanup.
185
                avahi.DBUS_INTERFACE_ENTRY_GROUP)
186
            self.group.connect_to_signal('StateChanged',
379 by Teddy Hogeborn
* mandos: Fix line lengths.
187
                                         self
188
                                         .entry_group_state_changed)
109 by Teddy Hogeborn
* .bzrignore: New.
189
        logger.debug(u"Adding Zeroconf service '%s' of type '%s' ...",
336 by Teddy Hogeborn
Code cleanup.
190
                     self.name, self.type)
191
        self.group.AddService(
192
            self.interface,
193
            self.protocol,
194
            dbus.UInt32(0),     # flags
195
            self.name, self.type,
196
            self.domain, self.host,
197
            dbus.UInt16(self.port),
198
            avahi.string_array_to_txt_array(self.TXT))
199
        self.group.Commit()
200
    def entry_group_state_changed(self, state, error):
201
        """Derived from the Avahi example code"""
417 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
202
        logger.debug(u"Avahi entry group state change: %i", state)
336 by Teddy Hogeborn
Code cleanup.
203
        
204
        if state == avahi.ENTRY_GROUP_ESTABLISHED:
205
            logger.debug(u"Zeroconf service established.")
206
        elif state == avahi.ENTRY_GROUP_COLLISION:
207
            logger.warning(u"Zeroconf service name collision.")
208
            self.rename()
209
        elif state == avahi.ENTRY_GROUP_FAILURE:
210
            logger.critical(u"Avahi: Error in group state changed %s",
211
                            unicode(error))
212
            raise AvahiGroupError(u"State changed: %s"
213
                                  % unicode(error))
214
    def cleanup(self):
215
        """Derived from the Avahi example code"""
216
        if self.group is not None:
217
            self.group.Free()
218
            self.group = None
219
    def server_state_changed(self, state):
220
        """Derived from the Avahi example code"""
417 by Teddy Hogeborn
* mandos (AvahiService.entry_group_state_changed): Better debug log
221
        logger.debug(u"Avahi server state change: %i", state)
336 by Teddy Hogeborn
Code cleanup.
222
        if state == avahi.SERVER_COLLISION:
223
            logger.error(u"Zeroconf server name collision")
224
            self.remove()
225
        elif state == avahi.SERVER_RUNNING:
226
            self.add()
227
    def activate(self):
228
        """Derived from the Avahi example code"""
229
        if self.server is None:
230
            self.server = dbus.Interface(
337 by Teddy Hogeborn
Code cleanup. Move some global stuff into main.
231
                self.bus.get_object(avahi.DBUS_NAME,
232
                                    avahi.DBUS_PATH_SERVER),
336 by Teddy Hogeborn
Code cleanup.
233
                avahi.DBUS_INTERFACE_SERVER)
234
        self.server.connect_to_signal(u"StateChanged",
235
                                 self.server_state_changed)
236
        self.server_state_changed(self.server.GetState())
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
237
238
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
239
class Client(object):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
240
    """A representation of a client host served by this server.
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
241
    
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
242
    Attributes:
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
243
    name:       string; from the config file, used in log messages and
244
                        D-Bus identifiers
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
245
    fingerprint: string (40 or 32 hexadecimal digits); used to
246
                 uniquely identify the client
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
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
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
250
    last_enabled: datetime.datetime(); (UTC)
251
    enabled:    bool()
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
252
    last_checked_ok: datetime.datetime(); (UTC) or None
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
253
    timeout:    datetime.timedelta(); How long from last_checked_ok
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
254
                                      until this client is disabled
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
255
    interval:   datetime.timedelta(); How often to start a new checker
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
256
    disable_hook:  If set, called by disable() as disable_hook(self)
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
257
    checker:    subprocess.Popen(); a running checker process used
258
                                    to see if the client lives.
259
                                    'None' if no process is running.
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
260
    checker_initiator_tag: a gobject event source tag, or None
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
261
    disable_initiator_tag: - '' -
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
262
    checker_callback_tag:  - '' -
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
263
    checker_command: string; External command which is run to check if
28 by Teddy Hogeborn
* server.conf: New file.
264
                     client lives.  %() expansions are done at
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
265
                     runtime with vars(self) as dict, so that for
266
                     instance %(name)s can be used in the command.
315 by Teddy Hogeborn
* mandos (Client.current_checker_command): New attribute.
267
    current_checker_command: string; current running checker_command
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
268
    approved_delay: datetime.timedelta(); Time to wait for approval
269
    _approved:   bool(); 'None' if not yet approved/disapproved
270
    approved_duration: datetime.timedelta(); Duration of one approval
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
271
    """
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
272
    
273
    @staticmethod
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
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))
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
279
    
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
280
    def timeout_milliseconds(self):
281
        "Return the 'timeout' attribute in milliseconds"
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
282
        return self._timedelta_to_milliseconds(self.timeout)
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
283
    
284
    def interval_milliseconds(self):
285
        "Return the 'interval' attribute in milliseconds"
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
286
        return self._timedelta_to_milliseconds(self.interval)
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
287
288
    def approved_delay_milliseconds(self):
289
        return self._timedelta_to_milliseconds(self.approved_delay)
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
290
    
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
291
    def __init__(self, name = None, disable_hook=None, config=None):
45 by Teddy Hogeborn
* server.py: Cosmetic changes.
292
        """Note: the 'checker' key in 'config' sets the
293
        'checker_command' attribute and *not* the 'checker'
294
        attribute."""
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
295
        self.name = name
215 by Teddy Hogeborn
* mandos: Remove unused "select" module. Import "ctypes.util".
296
        if config is None:
297
            config = {}
25 by Teddy Hogeborn
* mandos-clients.conf ([DEFAULT]): New section.
298
        logger.debug(u"Creating client %r", self.name)
45 by Teddy Hogeborn
* server.py: Cosmetic changes.
299
        # Uppercase and remove spaces from fingerprint for later
300
        # comparison purposes with return value from the fingerprint()
301
        # function
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
302
        self.fingerprint = (config[u"fingerprint"].upper()
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
303
                            .replace(u" ", u""))
25 by Teddy Hogeborn
* mandos-clients.conf ([DEFAULT]): New section.
304
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
305
        if u"secret" in config:
306
            self.secret = config[u"secret"].decode(u"base64")
307
        elif u"secfile" in config:
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
308
            with open(os.path.expanduser(os.path.expandvars
309
                                         (config[u"secfile"])),
310
                      "rb") as secfile:
237 by Teddy Hogeborn
* mandos: Also import "with_statement" and "absolute_import" from
311
                self.secret = secfile.read()
3 by Björn Påhlsson
Python based server
312
        else:
28 by Teddy Hogeborn
* server.conf: New file.
313
            raise TypeError(u"No secret or secfile for client %s"
314
                            % self.name)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
315
        self.host = config.get(u"host", u"")
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
316
        self.created = datetime.datetime.utcnow()
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
317
        self.enabled = False
318
        self.last_enabled = None
28 by Teddy Hogeborn
* server.conf: New file.
319
        self.last_checked_ok = None
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
320
        self.timeout = string_to_delta(config[u"timeout"])
321
        self.interval = string_to_delta(config[u"interval"])
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
322
        self.disable_hook = disable_hook
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
323
        self.checker = None
324
        self.checker_initiator_tag = None
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
325
        self.disable_initiator_tag = None
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
326
        self.checker_callback_tag = None
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
327
        self.checker_command = config[u"checker"]
315 by Teddy Hogeborn
* mandos (Client.current_checker_command): New attribute.
328
        self.current_checker_command = None
279 by Teddy Hogeborn
* mandos (Client.__init__): Disable D-Bus during init to avoid
329
        self.last_connect = None
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
330
        self._approved = None
331
        self.approved_by_default = config.get(u"approved_by_default",
24.2.1 by teddy at bsnet
* debian/control (mandos/Depends): Added "python-urwid".
332
                                              True)
24.1.150 by Björn Påhlsson
* mandos: Added ClientDBus.approve_pending property. Exposed
333
        self.approvals_pending = 0
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
334
        self.approved_delay = string_to_delta(
335
            config[u"approved_delay"])
336
        self.approved_duration = string_to_delta(
337
            config[u"approved_duration"])
338
        self.changedstate = multiprocessing_manager.Condition(multiprocessing_manager.Lock())
24.2.4 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Changed to be a property
339
    
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
340
    def send_changedstate(self):
341
        self.changedstate.acquire()
342
        self.changedstate.notify_all()
343
        self.changedstate.release()
344
        
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
345
    def enable(self):
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
346
        """Start this client's checker and timeout hooks"""
341 by Teddy Hogeborn
Code cleanup and one bug fix.
347
        if getattr(self, u"enabled", False):
348
            # Already enabled
349
            return
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
350
        self.send_changedstate()
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
351
        self.last_enabled = datetime.datetime.utcnow()
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
352
        # Schedule a new checker to be started an 'interval' from now,
353
        # and every interval from then on.
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
354
        self.checker_initiator_tag = (gobject.timeout_add
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
355
                                      (self.interval_milliseconds(),
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
356
                                       self.start_checker))
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
357
        # Schedule a disable() when 'timeout' has passed
358
        self.disable_initiator_tag = (gobject.timeout_add
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
359
                                   (self.timeout_milliseconds(),
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
360
                                    self.disable))
361
        self.enabled = True
400 by Teddy Hogeborn
* mandos (Client.enable): Bug fix: Start new immediate checker last to
362
        # Also start a new checker *right now*.
363
        self.start_checker()
237.1.1 by Teddy Hogeborn
First steps of a D-Bus interface to the server.
364
    
402 by Teddy Hogeborn
* mandos (Client.disable): Rename keyword argument "log" to "quiet",
365
    def disable(self, quiet=True):
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
366
        """Disable this client."""
367
        if not getattr(self, "enabled", False):
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
368
            return False
402 by Teddy Hogeborn
* mandos (Client.disable): Rename keyword argument "log" to "quiet",
369
        if not quiet:
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
370
            self.send_changedstate()
371
        if not quiet:
400 by Teddy Hogeborn
* mandos (Client.enable): Bug fix: Start new immediate checker last to
372
            logger.info(u"Disabling client %s", self.name)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
373
        if getattr(self, u"disable_initiator_tag", False):
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
374
            gobject.source_remove(self.disable_initiator_tag)
375
            self.disable_initiator_tag = None
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
376
        if getattr(self, u"checker_initiator_tag", False):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
377
            gobject.source_remove(self.checker_initiator_tag)
378
            self.checker_initiator_tag = None
379
        self.stop_checker()
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
380
        if self.disable_hook:
381
            self.disable_hook(self)
382
        self.enabled = False
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
383
        # Do not run this again if called by a gobject.timeout_add
384
        return False
237.1.1 by Teddy Hogeborn
First steps of a D-Bus interface to the server.
385
    
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
386
    def __del__(self):
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
387
        self.disable_hook = None
388
        self.disable()
237.1.1 by Teddy Hogeborn
First steps of a D-Bus interface to the server.
389
    
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
390
    def checker_callback(self, pid, condition, command):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
391
        """The checker has completed, so take appropriate actions."""
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
392
        self.checker_callback_tag = None
393
        self.checker = None
280 by Teddy Hogeborn
* mandos (Client.CheckerCompleted): Changed signature to "nxs"; return
394
        if os.WIFEXITED(condition):
395
            exitstatus = os.WEXITSTATUS(condition)
396
            if exitstatus == 0:
397
                logger.info(u"Checker for %(name)s succeeded",
398
                            vars(self))
281 by Teddy Hogeborn
* mandos (Client.bump_timeout): Renamed to "checked_ok". All callers
399
                self.checked_ok()
280 by Teddy Hogeborn
* mandos (Client.CheckerCompleted): Changed signature to "nxs"; return
400
            else:
401
                logger.info(u"Checker for %(name)s failed",
402
                            vars(self))
403
        else:
13 by Björn Påhlsson
Added following support:
404
            logger.warning(u"Checker for %(name)s crashed?",
405
                           vars(self))
237.1.1 by Teddy Hogeborn
First steps of a D-Bus interface to the server.
406
    
281 by Teddy Hogeborn
* mandos (Client.bump_timeout): Renamed to "checked_ok". All callers
407
    def checked_ok(self):
237 by Teddy Hogeborn
* mandos: Also import "with_statement" and "absolute_import" from
408
        """Bump up the timeout for this client.
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
409
        
237 by Teddy Hogeborn
* mandos: Also import "with_statement" and "absolute_import" from
410
        This should only be called when the client has been seen,
411
        alive and well.
412
        """
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
413
        self.last_checked_ok = datetime.datetime.utcnow()
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
414
        gobject.source_remove(self.disable_initiator_tag)
415
        self.disable_initiator_tag = (gobject.timeout_add
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
416
                                      (self.timeout_milliseconds(),
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
417
                                       self.disable))
237.1.1 by Teddy Hogeborn
First steps of a D-Bus interface to the server.
418
    
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
419
    def start_checker(self):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
420
        """Start a new checker subprocess if one is not running.
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
421
        
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
422
        If a checker already exists, leave it running and do
423
        nothing."""
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
424
        # The reason for not killing a running checker is that if we
425
        # did that, then if a checker (for some reason) started
426
        # running slowly and taking more than 'interval' time, the
427
        # client would inevitably timeout, since no checker would get
428
        # a chance to run to completion.  If we instead leave running
429
        # checkers alone, the checker would have to take more time
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
430
        # than 'timeout' for the client to be disabled, which is as it
431
        # should be.
315 by Teddy Hogeborn
* mandos (Client.current_checker_command): New attribute.
432
        
433
        # If a checker exists, make sure it is not a zombie
383 by Teddy Hogeborn
* mandos (Client.start_checker): Bug fix: Fix race condition with
434
        try:
315 by Teddy Hogeborn
* mandos (Client.current_checker_command): New attribute.
435
            pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
383 by Teddy Hogeborn
* mandos (Client.start_checker): Bug fix: Fix race condition with
436
        except (AttributeError, OSError), error:
437
            if (isinstance(error, OSError)
438
                and error.errno != errno.ECHILD):
439
                raise error
440
        else:
315 by Teddy Hogeborn
* mandos (Client.current_checker_command): New attribute.
441
            if pid:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
442
                logger.warning(u"Checker was a zombie")
315 by Teddy Hogeborn
* mandos (Client.current_checker_command): New attribute.
443
                gobject.source_remove(self.checker_callback_tag)
444
                self.checker_callback(pid, status,
445
                                      self.current_checker_command)
446
        # Start a new checker if needed
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
447
        if self.checker is None:
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
448
            try:
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
449
                # In case checker_command has exactly one % operator
450
                command = self.checker_command % self.host
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
451
            except TypeError:
28 by Teddy Hogeborn
* server.conf: New file.
452
                # Escape attributes for the shell
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
453
                escaped_attrs = dict((key,
454
                                      re.escape(unicode(str(val),
455
                                                        errors=
456
                                                        u'replace')))
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
457
                                     for key, val in
458
                                     vars(self).iteritems())
13 by Björn Påhlsson
Added following support:
459
                try:
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
460
                    command = self.checker_command % escaped_attrs
13 by Björn Påhlsson
Added following support:
461
                except TypeError, error:
28 by Teddy Hogeborn
* server.conf: New file.
462
                    logger.error(u'Could not format string "%s":'
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
463
                                 u' %s', self.checker_command, error)
13 by Björn Påhlsson
Added following support:
464
                    return True # Try again later
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
465
            self.current_checker_command = command
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
466
            try:
44 by Teddy Hogeborn
* ca.pem: Removed.
467
                logger.info(u"Starting checker %r for %s",
468
                            command, self.name)
94 by Teddy Hogeborn
* clients.conf ([DEFAULT]/checker): Update to new default value.
469
                # We don't need to redirect stdout and stderr, since
470
                # in normal mode, that is already done by daemon(),
471
                # and in debug mode we don't want to.  (Stdin is
472
                # always replaced by /dev/null.)
28 by Teddy Hogeborn
* server.conf: New file.
473
                self.checker = subprocess.Popen(command,
474
                                                close_fds=True,
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
475
                                                shell=True, cwd=u"/")
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
476
                self.checker_callback_tag = (gobject.child_watch_add
477
                                             (self.checker.pid,
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
478
                                              self.checker_callback,
479
                                              data=command))
310 by Teddy Hogeborn
* mandos (Client.start_checker): Bug fix: Add extra check in case the
480
                # The checker may have completed before the gobject
481
                # watch was added.  Check for this.
482
                pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
483
                if pid:
484
                    gobject.source_remove(self.checker_callback_tag)
485
                    self.checker_callback(pid, status, command)
94 by Teddy Hogeborn
* clients.conf ([DEFAULT]/checker): Update to new default value.
486
            except OSError, error:
13 by Björn Påhlsson
Added following support:
487
                logger.error(u"Failed to start subprocess: %s",
488
                             error)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
489
        # Re-run this periodically if run by gobject.timeout_add
490
        return True
237.1.1 by Teddy Hogeborn
First steps of a D-Bus interface to the server.
491
    
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
492
    def stop_checker(self):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
493
        """Force the checker process, if any, to stop."""
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
494
        if self.checker_callback_tag:
495
            gobject.source_remove(self.checker_callback_tag)
496
            self.checker_callback_tag = None
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
497
        if getattr(self, u"checker", None) is None:
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
498
            return
51 by Teddy Hogeborn
* clients.conf: Better comments.
499
        logger.debug(u"Stopping checker for %(name)s", vars(self))
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
500
        try:
501
            os.kill(self.checker.pid, signal.SIGTERM)
400 by Teddy Hogeborn
* mandos (Client.enable): Bug fix: Start new immediate checker last to
502
            #time.sleep(0.5)
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
503
            #if self.checker.poll() is None:
504
            #    os.kill(self.checker.pid, signal.SIGKILL)
505
        except OSError, error:
28 by Teddy Hogeborn
* server.conf: New file.
506
            if error.errno != errno.ESRCH: # No such process
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
507
                raise
9 by Teddy Hogeborn
* client.cpp (main): Get t_old early since it is used on error exits.
508
        self.checker = None
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
509
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
510
def dbus_service_property(dbus_interface, signature=u"v",
511
                          access=u"readwrite", byte_arrays=False):
512
    """Decorators for marking methods of a DBusObjectWithProperties to
513
    become properties on the D-Bus.
514
    
515
    The decorated method will be called with no arguments by "Get"
516
    and with one argument by "Set".
517
    
518
    The parameters, where they are supported, are the same as
519
    dbus.service.method, except there is only "signature", since the
520
    type from Get() and the type sent to Set() is the same.
521
    """
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
522
    # Encoding deeply encoded byte arrays is not supported yet by the
523
    # "Set" method, so we fail early here:
524
    if byte_arrays and signature != u"ay":
525
        raise ValueError(u"Byte arrays not supported for non-'ay'"
526
                         u" signature %r" % signature)
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
527
    def decorator(func):
528
        func._dbus_is_property = True
529
        func._dbus_interface = dbus_interface
530
        func._dbus_signature = signature
531
        func._dbus_access = access
532
        func._dbus_name = func.__name__
533
        if func._dbus_name.endswith(u"_dbus_property"):
534
            func._dbus_name = func._dbus_name[:-14]
535
        func._dbus_get_args_options = {u'byte_arrays': byte_arrays }
536
        return func
537
    return decorator
538
539
540
class DBusPropertyException(dbus.exceptions.DBusException):
541
    """A base class for D-Bus property-related exceptions
542
    """
543
    def __unicode__(self):
544
        return unicode(str(self))
545
546
547
class DBusPropertyAccessException(DBusPropertyException):
548
    """A property's access permissions disallows an operation.
549
    """
550
    pass
551
552
553
class DBusPropertyNotFound(DBusPropertyException):
554
    """An attempt was made to access a non-existing property.
555
    """
556
    pass
557
558
559
class DBusObjectWithProperties(dbus.service.Object):
560
    """A D-Bus object with properties.
561
562
    Classes inheriting from this can use the dbus_service_property
563
    decorator to expose methods as D-Bus properties.  It exposes the
564
    standard Get(), Set(), and GetAll() methods on the D-Bus.
565
    """
566
    
567
    @staticmethod
568
    def _is_dbus_property(obj):
569
        return getattr(obj, u"_dbus_is_property", False)
570
    
571
    def _get_all_dbus_properties(self):
572
        """Returns a generator of (name, attribute) pairs
573
        """
574
        return ((prop._dbus_name, prop)
575
                for name, prop in
576
                inspect.getmembers(self, self._is_dbus_property))
577
    
578
    def _get_dbus_property(self, interface_name, property_name):
579
        """Returns a bound method if one exists which is a D-Bus
580
        property with the specified name and interface.
581
        """
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
592
        # No such property
593
        raise DBusPropertyNotFound(self.dbus_object_path + u":"
594
                                   + interface_name + u"."
595
                                   + property_name)
596
    
597
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ss",
598
                         out_signature=u"v")
599
    def Get(self, interface_name, property_name):
600
        """Standard D-Bus property Get() method, see D-Bus standard.
601
        """
602
        prop = self._get_dbus_property(interface_name, property_name)
603
        if prop._dbus_access == u"write":
604
            raise DBusPropertyAccessException(property_name)
605
        value = prop()
606
        if not hasattr(value, u"variant_level"):
607
            return value
608
        return type(value)(value, variant_level=value.variant_level+1)
609
    
610
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"ssv")
611
    def Set(self, interface_name, property_name, value):
612
        """Standard D-Bus property Set() method, see D-Bus standard.
613
        """
614
        prop = self._get_dbus_property(interface_name, property_name)
615
        if prop._dbus_access == u"read":
616
            raise DBusPropertyAccessException(property_name)
617
        if prop._dbus_get_args_options[u"byte_arrays"]:
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
618
            # The byte_arrays option is not supported yet on
619
            # signatures other than "ay".
620
            if prop._dbus_signature != u"ay":
621
                raise ValueError
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
622
            value = dbus.ByteArray(''.join(unichr(byte)
623
                                           for byte in value))
624
        prop(value)
625
    
626
    @dbus.service.method(dbus.PROPERTIES_IFACE, in_signature=u"s",
627
                         out_signature=u"a{sv}")
628
    def GetAll(self, interface_name):
629
        """Standard D-Bus property GetAll() method, see D-Bus
630
        standard.
631
632
        Note: Will not include properties with access="write".
633
        """
634
        all = {}
635
        for name, prop in self._get_all_dbus_properties():
636
            if (interface_name
637
                and interface_name != prop._dbus_interface):
638
                # Interface non-empty but did not match
639
                continue
640
            # Ignore write-only properties
641
            if prop._dbus_access == u"write":
642
                continue
643
            value = prop()
644
            if not hasattr(value, u"variant_level"):
645
                all[name] = value
646
                continue
647
            all[name] = type(value)(value, variant_level=
648
                                    value.variant_level+1)
649
        return dbus.Dictionary(all, signature=u"sv")
650
    
651
    @dbus.service.method(dbus.INTROSPECTABLE_IFACE,
652
                         out_signature=u"s",
653
                         path_keyword='object_path',
654
                         connection_keyword='connection')
655
    def Introspect(self, object_path, connection):
656
        """Standard D-Bus method, overloaded to insert property tags.
657
        """
658
        xmlstring = dbus.service.Object.Introspect(self, object_path,
386 by Teddy Hogeborn
* mandos (DBusObjectWithProperties.Introspect): Add the name
659
                                                   connection)
387 by Teddy Hogeborn
* mandos (ClientDBus.ReceivedSecret): Renamed to "GotSecret". All
660
        try:
661
            document = xml.dom.minidom.parseString(xmlstring)
662
            def make_tag(document, name, prop):
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)
667
                return e
668
            for if_tag in document.getElementsByTagName(u"interface"):
669
                for tag in (make_tag(document, name, prop)
670
                            for name, prop
671
                            in self._get_all_dbus_properties()
672
                            if prop._dbus_interface
673
                            == if_tag.getAttribute(u"name")):
674
                    if_tag.appendChild(tag)
675
                # Add the names to the return values for the
676
                # "org.freedesktop.DBus.Properties" methods
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")
691
            document.unlink()
692
        except (AttributeError, xml.dom.DOMException,
693
                xml.parsers.expat.ExpatError), error:
694
            logger.error(u"Failed to override Introspection method",
695
                         error)
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
696
        return xmlstring
697
698
699
class ClientDBus(Client, DBusObjectWithProperties):
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
700
    """A Client class using D-Bus
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
701
    
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
702
    Attributes:
338 by Teddy Hogeborn
* mandos: Minor doc string fixes.
703
    dbus_object_path: dbus.ObjectPath
704
    bus: dbus.SystemBus()
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
705
    """
706
    # dbus.service.Object doesn't use super(), so we can't either.
707
    
337 by Teddy Hogeborn
Code cleanup. Move some global stuff into main.
708
    def __init__(self, bus = None, *args, **kwargs):
24.2.4 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Changed to be a property
709
        self._approvals_pending = 0
337 by Teddy Hogeborn
Code cleanup. Move some global stuff into main.
710
        self.bus = bus
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
711
        Client.__init__(self, *args, **kwargs)
712
        # Only now, when this client is initialized, can it show up on
713
        # the D-Bus
714
        self.dbus_object_path = (dbus.ObjectPath
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
715
                                 (u"/clients/"
716
                                  + self.name.replace(u".", u"_")))
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
717
        DBusObjectWithProperties.__init__(self, self.bus,
718
                                          self.dbus_object_path)
24.1.154 by Björn Påhlsson
merge
719
720
    def _get_approvals_pending(self):
24.2.4 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Changed to be a property
721
        return self._approvals_pending
24.2.5 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Bug fix: Only send D-Bus
722
    def _set_approvals_pending(self, value):
24.2.4 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Changed to be a property
723
        old_value = self._approvals_pending
724
        self._approvals_pending = value
725
        bval = bool(value)
24.2.5 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Bug fix: Only send D-Bus
726
        if (hasattr(self, "dbus_object_path")
24.1.154 by Björn Påhlsson
merge
727
            and bval is not bool(old_value)):
24.2.4 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Changed to be a property
728
            dbus_bool = dbus.Boolean(bval, variant_level=1)
729
            self.PropertyChanged(dbus.String(u"approved_pending"),
730
                                 dbus_bool)
24.1.154 by Björn Påhlsson
merge
731
24.2.4 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Changed to be a property
732
    approvals_pending = property(_get_approvals_pending,
733
                                 _set_approvals_pending)
734
    del _get_approvals_pending, _set_approvals_pending
735
    
336 by Teddy Hogeborn
Code cleanup.
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
    
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
742
    def enable(self):
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
743
        oldstate = getattr(self, u"enabled", False)
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
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))
336 by Teddy Hogeborn
Code cleanup.
749
            self.PropertyChanged(
750
                dbus.String(u"last_enabled"),
751
                self._datetime_to_dbus(self.last_enabled,
752
                                       variant_level=1))
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
753
        return r
754
    
402 by Teddy Hogeborn
* mandos (Client.disable): Rename keyword argument "log" to "quiet",
755
    def disable(self, quiet = False):
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
756
        oldstate = getattr(self, u"enabled", False)
403 by Teddy Hogeborn
* mandos (ClientDBus.disable): Bug fix: complete rename of "log" and
757
        r = Client.disable(self, quiet=quiet)
402 by Teddy Hogeborn
* mandos (Client.disable): Rename keyword argument "log" to "quiet",
758
        if not quiet and oldstate != self.enabled:
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
759
            # Emit D-Bus signal
760
            self.PropertyChanged(dbus.String(u"enabled"),
761
                                 dbus.Boolean(False, variant_level=1))
762
        return r
763
    
764
    def __del__(self, *args, **kwargs):
765
        try:
766
            self.remove_from_connection()
329 by Teddy Hogeborn
* mandos (ClientDBus.__del__): Bug fix: Correct mispasted code, and do
767
        except LookupError:
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
768
            pass
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
769
        if hasattr(DBusObjectWithProperties, u"__del__"):
770
            DBusObjectWithProperties.__del__(self, *args, **kwargs)
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
771
        Client.__del__(self, *args, **kwargs)
772
    
773
    def checker_callback(self, pid, condition, command,
774
                         *args, **kwargs):
775
        self.checker_callback_tag = None
776
        self.checker = None
777
        # Emit D-Bus signal
778
        self.PropertyChanged(dbus.String(u"checker_running"),
779
                             dbus.Boolean(False, variant_level=1))
780
        if os.WIFEXITED(condition):
781
            exitstatus = os.WEXITSTATUS(condition)
782
            # Emit D-Bus signal
783
            self.CheckerCompleted(dbus.Int16(exitstatus),
784
                                  dbus.Int64(condition),
785
                                  dbus.String(command))
786
        else:
787
            # Emit D-Bus signal
788
            self.CheckerCompleted(dbus.Int16(-1),
789
                                  dbus.Int64(condition),
790
                                  dbus.String(command))
791
        
792
        return Client.checker_callback(self, pid, condition, command,
793
                                       *args, **kwargs)
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"last_checked_ok"),
336 by Teddy Hogeborn
Code cleanup.
800
            (self._datetime_to_dbus(self.last_checked_ok,
801
                                    variant_level=1)))
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
802
        return r
803
    
804
    def start_checker(self, *args, **kwargs):
805
        old_checker = self.checker
806
        if self.checker is not None:
807
            old_checker_pid = self.checker.pid
808
        else:
809
            old_checker_pid = None
810
        r = Client.start_checker(self, *args, **kwargs)
329 by Teddy Hogeborn
* mandos (ClientDBus.__del__): Bug fix: Correct mispasted code, and do
811
        # Only if new checker process was started
812
        if (self.checker is not None
813
            and old_checker_pid != self.checker.pid):
814
            # Emit D-Bus signal
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
815
            self.CheckerStarted(self.current_checker_command)
816
            self.PropertyChanged(
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
817
                dbus.String(u"checker_running"),
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
818
                dbus.Boolean(True, variant_level=1))
819
        return r
820
    
821
    def stop_checker(self, *args, **kwargs):
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
822
        old_checker = getattr(self, u"checker", None)
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
823
        r = Client.stop_checker(self, *args, **kwargs)
824
        if (old_checker is not None
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
825
            and getattr(self, u"checker", None) is None):
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
826
            self.PropertyChanged(dbus.String(u"checker_running"),
827
                                 dbus.Boolean(False, variant_level=1))
828
        return r
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
829
830
    def _reset_approved(self):
831
        self._approved = None
832
        return False
833
    
834
    def approve(self, value=True):
24.1.154 by Björn Påhlsson
merge
835
        self.send_changedstate()
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
836
        self._approved = value
24.1.154 by Björn Påhlsson
merge
837
        gobject.timeout_add(self._timedelta_to_milliseconds(self.approved_duration),
838
                            self._reset_approved)
24.2.1 by teddy at bsnet
* debian/control (mandos/Depends): Added "python-urwid".
839
    
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
840
    
24.1.147 by Björn Påhlsson
The IPC pipes are now file objects, not file descriptors:
841
    ## D-Bus methods, signals & properties
279 by Teddy Hogeborn
* mandos (Client.__init__): Disable D-Bus during init to avoid
842
    _interface = u"se.bsnet.fukt.Mandos.Client"
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
843
    
24.1.147 by Björn Påhlsson
The IPC pipes are now file objects, not file descriptors:
844
    ## Signals
381 by Teddy Hogeborn
Restore some poor D-Bus methods who got a bit hastily deleted. Enable
845
    
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
846
    # CheckerCompleted - signal
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
847
    @dbus.service.signal(_interface, signature=u"nxs")
280 by Teddy Hogeborn
* mandos (Client.CheckerCompleted): Changed signature to "nxs"; return
848
    def CheckerCompleted(self, exitcode, waitstatus, command):
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
849
        "D-Bus signal"
850
        pass
851
    
852
    # CheckerStarted - signal
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
853
    @dbus.service.signal(_interface, signature=u"s")
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
854
    def CheckerStarted(self, command):
855
        "D-Bus signal"
856
        pass
857
    
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
858
    # PropertyChanged - signal
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
859
    @dbus.service.signal(_interface, signature=u"sv")
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
860
    def PropertyChanged(self, property, value):
861
        "D-Bus signal"
862
        pass
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
863
    
387 by Teddy Hogeborn
* mandos (ClientDBus.ReceivedSecret): Renamed to "GotSecret". All
864
    # GotSecret - signal
327 by Teddy Hogeborn
Merge from pipe IPC branch.
865
    @dbus.service.signal(_interface)
387 by Teddy Hogeborn
* mandos (ClientDBus.ReceivedSecret): Renamed to "GotSecret". All
866
    def GotSecret(self):
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
867
        """D-Bus signal
868
        Is sent after a successful transfer of secret from the Mandos
869
        server to mandos-client
870
        """
24.2.4 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Changed to be a property
871
        pass
327 by Teddy Hogeborn
Merge from pipe IPC branch.
872
    
873
    # Rejected - signal
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
874
    @dbus.service.signal(_interface, signature=u"s")
875
    def Rejected(self, reason):
876
        "D-Bus signal"
24.2.4 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Changed to be a property
877
        pass
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
878
    
879
    # NeedApproval - signal
880
    @dbus.service.signal(_interface, signature=u"db")
881
    def NeedApproval(self, timeout, default):
327 by Teddy Hogeborn
Merge from pipe IPC branch.
882
        "D-Bus signal"
24.2.4 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Changed to be a property
883
        pass
327 by Teddy Hogeborn
Merge from pipe IPC branch.
884
    
24.1.147 by Björn Påhlsson
The IPC pipes are now file objects, not file descriptors:
885
    ## Methods
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
886
887
    # Approve - method
888
    @dbus.service.method(_interface, in_signature=u"b")
889
    def Approve(self, value):
890
        self.approve(value)
891
24.1.147 by Björn Påhlsson
The IPC pipes are now file objects, not file descriptors:
892
    # CheckedOK - method
893
    @dbus.service.method(_interface)
894
    def CheckedOK(self):
895
        return self.checked_ok()
896
    
381 by Teddy Hogeborn
Restore some poor D-Bus methods who got a bit hastily deleted. Enable
897
    # Enable - method
898
    @dbus.service.method(_interface)
899
    def Enable(self):
900
        "D-Bus method"
901
        self.enable()
902
    
903
    # StartChecker - method
904
    @dbus.service.method(_interface)
905
    def StartChecker(self):
906
        "D-Bus method"
907
        self.start_checker()
908
    
909
    # Disable - method
910
    @dbus.service.method(_interface)
911
    def Disable(self):
912
        "D-Bus method"
913
        self.disable()
914
    
915
    # StopChecker - method
916
    @dbus.service.method(_interface)
917
    def StopChecker(self):
918
        self.stop_checker()
919
    
24.1.147 by Björn Påhlsson
The IPC pipes are now file objects, not file descriptors:
920
    ## Properties
921
    
24.1.150 by Björn Påhlsson
* mandos: Added ClientDBus.approve_pending property. Exposed
922
    # approved_pending - property
923
    @dbus_service_property(_interface, signature=u"b", access=u"read")
924
    def approved_pending_dbus_property(self):
24.2.4 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Changed to be a property
925
        return dbus.Boolean(bool(self.approvals_pending))
24.1.150 by Björn Påhlsson
* mandos: Added ClientDBus.approve_pending property. Exposed
926
    
927
    # approved_by_default - property
928
    @dbus_service_property(_interface, signature=u"b",
929
                           access=u"readwrite")
930
    def approved_by_default_dbus_property(self):
931
        return dbus.Boolean(self.approved_by_default)
932
    
933
    # approved_delay - property
934
    @dbus_service_property(_interface, signature=u"t",
935
                           access=u"readwrite")
936
    def approved_delay_dbus_property(self):
937
        return dbus.UInt64(self.approved_delay_milliseconds())
938
    
939
    # approved_duration - property
940
    @dbus_service_property(_interface, signature=u"t",
941
                           access=u"readwrite")
942
    def approved_duration_dbus_property(self):
943
        return dbus.UInt64(self._timedelta_to_milliseconds(
944
                self.approved_duration))
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
945
    
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
946
    # name - property
947
    @dbus_service_property(_interface, signature=u"s", access=u"read")
948
    def name_dbus_property(self):
949
        return dbus.String(self.name)
950
    
951
    # fingerprint - property
952
    @dbus_service_property(_interface, signature=u"s", access=u"read")
953
    def fingerprint_dbus_property(self):
954
        return dbus.String(self.fingerprint)
955
    
956
    # host - property
957
    @dbus_service_property(_interface, signature=u"s",
958
                           access=u"readwrite")
959
    def host_dbus_property(self, value=None):
960
        if value is None:       # get
961
            return dbus.String(self.host)
962
        self.host = value
963
        # Emit D-Bus signal
964
        self.PropertyChanged(dbus.String(u"host"),
965
                             dbus.String(value, variant_level=1))
966
    
967
    # created - property
968
    @dbus_service_property(_interface, signature=u"s", access=u"read")
969
    def created_dbus_property(self):
970
        return dbus.String(self._datetime_to_dbus(self.created))
971
    
972
    # last_enabled - property
973
    @dbus_service_property(_interface, signature=u"s", access=u"read")
974
    def last_enabled_dbus_property(self):
975
        if self.last_enabled is None:
976
            return dbus.String(u"")
977
        return dbus.String(self._datetime_to_dbus(self.last_enabled))
978
    
979
    # enabled - property
980
    @dbus_service_property(_interface, signature=u"b",
981
                           access=u"readwrite")
982
    def enabled_dbus_property(self, value=None):
983
        if value is None:       # get
984
            return dbus.Boolean(self.enabled)
985
        if value:
986
            self.enable()
987
        else:
988
            self.disable()
989
    
990
    # last_checked_ok - property
381 by Teddy Hogeborn
Restore some poor D-Bus methods who got a bit hastily deleted. Enable
991
    @dbus_service_property(_interface, signature=u"s",
992
                           access=u"readwrite")
993
    def last_checked_ok_dbus_property(self, value=None):
994
        if value is not None:
995
            self.checked_ok()
996
            return
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
997
        if self.last_checked_ok is None:
998
            return dbus.String(u"")
999
        return dbus.String(self._datetime_to_dbus(self
1000
                                                  .last_checked_ok))
1001
    
1002
    # timeout - property
1003
    @dbus_service_property(_interface, signature=u"t",
1004
                           access=u"readwrite")
1005
    def timeout_dbus_property(self, value=None):
1006
        if value is None:       # get
1007
            return dbus.UInt64(self.timeout_milliseconds())
1008
        self.timeout = datetime.timedelta(0, 0, 0, value)
1009
        # Emit D-Bus signal
1010
        self.PropertyChanged(dbus.String(u"timeout"),
1011
                             dbus.UInt64(value, variant_level=1))
1012
        if getattr(self, u"disable_initiator_tag", None) is None:
1013
            return
1014
        # Reschedule timeout
1015
        gobject.source_remove(self.disable_initiator_tag)
1016
        self.disable_initiator_tag = None
1017
        time_to_die = (self.
1018
                       _timedelta_to_milliseconds((self
1019
                                                   .last_checked_ok
1020
                                                   + self.timeout)
1021
                                                  - datetime.datetime
1022
                                                  .utcnow()))
1023
        if time_to_die <= 0:
1024
            # The timeout has passed
1025
            self.disable()
1026
        else:
1027
            self.disable_initiator_tag = (gobject.timeout_add
1028
                                          (time_to_die, self.disable))
1029
    
1030
    # interval - property
1031
    @dbus_service_property(_interface, signature=u"t",
1032
                           access=u"readwrite")
1033
    def interval_dbus_property(self, value=None):
1034
        if value is None:       # get
1035
            return dbus.UInt64(self.interval_milliseconds())
1036
        self.interval = datetime.timedelta(0, 0, 0, value)
1037
        # Emit D-Bus signal
1038
        self.PropertyChanged(dbus.String(u"interval"),
1039
                             dbus.UInt64(value, variant_level=1))
1040
        if getattr(self, u"checker_initiator_tag", None) is None:
1041
            return
1042
        # Reschedule checker run
1043
        gobject.source_remove(self.checker_initiator_tag)
1044
        self.checker_initiator_tag = (gobject.timeout_add
1045
                                      (value, self.start_checker))
1046
        self.start_checker()    # Start one now, too
1047
1048
    # checker - property
1049
    @dbus_service_property(_interface, signature=u"s",
1050
                           access=u"readwrite")
1051
    def checker_dbus_property(self, value=None):
1052
        if value is None:       # get
1053
            return dbus.String(self.checker_command)
1054
        self.checker_command = value
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1055
        # Emit D-Bus signal
1056
        self.PropertyChanged(dbus.String(u"checker"),
1057
                             dbus.String(self.checker_command,
1058
                                         variant_level=1))
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
1059
    
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
1060
    # checker_running - property
1061
    @dbus_service_property(_interface, signature=u"b",
1062
                           access=u"readwrite")
1063
    def checker_running_dbus_property(self, value=None):
1064
        if value is None:       # get
1065
            return dbus.Boolean(self.checker is not None)
1066
        if value:
1067
            self.start_checker()
1068
        else:
1069
            self.stop_checker()
1070
    
1071
    # object_path - property
1072
    @dbus_service_property(_interface, signature=u"o", access=u"read")
1073
    def object_path_dbus_property(self):
1074
        return self.dbus_object_path # is already a dbus.ObjectPath
1075
    
381 by Teddy Hogeborn
Restore some poor D-Bus methods who got a bit hastily deleted. Enable
1076
    # secret = property
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
1077
    @dbus_service_property(_interface, signature=u"ay",
1078
                           access=u"write", byte_arrays=True)
1079
    def secret_dbus_property(self, value):
1080
        self.secret = str(value)
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
1081
    
1082
    del _interface
3 by Björn Påhlsson
Python based server
1083
1084
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1085
class ProxyClient(object):
1086
    def __init__(self, child_pipe, fpr, address):
1087
        self._pipe = child_pipe
1088
        self._pipe.send(('init', fpr, address))
1089
        if not self._pipe.recv():
1090
            raise KeyError()
1091
1092
    def __getattribute__(self, name):
1093
        if(name == '_pipe'):
1094
            return super(ProxyClient, self).__getattribute__(name)
1095
        self._pipe.send(('getattr', name))
1096
        data = self._pipe.recv()
1097
        if data[0] == 'data':
1098
            return data[1]
1099
        if data[0] == 'function':
1100
            def func(*args, **kwargs):
1101
                self._pipe.send(('funcall', name, args, kwargs))
1102
                return self._pipe.recv()[1]
1103
            return func
1104
1105
    def __setattr__(self, name, value):
1106
        if(name == '_pipe'):
1107
            return super(ProxyClient, self).__setattr__(name, value)
1108
        self._pipe.send(('setattr', name, value))
1109
1110
335 by Teddy Hogeborn
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
1111
class ClientHandler(socketserver.BaseRequestHandler, object):
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1112
    """A class to handle client connections.
1113
    
1114
    Instantiated once for each connection to handle it.
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1115
    Note: This will run in its own forked process."""
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
1116
    
3 by Björn Påhlsson
Python based server
1117
    def handle(self):
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1118
        with contextlib.closing(self.server.child_pipe) as child_pipe:
1119
            logger.info(u"TCP connection from: %s",
1120
                        unicode(self.client_address))
1121
            logger.debug(u"Pipe FD: %d",
1122
                         self.server.child_pipe.fileno())
1123
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1124
            session = (gnutls.connection
1125
                       .ClientSession(self.request,
1126
                                      gnutls.connection
1127
                                      .X509Credentials()))
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1128
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1129
            # Note: gnutls.connection.X509Credentials is really a
1130
            # generic GnuTLS certificate credentials object so long as
1131
            # no X.509 keys are added to it.  Therefore, we can use it
1132
            # here despite using OpenPGP certificates.
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1133
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1134
            #priority = u':'.join((u"NONE", u"+VERS-TLS1.1",
1135
            #                      u"+AES-256-CBC", u"+SHA1",
1136
            #                      u"+COMP-NULL", u"+CTYPE-OPENPGP",
1137
            #                      u"+DHE-DSS"))
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1138
            # Use a fallback default, since this MUST be set.
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1139
            priority = self.server.gnutls_priority
1140
            if priority is None:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1141
                priority = u"NORMAL"
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1142
            (gnutls.library.functions
1143
             .gnutls_priority_set_direct(session._c_object,
1144
                                         priority, None))
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1145
416.1.2 by Teddy Hogeborn
* mandos (ClientHandler.handle): Set up the GnuTLS session object
1146
            # Start communication using the Mandos protocol
1147
            # Get protocol number
1148
            line = self.request.makefile().readline()
1149
            logger.debug(u"Protocol version: %r", line)
1150
            try:
1151
                if int(line.strip().split()[0]) > 1:
1152
                    raise RuntimeError
1153
            except (ValueError, IndexError, RuntimeError), error:
1154
                logger.error(u"Unknown protocol version: %s", error)
1155
                return
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1156
416.1.2 by Teddy Hogeborn
* mandos (ClientHandler.handle): Set up the GnuTLS session object
1157
            # Start GnuTLS connection
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1158
            try:
1159
                session.handshake()
1160
            except gnutls.errors.GNUTLSError, error:
1161
                logger.warning(u"Handshake failed: %s", error)
1162
                # Do not run session.bye() here: the session is not
1163
                # established.  Just abandon the request.
1164
                return
1165
            logger.debug(u"Handshake succeeded")
24.1.150 by Björn Påhlsson
* mandos: Added ClientDBus.approve_pending property. Exposed
1166
1167
            approval_required = False
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1168
            try:
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1169
                try:
1170
                    fpr = self.fingerprint(self.peer_certificate
1171
                                           (session))
1172
                except (TypeError, gnutls.errors.GNUTLSError), error:
1173
                    logger.warning(u"Bad certificate: %s", error)
1174
                    return
1175
                logger.debug(u"Fingerprint: %s", fpr)
1176
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1177
                try:
1178
                    client = ProxyClient(child_pipe, fpr,
1179
                                         self.client_address)
1180
                except KeyError:
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1181
                    return
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1182
                
24.1.150 by Björn Påhlsson
* mandos: Added ClientDBus.approve_pending property. Exposed
1183
                if client.approved_delay:
1184
                    delay = client.approved_delay
1185
                    client.approvals_pending += 1
1186
                    approval_required = True
1187
                
24.1.148 by Björn Påhlsson
half working on-demand password and approved code
1188
                while True:
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1189
                    if not client.enabled:
1190
                        logger.warning(u"Client %s is disabled",
1191
                                       client.name)
1192
                        if self.server.use_dbus:
1193
                            # Emit D-Bus signal
1194
                            client.Rejected("Disabled")                    
1195
                        return
24.1.150 by Björn Påhlsson
* mandos: Added ClientDBus.approve_pending property. Exposed
1196
                    
1197
                    if client._approved or not client.approved_delay:
1198
                        #We are approved or approval is disabled
1199
                        break
1200
                    elif client._approved is None:
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1201
                        logger.info(u"Client %s need approval",
1202
                                    client.name)
1203
                        if self.server.use_dbus:
1204
                            # Emit D-Bus signal
1205
                            client.NeedApproval(
1206
                                client.approved_delay_milliseconds(),
1207
                                client.approved_by_default)
1208
                    else:
1209
                        logger.warning(u"Client %s was not approved",
1210
                                       client.name)
1211
                        if self.server.use_dbus:
24.1.150 by Björn Påhlsson
* mandos: Added ClientDBus.approve_pending property. Exposed
1212
                            # Emit D-Bus signal
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1213
                            client.Rejected("Disapproved")
24.1.148 by Björn Påhlsson
half working on-demand password and approved code
1214
                        return
1215
                    
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1216
                    #wait until timeout or approved
1217
                    #x = float(client._timedelta_to_milliseconds(delay))
1218
                    time = datetime.datetime.now()
1219
                    client.changedstate.acquire()
1220
                    client.changedstate.wait(float(client._timedelta_to_milliseconds(delay) / 1000))
1221
                    client.changedstate.release()
1222
                    time2 = datetime.datetime.now()
1223
                    if (time2 - time) >= delay:
1224
                        if not client.approved_by_default:
1225
                            logger.warning("Client %s timed out while"
1226
                                           " waiting for approval",
1227
                                           client.name)
1228
                            if self.server.use_dbus:
1229
                                # Emit D-Bus signal
1230
                                client.Rejected("Time out")
1231
                            return
1232
                        else:
1233
                            break
1234
                    else:
1235
                        delay -= time2 - time
1236
                
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1237
                sent_size = 0
1238
                while sent_size < len(client.secret):
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
1239
                    try:
1240
                        sent = session.send(client.secret[sent_size:])
1241
                    except (gnutls.errors.GNUTLSError), error:
1242
                        logger.warning("gnutls send failed")
1243
                        return
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1244
                    logger.debug(u"Sent: %d, remaining: %d",
1245
                                 sent, len(client.secret)
1246
                                 - (sent_size + sent))
1247
                    sent_size += sent
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1248
1249
                logger.info(u"Sending secret to %s", client.name)
1250
                # bump the timeout as if seen
1251
                client.checked_ok()
1252
                if self.server.use_dbus:
1253
                    # Emit D-Bus signal
1254
                    client.GotSecret()
24.1.150 by Björn Påhlsson
* mandos: Added ClientDBus.approve_pending property. Exposed
1255
            
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1256
            finally:
24.1.150 by Björn Påhlsson
* mandos: Added ClientDBus.approve_pending property. Exposed
1257
                if approval_required:
1258
                    client.approvals_pending -= 1
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
1259
                try:
1260
                    session.bye()
1261
                except (gnutls.errors.GNUTLSError), error:
1262
                    logger.warning("gnutls bye failed")
330 by Teddy Hogeborn
* mandos (peer_certificate, fingerprint): Moved into "TCP_handler"
1263
    
1264
    @staticmethod
1265
    def peer_certificate(session):
1266
        "Return the peer's OpenPGP certificate as a bytestring"
1267
        # If not an OpenPGP certificate...
1268
        if (gnutls.library.functions
1269
            .gnutls_certificate_type_get(session._c_object)
1270
            != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1271
            # ...do the normal thing
1272
            return session.peer_certificate
1273
        list_size = ctypes.c_uint(1)
1274
        cert_list = (gnutls.library.functions
1275
                     .gnutls_certificate_get_peers
1276
                     (session._c_object, ctypes.byref(list_size)))
1277
        if not bool(cert_list) and list_size.value != 0:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1278
            raise gnutls.errors.GNUTLSError(u"error getting peer"
1279
                                            u" certificate")
330 by Teddy Hogeborn
* mandos (peer_certificate, fingerprint): Moved into "TCP_handler"
1280
        if list_size.value == 0:
1281
            return None
1282
        cert = cert_list[0]
1283
        return ctypes.string_at(cert.data, cert.size)
1284
    
1285
    @staticmethod
1286
    def fingerprint(openpgp):
1287
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
1288
        # New GnuTLS "datum" with the OpenPGP public key
1289
        datum = (gnutls.library.types
1290
                 .gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1291
                                             ctypes.POINTER
1292
                                             (ctypes.c_ubyte)),
1293
                                 ctypes.c_uint(len(openpgp))))
1294
        # New empty GnuTLS certificate
1295
        crt = gnutls.library.types.gnutls_openpgp_crt_t()
1296
        (gnutls.library.functions
1297
         .gnutls_openpgp_crt_init(ctypes.byref(crt)))
1298
        # Import the OpenPGP public key into the certificate
1299
        (gnutls.library.functions
1300
         .gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1301
                                    gnutls.library.constants
1302
                                    .GNUTLS_OPENPGP_FMT_RAW))
1303
        # Verify the self signature in the key
1304
        crtverify = ctypes.c_uint()
1305
        (gnutls.library.functions
1306
         .gnutls_openpgp_crt_verify_self(crt, 0,
1307
                                         ctypes.byref(crtverify)))
1308
        if crtverify.value != 0:
1309
            gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1310
            raise (gnutls.errors.CertificateSecurityError
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1311
                   (u"Verify failed"))
330 by Teddy Hogeborn
* mandos (peer_certificate, fingerprint): Moved into "TCP_handler"
1312
        # New buffer for the fingerprint
1313
        buf = ctypes.create_string_buffer(20)
1314
        buf_len = ctypes.c_size_t()
1315
        # Get the fingerprint from the certificate into the buffer
1316
        (gnutls.library.functions
1317
         .gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1318
                                             ctypes.byref(buf_len)))
1319
        # Deinit the certificate
1320
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1321
        # Convert the buffer to a Python bytestring
1322
        fpr = ctypes.string_at(buf, buf_len.value)
1323
        # Convert the bytestring to hexadecimal notation
1324
        hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1325
        return hex_fpr
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1326
1327
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1328
class MultiprocessingMixIn(object):
1329
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
1330
    def sub_process_main(self, request, address):
1331
        try:
1332
            self.finish_request(request, address)
1333
        except:
1334
            self.handle_error(request, address)
1335
        self.close_request(request)
1336
            
1337
    def process_request(self, request, address):
1338
        """Start a new process to process the request."""
1339
        multiprocessing.Process(target = self.sub_process_main,
1340
                                args = (request, address)).start()
1341
1342
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1343
    """ adds a pipe to the MixIn """
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1344
    def process_request(self, request, client_address):
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1345
        """Overrides and wraps the original process_request().
1346
        
355 by Teddy Hogeborn
* mandos: White-space fixes only.
1347
        This function creates a new pipe in self.pipe
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1348
        """
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1349
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
1350
1351
        super(MultiprocessingMixInWithPipe,
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1352
              self).process_request(request, client_address)
24.1.152 by Björn Påhlsson
bug fixes that prevent problems when runing server as root
1353
        self.child_pipe.close()
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1354
        self.add_pipe(parent_pipe)
24.1.154 by Björn Påhlsson
merge
1355
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1356
    def add_pipe(self, parent_pipe):
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1357
        """Dummy function; override as necessary"""
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1358
        pass
1359
1360
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
335 by Teddy Hogeborn
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
1361
                     socketserver.TCPServer, object):
237.2.13 by Teddy Hogeborn
Merge from trunk. Notable changes:
1362
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1363
    
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1364
    Attributes:
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1365
        enabled:        Boolean; whether this server is activated yet
1366
        interface:      None or a network interface name (string)
1367
        use_ipv6:       Boolean; to use IPv6 or not
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1368
    """
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1369
    def __init__(self, server_address, RequestHandlerClass,
339 by Teddy Hogeborn
Code cleanup.
1370
                 interface=None, use_ipv6=True):
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1371
        self.interface = interface
1372
        if use_ipv6:
1373
            self.address_family = socket.AF_INET6
335 by Teddy Hogeborn
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
1374
        socketserver.TCPServer.__init__(self, server_address,
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1375
                                        RequestHandlerClass)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1376
    def server_bind(self):
1377
        """This overrides the normal server_bind() function
1378
        to bind to an interface if one was specified, and also NOT to
1379
        bind to an address or port if they were not specified."""
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1380
        if self.interface is not None:
338 by Teddy Hogeborn
* mandos: Minor doc string fixes.
1381
            if SO_BINDTODEVICE is None:
1382
                logger.error(u"SO_BINDTODEVICE does not exist;"
1383
                             u" cannot bind to interface %s",
1384
                             self.interface)
1385
            else:
1386
                try:
1387
                    self.socket.setsockopt(socket.SOL_SOCKET,
1388
                                           SO_BINDTODEVICE,
1389
                                           str(self.interface
1390
                                               + u'\0'))
1391
                except socket.error, error:
1392
                    if error[0] == errno.EPERM:
1393
                        logger.error(u"No permission to"
1394
                                     u" bind to interface %s",
1395
                                     self.interface)
1396
                    elif error[0] == errno.ENOPROTOOPT:
1397
                        logger.error(u"SO_BINDTODEVICE not available;"
1398
                                     u" cannot bind to interface %s",
1399
                                     self.interface)
1400
                    else:
1401
                        raise
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1402
        # Only bind(2) the socket if we really need to.
1403
        if self.server_address[0] or self.server_address[1]:
1404
            if not self.server_address[0]:
314 by Teddy Hogeborn
Support not using IPv6 in server:
1405
                if self.address_family == socket.AF_INET6:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1406
                    any_address = u"::" # in6addr_any
314 by Teddy Hogeborn
Support not using IPv6 in server:
1407
                else:
1408
                    any_address = socket.INADDR_ANY
1409
                self.server_address = (any_address,
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1410
                                       self.server_address[1])
49 by Teddy Hogeborn
* mandos (IPv6_TCPServer.server_bind): Bug fix: allow port to be empty
1411
            elif not self.server_address[1]:
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1412
                self.server_address = (self.server_address[0],
1413
                                       0)
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1414
#                 if self.interface:
49 by Teddy Hogeborn
* mandos (IPv6_TCPServer.server_bind): Bug fix: allow port to be empty
1415
#                     self.server_address = (self.server_address[0],
1416
#                                            0, # port
1417
#                                            0, # flowinfo
1418
#                                            if_nametoindex
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1419
#                                            (self.interface))
335 by Teddy Hogeborn
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
1420
            return socketserver.TCPServer.server_bind(self)
339 by Teddy Hogeborn
Code cleanup.
1421
1422
1423
class MandosServer(IPv6_TCPServer):
1424
    """Mandos server.
1425
    
1426
    Attributes:
1427
        clients:        set of Client objects
1428
        gnutls_priority GnuTLS priority string
1429
        use_dbus:       Boolean; to emit D-Bus signals or not
340 by Teddy Hogeborn
Code cleanup.
1430
    
1431
    Assumes a gobject.MainLoop event loop.
339 by Teddy Hogeborn
Code cleanup.
1432
    """
1433
    def __init__(self, server_address, RequestHandlerClass,
1434
                 interface=None, use_ipv6=True, clients=None,
1435
                 gnutls_priority=None, use_dbus=True):
1436
        self.enabled = False
1437
        self.clients = clients
341 by Teddy Hogeborn
Code cleanup and one bug fix.
1438
        if self.clients is None:
1439
            self.clients = set()
339 by Teddy Hogeborn
Code cleanup.
1440
        self.use_dbus = use_dbus
1441
        self.gnutls_priority = gnutls_priority
1442
        IPv6_TCPServer.__init__(self, server_address,
1443
                                RequestHandlerClass,
1444
                                interface = interface,
1445
                                use_ipv6 = use_ipv6)
163 by Teddy Hogeborn
* Makefile (PIDDIR, USER, GROUP): Removed.
1446
    def server_activate(self):
1447
        if self.enabled:
335 by Teddy Hogeborn
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
1448
            return socketserver.TCPServer.server_activate(self)
163 by Teddy Hogeborn
* Makefile (PIDDIR, USER, GROUP): Removed.
1449
    def enable(self):
1450
        self.enabled = True
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1451
    def add_pipe(self, parent_pipe):
340 by Teddy Hogeborn
Code cleanup.
1452
        # Call "handle_ipc" for both data and EOF events
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1453
        gobject.io_add_watch(parent_pipe.fileno(),
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1454
                             gobject.IO_IN | gobject.IO_HUP,
1455
                             functools.partial(self.handle_ipc,
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1456
                                               parent_pipe = parent_pipe))
1457
        
1458
    def handle_ipc(self, source, condition, parent_pipe=None,
1459
                   client_object=None):
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1460
        condition_names = {
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1461
            gobject.IO_IN: u"IN",   # There is data to read.
1462
            gobject.IO_OUT: u"OUT", # Data can be written (without
1463
                                    # blocking).
1464
            gobject.IO_PRI: u"PRI", # There is urgent data to read.
1465
            gobject.IO_ERR: u"ERR", # Error condition.
1466
            gobject.IO_HUP: u"HUP"  # Hung up (the connection has been
1467
                                    # broken, usually for pipes and
1468
                                    # sockets).
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1469
            }
1470
        conditions_string = ' | '.join(name
1471
                                       for cond, name in
1472
                                       condition_names.iteritems()
1473
                                       if cond & condition)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1474
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1475
                     conditions_string)
24.1.152 by Björn Påhlsson
bug fixes that prevent problems when runing server as root
1476
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
1477
        # error or the other end of multiprocessing.Pipe has closed
1478
        if condition & (gobject.IO_ERR | condition & gobject.IO_HUP):
24.1.152 by Björn Påhlsson
bug fixes that prevent problems when runing server as root
1479
            return False
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1480
        
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1481
        # Read a request from the child
1482
        request = parent_pipe.recv()
24.1.152 by Björn Påhlsson
bug fixes that prevent problems when runing server as root
1483
        logger.debug(u"IPC request: %s", repr(request))
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1484
        command = request[0]
1485
        
1486
        if command == 'init':
1487
            fpr = request[1]
1488
            address = request[2]
1489
            
1490
            for c in self.clients:
1491
                if c.fingerprint == fpr:
1492
                    client = c
1493
                    break
1494
            else:
1495
                logger.warning(u"Client not found for fingerprint: %s, ad"
1496
                               u"dress: %s", fpr, address)
1497
                if self.use_dbus:
1498
                    # Emit D-Bus signal
1499
                    mandos_dbus_service.ClientNotFound(fpr, address)
1500
                parent_pipe.send(False)
1501
                return False
1502
            
1503
            gobject.io_add_watch(parent_pipe.fileno(),
1504
                                 gobject.IO_IN | gobject.IO_HUP,
1505
                                 functools.partial(self.handle_ipc,
1506
                                                   parent_pipe = parent_pipe,
1507
                                                   client_object = client))
1508
            parent_pipe.send(True)
1509
            # remove the old hook in favor of the new above hook on same fileno
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1510
            return False
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1511
        if command == 'funcall':
1512
            funcname = request[1]
1513
            args = request[2]
1514
            kwargs = request[3]
1515
            
1516
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1517
1518
        if command == 'getattr':
1519
            attrname = request[1]
1520
            if callable(client_object.__getattribute__(attrname)):
1521
                parent_pipe.send(('function',))
1522
            else:
1523
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
24.2.4 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Changed to be a property
1524
        
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1525
        if command == 'setattr':
1526
            attrname = request[1]
1527
            value = request[2]
1528
            setattr(client_object, attrname, value)
24.1.154 by Björn Påhlsson
merge
1529
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1530
        return True
10 by Teddy Hogeborn
* server.py: Bug fix: Do "from __future__ import division".
1531
3 by Björn Påhlsson
Python based server
1532
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
1533
def string_to_delta(interval):
1534
    """Parse a string and return a datetime.timedelta
290 by Teddy Hogeborn
* mandos (main): Bug fix: Do setgid before setuid. Add verbose GnuTLS
1535
    
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1536
    >>> string_to_delta(u'7d')
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
1537
    datetime.timedelta(7)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1538
    >>> string_to_delta(u'60s')
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
1539
    datetime.timedelta(0, 60)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1540
    >>> string_to_delta(u'60m')
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
1541
    datetime.timedelta(0, 3600)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1542
    >>> string_to_delta(u'24h')
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
1543
    datetime.timedelta(1)
1544
    >>> string_to_delta(u'1w')
1545
    datetime.timedelta(7)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1546
    >>> string_to_delta(u'5m 30s')
93 by Teddy Hogeborn
* mandos (string_to_delta): Accept a whitespace-separated sequence of
1547
    datetime.timedelta(0, 330)
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
1548
    """
93 by Teddy Hogeborn
* mandos (string_to_delta): Accept a whitespace-separated sequence of
1549
    timevalue = datetime.timedelta(0)
1550
    for s in interval.split():
1551
        try:
215 by Teddy Hogeborn
* mandos: Remove unused "select" module. Import "ctypes.util".
1552
            suffix = unicode(s[-1])
1553
            value = int(s[:-1])
93 by Teddy Hogeborn
* mandos (string_to_delta): Accept a whitespace-separated sequence of
1554
            if suffix == u"d":
1555
                delta = datetime.timedelta(value)
1556
            elif suffix == u"s":
1557
                delta = datetime.timedelta(0, value)
1558
            elif suffix == u"m":
1559
                delta = datetime.timedelta(0, 0, 0, 0, value)
1560
            elif suffix == u"h":
1561
                delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1562
            elif suffix == u"w":
1563
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1564
            else:
400 by Teddy Hogeborn
* mandos (Client.enable): Bug fix: Start new immediate checker last to
1565
                raise ValueError(u"Unknown suffix %r" % suffix)
1566
        except (ValueError, IndexError), e:
1567
            raise ValueError(e.message)
93 by Teddy Hogeborn
* mandos (string_to_delta): Accept a whitespace-separated sequence of
1568
        timevalue += delta
1569
    return timevalue
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
1570
8 by Teddy Hogeborn
* Makefile (client_debug): Bug fix; add quotes and / to CERT_ROOT.
1571
24.1.13 by Björn Påhlsson
mandosclient
1572
def if_nametoindex(interface):
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1573
    """Call the C function if_nametoindex(), or equivalent
1574
    
1575
    Note: This function cannot accept a unicode string."""
24.1.13 by Björn Påhlsson
mandosclient
1576
    global if_nametoindex
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1577
    try:
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
1578
        if_nametoindex = (ctypes.cdll.LoadLibrary
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1579
                          (ctypes.util.find_library(u"c"))
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
1580
                          .if_nametoindex)
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
1581
    except (OSError, AttributeError):
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1582
        logger.warning(u"Doing if_nametoindex the hard way")
24.1.13 by Björn Påhlsson
mandosclient
1583
        def if_nametoindex(interface):
28 by Teddy Hogeborn
* server.conf: New file.
1584
            "Get an interface index the hard way, i.e. using fcntl()"
1585
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1586
            with contextlib.closing(socket.socket()) as s:
237 by Teddy Hogeborn
* mandos: Also import "with_statement" and "absolute_import" from
1587
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1588
                                    struct.pack(str(u"16s16x"),
1589
                                                interface))
1590
            interface_index = struct.unpack(str(u"I"),
1591
                                            ifreq[16:20])[0]
28 by Teddy Hogeborn
* server.conf: New file.
1592
            return interface_index
24.1.13 by Björn Påhlsson
mandosclient
1593
    return if_nametoindex(interface)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1594
1595
47 by Teddy Hogeborn
* plugbasedclient.c: Renamed to "mandos-client.c". All users changed.
1596
def daemon(nochdir = False, noclose = False):
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
1597
    """See daemon(3).  Standard BSD Unix function.
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1598
    
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
1599
    This should really exist as os.daemon, but it doesn't (yet)."""
1600
    if os.fork():
1601
        sys.exit()
1602
    os.setsid()
1603
    if not nochdir:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1604
        os.chdir(u"/")
46 by Teddy Hogeborn
* network-protocol.txt: New.
1605
    if os.fork():
1606
        sys.exit()
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
1607
    if not noclose:
1608
        # Close all standard open file descriptors
28 by Teddy Hogeborn
* server.conf: New file.
1609
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
1610
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1611
            raise OSError(errno.ENODEV,
388 by Teddy Hogeborn
* mandos (daemon): Use "os.path.devnull" in the error message.
1612
                          u"%s not a character device"
1613
                          % os.path.devnull)
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
1614
        os.dup2(null, sys.stdin.fileno())
1615
        os.dup2(null, sys.stdout.fileno())
1616
        os.dup2(null, sys.stderr.fileno())
1617
        if null > 2:
1618
            os.close(null)
1619
1620
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
1621
def main():
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1622
    
379 by Teddy Hogeborn
* mandos: Fix line lengths.
1623
    ##################################################################
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1624
    # Parsing of options, both command line and config file
1625
    
237.2.1 by Teddy Hogeborn
Merge from trunk, but disable the unfinished D-Bus feature:
1626
    parser = optparse.OptionParser(version = "%%prog %s" % version)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1627
    parser.add_option("-i", u"--interface", type=u"string",
1628
                      metavar="IF", help=u"Bind to interface IF")
1629
    parser.add_option("-a", u"--address", type=u"string",
1630
                      help=u"Address to listen for requests on")
1631
    parser.add_option("-p", u"--port", type=u"int",
1632
                      help=u"Port number to receive requests on")
1633
    parser.add_option("--check", action=u"store_true",
1634
                      help=u"Run self-test")
1635
    parser.add_option("--debug", action=u"store_true",
1636
                      help=u"Debug mode; run in foreground and log to"
1637
                      u" terminal")
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
1638
    parser.add_option("--debuglevel", type=u"string", metavar="Level",
1639
                      help=u"Debug level for stdout output")
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1640
    parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1641
                      u" priority string (see GnuTLS documentation)")
1642
    parser.add_option("--servicename", type=u"string",
1643
                      metavar=u"NAME", help=u"Zeroconf service name")
1644
    parser.add_option("--configdir", type=u"string",
1645
                      default=u"/etc/mandos", metavar=u"DIR",
1646
                      help=u"Directory to search for configuration"
1647
                      u" files")
1648
    parser.add_option("--no-dbus", action=u"store_false",
1649
                      dest=u"use_dbus", help=u"Do not provide D-Bus"
1650
                      u" system bus interface")
1651
    parser.add_option("--no-ipv6", action=u"store_false",
1652
                      dest=u"use_ipv6", help=u"Do not use IPv6")
215 by Teddy Hogeborn
* mandos: Remove unused "select" module. Import "ctypes.util".
1653
    options = parser.parse_args()[0]
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1654
    
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
1655
    if options.check:
1656
        import doctest
1657
        doctest.testmod()
1658
        sys.exit()
3 by Björn Påhlsson
Python based server
1659
    
28 by Teddy Hogeborn
* server.conf: New file.
1660
    # Default values for config file for server-global settings
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1661
    server_defaults = { u"interface": u"",
1662
                        u"address": u"",
1663
                        u"port": u"",
1664
                        u"debug": u"False",
1665
                        u"priority":
1666
                        u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1667
                        u"servicename": u"Mandos",
1668
                        u"use_dbus": u"True",
1669
                        u"use_ipv6": u"True",
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
1670
                        u"debuglevel": u"",
28 by Teddy Hogeborn
* server.conf: New file.
1671
                        }
1672
    
1673
    # Parse config file for server-global settings
335 by Teddy Hogeborn
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
1674
    server_config = configparser.SafeConfigParser(server_defaults)
28 by Teddy Hogeborn
* server.conf: New file.
1675
    del server_defaults
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1676
    server_config.read(os.path.join(options.configdir,
1677
                                    u"mandos.conf"))
28 by Teddy Hogeborn
* server.conf: New file.
1678
    # Convert the SafeConfigParser object to a dict
89 by Teddy Hogeborn
* Makefile: Bug fix: fixed creation of man pages for section 5 pages.
1679
    server_settings = server_config.defaults()
282 by Teddy Hogeborn
* mandos (main): Bug fix: use "getint" on the "port" config file
1680
    # Use the appropriate methods on the non-string config options
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1681
    for option in (u"debug", u"use_dbus", u"use_ipv6"):
1682
        server_settings[option] = server_config.getboolean(u"DEFAULT",
1683
                                                           option)
282 by Teddy Hogeborn
* mandos (main): Bug fix: use "getint" on the "port" config file
1684
    if server_settings["port"]:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1685
        server_settings["port"] = server_config.getint(u"DEFAULT",
1686
                                                       u"port")
28 by Teddy Hogeborn
* server.conf: New file.
1687
    del server_config
1688
    
1689
    # Override the settings from the config file with command line
1690
    # options, if set.
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1691
    for option in (u"interface", u"address", u"port", u"debug",
1692
                   u"priority", u"servicename", u"configdir",
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
1693
                   u"use_dbus", u"use_ipv6", u"debuglevel"):
28 by Teddy Hogeborn
* server.conf: New file.
1694
        value = getattr(options, option)
1695
        if value is not None:
1696
            server_settings[option] = value
1697
    del options
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1698
    # Force all strings to be unicode
1699
    for option in server_settings.keys():
1700
        if type(server_settings[option]) is str:
1701
            server_settings[option] = unicode(server_settings[option])
28 by Teddy Hogeborn
* server.conf: New file.
1702
    # Now we have our good server settings in "server_settings"
1703
    
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1704
    ##################################################################
1705
    
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1706
    # For convenience
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1707
    debug = server_settings[u"debug"]
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
1708
    debuglevel = server_settings[u"debuglevel"]
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1709
    use_dbus = server_settings[u"use_dbus"]
1710
    use_ipv6 = server_settings[u"use_ipv6"]
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
1711
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1712
    if server_settings[u"servicename"] != u"Mandos":
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
1713
        syslogger.setFormatter(logging.Formatter
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1714
                               (u'Mandos (%s) [%%(process)d]:'
1715
                                u' %%(levelname)s: %%(message)s'
1716
                                % server_settings[u"servicename"]))
52 by Teddy Hogeborn
* mandos: Make syslog use "/dev/log" instead of UDP to localhost.
1717
    
28 by Teddy Hogeborn
* server.conf: New file.
1718
    # Parse config file with clients
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1719
    client_defaults = { u"timeout": u"1h",
1720
                        u"interval": u"5m",
1721
                        u"checker": u"fping -q -- %%(host)s",
1722
                        u"host": u"",
24.2.1 by teddy at bsnet
* debian/control (mandos/Depends): Added "python-urwid".
1723
                        u"approved_delay": u"0s",
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1724
                        u"approved_duration": u"1s",
28 by Teddy Hogeborn
* server.conf: New file.
1725
                        }
335 by Teddy Hogeborn
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
1726
    client_config = configparser.SafeConfigParser(client_defaults)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1727
    client_config.read(os.path.join(server_settings[u"configdir"],
1728
                                    u"clients.conf"))
1729
    
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1730
    global mandos_dbus_service
1731
    mandos_dbus_service = None
28 by Teddy Hogeborn
* server.conf: New file.
1732
    
339 by Teddy Hogeborn
Code cleanup.
1733
    tcp_server = MandosServer((server_settings[u"address"],
1734
                               server_settings[u"port"]),
1735
                              ClientHandler,
237.2.35 by teddy at bsnet
* mandos (main): Bug fix: Don't try to bind to an empty string
1736
                              interface=(server_settings[u"interface"]
1737
                                         or None),
339 by Teddy Hogeborn
Code cleanup.
1738
                              use_ipv6=use_ipv6,
1739
                              gnutls_priority=
1740
                              server_settings[u"priority"],
1741
                              use_dbus=use_dbus)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1742
    pidfilename = u"/var/run/mandos.pid"
164 by Teddy Hogeborn
* mandos: Open the PID file before daemonizing, but write to it
1743
    try:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1744
        pidfile = open(pidfilename, u"w")
292 by Teddy Hogeborn
* Makefile (run-server): Use "--no-dbus" unconditionally.
1745
    except IOError:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1746
        logger.error(u"Could not open file %r", pidfilename)
164 by Teddy Hogeborn
* mandos: Open the PID file before daemonizing, but write to it
1747
    
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
1748
    try:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1749
        uid = pwd.getpwnam(u"_mandos").pw_uid
1750
        gid = pwd.getpwnam(u"_mandos").pw_gid
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
1751
    except KeyError:
1752
        try:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1753
            uid = pwd.getpwnam(u"mandos").pw_uid
1754
            gid = pwd.getpwnam(u"mandos").pw_gid
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
1755
        except KeyError:
1756
            try:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1757
                uid = pwd.getpwnam(u"nobody").pw_uid
1758
                gid = pwd.getpwnam(u"nobody").pw_gid
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
1759
            except KeyError:
1760
                uid = 65534
1761
                gid = 65534
163 by Teddy Hogeborn
* Makefile (PIDDIR, USER, GROUP): Removed.
1762
    try:
290 by Teddy Hogeborn
* mandos (main): Bug fix: Do setgid before setuid. Add verbose GnuTLS
1763
        os.setgid(gid)
163 by Teddy Hogeborn
* Makefile (PIDDIR, USER, GROUP): Removed.
1764
        os.setuid(uid)
1765
    except OSError, error:
1766
        if error[0] != errno.EPERM:
1767
            raise error
164 by Teddy Hogeborn
* mandos: Open the PID file before daemonizing, but write to it
1768
    
290 by Teddy Hogeborn
* mandos (main): Bug fix: Do setgid before setuid. Add verbose GnuTLS
1769
    # Enable all possible GnuTLS debugging
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
1770
1771
1772
    if not debug and not debuglevel:
1773
        syslogger.setLevel(logging.WARNING)
1774
        console.setLevel(logging.WARNING)
1775
    if debuglevel:
1776
        level = getattr(logging, debuglevel.upper())
1777
        syslogger.setLevel(level)
1778
        console.setLevel(level)
1779
290 by Teddy Hogeborn
* mandos (main): Bug fix: Do setgid before setuid. Add verbose GnuTLS
1780
    if debug:
1781
        # "Use a log level over 10 to enable all debugging options."
1782
        # - GnuTLS manual
1783
        gnutls.library.functions.gnutls_global_set_log_level(11)
1784
        
1785
        @gnutls.library.types.gnutls_log_func
1786
        def debug_gnutls(level, string):
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1787
            logger.debug(u"GnuTLS: %s", string[:-1])
290 by Teddy Hogeborn
* mandos (main): Bug fix: Do setgid before setuid. Add verbose GnuTLS
1788
        
1789
        (gnutls.library.functions
1790
         .gnutls_global_set_log_function(debug_gnutls))
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
1791
1792
        # Redirect stdin so all checkers get /dev/null
1793
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1794
        os.dup2(null, sys.stdin.fileno())
1795
        if null > 2:
1796
            os.close(null)
1797
    else:
1798
        # No console logging
1799
        logger.removeHandler(console)
1800
290 by Teddy Hogeborn
* mandos (main): Bug fix: Do setgid before setuid. Add verbose GnuTLS
1801
    
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
1802
    global main_loop
1803
    # From the Avahi example code
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1804
    DBusGMainLoop(set_as_default=True )
1805
    main_loop = gobject.MainLoop()
1806
    bus = dbus.SystemBus()
1807
    # End of Avahi example code
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1808
    if use_dbus:
402 by Teddy Hogeborn
* mandos (Client.disable): Rename keyword argument "log" to "quiet",
1809
        try:
1810
            bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1811
                                            bus, do_not_queue=True)
1812
        except dbus.exceptions.NameExistsException, e:
1813
            logger.error(unicode(e) + u", disabling D-Bus")
1814
            use_dbus = False
1815
            server_settings[u"use_dbus"] = False
1816
            tcp_server.use_dbus = False
337 by Teddy Hogeborn
Code cleanup. Move some global stuff into main.
1817
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1818
    service = AvahiService(name = server_settings[u"servicename"],
1819
                           servicetype = u"_mandos._tcp",
1820
                           protocol = protocol, bus = bus)
1821
    if server_settings["interface"]:
1822
        service.interface = (if_nametoindex
1823
                             (str(server_settings[u"interface"])))
24.1.152 by Björn Påhlsson
bug fixes that prevent problems when runing server as root
1824
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
1825
    if not debug:
1826
        # Close all input and output, do double fork, etc.
1827
        daemon()
1828
        
24.1.152 by Björn Påhlsson
bug fixes that prevent problems when runing server as root
1829
    global multiprocessing_manager
1830
    multiprocessing_manager = multiprocessing.Manager()
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1831
    
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
1832
    client_class = Client
1833
    if use_dbus:
337 by Teddy Hogeborn
Code cleanup. Move some global stuff into main.
1834
        client_class = functools.partial(ClientDBus, bus = bus)
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1835
    def client_config_items(config, section):
1836
        special_settings = {
24.1.150 by Björn Påhlsson
* mandos: Added ClientDBus.approve_pending property. Exposed
1837
            "approved_by_default":
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1838
                lambda: config.getboolean(section,
24.1.150 by Björn Påhlsson
* mandos: Added ClientDBus.approve_pending property. Exposed
1839
                                          "approved_by_default"),
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1840
            }
1841
        for name, value in config.items(section):
1842
            try:
24.1.150 by Björn Påhlsson
* mandos: Added ClientDBus.approve_pending property. Exposed
1843
                yield (name, special_settings[name]())
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1844
            except KeyError:
1845
                yield (name, value)
1846
    
341 by Teddy Hogeborn
Code cleanup and one bug fix.
1847
    tcp_server.clients.update(set(
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
1848
            client_class(name = section,
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1849
                         config= dict(client_config_items(
1850
                        client_config, section)))
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
1851
            for section in client_config.sections()))
341 by Teddy Hogeborn
Code cleanup and one bug fix.
1852
    if not tcp_server.clients:
244 by Teddy Hogeborn
* debian/control (Build-Depends): Bug fix: Added "docbook-xml".
1853
        logger.warning(u"No clients defined")
24.1.155 by Björn Påhlsson
mandos server: Added debuglevel that adjust at what level information
1854
        
165 by Teddy Hogeborn
* mandos (main): Use EAFP with pidfile.
1855
    try:
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1856
        with pidfile:
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1857
            pid = os.getpid()
1858
            pidfile.write(str(pid) + "\n")
165 by Teddy Hogeborn
* mandos (main): Use EAFP with pidfile.
1859
        del pidfile
215 by Teddy Hogeborn
* mandos: Remove unused "select" module. Import "ctypes.util".
1860
    except IOError:
165 by Teddy Hogeborn
* mandos (main): Use EAFP with pidfile.
1861
        logger.error(u"Could not write to file %r with PID %d",
1862
                     pidfilename, pid)
1863
    except NameError:
1864
        # "pidfile" was never created
1865
        pass
164 by Teddy Hogeborn
* mandos: Open the PID file before daemonizing, but write to it
1866
    del pidfilename
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
1867
    
1868
    if not debug:
1869
        signal.signal(signal.SIGINT, signal.SIG_IGN)
28 by Teddy Hogeborn
* server.conf: New file.
1870
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1871
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
1872
    
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1873
    if use_dbus:
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1874
        class MandosDBusService(dbus.service.Object):
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1875
            """A D-Bus proxy object"""
1876
            def __init__(self):
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1877
                dbus.service.Object.__init__(self, bus, u"/")
279 by Teddy Hogeborn
* mandos (Client.__init__): Disable D-Bus during init to avoid
1878
            _interface = u"se.bsnet.fukt.Mandos"
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1879
            
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1880
            @dbus.service.signal(_interface, signature=u"o")
1881
            def ClientAdded(self, objpath):
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1882
                "D-Bus signal"
1883
                pass
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1884
            
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
1885
            @dbus.service.signal(_interface, signature=u"ss")
1886
            def ClientNotFound(self, fingerprint, address):
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1887
                "D-Bus signal"
1888
                pass
1889
            
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1890
            @dbus.service.signal(_interface, signature=u"os")
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1891
            def ClientRemoved(self, objpath, name):
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1892
                "D-Bus signal"
1893
                pass
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1894
            
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1895
            @dbus.service.method(_interface, out_signature=u"ao")
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1896
            def GetAllClients(self):
283 by Teddy Hogeborn
* mandos (peer_certificate): Handle NULL pointer from
1897
                "D-Bus method"
341 by Teddy Hogeborn
Code cleanup and one bug fix.
1898
                return dbus.Array(c.dbus_object_path
1899
                                  for c in tcp_server.clients)
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1900
            
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1901
            @dbus.service.method(_interface,
1902
                                 out_signature=u"a{oa{sv}}")
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1903
            def GetAllClientsWithProperties(self):
283 by Teddy Hogeborn
* mandos (peer_certificate): Handle NULL pointer from
1904
                "D-Bus method"
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1905
                return dbus.Dictionary(
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
1906
                    ((c.dbus_object_path, c.GetAll(u""))
341 by Teddy Hogeborn
Code cleanup and one bug fix.
1907
                     for c in tcp_server.clients),
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1908
                    signature=u"oa{sv}")
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1909
            
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1910
            @dbus.service.method(_interface, in_signature=u"o")
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1911
            def RemoveClient(self, object_path):
283 by Teddy Hogeborn
* mandos (peer_certificate): Handle NULL pointer from
1912
                "D-Bus method"
341 by Teddy Hogeborn
Code cleanup and one bug fix.
1913
                for c in tcp_server.clients:
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1914
                    if c.dbus_object_path == object_path:
341 by Teddy Hogeborn
Code cleanup and one bug fix.
1915
                        tcp_server.clients.remove(c)
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
1916
                        c.remove_from_connection()
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1917
                        # Don't signal anything except ClientRemoved
402 by Teddy Hogeborn
* mandos (Client.disable): Rename keyword argument "log" to "quiet",
1918
                        c.disable(quiet=True)
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1919
                        # Emit D-Bus signal
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1920
                        self.ClientRemoved(object_path, c.name)
1921
                        return
400 by Teddy Hogeborn
* mandos (Client.enable): Bug fix: Start new immediate checker last to
1922
                raise KeyError(object_path)
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1923
            
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1924
            del _interface
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1925
        
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1926
        mandos_dbus_service = MandosDBusService()
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
1927
    
400 by Teddy Hogeborn
* mandos (Client.enable): Bug fix: Start new immediate checker last to
1928
    def cleanup():
1929
        "Cleanup function; run on exit"
1930
        service.cleanup()
1931
        
1932
        while tcp_server.clients:
1933
            client = tcp_server.clients.pop()
402 by Teddy Hogeborn
* mandos (Client.disable): Rename keyword argument "log" to "quiet",
1934
            if use_dbus:
1935
                client.remove_from_connection()
400 by Teddy Hogeborn
* mandos (Client.enable): Bug fix: Start new immediate checker last to
1936
            client.disable_hook = None
1937
            # Don't signal anything except ClientRemoved
402 by Teddy Hogeborn
* mandos (Client.disable): Rename keyword argument "log" to "quiet",
1938
            client.disable(quiet=True)
400 by Teddy Hogeborn
* mandos (Client.enable): Bug fix: Start new immediate checker last to
1939
            if use_dbus:
1940
                # Emit D-Bus signal
1941
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
1942
                                                  client.name)
1943
    
1944
    atexit.register(cleanup)
1945
    
341 by Teddy Hogeborn
Code cleanup and one bug fix.
1946
    for client in tcp_server.clients:
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1947
        if use_dbus:
1948
            # Emit D-Bus signal
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1949
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
1950
        client.enable()
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1951
    
163 by Teddy Hogeborn
* Makefile (PIDDIR, USER, GROUP): Removed.
1952
    tcp_server.enable()
1953
    tcp_server.server_activate()
1954
    
28 by Teddy Hogeborn
* server.conf: New file.
1955
    # Find out what port we got
1956
    service.port = tcp_server.socket.getsockname()[1]
314 by Teddy Hogeborn
Support not using IPv6 in server:
1957
    if use_ipv6:
1958
        logger.info(u"Now listening on address %r, port %d,"
1959
                    " flowinfo %d, scope_id %d"
1960
                    % tcp_server.socket.getsockname())
1961
    else:                       # IPv4
1962
        logger.info(u"Now listening on address %r, port %d"
1963
                    % tcp_server.socket.getsockname())
28 by Teddy Hogeborn
* server.conf: New file.
1964
    
29 by Teddy Hogeborn
* plugins.d/mandosclient.c (start_mandos_communication): Changed
1965
    #service.interface = tcp_server.socket.getsockname()[3]
28 by Teddy Hogeborn
* server.conf: New file.
1966
    
1967
    try:
1968
        # From the Avahi example code
1969
        try:
336 by Teddy Hogeborn
Code cleanup.
1970
            service.activate()
28 by Teddy Hogeborn
* server.conf: New file.
1971
        except dbus.exceptions.DBusException, error:
1972
            logger.critical(u"DBusException: %s", error)
401 by Teddy Hogeborn
* README (FAQ): Fix typo.
1973
            cleanup()
28 by Teddy Hogeborn
* server.conf: New file.
1974
            sys.exit(1)
1975
        # End of Avahi example code
1976
        
1977
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
1978
                             lambda *args, **kwargs:
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
1979
                             (tcp_server.handle_request
1980
                              (*args[2:], **kwargs) or True))
28 by Teddy Hogeborn
* server.conf: New file.
1981
        
51 by Teddy Hogeborn
* clients.conf: Better comments.
1982
        logger.debug(u"Starting main loop")
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1983
        main_loop.run()
28 by Teddy Hogeborn
* server.conf: New file.
1984
    except AvahiError, error:
242 by Teddy Hogeborn
* mandos (AvahiError): Converted to use unicode. All users changed.
1985
        logger.critical(u"AvahiError: %s", error)
401 by Teddy Hogeborn
* README (FAQ): Fix typo.
1986
        cleanup()
28 by Teddy Hogeborn
* server.conf: New file.
1987
        sys.exit(1)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1988
    except KeyboardInterrupt:
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
1989
        if debug:
292 by Teddy Hogeborn
* Makefile (run-server): Use "--no-dbus" unconditionally.
1990
            print >> sys.stderr
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1991
        logger.debug(u"Server received KeyboardInterrupt")
1992
    logger.debug(u"Server exiting")
401 by Teddy Hogeborn
* README (FAQ): Fix typo.
1993
    # Must run before the D-Bus bus name gets deregistered
1994
    cleanup()
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
1995
1996
if __name__ == '__main__':
1997
    main()