diff options
Diffstat (limited to 'libtecla-1.4.1/homedir.c')
-rw-r--r-- | libtecla-1.4.1/homedir.c | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/libtecla-1.4.1/homedir.c b/libtecla-1.4.1/homedir.c new file mode 100644 index 0000000..f2029b7 --- /dev/null +++ b/libtecla-1.4.1/homedir.c @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2000, 2001 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. + */ + +#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" + +/* + * Set the max length of the error-reporting string. There is no point + * in this being longer than the width of a typical terminal window. + * In composing error messages, I have assumed that this number is + * at least 80, so you don't decrease it below this number. + */ +#define ERRLEN 200 + +/* + * Use the reentrant POSIX threads versions of the password lookup functions? + */ +#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199506L +#define THREAD_COMPATIBLE 1 +#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 { + char errmsg[ERRLEN+1]; /* Error-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) { + fprintf(stderr, "_new_HomeDir: Insufficient memory.\n"); + 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->errmsg[0] = '\0'; + home->buffer = NULL; + home->buflen = 0; +/* + * 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) { + fprintf(stderr, "_new_HomeDir: Insufficient memory."); + 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) { + 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) { + fprintf(stderr, "_hd_lookup_home_dir: NULL argument(s).\n"); + 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) { + strncpy(home->errmsg, "Cannot determine current directory.", ERRLEN); + home->errmsg[ERRLEN] = '\0'; + return NULL; + } + 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) { + const char *fmt = "User '%.*s' doesn't exist."; + sprintf(home->errmsg, fmt, ERRLEN - strlen(fmt), user); + 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) { + const char *fmt = "User '%.*s' doesn't exist."; + sprintf(home->errmsg, fmt, ERRLEN - strlen(fmt), user); + 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 ? home->errmsg : "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. + * 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, void *data, HOME_DIR_FN(*callback_fn)) +{ + int waserr = 0; /* True after errors */ +/* + * Check the arguments. + */ + if(!home || !callback_fn) { + if(home) + strcpy(home->errmsg, + "_hd_scan_user_home_dirs: Missing callback function"); + return 1; + }; +/* + * 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(_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 199506L + { + 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. + */ + while((pwd = getpwent()) != NULL && !waserr) + waserr = callback_fn(data, pwd->pw_name, pwd->pw_dir, home->errmsg, + ERRLEN); +/* + * Close the password file. + */ + endpwent(); + }; +#endif +/* + * Under ksh ~+ stands for the absolute pathname of the current working + * directory. + */ + if (!waserr) { + const char *pwd = hd_getpwd(home); + if(pwd) { + waserr = callback_fn(data, "+", pwd, home->errmsg, ERRLEN); + } else { + waserr = 1; + strncpy(home->errmsg, "Cannot determine current directory.", ERRLEN); + home->errmsg[ERRLEN] = '\0'; + }; + }; + 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; +} |