1 /*
2 usermod.c - routines for changing user information such as full name,
3 login shell, etc
4
5 Copyright (C) 2013-2017 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
23 #include "config.h"
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #ifdef HAVE_STDINT_H
29 #include <stdint.h>
30 #endif /* HAVE_STDINT_H */
31 #include <unistd.h>
32 #include <sys/stat.h>
33
34 #include "common.h"
35 #include "log.h"
36 #include "myldap.h"
37 #include "cfg.h"
38 #include "attmap.h"
39 #include "compat/shell.h"
40
41 /* ensure that both userdn and username are filled in from the entry,
42 returns an LDAP result code */
validate_user(MYLDAP_SESSION * session,char * username,int * rcp)43 static MYLDAP_ENTRY *validate_user(MYLDAP_SESSION *session,
44 char *username, int *rcp)
45 {
46 int rc;
47 MYLDAP_ENTRY *entry = NULL;
48 /* check username for validity */
49 if (!isvalidname(username))
50 {
51 log_log(LOG_WARNING, "request denied by validnames option");
52 *rcp = LDAP_NO_SUCH_OBJECT;
53 return NULL;
54 }
55 /* get the user entry based on the username */
56 entry = uid2entry(session, username, &rc);
57 if (entry == NULL)
58 {
59 if (rc == LDAP_SUCCESS)
60 rc = LDAP_NO_SUCH_OBJECT;
61 log_log(LOG_DEBUG, "\"%s\": user not found: %s", username, ldap_err2string(rc));
62 *rcp = rc;
63 return NULL;
64 }
65 return entry;
66 }
67
is_valid_homedir(const char * homedir)68 static int is_valid_homedir(const char *homedir)
69 {
70 struct stat sb;
71 /* should be absolute path */
72 if (homedir[0] != '/')
73 return 0;
74 /* get directory status */
75 if (stat(homedir, &sb))
76 {
77 log_log(LOG_DEBUG, "cannot stat() %s: %s", homedir, strerror(errno));
78 return 0;
79 }
80 /* check if a directory */
81 if (!S_ISDIR(sb.st_mode))
82 {
83 log_log(LOG_DEBUG, "%s: not a directory", homedir);
84 return 0;
85 }
86 /* FIXME: check ownership */
87 return 1;
88 }
89
is_valid_shell(const char * shell)90 static int is_valid_shell(const char *shell)
91 {
92 int valid = 0;
93 char *l;
94 setusershell();
95 while ((l = getusershell()) != NULL)
96 {
97 if (strcmp(l, shell) == 0)
98 {
99 valid = 1;
100 break;
101 }
102 }
103 endusershell();
104 return valid;
105 }
106
get_session(const char * binddn,const char * password,int * rcp)107 static MYLDAP_SESSION *get_session(const char *binddn, const char *password,
108 int *rcp)
109 {
110 MYLDAP_SESSION *session;
111 /* set up a new connection */
112 session = myldap_create_session();
113 if (session == NULL)
114 {
115 *rcp = LDAP_UNAVAILABLE;
116 return NULL;
117 }
118 /* check that we can bind */
119 *rcp = myldap_bind(session, binddn, password, NULL, NULL);
120 if (*rcp != LDAP_SUCCESS)
121 {
122 myldap_session_close(session);
123 return NULL;
124 }
125 return session;
126 }
127
128 #define ADD_MOD(attribute, value) \
129 if ((value != NULL) && (attribute[0] != '"')) \
130 { \
131 strvals[i * 2] = (char *)value; \
132 strvals[i * 2 + 1] = NULL; \
133 mods[i].mod_op = LDAP_MOD_REPLACE; \
134 mods[i].mod_type = (char *)attribute; \
135 mods[i].mod_values = strvals + (i * 2); \
136 modsp[i] = mods + i; \
137 i++; \
138 }
139
change(MYLDAP_SESSION * session,const char * userdn,const char * homedir,const char * shell)140 static int change(MYLDAP_SESSION *session, const char *userdn,
141 const char *homedir, const char *shell)
142 {
143 #define NUMARGS 2
144 char *strvals[(NUMARGS + 1) * 2];
145 LDAPMod mods[(NUMARGS + 1)], *modsp[(NUMARGS + 1)];
146 int i = 0;
147 /* build the list of modifications */
148 ADD_MOD(attmap_passwd_homeDirectory, homedir);
149 ADD_MOD(attmap_passwd_loginShell, shell);
150 /* terminate the list of modifications */
151 modsp[i] = NULL;
152 /* execute the update */
153 return myldap_modify(session, userdn, modsp);
154 }
155
nslcd_usermod(TFILE * fp,MYLDAP_SESSION * session,uid_t calleruid)156 int nslcd_usermod(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid)
157 {
158 int32_t tmpint32;
159 int rc = LDAP_SUCCESS;
160 char username[BUFLEN_NAME];
161 int asroot, isroot;
162 char password[BUFLEN_PASSWORD];
163 int32_t param;
164 char buffer[4096];
165 size_t buflen = sizeof(buffer);
166 size_t bufptr = 0;
167 const char *value = NULL;
168 const char *fullname = NULL, *roomnumber = NULL, *workphone = NULL;
169 const char *homephone = NULL, *other = NULL, *homedir = NULL;
170 const char *shell = NULL;
171 const char *binddn = NULL; /* the user performing the modification */
172 MYLDAP_ENTRY *entry;
173 MYLDAP_SESSION *newsession;
174 char errmsg[BUFLEN_MESSAGE];
175 /* read request parameters */
176 READ_STRING(fp, username);
177 READ_INT32(fp, asroot);
178 READ_STRING(fp, password);
179 /* read the usermod parameters */
180 while (1)
181 {
182 READ_INT32(fp, param);
183 if (param == NSLCD_USERMOD_END)
184 break;
185 READ_BUF_STRING(fp, value);
186 switch (param)
187 {
188 case NSLCD_USERMOD_FULLNAME: fullname = value; break;
189 case NSLCD_USERMOD_ROOMNUMBER: roomnumber = value; break;
190 case NSLCD_USERMOD_WORKPHONE: workphone = value; break;
191 case NSLCD_USERMOD_HOMEPHONE: homephone = value; break;
192 case NSLCD_USERMOD_OTHER: other = value; break;
193 case NSLCD_USERMOD_HOMEDIR: homedir = value; break;
194 case NSLCD_USERMOD_SHELL: shell = value; break;
195 default: /* other parameters are silently ignored */ break;
196 }
197 }
198 /* log call */
199 log_setrequest("usermod=\"%s\"", username);
200 log_log(LOG_DEBUG, "nslcd_usermod(\"%s\",%s,\"%s\")",
201 username, asroot ? "asroot" : "asuser", *password ? "***" : "");
202 if (fullname != NULL)
203 log_log(LOG_DEBUG, "nslcd_usermod(fullname=\"%s\")", fullname);
204 if (roomnumber != NULL)
205 log_log(LOG_DEBUG, "nslcd_usermod(roomnumber=\"%s\")", roomnumber);
206 if (workphone != NULL)
207 log_log(LOG_DEBUG, "nslcd_usermod(workphone=\"%s\")", workphone);
208 if (homephone != NULL)
209 log_log(LOG_DEBUG, "nslcd_usermod(homephone=\"%s\")", homephone);
210 if (other != NULL)
211 log_log(LOG_DEBUG, "nslcd_usermod(other=\"%s\")", other);
212 if (homedir != NULL)
213 log_log(LOG_DEBUG, "nslcd_usermod(homedir=\"%s\")", homedir);
214 if (shell != NULL)
215 log_log(LOG_DEBUG, "nslcd_usermod(shell=\"%s\")", shell);
216 /* write the response header */
217 WRITE_INT32(fp, NSLCD_VERSION);
218 WRITE_INT32(fp, NSLCD_ACTION_USERMOD);
219 /* validate request */
220 entry = validate_user(session, username, &rc);
221 if (entry == NULL)
222 {
223 /* for user not found we just say no result, otherwise break the protocol */
224 if (rc == LDAP_NO_SUCH_OBJECT)
225 {
226 WRITE_INT32(fp, NSLCD_RESULT_END);
227 }
228 return -1;
229 }
230 /* check if it is a modification as root */
231 isroot = (calleruid == 0) && asroot;
232 if (asroot)
233 {
234 if (nslcd_cfg->rootpwmoddn == NULL)
235 {
236 log_log(LOG_NOTICE, "rootpwmoddn not configured");
237 /* we break the protocol */
238 return -1;
239 }
240 binddn = nslcd_cfg->rootpwmoddn;
241 /* check if rootpwmodpw should be used */
242 if ((*password == '\0') && isroot && (nslcd_cfg->rootpwmodpw != NULL))
243 {
244 if (strlen(nslcd_cfg->rootpwmodpw) >= sizeof(password))
245 {
246 log_log(LOG_ERR, "nslcd_pam_pwmod(): rootpwmodpw will not fit in password");
247 return -1;
248 }
249 strcpy(password, nslcd_cfg->rootpwmodpw);
250 }
251 }
252 else
253 binddn = myldap_get_dn(entry);
254 WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
255 /* home directory change requires either root or valid directory */
256 if ((homedir != NULL) && (!isroot) && !is_valid_homedir(homedir))
257 {
258 log_log(LOG_NOTICE, "invalid directory: %s", homedir);
259 WRITE_INT32(fp, NSLCD_USERMOD_HOMEDIR);
260 WRITE_STRING(fp, "invalid directory");
261 homedir = NULL;
262 }
263 /* shell change requires either root or a valid shell */
264 if ((shell != NULL) && (!isroot) && !is_valid_shell(shell))
265 {
266 log_log(LOG_NOTICE, "invalid shell: %s", shell);
267 WRITE_INT32(fp, NSLCD_USERMOD_SHELL);
268 WRITE_STRING(fp, "invalid shell");
269 shell = NULL;
270 }
271 /* perform requested changes */
272 newsession = get_session(binddn, password, &rc);
273 if (newsession != NULL)
274 {
275 rc = change(newsession, myldap_get_dn(entry), homedir, shell);
276 myldap_session_close(newsession);
277 }
278 /* return response to caller */
279 if (rc != LDAP_SUCCESS)
280 {
281 log_log(LOG_WARNING, "%s: modification failed: %s",
282 myldap_get_dn(entry), ldap_err2string(rc));
283 mysnprintf(errmsg, sizeof(errmsg) - 1, "change failed: %s", ldap_err2string(rc));
284 WRITE_INT32(fp, NSLCD_USERMOD_RESULT);
285 WRITE_STRING(fp, errmsg);
286 WRITE_INT32(fp, NSLCD_USERMOD_END);
287 WRITE_INT32(fp, NSLCD_RESULT_END);
288 return 0;
289 }
290 log_log(LOG_NOTICE, "changed information for %s", myldap_get_dn(entry));
291 WRITE_INT32(fp, NSLCD_USERMOD_END);
292 WRITE_INT32(fp, NSLCD_RESULT_END);
293 return 0;
294 }
295