OpenSolaris_b135/lib/libtecla/common/homedir.c

Compare this file to the similar file:
Show the results in this format:

/*
 * Copyright (c) 2000, 2001, 2002, 2003, 2004 by Martin C. Shepherd.
 * 
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons
 * to whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
 * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
 * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 * 
 * Except as contained in this notice, the name of a copyright holder
 * shall not be used in advertising or otherwise to promote the sale, use
 * or other dealings in this Software without prior written authorization
 * of the copyright holder.
 */

/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * If file-system access is to be excluded, this module has no function,
 * so all of its code should be excluded.
 */
#ifndef WITHOUT_FILE_SYSTEM

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>

#include "pathutil.h"
#include "homedir.h"
#include "errmsg.h"

/*
 * Use the reentrant POSIX threads versions of the password lookup functions?
 */
#if defined(PREFER_REENTRANT) && defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199506L
#define THREAD_COMPATIBLE 1
/*
 * Under Solaris we can use thr_main() to determine whether
 * threads are actually running, and thus when it is necessary
 * to avoid non-reentrant features.
 */
#if defined __sun && defined __SVR4
#include <thread.h>                      /* Solaris thr_main() */
#endif
#endif

/*
 * Provide a password buffer size fallback in case the max size reported
 * by sysconf() is said to be indeterminate.
 */
#define DEF_GETPW_R_SIZE_MAX 1024

/*
 * The resources needed to lookup and record a home directory are
 * maintained in objects of the following type.
 */
struct HomeDir {
  ErrMsg *err;             /* The error message report buffer */
  char *buffer;            /* A buffer for reading password entries and */
                           /*  directory paths. */
  int buflen;              /* The allocated size of buffer[] */
#ifdef THREAD_COMPATIBLE
  struct passwd pwd;       /* The password entry of a user */
#endif
};

static const char *hd_getpwd(HomeDir *home);

/*.......................................................................
 * Create a new HomeDir object.
 *
 * Output:
 *  return  HomeDir *  The new object, or NULL on error.
 */
HomeDir *_new_HomeDir(void)
{
  HomeDir *home;  /* The object to be returned */
  size_t pathlen; /* The estimated maximum size of a pathname */
/*
 * Allocate the container.
 */
  home = (HomeDir *) malloc(sizeof(HomeDir));
  if(!home) {
    errno = ENOMEM;
    return NULL;
  };
/*
 * Before attempting any operation that might fail, initialize the
 * container at least up to the point at which it can safely be passed
 * to _del_HomeDir().
 */
  home->err = NULL;
  home->buffer = NULL;
  home->buflen = 0;
/*
 * Allocate a place to record error messages.
 */
  home->err = _new_ErrMsg();
  if(!home->err)
    return _del_HomeDir(home);
/*
 * Allocate the buffer that is used by the reentrant POSIX password-entry
 * lookup functions.
 */
#ifdef THREAD_COMPATIBLE
/*
 * Get the length of the buffer needed by the reentrant version
 * of getpwnam().
 */
#ifndef _SC_GETPW_R_SIZE_MAX
  home->buflen = DEF_GETPW_R_SIZE_MAX;
#else
  errno = 0;
  home->buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
/*
 * If the limit isn't available, substitute a suitably large fallback value.
 */
  if(home->buflen < 0 || errno)
    home->buflen = DEF_GETPW_R_SIZE_MAX;
#endif
#endif
/*
 * If the existing buffer length requirement is too restrictive to record
 * a pathname, increase its length.
 */
  pathlen = _pu_pathname_dim();
  if(pathlen > home->buflen)
    home->buflen = pathlen;
/*
 * Allocate a work buffer.
 */
  home->buffer = (char *) malloc(home->buflen);
  if(!home->buffer) {
    errno = ENOMEM;
    return _del_HomeDir(home);
  };
  return home;
}

/*.......................................................................
 * Delete a HomeDir object.
 *
 * Input:
 *  home   HomeDir *  The object to be deleted.
 * Output:
 *  return HomeDir *  The deleted object (always NULL).
 */
HomeDir *_del_HomeDir(HomeDir *home)
{
  if(home) {
    home->err = _del_ErrMsg(home->err);
    if(home->buffer)
      free(home->buffer);
    free(home);
  };
  return NULL;
}

/*.......................................................................
 * Lookup the home directory of a given user in the password file.
 *
 * Input:
 *  home      HomeDir *   The resources needed to lookup the home directory.
 *  user   const char *   The name of the user to lookup, or "" to lookup
 *                        the home directory of the person running the
 *                        program.
 * Output:
 *  return const char *   The home directory. If the library was compiled
 *                        with threads, this string is part of the HomeDir
 *                        object and will change on subsequent calls. If
 *                        the library wasn't compiled to be reentrant,
 *                        then the string is a pointer into a static string
 *                        in the C library and will change not only on
 *                        subsequent calls to this function, but also if
 *                        any calls are made to the C library password
 *                        file lookup functions. Thus to be safe, you should
 *                        make a copy of this string before calling any
 *                        other function that might do a password file
 *                        lookup.
 *
 *                        On error, NULL is returned and a description
 *                        of the error can be acquired by calling
 *                        _hd_last_home_dir_error().
 */
const char *_hd_lookup_home_dir(HomeDir *home, const char *user)
{
  const char *home_dir;   /* A pointer to the home directory of the user */
/*
 * If no username has been specified, arrange to lookup the current
 * user.
 */
  int login_user = !user || *user=='\0';
/*
 * Check the arguments.
 */
  if(!home) {
    errno = EINVAL;
    return NULL;
  };
/*
 * Handle the ksh "~+". This expands to the absolute path of the
 * current working directory.
 */
  if(!login_user && strcmp(user, "+") == 0) {
    home_dir = hd_getpwd(home);
    if(!home_dir) {
      _err_record_msg(home->err, "Can't determine current directory",
		      END_ERR_MSG);
      return NULL;
    }
    return home_dir;
  };
/*
 * When looking up the home directory of the current user, see if the
 * HOME environment variable is set, and if so, return its value.
 */
  if(login_user) {
    home_dir = getenv("HOME");
    if(home_dir)
      return home_dir;
  };
/*
 * Look up the password entry of the user.
 * First the POSIX threads version - this is painful!
 */
#ifdef THREAD_COMPATIBLE
  {
    struct passwd *ret; /* The returned pointer to pwd */
    int status;         /* The return value of getpwnam_r() */
/*
 * Look up the password entry of the specified user.
 */
    if(login_user)
      status = getpwuid_r(geteuid(), &home->pwd, home->buffer, home->buflen,
			  &ret);
    else
      status = getpwnam_r(user, &home->pwd, home->buffer, home->buflen, &ret);
    if(status || !ret) {
      _err_record_msg(home->err, "User '", user, "' doesn't exist.",
		      END_ERR_MSG);
      return NULL;
    };
/*
 * Get a pointer to the string that holds the home directory.
 */
    home_dir = home->pwd.pw_dir;
  };
/*
 * Now the classic unix version.
 */
#else
  {
    struct passwd *pwd = login_user ? getpwuid(geteuid()) : getpwnam(user);
    if(!pwd) {
      _err_record_msg(home->err, "User '", user, "' doesn't exist.",
		      END_ERR_MSG);
      return NULL;
    };
/*
 * Get a pointer to the home directory.
 */
    home_dir = pwd->pw_dir;
  };
#endif
  return home_dir;
}

/*.......................................................................
 * Return a description of the last error that caused _hd_lookup_home_dir()
 * to return NULL.
 *
 * Input:
 *  home   HomeDir *  The resources needed to record the home directory.
 * Output:
 *  return    char *  The description of the last error.
 */
const char *_hd_last_home_dir_error(HomeDir *home)
{
  return home ? _err_get_msg(home->err) : "NULL HomeDir argument";
}

/*.......................................................................
 * The _hd_scan_user_home_dirs() function calls a user-provided function
 * for each username known by the system, passing the function both
 * the name and the home directory of the user.
 *
 * Input:
 *  home             HomeDir *  The resource object for reading home
 *                              directories.
 *  prefix        const char *  Only information for usernames that
 *                              start with this prefix will be
 *                              returned. Note that the empty
 &                              string "", matches all usernames.
 *  data                void *  Anonymous data to be passed to the
 *                              callback function.
 *  callback_fn  HOME_DIR_FN(*) The function to call for each user.
 * Output:
 *  return               int    0 - Successful completion.
 *                              1 - An error occurred. A description
 *                                  of the error can be obtained by
 *                                  calling _hd_last_home_dir_error().
 */
int _hd_scan_user_home_dirs(HomeDir *home, const char *prefix,
			    void *data, HOME_DIR_FN(*callback_fn))
{
  int waserr = 0;       /* True after errors */
  int prefix_len;       /* The length of prefix[] */
/*
 * Check the arguments.
 */
  if(!home || !prefix || !callback_fn) {
    if(home) {
      _err_record_msg(home->err,
		      "_hd_scan_user_home_dirs: Missing callback function",
		      END_ERR_MSG);
    };
    return 1;
  };
/*
 * Get the length of the username prefix.
 */
  prefix_len = strlen(prefix);
/*
 * There are no reentrant versions of getpwent() etc for scanning
 * the password file, so disable username completion when the
 * library is compiled to be reentrant.
 */
#if defined(PREFER_REENTRANT) && defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199506L
#if defined __sun && defined __SVR4
  if(0)
#else
  if(1)
#endif
  {
    struct passwd pwd_buffer;  /* A returned password entry */
    struct passwd *pwd;        /* A pointer to pwd_buffer */
    char buffer[512];          /* The buffer in which the string members of */
                               /* pwd_buffer are stored. */
/*
 * See if the prefix that is being completed is a complete username.
 */
    if(!waserr && getpwnam_r(prefix, &pwd_buffer, buffer, sizeof(buffer),
			     &pwd) == 0 && pwd != NULL) {
      waserr = callback_fn(data, pwd->pw_name, pwd->pw_dir,
			   _err_get_msg(home->err), ERR_MSG_LEN);
    };
/*
 * See if the username of the current user minimally matches the prefix.
 */
    if(!waserr && getpwuid_r(getuid(), &pwd_buffer, buffer, sizeof(buffer),
			     &pwd) == 0 && pwd != NULL &&
                             strncmp(prefix, pwd->pw_name, prefix_len)==0) {
      waserr = callback_fn(data, pwd->pw_name, pwd->pw_dir,
			   _err_get_msg(home->err), ERR_MSG_LEN);
    };
/*
 * Reentrancy not required?
 */
  } else
#endif
  {
    struct passwd pwd_buffer;  /* A returned password entry */
    struct passwd *pwd;   /* The pointer to the latest password entry */
/*
 * Open the password file.
 */
    setpwent();
/*
 * Read the contents of the password file, looking for usernames
 * that start with the specified prefix, and adding them to the
 * list of matches.
 */
#if defined __sun && defined __SVR4
    while((pwd = getpwent_r(&pwd_buffer, home->buffer, home->buflen)) != NULL && !waserr) {
#else
    while((pwd = getpwent()) != NULL && !waserr) {
#endif
      if(strncmp(prefix, pwd->pw_name, prefix_len) == 0) {
	waserr = callback_fn(data, pwd->pw_name, pwd->pw_dir,
			     _err_get_msg(home->err), ERR_MSG_LEN);
      };
    };
/*
 * Close the password file.
 */
    endpwent();
  };
/*
 * Under ksh ~+ stands for the absolute pathname of the current working
 * directory.
 */
  if(!waserr && strncmp(prefix, "+", prefix_len) == 0) {
    const char *pwd = hd_getpwd(home);
    if(pwd) {
      waserr = callback_fn(data, "+", pwd, _err_get_msg(home->err),ERR_MSG_LEN);
    } else {
      waserr = 1;
      _err_record_msg(home->err, "Can't determine current directory.",
		      END_ERR_MSG);
    };
  };
  return waserr;
}

/*.......................................................................
 * Return the value of getenv("PWD") if this points to the current
 * directory, or the return value of getcwd() otherwise. The reason for
 * prefering PWD over getcwd() is that the former preserves the history
 * of symbolic links that have been traversed to reach the current
 * directory. This function is designed to provide the equivalent
 * expansion of the ksh ~+ directive, which normally returns its value
 * of PWD.
 *
 * Input:
 *  home      HomeDir *  The resource object for reading home directories.
 * Output:
 *  return const char *  A pointer to either home->buffer, where the
 *                       pathname is recorded, the string returned by
 *                       getenv("PWD"), or NULL on error.
 */
static const char *hd_getpwd(HomeDir *home)
{
/*
 * Get the absolute path of the current working directory.
 */
  char *cwd = getcwd(home->buffer, home->buflen);
/*
 * Some shells set PWD with the path of the current working directory.
 * This will differ from cwd in that it won't have had symbolic links
 * expanded.
 */
  const char *pwd = getenv("PWD");
/*
 * If PWD was set, and it points to the same directory as cwd, return
 * its value. Note that it won't be the same if the current shell or
 * the current program has changed directories, after inheriting PWD
 * from a parent shell.
 */
  struct stat cwdstat, pwdstat;
  if(pwd && cwd && stat(cwd, &cwdstat)==0 && stat(pwd, &pwdstat)==0 &&
     cwdstat.st_dev == pwdstat.st_dev && cwdstat.st_ino == pwdstat.st_ino)
    return pwd;
/*
 * Also return pwd if getcwd() failed, since it represents the best
 * information that we have access to.
 */
  if(!cwd)
    return pwd;
/*
 * In the absence of a valid PWD, return cwd.
 */
  return cwd;
}

#endif  /* ifndef WITHOUT_FILE_SYSTEM */