~ubuntu-branches/ubuntu/precise/nss-pam-ldapd/precise-security

« back to all changes in this revision

Viewing changes to pynslcd/pynslcd.py

  • Committer: Package Import Robot
  • Author(s): Arthur de Jong
  • Date: 2011-09-04 21:00:00 UTC
  • mfrom: (14.1.4 experimental)
  • Revision ID: package-import@ubuntu.com-20110904210000-pe3u91iga88vtr16
Tags: 0.8.4
* Upload to unstable
* switch to using the member attribute by default instead of
  uniqueMember (backwards incompatible change)
* only return "x" as a password hash when the object has the shadowAccount
  objectClass and nsswitch.conf is configured to do shadow lookups using
  LDAP (this avoids some problems with pam_unix)
* fix problem with partial attribute name matches in DN (thanks Timothy
  White)
* fix a problem with objectSid mappings with recent versions of OpenLDAP
  (patch by Wesley Mason)
* set the socket timeout in a connection callback to avoid timeout
  issues during the SSL handshake (patch by Stefan Völkel)
* check for unknown variables in pam_authz_search
* only check password expiration when authenticating, only check account
  expiration when doing authorisation
* make buffer sizes consistent and grow all buffers holding string
  representations of numbers to be able to hold 64-bit numbers
* update AX_PTHREAD from autoconf-archive
* support querying DNS SRV records from a different domain than the current
  one (based on a patch by James M. Leddy)
* fix a problem with uninitialised memory while parsing the tls_ciphers
  option (closes: #638872) (but doesn't work yet due to #640384)
* implement bounds checking of numeric values read from LDAP (patch by
  Jakub Hrozek)
* correctly support large uid and gid values from LDAP (patch by Jakub
  Hrozek)
* improvements to the configure script (patch by Jakub Hrozek)
* switch to dh for debian/rules and bump debhelper compatibility to 8
* build Debian packages with multiarch support
* ship shlibs (but still no symbol files) for libnss-ldapd since that was
  the easiest way to support multiarch
* fix output in init script when restarting nslcd (closes: #637132)
* correctly handle leading and trailing spaces in preseeded debconf uri
  option (patch by Andreas B. Mundt) (closes: #637863)
* support spaces around database names in /etc/nsswitch.conf while
  configuring package (closes: #640185)
* updated Russian debconf translation by Yuri Kozlov (closes: #637751)
* updated French debconf translation by Christian Perrier (closes: #637756)
* added Slovak debconf translation by Slavko (closes: #637759)
* updated Danish debconf translation by Joe Hansen (closes :#637763)
* updated Brazilian Portuguese debconf translation by Denis Doria
* updated Portuguese debconf translation by Américo Monteiro
* updated Japanese debconf translation by Kenshi Muto (closes: #638195)
* updated Czech debconf translation by Miroslav Kure (closes: #639026)
* updated German debconf translation by Chris Leick (closes: #639107)
* updated Spanish debconf translation by Francisco Javier Cuadrado
  (closes: #639236)
* updated Dutch debconf translation by Arthur de Jong with help from Paul
  Gevers and Jeroen Schot

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
 
 
3
# pynslcd.py - main daemon module
 
4
#
 
5
# Copyright (C) 2010, 2011 Arthur de Jong
 
6
#
 
7
# This library is free software; you can redistribute it and/or
 
8
# modify it under the terms of the GNU Lesser General Public
 
9
# License as published by the Free Software Foundation; either
 
10
# version 2.1 of the License, or (at your option) any later version.
 
11
#
 
12
# This library is distributed in the hope that it will be useful,
 
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
15
# Lesser General Public License for more details.
 
16
#
 
17
# You should have received a copy of the GNU Lesser General Public
 
18
# License along with this library; if not, write to the Free Software
 
19
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 
20
# 02110-1301 USA
 
21
 
 
22
import os
 
23
import sys
 
24
import daemon
 
25
import mypidfile
 
26
import threading
 
27
import logging
 
28
import logging.handlers
 
29
import signal
 
30
import ldap
 
31
 
 
32
import constants  # from nslcd.h
 
33
import config     # from configure
 
34
import cfg        # from nslcd.conf
 
35
import common
 
36
 
 
37
from tio import TIOStream
 
38
 
 
39
 
 
40
# configure logging
 
41
class MyFormatter(logging.Formatter):
 
42
    def format(self, record):
 
43
        msg = logging.Formatter.format(self, record)
 
44
        if record.levelno == logging.DEBUG:
 
45
            msg = 'DEBUG: %s' % msg
 
46
        return msg
 
47
#logging.basicConfig(level=logging.INFO)
 
48
# , format='%(message)s'
 
49
formatter = MyFormatter('%(message)s')
 
50
stderrhandler = logging.StreamHandler(sys.stderr)
 
51
stderrhandler.setFormatter(formatter)
 
52
##sysloghandler = logging.handlers.SysLogHandler(address='/dev/log')
 
53
##sysloghandler.setFormatter(formatter)
 
54
#logging.getLogger().setFormatter(MyFormatter())
 
55
logging.getLogger().addHandler(stderrhandler)
 
56
 
 
57
#logger = logging.getLogger()
 
58
#logger.setLevel(logging.INFO)
 
59
#syslog = logging.handlers.SysLogHandler(address='/dev/log')
 
60
#formatter = logging.Formatter('%(name)s: %(levelname)s %(message)s')
 
61
#syslog.setFormatter(formatter)
 
62
#logger.addHandler(syslog)
 
63
 
 
64
def display_version(fp):
 
65
    fp.write('%(PACKAGE_STRING)s\n'
 
66
             'Written by Arthur de Jong.\n'
 
67
             '\n'
 
68
             'Copyright (C) 2010, 2011 Arthur de Jong\n'
 
69
             'This is free software; see the source for copying conditions.  There is NO\n'
 
70
             'warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n'
 
71
             % { 'PACKAGE_STRING': config.PACKAGE_STRING, } )
 
72
 
 
73
def display_usage(fp):
 
74
    fp.write("Usage: %(program_name)s [OPTION]...\n"
 
75
             "Name Service LDAP connection daemon.\n"
 
76
             "  -c, --check        check if the daemon already is running\n"
 
77
             "  -d, --debug        don't fork and print debugging to stderr\n"
 
78
             "      --help         display this help and exit\n"
 
79
             "      --version      output version information and exit\n"
 
80
             "\n"
 
81
             "Report bugs to <%(PACKAGE_BUGREPORT)s>.\n"
 
82
             % { 'program_name': cfg.program_name,
 
83
                 'PACKAGE_BUGREPORT': config.PACKAGE_BUGREPORT, } )
 
84
 
 
85
def parse_cmdline():
 
86
    """Parse command-line arguments."""
 
87
    import getopt
 
88
    cfg.program_name = sys.argv[0] or 'pynslcd'
 
89
    try:
 
90
        optlist, args = getopt.gnu_getopt(sys.argv[1:],
 
91
          'cdhV', ('check', 'debug', 'help', 'version', ))
 
92
        for flag, arg in optlist:
 
93
            if flag in ('-c', '--check'):
 
94
                cfg.check = True
 
95
            elif flag in ('-d', '--debug'):
 
96
                cfg.debug += 1
 
97
            elif flag in ('-h', '--help'):
 
98
                display_usage(sys.stdout)
 
99
                sys.exit(0)
 
100
            elif flag in ('-V', '--version'):
 
101
                display_version(sys.stdout)
 
102
                sys.exit(0)
 
103
        if len(args):
 
104
            raise getopt.GetoptError('unrecognized option \'%s\'' % args[0], args[0])
 
105
    except getopt.GetoptError, reason:
 
106
        sys.stderr.write("%(program_name)s: %(reason)s\n"
 
107
                         "Try '%(program_name)s --help' for more information.\n"
 
108
                          % { 'program_name': cfg.program_name,
 
109
                              'reason': reason, })
 
110
        sys.exit(1)
 
111
 
 
112
def create_socket():
 
113
    """Returns a socket ready to answer requests from the client."""
 
114
    import socket
 
115
    import fcntl
 
116
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
 
117
    # remove existing named socket
 
118
    try:
 
119
        os.unlink(config.NSLCD_SOCKET)
 
120
    except OSError:
 
121
        pass # ignore any problems
 
122
    # bind to named socket
 
123
    sock.bind((config.NSLCD_SOCKET))
 
124
    # close the file descriptor on exit
 
125
    fcntl.fcntl(sock, fcntl.F_SETFD, fcntl.FD_CLOEXEC)
 
126
    # set permissions of socket so anybody can do requests
 
127
    os.chmod(config.NSLCD_SOCKET, 0666)
 
128
    # start listening for connections
 
129
    sock.listen(socket.SOMAXCONN)
 
130
    return sock
 
131
 
 
132
def log_newsession():
 
133
    pass
 
134
    # FIXME: implement
 
135
 
 
136
def getpeercred(fd):
 
137
    return (None, None, None)
 
138
    # FIXME: implement and return uid, gid, pid
 
139
 
 
140
handlers = {}
 
141
handlers.update(common.get_handlers('alias'))
 
142
handlers.update(common.get_handlers('ether'))
 
143
handlers.update(common.get_handlers('group'))
 
144
handlers.update(common.get_handlers('host'))
 
145
handlers.update(common.get_handlers('netgroup'))
 
146
handlers.update(common.get_handlers('network'))
 
147
handlers.update(common.get_handlers('pam'))
 
148
handlers.update(common.get_handlers('passwd'))
 
149
handlers.update(common.get_handlers('protocol'))
 
150
handlers.update(common.get_handlers('rpc'))
 
151
handlers.update(common.get_handlers('service'))
 
152
handlers.update(common.get_handlers('shadow'))
 
153
 
 
154
def acceptconnection(session):
 
155
    # accept a new connection
 
156
    conn, addr = nslcd_serversocket.accept()
 
157
    # See: http://docs.python.org/library/socket.html#socket.socket.settimeout
 
158
    fp = None
 
159
    try:
 
160
        # probably use finally
 
161
        # indicate new connection to logging module (genrates unique id)
 
162
        log_newsession()
 
163
        # log connection
 
164
        try:
 
165
            uid, gid, pid = getpeercred(conn)
 
166
            logging.debug('connection from pid=%r uid=%r gid=%r', pid, uid, gid)
 
167
        except:
 
168
            raise # FIXME: handle exception gracefully
 
169
        # create a stream object
 
170
        fp = TIOStream(conn)
 
171
        # read request
 
172
        version = fp.read_int32()
 
173
        if version != constants.NSLCD_VERSION:
 
174
            logging.debug('wrong nslcd version id (%r)', version)
 
175
            return
 
176
        action = fp.read_int32()
 
177
        try:
 
178
            handler = handlers[action]
 
179
        except KeyError:
 
180
            logging.warn('invalid action id: %r', action)
 
181
            return
 
182
        handler(fp, session, uid)()
 
183
    finally:
 
184
        if fp:
 
185
            fp.close()
 
186
 
 
187
def disable_nss_ldap():
 
188
    """Disable the nss_ldap module to avoid lookup loops."""
 
189
    import ctypes
 
190
    lib = ctypes.CDLL(config.NSS_LDAP_SONAME)
 
191
    ctypes.c_int.in_dll(lib, '_nss_ldap_enablelookups').value = 0
 
192
 
 
193
def worker():
 
194
    # create a new LDAP session
 
195
    #session = myldap_create_session()
 
196
    session = ldap.initialize(cfg.ldap_uri)
 
197
    # start waiting for incoming connections
 
198
    while True:
 
199
        # wait for a new connection
 
200
        acceptconnection(session)
 
201
        # FIXME: handle exceptions
 
202
 
 
203
if __name__ == '__main__':
 
204
    # parse options
 
205
    parse_cmdline()
 
206
    # clean the environment
 
207
    os.environ.clear()
 
208
    os.putenv('HOME', '/')
 
209
    os.putenv('TMPDIR', '/tmp')
 
210
    os.putenv('LDAPNOINIT', '1')
 
211
    # disable ldap lookups of host names to avoid lookup loop
 
212
    disable_nss_ldap()
 
213
    # set log level
 
214
    if cfg.debug:
 
215
        logging.getLogger().setLevel(logging.DEBUG)
 
216
    # FIXME: implement
 
217
    #if myldap_set_debuglevel(cfg.debug) != LDAP_SUCCESS:
 
218
    #    sys.exit(1)
 
219
    # read configuration file
 
220
    cfg.read(config.NSLCD_CONF_PATH)
 
221
    # set a default umask for the pidfile and socket
 
222
    os.umask(0022)
 
223
    # see if someone already locked the pidfile
 
224
    pidfile = mypidfile.MyPIDLockFile(config.NSLCD_PIDFILE)
 
225
    # see if --check option was given
 
226
    if cfg.check:
 
227
        if pidfile.is_locked():
 
228
            logging.debug('pidfile (%s) is locked', config.NSLCD_PIDFILE)
 
229
            sys.exit(0)
 
230
        else:
 
231
            logging.debug('pidfile (%s) is not locked', config.NSLCD_PIDFILE)
 
232
            sys.exit(1)
 
233
    # normal check for pidfile locked
 
234
    if pidfile.is_locked():
 
235
        logging.error('daemon may already be active, cannot acquire lock (%s)', config.NSLCD_PIDFILE)
 
236
        sys.exit(1)
 
237
    # daemonize
 
238
    if cfg.debug:
 
239
        daemon = pidfile
 
240
    else:
 
241
        daemon = daemon.DaemonContext(
 
242
                      pidfile=pidfile,
 
243
                      signal_map={
 
244
                          signal.SIGTERM: 'terminate',
 
245
                          signal.SIGINT:  'terminate',
 
246
                          signal.SIGPIPE: None,
 
247
                      })
 
248
    # start daemon
 
249
    with daemon:
 
250
        # start normal logging
 
251
        if not cfg.debug:
 
252
            log_startlogging()
 
253
        logging.info('version %s starting', config.VERSION)
 
254
        # create socket
 
255
        nslcd_serversocket = create_socket()
 
256
        # drop all supplemental groups
 
257
        try:
 
258
            os.setgroups(())
 
259
        except OSError, e:
 
260
            logging.warn('cannot setgroups(()) (ignored): %s', e)
 
261
        # change to nslcd gid
 
262
        if cfg.gid is not None:
 
263
            import grp
 
264
            os.setgid(grp.getgrnam(cfg.gid).gr_gid)
 
265
        # change to nslcd uid
 
266
        if cfg.uid is not None:
 
267
            import pwd
 
268
            u = pwd.getpwnam(cfg.uid)
 
269
            os.setuid(u.pw_uid)
 
270
            os.environ['HOME'] = u.pw_dir
 
271
        logging.info('accepting connections')
 
272
        # start worker threads
 
273
        threads = []
 
274
        for i in range(cfg.threads):
 
275
            thread = threading.Thread(target=worker, name='thread%d' % i)
 
276
            thread.setDaemon(True)
 
277
            thread.start()
 
278
            logging.debug('started thread %s', thread.getName())
 
279
            threads.append(thread)
 
280
        # wait for all threads to die
 
281
        for thread in threads:
 
282
            thread.join(10000)