/*
 * TELNET Server
 *
 * Copyright (c) 1990, 1999 Erick Engelke
 */

#include <net.h>
#include <rtos.h>
#include <telnetd.h>
#include <stdio.h>
#include <mem.h>
#include <string.h>


#define IO_ST_INIT 0
#define IO_ST_NORM 1	/* normal chars */

#define TEL_SE     240
#define TEL_IP     244  /* interrupt process */
#define TEL_AYT    246  /* are you there */
#define TEL_WILL   251
#define TEL_WONT   252
#define TEL_DO	   253
#define TEL_DONT   254
#define TEL_IAC	   255

#define LAST_MODE  6


#define OPT_ECHO	1
#define OPT_SUPPRESS_GA	3
#define OPT_TERM_TYPE	24



static void send_iac( teld_str *t, BYTE cmd, BYTE opt)
{
    static char io_data[3];
    io_data[0] = TEL_IAC;
    io_data[1] = cmd;
    io_data[2] = opt;

    sock_write( &t->teld_socket, io_data, 3 );
}

static int io_pre_get( teld_str *t )
{
    tcp_Socket *s = &t->teld_socket;
    unsigned char ch;

    if ( t->teld_iostate == IO_ST_INIT )
        t->teld_iostate = IO_ST_NORM;

    while (sock_dataready(s)) {
        sock_fastread( s, &ch, 1 );
        switch ( t->teld_iostate ) {
	    case IO_ST_NORM:
		    if (ch != TEL_IAC)
			return( ch );
                    t->teld_iostate = TEL_IAC;
		    break;

            case TEL_IAC :
                    t->teld_iostate = IO_ST_NORM;      /* assume last */
		    if (ch == TEL_IAC) 		/* byte stuffed */
			return( ch );

                    if (ch >= TEL_IP )
                        t->teld_iostate = ch;
		    break;			/* loose all others */
            case TEL_IP  :
                    t->teld_iostate = IO_ST_NORM;
                    return( 3 );         /* control break */
            case TEL_AYT :
                    t->teld_iostate = IO_ST_NORM;
                    sock_puts( s, "[YES]");
                    break;
	    case TEL_WILL:
	    case TEL_WONT:
	    case TEL_DO:
	    case TEL_DONT :
                    if ( ch == OPT_TERM_TYPE ) t->teld_iostate = ch;
                    else t->teld_iostate = IO_ST_NORM;
		    break;
            case OPT_TERM_TYPE :
                    if ( ch >= TEL_SE ) t->teld_iostate = ch;
                    break;
	    default :
                    t->teld_iostate = IO_ST_NORM;
                    break;
	}
    }
    rt_sleep( 0 );
    return( 0 );
}




/******************************************************************/

teld_str *teld_listen( int port )
{
    teld_str *t;
    tcp_Socket *s;

    t = kcalloc( sizeof( teld_str ), 1 );
    if ( t == NULL ) return( NULL );
    if ( port == 0 ) port = TELNETPORT;

    s = &t->teld_socket;
    do {
        tcp_listen( s, port, 0L, 0, NULL, 0 );

        while ( ! sock_established( s )) {
            if ( tcp_tick( s ) == NULL ) goto retry;
            rt_sleep( 250 );
        }

        /* we are connected */
        t->teld_connected = 1;
        t->teld_iostate = IO_ST_INIT;
        sock_mode( s, TCP_MODE_NONAGLE );
        send_iac( t, TEL_WILL, OPT_ECHO );
        return( t );

retry:
        sock_abort( s );
    } while ( 1 );
}

/*
 * teld_write - returns 0 on failure
 */
int teld_write( teld_str *t, char *buffer, int len )
{
    tcp_Socket *s;

    if ( t->teld_connected == 0 ) return( 0 );

    s = &t->teld_socket;
    sock_write( s, buffer, len );
    return( tcp_tick( s ) != 0);
}

int teld_puts( teld_str *t, char *s )
{
    return( teld_write( t, s, strlen( s)));
}

char teld_putch( teld_str *t, char x )
{
    return( teld_write( t, &x, 1 ) ? x : 0 );
}

/*
 * teld_getc - returns character or 255 on connection failed
 */
BYTE teld_getc( teld_str *t )
{

    if ( ! tcp_tick( &t->teld_socket ))
        t->teld_connected = 0;
    if ( t->teld_connected == 0 )
        return( 255 );
    return( io_pre_get( t ));
}

int teld_iswaiting( teld_str *t )
{
    tcp_Socket *s;

    s = &t->teld_socket;
    if (tcp_tick( s ) == NULL ) t->teld_connected = 0;
    if ( t->teld_connected == 0 ) return( 1 );  /* EOT is waiting */

    return(sock_rbused( s ));
}

char *teld_getstring( teld_str *t, char *buf, int maxlen, long timeout, char dispchar )
{
    long now, whenever;
    int len;
    BYTE ch;

    if ( timeout > 0 ) {
        dos_enter();
        now = time( NULL );
        dos_exit();
        whenever = now + timeout;
    }
    len = 0;
    do {
        buf[ len ] = 0;

        /* reached max len */
        if ( len == (maxlen - 1 )) {
            return( buf );
        }

        if ( teld_iswaiting( t ) ) {
            ch = teld_getc( t );
            if ( ch == 255 ) {
                /* EOT */
                return( NULL );
            }
            if ( ch == 13 ) {
                buf[ len ] = 0;
                teld_puts( t, "\r\n");
                return( buf );
            }
            if (( ch == 8 ) || ( ch == 127 )) {
                if ( len > 0 ) {
                    len--;
                    teld_puts( t, "\010 \010" ); /* BS SP BS */
                } else
                    teld_putch( t, 7 ); /* bell */
            } else if ( ( ch == 0 ) || ( ch == 10 )) {
                /* do nothing */
            } else {
                /* normal char */
                buf[ len++ ] = ch;
                /* handle normal or password type entries */
                teld_putch( t, dispchar ? dispchar : ch );
            }
            rt_sleep( 0 );      /* there may be more chars to process */
        } else {
            /* nothing to do.. so we do almost nothing */

            rt_sleep( 10 );

            /* look for timeout */
            if ( timeout != 0 ) {
                dos_enter();
                now = time( NULL );
                dos_exit();
                if ( now >= whenever ) {
                    return( NULL );
                }
            }
        }
    } while ( 1 );
}



char *teld_getpassword( teld_str *t, char *buf, int maxlen, long timeout )
{
    return teld_getstring( t, buf, maxlen, timeout, '*' );
}

/*
 * teld_gets - returns string on success
 *           - returns NULL on closed session or on timeout reached
 *           - timeout = 0 means never
 */

char *teld_gets( teld_str *t, char *buf, int maxlen, long timeout )
{
    return teld_getstring( t, buf, maxlen, timeout, 0 );
}



void teld_close( teld_str *t )
{
    tcp_Socket *s;

    if ( t != NULL ) {
        s = &t->teld_socket;


        sock_close( s );
        while ( tcp_tick( s ))
            rt_sleep( 250 );
        kfree( t );
    }
}

