xref: /illumos-gate/usr/src/lib/libtecla/common/homedir.c (revision 8119dad84d6416f13557b0ba8e2aaf9064cbcfd3)
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  */
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  */
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  */
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  */
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  */
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