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