1 /*
2 * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2012 by Martin C. Shepherd.
3 *
4 * All rights reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, and/or sell copies of the Software, and to permit persons
11 * to whom the Software is furnished to do so, provided that the above
12 * copyright notice(s) and this permission notice appear in all copies of
13 * the Software and that both the above copyright notice(s) and this
14 * permission notice appear in supporting documentation.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
19 * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
20 * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
21 * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
22 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
23 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
24 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25 *
26 * Except as contained in this notice, the name of a copyright holder
27 * shall not be used in advertising or otherwise to promote the sale, use
28 * or other dealings in this Software without prior written authorization
29 * of the copyright holder.
30 */
31
32 /*
33 * If file-system access is to be excluded, this module has no function,
34 * so all of its code should be excluded.
35 */
36 #ifndef WITHOUT_FILE_SYSTEM
37
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <errno.h>
42
43 #include <unistd.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46 #include <pwd.h>
47
48 #include "pathutil.h"
49 #include "homedir.h"
50 #include "errmsg.h"
51
52 /*
53 * Use the reentrant POSIX threads versions of the password lookup functions?
54 */
55 #if defined(PREFER_REENTRANT) && defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199506L
56 #define THREAD_COMPATIBLE 1
57 /*
58 * Under Solaris we can use thr_main() to determine whether
59 * threads are actually running, and thus when it is necessary
60 * to avoid non-reentrant features.
61 */
62 #if defined __sun && defined __SVR4
63 #include <thread.h> /* Solaris thr_main() */
64 #endif
65 #endif
66
67 /*
68 * Provide a password buffer size fallback in case the max size reported
69 * by sysconf() is said to be indeterminate.
70 */
71 #define DEF_GETPW_R_SIZE_MAX 1024
72
73 /*
74 * The resources needed to lookup and record a home directory are
75 * maintained in objects of the following type.
76 */
77 struct HomeDir {
78 ErrMsg *err; /* The error message report buffer */
79 char *buffer; /* A buffer for reading password entries and */
80 /* directory paths. */
81 int buflen; /* The allocated size of buffer[] */
82 #ifdef THREAD_COMPATIBLE
83 struct passwd pwd; /* The password entry of a user */
84 #endif
85 };
86
87 static const char *hd_getpwd(HomeDir *home);
88
89 /*.......................................................................
90 * Create a new HomeDir object.
91 *
92 * Output:
93 * return HomeDir * The new object, or NULL on error.
94 */
_new_HomeDir(void)95 HomeDir *_new_HomeDir(void)
96 {
97 HomeDir *home; /* The object to be returned */
98 size_t pathlen; /* The estimated maximum size of a pathname */
99 /*
100 * Allocate the container.
101 */
102 home = (HomeDir *) malloc(sizeof(HomeDir));
103 if(!home) {
104 errno = ENOMEM;
105 return NULL;
106 };
107 /*
108 * Before attempting any operation that might fail, initialize the
109 * container at least up to the point at which it can safely be passed
110 * to _del_HomeDir().
111 */
112 home->err = NULL;
113 home->buffer = NULL;
114 home->buflen = 0;
115 /*
116 * Allocate a place to record error messages.
117 */
118 home->err = _new_ErrMsg();
119 if(!home->err)
120 return _del_HomeDir(home);
121 /*
122 * Allocate the buffer that is used by the reentrant POSIX password-entry
123 * lookup functions.
124 */
125 #ifdef THREAD_COMPATIBLE
126 /*
127 * Get the length of the buffer needed by the reentrant version
128 * of getpwnam().
129 */
130 #ifndef _SC_GETPW_R_SIZE_MAX
131 home->buflen = DEF_GETPW_R_SIZE_MAX;
132 #else
133 errno = 0;
134 home->buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
135 /*
136 * If the limit isn't available, substitute a suitably large fallback value.
137 */
138 if(home->buflen < 0 || errno)
139 home->buflen = DEF_GETPW_R_SIZE_MAX;
140 #endif
141 #endif
142 /*
143 * If the existing buffer length requirement is too restrictive to record
144 * a pathname, increase its length.
145 */
146 pathlen = _pu_pathname_dim();
147 if(pathlen > home->buflen)
148 home->buflen = pathlen;
149 /*
150 * Allocate a work buffer.
151 */
152 home->buffer = (char *) malloc(home->buflen);
153 if(!home->buffer) {
154 errno = ENOMEM;
155 return _del_HomeDir(home);
156 };
157 return home;
158 }
159
160 /*.......................................................................
161 * Delete a HomeDir object.
162 *
163 * Input:
164 * home HomeDir * The object to be deleted.
165 * Output:
166 * return HomeDir * The deleted object (always NULL).
167 */
_del_HomeDir(HomeDir * home)168 HomeDir *_del_HomeDir(HomeDir *home)
169 {
170 if(home) {
171 home->err = _del_ErrMsg(home->err);
172 if(home->buffer)
173 free(home->buffer);
174 free(home);
175 };
176 return NULL;
177 }
178
179 /*.......................................................................
180 * Lookup the home directory of a given user in the password file.
181 *
182 * Input:
183 * home HomeDir * The resources needed to lookup the home directory.
184 * user const char * The name of the user to lookup, or "" to lookup
185 * the home directory of the person running the
186 * program.
187 * Output:
188 * return const char * The home directory. If the library was compiled
189 * with threads, this string is part of the HomeDir
190 * object and will change on subsequent calls. If
191 * the library wasn't compiled to be reentrant,
192 * then the string is a pointer into a static string
193 * in the C library and will change not only on
194 * subsequent calls to this function, but also if
195 * any calls are made to the C library password
196 * file lookup functions. Thus to be safe, you should
197 * make a copy of this string before calling any
198 * other function that might do a password file
199 * lookup.
200 *
201 * On error, NULL is returned and a description
202 * of the error can be acquired by calling
203 * _hd_last_home_dir_error().
204 */
_hd_lookup_home_dir(HomeDir * home,const char * user)205 const char *_hd_lookup_home_dir(HomeDir *home, const char *user)
206 {
207 const char *home_dir; /* A pointer to the home directory of the user */
208 /*
209 * If no username has been specified, arrange to lookup the current
210 * user.
211 */
212 int login_user = !user || *user=='\0';
213 /*
214 * Check the arguments.
215 */
216 if(!home) {
217 errno = EINVAL;
218 return NULL;
219 };
220 /*
221 * Handle the ksh "~+". This expands to the absolute path of the
222 * current working directory.
223 */
224 if(!login_user && strcmp(user, "+") == 0) {
225 home_dir = hd_getpwd(home);
226 if(!home_dir) {
227 _err_record_msg(home->err, "Can't determine current directory",
228 END_ERR_MSG);
229 return NULL;
230 }
231 return home_dir;
232 };
233 /*
234 * When looking up the home directory of the current user, see if the
235 * HOME environment variable is set, and if so, return its value.
236 */
237 if(login_user) {
238 home_dir = getenv("HOME");
239 if(home_dir)
240 return home_dir;
241 };
242 /*
243 * Look up the password entry of the user.
244 * First the POSIX threads version - this is painful!
245 */
246 #ifdef THREAD_COMPATIBLE
247 {
248 struct passwd *ret; /* The returned pointer to pwd */
249 int status; /* The return value of getpwnam_r() */
250 /*
251 * Look up the password entry of the specified user.
252 */
253 if(login_user)
254 status = getpwuid_r(geteuid(), &home->pwd, home->buffer, home->buflen,
255 &ret);
256 else
257 status = getpwnam_r(user, &home->pwd, home->buffer, home->buflen, &ret);
258 if(status || !ret) {
259 _err_record_msg(home->err, "User '", user, "' doesn't exist.",
260 END_ERR_MSG);
261 return NULL;
262 };
263 /*
264 * Get a pointer to the string that holds the home directory.
265 */
266 home_dir = home->pwd.pw_dir;
267 };
268 /*
269 * Now the classic unix version.
270 */
271 #else
272 {
273 struct passwd *pwd = login_user ? getpwuid(geteuid()) : getpwnam(user);
274 if(!pwd) {
275 _err_record_msg(home->err, "User '", user, "' doesn't exist.",
276 END_ERR_MSG);
277 return NULL;
278 };
279 /*
280 * Get a pointer to the home directory.
281 */
282 home_dir = pwd->pw_dir;
283 };
284 #endif
285 return home_dir;
286 }
287
288 /*.......................................................................
289 * Return a description of the last error that caused _hd_lookup_home_dir()
290 * to return NULL.
291 *
292 * Input:
293 * home HomeDir * The resources needed to record the home directory.
294 * Output:
295 * return char * The description of the last error.
296 */
_hd_last_home_dir_error(HomeDir * home)297 const char *_hd_last_home_dir_error(HomeDir *home)
298 {
299 return home ? _err_get_msg(home->err) : "NULL HomeDir argument";
300 }
301
302 /*.......................................................................
303 * The _hd_scan_user_home_dirs() function calls a user-provided function
304 * for each username known by the system, passing the function both
305 * the name and the home directory of the user.
306 *
307 * Input:
308 * home HomeDir * The resource object for reading home
309 * directories.
310 * prefix const char * Only information for usernames that
311 * start with this prefix will be
312 * returned. Note that the empty
313 & string "", matches all usernames.
314 * data void * Anonymous data to be passed to the
315 * callback function.
316 * callback_fn HOME_DIR_FN(*) The function to call for each user.
317 * Output:
318 * return int 0 - Successful completion.
319 * 1 - An error occurred. A description
320 * of the error can be obtained by
321 * calling _hd_last_home_dir_error().
322 */
_hd_scan_user_home_dirs(HomeDir * home,const char * prefix,void * data,HOME_DIR_FN (* callback_fn))323 int _hd_scan_user_home_dirs(HomeDir *home, const char *prefix,
324 void *data, HOME_DIR_FN(*callback_fn))
325 {
326 int waserr = 0; /* True after errors */
327 int prefix_len; /* The length of prefix[] */
328 /*
329 * Check the arguments.
330 */
331 if(!home || !prefix || !callback_fn) {
332 if(home) {
333 _err_record_msg(home->err,
334 "_hd_scan_user_home_dirs: Missing callback function",
335 END_ERR_MSG);
336 };
337 return 1;
338 };
339 /*
340 * Get the length of the username prefix.
341 */
342 prefix_len = strlen(prefix);
343 /*
344 * There are no reentrant versions of getpwent() etc for scanning
345 * the password file, so disable username completion when the
346 * library is compiled to be reentrant.
347 */
348 #if defined(PREFER_REENTRANT) && defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199506L
349 #if defined __sun && defined __SVR4
350 if(thr_main() >= 0) /* thread library is linked in */
351 #else
352 if(1)
353 #endif
354 {
355 struct passwd pwd_buffer; /* A returned password entry */
356 struct passwd *pwd; /* A pointer to pwd_buffer */
357 char buffer[512]; /* The buffer in which the string members of */
358 /* pwd_buffer are stored. */
359 /*
360 * See if the prefix that is being completed is a complete username.
361 */
362 if(!waserr && getpwnam_r(prefix, &pwd_buffer, buffer, sizeof(buffer),
363 &pwd) == 0 && pwd != NULL) {
364 waserr = callback_fn(data, pwd->pw_name, pwd->pw_dir,
365 _err_get_msg(home->err), ERR_MSG_LEN);
366 };
367 /*
368 * See if the username of the current user minimally matches the prefix.
369 */
370 if(!waserr && getpwuid_r(getuid(), &pwd_buffer, buffer, sizeof(buffer),
371 &pwd) == 0 && pwd != NULL &&
372 strncmp(prefix, pwd->pw_name, prefix_len)==0) {
373 waserr = callback_fn(data, pwd->pw_name, pwd->pw_dir,
374 _err_get_msg(home->err), ERR_MSG_LEN);
375 };
376 /*
377 * Reentrancy not required?
378 */
379 } else
380 #endif
381 {
382 struct passwd *pwd; /* The pointer to the latest password entry */
383 /*
384 * Open the password file.
385 */
386 setpwent();
387 /*
388 * Read the contents of the password file, looking for usernames
389 * that start with the specified prefix, and adding them to the
390 * list of matches.
391 */
392 while((pwd = getpwent()) != NULL && !waserr) {
393 if(strncmp(prefix, pwd->pw_name, prefix_len) == 0) {
394 waserr = callback_fn(data, pwd->pw_name, pwd->pw_dir,
395 _err_get_msg(home->err), ERR_MSG_LEN);
396 };
397 };
398 /*
399 * Close the password file.
400 */
401 endpwent();
402 };
403 /*
404 * Under ksh ~+ stands for the absolute pathname of the current working
405 * directory.
406 */
407 if(!waserr && strncmp(prefix, "+", prefix_len) == 0) {
408 const char *pwd = hd_getpwd(home);
409 if(pwd) {
410 waserr = callback_fn(data, "+", pwd, _err_get_msg(home->err),ERR_MSG_LEN);
411 } else {
412 waserr = 1;
413 _err_record_msg(home->err, "Can't determine current directory.",
414 END_ERR_MSG);
415 };
416 };
417 return waserr;
418 }
419
420 /*.......................................................................
421 * Return the value of getenv("PWD") if this points to the current
422 * directory, or the return value of getcwd() otherwise. The reason for
423 * prefering PWD over getcwd() is that the former preserves the history
424 * of symbolic links that have been traversed to reach the current
425 * directory. This function is designed to provide the equivalent
426 * expansion of the ksh ~+ directive, which normally returns its value
427 * of PWD.
428 *
429 * Input:
430 * home HomeDir * The resource object for reading home directories.
431 * Output:
432 * return const char * A pointer to either home->buffer, where the
433 * pathname is recorded, the string returned by
434 * getenv("PWD"), or NULL on error.
435 */
hd_getpwd(HomeDir * home)436 static const char *hd_getpwd(HomeDir *home)
437 {
438 /*
439 * Get the absolute path of the current working directory.
440 */
441 char *cwd = getcwd(home->buffer, home->buflen);
442 /*
443 * Some shells set PWD with the path of the current working directory.
444 * This will differ from cwd in that it won't have had symbolic links
445 * expanded.
446 */
447 const char *pwd = getenv("PWD");
448 /*
449 * If PWD was set, and it points to the same directory as cwd, return
450 * its value. Note that it won't be the same if the current shell or
451 * the current program has changed directories, after inheriting PWD
452 * from a parent shell.
453 */
454 struct stat cwdstat, pwdstat;
455 if(pwd && cwd && stat(cwd, &cwdstat)==0 && stat(pwd, &pwdstat)==0 &&
456 cwdstat.st_dev == pwdstat.st_dev && cwdstat.st_ino == pwdstat.st_ino)
457 return pwd;
458 /*
459 * Also return pwd if getcwd() failed, since it represents the best
460 * information that we have access to.
461 */
462 if(!cwd)
463 return pwd;
464 /*
465 * In the absence of a valid PWD, return cwd.
466 */
467 return cwd;
468 }
469
470 #endif /* ifndef WITHOUT_FILE_SYSTEM */
471