/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")
336 by Teddy Hogeborn
Code cleanup.
160
        self.name = self.server.GetAlternativeServiceName(self.name)
109 by Teddy Hogeborn
* .bzrignore: New.
161
        logger.info(u"Changing Zeroconf service name to %r ...",
336 by Teddy Hogeborn
Code cleanup.
162
                    unicode(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"""
197
        logger.debug(u"Avahi state change: %i", state)
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"""
216
        if state == avahi.SERVER_COLLISION:
217
            logger.error(u"Zeroconf server name collision")
218
            self.remove()
219
        elif state == avahi.SERVER_RUNNING:
220
            self.add()
221
    def activate(self):
222
        """Derived from the Avahi example code"""
223
        if self.server is None:
224
            self.server = dbus.Interface(
337 by Teddy Hogeborn
Code cleanup. Move some global stuff into main.
225
                self.bus.get_object(avahi.DBUS_NAME,
226
                                    avahi.DBUS_PATH_SERVER),
336 by Teddy Hogeborn
Code cleanup.
227
                avahi.DBUS_INTERFACE_SERVER)
228
        self.server.connect_to_signal(u"StateChanged",
229
                                 self.server_state_changed)
230
        self.server_state_changed(self.server.GetState())
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
231
232
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
233
class Client(object):
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
234
    """A representation of a client host served by this server.
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
235
    
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
236
    Attributes:
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
237
    name:       string; from the config file, used in log messages and
238
                        D-Bus identifiers
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
239
    fingerprint: string (40 or 32 hexadecimal digits); used to
240
                 uniquely identify the client
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
241
    secret:     bytestring; sent verbatim (over TLS) to client
242
    host:       string; available for use by the checker command
243
    created:    datetime.datetime(); (UTC) object creation
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
244
    last_enabled: datetime.datetime(); (UTC)
245
    enabled:    bool()
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
246
    last_checked_ok: datetime.datetime(); (UTC) or None
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
247
    timeout:    datetime.timedelta(); How long from last_checked_ok
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
248
                                      until this client is disabled
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
249
    interval:   datetime.timedelta(); How often to start a new checker
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
250
    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
251
    checker:    subprocess.Popen(); a running checker process used
252
                                    to see if the client lives.
253
                                    'None' if no process is running.
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
254
    checker_initiator_tag: a gobject event source tag, or None
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
255
    disable_initiator_tag: - '' -
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
256
    checker_callback_tag:  - '' -
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
257
    checker_command: string; External command which is run to check if
28 by Teddy Hogeborn
* server.conf: New file.
258
                     client lives.  %() expansions are done at
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
259
                     runtime with vars(self) as dict, so that for
260
                     instance %(name)s can be used in the command.
315 by Teddy Hogeborn
* mandos (Client.current_checker_command): New attribute.
261
    current_checker_command: string; current running checker_command
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
262
    approved_delay: datetime.timedelta(); Time to wait for approval
263
    _approved:   bool(); 'None' if not yet approved/disapproved
264
    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
265
    """
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
266
    
267
    @staticmethod
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
268
    def _timedelta_to_milliseconds(td):
269
        "Convert a datetime.timedelta() to milliseconds"
270
        return ((td.days * 24 * 60 * 60 * 1000)
271
                + (td.seconds * 1000)
272
                + (td.microseconds // 1000))
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
273
    
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
274
    def timeout_milliseconds(self):
275
        "Return the 'timeout' attribute in milliseconds"
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
276
        return self._timedelta_to_milliseconds(self.timeout)
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
277
    
278
    def interval_milliseconds(self):
279
        "Return the 'interval' attribute in milliseconds"
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
280
        return self._timedelta_to_milliseconds(self.interval)
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
281
282
    def approved_delay_milliseconds(self):
283
        return self._timedelta_to_milliseconds(self.approved_delay)
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
284
    
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
285
    def __init__(self, name = None, disable_hook=None, config=None):
45 by Teddy Hogeborn
* server.py: Cosmetic changes.
286
        """Note: the 'checker' key in 'config' sets the
287
        'checker_command' attribute and *not* the 'checker'
288
        attribute."""
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
289
        self.name = name
215 by Teddy Hogeborn
* mandos: Remove unused "select" module. Import "ctypes.util".
290
        if config is None:
291
            config = {}
25 by Teddy Hogeborn
* mandos-clients.conf ([DEFAULT]): New section.
292
        logger.debug(u"Creating client %r", self.name)
45 by Teddy Hogeborn
* server.py: Cosmetic changes.
293
        # Uppercase and remove spaces from fingerprint for later
294
        # comparison purposes with return value from the fingerprint()
295
        # function
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
296
        self.fingerprint = (config[u"fingerprint"].upper()
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
297
                            .replace(u" ", u""))
25 by Teddy Hogeborn
* mandos-clients.conf ([DEFAULT]): New section.
298
        logger.debug(u"  Fingerprint: %s", self.fingerprint)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
299
        if u"secret" in config:
300
            self.secret = config[u"secret"].decode(u"base64")
301
        elif u"secfile" in config:
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
302
            with open(os.path.expanduser(os.path.expandvars
303
                                         (config[u"secfile"])),
304
                      "rb") as secfile:
237 by Teddy Hogeborn
* mandos: Also import "with_statement" and "absolute_import" from
305
                self.secret = secfile.read()
3 by Björn Påhlsson
Python based server
306
        else:
24.1.148 by Björn Påhlsson
half working on-demand password and approved code
307
            #XXX Need to allow secret on demand!
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
    #Could possible return a bool(self._approvals_pending),
716
    #but this could mess up approvals_pending += 1 XXX 
717
    def _get_approvals_pending(self):
24.2.4 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Changed to be a property
718
        return self._approvals_pending
24.2.5 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Bug fix: Only send D-Bus
719
    def _set_approvals_pending(self, value):
24.2.4 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Changed to be a property
720
        old_value = self._approvals_pending
721
        self._approvals_pending = value
722
        bval = bool(value)
24.2.5 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Bug fix: Only send D-Bus
723
        if (hasattr(self, "dbus_object_path")
24.1.154 by Björn Påhlsson
merge
724
            and bval is not bool(old_value)):
24.2.4 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Changed to be a property
725
            dbus_bool = dbus.Boolean(bval, variant_level=1)
726
            self.PropertyChanged(dbus.String(u"approved_pending"),
727
                                 dbus_bool)
24.1.154 by Björn Påhlsson
merge
728
24.2.4 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Changed to be a property
729
    approvals_pending = property(_get_approvals_pending,
730
                                 _set_approvals_pending)
731
    del _get_approvals_pending, _set_approvals_pending
732
    
336 by Teddy Hogeborn
Code cleanup.
733
    @staticmethod
734
    def _datetime_to_dbus(dt, variant_level=0):
735
        """Convert a UTC datetime.datetime() to a D-Bus type."""
736
        return dbus.String(dt.isoformat(),
737
                           variant_level=variant_level)
738
    
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
739
    def enable(self):
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
740
        oldstate = getattr(self, u"enabled", False)
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
741
        r = Client.enable(self)
742
        if oldstate != self.enabled:
743
            # Emit D-Bus signals
744
            self.PropertyChanged(dbus.String(u"enabled"),
745
                                 dbus.Boolean(True, variant_level=1))
336 by Teddy Hogeborn
Code cleanup.
746
            self.PropertyChanged(
747
                dbus.String(u"last_enabled"),
748
                self._datetime_to_dbus(self.last_enabled,
749
                                       variant_level=1))
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
750
        return r
751
    
402 by Teddy Hogeborn
* mandos (Client.disable): Rename keyword argument "log" to "quiet",
752
    def disable(self, quiet = False):
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
753
        oldstate = getattr(self, u"enabled", False)
403 by Teddy Hogeborn
* mandos (ClientDBus.disable): Bug fix: complete rename of "log" and
754
        r = Client.disable(self, quiet=quiet)
402 by Teddy Hogeborn
* mandos (Client.disable): Rename keyword argument "log" to "quiet",
755
        if not quiet and oldstate != self.enabled:
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
756
            # Emit D-Bus signal
757
            self.PropertyChanged(dbus.String(u"enabled"),
758
                                 dbus.Boolean(False, variant_level=1))
759
        return r
760
    
761
    def __del__(self, *args, **kwargs):
762
        try:
763
            self.remove_from_connection()
329 by Teddy Hogeborn
* mandos (ClientDBus.__del__): Bug fix: Correct mispasted code, and do
764
        except LookupError:
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
765
            pass
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
766
        if hasattr(DBusObjectWithProperties, u"__del__"):
767
            DBusObjectWithProperties.__del__(self, *args, **kwargs)
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
768
        Client.__del__(self, *args, **kwargs)
769
    
770
    def checker_callback(self, pid, condition, command,
771
                         *args, **kwargs):
772
        self.checker_callback_tag = None
773
        self.checker = None
774
        # Emit D-Bus signal
775
        self.PropertyChanged(dbus.String(u"checker_running"),
776
                             dbus.Boolean(False, variant_level=1))
777
        if os.WIFEXITED(condition):
778
            exitstatus = os.WEXITSTATUS(condition)
779
            # Emit D-Bus signal
780
            self.CheckerCompleted(dbus.Int16(exitstatus),
781
                                  dbus.Int64(condition),
782
                                  dbus.String(command))
783
        else:
784
            # Emit D-Bus signal
785
            self.CheckerCompleted(dbus.Int16(-1),
786
                                  dbus.Int64(condition),
787
                                  dbus.String(command))
788
        
789
        return Client.checker_callback(self, pid, condition, command,
790
                                       *args, **kwargs)
791
    
792
    def checked_ok(self, *args, **kwargs):
793
        r = Client.checked_ok(self, *args, **kwargs)
794
        # Emit D-Bus signal
795
        self.PropertyChanged(
796
            dbus.String(u"last_checked_ok"),
336 by Teddy Hogeborn
Code cleanup.
797
            (self._datetime_to_dbus(self.last_checked_ok,
798
                                    variant_level=1)))
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
799
        return r
800
    
801
    def start_checker(self, *args, **kwargs):
802
        old_checker = self.checker
803
        if self.checker is not None:
804
            old_checker_pid = self.checker.pid
805
        else:
806
            old_checker_pid = None
807
        r = Client.start_checker(self, *args, **kwargs)
329 by Teddy Hogeborn
* mandos (ClientDBus.__del__): Bug fix: Correct mispasted code, and do
808
        # Only if new checker process was started
809
        if (self.checker is not None
810
            and old_checker_pid != self.checker.pid):
811
            # Emit D-Bus signal
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
812
            self.CheckerStarted(self.current_checker_command)
813
            self.PropertyChanged(
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
814
                dbus.String(u"checker_running"),
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
815
                dbus.Boolean(True, variant_level=1))
816
        return r
817
    
818
    def stop_checker(self, *args, **kwargs):
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
819
        old_checker = getattr(self, u"checker", None)
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
820
        r = Client.stop_checker(self, *args, **kwargs)
821
        if (old_checker is not None
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
822
            and getattr(self, u"checker", None) is None):
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
823
            self.PropertyChanged(dbus.String(u"checker_running"),
824
                                 dbus.Boolean(False, variant_level=1))
825
        return r
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
826
827
    def _reset_approved(self):
828
        self._approved = None
829
        return False
830
    
831
    def approve(self, value=True):
24.1.154 by Björn Påhlsson
merge
832
        self.send_changedstate()
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
833
        self._approved = value
24.1.154 by Björn Påhlsson
merge
834
        gobject.timeout_add(self._timedelta_to_milliseconds(self.approved_duration),
835
                            self._reset_approved)
24.2.1 by teddy at bsnet
* debian/control (mandos/Depends): Added "python-urwid".
836
    
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
837
    
24.1.147 by Björn Påhlsson
The IPC pipes are now file objects, not file descriptors:
838
    ## D-Bus methods, signals & properties
279 by Teddy Hogeborn
* mandos (Client.__init__): Disable D-Bus during init to avoid
839
    _interface = u"se.bsnet.fukt.Mandos.Client"
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
840
    
24.1.147 by Björn Påhlsson
The IPC pipes are now file objects, not file descriptors:
841
    ## Signals
381 by Teddy Hogeborn
Restore some poor D-Bus methods who got a bit hastily deleted. Enable
842
    
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
843
    # CheckerCompleted - signal
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
844
    @dbus.service.signal(_interface, signature=u"nxs")
280 by Teddy Hogeborn
* mandos (Client.CheckerCompleted): Changed signature to "nxs"; return
845
    def CheckerCompleted(self, exitcode, waitstatus, command):
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
846
        "D-Bus signal"
847
        pass
848
    
849
    # CheckerStarted - signal
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
850
    @dbus.service.signal(_interface, signature=u"s")
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
851
    def CheckerStarted(self, command):
852
        "D-Bus signal"
853
        pass
854
    
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
855
    # PropertyChanged - signal
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
856
    @dbus.service.signal(_interface, signature=u"sv")
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
857
    def PropertyChanged(self, property, value):
858
        "D-Bus signal"
859
        pass
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
860
    
387 by Teddy Hogeborn
* mandos (ClientDBus.ReceivedSecret): Renamed to "GotSecret". All
861
    # GotSecret - signal
24.1.154 by Björn Påhlsson
merge
862
    # XXXTEDDY Is sent after succesfull transfer of secret from mandos-server to mandos-client
327 by Teddy Hogeborn
Merge from pipe IPC branch.
863
    @dbus.service.signal(_interface)
387 by Teddy Hogeborn
* mandos (ClientDBus.ReceivedSecret): Renamed to "GotSecret". All
864
    def GotSecret(self):
327 by Teddy Hogeborn
Merge from pipe IPC branch.
865
        "D-Bus signal"
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.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1234
                    # XXX handle session exception
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1235
                    sent = session.send(client.secret[sent_size:])
1236
                    logger.debug(u"Sent: %d, remaining: %d",
1237
                                 sent, len(client.secret)
1238
                                 - (sent_size + sent))
1239
                    sent_size += sent
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1240
1241
                logger.info(u"Sending secret to %s", client.name)
1242
                # bump the timeout as if seen
1243
                client.checked_ok()
1244
                if self.server.use_dbus:
1245
                    # Emit D-Bus signal
1246
                    client.GotSecret()
24.1.150 by Björn Påhlsson
* mandos: Added ClientDBus.approve_pending property. Exposed
1247
            
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1248
            finally:
24.1.150 by Björn Påhlsson
* mandos: Added ClientDBus.approve_pending property. Exposed
1249
                if approval_required:
1250
                    client.approvals_pending -= 1
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1251
                session.bye()
330 by Teddy Hogeborn
* mandos (peer_certificate, fingerprint): Moved into "TCP_handler"
1252
    
1253
    @staticmethod
1254
    def peer_certificate(session):
1255
        "Return the peer's OpenPGP certificate as a bytestring"
1256
        # If not an OpenPGP certificate...
1257
        if (gnutls.library.functions
1258
            .gnutls_certificate_type_get(session._c_object)
1259
            != gnutls.library.constants.GNUTLS_CRT_OPENPGP):
1260
            # ...do the normal thing
1261
            return session.peer_certificate
1262
        list_size = ctypes.c_uint(1)
1263
        cert_list = (gnutls.library.functions
1264
                     .gnutls_certificate_get_peers
1265
                     (session._c_object, ctypes.byref(list_size)))
1266
        if not bool(cert_list) and list_size.value != 0:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1267
            raise gnutls.errors.GNUTLSError(u"error getting peer"
1268
                                            u" certificate")
330 by Teddy Hogeborn
* mandos (peer_certificate, fingerprint): Moved into "TCP_handler"
1269
        if list_size.value == 0:
1270
            return None
1271
        cert = cert_list[0]
1272
        return ctypes.string_at(cert.data, cert.size)
1273
    
1274
    @staticmethod
1275
    def fingerprint(openpgp):
1276
        "Convert an OpenPGP bytestring to a hexdigit fingerprint"
1277
        # New GnuTLS "datum" with the OpenPGP public key
1278
        datum = (gnutls.library.types
1279
                 .gnutls_datum_t(ctypes.cast(ctypes.c_char_p(openpgp),
1280
                                             ctypes.POINTER
1281
                                             (ctypes.c_ubyte)),
1282
                                 ctypes.c_uint(len(openpgp))))
1283
        # New empty GnuTLS certificate
1284
        crt = gnutls.library.types.gnutls_openpgp_crt_t()
1285
        (gnutls.library.functions
1286
         .gnutls_openpgp_crt_init(ctypes.byref(crt)))
1287
        # Import the OpenPGP public key into the certificate
1288
        (gnutls.library.functions
1289
         .gnutls_openpgp_crt_import(crt, ctypes.byref(datum),
1290
                                    gnutls.library.constants
1291
                                    .GNUTLS_OPENPGP_FMT_RAW))
1292
        # Verify the self signature in the key
1293
        crtverify = ctypes.c_uint()
1294
        (gnutls.library.functions
1295
         .gnutls_openpgp_crt_verify_self(crt, 0,
1296
                                         ctypes.byref(crtverify)))
1297
        if crtverify.value != 0:
1298
            gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1299
            raise (gnutls.errors.CertificateSecurityError
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1300
                   (u"Verify failed"))
330 by Teddy Hogeborn
* mandos (peer_certificate, fingerprint): Moved into "TCP_handler"
1301
        # New buffer for the fingerprint
1302
        buf = ctypes.create_string_buffer(20)
1303
        buf_len = ctypes.c_size_t()
1304
        # Get the fingerprint from the certificate into the buffer
1305
        (gnutls.library.functions
1306
         .gnutls_openpgp_crt_get_fingerprint(crt, ctypes.byref(buf),
1307
                                             ctypes.byref(buf_len)))
1308
        # Deinit the certificate
1309
        gnutls.library.functions.gnutls_openpgp_crt_deinit(crt)
1310
        # Convert the buffer to a Python bytestring
1311
        fpr = ctypes.string_at(buf, buf_len.value)
1312
        # Convert the bytestring to hexadecimal notation
1313
        hex_fpr = u''.join(u"%02X" % ord(char) for char in fpr)
1314
        return hex_fpr
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1315
1316
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1317
class MultiprocessingMixIn(object):
1318
    """Like socketserver.ThreadingMixIn, but with multiprocessing"""
1319
    def sub_process_main(self, request, address):
1320
        try:
1321
            self.finish_request(request, address)
1322
        except:
1323
            self.handle_error(request, address)
1324
        self.close_request(request)
1325
            
1326
    def process_request(self, request, address):
1327
        """Start a new process to process the request."""
1328
        multiprocessing.Process(target = self.sub_process_main,
1329
                                args = (request, address)).start()
1330
1331
class MultiprocessingMixInWithPipe(MultiprocessingMixIn, object):
1332
    """ adds a pipe to the MixIn """
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1333
    def process_request(self, request, client_address):
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1334
        """Overrides and wraps the original process_request().
1335
        
355 by Teddy Hogeborn
* mandos: White-space fixes only.
1336
        This function creates a new pipe in self.pipe
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1337
        """
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1338
        parent_pipe, self.child_pipe = multiprocessing.Pipe()
1339
1340
        super(MultiprocessingMixInWithPipe,
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1341
              self).process_request(request, client_address)
24.1.152 by Björn Påhlsson
bug fixes that prevent problems when runing server as root
1342
        self.child_pipe.close()
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1343
        self.add_pipe(parent_pipe)
24.1.154 by Björn Påhlsson
merge
1344
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1345
    def add_pipe(self, parent_pipe):
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1346
        """Dummy function; override as necessary"""
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1347
        pass
1348
1349
class IPv6_TCPServer(MultiprocessingMixInWithPipe,
335 by Teddy Hogeborn
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
1350
                     socketserver.TCPServer, object):
237.2.13 by Teddy Hogeborn
Merge from trunk. Notable changes:
1351
    """IPv6-capable TCP server.  Accepts 'None' as address and/or port
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1352
    
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1353
    Attributes:
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1354
        enabled:        Boolean; whether this server is activated yet
1355
        interface:      None or a network interface name (string)
1356
        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
1357
    """
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1358
    def __init__(self, server_address, RequestHandlerClass,
339 by Teddy Hogeborn
Code cleanup.
1359
                 interface=None, use_ipv6=True):
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1360
        self.interface = interface
1361
        if use_ipv6:
1362
            self.address_family = socket.AF_INET6
335 by Teddy Hogeborn
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
1363
        socketserver.TCPServer.__init__(self, server_address,
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1364
                                        RequestHandlerClass)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1365
    def server_bind(self):
1366
        """This overrides the normal server_bind() function
1367
        to bind to an interface if one was specified, and also NOT to
1368
        bind to an address or port if they were not specified."""
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1369
        if self.interface is not None:
338 by Teddy Hogeborn
* mandos: Minor doc string fixes.
1370
            if SO_BINDTODEVICE is None:
1371
                logger.error(u"SO_BINDTODEVICE does not exist;"
1372
                             u" cannot bind to interface %s",
1373
                             self.interface)
1374
            else:
1375
                try:
1376
                    self.socket.setsockopt(socket.SOL_SOCKET,
1377
                                           SO_BINDTODEVICE,
1378
                                           str(self.interface
1379
                                               + u'\0'))
1380
                except socket.error, error:
1381
                    if error[0] == errno.EPERM:
1382
                        logger.error(u"No permission to"
1383
                                     u" bind to interface %s",
1384
                                     self.interface)
1385
                    elif error[0] == errno.ENOPROTOOPT:
1386
                        logger.error(u"SO_BINDTODEVICE not available;"
1387
                                     u" cannot bind to interface %s",
1388
                                     self.interface)
1389
                    else:
1390
                        raise
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1391
        # Only bind(2) the socket if we really need to.
1392
        if self.server_address[0] or self.server_address[1]:
1393
            if not self.server_address[0]:
314 by Teddy Hogeborn
Support not using IPv6 in server:
1394
                if self.address_family == socket.AF_INET6:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1395
                    any_address = u"::" # in6addr_any
314 by Teddy Hogeborn
Support not using IPv6 in server:
1396
                else:
1397
                    any_address = socket.INADDR_ANY
1398
                self.server_address = (any_address,
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1399
                                       self.server_address[1])
49 by Teddy Hogeborn
* mandos (IPv6_TCPServer.server_bind): Bug fix: allow port to be empty
1400
            elif not self.server_address[1]:
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1401
                self.server_address = (self.server_address[0],
1402
                                       0)
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1403
#                 if self.interface:
49 by Teddy Hogeborn
* mandos (IPv6_TCPServer.server_bind): Bug fix: allow port to be empty
1404
#                     self.server_address = (self.server_address[0],
1405
#                                            0, # port
1406
#                                            0, # flowinfo
1407
#                                            if_nametoindex
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1408
#                                            (self.interface))
335 by Teddy Hogeborn
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
1409
            return socketserver.TCPServer.server_bind(self)
339 by Teddy Hogeborn
Code cleanup.
1410
1411
1412
class MandosServer(IPv6_TCPServer):
1413
    """Mandos server.
1414
    
1415
    Attributes:
1416
        clients:        set of Client objects
1417
        gnutls_priority GnuTLS priority string
1418
        use_dbus:       Boolean; to emit D-Bus signals or not
340 by Teddy Hogeborn
Code cleanup.
1419
    
1420
    Assumes a gobject.MainLoop event loop.
339 by Teddy Hogeborn
Code cleanup.
1421
    """
1422
    def __init__(self, server_address, RequestHandlerClass,
1423
                 interface=None, use_ipv6=True, clients=None,
1424
                 gnutls_priority=None, use_dbus=True):
1425
        self.enabled = False
1426
        self.clients = clients
341 by Teddy Hogeborn
Code cleanup and one bug fix.
1427
        if self.clients is None:
1428
            self.clients = set()
339 by Teddy Hogeborn
Code cleanup.
1429
        self.use_dbus = use_dbus
1430
        self.gnutls_priority = gnutls_priority
1431
        IPv6_TCPServer.__init__(self, server_address,
1432
                                RequestHandlerClass,
1433
                                interface = interface,
1434
                                use_ipv6 = use_ipv6)
163 by Teddy Hogeborn
* Makefile (PIDDIR, USER, GROUP): Removed.
1435
    def server_activate(self):
1436
        if self.enabled:
335 by Teddy Hogeborn
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
1437
            return socketserver.TCPServer.server_activate(self)
163 by Teddy Hogeborn
* Makefile (PIDDIR, USER, GROUP): Removed.
1438
    def enable(self):
1439
        self.enabled = True
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1440
    def add_pipe(self, parent_pipe):
340 by Teddy Hogeborn
Code cleanup.
1441
        # Call "handle_ipc" for both data and EOF events
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1442
        gobject.io_add_watch(parent_pipe.fileno(),
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1443
                             gobject.IO_IN | gobject.IO_HUP,
1444
                             functools.partial(self.handle_ipc,
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1445
                                               parent_pipe = parent_pipe))
1446
        
1447
    def handle_ipc(self, source, condition, parent_pipe=None,
1448
                   client_object=None):
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1449
        condition_names = {
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1450
            gobject.IO_IN: u"IN",   # There is data to read.
1451
            gobject.IO_OUT: u"OUT", # Data can be written (without
1452
                                    # blocking).
1453
            gobject.IO_PRI: u"PRI", # There is urgent data to read.
1454
            gobject.IO_ERR: u"ERR", # Error condition.
1455
            gobject.IO_HUP: u"HUP"  # Hung up (the connection has been
1456
                                    # broken, usually for pipes and
1457
                                    # sockets).
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1458
            }
1459
        conditions_string = ' | '.join(name
1460
                                       for cond, name in
1461
                                       condition_names.iteritems()
1462
                                       if cond & condition)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1463
        logger.debug(u"Handling IPC: FD = %d, condition = %s", source,
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1464
                     conditions_string)
24.1.152 by Björn Påhlsson
bug fixes that prevent problems when runing server as root
1465
24.1.154 by Björn Påhlsson
merge
1466
        # XXXTEDDY error or the other end of multiprocessing.Pipe has closed
24.1.152 by Björn Påhlsson
bug fixes that prevent problems when runing server as root
1467
        if condition & gobject.IO_HUP or condition & gobject.IO_ERR:
1468
            return False
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1469
        
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1470
        # Read a request from the child
1471
        request = parent_pipe.recv()
24.1.152 by Björn Påhlsson
bug fixes that prevent problems when runing server as root
1472
        logger.debug(u"IPC request: %s", repr(request))
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1473
        command = request[0]
1474
        
1475
        if command == 'init':
1476
            fpr = request[1]
1477
            address = request[2]
1478
            
1479
            for c in self.clients:
1480
                if c.fingerprint == fpr:
1481
                    client = c
1482
                    break
1483
            else:
1484
                logger.warning(u"Client not found for fingerprint: %s, ad"
1485
                               u"dress: %s", fpr, address)
1486
                if self.use_dbus:
1487
                    # Emit D-Bus signal
1488
                    mandos_dbus_service.ClientNotFound(fpr, address)
1489
                parent_pipe.send(False)
1490
                return False
1491
            
1492
            gobject.io_add_watch(parent_pipe.fileno(),
1493
                                 gobject.IO_IN | gobject.IO_HUP,
1494
                                 functools.partial(self.handle_ipc,
1495
                                                   parent_pipe = parent_pipe,
1496
                                                   client_object = client))
1497
            parent_pipe.send(True)
1498
            # 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.
1499
            return False
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1500
        if command == 'funcall':
1501
            funcname = request[1]
1502
            args = request[2]
1503
            kwargs = request[3]
1504
            
1505
            parent_pipe.send(('data', getattr(client_object, funcname)(*args, **kwargs)))
1506
1507
        if command == 'getattr':
1508
            attrname = request[1]
1509
            if callable(client_object.__getattribute__(attrname)):
1510
                parent_pipe.send(('function',))
1511
            else:
1512
                parent_pipe.send(('data', client_object.__getattribute__(attrname)))
24.2.4 by teddy at bsnet
* mandos (ClientDBus.approvals_pending): Changed to be a property
1513
        
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1514
        if command == 'setattr':
1515
            attrname = request[1]
1516
            value = request[2]
1517
            setattr(client_object, attrname, value)
24.1.154 by Björn Påhlsson
merge
1518
288.1.1 by Teddy Hogeborn
Start of new pipe-based IPC mechanism.
1519
        return True
10 by Teddy Hogeborn
* server.py: Bug fix: Do "from __future__ import division".
1520
3 by Björn Påhlsson
Python based server
1521
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
1522
def string_to_delta(interval):
1523
    """Parse a string and return a datetime.timedelta
290 by Teddy Hogeborn
* mandos (main): Bug fix: Do setgid before setuid. Add verbose GnuTLS
1524
    
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1525
    >>> string_to_delta(u'7d')
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
1526
    datetime.timedelta(7)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1527
    >>> string_to_delta(u'60s')
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
1528
    datetime.timedelta(0, 60)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1529
    >>> string_to_delta(u'60m')
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
1530
    datetime.timedelta(0, 3600)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1531
    >>> string_to_delta(u'24h')
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
1532
    datetime.timedelta(1)
1533
    >>> string_to_delta(u'1w')
1534
    datetime.timedelta(7)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1535
    >>> string_to_delta(u'5m 30s')
93 by Teddy Hogeborn
* mandos (string_to_delta): Accept a whitespace-separated sequence of
1536
    datetime.timedelta(0, 330)
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
1537
    """
93 by Teddy Hogeborn
* mandos (string_to_delta): Accept a whitespace-separated sequence of
1538
    timevalue = datetime.timedelta(0)
1539
    for s in interval.split():
1540
        try:
215 by Teddy Hogeborn
* mandos: Remove unused "select" module. Import "ctypes.util".
1541
            suffix = unicode(s[-1])
1542
            value = int(s[:-1])
93 by Teddy Hogeborn
* mandos (string_to_delta): Accept a whitespace-separated sequence of
1543
            if suffix == u"d":
1544
                delta = datetime.timedelta(value)
1545
            elif suffix == u"s":
1546
                delta = datetime.timedelta(0, value)
1547
            elif suffix == u"m":
1548
                delta = datetime.timedelta(0, 0, 0, 0, value)
1549
            elif suffix == u"h":
1550
                delta = datetime.timedelta(0, 0, 0, 0, 0, value)
1551
            elif suffix == u"w":
1552
                delta = datetime.timedelta(0, 0, 0, 0, 0, 0, value)
1553
            else:
400 by Teddy Hogeborn
* mandos (Client.enable): Bug fix: Start new immediate checker last to
1554
                raise ValueError(u"Unknown suffix %r" % suffix)
1555
        except (ValueError, IndexError), e:
1556
            raise ValueError(e.message)
93 by Teddy Hogeborn
* mandos (string_to_delta): Accept a whitespace-separated sequence of
1557
        timevalue += delta
1558
    return timevalue
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
1559
8 by Teddy Hogeborn
* Makefile (client_debug): Bug fix; add quotes and / to CERT_ROOT.
1560
24.1.13 by Björn Påhlsson
mandosclient
1561
def if_nametoindex(interface):
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1562
    """Call the C function if_nametoindex(), or equivalent
1563
    
1564
    Note: This function cannot accept a unicode string."""
24.1.13 by Björn Påhlsson
mandosclient
1565
    global if_nametoindex
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1566
    try:
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
1567
        if_nametoindex = (ctypes.cdll.LoadLibrary
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1568
                          (ctypes.util.find_library(u"c"))
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
1569
                          .if_nametoindex)
12 by Teddy Hogeborn
* mandos-clients.conf ([foo]/dn, [foo]/password, [braxen_client]/dn,
1570
    except (OSError, AttributeError):
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1571
        logger.warning(u"Doing if_nametoindex the hard way")
24.1.13 by Björn Påhlsson
mandosclient
1572
        def if_nametoindex(interface):
28 by Teddy Hogeborn
* server.conf: New file.
1573
            "Get an interface index the hard way, i.e. using fcntl()"
1574
            SIOCGIFINDEX = 0x8933  # From /usr/include/linux/sockios.h
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1575
            with contextlib.closing(socket.socket()) as s:
237 by Teddy Hogeborn
* mandos: Also import "with_statement" and "absolute_import" from
1576
                ifreq = fcntl.ioctl(s, SIOCGIFINDEX,
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1577
                                    struct.pack(str(u"16s16x"),
1578
                                                interface))
1579
            interface_index = struct.unpack(str(u"I"),
1580
                                            ifreq[16:20])[0]
28 by Teddy Hogeborn
* server.conf: New file.
1581
            return interface_index
24.1.13 by Björn Påhlsson
mandosclient
1582
    return if_nametoindex(interface)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1583
1584
47 by Teddy Hogeborn
* plugbasedclient.c: Renamed to "mandos-client.c". All users changed.
1585
def daemon(nochdir = False, noclose = False):
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
1586
    """See daemon(3).  Standard BSD Unix function.
331 by Teddy Hogeborn
Minor code cleanup, and a bug fix.
1587
    
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
1588
    This should really exist as os.daemon, but it doesn't (yet)."""
1589
    if os.fork():
1590
        sys.exit()
1591
    os.setsid()
1592
    if not nochdir:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1593
        os.chdir(u"/")
46 by Teddy Hogeborn
* network-protocol.txt: New.
1594
    if os.fork():
1595
        sys.exit()
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
1596
    if not noclose:
1597
        # Close all standard open file descriptors
28 by Teddy Hogeborn
* server.conf: New file.
1598
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
1599
        if not stat.S_ISCHR(os.fstat(null).st_mode):
1600
            raise OSError(errno.ENODEV,
388 by Teddy Hogeborn
* mandos (daemon): Use "os.path.devnull" in the error message.
1601
                          u"%s not a character device"
1602
                          % os.path.devnull)
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
1603
        os.dup2(null, sys.stdin.fileno())
1604
        os.dup2(null, sys.stdout.fileno())
1605
        os.dup2(null, sys.stderr.fileno())
1606
        if null > 2:
1607
            os.close(null)
1608
1609
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
1610
def main():
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1611
    
379 by Teddy Hogeborn
* mandos: Fix line lengths.
1612
    ##################################################################
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1613
    # Parsing of options, both command line and config file
1614
    
237.2.1 by Teddy Hogeborn
Merge from trunk, but disable the unfinished D-Bus feature:
1615
    parser = optparse.OptionParser(version = "%%prog %s" % version)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1616
    parser.add_option("-i", u"--interface", type=u"string",
1617
                      metavar="IF", help=u"Bind to interface IF")
1618
    parser.add_option("-a", u"--address", type=u"string",
1619
                      help=u"Address to listen for requests on")
1620
    parser.add_option("-p", u"--port", type=u"int",
1621
                      help=u"Port number to receive requests on")
1622
    parser.add_option("--check", action=u"store_true",
1623
                      help=u"Run self-test")
1624
    parser.add_option("--debug", action=u"store_true",
1625
                      help=u"Debug mode; run in foreground and log to"
1626
                      u" terminal")
1627
    parser.add_option("--priority", type=u"string", help=u"GnuTLS"
1628
                      u" priority string (see GnuTLS documentation)")
1629
    parser.add_option("--servicename", type=u"string",
1630
                      metavar=u"NAME", help=u"Zeroconf service name")
1631
    parser.add_option("--configdir", type=u"string",
1632
                      default=u"/etc/mandos", metavar=u"DIR",
1633
                      help=u"Directory to search for configuration"
1634
                      u" files")
1635
    parser.add_option("--no-dbus", action=u"store_false",
1636
                      dest=u"use_dbus", help=u"Do not provide D-Bus"
1637
                      u" system bus interface")
1638
    parser.add_option("--no-ipv6", action=u"store_false",
1639
                      dest=u"use_ipv6", help=u"Do not use IPv6")
215 by Teddy Hogeborn
* mandos: Remove unused "select" module. Import "ctypes.util".
1640
    options = parser.parse_args()[0]
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1641
    
4 by Teddy Hogeborn
* server.py (Client.created, Client.next_check): New.
1642
    if options.check:
1643
        import doctest
1644
        doctest.testmod()
1645
        sys.exit()
3 by Björn Påhlsson
Python based server
1646
    
28 by Teddy Hogeborn
* server.conf: New file.
1647
    # Default values for config file for server-global settings
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1648
    server_defaults = { u"interface": u"",
1649
                        u"address": u"",
1650
                        u"port": u"",
1651
                        u"debug": u"False",
1652
                        u"priority":
1653
                        u"SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP",
1654
                        u"servicename": u"Mandos",
1655
                        u"use_dbus": u"True",
1656
                        u"use_ipv6": u"True",
28 by Teddy Hogeborn
* server.conf: New file.
1657
                        }
1658
    
1659
    # Parse config file for server-global settings
335 by Teddy Hogeborn
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
1660
    server_config = configparser.SafeConfigParser(server_defaults)
28 by Teddy Hogeborn
* server.conf: New file.
1661
    del server_defaults
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1662
    server_config.read(os.path.join(options.configdir,
1663
                                    u"mandos.conf"))
28 by Teddy Hogeborn
* server.conf: New file.
1664
    # Convert the SafeConfigParser object to a dict
89 by Teddy Hogeborn
* Makefile: Bug fix: fixed creation of man pages for section 5 pages.
1665
    server_settings = server_config.defaults()
282 by Teddy Hogeborn
* mandos (main): Bug fix: use "getint" on the "port" config file
1666
    # Use the appropriate methods on the non-string config options
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1667
    for option in (u"debug", u"use_dbus", u"use_ipv6"):
1668
        server_settings[option] = server_config.getboolean(u"DEFAULT",
1669
                                                           option)
282 by Teddy Hogeborn
* mandos (main): Bug fix: use "getint" on the "port" config file
1670
    if server_settings["port"]:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1671
        server_settings["port"] = server_config.getint(u"DEFAULT",
1672
                                                       u"port")
28 by Teddy Hogeborn
* server.conf: New file.
1673
    del server_config
1674
    
1675
    # Override the settings from the config file with command line
1676
    # options, if set.
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1677
    for option in (u"interface", u"address", u"port", u"debug",
1678
                   u"priority", u"servicename", u"configdir",
1679
                   u"use_dbus", u"use_ipv6"):
28 by Teddy Hogeborn
* server.conf: New file.
1680
        value = getattr(options, option)
1681
        if value is not None:
1682
            server_settings[option] = value
1683
    del options
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1684
    # Force all strings to be unicode
1685
    for option in server_settings.keys():
1686
        if type(server_settings[option]) is str:
1687
            server_settings[option] = unicode(server_settings[option])
28 by Teddy Hogeborn
* server.conf: New file.
1688
    # Now we have our good server settings in "server_settings"
1689
    
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1690
    ##################################################################
1691
    
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1692
    # For convenience
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1693
    debug = server_settings[u"debug"]
1694
    use_dbus = server_settings[u"use_dbus"]
1695
    use_ipv6 = server_settings[u"use_ipv6"]
52 by Teddy Hogeborn
* mandos: Make syslog use "/dev/log" instead of UDP to localhost.
1696
    
1697
    if not debug:
1698
        syslogger.setLevel(logging.WARNING)
61 by Teddy Hogeborn
* mandos (console): Define handler globally.
1699
        console.setLevel(logging.WARNING)
52 by Teddy Hogeborn
* mandos: Make syslog use "/dev/log" instead of UDP to localhost.
1700
    
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1701
    if server_settings[u"servicename"] != u"Mandos":
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
1702
        syslogger.setFormatter(logging.Formatter
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1703
                               (u'Mandos (%s) [%%(process)d]:'
1704
                                u' %%(levelname)s: %%(message)s'
1705
                                % server_settings[u"servicename"]))
52 by Teddy Hogeborn
* mandos: Make syslog use "/dev/log" instead of UDP to localhost.
1706
    
28 by Teddy Hogeborn
* server.conf: New file.
1707
    # Parse config file with clients
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1708
    client_defaults = { u"timeout": u"1h",
1709
                        u"interval": u"5m",
1710
                        u"checker": u"fping -q -- %%(host)s",
1711
                        u"host": u"",
24.2.1 by teddy at bsnet
* debian/control (mandos/Depends): Added "python-urwid".
1712
                        u"approved_delay": u"0s",
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1713
                        u"approved_duration": u"1s",
28 by Teddy Hogeborn
* server.conf: New file.
1714
                        }
335 by Teddy Hogeborn
* mandos: Import "SocketServer" as "socketserver" and "ConfigParser"
1715
    client_config = configparser.SafeConfigParser(client_defaults)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1716
    client_config.read(os.path.join(server_settings[u"configdir"],
1717
                                    u"clients.conf"))
1718
    
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1719
    global mandos_dbus_service
1720
    mandos_dbus_service = None
28 by Teddy Hogeborn
* server.conf: New file.
1721
    
339 by Teddy Hogeborn
Code cleanup.
1722
    tcp_server = MandosServer((server_settings[u"address"],
1723
                               server_settings[u"port"]),
1724
                              ClientHandler,
1725
                              interface=server_settings[u"interface"],
1726
                              use_ipv6=use_ipv6,
1727
                              gnutls_priority=
1728
                              server_settings[u"priority"],
1729
                              use_dbus=use_dbus)
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1730
    pidfilename = u"/var/run/mandos.pid"
164 by Teddy Hogeborn
* mandos: Open the PID file before daemonizing, but write to it
1731
    try:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1732
        pidfile = open(pidfilename, u"w")
292 by Teddy Hogeborn
* Makefile (run-server): Use "--no-dbus" unconditionally.
1733
    except IOError:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1734
        logger.error(u"Could not open file %r", pidfilename)
164 by Teddy Hogeborn
* mandos: Open the PID file before daemonizing, but write to it
1735
    
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
1736
    try:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1737
        uid = pwd.getpwnam(u"_mandos").pw_uid
1738
        gid = pwd.getpwnam(u"_mandos").pw_gid
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
1739
    except KeyError:
1740
        try:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1741
            uid = pwd.getpwnam(u"mandos").pw_uid
1742
            gid = pwd.getpwnam(u"mandos").pw_gid
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
1743
        except KeyError:
1744
            try:
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1745
                uid = pwd.getpwnam(u"nobody").pw_uid
1746
                gid = pwd.getpwnam(u"nobody").pw_gid
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
1747
            except KeyError:
1748
                uid = 65534
1749
                gid = 65534
163 by Teddy Hogeborn
* Makefile (PIDDIR, USER, GROUP): Removed.
1750
    try:
290 by Teddy Hogeborn
* mandos (main): Bug fix: Do setgid before setuid. Add verbose GnuTLS
1751
        os.setgid(gid)
163 by Teddy Hogeborn
* Makefile (PIDDIR, USER, GROUP): Removed.
1752
        os.setuid(uid)
1753
    except OSError, error:
1754
        if error[0] != errno.EPERM:
1755
            raise error
164 by Teddy Hogeborn
* mandos: Open the PID file before daemonizing, but write to it
1756
    
290 by Teddy Hogeborn
* mandos (main): Bug fix: Do setgid before setuid. Add verbose GnuTLS
1757
    # Enable all possible GnuTLS debugging
1758
    if debug:
1759
        # "Use a log level over 10 to enable all debugging options."
1760
        # - GnuTLS manual
1761
        gnutls.library.functions.gnutls_global_set_log_level(11)
1762
        
1763
        @gnutls.library.types.gnutls_log_func
1764
        def debug_gnutls(level, string):
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1765
            logger.debug(u"GnuTLS: %s", string[:-1])
290 by Teddy Hogeborn
* mandos (main): Bug fix: Do setgid before setuid. Add verbose GnuTLS
1766
        
1767
        (gnutls.library.functions
1768
         .gnutls_global_set_log_function(debug_gnutls))
1769
    
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
1770
    global main_loop
1771
    # From the Avahi example code
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1772
    DBusGMainLoop(set_as_default=True )
1773
    main_loop = gobject.MainLoop()
1774
    bus = dbus.SystemBus()
1775
    # End of Avahi example code
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1776
    if use_dbus:
402 by Teddy Hogeborn
* mandos (Client.disable): Rename keyword argument "log" to "quiet",
1777
        try:
1778
            bus_name = dbus.service.BusName(u"se.bsnet.fukt.Mandos",
1779
                                            bus, do_not_queue=True)
1780
        except dbus.exceptions.NameExistsException, e:
1781
            logger.error(unicode(e) + u", disabling D-Bus")
1782
            use_dbus = False
1783
            server_settings[u"use_dbus"] = False
1784
            tcp_server.use_dbus = False
337 by Teddy Hogeborn
Code cleanup. Move some global stuff into main.
1785
    protocol = avahi.PROTO_INET6 if use_ipv6 else avahi.PROTO_INET
1786
    service = AvahiService(name = server_settings[u"servicename"],
1787
                           servicetype = u"_mandos._tcp",
1788
                           protocol = protocol, bus = bus)
1789
    if server_settings["interface"]:
1790
        service.interface = (if_nametoindex
1791
                             (str(server_settings[u"interface"])))
24.1.152 by Björn Påhlsson
bug fixes that prevent problems when runing server as root
1792
1793
    global multiprocessing_manager
1794
    multiprocessing_manager = multiprocessing.Manager()
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1795
    
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
1796
    client_class = Client
1797
    if use_dbus:
337 by Teddy Hogeborn
Code cleanup. Move some global stuff into main.
1798
        client_class = functools.partial(ClientDBus, bus = bus)
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1799
    def client_config_items(config, section):
1800
        special_settings = {
24.1.150 by Björn Påhlsson
* mandos: Added ClientDBus.approve_pending property. Exposed
1801
            "approved_by_default":
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1802
                lambda: config.getboolean(section,
24.1.150 by Björn Påhlsson
* mandos: Added ClientDBus.approve_pending property. Exposed
1803
                                          "approved_by_default"),
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1804
            }
1805
        for name, value in config.items(section):
1806
            try:
24.1.150 by Björn Påhlsson
* mandos: Added ClientDBus.approve_pending property. Exposed
1807
                yield (name, special_settings[name]())
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1808
            except KeyError:
1809
                yield (name, value)
1810
    
341 by Teddy Hogeborn
Code cleanup and one bug fix.
1811
    tcp_server.clients.update(set(
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
1812
            client_class(name = section,
24.1.149 by Björn Påhlsson
Changed ForkingMixIn in favor of multiprocessing
1813
                         config= dict(client_config_items(
1814
                        client_config, section)))
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
1815
            for section in client_config.sections()))
341 by Teddy Hogeborn
Code cleanup and one bug fix.
1816
    if not tcp_server.clients:
244 by Teddy Hogeborn
* debian/control (Build-Depends): Bug fix: Added "docbook-xml".
1817
        logger.warning(u"No clients defined")
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
1818
    
94 by Teddy Hogeborn
* clients.conf ([DEFAULT]/checker): Update to new default value.
1819
    if debug:
1820
        # Redirect stdin so all checkers get /dev/null
1821
        null = os.open(os.path.devnull, os.O_NOCTTY | os.O_RDWR)
1822
        os.dup2(null, sys.stdin.fileno())
1823
        if null > 2:
1824
            os.close(null)
1825
    else:
1826
        # No console logging
61 by Teddy Hogeborn
* mandos (console): Define handler globally.
1827
        logger.removeHandler(console)
94 by Teddy Hogeborn
* clients.conf ([DEFAULT]/checker): Update to new default value.
1828
        # Close all input and output, do double fork, etc.
47 by Teddy Hogeborn
* plugbasedclient.c: Renamed to "mandos-client.c". All users changed.
1829
        daemon()
51 by Teddy Hogeborn
* clients.conf: Better comments.
1830
    
165 by Teddy Hogeborn
* mandos (main): Use EAFP with pidfile.
1831
    try:
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1832
        with pidfile:
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1833
            pid = os.getpid()
1834
            pidfile.write(str(pid) + "\n")
165 by Teddy Hogeborn
* mandos (main): Use EAFP with pidfile.
1835
        del pidfile
215 by Teddy Hogeborn
* mandos: Remove unused "select" module. Import "ctypes.util".
1836
    except IOError:
165 by Teddy Hogeborn
* mandos (main): Use EAFP with pidfile.
1837
        logger.error(u"Could not write to file %r with PID %d",
1838
                     pidfilename, pid)
1839
    except NameError:
1840
        # "pidfile" was never created
1841
        pass
164 by Teddy Hogeborn
* mandos: Open the PID file before daemonizing, but write to it
1842
    del pidfilename
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
1843
    
1844
    if not debug:
1845
        signal.signal(signal.SIGINT, signal.SIG_IGN)
28 by Teddy Hogeborn
* server.conf: New file.
1846
    signal.signal(signal.SIGHUP, lambda signum, frame: sys.exit())
1847
    signal.signal(signal.SIGTERM, lambda signum, frame: sys.exit())
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
1848
    
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1849
    if use_dbus:
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1850
        class MandosDBusService(dbus.service.Object):
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1851
            """A D-Bus proxy object"""
1852
            def __init__(self):
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1853
                dbus.service.Object.__init__(self, bus, u"/")
279 by Teddy Hogeborn
* mandos (Client.__init__): Disable D-Bus during init to avoid
1854
            _interface = u"se.bsnet.fukt.Mandos"
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1855
            
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1856
            @dbus.service.signal(_interface, signature=u"o")
1857
            def ClientAdded(self, objpath):
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1858
                "D-Bus signal"
1859
                pass
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1860
            
409 by Teddy Hogeborn
* mandos (MandosServer.handle_ipc): Better log message.
1861
            @dbus.service.signal(_interface, signature=u"ss")
1862
            def ClientNotFound(self, fingerprint, address):
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1863
                "D-Bus signal"
1864
                pass
1865
            
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1866
            @dbus.service.signal(_interface, signature=u"os")
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1867
            def ClientRemoved(self, objpath, name):
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1868
                "D-Bus signal"
1869
                pass
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1870
            
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1871
            @dbus.service.method(_interface, out_signature=u"ao")
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1872
            def GetAllClients(self):
283 by Teddy Hogeborn
* mandos (peer_certificate): Handle NULL pointer from
1873
                "D-Bus method"
341 by Teddy Hogeborn
Code cleanup and one bug fix.
1874
                return dbus.Array(c.dbus_object_path
1875
                                  for c in tcp_server.clients)
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1876
            
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1877
            @dbus.service.method(_interface,
1878
                                 out_signature=u"a{oa{sv}}")
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1879
            def GetAllClientsWithProperties(self):
283 by Teddy Hogeborn
* mandos (peer_certificate): Handle NULL pointer from
1880
                "D-Bus method"
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1881
                return dbus.Dictionary(
380 by Teddy Hogeborn
Use D-Bus properties instead of our own methods.
1882
                    ((c.dbus_object_path, c.GetAll(u""))
341 by Teddy Hogeborn
Code cleanup and one bug fix.
1883
                     for c in tcp_server.clients),
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1884
                    signature=u"oa{sv}")
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1885
            
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1886
            @dbus.service.method(_interface, in_signature=u"o")
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1887
            def RemoveClient(self, object_path):
283 by Teddy Hogeborn
* mandos (peer_certificate): Handle NULL pointer from
1888
                "D-Bus method"
341 by Teddy Hogeborn
Code cleanup and one bug fix.
1889
                for c in tcp_server.clients:
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1890
                    if c.dbus_object_path == object_path:
341 by Teddy Hogeborn
Code cleanup and one bug fix.
1891
                        tcp_server.clients.remove(c)
328 by Teddy Hogeborn
* mandos (Client): Move all D-Bus code to new "ClientDBus" class.
1892
                        c.remove_from_connection()
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1893
                        # Don't signal anything except ClientRemoved
402 by Teddy Hogeborn
* mandos (Client.disable): Rename keyword argument "log" to "quiet",
1894
                        c.disable(quiet=True)
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1895
                        # Emit D-Bus signal
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1896
                        self.ClientRemoved(object_path, c.name)
1897
                        return
400 by Teddy Hogeborn
* mandos (Client.enable): Bug fix: Start new immediate checker last to
1898
                raise KeyError(object_path)
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1899
            
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1900
            del _interface
278 by Teddy Hogeborn
* mandos (Client.GetAllProperties): Also send Client.dbus_object_path
1901
        
327 by Teddy Hogeborn
Merge from pipe IPC branch.
1902
        mandos_dbus_service = MandosDBusService()
238 by Teddy Hogeborn
First version of a somewhat complete D-Bus server interface. Also
1903
    
400 by Teddy Hogeborn
* mandos (Client.enable): Bug fix: Start new immediate checker last to
1904
    def cleanup():
1905
        "Cleanup function; run on exit"
1906
        service.cleanup()
1907
        
1908
        while tcp_server.clients:
1909
            client = tcp_server.clients.pop()
402 by Teddy Hogeborn
* mandos (Client.disable): Rename keyword argument "log" to "quiet",
1910
            if use_dbus:
1911
                client.remove_from_connection()
400 by Teddy Hogeborn
* mandos (Client.enable): Bug fix: Start new immediate checker last to
1912
            client.disable_hook = None
1913
            # Don't signal anything except ClientRemoved
402 by Teddy Hogeborn
* mandos (Client.disable): Rename keyword argument "log" to "quiet",
1914
            client.disable(quiet=True)
400 by Teddy Hogeborn
* mandos (Client.enable): Bug fix: Start new immediate checker last to
1915
            if use_dbus:
1916
                # Emit D-Bus signal
1917
                mandos_dbus_service.ClientRemoved(client.dbus_object_path,
1918
                                                  client.name)
1919
    
1920
    atexit.register(cleanup)
1921
    
341 by Teddy Hogeborn
Code cleanup and one bug fix.
1922
    for client in tcp_server.clients:
243 by Teddy Hogeborn
* mandos (Client.timeout, Client.interval): Changed from being a
1923
        if use_dbus:
1924
            # Emit D-Bus signal
411 by Teddy Hogeborn
More consistent terminology: Clients are no longer "invalid" - they
1925
            mandos_dbus_service.ClientAdded(client.dbus_object_path)
239 by Teddy Hogeborn
* mandos (_datetime_to_dbus_struct): Renamed to "_datetime_to_dbus";
1926
        client.enable()
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1927
    
163 by Teddy Hogeborn
* Makefile (PIDDIR, USER, GROUP): Removed.
1928
    tcp_server.enable()
1929
    tcp_server.server_activate()
1930
    
28 by Teddy Hogeborn
* server.conf: New file.
1931
    # Find out what port we got
1932
    service.port = tcp_server.socket.getsockname()[1]
314 by Teddy Hogeborn
Support not using IPv6 in server:
1933
    if use_ipv6:
1934
        logger.info(u"Now listening on address %r, port %d,"
1935
                    " flowinfo %d, scope_id %d"
1936
                    % tcp_server.socket.getsockname())
1937
    else:                       # IPv4
1938
        logger.info(u"Now listening on address %r, port %d"
1939
                    % tcp_server.socket.getsockname())
28 by Teddy Hogeborn
* server.conf: New file.
1940
    
29 by Teddy Hogeborn
* plugins.d/mandosclient.c (start_mandos_communication): Changed
1941
    #service.interface = tcp_server.socket.getsockname()[3]
28 by Teddy Hogeborn
* server.conf: New file.
1942
    
1943
    try:
1944
        # From the Avahi example code
1945
        try:
336 by Teddy Hogeborn
Code cleanup.
1946
            service.activate()
28 by Teddy Hogeborn
* server.conf: New file.
1947
        except dbus.exceptions.DBusException, error:
1948
            logger.critical(u"DBusException: %s", error)
401 by Teddy Hogeborn
* README (FAQ): Fix typo.
1949
            cleanup()
28 by Teddy Hogeborn
* server.conf: New file.
1950
            sys.exit(1)
1951
        # End of Avahi example code
1952
        
1953
        gobject.io_add_watch(tcp_server.fileno(), gobject.IO_IN,
1954
                             lambda *args, **kwargs:
237.1.2 by Teddy Hogeborn
Further steps towards a D-Bus server interface, plus minor syntax
1955
                             (tcp_server.handle_request
1956
                              (*args[2:], **kwargs) or True))
28 by Teddy Hogeborn
* server.conf: New file.
1957
        
51 by Teddy Hogeborn
* clients.conf: Better comments.
1958
        logger.debug(u"Starting main loop")
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1959
        main_loop.run()
28 by Teddy Hogeborn
* server.conf: New file.
1960
    except AvahiError, error:
242 by Teddy Hogeborn
* mandos (AvahiError): Converted to use unicode. All users changed.
1961
        logger.critical(u"AvahiError: %s", error)
401 by Teddy Hogeborn
* README (FAQ): Fix typo.
1962
        cleanup()
28 by Teddy Hogeborn
* server.conf: New file.
1963
        sys.exit(1)
11 by Teddy Hogeborn
* server.py: Rewritten to use Zeroconf (mDNS/DNS-SD) in place of the
1964
    except KeyboardInterrupt:
15 by Teddy Hogeborn
* mandos-clients.conf ([foo]): Uncommented.
1965
        if debug:
292 by Teddy Hogeborn
* Makefile (run-server): Use "--no-dbus" unconditionally.
1966
            print >> sys.stderr
333 by Teddy Hogeborn
Minor code cleanup; one minor bug fix.
1967
        logger.debug(u"Server received KeyboardInterrupt")
1968
    logger.debug(u"Server exiting")
401 by Teddy Hogeborn
* README (FAQ): Fix typo.
1969
    # Must run before the D-Bus bus name gets deregistered
1970
    cleanup()
16 by Teddy Hogeborn
* Makefile: Include targets for all binaries.
1971
1972
if __name__ == '__main__':
1973
    main()