1
2# usermod.py - functions for modifying user information
3#
4# Copyright (C) 2013-2019 Arthur de Jong
5#
6# This library is free software; you can redistribute it and/or
7# modify it under the terms of the GNU Lesser General Public
8# License as published by the Free Software Foundation; either
9# version 2.1 of the License, or (at your option) any later version.
10#
11# This library is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14# Lesser General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public
17# License along with this library; if not, write to the Free Software
18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19# 02110-1301 USA
20
21import ctypes
22import ctypes.util
23import logging
24import os
25import os.path
26
27import ldap
28
29import cfg
30import constants
31import pam
32import passwd
33
34
35def list_shells():
36    """List the shells from /etc/shells."""
37    libc = ctypes.CDLL(ctypes.util.find_library("c"))
38    libc.setusershell()
39    while True:
40        shell = ctypes.c_char_p(libc.getusershell()).value
41        if not shell:
42            break
43        yield shell
44    libc.endusershell()
45
46
47class UserModRequest(pam.PAMRequest):
48
49    action = constants.NSLCD_ACTION_USERMOD
50
51    def read_parameters(self, fp):
52        username = fp.read_string()
53        asroot = fp.read_int32()
54        password = fp.read_string()
55        mods = {}
56        while True:
57            key = fp.read_int32()
58            if key == constants.NSLCD_USERMOD_END:
59                break
60            mods[key] = fp.read_string()
61        return dict(username=username,
62                    asroot=asroot,
63                    password=password,
64                    mods=mods)
65
66    def write_result(self, mod, message):
67        self.fp.write_int32(mod)
68        self.fp.write_string(message)
69
70    def handle_request(self, parameters):
71        # fill in any missing userdn, etc.
72        self.validate(parameters)
73        is_root = (self.calleruid == 0) and parameters['asroot']
74        mods = []
75        # check if the the user passed the rootpwmoddn
76        if parameters['asroot']:
77            binddn = cfg.rootpwmoddn
78            # check if rootpwmodpw should be used
79            if not parameters['password'] and is_root and cfg.rootpwmodpw:
80                password = cfg.rootpwmodpw
81            else:
82                password = parameters['password']
83        else:
84            binddn = parameters['userdn']
85            password = parameters['password']
86        # write response header
87        self.fp.write_int32(constants.NSLCD_RESULT_BEGIN)
88        # check home directory modification
89        homedir = parameters['mods'].get(constants.NSLCD_USERMOD_HOMEDIR)
90        if homedir:
91            if is_root:
92                mods.append((ldap.MOD_REPLACE, passwd.attmap['homeDirectory'], [homedir]))
93            elif not os.path.isabs(homedir):
94                self.write_result(constants.NSLCD_USERMOD_HOMEDIR,
95                                  'should be an absolute path')
96            elif not os.path.isdir(homedir):
97                self.write_result(constants.NSLCD_USERMOD_HOMEDIR,
98                                  'not a directory')
99            else:
100                mods.append((ldap.MOD_REPLACE, passwd.attmap['homeDirectory'], [homedir]))
101        # check login shell modification
102        shell = parameters['mods'].get(constants.NSLCD_USERMOD_SHELL)
103        if shell:
104            if is_root:
105                mods.append((ldap.MOD_REPLACE, passwd.attmap['loginShell'], [shell]))
106            elif shell not in list_shells():
107                self.write_result(constants.NSLCD_USERMOD_SHELL,
108                                  'unlisted shell')
109            elif not os.path.isfile(shell) or not os.access(shell, os.X_OK):
110                self.write_result(constants.NSLCD_USERMOD_SHELL,
111                                  'not an executable')
112            else:
113                mods.append((ldap.MOD_REPLACE, passwd.attmap['loginShell'], [shell]))
114        # get a connection and perform the modification
115        if mods:
116            try:
117                conn, authz, msg = pam.authenticate(binddn, password)
118                conn.modify_s(parameters['userdn'], mods)
119                logging.info('changed information for %s', parameters['userdn'])
120            except (ldap.INVALID_CREDENTIALS, ldap.INSUFFICIENT_ACCESS) as e:
121                try:
122                    msg = e[0]['desc']
123                except Exception:
124                    msg = str(e)
125                logging.debug('modification failed: %s', msg)
126                self.write_result(constants.NSLCD_USERMOD_RESULT, msg)
127        # write closing statement
128        self.fp.write_int32(constants.NSLCD_USERMOD_END)
129        self.fp.write_int32(constants.NSLCD_RESULT_END)
130