/**********************************************************************
 *  getpwnam.c -- a uniform reentrant interface to getpwnam()
 *
 * Copyright 1996 by the Massachusetts Institute of Technology
 * For copying and distribution information, please see the file
 * <mit-copyright.h>.
 **********************************************************************/
#include <mit-copyright.h>

#include <AL/AL.h>
#include <errno.h>
#include <string.h>
#include <sys/param.h>

#ifdef SOLARIS
/* Solaris provides getpwnam_r() but doesn't provide the password field. */
#define REENTRANT_WITH_SHADOW
#else
#ifdef BSD4_4
/* BSD 4.4 has DB databases (one with passwords, one without). */
#define DB_FILES
#include <db.h>
#include <utmp.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#else
/* Standard /etc/passwd */
#define ETC_PASSWD
#endif
#endif

/* This function needs to be reentrant, and it needs to return the
 * password field on systems with Solaris-style shadow passwords. */
struct passwd *ALgetpwnam_r(const char *name, struct passwd *result,
			    char *buffer, int buflen)
{
#ifdef REENTRANT_WITH_SHADOW
    struct spwd spent;
    int pwsize;

    if (getspnam_r(name, &spent, buffer, buflen)) {
	pwsize = strlen(spent.sp_pwdp) + 1;
	memmove(buffer, spent.sp_pwdp, pwsize);
	if (!getpwnam_r(name, result, buffer + pwsize, buflen - pwsize))
	    return NULL;
	result->pw_passwd = buffer;
	return result;
    }
    return getpwnam_r(name, result, buffer, buflen);
#endif
#ifdef DB_FILES
    DB *db;
    DBT key, value;
    unsigned char buf[UT_NAMESIZE + 1];
    int len, success = 0;
    char *p;

    errno = 0;

    /* Open the insecure or secure database depending on whether we're root. */
    db = dbopen((geteuid()) ? _PATH_MP_DB : _PATH_SMP_DB, O_RDONLY, 0,
		DB_HASH, NULL);
    if (!db)
	return(NULL);

    /* Look up the username. */
    len = strlen(name);
    len = MIN(len, UT_NAMESIZE);
    buf[0] = _PW_KEYBYNAME;
    memcpy(buf + 1, name, len);
    key.data = buf;
    key.size = len + 1;
    if (db->get(db, &key, &value, 0) == 0) {
	if (value.size <= buflen) {
	    memcpy(buffer, value.data, value.size);
#define FIELD(v) v = buffer; buffer += strlen(buffer) + 1
	    FIELD(result->pw_name);
	    FIELD(result->pw_passwd);
	    memcpy(&result->pw_uid, buffer, sizeof(int));
	    memcpy(&result->pw_gid, buffer + sizeof(int), sizeof(int));
	    memcpy(&result->pw_change, buffer + 2 * sizeof(int),
		   sizeof(time_t));
	    buffer += 2 * sizeof(int) + sizeof(time_t);
	    FIELD(result->pw_class);
	    FIELD(result->pw_gecos);
	    FIELD(result->pw_dir);
	    FIELD(result->pw_shell);
	    memcpy(&result->pw_expire, buffer, sizeof(time_t));
	    success = 1;
	} else {
	    errno = ERANGE;
	}
    }
    db->close(db);
    return (success) ? result : NULL;
#endif
#ifdef ETC_PASSWD
    /* BSD 4.3 has /etc/passwd and /etc/passwd.{dir,pag}.  Only implement
     * reading /etc/passwd, since the DBM routines aren't reentrant and
     * we don't really need that level of performance in the login system
     * anyway. */
    FILE *fp;
    int success = 0, len;

    /* Sanity-check buflen so we can assume it's at least the name length. */
    len = strlen(name);
    if (buflen < len + 1) {
	errno = ERANGE;
	return NULL;
    }

    errno = 0;
#if defined(BSD) || defined(ultrix)
    result->pw_quota = 0;
    result->pw_comment = "";
#endif

    fp = fopen("/etc/passwd", "r");
    if (!fp)
	return NULL;

    while (fgets(buffer, buflen, fp)) {
	if (strncmp(buffer, name, len) == 0 && buffer[len] == ':') {
	    if (buffer[strlen(buffer) - 1] != '\n') {
		errno = ERANGE;
		break;
	    }
#define NEXT_FIELD	buffer = strchr(buffer, ':'); \
			if (!buffer) break; *buffer++ = 0
#define FIELD(v) v = buffer; NEXT_FIELD
	    FIELD(result->pw_name);
	    FIELD(result->pw_passwd);
	    result->pw_uid = atoi(buffer);
	    NEXT_FIELD;
	    result->pw_gid = atoi(buffer);
	    NEXT_FIELD;
	    FIELD(result->pw_gecos);
	    FIELD(result->pw_dir);
	    result->pw_shell = buffer;
	    buffer[strlen(buffer) - 1] = 0;
	    success = 1;
	    break;
	}
    }

    fclose(fp);
    return (success) ? result : NULL;
#endif
}

