//#define PIPE_DEBUG
//#define SHOW_IAC

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <winsock.h>
#include <process.h>

#include "vt.h"

#if defined(THIS_IS_RFC_854)

This is copied directly from RFC854.TXT.

      NAME               CODE              MEANING

      SE                  240    End of subnegotiation parameters.
      NOP                 241    No operation.
      Data Mark           242    The data stream portion of a Synch.
                                 This should always be accompanied
                                 by a TCP Urgent notification.
      Break               243    NVT character BRK.
      Interrupt Process   244    The function IP.
      Abort output        245    The function AO.
      Are You There       246    The function AYT.
      Erase character     247    The function EC.
      Erase Line          248    The function EL.
      Go ahead            249    The GA signal.
      SB                  250    Indicates that what follows is
                                 subnegotiation of the indicated
                                 option.
      WILL (option code)  251    Indicates the desire to begin
                                 performing, or confirmation that
                                 you are now performing, the
                                 indicated option.
      WON'T (option code) 252    Indicates the refusal to perform,
                                 or continue performing, the
                                 indicated option.
      DO (option code)    253    Indicates the request that the
                                 other party perform, or
                                 confirmation that you are expecting
                                 the other party to perform, the
                                 indicated option.
      DON'T (option code) 254    Indicates the demand that the
                                 other party stop performing,
                                 or confirmation that you are no
                                 longer expecting the other party
                                 to perform, the indicated option.
      IAC                 255    Data Byte 255.
#endif

#define TELNET_PORT 23
#define IAC_SIZE    3
#define SE          240         /* end of subnegotiation parameters */
#define NOP         241         /* no-op */
#define DATAMARK    242         /* data mark */
#define BRK         243         /* break */
#define IP          244         /* interrupt process */
#define AO          245         /* abort output */
#define AYT         246         /* are you there */
#define EC          247         /* erase character */
#define EL          248         /* erase line */
#define GA          249         /* Go Ahead */
#define SB          250         /* subnegotiation begin */
#define WILL        251         /* will do something */
#define WONT        252         /* wont do something */
#define DO          253         /* you do something */
#define DONT        254         /* you dont do something */
#define IAC         255         /* data byte 255 or introducer */

#define ECHO        1           /* echo command */

typedef struct  {
    int             NumThreads ;
    unsigned char   LastInputChar ;
    HANDLE          Socket ;
    HANDLE          hStdinWrite ;
    HANDLE          hStdoutRead ;
    OVERLAPPED      NetRead ;
    OVERLAPPED      NetWrite ;
    CRITICAL_SECTION WriteSocketSection ;
    CRITICAL_SECTION RefSection ;
    PROCESS_INFORMATION ProcInfo ;
    int             IacIndex ;
    unsigned char   PipeBuf[512] ;
    unsigned char   NetBuf[512] ;
    unsigned char   IacBuf[32] ;
} TELNET_DATA ;

static unsigned char EchoOut[] = { IAC,WILL,ECHO,IAC,DONT,ECHO } ;
static struct vt_info v ;
static BOOLEAN bStdinIsConsole ;
static char sComputerName[MAX_COMPUTERNAME_LENGTH+1] ;
static char *sHostName ;
static TELNET_DATA *pConnection ;

int
WriteSocket(
    TELNET_DATA *pTelnet,
    unsigned char *Buf,
    unsigned Len
){
    DWORD wnb ;

    EnterCriticalSection(&pTelnet->WriteSocketSection) ;
    WriteFile(pTelnet->Socket,Buf,Len,&wnb,
        &pTelnet->NetWrite) ;
    if (GetOverlappedResult(pTelnet->Socket,
        &pTelnet->NetWrite,&wnb,TRUE))  {
    }
    else  {
        wnb = 0 ;
    }
    LeaveCriticalSection(&pTelnet->WriteSocketSection) ;
    return(wnb) ;
}

static BOOLEAN
bDoIac(
    TELNET_DATA *pTelnet
){
    pTelnet->IacIndex = 0 ;
#if defined(SHOW_IAC)
    fprintf(stderr,"IAC %03d %03d\n",pTelnet->IacBuf[1],pTelnet->IacBuf[2]) ;
#endif
    if (pTelnet->IacBuf[1] == DO)  {
        pTelnet->IacBuf[1] = WONT ;
        WriteSocket(pTelnet,pTelnet->IacBuf,IAC_SIZE) ;
    }
    return(TRUE) ;
}

static unsigned
FixInput(
    TELNET_DATA *pTelnet,
    unsigned char *s,
    unsigned l
){
/*
 * prepares the specified input for injection into the Stdin pipe and also
 * generates the echo buffer if we are in echo mode.
 */
    unsigned i ;
    unsigned char *d = s ;
    unsigned char c ;

    for (i=0 ; i<l ; i++)  {
        c = s[i] ;
        switch (c)  {
            case 0:  {
                break ;
            }
            case '\r':  {
                *d++ = '\n' ;
                break ;
            }
            case '\n':  {
                if (pTelnet->LastInputChar == '\r')  {   /* previous was CR */
                    break ;                         /* then drop newline */
                }
                /* fall through */
            }
            default:  {
                *d++ = c ;
                break ;
            }
        }
        pTelnet->LastInputChar = c ;
    }
    return(d-s) ;
}

static BOOLEAN
bProcessNetRead(
    TELNET_DATA *pTelnet,
    DWORD   nb
){
/*
 * processes data in NetBuf length nb.  worries about IACs and writes non-IAC
 * stuff to the hStdinWrite pipe.
 */
    unsigned i, l ;
    unsigned char *s ;
    DWORD   wnb ;

    i = 0 ;
    while (i < nb)  {
        /*
         * if we're inside or starting an IAC, gather the remainder.
         */
        if (pTelnet->IacIndex > 0 || pTelnet->NetBuf[i] == IAC)  {
            pTelnet->IacBuf[pTelnet->IacIndex++] = pTelnet->NetBuf[i++] ;
            if (pTelnet->IacIndex == IAC_SIZE)  {
                if (!bDoIac(pTelnet))  {
                    return(FALSE) ;
                }
            }
            else  {                     /* need more of IAC */
                continue ;              /* get it if we have it */
            }
        }
        /*
         * at this point, we are not inside an IAC - see if we can find
         * one.  if so, transfer everything up to that point.
         */
        l = i ;
        for (s=&pTelnet->NetBuf[i] ; *s++ != IAC && l < nb ; l++) ;
        l -= i ;                        /* compute length we can write */
        if (l > 0)  {
#if defined(PIPE_DEBUG)
            unsigned z ;
            fprintf(stderr,"Write pipe: ") ;
            for (z=0 ; z<l ; z++)  {
                fprintf(stderr," %c[%02X]",pTelnet->NetBuf[z+i],
                    pTelnet->NetBuf[z+i]) ;
            }
            fprintf(stderr,"\n") ;
#endif
            if (bStdinIsConsole)  {
                vtdecode(&v,&pTelnet->NetBuf[i],l) ;
            }
            else  {
                if (!WriteFile(pTelnet->hStdinWrite,&pTelnet->NetBuf[i],l,&wnb,
                    NULL))  {
                    return(FALSE) ;
                }
            }
            i += l ;            /* advance past bytes just processed */
        }
    }
    return(TRUE) ;
}

static HANDLE
hDuplicate(
    HANDLE  hSource,
    BOOLEAN bInherit,
    DWORD   dwOptions,
    DWORD   dwAccess
){
    HANDLE  hResult ;
    HANDLE  hMyProcess = GetCurrentProcess() ;

    if (DuplicateHandle(hMyProcess,hSource,hMyProcess,&hResult,
        dwAccess,bInherit,dwOptions))  {
        return(hResult) ;
    }
    return(INVALID_HANDLE_VALUE) ;
}

static void
bail(
    void
){
    printf("\n[ Control returned to %s ]%s\n\n",sComputerName,
        bStdinIsConsole ? "" : "\201") ;
    fflush(stdout) ;
    ExitProcess(0) ;
}

static int
iTelnetNumThreads(
    TELNET_DATA *pTelnet,
    int     iThreadDelta
){
    EnterCriticalSection(&pTelnet->RefSection) ;
    pTelnet->NumThreads += iThreadDelta ;
    iThreadDelta = pTelnet->NumThreads ;
    LeaveCriticalSection(&pTelnet->RefSection) ;
    return(iThreadDelta) ;
}

static void
vTelnetEndThread(
    TELNET_DATA *pTelnet
){
    if (iTelnetNumThreads(pTelnet,-1) <= 0)  {  /* if time to destroy it */
        DeleteCriticalSection(&pTelnet->RefSection) ;
        DeleteCriticalSection(&pTelnet->WriteSocketSection) ;
        CloseHandle(pTelnet->hStdoutRead) ;
        CloseHandle(pTelnet->hStdinWrite) ;
        CloseHandle(pTelnet->NetRead.hEvent) ;
        CloseHandle(pTelnet->NetWrite.hEvent) ;
        CloseHandle(pTelnet->ProcInfo.hProcess) ;
        closesocket((unsigned)pTelnet->Socket) ;
    }
    _endthread() ;
}

static void
vCmdOutputToNetwork(
    TELNET_DATA *pTelnet
){
/*
 * copies data from output of command process to network.
 */
    DWORD   nb ;

    while (ReadFile(pTelnet->hStdoutRead,pTelnet->PipeBuf,
        sizeof pTelnet->PipeBuf,&nb,NULL))  {
#if defined(NET_DEBUG)
        unsigned z ;
        fprintf(stderr,"Write net: ") ;
        for (z=0 ; z<nb ; z++)  {
            fprintf(stderr," %c[%02X]",pTelnet->PipeBuf[z],
                pTelnet->PipeBuf[z]) ;
        }
        fprintf(stderr,"\n") ;
#endif
        if (pTelnet->PipeBuf[0] == ('Y'-'@'))  {
            printf("\r\nAbort connection from %s to %s [y/n] ?",
                sComputerName,sHostName) ;
            fflush(stdout) ;
            ReadFile(pTelnet->hStdoutRead,&pTelnet->PipeBuf[2],1,&nb,NULL) ;
            if (nb == 1 && pTelnet->PipeBuf[2] == 'y')  {
                break ;
            }
            nb = 1 ;
        }
        else if (pTelnet->PipeBuf[0] == '\n')  {
            strcpy(pTelnet->PipeBuf,"\r\n") ;
            nb = 2 ;
        }
        if (WriteSocket(pTelnet,pTelnet->PipeBuf,nb) != (int)nb)  {
            break ;
        }
    }
    bail() ;
}

static void
vWriteTty(
    void    *pvTelnet,
    char    *Buf,
    int     Len
){
    WriteSocket(pvTelnet,Buf,Len) ;
}

static void
telnetd(
    void    *vSocket
){
    TELNET_DATA *pTelnet = calloc(1,sizeof *pTelnet) ;
    DWORD   nb ;
    HANDLE  hEvent[2] ;

    InitializeCriticalSection(&pTelnet->RefSection) ;
    iTelnetNumThreads(pTelnet,1) ;          /* count self */

    pTelnet->Socket = (HANDLE)vSocket ;     /* store socket */
    pTelnet->NetRead.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL) ;
    pTelnet->NetWrite.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL) ;
    pTelnet->hStdoutRead = GetStdHandle(STD_INPUT_HANDLE) ;
    pTelnet->hStdinWrite = GetStdHandle(STD_OUTPUT_HANDLE) ;
    InitializeCriticalSection(&pTelnet->WriteSocketSection) ;
    pConnection = pTelnet ;
    v.tty_input = vWriteTty ;
    v.pvInputParam = pTelnet ;
//    WriteSocket(pTelnet,EchoOut,sizeof EchoOut) ;
    iTelnetNumThreads(pTelnet,1) ;      /* count this thread */
    if (_beginthread(vCmdOutputToNetwork,0,pTelnet) == -1)  {
        iTelnetNumThreads(pTelnet,-1) ; /* discount thread not started */
    }
    else  {
        for (;;)  {
            hEvent[0] = pTelnet->NetRead.hEvent ;
            ResetEvent(pTelnet->NetRead.hEvent) ;
            ReadFile(pTelnet->Socket,pTelnet->NetBuf,sizeof pTelnet->NetBuf,
                &nb,&pTelnet->NetRead) ;
            if (WaitForMultipleObjects(1,hEvent,FALSE,INFINITE) ==
                WAIT_OBJECT_0)  {
                if (!GetOverlappedResult(pTelnet->Socket,
                    &pTelnet->NetRead,&nb,TRUE))  {
                    break ;
                }
                if (nb > 0)  {
                    if (!bProcessNetRead(pTelnet,nb))  {
                        break ;
                    }
                }
                else  {
                    break ;
                }
            }
            else  {
                break ;
            }
        }
    }
    bail() ;
}

static void
Err(
    char    *why
){
    fprintf(stderr,"%s: Error %lu SockError %lu\n",why,
        GetLastError(),WSAGetLastError()) ;
    exit(2) ;
}


static BOOL
bNoEcho(
    void
){
    HANDLE  hStdin = GetStdHandle(STD_INPUT_HANDLE) ;
    DWORD   dwMode ;

    if (GetConsoleMode(hStdin,&dwMode))  {
        return(SetConsoleMode(hStdin,dwMode&
            ~(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT))) ;
    }
    return(FALSE) ;
}

static BOOL WINAPI
bControlHandler(
    DWORD   dwControlType
){
    switch (dwControlType)  {
        case CTRL_C_EVENT:  {
            if (pConnection != NULL)  {
                WriteSocket(pConnection,"\3",1) ;
            }
            /* fall through */
        }
        case CTRL_BREAK_EVENT:  {
            return(TRUE) ;
        }
        default:  {
            return(FALSE) ;
        }
    }
}

main(
    int argc,
    char *argv[]
){
    int         Socket ;
    struct      sockaddr_in SockAddr ;
    WORD        wVer ;
    WSADATA     wsaData ;
    struct      hostent *pHostEnt ;
    char        *s ;
    struct  {
        unsigned char vn ;
        unsigned char fc ;
        unsigned short port ;
        unsigned long addr ;
        char username ;
    } SocksConnect = { 4, 1, 0, 0, 0 } ;
    struct  {
        unsigned char vn ;
        unsigned char cd ;
        unsigned short port ;
        unsigned long addr ;
    } SocksReply ;
    DWORD   dwNameLen ;

    dwNameLen = sizeof sComputerName ;
    GetComputerName(sComputerName,&dwNameLen) ;
    sComputerName[dwNameLen] = 0 ;
    if (argc < 2)  {
        fprintf(stderr,"telnet host [port]\n") ;
        return(3) ;
    }
    bStdinIsConsole = bNoEcho() ;
    wVer = MAKEWORD(1,1) ;              // request WinSock version 1.1
    if (WSAStartup(wVer,&wsaData) != 0)  {  // if startup failed
        Err("wsastartup") ;
    }
    Socket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP) ;
    ZeroMemory((void *)&SockAddr,sizeof SockAddr) ;   /* zero sockaddr */
    SockAddr.sin_family = AF_INET ;
    SockAddr.sin_port = htons((u_short)
        (argc > 2 ? atoi(argv[2]) : TELNET_PORT)) ;
    sHostName = argv[1] ;
    s = strchr(argv[1],'@') ;
    if (s != NULL)  {
        *s++ = 0 ;
    }
    if (isdigit(argv[1][0]))  {
        SockAddr.sin_addr.s_addr = inet_addr(argv[1]) ;
    }
    else  {                         /* by name */
        pHostEnt = gethostbyname(argv[1]) ;
        if (pHostEnt == NULL)  {    /* hostname lookup error */
            fprintf(stderr,"%s: lookup failed\n",argv[1]) ;
            return(5) ;
        }
        SockAddr.sin_addr =
            *((struct in_addr *)pHostEnt->h_addr_list[0]) ;
    }
    if (s == NULL)  {           /* no socks specification in host addr */
        s = getenv("socks") ;   /* maybe one in the environment */
    }
    if (s != NULL)  {
        char *p = strchr(s,':') ;
        if (p != NULL)  {
            *p++ = 0 ;
        }
        else  {
            p = "1080" ;
        }
        SocksConnect.port = SockAddr.sin_port ;
        SocksConnect.addr = SockAddr.sin_addr.s_addr ;
        SockAddr.sin_port = htons((u_short)atoi(p)) ;
        SockAddr.sin_addr.s_addr = inet_addr(s) ;
    }
    if (connect(Socket,(struct sockaddr *)&SockAddr,sizeof SockAddr) != 0)  {
        fprintf(stderr,"Connect failed error %lu\n",GetLastError()) ;
        exit(3) ;
    }
    if (s != NULL)  {
        int     n ;
        send(Socket,(void *)&SocksConnect,sizeof SocksConnect,0) ;
        n = recv(Socket,(void *)&SocksReply,sizeof SocksReply,0) ;
        if (n == sizeof SocksReply && SocksReply.cd == 90)  {
            fprintf(stderr,"Connected via Socks gateway.\n") ;
        }
        else  {
            fprintf(stderr,"Socks reply len %d, status %d\n",n,
                SocksReply.cd) ;
        }
        fflush(stderr) ;
    }
    SetConsoleCtrlHandler(bControlHandler,TRUE) ;
    if (!bStdinIsConsole)  {
        fwrite("\200",1,1,stdout) ;    // turn off echo
        fflush(stdout) ;
    }
    else  {
        ConsoleInit() ;
    }
    telnetd((void *)Socket) ;
    bail() ;
    return(0) ;
}
