/* ### Modified by T.Tanaka on 4 Dec 1995 */
/* experimental TELNET/TCP support added */
#ifdef HAS_TCP

#include <sys/types.h>
#include <sys/param.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include "lutil.h"
#include "ttyio.h"

extern void linedrop(int);

int opentcp(char*);
void closetcp(void);

extern int h_errno;

static int fd=-1;
extern int f_flags;

extern int tcp_mode;

char	telnet_options[256];		/* The combined options */
char	do_dont_resp[256];
char	will_wont_resp[256];

void tel_enter_binary(int rw);
void tel_leave_binary(int rw);
void send_do(register int c, register int init);
void send_dont(register int c, register int init);
void send_will(register int c, register int init);
void send_wont(register int c, register int init);

/* opentcp() was rewritten by Martin Junius */

int opentcp(name)
char *name;
{
	struct servent *se;
	struct hostent *he;
	int a1,a2,a3,a4;
	char *errmsg;
	char *portname;
	int fd;
	short portnum;
	struct sockaddr_in server;

	debug(18,"try open tcp connection to %s",S(name));

	ClearArray(telnet_options);

	server.sin_family=AF_INET;

	if ((portname=strchr(name,':')))
	{
		*portname++='\0';
		if ((portnum=atoi(portname)))
			server.sin_port=htons(portnum);
		else if ((se=getservbyname(portname,"tcp")))
			server.sin_port=se->s_port;
		else if (tcp_mode == TCPMODE_BINKP)
			server.sin_port=htons(BINKPPORT);
		else server.sin_port=htons(FIDOPORT);
	}
	else
	{
		if (tcp_mode == TCPMODE_BINKP) {
			if ((se=getservbyname("binkp","tcp")))
				server.sin_port=se->s_port;
			else server.sin_port=htons(BINKPPORT);
		} else {
			if ((se=getservbyname("fido","tcp")))
				server.sin_port=se->s_port;
			else server.sin_port=htons(FIDOPORT);
		}
	}

	if (sscanf(name,"%d.%d.%d.%d",&a1,&a2,&a3,&a4) == 4)
		server.sin_addr.s_addr=inet_addr(name);
	else if ((he=gethostbyname(name)))
		memcpy(&server.sin_addr,he->h_addr,he->h_length);
	else
	{
		switch (h_errno)
		{
		case HOST_NOT_FOUND:	errmsg="Authoritative: Host not found"; break;
		case TRY_AGAIN:		errmsg="Non-Authoritive: Host not found"; break;
		case NO_RECOVERY:	errmsg="Non recoverable errors"; break;
		default:		errmsg="Unknown error"; break;
		}
		loginf("no IP address for %s: %s\n",name,errmsg);
		return -1;
	}

	debug(18,"trying %s at port %d",
		inet_ntoa(server.sin_addr),(int)ntohs(server.sin_port));

	signal(SIGPIPE,linedrop);
	fflush(stdin);
	fflush(stdout);
	setbuf(stdin,NULL);
	setbuf(stdout,NULL);
	close(0);
	close(1);
	if ((fd=socket(AF_INET,SOCK_STREAM,0)) != 0)
	{
		logerr("$cannot create socket (got %d, expected 0");
		open("/dev/null",O_RDONLY);
		open("/dev/null",O_WRONLY);
		return -1;
	}
	if (dup(fd) != 1)
	{
		logerr("$cannot dup socket");
		open("/dev/null",O_WRONLY);
		return -1;
	}
	clearerr(stdin);
	clearerr(stdout);
	if (connect(fd,(struct sockaddr *)&server,sizeof(server)) == -1)
	{
		loginf("$cannot connect %s",inet_ntoa(server.sin_addr));
		return -1;
	}

	f_flags=0;

	if (tcp_mode == TCPMODE_TELNET) {
		tel_enter_binary(3);
	} else if (tcp_mode == TCPMODE_BINKP) {
#if defined(FIONBIO)
		if (ioctl (fd, FIONBIO, (char *) &arg, sizeof arg) < 0) {
			loginf("$ioctl (FIONBIO) failed: %s", strerror(errno));
		}
		
#endif
		if (fcntl (fd, F_SETFL, O_NONBLOCK) == -1) {
			loginf("$fcntl failed: %s", strerror(errno));
		}
	}

	if (tcp_mode == TCPMODE_TELNET)
		loginf("established TELNET/TCP connection with %s",inet_ntoa(server.sin_addr));
	else if (tcp_mode == TCPMODE_BINKP)
		loginf("established BINKP/TCP connection with %s",inet_ntoa(server.sin_addr));
	else
		loginf("established IFC/TCP connection with %s",inet_ntoa(server.sin_addr));

	return 0;
}

void closetcp(void)
{
	if (tcp_mode == TCPMODE_TELNET)
		tel_leave_binary(3);

	shutdown(fd,2);
	signal(SIGPIPE,SIG_DFL);
}

void tel_enter_binary(int rw) {
    if (rw & 1)
	send_do(TELOPT_BINARY, 1);
    if (rw & 2)
	send_will(TELOPT_BINARY, 1);

    send_dont/*do*/(TELOPT_ECHO, 1);
    send_do(TELOPT_SGA, 1);
    send_dont(TELOPT_RCTE, 1);
    send_dont(TELOPT_TTYPE, 1);

    send_wont/*will*/(TELOPT_ECHO, 1);
    send_will(TELOPT_SGA, 1);
    send_wont(TELOPT_RCTE, 1);
    send_wont(TELOPT_TTYPE, 1);
}

void tel_leave_binary(int rw) {
    if (rw & 1)
	send_dont(TELOPT_BINARY, 1);
    if (rw & 2)
	send_wont(TELOPT_BINARY, 1);
}

/*
 * These routines are in charge of sending option negotiations
 * to the other side.
 *
 * The basic idea is that we send the negotiation if either side
 * is in disagreement as to what the current state should be.
 */

void send_do(register int c, register int init) {
/*    if (init) {
	if (((do_dont_resp[c] == 0) && my_state_is_do(c)) ||
				my_want_state_is_do(c))
	    return;
	set_my_want_state_do(c);
	do_dont_resp[c]++;
    }
*/    NET2ADD(IAC, DO);
    NETADD(c);
}

void send_dont(register int c, register int init) {
/*    if (init) {
	if (((do_dont_resp[c] == 0) && my_state_is_dont(c)) ||
				my_want_state_is_dont(c))
	    return;
	set_my_want_state_dont(c);
	do_dont_resp[c]++;
    }
*/    NET2ADD(IAC, DONT);
    NETADD(c);
}

void send_will(register int c, register int init) {
/*    if (init) {
	if (((will_wont_resp[c] == 0) && my_state_is_will(c)) ||
				my_want_state_is_will(c))
	    return;
	set_my_want_state_will(c);
	will_wont_resp[c]++;
    }
*/    NET2ADD(IAC, WILL);
    NETADD(c);
}

void send_wont(register int c, register int init) {
/*    if (init) {
	if (((will_wont_resp[c] == 0) && my_state_is_wont(c)) ||
				my_want_state_is_wont(c))
	    return;
	set_my_want_state_wont(c);
	will_wont_resp[c]++;
    }
*/    NET2ADD(IAC, WONT);
    NETADD(c);
}

#endif
