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