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