/*
 * Common PDB SQL backend functions
 * Copyright (C) Jelmer Vernooij 2003-2004
 * 
 * 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 of the License, 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 "includes.h"

#define CONFIG_TABLE_DEFAULT				"user"
#define CONFIG_LOGON_TIME_DEFAULT			"logon_time"
#define CONFIG_LOGOFF_TIME_DEFAULT			"logoff_time"
#define CONFIG_KICKOFF_TIME_DEFAULT			"kickoff_time"
#define CONFIG_PASS_LAST_SET_TIME_DEFAULT		"pass_last_set_time"
#define CONFIG_PASS_CAN_CHANGE_TIME_DEFAULT		"pass_can_change_time"
#define CONFIG_PASS_MUST_CHANGE_TIME_DEFAULT 		"pass_must_change_time"
#define CONFIG_USERNAME_DEFAULT 			"username"
#define CONFIG_DOMAIN_DEFAULT				"domain"
#define CONFIG_NT_USERNAME_DEFAULT  			"nt_username"
#define CONFIG_FULLNAME_DEFAULT				"nt_fullname"
#define CONFIG_HOME_DIR_DEFAULT				"home_dir"
#define CONFIG_DIR_DRIVE_DEFAULT			"dir_drive"
#define CONFIG_LOGON_SCRIPT_DEFAULT			"logon_script"
#define CONFIG_PROFILE_PATH_DEFAULT			"profile_path"
#define CONFIG_ACCT_DESC_DEFAULT			"acct_desc"
#define CONFIG_WORKSTATIONS_DEFAULT			"workstations"
#define CONFIG_UNKNOWN_STR_DEFAULT			"unknown_str"
#define CONFIG_MUNGED_DIAL_DEFAULT			"munged_dial"
#define CONFIG_USER_SID_DEFAULT				"user_sid"
#define CONFIG_GROUP_SID_DEFAULT			"group_sid"
#define CONFIG_LM_PW_DEFAULT				"lm_pw"
#define CONFIG_NT_PW_DEFAULT				"nt_pw"
#define CONFIG_PLAIN_PW_DEFAULT				"NULL"
#define CONFIG_ACCT_CTRL_DEFAULT			"acct_ctrl"
#define CONFIG_LOGON_DIVS_DEFAULT			"logon_divs"
#define CONFIG_HOURS_LEN_DEFAULT			"hours_len"
#define CONFIG_BAD_PASSWORD_COUNT_DEFAULT		"bad_password_count"
#define CONFIG_LOGON_COUNT_DEFAULT			"logon_count"
#define CONFIG_UNKNOWN_6_DEFAULT			"unknown_6"

/* Used to construct insert and update queries */

typedef struct pdb_sql_query {
	char update;
	char *part1;
	char *part2;
} pdb_sql_query;

static void pdb_sql_int_field(struct pdb_sql_query *q, const char *name, int value)
{
	if (!name || strchr(name, '\''))
		return;                 /* This field shouldn't be set by us */

	if (q->update) {
		q->part1 =
			talloc_asprintf_append(q->part1,
								   "%s = %d,", name, value);
	} else {
		q->part1 =
			talloc_asprintf_append(q->part1, "%s,", name);
		q->part2 =
			talloc_asprintf_append(q->part2, "%d,", value);
	}
}

char *sql_escape_string(TALLOC_CTX *mem_ctx, const char *unesc)
{
	char *esc = talloc_array(mem_ctx, char, strlen(unesc) * 2 + 3);
	size_t pos_unesc = 0, pos_esc = 0;

	for(pos_unesc = 0; unesc[pos_unesc]; pos_unesc++) {
		switch(unesc[pos_unesc]) {
		case '\\':
		case '\'':
		case '"':
			esc[pos_esc] = '\\'; pos_esc++;
		default:
			esc[pos_esc] = unesc[pos_unesc]; pos_esc++;
			break;
		}
	}

	esc[pos_esc] = '\0';
	
	return esc;
}

static NTSTATUS pdb_sql_string_field(struct pdb_sql_query *q,
					   const char *name, const char *value)
{
	char *esc_value;

	if (!name || !value || !strcmp(value, "") || strchr(name, '\''))
		return NT_STATUS_INVALID_PARAMETER;   /* This field shouldn't be set by module */

	esc_value = sql_escape_string(q, value);

	if (q->update) {
		q->part1 =
			talloc_asprintf_append(q->part1,
								   "%s = '%s',", name, esc_value);
	} else {
		q->part1 =
			talloc_asprintf_append(q->part1, "%s,", name);
		q->part2 =
			talloc_asprintf_append(q->part2, "'%s',",
								   esc_value);
	}

	talloc_free(esc_value);

	return NT_STATUS_OK;
}

#define config_value(data,name,default_value) \
	lp_parm_const_string(GLOBAL_SECTION_SNUM, data, name, default_value)

static const char * config_value_write(const char *location, const char *name, const char *default_value) 
{
	char const *v = NULL;
	char const *swrite = NULL;

	v = lp_parm_const_string(GLOBAL_SECTION_SNUM, location, name, default_value);

	if (!v)
		return NULL;

	swrite = strrchr(v, ':');

	/* Default to the same field as read field */
	if (!swrite) {

		/* Updating NULL does not make much sense */
		if (!strcmp(v, "NULL")) 
			return NULL;

		return v;
	}

	swrite++;

	/* If the field is 0 chars long, we shouldn't write to it */
	if (!strlen(swrite) || !strcmp(swrite, "NULL"))
		return NULL;

	/* Otherwise, use the additionally specified */
	return swrite;
}

static const char * config_value_read(const char *location, const char *name, const char *default_value)
{
	char *v = NULL;
	char *swrite;

	v = lp_parm_talloc_string(GLOBAL_SECTION_SNUM, location, name, default_value);

	if (!v)
		return "NULL";

	swrite = strrchr(v, ':');

	/* If no write is specified, there are no problems */
	if (!swrite) {
		if (strlen(v) == 0)
			return "NULL";
		return (const char *)v;
	}

	/* Otherwise, we have to cut the ':write_part' */
	*swrite = '\0';
	if (strlen(v) == 0)
		return "NULL";

	return (const char *)v;
}

char *sql_account_query_select(TALLOC_CTX *mem_ctx, const char *data, BOOL update, enum sql_search_field field, const char *value)
{
	const char *field_string;
	char *query;

	switch(field) {
	case SQL_SEARCH_NONE: 
		field_string = "'1'"; 
		value = "1"; 
		break;
		
	case SQL_SEARCH_USER_SID: 
		field_string = config_value_read(data, "user sid column", 
										 CONFIG_USER_SID_DEFAULT); 
		break;
		
	case SQL_SEARCH_USER_NAME: 
		field_string = config_value_read(data, "username column", 
										 CONFIG_USERNAME_DEFAULT);
		break;
	default:
		field_string = "unknown";
		break;
	}

	query = talloc_asprintf(mem_ctx,
			 "SELECT %s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s FROM %s WHERE %s = '%s'",
			 config_value_read(data, "logon time column",
							   CONFIG_LOGON_TIME_DEFAULT),
			 config_value_read(data, "logoff time column",
							   CONFIG_LOGOFF_TIME_DEFAULT),
			 config_value_read(data, "kickoff time column",
							   CONFIG_KICKOFF_TIME_DEFAULT),
			 config_value_read(data, "pass last set time column",
							   CONFIG_PASS_LAST_SET_TIME_DEFAULT),
			 config_value_read(data, "pass can change time column",
							   CONFIG_PASS_CAN_CHANGE_TIME_DEFAULT),
			 config_value_read(data, "pass must change time column",
							   CONFIG_PASS_MUST_CHANGE_TIME_DEFAULT),
			 config_value_read(data, "username column",
							   CONFIG_USERNAME_DEFAULT),
			 config_value_read(data, "domain column",
							   CONFIG_DOMAIN_DEFAULT),
			 config_value_read(data, "nt username column",
							   CONFIG_NT_USERNAME_DEFAULT),
			 config_value_read(data, "fullname column",
							   CONFIG_FULLNAME_DEFAULT),
			 config_value_read(data, "home dir column",
							   CONFIG_HOME_DIR_DEFAULT),
			 config_value_read(data, "dir drive column",
							   CONFIG_DIR_DRIVE_DEFAULT),
			 config_value_read(data, "logon script column",
							   CONFIG_LOGON_SCRIPT_DEFAULT),
			 config_value_read(data, "profile path column",
							   CONFIG_PROFILE_PATH_DEFAULT),
			 config_value_read(data, "acct desc column",
							   CONFIG_ACCT_DESC_DEFAULT),
			 config_value_read(data, "workstations column",
							   CONFIG_WORKSTATIONS_DEFAULT),
			 config_value_read(data, "unknown string column",
							   CONFIG_UNKNOWN_STR_DEFAULT),
			 config_value_read(data, "munged dial column",
							   CONFIG_MUNGED_DIAL_DEFAULT),
			 config_value_read(data, "user sid column",
							   CONFIG_USER_SID_DEFAULT),
			 config_value_read(data, "group sid column",
							   CONFIG_GROUP_SID_DEFAULT),
			 config_value_read(data, "lanman pass column",
							   CONFIG_LM_PW_DEFAULT),
			 config_value_read(data, "nt pass column",
							   CONFIG_NT_PW_DEFAULT),
			 config_value_read(data, "plain pass column",
							   CONFIG_PLAIN_PW_DEFAULT),
			 config_value_read(data, "acct ctrl column",
							   CONFIG_ACCT_CTRL_DEFAULT),
			 config_value_read(data, "logon divs column",
							   CONFIG_LOGON_DIVS_DEFAULT),
			 config_value_read(data, "hours len column",
							   CONFIG_HOURS_LEN_DEFAULT),
			 config_value_read(data, "bad password count column",
							   CONFIG_BAD_PASSWORD_COUNT_DEFAULT),
			 config_value_read(data, "logon count column",
							   CONFIG_LOGON_COUNT_DEFAULT),
			 config_value_read(data, "unknown 6 column",
							   CONFIG_UNKNOWN_6_DEFAULT),
			 config_value(data, "table", CONFIG_TABLE_DEFAULT), 
			 field_string, value
				 );
	 return query;
}

char *sql_account_query_delete(TALLOC_CTX *mem_ctx, const char *data, const char *esc) 
{
	char *query;
	
	query = talloc_asprintf(mem_ctx, "DELETE FROM %s WHERE %s = '%s'",
			 config_value(data, "table", CONFIG_TABLE_DEFAULT),
			 config_value_read(data, "username column",
							   CONFIG_USERNAME_DEFAULT), esc);
	return query;
}

char *sql_account_query_update(TALLOC_CTX *mem_ctx, const char *location, const SAM_ACCOUNT *newpwd, char isupdate)
{
	char *ret;
	pstring temp;
	fstring sid_str;
	pdb_sql_query *query;

	query = talloc(mem_ctx, pdb_sql_query);
	query->update = isupdate;

	/* I know this is somewhat overkill but only the talloc 
	 * functions have asprint_append and the 'normal' asprintf 
	 * is a GNU extension */
	query->part2 = talloc_asprintf(query, "%s", "");
	if (query->update) {
		query->part1 =
			talloc_asprintf(query, "UPDATE %s SET ",
							config_value(location, "table",
										 CONFIG_TABLE_DEFAULT));
	} else {
		query->part1 =
			talloc_asprintf(query, "INSERT INTO %s (",
							config_value(location, "table",
										 CONFIG_TABLE_DEFAULT));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_ACCTCTRL)) {
		pdb_sql_int_field(query,
						config_value_write(location, "acct ctrl column",
										   CONFIG_ACCT_CTRL_DEFAULT),
						pdb_get_acct_ctrl(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_LOGONTIME)) {
		pdb_sql_int_field(query,
							config_value_write(location,
											   "logon time column",
											   CONFIG_LOGON_TIME_DEFAULT),
							pdb_get_logon_time(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_LOGOFFTIME)) {
		pdb_sql_int_field(query,
							config_value_write(location,
											   "logoff time column",
											   CONFIG_LOGOFF_TIME_DEFAULT),
							pdb_get_logoff_time(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_KICKOFFTIME)) {
		pdb_sql_int_field(query,
							config_value_write(location,
											   "kickoff time column",
											   CONFIG_KICKOFF_TIME_DEFAULT),
							pdb_get_kickoff_time(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_CANCHANGETIME)) {
		pdb_sql_int_field(query,
							config_value_write(location,
											   "pass can change time column",
											   CONFIG_PASS_CAN_CHANGE_TIME_DEFAULT),
							pdb_get_pass_can_change_time(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_MUSTCHANGETIME)) {
		pdb_sql_int_field(query,
							config_value_write(location,
											   "pass must change time column",
											   CONFIG_PASS_MUST_CHANGE_TIME_DEFAULT),
							pdb_get_pass_must_change_time(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_PASSLASTSET)) {
		pdb_sql_int_field(query,
							config_value_write(location,
											   "pass last set time column",
											   CONFIG_PASS_LAST_SET_TIME_DEFAULT),
							pdb_get_pass_last_set_time(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_HOURSLEN)) {
		pdb_sql_int_field(query,
							config_value_write(location,
											   "hours len column",
											   CONFIG_HOURS_LEN_DEFAULT),
							pdb_get_hours_len(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_LOGONDIVS)) {
		pdb_sql_int_field(query,
							config_value_write(location,
											   "logon divs column",
											   CONFIG_LOGON_DIVS_DEFAULT),
							pdb_get_logon_divs(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_USERSID)) {
		pdb_sql_string_field(query,
						   config_value_write(location, "user sid column",
											  CONFIG_USER_SID_DEFAULT),
						   sid_to_string(sid_str, 
										 pdb_get_user_sid(newpwd)));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_GROUPSID)) {
		pdb_sql_string_field(query,
						   config_value_write(location, "group sid column",
											  CONFIG_GROUP_SID_DEFAULT),
						   sid_to_string(sid_str,
										 pdb_get_group_sid(newpwd)));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_USERNAME)) {
		pdb_sql_string_field(query,
						   config_value_write(location, "username column",
											  CONFIG_USERNAME_DEFAULT),
						   pdb_get_username(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_DOMAIN)) {
		pdb_sql_string_field(query,
						   config_value_write(location, "domain column",
											  CONFIG_DOMAIN_DEFAULT),
						   pdb_get_domain(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_USERNAME)) {
		pdb_sql_string_field(query,
						   config_value_write(location,
											  "nt username column",
											  CONFIG_NT_USERNAME_DEFAULT),
						   pdb_get_nt_username(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_FULLNAME)) {
		pdb_sql_string_field(query,
						   config_value_write(location, "fullname column",
											  CONFIG_FULLNAME_DEFAULT),
						   pdb_get_fullname(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_LOGONSCRIPT)) {
		pdb_sql_string_field(query,
						   config_value_write(location,
											  "logon script column",
											  CONFIG_LOGON_SCRIPT_DEFAULT),
						   pdb_get_logon_script(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_PROFILE)) {
		pdb_sql_string_field(query,
						   config_value_write(location,
											  "profile path column",
											  CONFIG_PROFILE_PATH_DEFAULT),
						   pdb_get_profile_path(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_DRIVE)) {
		pdb_sql_string_field(query,
						   config_value_write(location, "dir drive column",
											  CONFIG_DIR_DRIVE_DEFAULT),
						   pdb_get_dir_drive(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_SMBHOME)) {
		pdb_sql_string_field(query,
						   config_value_write(location, "home dir column",
											  CONFIG_HOME_DIR_DEFAULT),
						   pdb_get_homedir(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_WORKSTATIONS)) {
		pdb_sql_string_field(query,
						   config_value_write(location,
											  "workstations column",
											  CONFIG_WORKSTATIONS_DEFAULT),
						   pdb_get_workstations(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_UNKNOWNSTR)) {
		pdb_sql_string_field(query,
						   config_value_write(location,
											  "unknown string column",
											  CONFIG_UNKNOWN_STR_DEFAULT),
						   pdb_get_workstations(newpwd));
	}

	if (IS_SAM_CHANGED(newpwd, PDB_LMPASSWD)) {
		pdb_sethexpwd(temp, pdb_get_lanman_passwd(newpwd),
					  pdb_get_acct_ctrl(newpwd));
		pdb_sql_string_field(query,
						   config_value_write(location,
											  "lanman pass column",
											  CONFIG_LM_PW_DEFAULT), temp);
	}

	if (IS_SAM_CHANGED(newpwd, PDB_NTPASSWD)) {
		pdb_sethexpwd(temp, pdb_get_nt_passwd(newpwd),
					  pdb_get_acct_ctrl(newpwd));
		pdb_sql_string_field(query,
						   config_value_write(location, "nt pass column",
											  CONFIG_NT_PW_DEFAULT), temp);
	}

	if (query->update) {
		query->part1[strlen(query->part1) - 1] = '\0';
		query->part1 =
			talloc_asprintf_append(query->part1,
								   " WHERE %s = '%s'",
								   config_value_read(location,
													 "user sid column",
													 CONFIG_USER_SID_DEFAULT),
								   sid_to_string(sid_str, pdb_get_user_sid (newpwd)));
	} else {
		query->part2[strlen(query->part2) - 1] = ')';
		query->part1[strlen(query->part1) - 1] = ')';
		query->part1 =
			talloc_asprintf_append(query->part1,
								   " VALUES (%s", query->part2);
	}

	ret = talloc_strdup(mem_ctx, query->part1);
	talloc_free(query);
	return ret;
}

BOOL sql_account_config_valid(const char *data)
{
	const char *sid_column, *username_column;
	
    sid_column = config_value_read(data, "user sid column", CONFIG_USER_SID_DEFAULT);
    username_column = config_value_read(data, "username column", CONFIG_USERNAME_DEFAULT);
	
    if(!strcmp(sid_column,"NULL") || !strcmp(username_column, "NULL")) {
        DEBUG(0,("Please specify both a valid 'user sid column' and a valid 'username column' in smb.conf\n"));
        return False;
    }

	return True;
}