/* 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 <mysql.h>

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

#define ENCRYPTED_NONE 0
#define ENCRYPTED_MYSQL 1
#define ENCRYPTED_CRYPT 2

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)
{
	MYSQL *sql = NULL;
	MYSQL_RES *result = NULL;
	MYSQL_ROW row = NULL;
	PASSWDSTRUCT *newhandle;
	int section;
	char *sqlserver;
	char *sqlencryption;
	char *sqlusername;
	char *sqlpassword;
	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, "mysql_host", "localhost");
	sqlusername = mktokconfstr(tset, section, "mysql_user", NULL);
	sqlpassword = mktokconfstr(tset, section, "mysql_password", NULL);
	sqldatabase = mktokconfstr(tset, section, "mysql_database", NULL);
	sqltable = mktokconfstr(tset, section, "mysql_table", "users");
	sqlport = mktokconfint(tset, section, "mysql_port", "%d", NULL, 0);
	sqlencryption = mktokconfstr(tset, section, "mysql_encryption", "mysql");
	sqlquery = mktokconfstr(tset, section, "mysql_query", NULL);

	if (!sqlusername)
	{
		log_addentry(MYLOG_INFO, NULL, "libauthmysql REQUIRES mysql_user to connect to sql server");
		goto error;
	}
				
	if (!sqlpassword)
	{
		log_addentry(MYLOG_INFO, NULL, "libauthmysql REQUIRES mysql_password to connect to sql server");
		goto error;
	}

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

	if (strcasecmp(sqlencryption, "mysql") == 0)
		newhandle->encrypted = ENCRYPTED_MYSQL;
	else 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, "libauthmysql REQUIRES mysql_encryption to be set to either plaintext, mysql, 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 */
	
	sql = mysql_init(NULL);
	if (!mysql_real_connect(sql, sqlserver, sqlusername, sqlpassword,
			        sqldatabase, sqlport, NULL, 0))
	{
		log_giveentry(MYLOG_INFO, NULL, 
			      safe_snprintf("libauthmysql could not connect to SQL server: %s", mysql_error(sql)));	      
		goto error;	
	}
	
	/* perform sql query */
	
	if (mysql_query(sql, query) != 0)
	{
		log_giveentry(MYLOG_INFO, NULL,
			      safe_snprintf("libauthmysql could not perform query on SQL server: %s", mysql_error(sql)));
		goto error;
	}

	if (mysql_field_count(sql) != 5)
	{
		log_addentry(MYLOG_INFO, NULL,
			     "libauthmysql, mysql_query setting incorrectly set");
		goto error;
	}
	/* get result data */
	
	if ((result = mysql_store_result(sql)) == NULL)
	{
		log_giveentry(MYLOG_INFO, NULL,
			      safe_snprintf("libauthmysql could not get results from SQL query: %s", mysql_error(sql)));
		goto error;
	}
	
	/* if we got no rows, then the user does not exist */
	
	if (mysql_num_rows(result) == 0)
	{
		freewrapper(newhandle);
		newhandle = NULL;
		*err = AUTH_USERNKNOW;
		goto cleanup;
	}
	
	if (mysql_num_rows(result) > 1)
	{
		log_giveentry(MYLOG_INFO, NULL,
			safe_snprintf("libauthmysql got more than 1 result for query on user '%s'", username));
		goto error;
	}
	
	/* decode row */
	
	row = mysql_fetch_row(result);

	if (!row[0] || !row[1] || !row[2] || !row[3] || !row[4])
	{
		log_giveentry(MYLOG_INFO, NULL,
			safe_snprintf("libauthmysql 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("libauthmysql 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("libauthmysql 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) mysql_free_result(result);
	if (sql) mysql_close(sql);

	freewrapper(sqlserver);
	if (sqlusername) freewrapper(sqlusername);
	if (sqlpassword) freewrapper(sqlpassword);
	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;
	char pass2[1024];

	switch(h->encrypted)
	{
		case ENCRYPTED_NONE:
			return(strcmp(password, h->password) == 0);
		case ENCRYPTED_CRYPT:
			return(chkpassword(h->password, password));
		case ENCRYPTED_MYSQL:
			make_scrambled_password(pass2, password);
			return(strcmp(h->password, pass2) == 0);
		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);
}
