/* TELNETD.C - Telnet daemon for NT. */

//#define THREAD_DEBUG
//#define IS_INETD

//#define PIPE_DEBUG
//#define SHOW_IAC

/*
 * telnetd - telnet daemon for Windows NT WIN32.
 *
 * Revision history:
 *  13-Dec-1996   S. Freyder     Initial version.
 */

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <winsock.h>
#include <process.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 ;
    struct          sockaddr_in RemoteAddr ;
    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 ;

#define LOCK(x)     EnterCriticalSection(&(x)->RefSection)
#define UNLOCK(x)   LeaveCriticalSection(&(x)->RefSection)
#define IFCloseHandle(x)                    \
    if ((x) != INVALID_HANDLE_VALUE)  {     \
        CloseHandle(x) ;                    \
        x = INVALID_HANDLE_VALUE ;          \
    }

static unsigned char EchoOut[] = { IAC,WILL,ECHO,IAC,DONT,ECHO } ;
static char *sCommandLine = "loginout" ;

void
ShutSocket(
    TELNET_DATA *pTelnet
){
    EnterCriticalSection(&pTelnet->WriteSocketSection) ;
    if (pTelnet->Socket != INVALID_SOCKET)  {
        shutdown((int)pTelnet->Socket,0) ;
    }
    LeaveCriticalSection(&pTelnet->WriteSocketSection) ;
}

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

    EnterCriticalSection(&pTelnet->WriteSocketSection) ;
    if (pTelnet->Socket != INVALID_SOCKET)  {
        ResetEvent(pTelnet->NetWrite.hEvent) ;
        WriteFile(pTelnet->Socket,Buf,Len,&wnb,
            &pTelnet->NetWrite) ;
        if (GetOverlappedResult(pTelnet->Socket,
            &pTelnet->NetWrite,&wnb,TRUE))  {
        }
        else  {
            wnb = 0 ;
        }
    }
    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
    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, n ;
    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 ((n = FixInput(pTelnet,&pTelnet->NetBuf[i],l)) > 0)  {
                if (!WriteFile(pTelnet->hStdinWrite,&pTelnet->NetBuf[i],n,&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 BOOLEAN
bCreateCmdProcess(
    TELNET_DATA *pTelnet
){
    STARTUPINFO StartupInfo ;
    HANDLE  hStdinRead ;
    HANDLE  hStdoutWrite ;
    BOOLEAN bSpawnOk ;

    if (!CreatePipe(&hStdinRead,&pTelnet->hStdinWrite,NULL,0))  {
        return(FALSE) ;
    }
    if (!CreatePipe(&pTelnet->hStdoutRead,&hStdoutWrite,NULL,0))  {
        return(FALSE) ;
    }

    ZeroMemory(&StartupInfo,sizeof StartupInfo) ;
    StartupInfo.cb = sizeof StartupInfo ;

    StartupInfo.hStdInput = hDuplicate(hStdinRead,TRUE,
        DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE,0) ;

    StartupInfo.hStdOutput = hDuplicate(hStdoutWrite,TRUE,
        DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE,0) ;

    StartupInfo.hStdError = StartupInfo.hStdOutput ;

    StartupInfo.dwFlags = STARTF_USESTDHANDLES ;

    bSpawnOk = CreateProcess(
        NULL,       // application
        sCommandLine,      // command line
        NULL,       // process attributes
        NULL,       // thread attributes
        TRUE,       // inherit handles
        CREATE_NEW_PROCESS_GROUP,    // creation flags
        NULL,       // environment
        NULL,       // current directory
        &StartupInfo,
        &pTelnet->ProcInfo
        ) ;
    /*
     * now close the handles we passed to the child process.
     */
    CloseHandle(StartupInfo.hStdInput) ;
    CloseHandle(StartupInfo.hStdOutput) ;
    CloseHandle(pTelnet->ProcInfo.hThread) ;    /* don't need thread handle */
    return(bSpawnOk) ;
}

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 */
        IFCloseHandle(pTelnet->hStdoutRead) ;
        IFCloseHandle(pTelnet->hStdinWrite) ;
        IFCloseHandle(pTelnet->NetRead.hEvent) ;
        IFCloseHandle(pTelnet->NetWrite.hEvent) ;
        IFCloseHandle(pTelnet->ProcInfo.hProcess) ;
        if (pTelnet->Socket != INVALID_HANDLE_VALUE)  {
            closesocket((unsigned)pTelnet->Socket) ;
        }
        DeleteCriticalSection(&pTelnet->RefSection) ;
        DeleteCriticalSection(&pTelnet->WriteSocketSection) ;
    }
    _endthread() ;
}

static void
vCmdOutputToNetwork(
    TELNET_DATA *pTelnet
){
/*
 * copies data from output of command process to network.
 */
    DWORD   nb ;
#if !defined(IS_INETD)
    DWORD   wnb ;
    int     i ;
    unsigned char *s ;
    unsigned char *d ;
    unsigned char c ;
#endif

    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 !defined(IS_INETD)
        /*
         * strip any in-band control characters from the stream and act
         * upon them.
         */
        s = d = pTelnet->PipeBuf ;
        for (i=0 ; i<(int)nb ; i++)  {
            switch (c=(*s++))  {
                case 0x80:  {   /* send 82 */
                    c = 0x82 ;
                    WriteFile(pTelnet->hStdinWrite,&c,1,&wnb,NULL) ;
                    break ;
                }
                case 0x81:  {   /* send 83 */
                    c = 0x83 ;
                    WriteFile(pTelnet->hStdinWrite,&c,1,&wnb,NULL) ;
                    break ;
                }
                default:  {
                    *d++ = c ;
                    break ;
                }
            }
        }
        nb = d-pTelnet->PipeBuf ;
#endif

#if defined(THREAD_DEBUG)
        fprintf(stderr,"Writing Cmd output to socket.\n") ;
#endif
        if (nb > 0)  {
            if (WriteSocket(pTelnet,pTelnet->PipeBuf,nb) != (int)nb)  {
                break ;
            }
        }
#if defined(THREAD_DEBUG)
        fprintf(stderr,"Reading Cmd output.\n") ;
#endif
    }
#if defined(THREAD_DEBUG)
    fprintf(stderr,"CmdToNet exiting.\n") ;
#endif
//    ShutSocket(pTelnet) ;
    LOCK(pTelnet) ;
    IFCloseHandle(pTelnet->hStdoutRead) ;
    UNLOCK(pTelnet) ;
    vTelnetEndThread(pTelnet) ;
}

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

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

    pTelnet->Socket = (HANDLE)vSocket ;     /* store socket */
    iAddrLen = sizeof pTelnet->RemoteAddr ;
    getpeername((int)pTelnet->Socket,(struct sockaddr *)&pTelnet->RemoteAddr,
        &iAddrLen) ;
    pTelnet->NetRead.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL) ;
    pTelnet->NetWrite.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL) ;
    InitializeCriticalSection(&pTelnet->WriteSocketSection) ;
    if (!bCreateCmdProcess(pTelnet))  {
        vTelnetEndThread(pTelnet) ;
    }
#if !defined(IS_INETD)
    WriteSocket(pTelnet,EchoOut,sizeof EchoOut) ;
#endif
    /*
     * we have the command process running - crank up a thread to handle the
     * transfer of the process' output to the network, and we'll handle the
     * other direction here.
     */
    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 ;
            hEvent[1] = pTelnet->ProcInfo.hProcess ;
            ResetEvent(pTelnet->NetRead.hEvent) ;
            ReadFile(pTelnet->Socket,pTelnet->NetBuf,sizeof pTelnet->NetBuf,
                &nb,&pTelnet->NetRead) ;
            if (WaitForMultipleObjects(2,hEvent,FALSE,INFINITE) ==
                WAIT_OBJECT_0)  {
                if (!GetOverlappedResult(pTelnet->Socket,
                    &pTelnet->NetRead,&nb,TRUE))  {
                    break ;
                }
                Error = GetLastError() ;
                if (Error != NO_ERROR && Error != ERROR_IO_PENDING)  {
                    break ;
                }
                if (nb > 0)  {
                    if (!bProcessNetRead(pTelnet,nb))  {
                        break ;
                    }
                }
                else  {                 /* network read failed */
                    break ;
                }
            }
            else  {
                break ;
            }
        }
    }
    LOCK(pTelnet) ;
    IFCloseHandle(pTelnet->hStdinWrite) ;
    IFCloseHandle(pTelnet->ProcInfo.hProcess) ;
    UNLOCK(pTelnet) ;
#if defined(THREAD_DEBUG)
    fprintf(stderr,"vTelnet status %lu\n",GetLastError()) ;
#endif
    vTelnetEndThread(pTelnet) ;
}

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

main(
    int     argc,
    char    *argv[]
){
    int         Socket ;
    int         AccSocket ;
    struct      sockaddr_in SockAddr ;
    struct      sockaddr ConAddr ;
    int         iAddrLen ;
    WORD        wVer ;
    WSADATA     wsaData ;
    char        sTrustedNet[128] ;
    char        sRemoteAddr[128] ;
    struct      sockaddr_in RemoteAddr ;
    char        *s ;
    int         on = 1 ;

    if ((s=getenv("TRUSTED_NET")) != NULL)  {   /* have a trusted net to check */
        strcpy(sTrustedNet,s) ;
    }
    else  {
        sTrustedNet[0] = 0 ;
    }
    SetErrorMode(
        SEM_FAILCRITICALERRORS |
        SEM_NOGPFAULTERRORBOX  |
        SEM_NOOPENFILEERRORBOX
    ) ;
    wVer = MAKEWORD(1,1) ;              // request WinSock version 1.1
    if (WSAStartup(wVer,&wsaData) != 0)  {  // if startup failed
        Err("wsastartup") ;
    }
    Socket = (int)hDuplicate(
        (HANDLE)socket(AF_INET,SOCK_STREAM,IPPROTO_TCP),
        FALSE,DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE,0) ;

    if (argc > 2)  {
        sCommandLine = argv[2] ;
    }
    ZeroMemory((void *)&SockAddr,sizeof SockAddr) ;   /* zero sockaddr */
    SockAddr.sin_family = AF_INET ;
    SockAddr.sin_port = htons((u_short)(argc > 1 ? atoi(argv[1]) : TELNET_PORT)) ;
    SockAddr.sin_addr.s_addr = INADDR_ANY ;
    if ((setsockopt(Socket, SOL_SOCKET, SO_REUSEADDR,
            (char *) &on, sizeof(int))) != 0)  {
        Err("SO_REUSEADDR") ;
    }
    if (bind(Socket,(struct sockaddr *)&SockAddr,
        sizeof SockAddr) != 0)  {
        Err("bind") ;
    }
    if (listen(Socket,5) != 0)  {
        Err("listen") ;
    }
    /*
     * wait for next connection, accept it, and launch the thread.
     */
    for (;;)  {
        iAddrLen = sizeof ConAddr ;
        AccSocket = accept(Socket,
            (struct sockaddr *)&ConAddr,&iAddrLen) ;
        if (AccSocket == INVALID_SOCKET)  {
            Err("accept") ;
        }
        iAddrLen = sizeof RemoteAddr ;
        getpeername(AccSocket,(struct sockaddr *)&RemoteAddr,&iAddrLen) ;
        strcpy(sRemoteAddr,inet_ntoa(RemoteAddr.sin_addr)) ;
        if (strncmp(sRemoteAddr,sTrustedNet,strlen(sTrustedNet)) == 0)  {
            AccSocket = (int)hDuplicate((HANDLE)AccSocket,
                FALSE,DUPLICATE_SAME_ACCESS|DUPLICATE_CLOSE_SOURCE,0) ;
            if (_beginthread(telnetd,0,(void *)AccSocket) == -1)  {
                closesocket(AccSocket) ;
            }
        }
        else  {
            fprintf(stderr,"Connection from %s rejected.\n",sRemoteAddr) ;
            closesocket(AccSocket) ;
        }
    }
    return(0) ;
}
