/* FreeTDS - Library of routines accessing Sybase and Microsoft databases
 * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005  Brian Bruns
 * Copyright (C) 2005-2015  Ziglio Frediano
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include <config.h>

#include <stdarg.h>
#include <stdio.h>

#if HAVE_STDLIB_H
#include <stdlib.h>
#endif /* HAVE_STDLIB_H */

#include <assert.h>

#if HAVE_STRING_H
#include <string.h>
#endif /* HAVE_STRING_H */

#if HAVE_UNISTD_H
#include <unistd.h>
#endif /* HAVE_UNISTD_H */

#ifdef _WIN32
#include <process.h>
#endif

#include <freetds/tds.h>
#include <freetds/iconv.h>
#include <freetds/utils/string.h>
#include <freetds/bytes.h>
#include <freetds/tls.h>
#include <freetds/stream.h>
#include <freetds/checks.h>
#include "replacements.h"

static TDSRET tds_send_login(TDSSOCKET * tds, const TDSLOGIN * login);
static TDSRET tds71_do_login(TDSSOCKET * tds, TDSLOGIN * login);
static TDSRET tds7_send_login(TDSSOCKET * tds, const TDSLOGIN * login);
static void tds7_crypt_pass(const unsigned char *clear_pass,
			    size_t len, unsigned char *crypt_pass);

#undef MIN
#define MIN(a,b) (((a) < (b)) ? (a) : (b))

void
tds_set_version(TDSLOGIN * tds_login, TDS_TINYINT major_ver, TDS_TINYINT minor_ver)
{
	tds_login->tds_version = ((TDS_USMALLINT) major_ver << 8) + minor_ver;
}

void
tds_set_packet(TDSLOGIN * tds_login, int packet_size)
{
	tds_login->block_size = packet_size;
}

void
tds_set_port(TDSLOGIN * tds_login, int port)
{
	tds_login->port = port;
}

bool
tds_set_passwd(TDSLOGIN * tds_login, const char *password)
{
	if (password) {
		tds_dstr_zero(&tds_login->password);
		return !!tds_dstr_copy(&tds_login->password, password);
	}
	return true;
}
void
tds_set_bulk(TDSLOGIN * tds_login, bool enabled)
{
	tds_login->bulk_copy = enabled ? 1 : 0;
}

bool
tds_set_user(TDSLOGIN * tds_login, const char *username)
{
	return !!tds_dstr_copy(&tds_login->user_name, username);
}

bool
tds_set_host(TDSLOGIN * tds_login, const char *hostname)
{
	return !!tds_dstr_copy(&tds_login->client_host_name, hostname);
}

bool
tds_set_app(TDSLOGIN * tds_login, const char *application)
{
	return !!tds_dstr_copy(&tds_login->app_name, application);
}

/**
 * \brief Set the servername in a TDSLOGIN structure
 *
 * Normally copies \a server into \a tds_login.  If \a server does not point to a plausible name, the environment 
 * variables TDSQUERY and DSQUERY are used, in that order.  If they don't exist, the "default default" servername
 * is "SYBASE" (although the utility of that choice is a bit murky).  
 *
 * \param tds_login	points to a TDSLOGIN structure
 * \param server	the servername, or NULL, or a zero-length string
 * \todo open the log file earlier, so these messages can be seen.  
 */
bool
tds_set_server(TDSLOGIN * tds_login, const char *server)
{
#if 0
	/* Doing this in tds_alloc_login instead */
	static const char *names[] = { "TDSQUERY", "DSQUERY", "SYBASE" };
	int i;
	
	for (i=0; i < TDS_VECTOR_SIZE(names) && (!server || strlen(server) == 0); i++) {
		const char *source;
		if (i + 1 == TDS_VECTOR_SIZE(names)) {
			server = names[i];
			source = "compiled-in default";
		} else {
			server = getenv(names[i]);
			source = names[i];
		}
		if (server) {
			tdsdump_log(TDS_DBG_INFO1, "Setting TDSLOGIN::server_name to '%s' from %s.\n", server, source);
		}
	}
#endif
	if (server)
		return !!tds_dstr_copy(&tds_login->server_name, server);
	return true;
}

bool
tds_set_library(TDSLOGIN * tds_login, const char *library)
{
	return !!tds_dstr_copy(&tds_login->library, library);
}

bool
tds_set_client_charset(TDSLOGIN * tds_login, const char *charset)
{
	return !!tds_dstr_copy(&tds_login->client_charset, charset);
}

bool
tds_set_language(TDSLOGIN * tds_login, const char *language)
{
	return !!tds_dstr_copy(&tds_login->language, language);
}

struct tds_save_msg
{
	TDSMESSAGE msg;
	char type;
};

struct tds_save_env
{
	char *oldval;
	char *newval;
	int type;
};

typedef struct tds_save_context
{
	/* must be first !!! */
	TDSCONTEXT ctx;

	unsigned num_msg;
	struct tds_save_msg msgs[10];

	unsigned num_env;
	struct tds_save_env envs[10];
} TDSSAVECONTEXT;

static void
tds_save(TDSSAVECONTEXT *ctx, char type, TDSMESSAGE *msg)
{
	struct tds_save_msg *dest_msg;

	if (ctx->num_msg >= TDS_VECTOR_SIZE(ctx->msgs))
		return;

	dest_msg = &ctx->msgs[ctx->num_msg];
	dest_msg->type = type;
	dest_msg->msg = *msg;
#define COPY(name) if (msg->name) dest_msg->msg.name = strdup(msg->name);
	COPY(server);
	COPY(message);
	COPY(proc_name);
	COPY(sql_state);
#undef COPY
	++ctx->num_msg;
}

static int
tds_save_msg(const TDSCONTEXT *ctx, TDSSOCKET *tds, TDSMESSAGE *msg)
{
	tds_save((TDSSAVECONTEXT *) ctx, 0, msg);
	return 0;
}

static int
tds_save_err(const TDSCONTEXT *ctx, TDSSOCKET *tds, TDSMESSAGE *msg)
{
	tds_save((TDSSAVECONTEXT *) ctx, 1, msg);
	return TDS_INT_CANCEL;
}

static void
tds_save_env(TDSSOCKET * tds, int type, char *oldval, char *newval)
{
	TDSSAVECONTEXT *ctx;
	struct tds_save_env *env;

	if (tds_get_ctx(tds)->msg_handler != tds_save_msg)
		return;

	ctx = (TDSSAVECONTEXT *) tds_get_ctx(tds);
	if (ctx->num_env >= TDS_VECTOR_SIZE(ctx->envs))
		return;

	env = &ctx->envs[ctx->num_env];
	env->type = type;
	env->oldval = oldval ? strdup(oldval) : NULL;
	env->newval = newval ? strdup(newval) : NULL;
	++ctx->num_env;
}

static void
init_save_context(TDSSAVECONTEXT *ctx, const TDSCONTEXT *old_ctx)
{
	memset(ctx, 0, sizeof(*ctx));
	ctx->ctx.locale = old_ctx->locale;
	ctx->ctx.msg_handler = tds_save_msg;
	ctx->ctx.err_handler = tds_save_err;
}

static void
replay_save_context(TDSSOCKET *tds, TDSSAVECONTEXT *ctx)
{
	unsigned n;

	/* replay all recorded messages */
	for (n = 0; n < ctx->num_msg; ++n)
		if (ctx->msgs[n].type == 0) {
			if (tds_get_ctx(tds)->msg_handler)
				tds_get_ctx(tds)->msg_handler(tds_get_ctx(tds), tds, &ctx->msgs[n].msg);
		} else {
			if (tds_get_ctx(tds)->err_handler)
				tds_get_ctx(tds)->err_handler(tds_get_ctx(tds), tds, &ctx->msgs[n].msg);
		}

	/* replay all recorded envs */
	for (n = 0; n < ctx->num_env; ++n)
		if (tds->env_chg_func)
			tds->env_chg_func(tds, ctx->envs[n].type, ctx->envs[n].oldval, ctx->envs[n].newval);
}

static void
reset_save_context(TDSSAVECONTEXT *ctx)
{
	unsigned n;

	/* free all messages */
	for (n = 0; n < ctx->num_msg; ++n)
		tds_free_msg(&ctx->msgs[n].msg);
	ctx->num_msg = 0;

	/* free all envs */
	for (n = 0; n < ctx->num_env; ++n) {
		free(ctx->envs[n].oldval);
		free(ctx->envs[n].newval);
	}
	ctx->num_env = 0;
}

static void
free_save_context(TDSSAVECONTEXT *ctx)
{
	reset_save_context(ctx);
}

/**
 * Retrieve and set @@spid
 * \tds
 */
static TDSRET
tds_set_spid(TDSSOCKET * tds)
{
	TDS_INT result_type;
	TDS_INT done_flags;
	TDSRET rc;
	TDSCOLUMN *curcol;

	CHECK_TDS_EXTRA(tds);

	while ((rc = tds_process_tokens(tds, &result_type, &done_flags, TDS_RETURN_ROW|TDS_RETURN_DONE)) == TDS_SUCCESS) {

		switch (result_type) {
		case TDS_ROW_RESULT:
			if (!tds->res_info)
				return TDS_FAIL;
			if (tds->res_info->num_cols != 1)
				break;
			curcol = tds->res_info->columns[0];
			switch (tds_get_conversion_type(curcol->column_type, curcol->column_size)) {
			case SYBINT2:
				tds->conn->spid = *((TDS_USMALLINT *) curcol->column_data);
				break;
			case SYBINT4:
				tds->conn->spid = *((TDS_UINT *) curcol->column_data);
				break;
			default:
				return TDS_FAIL;
			}
			break;

		case TDS_DONE_RESULT:
			if ((done_flags & TDS_DONE_ERROR) != 0)
				return TDS_FAIL;
			break;
		}
	}
	if (rc == TDS_NO_MORE_RESULTS)
		rc = TDS_SUCCESS;

	return rc;
}

static TDSRET
tds_setup_connection(TDSSOCKET *tds, TDSLOGIN *login, bool set_db, bool set_spid)
{
	TDSRET erc;
	char *str;
	int len;

	len = 128 + tds_quote_id(tds, NULL, tds_dstr_cstr(&login->database),-1);
	if ((str = tds_new(char, len)) == NULL)
		return TDS_FAIL;

	str[0] = 0;
	if (login->text_size) {
		sprintf(str, "set textsize %d ", login->text_size);
	}
	if (set_spid && tds->conn->spid == -1) {
		strcat(str, "select @@spid ");
	}
	/* Select proper database if specified.
	 * SQL Anywhere does not support multiple databases and USE statement
	 * so don't send the request to avoid connection failures */
	if (set_db && !tds_dstr_isempty(&login->database) &&
	    (tds->conn->product_name == NULL || strcasecmp(tds->conn->product_name, "SQL Anywhere") != 0)) {
		strcat(str, "use ");
		tds_quote_id(tds, strchr(str, 0), tds_dstr_cstr(&login->database), -1);
	}

	/* nothing to set, just return */
	if (str[0] == 0) {
		free(str);
		return TDS_SUCCESS;
	}

	erc = tds_submit_query(tds, str);
	free(str);
	if (TDS_FAILED(erc))
		return erc;

	if (set_spid && tds->conn->spid == -1)
		erc = tds_set_spid(tds);
	else
		erc = tds_process_simple_query(tds);

	return erc;
}

/**
 * Do a connection to socket
 * @param tds connection structure. This should be a non-connected connection.
 * @return TDS_FAIL or TDS_SUCCESS if a connection was made to the server's port.
 * @return TDSERROR enumerated type if no TCP/IP connection could be formed. 
 * @param login info for login
 * @remark Possible error conditions:
 *		- TDSESOCK: socket(2) failed: insufficient local resources
 * 		- TDSECONN: connect(2) failed: invalid hostname or port (ETIMEDOUT, ECONNREFUSED, ENETUNREACH)
 * 		- TDSEFCON: connect(2) succeeded, login packet not acknowledged.  
 *		- TDS_FAIL: connect(2) succeeded, login failed.  
 */
static int
tds_connect(TDSSOCKET * tds, TDSLOGIN * login, int *p_oserr)
{
	int erc = -TDSEFCON;
	int connect_timeout = 0;
	bool db_selected = false;
	struct addrinfo *addrs;
	int orig_port;
	bool rerouted = false;
	/* save to restore during redirected connection */
	unsigned int orig_mars = login->mars;

	/*
	 * A major version of 0 means try to guess the TDS version. 
	 * We try them in an order that should work. 
	 */
	const static TDS_USMALLINT versions[] =
		{ 0x704
		, 0x500
		};

	if (!login->valid_configuration) {
		tdserror(tds_get_ctx(tds), tds, TDSECONF, 0);
		return TDS_FAIL;
	}

	if (TDS_MAJOR(login) == 0) {
		unsigned int i;
		TDSSAVECONTEXT save_ctx;
		const TDSCONTEXT *old_ctx = tds_get_ctx(tds);
		typedef void (*env_chg_func_t) (TDSSOCKET * tds, int type, char *oldval, char *newval);
		env_chg_func_t old_env_chg = tds->env_chg_func;
		/* the context of a socket is const; we have to modify it to suppress error messages during multiple tries. */
		TDSCONTEXT *mod_ctx = (TDSCONTEXT *) tds_get_ctx(tds);
		err_handler_t err_handler = tds_get_ctx(tds)->err_handler;

		init_save_context(&save_ctx, old_ctx);
		tds_set_ctx(tds, &save_ctx.ctx);
		tds->env_chg_func = tds_save_env;
		mod_ctx->err_handler = NULL;

		for (i = 0; i < TDS_VECTOR_SIZE(versions); ++i) {
			int orig_size = tds->conn->env.block_size;
			login->tds_version = versions[i];
			reset_save_context(&save_ctx);

			erc = tds_connect(tds, login, p_oserr);
			if (TDS_FAILED(erc)) {
				tds_close_socket(tds);
				if (tds->conn->env.block_size != orig_size)
					tds_realloc_socket(tds, orig_size);
			}
			
			if (erc != -TDSEFCON)	/* TDSEFCON indicates wrong TDS version */
				break;
			if (login->server_is_valid)
				break;
		}
		
		mod_ctx->err_handler = err_handler;
		tds->env_chg_func = old_env_chg;
		tds_set_ctx(tds, old_ctx);
		replay_save_context(tds, &save_ctx);
		free_save_context(&save_ctx);
		
		if (TDS_FAILED(erc))
			tdserror(tds_get_ctx(tds), tds, -erc, *p_oserr);

		return erc;
	}
	

	/*
	 * If a dump file has been specified, start logging
	 */
	if (!tds_dstr_isempty(&login->dump_file) && !tdsdump_isopen()) {
		if (login->debug_flags)
			tds_debug_flags = login->debug_flags;
		tdsdump_open(tds_dstr_cstr(&login->dump_file));
	}

	tds->login = login;

	tds->conn->tds_version = login->tds_version;

	/* set up iconv if not already initialized*/
	if (tds->conn->char_convs[client2ucs2]->to.cd == (iconv_t) -1) {
		if (!tds_dstr_isempty(&login->client_charset)) {
			if (TDS_FAILED(tds_iconv_open(tds->conn, tds_dstr_cstr(&login->client_charset), login->use_utf16)))
				return -TDSEMEM;
		}
	}

	connect_timeout = login->connect_timeout;

	/* Jeff's hack - begin */
	tds->query_timeout = connect_timeout ? connect_timeout : login->query_timeout;
	/* end */

	/* verify that ip_addr is not empty */
	if (login->ip_addrs == NULL) {
		tdserror(tds_get_ctx(tds), tds, TDSEUHST, 0 );
		tdsdump_log(TDS_DBG_ERROR, "IP address pointer is empty\n");
		if (!tds_dstr_isempty(&login->server_name)) {
			tdsdump_log(TDS_DBG_ERROR, "Server %s not found!\n", tds_dstr_cstr(&login->server_name));
		} else {
			tdsdump_log(TDS_DBG_ERROR, "No server specified!\n");
		}
		return -TDSECONN;
	}

	tds->conn->capabilities = login->capabilities;

reroute:
	erc = TDSEINTF;
	orig_port = login->port;
	for (addrs = login->ip_addrs; addrs != NULL; addrs = addrs->ai_next) {
		login->port = orig_port;

		if (!IS_TDS50(tds->conn) && !tds_dstr_isempty(&login->instance_name) && !login->port)
			login->port = tds7_get_instance_port(addrs, tds_dstr_cstr(&login->instance_name));

		if (login->port >= 1) {
			if ((erc = tds_open_socket(tds, addrs, login->port, connect_timeout, p_oserr)) == TDSEOK)
				break;
		} else {
			erc = TDSECONN;
		}
	}

	if (erc != TDSEOK) {
		if (login->port < 1)
			tdsdump_log(TDS_DBG_ERROR, "invalid port number\n");

		tdserror(tds_get_ctx(tds), tds, erc, *p_oserr);
		return -erc;
	}
		
	/*
	 * Beyond this point, we're connected to the server.  We know we have a valid TCP/IP address+socket pair.  
	 * Although network errors *might* happen, most problems from here on out will be TDS-level errors, 
	 * either TDS version problems or authentication problems.  
	 */
		
	tds_set_state(tds, TDS_IDLE);
	tds->conn->spid = -1;

	/* discard possible previous authentication */
	if (tds->conn->authentication) {
		tds->conn->authentication->free(tds->conn, tds->conn->authentication);
		tds->conn->authentication = NULL;
	}

	if (IS_TDS71_PLUS(tds->conn)) {
		erc = tds71_do_login(tds, login);
		db_selected = true;
	} else if (IS_TDS7_PLUS(tds->conn)) {
		erc = tds7_send_login(tds, login);
		db_selected = true;
	} else {
		tds->out_flag = TDS_LOGIN;
		erc = tds_send_login(tds, login);
	}
	if (TDS_FAILED(erc) || TDS_FAILED(tds_process_login_tokens(tds))) {
		tdsdump_log(TDS_DBG_ERROR, "login packet %s\n", TDS_SUCCEED(erc)? "accepted":"rejected");
		tds_close_socket(tds);
		tdserror(tds_get_ctx(tds), tds, TDSEFCON, 0); 	/* "Adaptive Server connection failed" */
		return -TDSEFCON;
	}

	/* need to do rerouting */
	if (IS_TDS71_PLUS(tds->conn)
	    && !tds_dstr_isempty(&login->routing_address) && login->routing_port) {
		TDSRET ret;
		char *server_name = NULL;

		tds_close_socket(tds);
		/* only one redirection is allowed */
		if (rerouted) {
			tdserror(tds_get_ctx(tds), tds, TDSEFCON, 0);
			return -TDSEFCON;
		}
		if (asprintf(&server_name, "%s,%d", tds_dstr_cstr(&login->routing_address), login->routing_port) < 0) {
			tdserror(tds_get_ctx(tds), tds, TDSEFCON, 0);
			return -TDSEMEM;
		}
		if (!tds_dstr_set(&login->server_name, server_name)) {
			free(server_name);
			tdserror(tds_get_ctx(tds), tds, TDSEFCON, 0);
			return -TDSEMEM;
		}
		login->mars = orig_mars;
		login->port = login->routing_port;
		ret = tds_lookup_host_set(tds_dstr_cstr(&login->routing_address), &login->ip_addrs);
		login->routing_port = 0;
		tds_dstr_free(&login->routing_address);
		if (TDS_FAILED(ret)) {
			tdserror(tds_get_ctx(tds), tds, TDSEFCON, 0);
			return -TDSEFCON;
		}
		rerouted = true;
		goto reroute;
	}

#if ENABLE_ODBC_MARS
	/* initialize SID */
	if (IS_TDS72_PLUS(tds->conn) && login->mars) {
		TDS72_SMP_HEADER *p;

		tds_extra_assert(tds->sid == 0);
		tds_extra_assert(tds->conn->sessions[0] == tds);
		tds_extra_assert(tds->send_packet != NULL);
		tds_extra_assert(!tds->send_packet->next);

		tds->conn->mars = 1;

		/* start session with a SMP SYN */
		if (TDS_FAILED(tds_append_syn(tds)))
			return -TDSEMEM;

		/* reallocate send_packet */
		if (!tds_realloc_socket(tds, tds->out_buf_max))
			return -TDSEMEM;

		/* start SMP DATA header */
		p = (TDS72_SMP_HEADER *) tds->send_packet->buf;
		p->signature = TDS72_SMP;
		p->type = TDS_SMP_DATA;

		tds_init_write_buf(tds);
	}
#endif

	erc = tds_setup_connection(tds, login, !db_selected, true);
	/* try without asking @@spid, some servers do not support it */
	if (TDS_FAILED(erc) && tds->conn->spid == -1)
		erc = tds_setup_connection(tds, login, !db_selected, false);
	if (TDS_FAILED(erc))
		return erc;

	tds->query_timeout = login->query_timeout;
	tds->login = NULL;
	return TDS_SUCCESS;
}

int
tds_connect_and_login(TDSSOCKET * tds, TDSLOGIN * login)
{
	int oserr = 0;
	return tds_connect(tds, login, &oserr);
}

static int
tds_put_login_string(TDSSOCKET * tds, const char *buf, int n)
{
	const int buf_len = buf ? (int)strlen(buf) : 0;
	return tds_put_buf(tds, (const unsigned char *) buf, n, buf_len);
}

static TDSRET
tds_send_login(TDSSOCKET * tds, const TDSLOGIN * login)
{
	static const unsigned char le1[] = { 0x03, 0x01, 0x06, 0x0a, 0x09, 0x01 };
	static const unsigned char le2[] = { 0x00, 13, 17 };

	/*
	 * capabilities are now part of the tds structure.
	 * unsigned char capabilities[]= {0x01,0x07,0x03,109,127,0xFF,0xFF,0xFF,0xFE,0x02,0x07,0x00,0x00,0x0A,104,0x00,0x00,0x00};
	 */
	/*
	 * This is the original capabilities packet we were working with (sqsh)
	 * unsigned char capabilities[]= {0x01,0x07,0x03,109,127,0xFF,0xFF,0xFF,0xFE,0x02,0x07,0x00,0x00,0x0A,104,0x00,0x00,0x00};
	 * original with 4.x messages
	 * unsigned char capabilities[]= {0x01,0x07,0x03,109,127,0xFF,0xFF,0xFF,0xFE,0x02,0x07,0x00,0x00,0x00,120,192,0x00,0x0D};
	 * This is isql 11.0.3
	 * unsigned char capabilities[]= {0x01,0x07,0x00,96, 129,207, 0xFF,0xFE,62,  0x02,0x07,0x00,0x00,0x00,120,192,0x00,0x0D};
	 * like isql but with 5.0 messages
	 * unsigned char capabilities[]= {0x01,0x07,0x00,96, 129,207, 0xFF,0xFE,62,  0x02,0x07,0x00,0x00,0x00,120,192,0x00,0x00};
	 */

	unsigned char protocol_version[4];
	unsigned char program_version[4];
	unsigned char sec_flags = 0;
	bool use_kerberos = false;

	int len;
	char blockstr[16];

	TDS_TINYINT encryption_level = login->encryption_level;

	/* override lservname field for ASA servers */	
	const char *lservname = getenv("ASA_DATABASE")? getenv("ASA_DATABASE") : tds_dstr_cstr(&login->server_name);

	if (strchr(tds_dstr_cstr(&login->user_name), '\\') != NULL) {
		tdsdump_log(TDS_DBG_ERROR, "NT login not supported using TDS 4.x or 5.0\n");
		return TDS_FAIL;
	}
	if (tds_dstr_isempty(&login->user_name)) {
		if (!IS_TDS50(tds->conn)) {
			tdsdump_log(TDS_DBG_ERROR, "Kerberos login not supported using TDS 4.x\n");
			return TDS_FAIL;
		}

#ifdef ENABLE_KRB5
		/* try kerberos */
		sec_flags = TDS5_SEC_LOG_SECSESS;
		use_kerberos = true;
		tds->conn->authentication = tds_gss_get_auth(tds);
		if (!tds->conn->authentication)
			return TDS_FAIL;
#else
		tdsdump_log(TDS_DBG_ERROR, "requested GSS authentication but not compiled in\n");
		return TDS_FAIL;
#endif
	}
	if (encryption_level == TDS_ENCRYPTION_DEFAULT)
		encryption_level = TDS_ENCRYPTION_OFF;
	if (!use_kerberos && encryption_level != TDS_ENCRYPTION_OFF) {
		if (!IS_TDS50(tds->conn)) {
			tdsdump_log(TDS_DBG_ERROR, "Encryption not supported using TDS 4.x\n");
			return TDS_FAIL;
		}
		tds->conn->authentication = tds5_negotiate_get_auth(tds);
		if (!tds->conn->authentication)
			return TDS_FAIL;
	}

	if (IS_TDS42(tds->conn)) {
		memcpy(protocol_version, "\004\002\000\000", 4);
		memcpy(program_version, "\004\002\000\000", 4);
	} else if (IS_TDS46(tds->conn)) {
		memcpy(protocol_version, "\004\006\000\000", 4);
		memcpy(program_version, "\004\002\000\000", 4);
	} else if (IS_TDS50(tds->conn)) {
		memcpy(protocol_version, "\005\000\000\000", 4);
		memcpy(program_version, "\005\000\000\000", 4);
	} else {
		tdsdump_log(TDS_DBG_SEVERE, "Unknown protocol version!\n");
		return TDS_FAIL;
	}
	/*
	 * the following code is adapted from  Arno Pedusaar's 
	 * (psaar@fenar.ee) MS-SQL Client. His was a much better way to
	 * do this, (well...mine was a kludge actually) so here's mostly his
	 */

	tds_put_login_string(tds, tds_dstr_cstr(&login->client_host_name), TDS_MAXNAME);	/* client host name */
	tds_put_login_string(tds, tds_dstr_cstr(&login->user_name), TDS_MAXNAME);	/* account name */
	/* account password */
	if (encryption_level != TDS_ENCRYPTION_OFF) {
		tds_put_login_string(tds, NULL, TDS_MAXNAME);
	} else {
		tds_put_login_string(tds, tds_dstr_cstr(&login->password), TDS_MAXNAME);
	}
	sprintf(blockstr, "%d", (int) getpid());
	tds_put_login_string(tds, blockstr, TDS_MAXNAME);	/* host process */
	tds_put_n(tds, le1, 6);
	tds_put_byte(tds, !login->bulk_copy);
	tds_put_n(tds, NULL, 2);
	if (IS_TDS42(tds->conn)) {
		tds_put_int(tds, 512);
	} else {
		tds_put_int(tds, 0);
	}
	tds_put_n(tds, NULL, 3);
	tds_put_login_string(tds, tds_dstr_cstr(&login->app_name), TDS_MAXNAME);
	tds_put_login_string(tds, lservname, TDS_MAXNAME);
	if (IS_TDS42(tds->conn)) {
		tds_put_login_string(tds, tds_dstr_cstr(&login->password), 255);
	} else if (encryption_level != TDS_ENCRYPTION_OFF) {
		tds_put_n(tds, NULL, 256);
	} else {
		len = (int)tds_dstr_len(&login->password);
		if (len > 253)
			len = 0;
		tds_put_byte(tds, 0);
		tds_put_byte(tds, len);
		tds_put_n(tds, tds_dstr_cstr(&login->password), len);
		tds_put_n(tds, NULL, 253 - len);
		tds_put_byte(tds, len + 2);
	}

	tds_put_n(tds, protocol_version, 4);	/* TDS version; { 0x04,0x02,0x00,0x00 } */
	tds_put_login_string(tds, tds_dstr_cstr(&login->library), TDS_PROGNLEN);	/* client program name */
	if (IS_TDS42(tds->conn)) {
		tds_put_int(tds, 0);
	} else {
		tds_put_n(tds, program_version, 4);	/* program version ? */
	}
	tds_put_n(tds, le2, 3);
	tds_put_login_string(tds, tds_dstr_cstr(&login->language), TDS_MAXNAME);	/* language */
	tds_put_byte(tds, login->suppress_language);

	/* oldsecure(2), should be zero, used by old software */
	tds_put_n(tds, NULL, 2);
	/* seclogin(1) bitmask */
	if (sec_flags == 0 && encryption_level != TDS_ENCRYPTION_OFF)
		sec_flags = TDS5_SEC_LOG_ENCRYPT2|TDS5_SEC_LOG_ENCRYPT3;
	tds_put_byte(tds, sec_flags);
	/* secbulk(1)
	 * halogin(1) type of ha login
	 * hasessionid(6) id of session to reconnect
	 * secspare(2) not used
	 */
	tds_put_n(tds, NULL, 10);

	/* use empty charset to handle conversions on client */
	tds_put_login_string(tds, "", TDS_MAXNAME);	/* charset */
	/* this is a flag, mean that server should use character set provided by client */
	/* TODO notify charset change ?? what's correct meaning ?? -- freddy77 */
	tds_put_byte(tds, 1);

	/* network packet size */
	if (login->block_size < 65536u && login->block_size >= 512)
		sprintf(blockstr, "%d", login->block_size);
	else
		strcpy(blockstr, "512");
	tds_put_login_string(tds, blockstr, TDS_PKTLEN);

	if (IS_TDS42(tds->conn)) {
		tds_put_n(tds, NULL, 8);
	} else if (IS_TDS46(tds->conn)) {
		tds_put_n(tds, NULL, 4);
	} else if (IS_TDS50(tds->conn)) {
		/* just padding to 8 bytes */
		tds_put_n(tds, NULL, 4);

		/* send capabilities */
		tds_put_byte(tds, TDS_CAPABILITY_TOKEN);
		tds_put_smallint(tds, sizeof(tds->conn->capabilities));
		tds_put_n(tds, &tds->conn->capabilities, sizeof(tds->conn->capabilities));
	}

#ifdef ENABLE_KRB5
	if (use_kerberos)
		tds5_gss_send(tds);
#endif

	return tds_flush_packet(tds);
}

/**
 * tds7_send_login() -- Send a TDS 7.0 login packet
 * TDS 7.0 login packet is vastly different and so gets its own function
 * \returns the return value is ignored by the caller. :-/
 */
static TDSRET
tds7_send_login(TDSSOCKET * tds, const TDSLOGIN * login)
{
	static const unsigned char 
		client_progver[] = {   6, 0x83, 0xf2, 0xf8 }, 

		connection_id[] = { 0x00, 0x00, 0x00, 0x00 }, 
		collation[] = { 0x36, 0x04, 0x00, 0x00 };

	enum {
		tds70Version = 0x70000000,
		tds71Version = 0x71000001,
		tds72Version = 0x72090002,
		tds73Version = 0x730B0003,
		tds74Version = 0x74000004,
	};
	TDS_UCHAR sql_type_flag = 0x00;
	TDS_INT time_zone = -120;
	TDS_INT tds7version = tds70Version;

	TDS_INT block_size = 4096;
	
	unsigned char option_flag1 = TDS_SET_LANG_ON | TDS_USE_DB_NOTIFY | TDS_INIT_DB_FATAL;
	unsigned char option_flag2 = login->option_flag2;
	unsigned char option_flag3 = 0;

	unsigned char hwaddr[6];
	size_t packet_size, current_pos;
	TDSRET rc;

	void *data = NULL;
	TDSDYNAMICSTREAM data_stream;
	TDSSTATICINSTREAM input;

	const char *user_name = tds_dstr_cstr(&login->user_name);
	unsigned char *pwd;

	/* FIXME: These are defined as size_t, but should be TDS_SMALLINT. */
	size_t user_name_len = strlen(user_name);
	size_t auth_len = 0;

	/* fields */
	enum {
		HOST_NAME,
		USER_NAME,
		PASSWORD,
		APP_NAME,
		SERVER_NAME,
		LIBRARY_NAME,
		LANGUAGE,
		DATABASE_NAME,
		DB_FILENAME,
		NEW_PASSWORD,
		NUM_DATA_FIELDS
	};
	struct {
		const void *ptr;
		unsigned pos, len, limit;
	} data_fields[NUM_DATA_FIELDS], *field;

	tds->out_flag = TDS7_LOGIN;

	current_pos = packet_size = IS_TDS72_PLUS(tds->conn) ? 86 + 8 : 86;	/* ? */

	/* check ntlm */
#ifdef HAVE_SSPI
	if (strchr(user_name, '\\') != NULL || user_name_len == 0) {
		tdsdump_log(TDS_DBG_INFO2, "using SSPI authentication for '%s' account\n", user_name);
		tds->conn->authentication = tds_sspi_get_auth(tds);
		if (!tds->conn->authentication)
			return TDS_FAIL;
		auth_len = tds->conn->authentication->packet_len;
		packet_size += auth_len;
#else
	if (strchr(user_name, '\\') != NULL) {
		tdsdump_log(TDS_DBG_INFO2, "using NTLM authentication for '%s' account\n", user_name);
		tds->conn->authentication = tds_ntlm_get_auth(tds);
		if (!tds->conn->authentication)
			return TDS_FAIL;
		auth_len = tds->conn->authentication->packet_len;
		packet_size += auth_len;
	} else if (user_name_len == 0) {
# ifdef ENABLE_KRB5
		/* try kerberos */
		tdsdump_log(TDS_DBG_INFO2, "using GSS authentication\n");
		tds->conn->authentication = tds_gss_get_auth(tds);
		if (!tds->conn->authentication)
			return TDS_FAIL;
		auth_len = tds->conn->authentication->packet_len;
		packet_size += auth_len;
# else
		tdsdump_log(TDS_DBG_ERROR, "requested GSS authentication but not compiled in\n");
		return TDS_FAIL;
# endif
#endif
	}


	/* initialize ouput buffer for strings */
	rc = tds_dynamic_stream_init(&data_stream, &data, 0);
	if (TDS_FAILED(rc))
		return rc;

#define SET_FIELD_DSTR(field, dstr, len_limit) do { \
	data_fields[field].ptr = tds_dstr_cstr(&(dstr)); \
	data_fields[field].len = tds_dstr_len(&(dstr)); \
	data_fields[field].limit = (len_limit) * 2; \
	} while(0)

	/* setup data fields */
	memset(data_fields, 0, sizeof(data_fields));
	SET_FIELD_DSTR(HOST_NAME, login->client_host_name, 128);
	if (!tds->conn->authentication) {
		SET_FIELD_DSTR(USER_NAME, login->user_name, 128);
		SET_FIELD_DSTR(PASSWORD, login->password, 128);
	}
	SET_FIELD_DSTR(APP_NAME, login->app_name, 128);
	SET_FIELD_DSTR(SERVER_NAME, login->server_name, 128);
	SET_FIELD_DSTR(LIBRARY_NAME, login->library, 128);
	SET_FIELD_DSTR(LANGUAGE, login->language, 128);
	SET_FIELD_DSTR(DATABASE_NAME, login->database, 128);
	SET_FIELD_DSTR(DB_FILENAME, login->db_filename, 260);
	if (IS_TDS72_PLUS(tds->conn) && login->use_new_password) {
		option_flag3 |= TDS_CHANGE_PASSWORD;
		SET_FIELD_DSTR(NEW_PASSWORD, login->new_password, 128);
	}

	/* convert data fields */
	for (field = data_fields; field < data_fields + TDS_VECTOR_SIZE(data_fields); ++field) {
		size_t data_pos;

		data_pos = data_stream.size;
		field->pos = current_pos + data_pos;
		if (field->len) {
			tds_staticin_stream_init(&input, field->ptr, field->len);
			rc = tds_convert_stream(tds, tds->conn->char_convs[client2ucs2], to_server, &input.stream, &data_stream.stream);
			if (TDS_FAILED(rc)) {
				free(data);
				return TDS_FAIL;
			}
		}
		data_stream.size = MIN(data_stream.size, data_pos + field->limit);
		data_stream.stream.write(&data_stream.stream, 0);
		field->len = data_stream.size - data_pos;
	}
	pwd = (unsigned char *) data + data_fields[PASSWORD].pos - current_pos;
	tds7_crypt_pass(pwd, data_fields[PASSWORD].len, pwd);
	pwd = (unsigned char *) data + data_fields[NEW_PASSWORD].pos - current_pos;
	tds7_crypt_pass(pwd, data_fields[NEW_PASSWORD].len, pwd);
	packet_size += data_stream.size;

#if !defined(TDS_DEBUG_LOGIN)
	tdsdump_log(TDS_DBG_INFO2, "quietly sending TDS 7+ login packet\n");
	tdsdump_off();
#endif
	TDS_PUT_INT(tds, packet_size);
	switch (login->tds_version) {
	case 0x700:
		tds7version = tds70Version;
		break;
	case 0x701:
		tds7version = tds71Version;
		break;
	case 0x702:
		tds7version = tds72Version;
		break;
	case 0x703:
		tds7version = tds73Version;
		break;
	case 0x704:
		tds7version = tds74Version;
		break;
	default:
		assert(0 && 0x700 <= login->tds_version && login->tds_version <= 0x704);
	}
	
	tds_put_int(tds, tds7version);

	if (4096 <= login->block_size && login->block_size < 65536u)
		block_size = login->block_size;

	tds_put_int(tds, block_size);	/* desired packet size being requested by client */

	if (block_size > tds->out_buf_max)
		tds_realloc_socket(tds, block_size);

	tds_put_n(tds, client_progver, sizeof(client_progver));	/* client program version ? */

	tds_put_int(tds, getpid());	/* process id of this process */

	tds_put_n(tds, connection_id, sizeof(connection_id));

	if (!login->bulk_copy)
		option_flag1 |= TDS_DUMPLOAD_OFF;
		
	tds_put_byte(tds, option_flag1);

	if (tds->conn->authentication)
		option_flag2 |= TDS_INTEGRATED_SECURITY_ON;

	tds_put_byte(tds, option_flag2);

	if (login->readonly_intent && IS_TDS71_PLUS(tds->conn))
		sql_type_flag |= TDS_READONLY_INTENT;
	tds_put_byte(tds, sql_type_flag);

	if (IS_TDS73_PLUS(tds->conn))
		option_flag3 |= TDS_UNKNOWN_COLLATION_HANDLING;
	tds_put_byte(tds, option_flag3);

	tds_put_int(tds, time_zone);
	tds_put_n(tds, collation, sizeof(collation));

#define PUT_STRING_FIELD_PTR(field) do { \
	TDS_PUT_SMALLINT(tds, data_fields[field].pos); \
	TDS_PUT_SMALLINT(tds, data_fields[field].len / 2u); \
	} while(0)

	/* host name */
	PUT_STRING_FIELD_PTR(HOST_NAME);
	if (tds->conn->authentication) {
		tds_put_int(tds, 0);
		tds_put_int(tds, 0);
	} else {
		/* username */
		PUT_STRING_FIELD_PTR(USER_NAME);
		/* password */
		PUT_STRING_FIELD_PTR(PASSWORD);
	}
	/* app name */
	PUT_STRING_FIELD_PTR(APP_NAME);
	/* server name */
	PUT_STRING_FIELD_PTR(SERVER_NAME);
	/* unknown */
	tds_put_smallint(tds, 0);
	tds_put_smallint(tds, 0);
	/* library name */
	PUT_STRING_FIELD_PTR(LIBRARY_NAME);
	/* language  - kostya@warmcat.excom.spb.su */
	PUT_STRING_FIELD_PTR(LANGUAGE);
	/* database name */
	PUT_STRING_FIELD_PTR(DATABASE_NAME);

	/* MAC address */
	tds_getmac(tds_get_s(tds), hwaddr);
	tds_put_n(tds, hwaddr, 6);

	/* authentication stuff */
	TDS_PUT_SMALLINT(tds, current_pos + data_stream.size);
	TDS_PUT_SMALLINT(tds, auth_len);	/* this matches numbers at end of packet */

	/* db file */
	PUT_STRING_FIELD_PTR(DB_FILENAME);

	if (IS_TDS72_PLUS(tds->conn)) {
		/* new password */
		PUT_STRING_FIELD_PTR(NEW_PASSWORD);

		/* SSPI long */
		tds_put_int(tds, 0);
	}

	tds_put_n(tds, data, data_stream.size);

	if (tds->conn->authentication)
		tds_put_n(tds, tds->conn->authentication->packet, auth_len);

	rc = tds_flush_packet(tds);
	tdsdump_on();

	free(data);
	return rc;
}

/**
 * tds7_crypt_pass() -- 'encrypt' TDS 7.0 style passwords.
 * the calling function is responsible for ensuring crypt_pass is at least 
 * 'len' characters
 */
static void
tds7_crypt_pass(const unsigned char *clear_pass, size_t len, unsigned char *crypt_pass)
{
	size_t i;

	for (i = 0; i < len; i++)
		crypt_pass[i] = ((clear_pass[i] << 4) | (clear_pass[i] >> 4)) ^ 0xA5;
}

static TDSRET
tds71_do_login(TDSSOCKET * tds, TDSLOGIN* login)
{
	int i, pkt_len;
	const char *instance_name = tds_dstr_isempty(&login->instance_name) ? "MSSQLServer" : tds_dstr_cstr(&login->instance_name);
	int instance_name_len = strlen(instance_name) + 1;
	TDS_CHAR crypt_flag;
	unsigned int start_pos = 21;
	TDSRET ret;

#define START_POS 21
#define UI16BE(n) ((n) >> 8), ((n) & 0xffu)
#define SET_UI16BE(i,n) TDS_PUT_UA2BE(&buf[i],n)
	TDS_UCHAR buf[] = {
		/* netlib version */
		0, UI16BE(START_POS), UI16BE(6),
		/* encryption */
		1, UI16BE(START_POS + 6), UI16BE(1),
		/* instance */
		2, UI16BE(START_POS + 6 + 1), UI16BE(0),
		/* process id */
		3, UI16BE(0), UI16BE(4),
		/* MARS enables */
		4, UI16BE(0), UI16BE(1),
		/* end */
		0xff
	};
	static const TDS_UCHAR netlib8[] = { 8, 0, 1, 0x55, 0, 0 };
	static const TDS_UCHAR netlib9[] = { 9, 0, 0,    0, 0, 0 };

	TDS_UCHAR *p;

	TDS_TINYINT encryption_level = login->encryption_level;

	SET_UI16BE(13, instance_name_len);
	if (!IS_TDS72_PLUS(tds->conn)) {
		SET_UI16BE(16, START_POS + 6 + 1 + instance_name_len);
		buf[20] = 0xff;
	} else {
		start_pos += 5;
#undef  START_POS
#define START_POS 26
		SET_UI16BE(1, START_POS);
		SET_UI16BE(6, START_POS + 6);
		SET_UI16BE(11, START_POS + 6 + 1);
		SET_UI16BE(16, START_POS + 6 + 1 + instance_name_len);
		SET_UI16BE(21, START_POS + 6 + 1 + instance_name_len + 4);
	}

	assert(start_pos >= 21 && start_pos <= sizeof(buf));
	assert(buf[start_pos-1] == 0xff);

	if (encryption_level == TDS_ENCRYPTION_DEFAULT)
		encryption_level = TDS_ENCRYPTION_REQUEST;

	/*
	 * fix a problem with mssql2k which doesn't like
	 * packet splitted during SSL handshake
	 */
	if (tds->out_buf_max < 4096)
		tds_realloc_socket(tds, 4096);

	/* do prelogin */
	tds->out_flag = TDS71_PRELOGIN;

	tds_put_n(tds, buf, start_pos);
	/* netlib version */
	tds_put_n(tds, IS_TDS72_PLUS(tds->conn) ? netlib9 : netlib8, 6);
	/* encryption */
#if !defined(HAVE_GNUTLS) && !defined(HAVE_OPENSSL)
	/* not supported */
	tds_put_byte(tds, TDS7_ENCRYPT_NOT_SUP);
#else
	/* The observation working with Microsoft SQL Server is that
	   OFF did not mean off, and you would end up with encryption
	   turned on. Therefore when the freetds.conf says encrypt = off
	   we really want no encryption, and claiming lack of support
	   works for that. Note that the configuration default in this
	   subroutine always been request due to code above that
	   tests for TDS_ENCRYPTION_DEFAULT.
	*/
	tds_put_byte(tds, encryption_level == TDS_ENCRYPTION_OFF ? TDS7_ENCRYPT_NOT_SUP :
			  encryption_level >= TDS_ENCRYPTION_REQUIRE ? TDS7_ENCRYPT_ON :
			  TDS7_ENCRYPT_OFF);
#endif
	/* instance */
	tds_put_n(tds, instance_name, instance_name_len);
	/* pid */
	tds_put_int(tds, getpid());
	/* MARS (1 enabled) */
	if (IS_TDS72_PLUS(tds->conn))
#if ENABLE_ODBC_MARS
		tds_put_byte(tds, login->mars);
	login->mars = 0;
#else
		tds_put_byte(tds, 0);
#endif
	ret = tds_flush_packet(tds);
	if (TDS_FAILED(ret))
		return ret;

	/* now process reply from server */
	ret = tds_read_packet(tds);
	if (ret <= 0 || tds->in_flag != TDS_REPLY)
		return TDS_FAIL;
	login->server_is_valid = 1;
	pkt_len = tds->in_len - tds->in_pos;

	/* the only thing we care is flag */
	p = tds->in_buf + tds->in_pos;
	/* default 2, no certificate, no encryption */
	crypt_flag = TDS7_ENCRYPT_NOT_SUP;
	for (i = 0;; i += 5) {
		TDS_UCHAR type;
		int off, len;

		if (i >= pkt_len)
			return TDS_FAIL;
		type = p[i];
		if (type == 0xff)
			break;
		/* check packet */
		if (i+4 >= pkt_len)
			return TDS_FAIL;
		off = TDS_GET_UA2BE(&p[i+1]);
		len = TDS_GET_UA2BE(&p[i+3]);
		if (off > pkt_len || (off+len) > pkt_len)
			return TDS_FAIL;
		if (type == 1 && len >= 1) {
			crypt_flag = p[off];
		}
#if ENABLE_ODBC_MARS
		if (IS_TDS72_PLUS(tds->conn) && type == 4 && len >= 1)
			login->mars = p[off];
#endif
	}
	/* we readed all packet */
	tds->in_pos += pkt_len;
	/* TODO some mssql version do not set last packet, update tds according */

	tdsdump_log(TDS_DBG_INFO1, "detected crypt flag %d\n", crypt_flag);

	/* if server do not has certificate do normal login */
	if (crypt_flag == TDS7_ENCRYPT_NOT_SUP) {
		/* unless we wanted encryption and got none, then fail */
		if (encryption_level >= TDS_ENCRYPTION_REQUIRE)
			return TDS_FAIL;

		return tds7_send_login(tds, login);
	}

	/*
	 * if server has a certificate it requires at least a crypted login
	 * (even if data is not encrypted)
	 */

	/* here we have to do encryption ... */

	ret = tds_ssl_init(tds);
	if (TDS_FAILED(ret))
		return ret;

	/* server just encrypt the first packet */
	if (crypt_flag == TDS7_ENCRYPT_OFF)
		tds->conn->encrypt_single_packet = 1;

	ret = tds7_send_login(tds, login);

	/* if flag is TDS7_ENCRYPT_OFF(0) it means that after login server continue not encrypted */
	if (crypt_flag == TDS7_ENCRYPT_OFF || TDS_FAILED(ret))
		tds_ssl_deinit(tds->conn);

	return ret;
}

