/* $Id: telnet.c,v 1.8 2003/04/13 09:14:36 steve Exp $ */ /*- * Copyright (c) 2001 Steve C. Woodford. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Steve C. Woodford. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include "context.h" #include "dispatcher.h" #include "telnet.h" #include "buffer.h" #include "tty.h" static int telnet_init(struct client_ctx *, const void *); static void telnet_destroy(struct client_ctx *); static int telnet_event(struct client_ctx *, int); static int telnet_output(struct client_ctx *, const char *, size_t, struct client_ctx *); static int telnet_write_pending(struct client_ctx *); struct client_ops telnet_ops = { "telnet", NULL, telnet_init, telnet_destroy, telnet_event, telnet_output, NULL, telnet_write_pending, NULL }; struct client_ops raw_ops = { "raw", NULL, telnet_init, telnet_destroy, telnet_event, telnet_output, NULL, telnet_write_pending, NULL }; enum telnet_mode { TM_TELNET, TM_RAW }; struct telnet_ctx { enum telnet_mode tc_mode; size_t tc_skipcnt; void (*tc_handler)(struct client_ctx *, unsigned char); struct buffer_ctx *tc_buff; void *tc_timeout; int tc_sent_disconnect; }; static void telnet_opt_dont(struct telnet_ctx *, int); static void telnet_opt_do(struct telnet_ctx *, int); static void telnet_opt_will(struct telnet_ctx *, int); static void telnet_command(struct client_ctx *, unsigned char); static void telnet_timeout(void *, void *); #ifndef MIN #define MIN(a,b) (((a) < (b)) ? (a) : (b)) #endif static int telnet_init(struct client_ctx *cc, const void *arg) { struct sockaddr_storage from; struct sockaddr *sa; socklen_t fromlen; struct telnet_ctx *tc; char buff[NI_MAXHOST]; int on = 1; if ((tc = calloc(1, sizeof(*tc))) == NULL) return (-1); cc->cc_fd = *((const int *)arg); sa = (struct sockaddr *)((void *)&from); fromlen = sizeof(from); if (getpeername(cc->cc_fd, sa, &fromlen) < 0) { (void) free(tc); return (-1); } setsockopt(cc->cc_fd, SOL_SOCKET, SO_KEEPALIVE, (char *)((void *)&on),sizeof(on)); if (getnameinfo(sa, sa->sa_len, buff, sizeof(buff), NULL, 0, 0) != 0) { (void) free(tc); return (-1); } if (cc->cc_ops == &telnet_ops) tc->tc_mode = TM_TELNET; else tc->tc_mode = TM_RAW; if (buffer_init(&tc->tc_buff, cc->cc_fd) < 0) { (void) free(tc); return (-1); } if (cc->cc_options.co_timeout) { tc->tc_timeout = dispatcher_init_timeout(telnet_timeout, cc); if (tc->tc_timeout == NULL) { buffer_destroy(tc->tc_buff); (void) free(tc); return (-1); } dispatcher_sched_timeout(tc->tc_timeout, cc->cc_options.co_timeout * 60); } if (tc->tc_mode == TM_TELNET) { /* * Assume the other end is a `telnet' client, which will default * to line-mode. We need to tell it do switch out of line-mode * and deal with just binary data. */ telnet_opt_do(tc, TELOPT_SGA); telnet_opt_will(tc, TELOPT_SGA); telnet_opt_will(tc, TELOPT_ECHO); telnet_opt_dont(tc, TELOPT_LINEMODE); } cc->cc_name = strdup(buff); cc->cc_data = tc; return (0); } static void telnet_destroy(struct client_ctx *cc) { struct telnet_ctx *tc = cc->cc_data; (void) close(cc->cc_fd); (void) free(cc->cc_name); buffer_destroy(tc->tc_buff); if (tc->tc_timeout) dispatcher_destroy_timeout(tc->tc_timeout); (void) free(tc); } static int telnet_event(struct client_ctx *cc, int events) { struct telnet_ctx *tc = cc->cc_data; ssize_t rv; /* * If we succeeded in sending the idle timeout disconnect message, * force the client to be destroyed. */ if (tc->tc_sent_disconnect && buffer_length(tc->tc_buff) == 0) return (-1); if (events & POLLRDNORM) { /* * Read data from the client */ int nbytes; if (ioctl(cc->cc_fd, FIONREAD, &nbytes) < 0 || nbytes == 0) return (-1); while (nbytes) { unsigned char inb[128]; rv = read(cc->cc_fd, inb, MIN(nbytes, sizeof(inb))); if (rv <= 0) return (-1); if (tc->tc_mode == TM_TELNET) { unsigned char outb[128], *in, *out; ssize_t i; for (i = 0, in = inb, out = outb; i < rv; i++, in++) { if (tc->tc_handler) { (tc->tc_handler)(cc, *in); continue; } if (tc->tc_skipcnt) { tc->tc_skipcnt--; continue; } if (*in == IAC) { tc->tc_handler = telnet_command; continue; } if (*in == '\n' || *in == '\0') continue; *out++ = *in; } if (out != outb) (void) context_server_output(cc->cc_ctx, (const char *)outb, (size_t)(out - outb), cc); } else { /* RAW mode */ (void) context_server_output(cc->cc_ctx, inb, (size_t)rv, cc); } nbytes -= rv; } if (tc->tc_timeout) dispatcher_sched_timeout(tc->tc_timeout, cc->cc_options.co_timeout * 60); } if ((events & POLLWRNORM) != 0) return ((int) buffer_drain(tc->tc_buff)); return (0); } /* ARGSUSED */ static int telnet_output(struct client_ctx *cc, const char *buff, size_t len, struct client_ctx *sender) { struct telnet_ctx *tc = cc->cc_data; return buffer_fill(tc->tc_buff, buff, len); } static int telnet_write_pending(struct client_ctx *cc) { struct telnet_ctx *tc = cc->cc_data; return (buffer_length(tc->tc_buff) != 0); } static void telnet_opt_dont(struct telnet_ctx *tc, int opt) { unsigned const char dont[] = { IAC, DONT, '%', 'c', 0 }; char buff[8]; (void) sprintf(buff, (const char *)dont, opt); (void) buffer_fill(tc->tc_buff, buff, sizeof(dont) - 2); } static void telnet_opt_do(struct telnet_ctx *tc, int opt) { unsigned const char doopt[] = { IAC, DO, '%', 'c', 0 }; char buff[8]; (void) sprintf(buff, (const char *)doopt, opt); (void) buffer_fill(tc->tc_buff, buff, sizeof(doopt) - 2); } static void telnet_opt_will(struct telnet_ctx *tc, int opt) { unsigned const char will[] = { IAC, WILL, '%', 'c', 0 }; char buff[8]; (void) sprintf(buff, (const char *)will, opt); (void) buffer_fill(tc->tc_buff, buff, sizeof(will) - 2); } static void telnet_command(struct client_ctx *cc, unsigned char cmd) { struct telnet_ctx *tc = cc->cc_data; switch (cmd) { case DONT: case DO: case WONT: case WILL: tc->tc_skipcnt = 1; break; case BREAK: context_server_ioctl(cc->cc_ctx, TTY_IOCTL_SENDBREAK, NULL, cc); break; default: break; } tc->tc_handler = NULL; } static void telnet_timeout(void *cookie, void *arg) { struct client_ctx *cc = arg; struct telnet_ctx *tc = cc->cc_data; static const char discon_msg[] = "\r\nDisconnected due to lack of activity\r\n"; /* * If we timed out while trying to send the `Disconnect due to timeout' * message, forcibly disconnect the client. */ if (tc->tc_sent_disconnect) { context_del_client(cc->cc_ctx, cc); return; } /* * Otherwise, tell the client they are being disconnected. */ if (client_output(cc, discon_msg, sizeof(discon_msg) - 1, NULL) < 0) { context_del_client(cc->cc_ctx, cc); return; } /* * Flag that we sent the message, and give ourselves the option * to force the disconnection after 10 seconds. This should prevent * problems if the socket has backed up due to congestion. */ tc->tc_sent_disconnect = 1; dispatcher_sched_timeout(tc->tc_timeout, 10); }