/* FreeTDS - Library of routines accessing Sybase and Microsoft databases
 * Copyright (C) 1998-1999  Brian Bruns
 *
 * 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 "tds.h"
#include "tdsutil.h"

#ifdef WIN32                                           
#define IOCTL(a,b,c) ioctlsocket(a, b, c)
#else
#define IOCTL(a,b,c) ioctl(a, b, c)
#endif

static char  software_version[]   = "$Id: login.c,v 1.39 2001/06/15 11:59:25 camber Exp $";
static void *no_unused_var_warn[] = {software_version,
                                     no_unused_var_warn};

void tds_set_version(TDSLOGIN *tds_login, short major_ver, short minor_ver)
{
	tds_login->major_version=major_ver;
	tds_login->minor_version=minor_ver;
}
void tds_set_packet(TDSLOGIN *tds_login, short packet_size)
{ 
	tds_login->block_size=packet_size; 
}
void tds_set_port(TDSLOGIN *tds_login, int port)
{ 
	tds_login->port=port; 
}
void tds_set_passwd(TDSLOGIN *tds_login, char *password)
{ 
	if (password) {
		strncpy(tds_login->password, password, 
			sizeof(tds_login->password)); 
	}
}
void tds_set_bulk(TDSLOGIN *tds_login, TDS_TINYINT enabled)
{
	tds_login->bulk_copy = enabled ? 0 : 1;
}
void tds_set_user(TDSLOGIN *tds_login, char *username)
{ 
	strncpy(tds_login->user_name, username, sizeof(tds_login->user_name)); 
}
void tds_set_host(TDSLOGIN *tds_login, char *hostname)
{
	strncpy(tds_login->host_name, hostname, sizeof(tds_login->host_name));
}
void tds_set_app(TDSLOGIN *tds_login, char *application)
{
	strncpy(tds_login->app_name, application, sizeof(tds_login->app_name));
}
void tds_set_server(TDSLOGIN *tds_login, char *server)
{
	if(!server || strlen(server) == 0) {
		server = getenv("DSQUERY");
		if(!server || strlen(server) == 0) {
			server = "SYBASE";
		}
	}
	strncpy(tds_login->server_name, server, sizeof(tds_login->server_name));
}
void tds_set_library(TDSLOGIN *tds_login, char *library)
{
	strncpy(tds_login->library, library, sizeof(tds_login->library));
}
void tds_set_charset(TDSLOGIN *tds_login, char *charset)
{
	strncpy(tds_login->char_set, charset, sizeof(tds_login->char_set));
}
void tds_set_language(TDSLOGIN *tds_login, char *language)
{
	strncpy(tds_login->language, language, sizeof(tds_login->language));
}
void tds_set_timeouts(TDSLOGIN *tds_login, int connect, int query, int longquery)                   /* Jeffs' hack to support timeouts */
{
	tds_login->connect_timeout = connect;
	tds_login->query_timeout = query;
	tds_login->longquery_timeout = longquery;
}
void tds_set_longquery_handler(TDSLOGIN * tds_login, void * longquery_func, long longquery_param) /* Jeff's hack */
{
	tds_login->longquery_func = longquery_func;
	tds_login->longquery_param = longquery_param;
}
extern void tds_set_capabilities(TDSLOGIN *tds_login, unsigned char *capabilities, int size)
{
	memcpy(tds_login->capabilities, capabilities, 
		size > TDS_MAX_CAPABILITY ? TDS_MAX_CAPABILITY : size);
}

TDSSOCKET *tds_connect(TDSLOGIN *login) 
{
TDSSOCKET	*tds;
struct sockaddr_in      sin;
char *s;
/* Jeff's hack - begin */
unsigned long ioctl_blocking = 1;                      
struct timeval selecttimeout;                         
fd_set fds;                                          
int retval;                                         
time_t start, now;
char path[100];
pid_t pid;
TDSCONFIGINFO *config;

FD_ZERO (&fds);                                    
/* end */

	config = tds_get_config(tds, login);

	/*
	** If a dump file has been specified, start logging
	*/
	if (config->dump_file) {
   		tdsdump_open(config->dump_file);
	}

	/* 
	** The initial login packet must have a block size of 512.
	** Once the connection is established the block size can be changed
	** by the server with TDS_ENV_CHG_TOKEN
	*/
	tds = tds_alloc_socket(512);

	tds->major_version=config->major_version;
	tds->minor_version=config->minor_version;

	/* Jeff's hack - begin */
        tds->timeout = (login->connect_timeout) ? login->query_timeout : 0;        
        tds->longquery_timeout = (login->connect_timeout) ? login->longquery_timeout : 0;
        tds->longquery_func = login->longquery_func;
        tds->longquery_param = login->longquery_param;
	/* end */


        sin.sin_addr.s_addr = inet_addr(config->ip_addr);
       	sin.sin_family = AF_INET;
       	sin.sin_port = htons(config->port);

	memcpy(tds->capabilities,login->capabilities,TDS_MAX_CAPABILITY);

	tdsdump_log(TDS_DBG_INFO1, "%L Connecting addr %s port %d\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
        if ((tds->s = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
                perror ("socket");
		tds_free_config(config);
		return NULL;
        }

    /* Jeff's hack *** START OF NEW CODE *** */
	if (login->connect_timeout) {
		start = time (NULL);
		ioctl_blocking = 1; /* ~0; //TRUE; */
		if (IOCTL(tds->s, FIONBIO, &ioctl_blocking) < 0) {
			tds_free_config(config);
			return NULL;
		}
		connect(tds->s, (struct sockaddr *) &sin, sizeof(sin));
          /* Select on writeability for connect_timeout */
		selecttimeout.tv_sec = 0;
		selecttimeout.tv_usec = 0;

		FD_SET (tds->s, &fds);
		retval = 0;
		retval = select(tds->s + 1, NULL, &fds, NULL, &selecttimeout);
		now = time (NULL);

		while ((retval == 0) && ((now-start) < login->connect_timeout)) {
			tds_msleep(1);
			FD_SET (tds->s, &fds);
			selecttimeout.tv_sec = 0;
			selecttimeout.tv_usec = 0;
			retval = select(tds->s + 1, NULL, &fds, NULL, &selecttimeout);
			now = time (NULL);
    		}

		if ((now-start) > login->connect_timeout) {
			tds_free_config(config);
			return NULL;
		}
	} else {
        if (connect(tds->s, (struct sockaddr *) &sin, sizeof(sin)) <0) {
                perror("connect");
		tds_free_config(config);
		return NULL;
        }
	}
	/* END OF NEW CODE */

	if (IS_TDS70(tds)) {
		tds->out_flag=0x10;
		tds7_send_login(tds,login);	
	} else {
		tds->out_flag=0x02;
		tds_send_login(tds,login);	
	}
	/* get_incoming(tds->s);  */
	if (!tds_process_login_tokens(tds)) {
		tds_free_socket(tds);
		tds = NULL;
	}
	tds_free_config(config);
	return tds;
}
int tds_send_login(TDSSOCKET *tds, TDSLOGIN *login)
{	
   unsigned char magic1[]= {
      
#ifdef WORDS_BIGENDIAN
      0x02,0x00,0x06,0x04,0x08,
#else
      0x03,0x01,0x06,0x0a,0x09,
#endif
      0x01};
      unsigned char magic2[]={0x00,0x00};
   
   unsigned char magic3[]= {0x00,0x00,0x00};
   
/* these seem to endian flags as well 13,17 on intel/alpha 12,16 on power */
   
#ifdef WORDS_BIGENDIAN
   unsigned char magic4[]= {0x00,12,16};
#else
   unsigned char magic4[]= {0x00,13,17};
#endif
   
   /* 
   ** the former byte 0 of magic5 causes the language token and message to be 
   ** absent from the login acknowledgement if set to 1. There must be a way 
   ** of setting this in the client layer, but I am not aware of any thing of
   ** the sort -- bsb 01/17/99
   */
   unsigned char magic5[]= {0x00,0x00};
   unsigned char magic6[]= {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
   unsigned char magic7=   0x01;
   
   unsigned char magic42[]= {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
   unsigned char magic50[]= {0x00,0x00,0x00,0x00};
/*
** 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];
   
   int rc;
   char blockstr[10], passwdstr[255];
   
   if (IS_TDS42(tds)) {
      memcpy(protocol_version,"\004\002\000\000",4);
      memcpy(program_version,"\004\002\000\000",4);
   } else if (IS_TDS46(tds)) {
      memcpy(protocol_version,"\004\006\000\000",4);
      memcpy(program_version,"\004\002\000\000",4);
   } else if (IS_TDS50(tds)) {
      memcpy(protocol_version,"\005\000\000\000",4);
      memcpy(program_version,"\005\000\000\000",4);
   } else {
      fprintf(stderr,"Unknown protocol version!");
      exit(1);
   }
   /*
   ** 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
   */
   
   rc=tds_put_string(tds,login->host_name,30);   /* client host name */
   rc|=tds_put_string(tds,login->user_name,30);  /* account name */
   rc|=tds_put_string(tds,login->password,30);  /* account password */
   rc|=tds_put_string(tds,"37876",30);        /* host process */
   rc|=tds_put_n(tds,magic1,6);
   rc|=tds_put_byte(tds,login->bulk_copy);
   rc|=tds_put_n(tds,magic2,2);
   if (IS_TDS42(tds)) {
      rc|=tds_put_int(tds,512);
   } else {
      rc|=tds_put_int(tds,0);
   }
   rc|=tds_put_n(tds,magic3,3);
   rc|=tds_put_string(tds,login->app_name,30);
   rc|=tds_put_string(tds,login->server_name,30);
   if (IS_TDS42(tds)) {
      rc|=tds_put_string(tds,login->password,255);
   } else {
      sprintf(passwdstr,"%c%c%s",0,(unsigned char) strlen(login->password),login->password);
      rc|=tds_put_buf(tds,passwdstr,255,(unsigned char)strlen(login->password)+2);
   }
   
   rc|=tds_put_n(tds,protocol_version,4); /* TDS version; { 0x04,0x02,0x00,0x00 } */
   rc|=tds_put_string(tds,login->library,10);  /* client program name */
   if (IS_TDS42(tds)) { 
      rc|=tds_put_int(tds,0);
   } else {
      rc|=tds_put_n(tds,program_version,4); /* program version ? */
   }
   rc|=tds_put_n(tds,magic4,3);
   rc|=tds_put_string(tds,login->language,30);  /* language */
   rc|=tds_put_byte(tds,login->suppress_language);
   rc|=tds_put_n(tds,magic5,2);
   rc|=tds_put_byte(tds,login->encrypted);
   rc|=tds_put_n(tds,magic6,10);
   rc|=tds_put_string(tds,login->char_set,30);  /* charset */
   rc|=tds_put_byte(tds,magic7);
   sprintf(blockstr,"%d",login->block_size);
   rc|=tds_put_string(tds,blockstr,6); /* network packet size */
   if (IS_TDS42(tds)) {
      rc|=tds_put_n(tds,magic42,8);
   } else if (IS_TDS46(tds)) {
      rc|=tds_put_n(tds,magic42,4);
   } else if (IS_TDS50(tds)) {
      rc|=tds_put_n(tds,magic50,4);
      rc|=tds_put_byte(tds, TDS_CAP_TOKEN);
      rc|=tds_put_smallint(tds, 18);
      rc|=tds_put_n(tds,tds->capabilities,TDS_MAX_CAPABILITY);
   }
   
   rc|=tds_flush_packet(tds);
   /* get_incoming(tds->s); */
	return 0;
}

/*
** tds7_send_login() -- Send a TDS 7.0 login packet
** TDS 7.0 login packet is vastly different and so gets its own function
*/
int tds7_send_login(TDSSOCKET *tds, TDSLOGIN *login)
{	
int rc;
unsigned char magic1[] = 
	{6,0x83,0xf2,0xf8,0xff,0x0,0x0,0x0,0x0,
	0xe0,0x03,0x0,0x0,0x88,0xff,0xff,0xff,
	0x36,0x04,0x00,0x00};
/* also seen
	{6,0x7d,0x0f,0xfd,0xff,0x0,0x0,0x0,0x0,
	0xe0,0x83,0x0,0x0,0x68,0x01,0x00,0x00,
	0x09,0x04,0x00,0x00};
*/
unsigned char magic2[] = {0x00,0x40,0x33,0x9a,0x6b,0x50};
/* 0xb4,0x00,0x30,0x00,0xe4,0x00,0x00,0x00}; */
unsigned char magic3[] = "NTLMSSP";
unsigned char unicode_string[255];
int packet_size;
int size1;
int current_pos;
int domain_login = 0;


   if (!domain_login) {
   	size1 = 86 + (strlen(login->host_name) +
			strlen(login->user_name) + 
			strlen(login->app_name) +
			strlen(login->password) +
			strlen(login->server_name) +
			strlen(login->library))*2;
   } else {
   	size1 = 86 + (strlen(login->host_name) +
			strlen(login->app_name) +
			strlen(login->server_name) +
			strlen(login->library))*2;
   }
   packet_size = size1 + 48;
   tds_put_smallint(tds,packet_size);
   tds_put_n(tds,NULL,5);
   tds_put_byte(tds,0x70);
   tds_put_n(tds,NULL,7);
   tds_put_n(tds,magic1,21);

   current_pos = 86; /* ? */
   /* host name */
   tds_put_smallint(tds,current_pos); 
   tds_put_smallint(tds,strlen(login->host_name)); 
   current_pos += strlen(login->host_name)*2;
   if (!domain_login) {
   	/* username */
   	tds_put_smallint(tds,current_pos);
   	tds_put_smallint(tds,strlen(login->user_name)); 
   	current_pos += strlen(login->user_name)*2;
   	/* password */
   	tds_put_smallint(tds,current_pos);
   	tds_put_smallint(tds,strlen(login->password)); 
   	current_pos += strlen(login->password)*2;
   } else {
	tds_put_smallint(tds,0);
	tds_put_smallint(tds,0);
	tds_put_smallint(tds,0);
	tds_put_smallint(tds,0);
   }
   /* app name */
   tds_put_smallint(tds,current_pos);
   tds_put_smallint(tds,strlen(login->app_name)); 
   current_pos += strlen(login->app_name)*2;
   /* server name */
   tds_put_smallint(tds,current_pos);
   tds_put_smallint(tds,strlen(login->server_name)); 
   current_pos += strlen(login->server_name)*2;
   /* unknown */
   tds_put_smallint(tds,0); 
   tds_put_smallint(tds,0); 
   /* library name */
   tds_put_smallint(tds,current_pos);
   tds_put_smallint(tds,strlen(login->library)); 
   current_pos += strlen(login->library)*2;
   /* unknown */
   tds_put_smallint(tds,current_pos); 
   tds_put_smallint(tds,0); 
   /* unknown */
   tds_put_smallint(tds,current_pos); 
   tds_put_smallint(tds,0); 

   tds_put_n(tds,magic2,6);
   tds_put_smallint(tds, size1);
   tds_put_smallint(tds, 0x30); /* this matches numbers at end of packet */
   tds_put_smallint(tds, packet_size);
   tds_put_smallint(tds, 0); 

   tds7_ascii2unicode(login->host_name, unicode_string, 255);
   tds_put_n(tds,unicode_string,strlen(login->host_name)*2);
   if (!domain_login) {
   	tds7_ascii2unicode(login->user_name, unicode_string, 255);
   	tds_put_n(tds,unicode_string,strlen(login->user_name)*2);
   	tds7_ascii2unicode(login->password, unicode_string, 255);
   	tds7_crypt_pass(unicode_string, strlen(login->password)*2, unicode_string);
   	tds_put_n(tds,unicode_string,strlen(login->password)*2);
   }
   tds7_ascii2unicode(login->app_name, unicode_string, 255);
   tds_put_n(tds,unicode_string,strlen(login->app_name)*2);
   tds7_ascii2unicode(login->server_name, unicode_string, 255);
   tds_put_n(tds,unicode_string,strlen(login->server_name)*2);
   tds7_ascii2unicode(login->library, unicode_string, 255);
   tds_put_n(tds,unicode_string,strlen(login->library)*2);

   /* from here to the end of the packet is totally unknown */
   tds_put_n(tds,magic3,7);
   /* next two bytes -- possibly version number (NTLMSSP v1) */
   tds_put_byte(tds,0);
   tds_put_byte(tds,1);
   tds_put_n(tds,NULL,3);
   tds_put_byte(tds,6);
   tds_put_byte(tds,130);
   tds_put_n(tds,NULL,22);
   tds_put_byte(tds,48);
   tds_put_n(tds,NULL,7);
   tds_put_byte(tds,48);
   tds_put_n(tds,NULL,3);

   rc|=tds_flush_packet(tds);
   
	return 0;
}

/*
** tds7_unicode2ascii()
** Note: The dest buf must be large enough to handle 'len' + 1 bytes.
*/
char *tds7_unicode2ascii(const char *in_string, char *out_string, int len)
{
int i;

	for (i=0;i<len;i++) {
		out_string[i]=in_string[i*2];
	}
	out_string[i]='\0';
	return out_string;
}
/*
** tds7_ascii2unicode()
*/
char *tds7_ascii2unicode(const char *in_string, char *out_string, int maxlen)
{
register int out_pos = 0;
register int i; 
size_t string_length = strlen(in_string);

	memset(out_string, 0, string_length*2);

	for (i=0;i<string_length;i++) {
		out_string[out_pos++]=in_string[i];	
		out_string[out_pos++]='\0';
	}

	return out_string;
}

/*
** tds7_crypt_pass() -- 'encrypt' TDS 7.0 style passwords.
** the calling function is responsible for ensuring crypt_pass is at least 
** 'len' characters
*/
char *tds7_crypt_pass(const unsigned char *clear_pass, int len, unsigned char *crypt_pass)
{
int i;
unsigned char xormask = 0x5A;
unsigned char hi_nibble,lo_nibble ;

	for (i=0;i<len;i++) {
		lo_nibble  = (clear_pass[i] ^ xormask) >> 4;
		hi_nibble  = (clear_pass[i] ^ xormask) << 4;
		crypt_pass[i] = hi_nibble | lo_nibble;
	}
	return crypt_pass;
}
