summaryrefslogblamecommitdiffstats
path: root/mbbsd/telnet.c
blob: aa9854710b2f4aa6cf55e1bd974b29623b7faa2e (plain) (tree)






































































































































































































































































































































                                                                                       
/*
 * piaip's simplified implementation of TELNET protocol
 */
#ifdef DEBUG
#define TELOPTS
#define TELCMDS
#endif

#include "bbs.h"

#ifdef DETECT_CLIENT
void UpdateClientCode(unsigned char c); // see mbbsd.c
#endif

unsigned int telnet_handler(unsigned char c) ;
void    telnet_init(void);
ssize_t tty_read(unsigned char *buf, size_t max);

enum TELNET_IAC_STATES {
    IAC_NONE,
    IAC_COMMAND,
    IAC_WAIT_OPT,
    IAC_WAIT_SE,
    IAC_PROCESS_OPT,
    IAC_ERROR
};

static unsigned char iac_state = 0; /* as byte to reduce memory */

#define TELNET_IAC_MAXLEN (16)
/* We don't reply to most commands, so this maxlen can be minimal.
 * Warning: if you want to support ENV passing or other long commands,
 * remember to increase this value. Howver, some poorly implemented
 * terminals like xxMan may not follow the protocols and user will hang
 * on those terminals when IACs were sent.
 */

void
telnet_init(void)
{
    /* We are the boss. We don't respect to client.
     * It's client's responsibility to follow us.
     * Please write these codes in i-dont-care opt handlers.
     */
    const char telnet_init_cmds[] = {
    /* retrieve terminal type and throw away.
     * why? because without this, clients enter line mode.
     */
    IAC, DO, TELOPT_TTYPE,
    IAC, SB, TELOPT_TTYPE, TELQUAL_SEND, IAC, SE,

    /* i'm a smart term with resize ability. */
    IAC, DO, TELOPT_NAWS,

    /* i will echo. */
    IAC, WILL, TELOPT_ECHO,
    /* supress ga. */
    IAC, WILL, TELOPT_SGA,
    /* 8 bit binary. */
    IAC, WILL, TELOPT_BINARY,
    IAC, DO,   TELOPT_BINARY,
    };

    raw_connection = 1;
    write(0, telnet_init_cmds, sizeof(telnet_init_cmds));
}

/* tty_read
 * read from tty, process telnet commands if raw connection.
 * return: >0 = length, <=0 means read more, abort/eof is automatically processed.
 */
ssize_t
tty_read(unsigned char *buf, size_t max)
{
    ssize_t l = read(0, buf, max);

    if(l == 0 || (l < 0 && !(errno == EINTR || errno == EAGAIN)))
    abort_bbs(0);

    if(!raw_connection)
    return l;

    /* process buffer */
    if (l > 0) {
    unsigned char *buf2 = buf;
    size_t i = 0, i2 = 0;

    /* prescan. because IAC is rare, 
     * this cost is worthy. */
    if (iac_state == IAC_NONE && memchr(buf, IAC, l) == NULL)
        return l;

    /* we have to look into the buffer. */
    for (i = 0; i < l; i++, buf++)
        if(telnet_handler(*buf) == 0)
        *(buf2++) = *buf;
        else
        i2 ++;
    l = (i2 == l) ? -1L : l - i2;
    }
    return l;
}

/* input:  raw character
 * output: telnet command if c was handled, otherwise zero.
 */
unsigned int 
telnet_handler(unsigned char c) 
{
    static unsigned char iac_quote = 0; /* as byte to reduce memory */
    static unsigned char iac_opt_req = 0;

    static unsigned char iac_buf[TELNET_IAC_MAXLEN];
    static unsigned int  iac_buflen = 0;

    /* we have to quote all IACs. */
    if(c == IAC && !iac_quote) {
    iac_quote = 1;
    return NOP;
    }

#ifdef DETECT_CLIENT
    /* hash client telnet sequences */
    if(cuser.userid[0]==0) {
    if(iac_state == IAC_WAIT_SE) {
        // skip suboption
    } else {
        if(iac_quote)
        UpdateClientCode(IAC);
        UpdateClientCode(c);
    }
    }
#endif

    /* a special case is the top level iac. otherwise, iac is just a quote. */
    if (iac_quote) {
    if(iac_state == IAC_NONE)
        iac_state = IAC_COMMAND;
    if(iac_state == IAC_WAIT_SE && c == SE)
        iac_state = IAC_PROCESS_OPT;
    iac_quote = 0;
    }

    /* now, let's process commands by state */
    switch(iac_state) {

    case IAC_NONE:
        return 0;

    case IAC_COMMAND:
#if 0 // def DEBUG
        {
        int cx = c; /* to make compiler happy */
        write(0, "-", 1);
        if(TELCMD_OK(cx))
            write(0, TELCMD(c), strlen(TELCMD(c)));
        write(0, " ", 1);
        }
#endif
        iac_state = IAC_NONE; /* by default we restore state. */
        switch(c) {
        case IAC:
            return 0;

        /* we don't want to process these. or maybe in future. */
        case BREAK:           /* break */
        case ABORT:           /* Abort process */
        case SUSP:            /* Suspend process */
        case AO:              /* abort output--but let prog finish */
        case IP:              /* interrupt process--permanently */
        case EOR:             /* end of record (transparent mode) */
        case DM:              /* data mark--for connect. cleaning */
        case xEOF:            /* End of file: EOF is already used... */
            return NOP;

        case NOP:             /* nop */
            return NOP;

        /* we should process these, but maybe in future. */
        case GA:              /* you may reverse the line */
        case EL:              /* erase the current line */
        case EC:              /* erase the current character */
            return NOP;

        /* good */
        case AYT:             /* are you there */
            {
                const char *alive = "I'm still alive, loading: ";
                char buf[STRLEN];

                /* respond as fast as we can */
                write(0, alive, strlen(alive));
                cpuload(buf);
                write(0, buf, strlen(buf));
                write(0, "\r\n", 2);
            }
            return NOP;

        case DONT:            /* you are not to use option */
        case DO:              /* please, you use option */
        case WONT:            /* I won't use option */
        case WILL:            /* I will use option */
            iac_opt_req = c;
            iac_state = IAC_WAIT_OPT;
            return NOP;

        case SB:              /* interpret as subnegotiation */
            iac_state = IAC_WAIT_SE;
            iac_buflen = 0;
            return NOP;

        case SE:              /* end sub negotiation */
        default:
            return NOP;
        }
        return 1;

    case IAC_WAIT_OPT:
#if 0 // def DEBUG
        write(0, "-", 1);
        if(TELOPT_OK(c))
        write(0, TELOPT(c), strlen(TELOPT(c)));
        write(0, " ", 1);
#endif
        iac_state = IAC_NONE;
        /*
         * According to RFC, there're some tricky steps to prevent loop.
         * However because we have a poor term which does not allow 
         * most abilities, let's be a strong boss here.
         *
         * Although my old imeplementation worked, it's even better to follow this:
         * http://www.tcpipguide.com/free/t_TelnetOptionsandOptionNegotiation-3.htm
         */
        switch(c) {
        /* i-dont-care: i don't care about what client is. 
         * these should be clamed in init and
         * client must follow me. */
        case TELOPT_TTYPE:  /* termtype or line. */
        case TELOPT_NAWS:       /* resize terminal */
        case TELOPT_SGA:    /* supress GA */
        case TELOPT_ECHO:       /* echo */
        case TELOPT_BINARY: /* we are CJK. */
            break;

        /* i-dont-agree: i don't understand/agree these.
         * according to RFC, saying NO stopped further
         * requests so there'll not be endless loop. */
        case TELOPT_RCP:         /* prepare to reconnect */
        default:
            if (iac_opt_req == WILL || iac_opt_req == DO)
            {
            /* unknown option, reply with won't */
            unsigned char cmd[3] = { IAC, DONT, 0 };
            if(iac_opt_req == DO) cmd[1] = WONT;
            cmd[2] = c;
            write(0, cmd, sizeof(cmd));
            }
            break;
        }
        return 1;

    case IAC_WAIT_SE:
        iac_buf[iac_buflen++] = c;
        /* no need to convert state because previous quoting will do. */
            
        if(iac_buflen == TELNET_IAC_MAXLEN) {
        /* may be broken protocol?
         * whether finished or not, break for safety 
         * or user may be frozen.
         */
        iac_state = IAC_NONE;
        return 0;
        }
        return 1;

    case IAC_PROCESS_OPT:
        iac_state = IAC_NONE;
#if 0 // def DEBUG
        write(0, "-", 1);
        if(TELOPT_OK(iac_buf[0]))
        write(0, TELOPT(iac_buf[0]), strlen(TELOPT(iac_buf[0])));
        write(0, " ", 1);
#endif
        switch(iac_buf[0]) {

        /* resize terminal */
        case TELOPT_NAWS:
            {
            int w = (iac_buf[1] << 8) + (iac_buf[2]);
            int h = (iac_buf[3] << 8) + (iac_buf[4]);
            term_resize(w, h);
#ifdef DETECT_CLIENT
            if(cuser.userid[0]==0) {
                UpdateClientCode(iac_buf[0]);
                if(w==80 && h==24)
                UpdateClientCode(1);
                else if(w==80)
                UpdateClientCode(2);
                else if(h==24)
                UpdateClientCode(3);
                else
                UpdateClientCode(4);
                UpdateClientCode(IAC);
                UpdateClientCode(SE);
            }
#endif
            }
            break;

        default:
#ifdef DETECT_CLIENT
            if(cuser.userid[0]==0) {
            int i;
            for(i=0;i<iac_buflen;i++)
                UpdateClientCode(iac_buf[i]);
            UpdateClientCode(IAC);
            UpdateClientCode(SE);
            }
#endif
            break;
        }
        return 1;
    }
    return 1; /* never reached */
}

// vim: sw=4