/* * 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. */ /* * 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 #include #include #include #include #include #include #include #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 /* 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 */