/*****************************************************************************\
*                                                                             *
* tstelnet.c -- telnet program for telser.device                              *
*                                                                             *
* Copyright (c) 1994-1995 by Sam Yee.                                         *
* Source freely distributable in unmodified form.                             *
*                                                                             *
* $ Author:       Sam Yee (samy@res.com) $                                    *
* $ Project:      telser $                                                    *
* $ Date Started: October 30, 1994 $                                          *
* $ Version:      1.31 (11.9.95) $                                            *
*                                                                             *
* History:                                                                    *
* Ver. YY/MM/DD Who                Modifications                              *
* --------------------------------------------------------------------------- *
* 1.40 96/03/16 Sam Yee            Recognizes console close window.           *
*                                  Can notify host of window dimension change *
* 1.30 95/08/27 Sam Yee            Terminates program after disconnection     *
*                                  added remote user support for rlogin.      *
* 1.20 95/03/05 Sam Yee            Prompts user for host if not specified on  *
*                                  command line                               *
*                                  three escapes will kill session            *
* 1.10 95/01/16 Sam Yee            Three breaks will kill session             *
* 1.00 95/01/01 Sam Yee            Original release                           *
*                                                                             *
\*****************************************************************************/

#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <dos/dosasl.h>
#include <devices/serial.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/exec_pragmas.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define RAWIN_RUNTIME
#include <RawIN.h>

#define ACTION_FORCE    2001
#define ACTION_QUEUE    2003

/********************************************************************/
/* imports */
extern struct DOSBase *DOSBase;
extern struct SysBase *SysBase;

/********************************************************************/
/* prototypes */
ULONG ScanSerial(struct IOExtSer *ser_rreq, char *buf, ULONG buf_len, BOOL *CD);
void SendSerReadRequest(struct IOExtSer *req, char *c);
void SendSerWriteRequest(struct IOExtSer *req, char *buf, ULONG buf_len);
void AbortReq(struct IORequest *req);
void Ask_WinSize_Change(void);

/********************************************************************/
void __regargs __chkabort(void);    /* disable SAS/C ^C checking */
void __regargs __chkabort(){}
void __regargs _CXBRK(void);
void __regargs _CXBRK(){}

const char version[] = "\0$VER: tstelnet 1.40 (17.3.96)";

/********************************************************************/
/* where the action starts */
int
main(int  argc,
     char *argv[])
{
    struct Library  *RawINBase = NULL;  /* std i/o handling library */
    struct Line     *line = NULL;       /* line buffer */
    struct IOExtSer *ser_rreq = NULL,   /* serial stuff */
                    *ser_wreq = NULL,
                    *ser_oreq = NULL;
    struct MsgPort  *ser_rport = NULL,
                    *ser_wport = NULL,
                    *ser_oport = NULL;
    char            device[64] = "telser.device",
                    *s,
                    *prog_name,
                    ser_wbuf[256],
                    raw_buf[256],
                    *host = NULL,
                    host_buf[256],
                    cmd[128],
                    port[64] = "",
                    *CLOSE_SEQ  = "\23311;0;0;32768",
                    *IGNORE_SEQ = "\23310;0;65535;",
                    ser_char;           /* for serial reads */
    unsigned char   buf[256];       /* these three buffers must be the same size! */
    ULONG           wait_mask,
                    return_mask;
    int             unit = 0,
                    i,
                    j,
                    read_len,
                    close_seq_len = strlen(CLOSE_SEQ),
                    ignore_seq_len = strlen(IGNORE_SEQ),
                    error_code = 0,
                    break_count = 0,
                    cols,
                    rows;
    BOOL            show_usage = FALSE,
                    got_stuff,
                    typed,
                    ser_wrote = FALSE,
                    device_specified = FALSE,
                    typed_ctrl_c,
                    typed_esc,
                    broke = FALSE,
                    ser_opened = FALSE,
                    was_connected = FALSE,
                    got_ruser = FALSE,
                    force_exit = FALSE,
                    CD = FALSE,
                    should_send_stuff;

    prog_name = FilePart(argv[0]);

    for (i = 1; i < argc; i++)
    {
        if (!stricmp(argv[i],"-h") || !strcmp(argv[i],"?"))
        {
            show_usage = TRUE;
            break;
        }
        else if (!strnicmp(argv[i],"-d",2))
        {
            device_specified = TRUE;
            s = argv[i] + 2;

            if (!(*s))
            {
                if ((i+1) < argc)
                    s = argv[++i];
            }

            if (*s)
            {
                j = 0;

                while (*s && (j < sizeof(device)-1) && (*s != ','))
                    device[j++] = *s++;

                device[j] = '\0';

                if (*s)
                    unit = atol(++s);
            }
            else
            {
                Printf("Device/unit not specified.\n");
                show_usage = TRUE;
            }
        }
        else if (!strnicmp(argv[i],"-l",2))
        {
            s = argv[i] + 2;

            if (!(*s))
            {
                if ((i+1) < argc)
                    s = argv[++i];
            }

            if (*s)
            {
                BPTR fh;

                /* write the remote user name to the environment.
                   telser will pick it up */
                if (fh = Open("ENV:RUSER",MODE_NEWFILE))
                {
                    got_ruser = TRUE;
                    FPrintf(fh,"%s",s);
                    Close(fh);
                }
            }
            else
            {
                Printf("Username not specified.\n");
                show_usage = TRUE;
            }
        }
        else if (!stricmp(argv[i],"-f"))
            force_exit = TRUE;
        else if (host)
        {
            strncpy(port,argv[i],sizeof(port)-1);
            break;
        }
        else
            host = argv[i];
    }

    if (show_usage)
        Printf("Usage: %s [ -d device,unit ] [ -l username ] [ -f ] hostname [port]\n",prog_name);
    else if (!(ser_rport = CreateMsgPort()) ||
             !(ser_wport = CreateMsgPort()) ||
             !(ser_oport = CreateMsgPort()))
    {
        Printf("%s: no memory for message ports\n",prog_name);
        error_code = 10;
    }
    else if (!(ser_rreq = CreateIORequest(ser_rport,sizeof(struct IOExtSer))) ||
             !(ser_wreq = CreateIORequest(ser_wport,sizeof(struct IOExtSer))) ||
             !(ser_oreq = CreateIORequest(ser_oport,sizeof(struct IOExtSer))))
    {
        Printf("%s: no memory for io requests\n",prog_name);
        error_code = 10;
    }
    else
    {
        if (device_specified)
        {
            if (OpenDevice(device,unit,(struct IORequest *)ser_rreq,0L))
                error_code = 10;
            else
                ser_opened = TRUE;
        }
        else
        {
            ser_rreq->io_SerFlags = 0;

            for (i = unit; i < (unit+20);i++)
            {
                if (CheckSignal(SIGBREAKF_CTRL_C))
                {
                    PrintFault(ERROR_BREAK,FilePart(prog_name));
                    error_code = 10;
                    broke = TRUE;
                    break;
                }

                if (!OpenDevice(device,i,(struct IORequest *)ser_rreq,0L))
                {
                    ser_opened = TRUE;
                    break;
                }
            }

            if (i == (unit+20))
                error_code = 10;

            unit = i;
        }

        if (ser_opened)
        {
            /* private way of getting the effective unit number */
            if (ser_rreq->IOSer.io_Offset)
                unit = ser_rreq->IOSer.io_Offset;
        }

        if (error_code && !broke)
            Printf("%s: cannot open device %s, unit %ld\n",prog_name,device,unit);
    }

    if (!error_code && !show_usage)
    {
        if (!(RawINBase = OpenLibrary(RAWIN_NAME,0)))
        {
            Printf("%s: " RAWIN_NAME " not found\n",prog_name);
            error_code = 10;
        }
    }

    if (RawINBase)
    {
        if (!(line = RI_AllocLine(2,2)))
        {
            Printf("%s: no memory for line buffer\n",prog_name);
            error_code = 10;
        }
        else if (!host)
        {
            Printf("Telnet/RLogin Host: "); Flush(Output());
            i = RI_Gets(line,host_buf,sizeof(host_buf),0,FALSE,FALSE,TRUE);
            Printf("\r\n");

            if (i > 1)
            {
                Printf("Port [%ld]: ",port); Flush(Output());
                i = RI_Gets(line,buf,6,0,FALSE,FALSE,TRUE);
                Printf("\r\n");

                if (i >= 0)
                {
                    Printf("*** Press Esc or ^C 3 times to exit\r\n\r\n");

                    if (i)
                        strncpy(port,buf,sizeof(port)-1);

                    host = host_buf;
                }
            }
        }
    }

    if (line && host)
    {
        CopyMem(ser_rreq,ser_wreq,sizeof(struct IOExtSer));
        CopyMem(ser_rreq,ser_oreq,sizeof(struct IOExtSer));

        ser_wreq->IOSer.io_Message.mn_ReplyPort = ser_rport;
        ser_oreq->IOSer.io_Message.mn_ReplyPort = ser_oport;

        ser_oreq->io_Baud = 19200;  /* set the baud rate */
        ser_oreq->io_RBufLen = 4096L;
        ser_oreq->IOSer.io_Command = SDCMD_SETPARAMS;
        DoIO((struct IORequest *)ser_oreq);
        SendSerReadRequest(ser_rreq,&ser_char);

        sprintf(ser_wbuf,"AT%sDT %s",force_exit ? "&Y1" : "",host);

        if (port[0])
            sprintf(ser_wbuf+strlen(ser_wbuf),",%s",port);

        Printf("Trying %s...\r\n",host); Flush(Output());
        strcat(ser_wbuf,"\r");
        SendSerWriteRequest(ser_wreq,ser_wbuf,strlen(ser_wbuf));

        ser_wrote = TRUE;

        typed = TRUE;
        RI_SendReadRequest(line);
        wait_mask = SIGBREAKF_CTRL_C |
                    (1L << ser_rport->mp_SigBit) |
                    (1L << ser_wport->mp_SigBit) |
                    (1L << line->ReadReplyPort->mp_SigBit);

        Ask_WinSize_Change();

        while (!(line->flags & (LNF_EOF|LNF_BREAKC|LNF_READERROR|LNF_WRITEERROR)))
        {
            typed_ctrl_c = typed_esc = got_stuff = FALSE;

            /* get user input */
            if ((read_len = RI_GetBlock(line,FALSE,FALSE,buf,sizeof(buf))) > 0L)
            {
                got_stuff = TRUE;

                if (!memcmp(buf,CLOSE_SEQ,close_seq_len))
                    Signal(FindTask(NULL),SIGBREAKF_CTRL_C);

                if (buf[0] == 0x1b)
                {
                    break_count++;
                    typed_esc = TRUE;
                }
                else if (buf[0] == 0x03)
                    typed_ctrl_c = TRUE;
                else
                    break_count = 0;

                if (ser_wrote)  /* already wrote something? if so, wait! */
                {
                    WaitIO((struct IORequest *)ser_wreq);
                    ser_wrote = FALSE;
                }

                i = 0;

                if (buf[0] == '\006')   /* ^F? */
                    i++;

                should_send_stuff = FALSE;

                /* did user hit the HELP key to change window size? */
                if (!memcmp(buf+i,"\233?~",3))
                    Ask_WinSize_Change();
                /* window bounds change? */
                else if (!memcmp(buf+i,"\233\061\073\061\073",5))
                {
                    rows = atoi(buf+5+i);

                    if (s = strchr(buf+5+i,';'))
                        cols = atoi(s+1);

                    sprintf(cmd,"tsctl -w%dx%d %d",cols,rows,unit);
                    system(cmd);
                    memset(buf+i,0,read_len-i);

                    if (i)
                    {
                        should_send_stuff = TRUE;
                        read_len = 1;
                    }
                }
                else if (memcmp(buf,IGNORE_SEQ,ignore_seq_len))
                    should_send_stuff = TRUE;

                if (should_send_stuff)
                {
                    /* convert Amiga cursor keys to proper ANSI/VT keys */
                    if ((read_len == 2) &&
                        (buf[0] == 0x9b) &&
                        ((buf[1] >= 'A') && (buf[1] <= 'D')))
                    {
                        buf[0] = 0x1b;
                        buf[2] = buf[1];
                        buf[1] = '[';
                        read_len++;
                    }

                    memcpy(ser_wbuf,buf,read_len);
                    SendSerWriteRequest(ser_wreq,ser_wbuf,read_len);
                    ser_wrote = TRUE;
                }

                if (break_count != 3)
                    typed = TRUE;
            }

            if (GetMsg(ser_wport))  /* a write reply? */
                ser_wrote = FALSE;

            /* read from serial port as much as possible */
            if (read_len = ScanSerial(ser_rreq,buf,sizeof(buf),&CD))
            {
                got_stuff = TRUE;
                RI_WaitWrite(line); /* wait for a write to finish if needed */
                memcpy(raw_buf,buf,read_len);
                RI_SendWriteRequest(line,raw_buf,read_len);

                if (CD)
                    was_connected = TRUE;
                else if (was_connected)
                    break;
            }

            /* if user typed key(s), send next read request */
            if (typed)
            {
                RI_SendReadRequest(line);
                typed = FALSE;
            }

            if (got_stuff)
                continue;

            return_mask = Wait(wait_mask);

            if (return_mask & SIGBREAKF_CTRL_C)
            {
                if (!CD)
                    break;

                break_count++;
            }

            if (break_count == 3)
                break;
        }

        AbortReq((struct IORequest *)ser_rreq);
    }

    if (ser_opened)
        CloseDevice((struct IORequest *)ser_rreq);

    /* cleanup */
    if (ser_rreq)
        DeleteIORequest((struct IORequest *)ser_rreq);

    if (ser_wreq)
        DeleteIORequest((struct IORequest *)ser_wreq);

    if (ser_oreq)
        DeleteIORequest((struct IORequest *)ser_oreq);

    if (ser_rport)
        DeleteMsgPort(ser_rport);

    if (ser_wport)
        DeleteMsgPort(ser_wport);

    if (ser_oport)
        DeleteMsgPort(ser_oport);

    if (line)
    {
        /* read pending? */
        if (line->flags & LNF_STARTEDREAD)
        {
            if (force_exit)
            {
                struct FileHandle *fhp = (struct FileHandle *)BADDR(line->InputStream);

                /* send abort packet requests */
                if (!DoPkt(fhp->fh_Type, ACTION_FORCE, fhp->fh_Arg1, (LONG)"\n",
                           1L, 0L, 0L))
                    DoPkt(fhp->fh_Type, ACTION_QUEUE, fhp->fh_Arg1, (LONG)"\n",
                          1L, 0L, 0L);
            }
            else
            {
                Printf("Press any key to exit..."),
                Flush(Output());
            }
        }

        RI_FreeLine(line);
        Printf("\n");
    }

    if (RawINBase)
        CloseLibrary(RawINBase);

    if (got_ruser)
        DeleteFile("ENV:RUSER");

    return(error_code);
}

/****************************************************************************/
/* grab bytes from serial port if any */
ULONG
ScanSerial(struct IOExtSer  *ser_rreq,
           char             *buf,
           ULONG            buf_len,
           BOOL             *CD)
{
    char    *s;
    ULONG   bytesRead = 0L,
            bytesInQ = 0L;

    if (!buf_len)
        return(0L);

    if (CheckIO((struct IORequest *)ser_rreq))
    {
        WaitIO((struct IORequest *)ser_rreq);
        s = (char *)ser_rreq->IOSer.io_Data;
        buf[0] = *s;
        bytesRead = 1L;
        ser_rreq->IOSer.io_Command = SDCMD_QUERY;

        if (!DoIO((struct IORequest *)ser_rreq))
        {
            *CD = (ser_rreq->io_Status & (1 << 0x05)) ? FALSE : TRUE;

            if (bytesInQ = ser_rreq->IOSer.io_Actual)
            {
                if (bytesInQ >= buf_len)
                    bytesInQ = buf_len - 1L;

                ser_rreq->IOSer.io_Data = buf+1;
                ser_rreq->IOSer.io_Length = bytesInQ;
                ser_rreq->IOSer.io_Command = CMD_READ;
                DoIO((struct IORequest *)ser_rreq);
                bytesRead += bytesInQ;
            }
        }

        SendSerReadRequest(ser_rreq,s);
    }

    return(bytesRead);
}

/****************************************************************************/
/* send an asynchronous serial read */
void
SendSerReadRequest(struct IOExtSer  *req,
                   char             *c)
{
    req->IOSer.io_Command = CMD_READ;
    req->IOSer.io_Data    = c;
    req->IOSer.io_Length  = 1L;
    SendIO((struct IORequest *)req);
}

/****************************************************************************/
/* send an asynchronous serial write */
void
SendSerWriteRequest(struct IOExtSer *req,
                    char            *buf,
                    ULONG           buf_len)
{
    req->IOSer.io_Command = CMD_WRITE;
    req->IOSer.io_Data    = buf;
    req->IOSer.io_Length  = buf_len;
    SendIO((struct IORequest *)req);
}

/****************************************************************************/
void
AbortReq(struct IORequest   *req)
{
    if (!CheckIO(req))
        AbortIO(req);

    WaitIO(req);
}

/****************************************************************************/
void
Ask_WinSize_Change(void)
{
    /* ask for window status report */
    Write(Output(),"\233\060\040\161",4);
    Flush(Output());
}

/****************************************************************************/
/* END */
