3
# pynslcd.py - main daemon module
5
# Copyright (C) 2010, 2011 Arthur de Jong
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.
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.
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
28
import logging.handlers
32
import constants # from nslcd.h
33
import config # from configure
34
import cfg # from nslcd.conf
37
from tio import TIOStream
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
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)
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)
64
def display_version(fp):
65
fp.write('%(PACKAGE_STRING)s\n'
66
'Written by Arthur de Jong.\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, } )
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"
81
"Report bugs to <%(PACKAGE_BUGREPORT)s>.\n"
82
% { 'program_name': cfg.program_name,
83
'PACKAGE_BUGREPORT': config.PACKAGE_BUGREPORT, } )
86
"""Parse command-line arguments."""
88
cfg.program_name = sys.argv[0] or 'pynslcd'
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'):
95
elif flag in ('-d', '--debug'):
97
elif flag in ('-h', '--help'):
98
display_usage(sys.stdout)
100
elif flag in ('-V', '--version'):
101
display_version(sys.stdout)
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,
113
"""Returns a socket ready to answer requests from the client."""
116
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
117
# remove existing named socket
119
os.unlink(config.NSLCD_SOCKET)
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)
132
def log_newsession():
137
return (None, None, None)
138
# FIXME: implement and return uid, gid, pid
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'))
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
160
# probably use finally
161
# indicate new connection to logging module (genrates unique id)
165
uid, gid, pid = getpeercred(conn)
166
logging.debug('connection from pid=%r uid=%r gid=%r', pid, uid, gid)
168
raise # FIXME: handle exception gracefully
169
# create a stream object
172
version = fp.read_int32()
173
if version != constants.NSLCD_VERSION:
174
logging.debug('wrong nslcd version id (%r)', version)
176
action = fp.read_int32()
178
handler = handlers[action]
180
logging.warn('invalid action id: %r', action)
182
handler(fp, session, uid)()
187
def disable_nss_ldap():
188
"""Disable the nss_ldap module to avoid lookup loops."""
190
lib = ctypes.CDLL(config.NSS_LDAP_SONAME)
191
ctypes.c_int.in_dll(lib, '_nss_ldap_enablelookups').value = 0
194
# create a new LDAP session
195
#session = myldap_create_session()
196
session = ldap.initialize(cfg.ldap_uri)
197
# start waiting for incoming connections
199
# wait for a new connection
200
acceptconnection(session)
201
# FIXME: handle exceptions
203
if __name__ == '__main__':
206
# clean the environment
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
215
logging.getLogger().setLevel(logging.DEBUG)
217
#if myldap_set_debuglevel(cfg.debug) != LDAP_SUCCESS:
219
# read configuration file
220
cfg.read(config.NSLCD_CONF_PATH)
221
# set a default umask for the pidfile and socket
223
# see if someone already locked the pidfile
224
pidfile = mypidfile.MyPIDLockFile(config.NSLCD_PIDFILE)
225
# see if --check option was given
227
if pidfile.is_locked():
228
logging.debug('pidfile (%s) is locked', config.NSLCD_PIDFILE)
231
logging.debug('pidfile (%s) is not locked', config.NSLCD_PIDFILE)
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)
241
daemon = daemon.DaemonContext(
244
signal.SIGTERM: 'terminate',
245
signal.SIGINT: 'terminate',
246
signal.SIGPIPE: None,
250
# start normal logging
253
logging.info('version %s starting', config.VERSION)
255
nslcd_serversocket = create_socket()
256
# drop all supplemental groups
260
logging.warn('cannot setgroups(()) (ignored): %s', e)
261
# change to nslcd gid
262
if cfg.gid is not None:
264
os.setgid(grp.getgrnam(cfg.gid).gr_gid)
265
# change to nslcd uid
266
if cfg.uid is not None:
268
u = pwd.getpwnam(cfg.uid)
270
os.environ['HOME'] = u.pw_dir
271
logging.info('accepting connections')
272
# start worker threads
274
for i in range(cfg.threads):
275
thread = threading.Thread(target=worker, name='thread%d' % i)
276
thread.setDaemon(True)
278
logging.debug('started thread %s', thread.getName())
279
threads.append(thread)
280
# wait for all threads to die
281
for thread in threads: