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