diff options
Diffstat (limited to 'accel-pppd/cli/telnet.c')
-rw-r--r-- | accel-pppd/cli/telnet.c | 757 |
1 files changed, 757 insertions, 0 deletions
diff --git a/accel-pppd/cli/telnet.c b/accel-pppd/cli/telnet.c new file mode 100644 index 0000000..5176df2 --- /dev/null +++ b/accel-pppd/cli/telnet.c @@ -0,0 +1,757 @@ +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <time.h> +#include <ctype.h> +#include <arpa/inet.h> +#include <arpa/telnet.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "triton.h" +#include "log.h" +#include "ppp.h" +#include "list.h" +#include "memdebug.h" + +#include "cli_p.h" + +#define RECV_BUF_SIZE 1024 + +#define MSG_AUTH_FAILED "\r\nAuthentication failed\r\n" +#define MSG_SHUTDOWN_IN_PROGRESS "note: 'shutdown soft' is in progress...\r\n" + +#define ESC_LEFT "[D" +#define ESC_RIGHT "[C" +#define ESC_UP "[A" +#define ESC_DOWN "[B" + +struct telnet_client_t +{ + struct cli_client_t cli_client; + struct list_head entry; + struct triton_md_handler_t hnd; + struct list_head xmit_queue; + struct buffer_t *xmit_buf; + int xmit_pos; + struct list_head history; + struct list_head *history_pos; + uint8_t *cmdline; + int cmdline_pos; + int cmdline_pos2; + int cmdline_len; + int auth:1; + int echo:1; + int telcmd:1; + int esc:1; + int disconnect:1; +}; + +struct buffer_t +{ + struct list_head entry; + int size; + struct buffer_t *p_buf; + uint8_t buf[0]; +}; + +static struct triton_context_t serv_ctx; +static struct triton_md_handler_t serv_hnd; +static LIST_HEAD(clients); + +static uint8_t *recv_buf; +static uint8_t *temp_buf; + +static int conf_history_len = 100; +static const char *conf_history_file = "/var/run/accel-ppp/history"; +static LIST_HEAD(history); +static int history_len; +static pthread_mutex_t history_lock = PTHREAD_MUTEX_INITIALIZER; + +static void disconnect(struct telnet_client_t *cln) +{ + struct buffer_t *b, *b2; + + log_debug("cli: disconnect\n"); + + triton_stop_collect_cpu_usage(); + + list_del(&cln->entry); + + triton_md_unregister_handler(&cln->hnd); + close(cln->hnd.fd); + + if (cln->xmit_buf) + _free(cln->xmit_buf); + + while (!list_empty(&cln->xmit_queue)) { + b = list_entry(cln->xmit_queue.next, typeof(*b), entry); + list_del(&b->entry); + _free(b); + } + + pthread_mutex_lock(&history_lock); + while (!list_empty(&cln->history)) { + b = list_entry(cln->history.prev, typeof(*b), entry); + list_del(&b->entry); + if (!b->p_buf) { + if (history_len == conf_history_len) { + b2 = list_entry(history.next, typeof(*b2), entry); + list_del(&b2->entry); + _free(b2); + } else + history_len++; + list_add_tail(&b->entry, &history); + } else + _free(b); + } + pthread_mutex_unlock(&history_lock); + + _free(cln->cmdline); + _free(cln); +} + +static void cli_client_disconnect(struct cli_client_t *tcln) +{ + struct telnet_client_t *cln = container_of(tcln, typeof(*cln), cli_client); + cln->disconnect = 1; +} + +static void queue_buffer(struct telnet_client_t *cln, struct buffer_t *b) +{ + if (cln->xmit_buf) + list_add_tail(&b->entry, &cln->xmit_queue); + else + cln->xmit_buf = b; +} + +static int telnet_send(struct telnet_client_t *cln, const void *_buf, int size) +{ + int n, k; + struct buffer_t *b; + const uint8_t *buf = (const uint8_t *)_buf; + + if (cln->disconnect) + return -1; + + if (!list_empty(&cln->xmit_queue)) { + b = _malloc(sizeof(*b) + size); + b->size = size; + memcpy(b->buf, buf, size); + queue_buffer(cln, b); + return 0; + } + + for (n = 0; n < size; n += k) { + k = write(cln->hnd.fd, buf + n, size - n); + if (k < 0) { + if (errno == EAGAIN) { + b = _malloc(sizeof(*b) + size - n); + b->size = size - n; + memcpy(b->buf, buf, size - n); + queue_buffer(cln, b); + + triton_md_enable_handler(&cln->hnd, MD_MODE_WRITE); + break; + } + if (errno != EPIPE) + log_error("cli: write: %s\n", strerror(errno)); + //disconnect(cln); + cln->disconnect = 1; + return -1; + } + } + return 0; +} + +static int cli_client_send(struct cli_client_t *tcln, const void *buf, int size) +{ + struct telnet_client_t *cln = container_of(tcln, typeof(*cln), cli_client); + return telnet_send(cln, buf, size); +} + +static int cli_client_sendv(struct cli_client_t *tcln, const char *fmt, va_list ap) +{ + struct telnet_client_t *cln = container_of(tcln, typeof(*cln), cli_client); + int r = vsnprintf((char *)temp_buf, RECV_BUF_SIZE, fmt, ap); + + if (r >= RECV_BUF_SIZE) { + strcpy((char *)temp_buf + RECV_BUF_SIZE - 6, "...\r\n"); + r = RECV_BUF_SIZE; + } + + return telnet_send(cln, temp_buf, r); +} + +static int send_banner(struct telnet_client_t *cln) +{ + if (telnet_send(cln, "accel-ppp version " ACCEL_PPP_VERSION "\r\n", sizeof("accel-ppp version " ACCEL_PPP_VERSION "\r\n"))) + return -1; + return 0; +} + +static int send_config(struct telnet_client_t *cln) +{ + uint8_t buf[] = {IAC, WILL, TELOPT_ECHO, IAC, WILL, TELOPT_SGA, IAC, DONT, TELOPT_LINEMODE}; + return telnet_send(cln, buf, sizeof(buf)); +} + +static int send_password_request(struct telnet_client_t *cln) +{ + uint8_t buf0[] = {IAC, WILL, TELOPT_ECHO}; + uint8_t buf1[] = "Password: "; + + if (telnet_send(cln, buf0, sizeof(buf0))) + return -1; + + if (telnet_send(cln, buf1, sizeof(buf1))) + return -1; + + return 0; +} + +static int send_prompt(struct telnet_client_t *cln) +{ + sprintf((char *)temp_buf, "%s%s# ", conf_cli_prompt, ppp_shutdown ? "(shutdown)" : ""); + return telnet_send(cln, temp_buf, strlen((char *)temp_buf)); +} + +/*static void print_buf(const uint8_t *buf, int size) +{ + int i; + + for (i = 0; i < size; i++) + log_debug("%x ", buf[i]); + log_debug("\n"); +}*/ + +static int send_cmdline_tail(struct telnet_client_t *cln, int corr) +{ + if (telnet_send(cln, cln->cmdline + cln->cmdline_pos, cln->cmdline_len - cln->cmdline_pos)) + return -1; + + memset(temp_buf, '\b', cln->cmdline_len - cln->cmdline_pos - corr); + + if (telnet_send(cln, temp_buf, cln->cmdline_len - cln->cmdline_pos - corr)) + return -1; + + return 0; +} + +static int load_history(struct telnet_client_t *cln) +{ + struct buffer_t *b = list_entry(cln->history_pos, typeof(*b), entry); + if (b->size < cln->cmdline_len) { + memset(temp_buf, '\b', cln->cmdline_len - b->size); + memset(temp_buf + cln->cmdline_len - b->size, ' ', cln->cmdline_len - b->size); + if (telnet_send(cln, temp_buf, (cln->cmdline_len - b->size) * 2)) + return -1; + } + if (telnet_send(cln, "\r", 1)) + return -1; + if (send_prompt(cln)) + return -1; + memcpy(cln->cmdline, b->p_buf ? b->p_buf->buf : b->buf, b->size); + cln->cmdline_pos = b->size; + cln->cmdline_len = b->size; + if (telnet_send(cln, b->p_buf ? b->p_buf->buf : b->buf, b->size)) + return -1; + + return 0; +} + +static int telnet_input_char(struct telnet_client_t *cln, uint8_t c) +{ + uint8_t buf[] = {IAC, DONT, 0}; + struct buffer_t *b; + + if (c == '\n') + return 0; + + if (c == '\r') { + cln->cmdline[cln->cmdline_len] = 0; + + if (cln->echo) { + if (telnet_send(cln, "\r\n", 2)) + return -1; + } + + if (!cln->auth) { + if (strcmp((char *)cln->cmdline, conf_cli_passwd)) { + if (telnet_send(cln, MSG_AUTH_FAILED, sizeof(MSG_AUTH_FAILED))) + return -1; + cln->disconnect = 1; + return -1; + } + cln->auth = 1; + if (ppp_shutdown) { + if (telnet_send(cln, MSG_SHUTDOWN_IN_PROGRESS, sizeof(MSG_SHUTDOWN_IN_PROGRESS))) + return -1; + } + } else if (cln->cmdline_len) { + b = _malloc(sizeof(*b) + cln->cmdline_len); + b->p_buf = NULL; + memcpy(b->buf, cln->cmdline, cln->cmdline_len); + b->size = cln->cmdline_len; + list_add(&b->entry, cln->history.next); + cln->history_pos = cln->history.next; + + if (cli_process_cmd(&cln->cli_client)) + return -1; + } + + cln->cmdline_pos = 0; + cln->cmdline_len = 0; + + return send_prompt(cln); + } + + if (cln->telcmd) { + if (cln->cmdline_pos2 == RECV_BUF_SIZE - 1) { + log_error("cli: buffer overflow, dropping connection ...\n"); + disconnect(cln); + return -1; + } + + cln->cmdline[cln->cmdline_pos2] = c; + cln->cmdline_pos2++; + + if (cln->cmdline[cln->cmdline_len] >= WILL && cln->cmdline[cln->cmdline_len] <= DONT && cln->cmdline_pos2 - cln->cmdline_len != 2) + return 0; + + switch (cln->cmdline[cln->cmdline_len]) { + case WILL: + case WONT: + buf[2] = c; + if (telnet_send(cln, buf, 3)) + return -1; + break; + case DO: + if (c == TELOPT_ECHO) + cln->echo = 1; + break; + case SB: + if (c != SE) + return 0; + } + + cln->telcmd = 0; + } else if (cln->esc) { + if (cln->cmdline_pos2 == RECV_BUF_SIZE - 1) { + log_error("cli: buffer overflow, dropping connection ...\n"); + disconnect(cln); + return -1; + } + + cln->cmdline[cln->cmdline_pos2] = c; + cln->cmdline_pos2++; + + if (cln->cmdline_pos2 - cln->cmdline_len != 2) + return 0; + + cln->esc = 0; + + if (cln->auth) { + if (!memcmp(cln->cmdline + cln->cmdline_len, ESC_LEFT, 2)) { + if (cln->cmdline_pos) { + if (telnet_send(cln, "\b", 1)) + return -1; + cln->cmdline_pos--; + } + } else if (!memcmp(cln->cmdline + cln->cmdline_len, ESC_RIGHT, 2)) { + if (cln->cmdline_pos < cln->cmdline_len) { + if (send_cmdline_tail(cln, 1)) + return -1; + cln->cmdline_pos++; + } + } else if (!memcmp(cln->cmdline + cln->cmdline_len, ESC_UP, 2)) { + if (cln->history_pos == cln->history.next) { + b = list_entry(cln->history_pos, typeof(*b), entry); + memcpy(b->buf, cln->cmdline, cln->cmdline_len); + b->size = cln->cmdline_len; + } + cln->history_pos = cln->history_pos->next; + if (cln->history_pos == &cln->history) { + cln->history_pos = cln->history_pos->prev; + return 0; + } + if (load_history(cln)) + return -1; + } else if (!memcmp(cln->cmdline + cln->cmdline_len, ESC_DOWN, 2)) { + cln->history_pos = cln->history_pos->prev; + if (cln->history_pos == &cln->history) { + cln->history_pos = cln->history_pos->next; + return 0; + } + if (load_history(cln)) + return -1; + } + } + } else { + switch (c) { + case 0xff: + cln->cmdline_pos2 = cln->cmdline_len; + cln->telcmd = 1; + return 0; + case 0x1b: + cln->cmdline_pos2 = cln->cmdline_len; + cln->esc = 1; + return 0; + case 0x7f: + if (cln->cmdline_pos) { + if (cln->cmdline_pos < cln->cmdline_len) { + memmove(cln->cmdline + cln->cmdline_pos - 1, cln->cmdline + cln->cmdline_pos, cln->cmdline_len - cln->cmdline_pos); + + cln->cmdline[cln->cmdline_len - 1] = ' '; + + if (telnet_send(cln, "\b", 1)) + return -1; + + cln->cmdline_pos--; + + if (send_cmdline_tail(cln, 0)) + return -1; + } else { + buf[0] = '\b'; + buf[1] = ' '; + buf[2] = '\b'; + if (telnet_send(cln, buf, 3)) + return -1; + cln->cmdline_pos--; + } + + cln->cmdline_len--; + } + return 0; + case 3: + cln->disconnect = 1; + return -1; + } + + if (isprint(c)) { + if (cln->cmdline_len == RECV_BUF_SIZE - 1) + return 0; + + if (cln->cmdline_pos < cln->cmdline_len) + memmove(cln->cmdline + cln->cmdline_pos + 1, cln->cmdline + cln->cmdline_pos, cln->cmdline_len - cln->cmdline_pos); + cln->cmdline[cln->cmdline_pos] = c; + cln->cmdline_pos++; + cln->cmdline_len++; + + if (cln->echo) { + if (!cln->auth) { + if (telnet_send(cln, "*", 1)) + return -1; + } else { + if (telnet_send(cln, &c, 1)) + return -1; + } + } + + if (cln->cmdline_pos < cln->cmdline_len) { + if (send_cmdline_tail(cln, 0)) + return -1; + } + } + } + + return 0; +} + +static int cln_read(struct triton_md_handler_t *h) +{ + struct telnet_client_t *cln = container_of(h, typeof(*cln), hnd); + int i, n; + + while (1) { + n = read(h->fd, recv_buf, RECV_BUF_SIZE); + if (n == 0) { + disconnect(cln); + return -1; + } + if (n < 0) { + if (errno != EAGAIN) + log_error("cli: telnet: read: %s\n", strerror(errno)); + return 0; + } + /*log_debug("cli: read(%i): ", n); + print_buf(cln->recv_buf + cln->recv_pos, n);*/ + for (i = 0; i < n; i++) { + if (telnet_input_char(cln, recv_buf[i])) + break; + } + if (cln->disconnect) { + disconnect(cln); + return -1; + } + } + + return 0; +} + +static int cln_write(struct triton_md_handler_t *h) +{ + struct telnet_client_t *cln = container_of(h, typeof(*cln), hnd); + int k; + + while (1) { + for (; cln->xmit_pos < cln->xmit_buf->size; cln->xmit_pos += k) { + k = write(cln->hnd.fd, cln->xmit_buf->buf + cln->xmit_pos, cln->xmit_buf->size - cln->xmit_pos); + if (k < 0) { + if (errno == EAGAIN) + return 0; + if (errno != EPIPE) + log_error("cli: telnet: write: %s\n", strerror(errno)); + disconnect(cln); + return -1; + } + } + + _free(cln->xmit_buf); + cln->xmit_pos = 0; + + if (list_empty(&cln->xmit_queue)) + break; + + cln->xmit_buf = list_entry(cln->xmit_queue.next, typeof(*cln->xmit_buf), entry); + list_del(&cln->xmit_buf->entry); + } + + triton_md_disable_handler(&cln->hnd, MD_MODE_WRITE); + + return 0; +} + +static int serv_read(struct triton_md_handler_t *h) +{ + struct sockaddr_in addr; + socklen_t size = sizeof(addr); + int sock; + struct telnet_client_t *conn; + struct buffer_t *b, *b2; + + while(1) { + sock = accept(h->fd, (struct sockaddr *)&addr, &size); + if (sock < 0) { + if (errno == EAGAIN) + return 0; + log_error("cli: telnet: accept failed: %s\n", strerror(errno)); + continue; + } + + log_info2("cli: telnet: new connection from %s\n", inet_ntoa(addr.sin_addr)); + + if (fcntl(sock, F_SETFL, O_NONBLOCK)) { + log_error("cli: telnet: failed to set nonblocking mode: %s, closing connection...\n", strerror(errno)); + close(sock); + continue; + } + + conn = _malloc(sizeof(*conn)); + memset(conn, 0, sizeof(*conn)); + conn->hnd.fd = sock; + conn->hnd.read = cln_read; + conn->hnd.write = cln_write; + conn->cmdline = _malloc(RECV_BUF_SIZE); + INIT_LIST_HEAD(&conn->xmit_queue); + INIT_LIST_HEAD(&conn->history); + + b = _malloc(sizeof(*b) + RECV_BUF_SIZE); + b->p_buf = b; + b->size = 0; + list_add_tail(&b->entry, &conn->history); + + pthread_mutex_lock(&history_lock); + list_for_each_entry(b, &history, entry) { + b2 = _malloc(sizeof(*b)); + b2->p_buf = b; + b2->size = b->size; + list_add(&b2->entry, conn->history.next); + } + pthread_mutex_unlock(&history_lock); + + conn->history_pos = conn->history.next; + + conn->cli_client.cmdline = conn->cmdline; + conn->cli_client.send = cli_client_send; + conn->cli_client.sendv = cli_client_sendv; + conn->cli_client.disconnect = cli_client_disconnect; + + triton_md_register_handler(&serv_ctx, &conn->hnd); + triton_md_enable_handler(&conn->hnd,MD_MODE_READ); + + list_add_tail(&conn->entry, &clients); + + if (send_banner(conn)) + continue; + + if (send_config(conn)) + continue; + + if (conf_cli_passwd) + send_password_request(conn); + else { + conn->auth = 1; + if (ppp_shutdown) { + if (telnet_send(conn, MSG_SHUTDOWN_IN_PROGRESS, sizeof(MSG_SHUTDOWN_IN_PROGRESS))) + continue; + } + send_prompt(conn); + } + triton_collect_cpu_usage(); + } + return 0; +} +static void serv_close(struct triton_context_t *ctx) +{ + struct telnet_client_t *cln; + + while (!list_empty(&clients)) { + cln = list_entry(clients.next, typeof(*cln), entry); + disconnect(cln); + } + + triton_md_unregister_handler(&serv_hnd); + close(serv_hnd.fd); + triton_context_unregister(ctx); +} + +static struct triton_context_t serv_ctx = { + .close = serv_close, + .before_switch = log_switch, +}; + +static struct triton_md_handler_t serv_hnd = { + .read = serv_read, +}; + +static void start_server(const char *host, int port) +{ + struct sockaddr_in addr; + + serv_hnd.fd = socket(PF_INET, SOCK_STREAM, 0); + if (serv_hnd.fd < 0) { + log_emerg("cli: telnet: failed to create server socket: %s\n", strerror(errno)); + return; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if (host) + addr.sin_addr.s_addr = inet_addr(host); + else + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + setsockopt(serv_hnd.fd, SOL_SOCKET, SO_REUSEADDR, &serv_hnd.fd, 4); + if (bind (serv_hnd.fd, (struct sockaddr *) &addr, sizeof (addr)) < 0) { + log_emerg("cli: telnet: failed to bind socket: %s\n", strerror(errno)); + close(serv_hnd.fd); + return; + } + + if (listen (serv_hnd.fd, 1) < 0) { + log_emerg("cli: telnet: failed to listen socket: %s\n", strerror(errno)); + close(serv_hnd.fd); + return; + } + + if (fcntl(serv_hnd.fd, F_SETFL, O_NONBLOCK)) { + log_emerg("cli: telnet: failed to set nonblocking mode: %s\n", strerror(errno)); + close(serv_hnd.fd); + return; + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = inet_addr(host); + + triton_context_register(&serv_ctx, NULL); + triton_context_set_priority(&serv_ctx, 1); + triton_md_register_handler(&serv_ctx, &serv_hnd); + triton_md_enable_handler(&serv_hnd, MD_MODE_READ); + triton_context_wakeup(&serv_ctx); +} + +static void save_history_file(void) +{ + int fd; + struct buffer_t *b; + + fd = open(conf_history_file, O_WRONLY | O_TRUNC | O_CREAT, S_IREAD | S_IWRITE); + if (!fd) + return; + + list_for_each_entry(b, &history, entry) { + b->buf[b->size] = '\n'; + write(fd, b->buf, b->size + 1); + } + + close(fd); +} + +static void load_history_file(void) +{ + struct buffer_t *b; + FILE *f; + + f = fopen(conf_history_file, "r"); + if (!f) + return; + + while (fgets((char *)temp_buf, RECV_BUF_SIZE, f)) { + b = _malloc(sizeof(*b) + strlen((char *)temp_buf)); + b->p_buf = NULL; + b->size = strlen((char *)temp_buf) - 1; + memcpy(b->buf, temp_buf, b->size); + list_add_tail(&b->entry, &history); + } + + fclose(f); +} + +static void __init init(void) +{ + const char *opt; + char *host, *d; + int port; + + opt = conf_get_opt("cli", "telnet"); + if (!opt) + return; + + host = strdup(opt); + d = strstr(host, ":"); + if (!d) + goto err_fmt; + + *d = 0; + port = atoi(d + 1); + if (port <= 0) + goto err_fmt; + + opt = conf_get_opt("cli", "history-file"); + if (opt) + conf_history_file = _strdup(opt); + + recv_buf = malloc(RECV_BUF_SIZE); + temp_buf = malloc(RECV_BUF_SIZE); + + load_history_file(); + + start_server(host, port); + + atexit(save_history_file); + + return; +err_fmt: + log_emerg("cli: telnet: invalid format\n"); + free(host); +} + |