summaryrefslogblamecommitdiffstats
path: root/common/sys/telnet.c
blob: 7d3d0eea847f09944363830c2de74c302b3c225e (plain) (tree)
1
2
3
4
5
6

                                                       

                                                              
   
 













                                                                     
                                                                                   









                        











                                                                       




                                        



                                                                                   

                                           



                             
                                                        
 
                         

 









                                                                




                                                          






















                                                          
                                              
 
                                                                  

 
                                                                        







                                         


                                           
















                                              








                                                                        
















                                                                                            












                                                           
                                                           





                                              

                                     





















                                                                              
                                          
                                 

                                                                    



































                                                                               
                                         






















                                                                       
                                      
                            

                                                                











                                                                                       
                                                      


















                                                                           
                                                            





















                                                                            
                                      
                                          

                                                                                            








                                                                           

                                                                              


                                                                    
                                                             

                                              
                                                   
                                          
                                                   
                                          
                                                   
                                


                                                   




                          
                                      





                                                                


                                                             








                                 
/*
 * piaip's simplified implementation of TELNET protocol
 * Copyright (c) 2005-2009 Hung-Te Lin <piaip@csie.ntu.edu.tw>
 * Improved by Kuang 2009
 */

#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>

#ifdef DEBUG
#define TELOPTS
#define TELCMDS
#endif
#include <arpa/telnet.h>

#include "cmsys.h"

static unsigned int telnet_handler(TelnetCtx *ctx, unsigned char c) ;
static void         telnet_write  (TelnetCtx *ctx, const void *buf, size_t nbytes);

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

/* 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.
 */

TelnetCtx *telnet_create_contex(void)
{
    return (TelnetCtx*) malloc(sizeof(TelnetCtx));
}

void telnet_free_context(TelnetCtx* ctx)
{
    free(ctx);
}

void telnet_ctx_init(TelnetCtx *ctx, const struct TelnetCallback *callback, int fd)
{
    memset(ctx, 0, sizeof(TelnetCtx));

    // callback structure must be provided.
    assert(callback);
    ctx->callback = callback;
    ctx->fd = fd;
}

void telnet_ctx_set_cc_arg(TelnetCtx *ctx, void *cc_arg)
{
    ctx->cc_arg = cc_arg;
}

void telnet_ctx_set_write_arg(TelnetCtx *ctx, void *write_arg)
{
    ctx->write_arg = write_arg;
}

void telnet_ctx_set_resize_arg(TelnetCtx *ctx, void *resize_arg)
{
    ctx->resize_arg = resize_arg;
}

void telnet_ctx_set_ayt_arg(TelnetCtx *ctx, void *ayt_arg)
{
    ctx->ayt_arg = ayt_arg;
}

/* 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.
 */
static 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,
};

void telnet_ctx_send_init_cmds(TelnetCtx *ctx)
{
    telnet_write(ctx, telnet_init_cmds, sizeof(telnet_init_cmds));
}

ssize_t telnet_process(TelnetCtx *ctx, unsigned char *buf, ssize_t size)
{
    /* process buffer */
    if (size > 0) {
    unsigned char *buf2 = buf;
    size_t i = 0, i2 = 0;

    /* prescan. because IAC is rare, 
     * this cost is worthy. */
    if (ctx->iac_state == IAC_NONE &&
        !ctx->iac_quote &&
        memchr(buf, IAC, size) == NULL)
        return size;

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

#ifdef DBG_OUTRPT
extern unsigned char fakeEscape;
#endif // DBG_OUTRPT

static void         
telnet_write  (TelnetCtx *ctx, const void *buf, size_t nbytes)
{
    if (ctx->callback->write_data)
    ctx->callback->write_data(ctx->write_arg, ctx->fd, buf, nbytes);
    else
    write(ctx->fd, buf, nbytes);
}

#ifndef _TELNET_DEFAULT_AYT_STRING
#define _TELNET_DEFAULT_AYT_STRING  "I'm still alive.\r\n"
#endif
static void         
telnet_send_ayt  (TelnetCtx *ctx)
{
    if (ctx->callback->send_ayt)
    {
    ctx->callback->send_ayt(ctx->ayt_arg, ctx->fd);
    }
    else
    {
    /* respond as fast as we can */
    telnet_write(ctx, _TELNET_DEFAULT_AYT_STRING, sizeof(_TELNET_DEFAULT_AYT_STRING)-1);
    }
}

/* input:  raw character
 * output: telnet command if c was handled, otherwise zero.
 */
static unsigned int 
telnet_handler(TelnetCtx *ctx, unsigned char c) 
{
    /* we have to quote all IACs. */
    if(c == IAC && !ctx->iac_quote) {
    ctx->iac_quote = 1;
    return NOP;
    }

    /* hash client telnet sequences */
    if (ctx->cc_arg && ctx->callback->update_client_code) {
    void (*cb)(void *, unsigned char) = 
        ctx->callback->update_client_code;
    if(ctx->iac_state == IAC_WAIT_SE) {
        // skip suboption
    } else {
        if(ctx->iac_quote)
        cb(ctx->cc_arg, IAC);
        cb(ctx->cc_arg, c);
    }
    }

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

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

    case IAC_NONE:
        return 0;

    case IAC_COMMAND:
#if 0 // def DEBUG
        {
        int cx = c; /* to make compiler happy */
        telnet_write(ctx, "-", 1);
        if(TELCMD_OK(cx))
            telnet_write(ctx, TELCMD(c), strlen(TELCMD(c)));
        telnet_write(ctx, " ", 1);
        }
#endif
        ctx->iac_state = IAC_NONE; /* by default we restore state. */
        switch(c) {
        case IAC:
            // return 0;
            // we don't want to allow IACs as input.
            return 1;

        /* we don't want to process these. or maybe in future. */
        case BREAK:           /* break */
#ifdef DBG_OUTRPT
            fakeEscape = !fakeEscape;
            return NOP;
#endif

        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 */
            telnet_send_ayt(ctx);
            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 */
            ctx->iac_opt_req = c;
            ctx->iac_state = IAC_WAIT_OPT;
            return NOP;

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

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

    case IAC_WAIT_OPT:
#if 0 // def DEBUG
        telnet_write(ctx, "-", 1);
        if(TELOPT_OK(c))
        telnet_write(ctx, TELOPT(c), strlen(TELOPT(c)));
        telnet_write(ctx, " ", 1);
#endif
        ctx->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 claimed 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 (ctx->iac_opt_req == WILL || ctx->iac_opt_req == DO)
            {
            /* unknown option, reply with won't */
            unsigned char cmd[3] = { IAC, DONT, 0 };
            if(ctx->iac_opt_req == DO) cmd[1] = WONT;
            cmd[2] = c;
            telnet_write(ctx, cmd, sizeof(cmd));
            }
            break;
        }
        return 1;

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

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

        /* resize terminal */
        case TELOPT_NAWS:
            {
            int w = (ctx->iac_buf[1] << 8) + (ctx->iac_buf[2]);
            int h = (ctx->iac_buf[3] << 8) + (ctx->iac_buf[4]);
            if (ctx->callback->term_resize)
                ctx->callback->term_resize(ctx->resize_arg, w, h);
            if (ctx->cc_arg &&
                ctx->callback->update_client_code) {
                void (*cb)(void *, unsigned char) = 
                ctx->callback->update_client_code;
                cb(ctx->cc_arg, ctx->iac_buf[0]);

                if(w==80 && h==24)
                cb(ctx->cc_arg, 1);
                else if(w==80)
                cb(ctx->cc_arg, 2);
                else if(h==24)
                cb(ctx->cc_arg, 3);
                else
                cb(ctx->cc_arg, 4);
                cb(ctx->cc_arg, IAC);
                cb(ctx->cc_arg, SE);
            }
            }
            break;

        default:
            if (ctx->cc_arg &&
                ctx->callback->update_client_code) {
            void (*cb)(void *, unsigned char) = 
                ctx->callback->update_client_code;
            
            int i;
            for(i=0;i<ctx->iac_buflen;i++)
                cb(ctx->cc_arg, ctx->iac_buf[i]);
            cb(ctx->cc_arg, IAC);
            cb(ctx->cc_arg, SE);
            }
            break;
        }
        return 1;
    }
    return 1; /* never reached */
}

// vim: sw=4