#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <syslog.h>

#include <rpc/rpc.h>
#include <rpc/xdr.h>
#include <rpcsvc/yp_prot.h>
#include <rpcsvc/ypclnt.h>

#define PAM_SM_ACCOUNT
#define PAM_SM_AUTH
#include <security/pam_modules.h>

#define SERVICE_NAME "PAM-nis"

typedef struct state_struct
{
	int onerr;
	int sense;
	int item;
	char *domain;
	char *map;
	char *value;
	int status;
	int errorret;
} STATE;

#define STATE_DEFAULT {1, 0, 0, NULL, NULL, NULL, 0, 0}

static const char *onerr_values[]={"succeed", "fail", NULL};
static const char *sense_values[]={"allow", "deny", NULL};
static const char *item_values[]={"user", "tty", "rhost", "ruser", NULL};
static const char *error_values[]={"default", "userunknown", "authfail",
				   "permdenied", "expired", NULL};
static const char *arg_values[]={"onerr", "sense", "domain", "map",
			  "value", "item", "error", NULL};
static int item_pam[]={PAM_USER, PAM_TTY, PAM_RHOST, PAM_RUSER};
static int err_pam[]={0, PAM_USER_UNKNOWN, PAM_AUTH_ERR, PAM_PERM_DENIED,
		      PAM_ACCT_EXPIRED};

static int set_onerr(STATE *state, const char *val);
static int set_sense(STATE *state, const char *val);
static int set_item(STATE *state, const char *val);
static int set_domain(STATE *state, const char *val);
static int set_map(STATE *state, const char *val);
static int set_value(STATE *state, const char *val);
static int set_error(STATE *state, const char *val);

static int (*arg_handlers[])(STATE *state, const char *val)=
{set_onerr, set_sense, set_domain, set_map, set_value, set_item, set_error};

static int arg_to_int(const char *arg, const char *strings[], const char *str)
{
	int i, len;
	char *buf;
	for (i=0,len=0; strings[i]; i++)
	{
		if (!strcasecmp(str, strings[i])) return i;
		len+=strlen(strings[i])+1;
	}
	/* syslog "arg=[value|value|value] expected but got str\0" */
	buf=malloc(strlen(arg)+len+strlen(str)+21);
	if (!buf) {syslog(LOG_CRIT, "malloc failed"); return -1;}
	strcpy(buf, arg);
	strcat(buf, "=");
	for (i=0; strings[i]; i++)
	{
		if (i) strcat(buf, "|"); else strcat(buf, "[");
		strcat(buf, strings[i]);
	}
	strcat(buf, "] expected but got ");
	strcat(buf, str);
	syslog(LOG_ERR, "%s", buf);
	free(buf);
	return -1;
}

static int set_onerr(STATE *state, const char *val)
{
	return ((state->onerr=arg_to_int("onerr", onerr_values, val))==-1);
}

static int set_sense(STATE *state, const char *val)
{
	return ((state->sense=arg_to_int("sense", sense_values, val))==-1);
}

static int set_item(STATE *state, const char *val)
{
	return ((state->item=arg_to_int("item", item_values, val))==-1);
}

static int set_domain(STATE *state, const char *val)
{
	free(state->domain);
	if ((state->domain=strdup(val))) return 0;
	syslog(LOG_CRIT, "No memory");
	state->status=PAM_BUF_ERR;
	return -1;
}

static int set_map(STATE *state, const char *val)
{

	free(state->map);
	if ((state->map=strdup(val))) return 0;
	syslog(LOG_CRIT, "No memory");
	state->status=PAM_BUF_ERR;
	return -1;
}

static int set_value(STATE *state, const char *val)
{
	free(state->value);
	if ((state->value=strdup(val))) return 0;
	syslog(LOG_CRIT, "No memory");
	state->status=PAM_BUF_ERR;
	return -1;
}

static int set_error(STATE *state, const char *val)
{
	return ((state->errorret=arg_to_int("error", error_values, val))==-1);
}

static int parse(STATE *state, const char *suppliedarg)
{
	char *arg, *value;
	int len, fn, ret;

	arg=strdup(suppliedarg);
	value=strchr(arg, '=');
	if (!value)
	{
		syslog(LOG_ERR, "unexpected word %s", arg);
		return -1;
	}
	len=value-arg;
	*value++=0;
	if ((fn=arg_to_int("argument", arg_values, arg))==-1) return -1;
	ret=(*arg_handlers[fn])(state, value);
	free(arg);
	return ret;
}

static int checkgroup(const char *group, const char *grouplist)
{
	int len=strlen(group);
	while (*grouplist)
	{
		while (*grouplist && (*grouplist==' ' || *grouplist=='\t' 
			|| *grouplist=='\n')) grouplist++;
		if (!strncasecmp(group, grouplist, len)) return 0;
		while (*grouplist && !(*grouplist==' ' || *grouplist=='\t' 
			|| *grouplist=='\n')) grouplist++;
	}
	return 1;
}

static int yperr(const char *fn, int err)
{
	if (!err) return 0;

	switch(err)
	{
	case YPERR_KEY:		/*case YPERR_NOMORE: */
		/* Not an error */
		break;
	case YPERR_ACCESS:	case YPERR_BUSY:	case YPERR_RESRC:
	case YPERR_DOMAIN:	case YPERR_PMAP:	case YPERR_YPERR:
	case YPERR_YPBIND:	case YPERR_VERS:	case YPERR_RPC:
		/* System failure affecting PAM service */
		syslog(LOG_ALERT, "%s() failed. Reason: %s",
			fn, yperr_string(err));
		break;
	default:
		/* Probably configuration error */
		syslog(LOG_ERR, "%s() failed. Reason: %s",
			fn, yperr_string(err));
	}
	return err;
}

static int ypauthenticate(pam_handle_t *pamh, int flags, 
	int argc, const char **argv,
	int errorret)
{
	int i, err, keytype;
	const char *key;
	STATE state=STATE_DEFAULT;
	char *value=NULL; int valuelen;
	openlog(SERVICE_NAME, LOG_PID, LOG_AUTHPRIV);
	for (i=0,err=0; i<argc; i++) err|=parse(&state, argv[i]);
	if (!state.map)
	{
		syslog(LOG_ERR, "map name missing - use map=mname");
		err=1;
	}
	if (!state.domain)
	{
		err|=yperr("yp_get_default_domain", 
			yp_get_default_domain(&state.domain));
	}
	if (err)
	{
		syslog(LOG_ERR, "error parsing options - assuming %s",
			state.onerr?"failure":"success");
		err=state.status?state.status:PAM_SERVICE_ERR;
		return state.onerr?err:PAM_SUCCESS;
	}

	keytype=item_pam[state.item];
	if (keytype==PAM_USER)
		err=pam_get_user(pamh, &key, NULL);
	else
		err=pam_get_item(pamh, keytype, (const void **)&key);

	if (err)
	{
		syslog(LOG_ERR, "couldn't get item %s - assuming %s",
			item_values[state.item],
			state.onerr?"failure":"success");
		return state.onerr?PAM_SERVICE_ERR:PAM_SUCCESS;
	}

	if (state.errorret) errorret=err_pam[state.errorret];

	if (!strlen(key)) return state.sense?errorret:PAM_SUCCESS;
	err=yperr("yp_match",
		yp_match(state.domain, state.map, (char *)key, strlen(key),
		&value, &valuelen));
	if (err && err!=YPERR_KEY) 
	{
		syslog(LOG_ERR, "couldn't get map %s from domain %s "
			"- assuming %s",
			state.map, state.domain,
			state.onerr?"failure":"success");
		return state.onerr?PAM_SERVICE_ERR:PAM_SUCCESS;
	}
	if (!err)
	{
		if (state.value) err=checkgroup(state.value, value);
		free(value);
	}
	return (state.sense^!err)?PAM_SUCCESS:errorret;
}

PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags,
                                   int argc, const char **argv)
{
	return ypauthenticate(pamh, flags, argc, argv, PAM_AUTH_ERR);
}

PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags,
	int argc, const char **argv)
{
	return PAM_SUCCESS;
}

PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
	int argc, const char **argv)
{
	return ypauthenticate(pamh, flags, argc, argv, PAM_PERM_DENIED);
}

