/*
 * Copyright (c) 2007, OpenFWTK Development Group
 * All rights reserved. See LICENSE.
 */

/*
**  Copyright 2000-2003 University of Illinois Board of Trustees
**  Copyright 2000-2003 Mark D. Roth
**  All rights reserved. 
**
**  pam_authsrv.c - PAM module for TIS authsrv authentication
**
**  Mark D. Roth <roth@uiuc.edu>
**  Campus Information Technologies and Educational Services
**  University of Illinois at Urbana-Champaign
*/

#include <stdio.h>
#include <syslog.h>

#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <security/pam_appl.h>
#include <security/pam_modules.h>

#include "firewall.h"
#include "firewall2.h"
#include "fwfunc.h"
#include "auth.h"
extern char* ip2name(struct in_addr);


#define PAM_AUTHSRV_MAPFILE "/etc/pam_tis.map"
#define PAM_AUTHSRV_BUFSIZE 1024

#ifndef LOG_AUTHPRIV
# define LOG_AUTHPRIV LOG_AUTH
#endif


/*
** tis_map_lookup() - look up key in PAM_AUTHSRV_MAPFILE
**
** If key is found and value is present, store value in val and return 0.
** If key is found without a value, copy key to val and return 0.
** If key is not found or mapfile cannot be read, copy key to val and return 1.
*/
static int
tis_map_lookup(char *key, char *val, size_t valuelen)
{
	FILE *fp;
	char buf[PAM_AUTHSRV_BUFSIZE];
	char *keyp, *valp;
	char *cp;
	int retval = 1;

	fp = fopen(PAM_AUTHSRV_MAPFILE, "r");

	while (fp != NULL
	       && fgets(buf, sizeof(buf), fp) != NULL)
	{
		/* strip newline */
		buf[strlen(buf) - 1] = '\0';

		/* strip comments */
		if ((cp = strchr(buf, '#')) != NULL
		    && (cp == buf || *(cp - 1) != '\\'))
			*cp = '\0';

		/* strip leading whitespace */
		keyp = buf + strspn(buf, " \t");

		/* skip blank lines */
		if (*keyp == '\0')
			continue;

		/* seperate the key and value */
		valp = NULL;
		if ((cp = strchr(keyp, ':')) != NULL)
		{
			*cp = '\0';
			valp = cp + 1;
		}

		/* strip trailing whitespace in key (if any) */
		if ((cp = strpbrk(keyp, " \t")) != NULL)
			*cp = '\0';

		/* if it's not a match, continue */
		if (strcmp(keyp, key) != 0)
			continue;

		/* strip whitespace in value */
		if (valp != NULL)
		{
			valp += strspn(valp, " \t");
			if ((cp = strpbrk(valp, " \t")) != NULL)
				*cp = '\0';
		}

		/* if no value is present, return key */
		if (valp == NULL || *valp == '\0')
		{
			retval = 0;
			break;
		}

		/* return value */
		strlcpy(val, valp, valuelen);
		fclose(fp);
		return 0;
	}

	if (fp != NULL)
		fclose(fp);

	strlcpy(val, key, valuelen);
	return retval;
}


/* macros for handling users which aren't listed in PAM_AUTHSRV_MAPFILE */
#define MAP_UNKNOWN_FAIL	1
#define MAP_UNKNOWN_SUCCEED	2
#define MAP_UNKNOWN_IGNORE	3


int
pam_sm_authenticate(pam_handle_t *pamh, int flags,
		    int argc, const char *argv[])
{
	char *user, *service, *rladdr, *riaddr;
	char authuser[PAM_AUTHSRV_BUFSIZE];
	struct pam_message prompt = { PAM_PROMPT_ECHO_ON, NULL };
	struct pam_message *msg[1];
	struct pam_conv *conv;
	Cfg *confp;
	char buf[PAM_AUTHSRV_BUFSIZE];
	struct pam_response *response = NULL;
	int i, map_flags = 0;
	struct in_addr addr;

#ifdef DEBUG
	printf("==> pam_sm_authenticate(pamh=0x%lx, flags=%d): "
	       "argument vector:", pamh, flags);
#endif

	for (i = 0; i < argc; i++)
	{
#ifdef DEBUG
		printf("    pam_sm_authenticate(): argv[%d] = \"%s\"", i,
		       argv[i]);
#endif
		if (strcmp(argv[i], "unknown_user=fail") == 0)
			map_flags = MAP_UNKNOWN_FAIL;
		else if (strcmp(argv[i], "unknown_user=succeed") == 0)
			map_flags = MAP_UNKNOWN_SUCCEED;
		else if (strcmp(argv[i], "unknown_user=ignore") == 0)
			map_flags = MAP_UNKNOWN_IGNORE;
		else
			syslog(LOG_AUTHPRIV | LOG_ERR,
			       "pam_authsrv_authenticate: unknown "
			       "argument \"%s\"", argv[i]);
	}

	if (pam_get_user(pamh, (const char **)&user, "login: ") != PAM_SUCCESS)
	{
		syslog(LOG_AUTHPRIV | LOG_ERR,
		       "pam_authsrv_authenticate: pam_get_user failed");
		return PAM_USER_UNKNOWN;
	}

	if (tis_map_lookup(user, authuser, sizeof(authuser)) != 0)
	{
		if (map_flags == MAP_UNKNOWN_FAIL)
			return PAM_USER_UNKNOWN;
		else if (map_flags == MAP_UNKNOWN_SUCCEED)
			return PAM_SUCCESS;
		else if (map_flags == MAP_UNKNOWN_IGNORE)
			return PAM_IGNORE;
	}

	if (pam_get_item(pamh, PAM_SERVICE, (const void **)&service) != PAM_SUCCESS)
		service = "unknown";

	/*
	 * We do not trust application's name resolution just because even if it 
	 * behaves properly,  it is not possible to know which ip of hostname 
	 * having multiple addresses is  our originator. So we suggest turning dns 
	 * resolution off in PAM client application, so we can deal with IP address.
	 */

	if ((pam_get_item(pamh, PAM_RHOST, (const void **)&riaddr) == PAM_SUCCESS) && 
	    ((addr.s_addr = inet_addr(riaddr)) != INADDR_NONE)) {
		rladdr = ip2name(addr);
	} else 
		rladdr = riaddr = NULL;
		

#ifdef DEBUG
	printf("user = \"%s\", authuser = \"%s\", service = \"%s\"\n",
	       user, authuser, service);
#endif

	confp = cfg_read(service);
	if (confp == NULL
	    || confp == (Cfg *)(-1))
	{
		syslog(LOG_AUTHPRIV | LOG_ERR,
		       "pam_authsrv_authenticate: cannot read "
		       "netperm-table file");
		return PAM_AUTHINFO_UNAVAIL;
	}

	if (auth_open(confp) != 0 ||
	    auth_recv(buf, sizeof(buf)) != 0 ||
	    strncmp(buf, "Authsrv ready", 13) != 0)
	{
		syslog(LOG_AUTHPRIV | LOG_ERR,
		       "pam_authsrv_authenticate: auth_open failed");
		cfg_free(confp);
		return PAM_AUTHINFO_UNAVAIL;
	}
	cfg_free(confp);

	if (riaddr)
		snprintf(buf, sizeof(buf), "authenticate %.64s '%.64s %.512s/%.64s'", authuser, service, rladdr, riaddr);
	else
		snprintf(buf, sizeof(buf), "authenticate %.64s '%.64s'", authuser, service);

	if (auth_send(buf) != 0
	    || auth_recv(buf, sizeof(buf)) != 0)
	{
		syslog(LOG_AUTHPRIV | LOG_ERR,
		       "pam_authsrv_authenticate: authentication "
		       "request failed");
		auth_close();
		return PAM_AUTHINFO_UNAVAIL;
	}

	if (strncmp(buf, "challenge ", 10) == 0)
		prompt.msg = buf + 10;
	else if (strncmp(buf, "chalnecho ", 10) == 0)
	{
		prompt.msg = buf + 10;
		prompt.msg_style = PAM_PROMPT_ECHO_OFF;
	}
	else if (strcmp(buf, "password") == 0)
		prompt.msg = "Password: ";
	else
	{
		syslog(LOG_AUTHPRIV | LOG_ERR,
		       "pam_authsrv_authenticate: server reply: %s", buf);
		auth_close();
		return PAM_USER_UNKNOWN;
	}

	msg[0] = &prompt;
	if (pam_get_item(pamh, PAM_CONV, (const void **)&conv) != PAM_SUCCESS
	    || conv->conv(1, (const struct pam_message **)msg,
			  &response, conv->appdata_ptr) != PAM_SUCCESS
	    || response == NULL
	    || response[0].resp == NULL)
	{
		syslog(LOG_AUTHPRIV | LOG_ERR,
		       "pam_authsrv_authenticate: cannot get user response");
		auth_close();
		return PAM_AUTHINFO_UNAVAIL;
	}

	snprintf(buf, sizeof(buf), "response '%s'", response[0].resp);
	free(response[0].resp);
	if (auth_send(buf) != 0 ||
	    auth_recv(buf, sizeof(buf)) != 0)
	{
		syslog(LOG_AUTHPRIV | LOG_ERR,
		       "pam_authsrv_authenticate: cannot send user response");
		auth_close();
		return PAM_AUTHINFO_UNAVAIL;
	}

	if (strncmp(buf, "ok", 2) != 0)
	{
		syslog(LOG_AUTHPRIV | LOG_ERR,
		       "pam_authsrv_authenticate: authentication rejected by server");
		auth_close();
		return PAM_AUTH_ERR;
	}

	auth_close();
	return PAM_SUCCESS;
}


int
pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char *argv[])
{
#ifdef DEBUG
	syslog(LOG_AUTHPRIV | LOG_ERR,
	       "pam_authsrv_setcred: not implemented");
#endif
	return PAM_SUCCESS;
}


int
pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char *argv[])
{
	syslog(LOG_AUTHPRIV | LOG_ERR,
	       "pam_authsrv_acct_mgmt: not implemented");
	return PAM_IGNORE;
}


int
pam_sm_open_session(pam_handle_t *pamh, int flags,
		    int argc, const char *argv[])
{
	syslog(LOG_AUTHPRIV | LOG_ERR,
	       "pam_authsrv_open_session: not implemented");
	return PAM_IGNORE;
}


int
pam_sm_close_session(pam_handle_t *pamh, int flags,
		     int argc, const char *argv[])
{
	syslog(LOG_AUTHPRIV | LOG_ERR,
	       "pam_authsrv_close_session: not implemented");
	return PAM_IGNORE;
}


int
pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char *argv[])
{
	syslog(LOG_AUTHPRIV | LOG_ERR,
	       "pam_authsrv_chauthtok: not implemented");
	return PAM_IGNORE;
}


