/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"@(#)utamghref_token.c	1.12 08/04/30 SMI"

#ifdef _SCCSID
static char *_SCCSid = "@(#)utamghref_token.c	1.12 08/04/30 SMI";
#endif	/* defined _SCCSID */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <syslog.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <stdarg.h>
#include <poll.h>
#include <fcntl.h>
#include <sys/file.h>
#include <ctype.h>

#include <utamgh.h>

/*
 * Variables used by Automated Multi-Group Hotdesking APIs.
 */

#define UT_AMGH_BUF_CHUNK_LEN 1024

#define UT_AMGH_BACK_END_DB	"/opt/SUNWutref/amgh/back_end_db"   
#define UT_AMGH_TOKEN       	"token"
#define UT_AMGH_USER_NAME   	"username"
#define UT_AMGH_HOST_NAME   	"host"

#define STRPREFIX(a,b)	    	(strncmp((a), (b), sizeof(b)-1)) 	 

static int safe_strncpy(char *dst, const char *src, size_t dstsize)
{
	(void) strncpy(dst, src, dstsize-1);
	dst[dstsize-1] = '\0';
	return(strlen(src));
}

static char **realloc_when_required(char** srclist, size_t srcsize)
{
	static int cursize = 0;
	int addsize = 5;

	if (cursize >= srcsize+2)
		return(srclist);

	cursize += addsize;
        return((char **)realloc(srclist,
		(cursize) * sizeof(char *)));
}

/*
 * ut_amgh_free_server_list(struct ut_amghret *amghret)
 *
 * calling:
 *     Free up any memory dynamically allocated in the
 *     ut_amghret structure.
 *
 * returns:
 *     UT_AMGH_SUCCESS
 */
int ut_amgh_free_server_list(struct ut_amghret *amghret)
{
	int i;

	if (amghret == NULL)
		return(UT_AMGH_SUCCESS);

	if (amghret->server_list != NULL) {
		for (i = 0; amghret->server_list[i] != NULL; ++i) {
			free(amghret->server_list[i]);
			amghret->server_list[i] = NULL;	
		}
	    	free(amghret->server_list);
        	amghret->server_list = NULL;
	}

	if (amghret->username != NULL) {
		free(amghret->username);
        	amghret->username = NULL;
	}

	if (amghret->errorstr != NULL) {
		free(amghret->errorstr);
		amghret->errorstr = NULL;
	}

	return(UT_AMGH_SUCCESS);
}


/*
 * Utility function to check if a given null terminated
 * string is fully blank or not
 */
int
is_blank_line(const char *line)
{
	if (!line)
		return (1);

	while (*line) {
		if (!isspace(*line))
			return (0);
		line++;
	}
	return (1); /* yup - fully blank */
}



/*
 * ut_amgh_get_server_list(const struct ut_amghargs *amghargs,
 *                         struct ut_amghret *amghret)
 *
 * calling:
 *     Retrieve a list of servers associated with the input parameters for
 *     the purposes of AMGH.
 *
 * returns:
 *     UT_AMGH_SUCCESS
 *
 *     UT_AMGH_INCOMPATIBLE_VERSION The version filled in struct ut_amghargs field
 *                                  amghversion by the customer is not compatible
 *                                  with UT_AMGH_VERSION value.
 *     UT_AMGH_NO_PERMISSION  No permission to access the back-end database for the
 *                            server lookups.
 *     UT_AMGH_DB_ERROR       Error accessing the back-end database.
 *     UT_AMGH_USERNAME_CONFLICT Error finding more than one non NULL user names
 *                               associated  with the token passed in.
 *     UT_AMGH_ERROR          Unknown error encountered by the implementation.
 */
int ut_amgh_get_server_list(const struct ut_amghargs *amghargs,
			    struct ut_amghret *amghret)
{
	FILE   *dbFile = NULL;
	char*  p;
	char   *valuepair; 
	int    server_count = 0;

	char   line[UT_AMGH_BUF_CHUNK_LEN+1];
	char   tokenvalue[UT_AMGH_BUF_CHUNK_LEN+1];
	char   uservalue[UT_AMGH_BUF_CHUNK_LEN+1];
	char   hostvalue[UT_AMGH_BUF_CHUNK_LEN+1];

        amghret->server_list = NULL;
        amghret->username = NULL;
	amghret->errorstr = NULL;

	/*
	 * The amghversion field is filled by the underlying PAM service before
	 * ut_amgh_get_server_list() is called.  ut_amgh_get_server_list() should
	 * verify that amghversion is >= UT_AMGH_VERSION to protect itself from
	 * being deployed on old and potentially incompatible versions of AMGH.
	 * If the test fails it should return UT_AMGH_INCOMPATIBLE_VERSION before
	 * accessing other fields in the ut_amghargs or ut_amghret structures.
	 */
	if (amghargs->amghversion < UT_AMGH_VERSION) { 
		return(UT_AMGH_INCOMPATIBLE_VERSION);
	}

	/* Check read-only permission of the server back-end DB. */
        if (access(UT_AMGH_BACK_END_DB, R_OK) != 0) {
                amghret->errorstr = strdup(strerror(errno));
		return(UT_AMGH_NO_PERMISSION);
	} 

	/* Open the server back-end DB */
	if ((dbFile = fopen(UT_AMGH_BACK_END_DB, "r")) == NULL) {
                amghret->errorstr = strdup(strerror(errno));
		return(UT_AMGH_DB_ERROR);
	} 

	/* Read each line of the server back-end DB */
	while (fgets(line, sizeof(line), dbFile) != NULL) {

		bzero(tokenvalue,sizeof(tokenvalue));
		bzero(uservalue,sizeof(uservalue));
		bzero(hostvalue,sizeof(hostvalue));

		p = NULL;
		if ((p = strchr(line, '\n')) != NULL) 
			*p = '\0'; 

		/* Don't process the comments */
		if (line != NULL && line[0] == '#')
			continue;

		/* Do not process blank lines */
		if (is_blank_line(line))
			continue;

		/*
		 * Each line must be of the form:
		 * token=<tokenid> [ username=<username> ] host=<fully-qualified hostname or IP address>
		 */
		if (strstr(line, UT_AMGH_TOKEN "=") == 0  ||
		    strstr(line, " " UT_AMGH_HOST_NAME "=") == 0) {
			fclose(dbFile);
			ut_amgh_free_server_list(amghret);
				amghret->errorstr = strdup("(utamghref_token) ERROR: Bad file entry: "
				"Mapping entries should be of "
				"the form token=<value> "
				"host=<value> [username=<value>]");
			return(UT_AMGH_ERROR);
		}

		valuepair = strtok(line, " ");
		while (valuepair != NULL) {

			/* If find token=xxx, grap the value of the token */
			if (STRPREFIX(valuepair, UT_AMGH_TOKEN "=") == 0) {
				if (safe_strncpy(tokenvalue,
					    valuepair + sizeof(UT_AMGH_TOKEN),
					    sizeof(tokenvalue))
				    >= sizeof(tokenvalue)) {
					fclose(dbFile);
					ut_amgh_free_server_list(amghret);
					amghret->errorstr = strdup("(utamghref_token) ERROR: Token too long");
					return(UT_AMGH_ERROR);
				}
			}

			/* If find username=xxx, grap the value of the user name */
			if (STRPREFIX(valuepair, UT_AMGH_USER_NAME "=") == 0) {
				if (safe_strncpy(uservalue,
					    valuepair + sizeof(UT_AMGH_USER_NAME),
					    sizeof(uservalue))
				    >= sizeof(uservalue)) {
					fclose(dbFile);
					ut_amgh_free_server_list(amghret);
					amghret->errorstr = strdup("(utamghref_token) ERROR: Username too long");
					return(UT_AMGH_ERROR);
				}
			}

			/* If find host=xxx, grap the value of the host server name */
			if (STRPREFIX(valuepair, UT_AMGH_HOST_NAME "=") == 0) {
				if (safe_strncpy(hostvalue,
					    valuepair + sizeof(UT_AMGH_HOST_NAME),
					    sizeof(hostvalue))
				    >= sizeof(hostvalue)){
					fclose(dbFile);
					ut_amgh_free_server_list(amghret);
					amghret->errorstr = strdup("(utamghref_token) ERROR: Host name too long");
					return(UT_AMGH_ERROR);
				}
			}

			/* Get next key=value pair */
			valuepair = strtok(NULL, " ");
		}

		if (strcmp(tokenvalue, amghargs->token) == 0) {	

			/*
			 * Realloc two elements (char *) to the server_list array
			 * Malloc a memory chuck for the first new element
			 * Assign NULL to the second new element 
			 */
			amghret->server_list = realloc_when_required(
							amghret->server_list,
							server_count);
			if (amghret->server_list == NULL) {
				fclose(dbFile);
				ut_amgh_free_server_list(amghret);
                		amghret->errorstr = strdup(strerror(errno));
				return(UT_AMGH_NOMEM);
			}

			amghret->server_list[server_count] = strdup(hostvalue);
			if (amghret->server_list[server_count] == NULL) {
				fclose(dbFile);
				ut_amgh_free_server_list(amghret);
                		amghret->errorstr = strdup(strerror(errno));
                                return(UT_AMGH_NOMEM);
			}
			amghret->server_list[++server_count] = NULL;

			/* 
                         * Allocate the username. If find more than one non
                         * NULL username associated with the token, return
                         * UT_AMGH_USERNAME_CONFLICT
			 */
			if (amghret->username == NULL || strlen(amghret->username) == 0) {
				amghret->username = strdup(uservalue);
                                if (amghret->username == NULL) {
					fclose(dbFile);
					ut_amgh_free_server_list(amghret);
                			amghret->errorstr = strdup(strerror(errno));
                                        return(UT_AMGH_NOMEM);
                                }
			} else {
                                if (strcasecmp(amghret->username, uservalue) != 0) {
					fclose(dbFile);
					ut_amgh_free_server_list(amghret);
                			amghret->errorstr = strdup(strerror(errno)); 
                                        return(UT_AMGH_USERNAME_CONFLICT);
			        }	
			}
		}
	}

	/* Close the server back-end DB */
	fclose(dbFile);

	return(UT_AMGH_SUCCESS);
}
