summaryrefslogtreecommitdiff
path: root/plugins/telnet/telnet_proto.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/telnet/telnet_proto.c')
-rw-r--r--plugins/telnet/telnet_proto.c213
1 files changed, 213 insertions, 0 deletions
diff --git a/plugins/telnet/telnet_proto.c b/plugins/telnet/telnet_proto.c
new file mode 100644
index 0000000..523c68c
--- /dev/null
+++ b/plugins/telnet/telnet_proto.c
@@ -0,0 +1,213 @@
+#include "telnet_proto.h"
+
+#include "hybbx/socket.h"
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#define TELNET_IAC 255
+#define TELNET_DONT 254
+#define TELNET_DO 253
+#define TELNET_WONT 252
+#define TELNET_WILL 251
+#define TELNET_SB 250
+#define TELNET_GA 249
+#define TELNET_EL 248
+#define TELNET_EC 247
+#define TELNET_AYT 246
+#define TELNET_AO 245
+#define TELNET_IP 244
+#define TELNET_BRK 243
+#define TELNET_DM 242
+#define TELNET_NOP 241
+#define TELNET_SE 240
+
+#define TELNET_OPT_ECHO 1
+#define TELNET_OPT_SGA 3
+#define TELNET_OPT_TTYPE 24
+#define TELNET_OPT_NAWS 31
+#define TELNET_OPT_LINEMODE 34
+#define TELNET_OPT_CHARSET 42
+
+enum telnet_parse_state {
+ TS_DATA = 0,
+ TS_IAC,
+ TS_CMD,
+ TS_SUBNEG,
+ TS_SUBNEG_IAC
+};
+
+static hybbx_result_t telnet_send_raw(int fd, const uint8_t *data, size_t len)
+{
+ size_t sent_total = 0;
+
+ while (sent_total < len) {
+ ssize_t sent;
+
+ sent = send(fd, data + sent_total, len - sent_total, MSG_NOSIGNAL);
+ if (sent < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return HYBBX_ERR_IO;
+ }
+ if (sent == 0) {
+ return HYBBX_ERR_IO;
+ }
+
+ sent_total += (size_t)sent;
+ }
+
+ return HYBBX_OK;
+}
+
+static hybbx_result_t telnet_send_option(int fd, uint8_t cmd, uint8_t option)
+{
+ uint8_t buf[3];
+
+ buf[0] = TELNET_IAC;
+ buf[1] = cmd;
+ buf[2] = option;
+ return telnet_send_raw(fd, buf, sizeof(buf));
+}
+
+static hybbx_result_t telnet_reply_do(int fd, uint8_t option)
+{
+ switch (option) {
+ case TELNET_OPT_ECHO:
+ return telnet_send_option(fd, TELNET_WONT, option);
+ case TELNET_OPT_SGA:
+ return telnet_send_option(fd, TELNET_WILL, option);
+ case TELNET_OPT_TTYPE:
+ case TELNET_OPT_NAWS:
+ case TELNET_OPT_LINEMODE:
+ case TELNET_OPT_CHARSET:
+ return telnet_send_option(fd, TELNET_WONT, option);
+ default:
+ return telnet_send_option(fd, TELNET_WONT, option);
+ }
+}
+
+static hybbx_result_t telnet_reply_will(int fd, uint8_t option)
+{
+ switch (option) {
+ case TELNET_OPT_SGA:
+ return telnet_send_option(fd, TELNET_DO, option);
+ case TELNET_OPT_ECHO:
+ return telnet_send_option(fd, TELNET_DONT, option);
+ default:
+ return telnet_send_option(fd, TELNET_DONT, option);
+ }
+}
+
+static void emit_byte(hybbx_telnet_data_cb on_data, void *ctx, uint8_t byte)
+{
+ if (on_data != NULL) {
+ on_data(ctx, &byte, 1);
+ }
+}
+
+void hybbx_telnet_parser_init(hybbx_telnet_parser_t *parser)
+{
+ if (parser == NULL) {
+ return;
+ }
+
+ parser->state = TS_DATA;
+ parser->command = 0;
+}
+
+hybbx_result_t hybbx_telnet_send_greeting(int fd)
+{
+ static const uint8_t greeting[] = {
+ TELNET_IAC, TELNET_WONT, TELNET_OPT_ECHO,
+ TELNET_IAC, TELNET_WILL, TELNET_OPT_SGA,
+ TELNET_IAC, TELNET_DONT, TELNET_OPT_LINEMODE,
+ };
+
+ if (fd < 0) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ return telnet_send_raw(fd, greeting, sizeof(greeting));
+}
+
+hybbx_result_t hybbx_telnet_parser_feed(hybbx_telnet_parser_t *parser, int fd,
+ const uint8_t *data, size_t len,
+ hybbx_telnet_data_cb on_data,
+ void *ctx)
+{
+ size_t i;
+ hybbx_result_t rc;
+
+ if (parser == NULL || data == NULL) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ for (i = 0; i < len; i++) {
+ uint8_t byte = data[i];
+
+ switch (parser->state) {
+ case TS_DATA:
+ if (byte == TELNET_IAC) {
+ parser->state = TS_IAC;
+ } else {
+ emit_byte(on_data, ctx, byte);
+ }
+ break;
+
+ case TS_IAC:
+ if (byte == TELNET_IAC) {
+ emit_byte(on_data, ctx, TELNET_IAC);
+ parser->state = TS_DATA;
+ } else if (byte == TELNET_WILL || byte == TELNET_WONT ||
+ byte == TELNET_DO || byte == TELNET_DONT) {
+ parser->command = (int)byte;
+ parser->state = TS_CMD;
+ } else if (byte == TELNET_SB) {
+ parser->state = TS_SUBNEG;
+ } else {
+ parser->state = TS_DATA;
+ }
+ break;
+
+ case TS_CMD:
+ if (fd >= 0) {
+ if (parser->command == TELNET_DO) {
+ rc = telnet_reply_do(fd, byte);
+ if (rc != HYBBX_OK) {
+ return rc;
+ }
+ } else if (parser->command == TELNET_WILL) {
+ rc = telnet_reply_will(fd, byte);
+ if (rc != HYBBX_OK) {
+ return rc;
+ }
+ }
+ }
+ parser->state = TS_DATA;
+ break;
+
+ case TS_SUBNEG:
+ if (byte == TELNET_IAC) {
+ parser->state = TS_SUBNEG_IAC;
+ }
+ break;
+
+ case TS_SUBNEG_IAC:
+ if (byte == TELNET_SE) {
+ parser->state = TS_DATA;
+ } else {
+ parser->state = TS_SUBNEG;
+ }
+ break;
+
+ default:
+ parser->state = TS_DATA;
+ break;
+ }
+ }
+
+ return HYBBX_OK;
+}
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com