/* Copyright (C) 1999 Beau Kuiper

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "config.h"
#include "auth.h"
#include <string.h>
#include <sys/time.h>
#include <pwd.h>
#include <msql.h>

/* This file contains code to autheticate against msql servers */

#define ENCRYPTED_NONE 0
#define ENCRYPTED_CRYPT 1

typedef struct 
{
	char *password;
	char *homedir;
	char *rootdir;
	int uid;
	int gid;
	int encrypted;
} PASSWDSTRUCT;

void *gethandle(void *peer, void *tset, char *username, int *err)
{
	int sqlhandle = -1;
	m_result *result = NULL;
	m_row row = NULL;
	PASSWDSTRUCT *newhandle;
	int section;
	char *sqlserver;
	char *sqlencryption;
	char *sqldatabase;
	int sqlport;
	char *sqltable;
	char *sqlquery, *query = NULL;

	section = auth_getcursectionid(peer);
	newhandle = (PASSWDSTRUCT *)mallocwrapper(sizeof(PASSWDSTRUCT));

	/* read info from config file */
	sqlserver = mktokconfstr(tset, section, "msql_host", NULL);
	sqldatabase = mktokconfstr(tset, section, "msql_database", NULL);
	sqltable = mktokconfstr(tset, section, "msql_table", "users");
	sqlport = mktokconfint(tset, section, "msql_port", "%d", NULL, 0);
	sqlencryption = mktokconfstr(tset, section, "msql_encryption", "crypt");
	sqlquery = mktokconfstr(tset, section, "msql_query", NULL);

	if (!sqldatabase)
	{
		log_addentry(MYLOG_INFO, NULL, "libauthmsql REQUIRES msql_database to connect to sql server");
		goto error;
	}

	if (strcasecmp(sqlencryption, "plaintext") == 0)
		newhandle->encrypted = ENCRYPTED_NONE;
	else if (strcasecmp(sqlencryption, "crypt") == 0)
		newhandle->encrypted = ENCRYPTED_CRYPT;
	else
	{
		log_addentry(MYLOG_INFO, NULL, "libauthmsql REQUIRES msql_encryption to be set to either plaintext or crypt");
		goto error;
	}

	if (!sqlquery)
		query = safe_snprintf("SELECT password,homedir,rootdir,uid,gid FROM %s WHERE username='%s'", sqltable, username);
	else
		query = strdupwrapper(sqlquery);

	/* connect to sql server */

	if ((sqlhandle = msqlConnect(sqlserver)) == -1)
	{
		log_giveentry(MYLOG_INFO, NULL, 
			      safe_snprintf("libauthmsql could not connect to SQL server: %s", msqlErrMsg));
		goto error;	
	}

	if (msqlSelectDB(sqlhandle, sqldatabase) == -1)
	{
		log_giveentry(MYLOG_INFO, NULL, 
			      safe_snprintf("libauthmsql could not connect to SQL database '%s': %s", sqldatabase, msqlErrMsg));
		goto error;	
	}
	
	/* perform sql query */
	
	if (msqlQuery(sqlhandle, query) == -1)
	{
		log_giveentry(MYLOG_INFO, NULL,
			      safe_snprintf("libauthmsql could not perform query on SQL server: %s", msqlErrMsg));
		goto error;
	}

	/* get result data */
	
	result = msqlStoreResult();
	
	if (msqlNumFields(result) != 5)
	{
		log_addentry(MYLOG_INFO, NULL,
			     "libauthmsql, msql_query setting incorrectly set");
		goto error;
	}
	
	/* if we got no rows, then the user does not exist */
	
	if (msqlNumRows(result) == 0)
	{
		freewrapper(newhandle);
		newhandle = NULL;
		*err = AUTH_USERNKNOW;
		goto cleanup;
	}
	
	if (msqlNumRows(result) > 1)
	{
		log_giveentry(MYLOG_INFO, NULL,
			safe_snprintf("libauthmsql got more than 1 result for query on user '%s'", username));
		goto error;
	}
	
	/* decode row */
	
	row = msqlFetchRow(result);

	if (!row[0] || !row[1] || !row[2] || !row[3] || !row[4])
	{
		log_giveentry(MYLOG_INFO, NULL,
			safe_snprintf("libauthmsql found NULL values in SQL result for user '%s'", username));
		goto error;
	}
	
	if (sscanf(row[3], "%d", &(newhandle->uid)) != 1)
	{
		log_giveentry(MYLOG_INFO, NULL,
			safe_snprintf("libauthmsql could not decode uid of user '%s'", username)); 
		goto error;
	}	

	if (sscanf(row[4], "%d", &(newhandle->gid)) != 1)
	{
		log_giveentry(MYLOG_INFO, NULL,
			safe_snprintf("libauthmsql could not decode gid of user '%s'", username)); 
		goto error;
	}	

	newhandle->password = strdupwrapper(row[0]);
	newhandle->homedir = strdupwrapper(row[1]);

	newhandle->rootdir = strdupwrapper(row[2]);

	/* we are FINALLY DONE!, clean up and go home! */
	
	*err = AUTH_OK;
	goto cleanup;
	
error:
	*err = AUTH_ERROR;
	freewrapper(newhandle);
	newhandle = NULL;
	
cleanup:
	if (query) freewrapper(query);
	if (result) msqlFreeResult(result);
	if (sqlhandle != -1) msqlClose(sqlhandle);

	if (sqlserver) freewrapper(sqlserver);
	if (sqldatabase) freewrapper(sqldatabase);
	freewrapper(sqltable);
	freewrapper(sqlencryption);
	if (sqlquery) freewrapper(sqlquery);
	
	return(newhandle);	
}

void freehandle(void *handle)
{
	PASSWDSTRUCT *h = (PASSWDSTRUCT *)handle;
	size_t sl = strlen(h->password);

	/* destroy record of password */
	memset(h->password, 0, sl);
	freewrapper(h->password);
	freewrapper(h->homedir);
	freewrapper(h->rootdir);
	freewrapper(h);
}

int chkpasswd(void *handle, char *password, char **errmsg)
{
	PASSWDSTRUCT *h = (PASSWDSTRUCT *)handle;

	switch(h->encrypted)
	{
		case ENCRYPTED_NONE:
			return(strcmp(password, h->password) == 0);
		case ENCRYPTED_CRYPT:
			return(chkpassword(h->password, password));
		default:
			return(FALSE);
	}

	return(FALSE);
}

char *gethomedir(void *h)
{
	return(((PASSWDSTRUCT *)h)->homedir);
}

char *getrootdir(void *h)
{
	return(((PASSWDSTRUCT *)h)->rootdir);
}

uid_t getuseruid(void *h)
{
	return(((PASSWDSTRUCT *)h)->uid);
}

gid_t getusergid(void *h)
{
	return(((PASSWDSTRUCT *)h)->gid);
}
