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 */
84struct 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
94static 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 */
102HomeDir *_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 */
175HomeDir *_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 */
212const 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 */
304const 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 */
330int _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 */
448static 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