/*
 *  This file is part of ixnet.library for the Amiga.
 *  Copyright (C) 1996 Jeff Shepherd
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  $Id:$
 *
 *  $Log:$
 *
 */

#define KERNEL
#include <pwd.h>
#include "ixnet.h"

#include <fcntl.h>
#include <db.h>
#include <utmp.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <time.h>

static struct passwd _pw_passwd;	/* password structure */
static DB *_pw_db;			/* password database */
static int _pw_keynum;			/* key counter */
static int _pw_stayopen;		/* keep fd's open */
static int __hashpw(), __initdb();

/* prototypes */
static struct passwd *__TCP2InetPwd(struct TCP_passwd *pwd);

struct passwd *
getpwent(void) {
    register struct ixnet *p = (struct ixnet *)u.u_ixnet;
    register int network_protocol = p->u_networkprotocol;

    if (network_protocol == IX_NETWORK_AMITCP) {
	struct TCP_passwd *err = UG_getpwent(p->u_UserGroupBase);

	if (err == NULL) {
	    errno = ug_GetErr(p->u_UserGroupBase);
	    return NULL;
	}
	else {
	    memcpy(&_pw_passwd,__TCP2InetPwd(err),sizeof(struct passwd));
	    return &_pw_passwd;
	}
    }
    else /*if (network_protocol == IX_NETWORK_AS225)*/ {
	DBT key;
	char bf[sizeof(_pw_keynum) + 1];

	if (!_pw_db && !__initdb())
		return((struct passwd *)NULL);

	++_pw_keynum;
	bf[0] = _PW_KEYBYNUM;
	bcopy((char *)&_pw_keynum, bf + 1, sizeof(_pw_keynum));
	key.data = (u_char *)bf;
	key.size = sizeof(_pw_keynum) + 1;
	return(__hashpw(&key) ? &_pw_passwd : (struct passwd *)NULL);
    }
}

struct passwd *
getpwnam(const char *name) {
    register struct ixnet *p = (struct ixnet *)u.u_ixnet;
    register int network_protocol = p->u_networkprotocol;

    if (network_protocol == IX_NETWORK_AMITCP) {
	struct TCP_passwd *err = UG_getpwnam(p->u_UserGroupBase,name);

	if (err == NULL) {
	    errno = ug_GetErr(p->u_UserGroupBase);
	    return NULL;
	}
	else {
	    memcpy(&_pw_passwd,__TCP2InetPwd(err),sizeof(struct passwd));
	}
	return &_pw_passwd;
    }
    else /* if (network_protocol == IX_NETWORK_AS225)*/ {
	DBT key;
	int len, rval;
	char bf[UT_NAMESIZE + 1];

	if (!_pw_db && !__initdb())
		return((struct passwd *)NULL);

	bf[0] = _PW_KEYBYNAME;
	len = strlen(name);
	bcopy(name, bf + 1, MIN(len, UT_NAMESIZE));
	key.data = (u_char *)bf;
	key.size = len + 1;
	rval = __hashpw(&key);

	if (!_pw_stayopen) {
	    (void)(_pw_db->close)(_pw_db);
	    _pw_db = (DB *)NULL;
	}
	return(rval ? &_pw_passwd : (struct passwd *)NULL);
    }
}

struct passwd *
getpwuid(uid_t uid) {
    register struct ixnet *p = (struct ixnet *)u.u_ixnet;
    register int network_protocol = p->u_networkprotocol;

    /* Don't do this if uid == -2 (nobody2) */
    /* This happens when someone doesn't use AmiTCP's login */
    if (network_protocol == IX_NETWORK_AMITCP) {
	if (uid != (uid_t)-2) {
	    struct TCP_passwd *err = UG_getpwuid(p->u_UserGroupBase,uid);

	    if (err == NULL) {
		errno = ug_GetErr(p->u_UserGroupBase);
		return NULL;
	    }
	    else {
		memcpy(&_pw_passwd,__TCP2InetPwd(err),sizeof(struct passwd));
	    }
	    return &_pw_passwd;
	}
	else {
	    return getpwnam(getenv("USER"));
	}
    }
    else /*if (network_protocol == IX_NETWORK_AS225)*/  {
	DBT key;
	int keyuid, rval;
	char bf[sizeof(keyuid) + 1];

	if (!_pw_db && !__initdb())
	    return((struct passwd *)NULL);

	bf[0] = _PW_KEYBYUID;
	keyuid = uid;
	bcopy(&keyuid, bf + 1, sizeof(keyuid));
	key.data = (u_char *)bf;
	key.size = sizeof(keyuid) + 1;
	rval = __hashpw(&key);

	if (!_pw_stayopen) {
	    (void)(_pw_db->close)(_pw_db);
	    _pw_db = (DB *)NULL;
	}
	return(rval ? &_pw_passwd : (struct passwd *)NULL);
    }
}

int
setpassent(int stayopen) {
    register struct ixnet *p = (struct ixnet *)u.u_ixnet;

    if (p->u_networkprotocol == IX_NETWORK_AS225) {
	_pw_keynum = 0;
	_pw_stayopen = stayopen;
    }
    return(1);
}

int
setpwent(void) {
    register struct ixnet *p = (struct ixnet *)u.u_ixnet;
    register int network_protocol = p->u_networkprotocol;

    if (network_protocol == IX_NETWORK_AMITCP) {
	return UG_setpwent(p->u_UserGroupBase);
    }
    else /* if (network_protocol == IX_NETWORK_AS225) */ {
	_pw_keynum = 0;
	_pw_stayopen = 0;
    }
    return 1;
}

void
endpwent(void) {
    register struct ixnet *p = (struct ixnet *)u.u_ixnet;
    register int network_protocol = p->u_networkprotocol;

    if (network_protocol == IX_NETWORK_AMITCP) {
	UG_endpwent(p->u_UserGroupBase);
    }
    else /* if (network_protocol == IX_NETWORK_AS225)*/ {
	_pw_keynum = 0;
	if (_pw_db) {
	    (void)(_pw_db->close)(_pw_db);
	    _pw_db = (DB *)NULL;
	}
    }
}

static int
__initdb(void) {
    char *p;

    p = (geteuid()) ? _PATH_MP_DB : _PATH_SMP_DB;
    _pw_db = hash_open(p, O_RDONLY, 0, NULL);
    if (_pw_db)
	return(1);
    return(0);
}

static	  int
__hashpw(DBT *key) {
    register char *p, *t;
    static u_int max;
    static char *line;
    DBT data;

    if ((_pw_db->get)(_pw_db, key, &data, 0))
	return(0);

    p = (char *)data.data;
    if (data.size > max && !(line = realloc(line, max += 1024)))
	return(0);

    t = line;
#define EXPAND(e)       e = t; while ((*t++ = *p++));
    EXPAND(_pw_passwd.pw_name);
    EXPAND(_pw_passwd.pw_passwd);
    bcopy(p, (char *)&_pw_passwd.pw_uid, sizeof(int));
    p += sizeof(int);
    bcopy(p, (char *)&_pw_passwd.pw_gid, sizeof(int));
    p += sizeof(int);
    bcopy(p, (char *)&_pw_passwd.pw_change, sizeof(time_t));
    p += sizeof(time_t);
    EXPAND(_pw_passwd.pw_class);
    EXPAND(_pw_passwd.pw_gecos);
    EXPAND(_pw_passwd.pw_dir);
    EXPAND(_pw_passwd.pw_shell);
    bcopy(p, (char *)&_pw_passwd.pw_expire, sizeof(time_t));
    p += sizeof(time_t);
    return(1);
}

/* change the AmiTCP password structure to the global format */
static struct passwd *__TCP2InetPwd(struct TCP_passwd *pwd) {
    static struct passwd retval;
    retval.pw_name = pwd->pw_name;
    retval.pw_passwd = pwd->pw_passwd;
    retval.pw_uid = pwd->pw_uid;
    retval.pw_gid = pwd->pw_gid;
    retval.pw_change = time((time_t *)NULL);
    retval.pw_class = NULL;
    retval.pw_gecos = pwd->pw_gecos;
    retval.pw_dir = pwd->pw_dir;
    retval.pw_shell = pwd->pw_shell;
    retval.pw_expire = (time_t)-1;
    return &retval;
}


