summaryrefslogtreecommitdiffstats
path: root/common/sys
diff options
context:
space:
mode:
Diffstat (limited to 'common/sys')
-rw-r--r--common/sys/Makefile2
-rw-r--r--common/sys/telnet.c344
2 files changed, 345 insertions, 1 deletions
diff --git a/common/sys/Makefile b/common/sys/Makefile
index b92d746d..717ccb0f 100644
--- a/common/sys/Makefile
+++ b/common/sys/Makefile
@@ -3,7 +3,7 @@
SRCROOT= ../..
.include "$(SRCROOT)/pttbbs.mk"
-SRCS:= daemon.c file.c lock.c log.c net.c sort.c string.c time.c crypt.c record.c vector.c
+SRCS:= daemon.c file.c lock.c log.c net.c sort.c string.c time.c crypt.c record.c vector.c telnet.c
LIB:= cmsys
all: .depend
diff --git a/common/sys/telnet.c b/common/sys/telnet.c
new file mode 100644
index 00000000..14b39c39
--- /dev/null
+++ b/common/sys/telnet.c
@@ -0,0 +1,344 @@
+/*
+ * piaip's simplified implementation of TELNET protocol
+ */
+#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) ;
+
+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_ctx_init(TelnetCtx *ctx, const struct TelnetCallback *callback, int fd)
+{
+ memset(ctx, 0, sizeof(TelnetCtx));
+
+ ctx->callback = callback;
+ ctx->fd = fd;
+}
+
+void telnet_ctx_set_ccctx(TelnetCtx *ctx, void *ccctx)
+{
+ ctx->ccctx = ccctx;
+}
+
+/* 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_send_init_cmds(int fd)
+{
+ write(fd, telnet_init_cmds, sizeof(telnet_init_cmds));
+}
+
+ssize_t telnet_process(TelnetCtx *ctx, unsigned char *buf, size_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 && 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
+
+/* 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->ccctx && 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->ccctx, IAC);
+ cb(ctx->ccctx, 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 */
+ write(ctx->fd, "-", 1);
+ if(TELCMD_OK(cx))
+ write(ctx->fd, TELCMD(c), strlen(TELCMD(c)));
+ write(ctx->fd, " ", 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 */
+ {
+#if 0
+ const char *alive = "I'm still alive, loading: ";
+ char buf[STRLEN];
+
+ /* respond as fast as we can */
+ write(ctx->fd, alive, strlen(alive));
+ cpuload(buf);
+ write(ctx->fd, buf, strlen(buf));
+ write(ctx->fd, "\r\n", 2);
+#else
+ const char *alive = "I'm still alive\r\n";
+ /* respond as fast as we can */
+ write(ctx->fd, alive, strlen(alive));
+#endif
+ }
+ 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
+ write(ctx->fd, "-", 1);
+ if(TELOPT_OK(c))
+ write(ctx->fd, TELOPT(c), strlen(TELOPT(c)));
+ write(ctx->fd, " ", 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 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 (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;
+ write(ctx->fd, 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
+ write(ctx->fd, "-", 1);
+ if(TELOPT_OK(ctx->iac_buf[0]))
+ write(ctx->fd, TELOPT(ctx->iac_buf[0]), strlen(TELOPT(ctx->iac_buf[0])));
+ write(ctx->fd, " ", 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(w, h);
+ if (ctx->ccctx &&
+ ctx->callback->update_client_code) {
+ void (*cb)(void *, unsigned char) =
+ ctx->callback->update_client_code;
+ cb(ctx->ccctx, ctx->iac_buf[0]);
+
+ if(w==80 && h==24)
+ cb(ctx->ccctx, 1);
+ else if(w==80)
+ cb(ctx->ccctx, 2);
+ else if(h==24)
+ cb(ctx->ccctx, 3);
+ else
+ cb(ctx->ccctx, 4);
+ cb(ctx->ccctx, IAC);
+ cb(ctx->ccctx, SE);
+ }
+ }
+ break;
+
+ default:
+ if (ctx->ccctx &&
+ 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->ccctx, ctx->iac_buf[i]);
+ cb(ctx->ccctx, IAC);
+ cb(ctx->ccctx, SE);
+ }
+ break;
+ }
+ return 1;
+ }
+ return 1; /* never reached */
+}
+
+// vim: sw=4