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