blob: 3660d788f4ad582d574aa6f070a33ef2bd70cf8f [file] [log] [blame]
Bernhard Reutner-Fischer2c998512006-04-12 18:09:26 +00001/* vi: set sw=4 ts=4: */
Bernhard Reutner-Fischerdac7ff12006-04-12 17:55:51 +00002/*
Eric Andersen08a72202002-09-30 20:52:10 +00003 * Simple telnet server
4 * Bjorn Wesen, Axis Communications AB (bjornw@axis.com)
5 *
Rob Landley1ec5b292006-05-29 07:42:02 +00006 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
Eric Andersen08a72202002-09-30 20:52:10 +00007 *
8 * ---------------------------------------------------------------------------
9 * (C) Copyright 2000, Axis Communications AB, LUND, SWEDEN
10 ****************************************************************************
11 *
12 * The telnetd manpage says it all:
13 *
14 * Telnetd operates by allocating a pseudo-terminal device (see pty(4)) for
15 * a client, then creating a login process which has the slave side of the
16 * pseudo-terminal as stdin, stdout, and stderr. Telnetd manipulates the
17 * master side of the pseudo-terminal, implementing the telnet protocol and
18 * passing characters between the remote client and the login process.
19 *
20 * Vladimir Oleynik <dzo@simtreas.ru> 2001
21 * Set process group corrections, initial busybox port
22 */
23
Denis Vlasenko75f8d082006-11-22 15:54:52 +000024#define DEBUG 0
Eric Andersen08a72202002-09-30 20:52:10 +000025
Denis Vlasenkob6adbf12007-05-26 19:00:18 +000026#include "libbb.h"
Bernhard Reutner-Fischerf4701962008-01-27 12:50:12 +000027#include <syslog.h>
Rob Landley099ed502006-08-28 09:41:49 +000028
Denis Vlasenko75f8d082006-11-22 15:54:52 +000029#if DEBUG
Eric Andersen08a72202002-09-30 20:52:10 +000030#define TELCMDS
31#define TELOPTS
32#endif
33#include <arpa/telnet.h>
Eric Andersen08a72202002-09-30 20:52:10 +000034
Denis Vlasenko59d7c432007-10-15 15:19:36 +000035/* Structure that describes a session */
Eric Andersen08a72202002-09-30 20:52:10 +000036struct tsession {
37 struct tsession *next;
Denis Vlasenko75f8d082006-11-22 15:54:52 +000038 int sockfd_read, sockfd_write, ptyfd;
Denis Vlasenko2450c452007-10-15 22:09:15 +000039 int shell_pid;
Denis Vlasenko59d7c432007-10-15 15:19:36 +000040
Eric Andersen08a72202002-09-30 20:52:10 +000041 /* two circular buffers */
Denis Vlasenko59d7c432007-10-15 15:19:36 +000042 /*char *buf1, *buf2;*/
43/*#define TS_BUF1 ts->buf1*/
44/*#define TS_BUF2 TS_BUF2*/
Denis Vlasenko10916c52007-10-15 17:28:00 +000045#define TS_BUF1 ((unsigned char*)(ts + 1))
46#define TS_BUF2 (((unsigned char*)(ts + 1)) + BUFSIZE)
Eric Andersen08a72202002-09-30 20:52:10 +000047 int rdidx1, wridx1, size1;
48 int rdidx2, wridx2, size2;
49};
50
Denis Vlasenko88308fe2007-06-25 10:35:11 +000051/* Two buffers are directly after tsession in malloced memory.
52 * Make whole thing fit in 4k */
Denis Vlasenko59d7c432007-10-15 15:19:36 +000053enum { BUFSIZE = (4 * 1024 - sizeof(struct tsession)) / 2 };
Denis Vlasenko88308fe2007-06-25 10:35:11 +000054
Eric Andersen08a72202002-09-30 20:52:10 +000055
Denis Vlasenko59d7c432007-10-15 15:19:36 +000056/* Globals */
Eric Andersen08a72202002-09-30 20:52:10 +000057static int maxfd;
Eric Andersen08a72202002-09-30 20:52:10 +000058static struct tsession *sessions;
Denis Vlasenko59d7c432007-10-15 15:19:36 +000059static const char *loginpath = "/bin/login";
Denis Vlasenko59d7c432007-10-15 15:19:36 +000060static const char *issuefile = "/etc/issue.net";
Eric Andersen08a72202002-09-30 20:52:10 +000061
62
63/*
Denis Vlasenko59d7c432007-10-15 15:19:36 +000064 Remove all IAC's from buf1 (received IACs are ignored and must be removed
65 so as to not be interpreted by the terminal). Make an uninterrupted
66 string of characters fit for the terminal. Do this by packing
67 all characters meant for the terminal sequentially towards the end of buf.
Eric Andersen08a72202002-09-30 20:52:10 +000068
69 Return a pointer to the beginning of the characters meant for the terminal.
70 and make *num_totty the number of characters that should be sent to
71 the terminal.
72
73 Note - If an IAC (3 byte quantity) starts before (bf + len) but extends
Denis Vlasenko59d7c432007-10-15 15:19:36 +000074 past (bf + len) then that IAC will be left unprocessed and *processed
75 will be less than len.
Eric Andersen08a72202002-09-30 20:52:10 +000076
77 FIXME - if we mean to send 0xFF to the terminal then it will be escaped,
78 what is the escape character? We aren't handling that situation here.
79
Denis Vlasenko10916c52007-10-15 17:28:00 +000080 CR-LF ->'s CR mapping is also done here, for convenience.
81
82 NB: may fail to remove iacs which wrap around buffer!
Denis Vlasenko75f8d082006-11-22 15:54:52 +000083 */
Denis Vlasenko10916c52007-10-15 17:28:00 +000084static unsigned char *
Denis Vlasenko75f8d082006-11-22 15:54:52 +000085remove_iacs(struct tsession *ts, int *pnum_totty)
86{
Denis Vlasenko10916c52007-10-15 17:28:00 +000087 unsigned char *ptr0 = TS_BUF1 + ts->wridx1;
Eric Andersen08a72202002-09-30 20:52:10 +000088 unsigned char *ptr = ptr0;
89 unsigned char *totty = ptr;
90 unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
Eric Andersen08a72202002-09-30 20:52:10 +000091 int num_totty;
92
93 while (ptr < end) {
94 if (*ptr != IAC) {
Denis Vlasenko10916c52007-10-15 17:28:00 +000095 char c = *ptr;
96
97 *totty++ = c;
98 ptr++;
Eric Andersen3752d332003-12-19 11:30:13 +000099 /* We now map \r\n ==> \r for pragmatic reasons.
100 * Many client implementations send \r\n when
101 * the user hits the CarriageReturn key.
102 */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000103 if (c == '\r' && ptr < end && (*ptr == '\n' || *ptr == '\0'))
Eric Andersen3752d332003-12-19 11:30:13 +0000104 ptr++;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000105 } else {
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000106 /*
107 * TELOPT_NAWS support!
108 */
109 if ((ptr+2) >= end) {
Eric Andersen08a72202002-09-30 20:52:10 +0000110 /* only the beginning of the IAC is in the
111 buffer we were asked to process, we can't
112 process this char. */
113 break;
114 }
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000115
116 /*
117 * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE
118 */
119 else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
120 struct winsize ws;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000121
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000122 if ((ptr+8) >= end)
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000123 break; /* incomplete, can't process */
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000124 ws.ws_col = (ptr[3] << 8) | ptr[4];
125 ws.ws_row = (ptr[5] << 8) | ptr[6];
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000126 ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000127 ptr += 9;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000128 } else {
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000129 /* skip 3-byte IAC non-SB cmd */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000130#if DEBUG
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000131 fprintf(stderr, "Ignoring IAC %s,%s\n",
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000132 TELCMD(ptr[1]), TELOPT(ptr[2]));
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000133#endif
134 ptr += 3;
135 }
Eric Andersen08a72202002-09-30 20:52:10 +0000136 }
137 }
138
Denis Vlasenko0de37e12007-10-17 11:08:53 +0000139 num_totty = totty - ptr0;
Eric Andersen08a72202002-09-30 20:52:10 +0000140 *pnum_totty = num_totty;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000141 /* the difference between ptr and totty is number of iacs
142 we removed from the stream. Adjust buf1 accordingly. */
143 if ((ptr - totty) == 0) /* 99.999% of cases */
144 return ptr0;
145 ts->wridx1 += ptr - totty;
146 ts->size1 -= ptr - totty;
147 /* move chars meant for the terminal towards the end of the buffer */
Eric Andersen08a72202002-09-30 20:52:10 +0000148 return memmove(ptr - num_totty, ptr0, num_totty);
149}
150
151
Eric Andersen08a72202002-09-30 20:52:10 +0000152static struct tsession *
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000153make_new_session(
Denis Vlasenkof472b232007-10-16 21:35:17 +0000154 USE_FEATURE_TELNETD_STANDALONE(int sock)
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000155 SKIP_FEATURE_TELNETD_STANDALONE(void)
156) {
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000157 const char *login_argv[2];
Eric Andersen08a72202002-09-30 20:52:10 +0000158 struct termios termbuf;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000159 int fd, pid;
Denis Vlasenko85c24712008-03-17 09:04:04 +0000160 char tty_name[GETPTY_BUFSIZE];
Rob Landley1ec5b292006-05-29 07:42:02 +0000161 struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2);
Eric Andersen08a72202002-09-30 20:52:10 +0000162
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000163 /*ts->buf1 = (char *)(ts + 1);*/
164 /*ts->buf2 = ts->buf1 + BUFSIZE;*/
Eric Andersen08a72202002-09-30 20:52:10 +0000165
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000166 /* Got a new connection, set up a tty. */
Bernhard Reutner-Fischerae4342c2008-05-19 08:18:50 +0000167 fd = xgetpty(tty_name);
Denis Vlasenkof472b232007-10-16 21:35:17 +0000168 if (fd > maxfd)
169 maxfd = fd;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000170 ts->ptyfd = fd;
171 ndelay_on(fd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000172#if ENABLE_FEATURE_TELNETD_STANDALONE
Denis Vlasenkof472b232007-10-16 21:35:17 +0000173 ts->sockfd_read = sock;
174 ndelay_on(sock);
Denis Vlasenko9e237672007-10-17 11:18:49 +0000175 if (!sock) { /* We are called with fd 0 - we are in inetd mode */
176 sock++; /* so use fd 1 for output */
177 ndelay_on(sock);
178 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000179 ts->sockfd_write = sock;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000180 if (sock > maxfd)
181 maxfd = sock;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000182#else
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000183 /* ts->sockfd_read = 0; - done by xzalloc */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000184 ts->sockfd_write = 1;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000185 ndelay_on(0);
186 ndelay_on(1);
187#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000188 /* Make the telnet client understand we will echo characters so it
189 * should not do it locally. We don't tell the client to run linemode,
190 * because we want to handle line editing and tab completion and other
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000191 * stuff that requires char-by-char support. */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000192 {
193 static const char iacs_to_send[] ALIGN1 = {
194 IAC, DO, TELOPT_ECHO,
195 IAC, DO, TELOPT_NAWS,
196 IAC, DO, TELOPT_LFLOW,
197 IAC, WILL, TELOPT_ECHO,
198 IAC, WILL, TELOPT_SGA
199 };
200 memcpy(TS_BUF2, iacs_to_send, sizeof(iacs_to_send));
201 ts->rdidx2 = sizeof(iacs_to_send);
202 ts->size2 = sizeof(iacs_to_send);
203 }
Eric Andersen08a72202002-09-30 20:52:10 +0000204
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000205 fflush(NULL); /* flush all streams */
206 pid = vfork(); /* NOMMU-friendly */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000207 if (pid < 0) {
208 free(ts);
209 close(fd);
Denis Vlasenko23c81282007-10-16 22:01:23 +0000210 /* sock will be closed by caller */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000211 bb_perror_msg("vfork");
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000212 return NULL;
Eric Andersen08a72202002-09-30 20:52:10 +0000213 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000214 if (pid > 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000215 /* Parent */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000216 ts->shell_pid = pid;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000217 return ts;
Eric Andersen08a72202002-09-30 20:52:10 +0000218 }
Denis Vlasenkof7996f32007-01-11 17:20:00 +0000219
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000220 /* Child */
221 /* Careful - we are after vfork! */
Eric Andersen08a72202002-09-30 20:52:10 +0000222
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000223 /* make new session and process group */
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000224 setsid();
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000225
Denis Vlasenko018b1552007-11-06 01:38:46 +0000226 /* Restore default signal handling */
Denis Vlasenko25591c32008-02-16 22:58:56 +0000227 bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000228
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000229 /* open the child's side of the tty. */
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000230 /* NB: setsid() disconnects from any previous ctty's. Therefore
231 * we must open child's side of the tty AFTER setsid! */
232 fd = xopen(tty_name, O_RDWR); /* becomes our ctty */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000233 dup2(fd, 0);
234 dup2(fd, 1);
235 dup2(fd, 2);
236 while (fd > 2) close(fd--);
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000237 tcsetpgrp(0, getpid()); /* switch this tty's process group to us */
Eric Andersen08a72202002-09-30 20:52:10 +0000238
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000239 /* The pseudo-terminal allocated to the client is configured to operate in
240 * cooked mode, and with XTABS CRMOD enabled (see tty(4)). */
241 tcgetattr(0, &termbuf);
242 termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000243 termbuf.c_oflag |= ONLCR | XTABS;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000244 termbuf.c_iflag |= ICRNL;
245 termbuf.c_iflag &= ~IXOFF;
246 /*termbuf.c_lflag &= ~ICANON;*/
247 tcsetattr(0, TCSANOW, &termbuf);
248
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000249 /* Uses FILE-based I/O to stdout, but does fflush(stdout),
250 * so should be safe with vfork.
251 * I fear, though, that some users will have ridiculously big
Denis Vlasenko018b1552007-11-06 01:38:46 +0000252 * issue files, and they may block writing to fd 1,
253 * (parent is supposed to read it, but parent waits
254 * for vforked child to exec!) */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000255 print_login_issue(issuefile, NULL);
256
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000257 /* Exec shell / login / whatever */
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000258 login_argv[0] = loginpath;
259 login_argv[1] = NULL;
Denis Vlasenko018b1552007-11-06 01:38:46 +0000260 /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
261 * exec external program */
262 BB_EXECVP(loginpath, (char **)login_argv);
263 /* _exit is safer with vfork, and we shouldn't send message
Denis Vlasenko2450c452007-10-15 22:09:15 +0000264 * to remote clients anyway */
Bernhard Reutner-Fischer636a1f82008-05-19 09:29:47 +0000265 _exit(EXIT_FAILURE); /*bb_perror_msg_and_die("execv %s", loginpath);*/
Eric Andersen08a72202002-09-30 20:52:10 +0000266}
267
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000268/* Must match getopt32 string */
269enum {
270 OPT_WATCHCHILD = (1 << 2), /* -K */
271 OPT_INETD = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
272 OPT_PORT = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */
273 OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
274};
275
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000276#if ENABLE_FEATURE_TELNETD_STANDALONE
277
Eric Andersen08a72202002-09-30 20:52:10 +0000278static void
279free_session(struct tsession *ts)
280{
281 struct tsession *t = sessions;
282
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000283 if (option_mask32 & OPT_INETD)
Bernhard Reutner-Fischer636a1f82008-05-19 09:29:47 +0000284 exit(EXIT_SUCCESS);
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000285
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000286 /* Unlink this telnet session from the session list */
Mike Frysinger731f81c2006-05-10 15:23:12 +0000287 if (t == ts)
Eric Andersen08a72202002-09-30 20:52:10 +0000288 sessions = ts->next;
289 else {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000290 while (t->next != ts)
Eric Andersen08a72202002-09-30 20:52:10 +0000291 t = t->next;
292 t->next = ts->next;
293 }
294
Denis Vlasenkof472b232007-10-16 21:35:17 +0000295#if 0
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000296 /* It was said that "normal" telnetd just closes ptyfd,
297 * doesn't send SIGKILL. When we close ptyfd,
298 * kernel sends SIGHUP to processes having slave side opened. */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000299 kill(ts->shell_pid, SIGKILL);
300 wait4(ts->shell_pid, NULL, 0, NULL);
301#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000302 close(ts->ptyfd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000303 close(ts->sockfd_read);
Denis Vlasenkof472b232007-10-16 21:35:17 +0000304 /* We do not need to close(ts->sockfd_write), it's the same
305 * as sockfd_read unless we are in inetd mode. But in inetd mode
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000306 * we do not reach this */
Eric Andersen08a72202002-09-30 20:52:10 +0000307 free(ts);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000308
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000309 /* Scan all sessions and find new maxfd */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000310 maxfd = 0;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000311 ts = sessions;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000312 while (ts) {
313 if (maxfd < ts->ptyfd)
314 maxfd = ts->ptyfd;
315 if (maxfd < ts->sockfd_read)
316 maxfd = ts->sockfd_read;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000317#if 0
318 /* Again, sockfd_write == sockfd_read here */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000319 if (maxfd < ts->sockfd_write)
320 maxfd = ts->sockfd_write;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000321#endif
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000322 ts = ts->next;
323 }
Eric Andersen08a72202002-09-30 20:52:10 +0000324}
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000325
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000326#else /* !FEATURE_TELNETD_STANDALONE */
327
Bernhard Reutner-Fischer636a1f82008-05-19 09:29:47 +0000328/* Used in main() only, thus "return 0" actually is exit(EXIT_SUCCESS). */
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000329#define free_session(ts) return 0
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000330
331#endif
332
Denis Vlasenko68404f12008-03-17 09:00:54 +0000333static void handle_sigchld(int sig ATTRIBUTE_UNUSED)
Denis Vlasenko2450c452007-10-15 22:09:15 +0000334{
335 pid_t pid;
336 struct tsession *ts;
337
Denis Vlasenko018b1552007-11-06 01:38:46 +0000338 /* Looping: more than one child may have exited */
339 while (1) {
Denis Vlasenkofb0eba72008-01-02 19:55:04 +0000340 pid = wait_any_nohang(NULL);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000341 if (pid <= 0)
342 break;
Denis Vlasenko2450c452007-10-15 22:09:15 +0000343 ts = sessions;
344 while (ts) {
345 if (ts->shell_pid == pid) {
346 ts->shell_pid = -1;
Denis Vlasenko018b1552007-11-06 01:38:46 +0000347 break;
Denis Vlasenko2450c452007-10-15 22:09:15 +0000348 }
349 ts = ts->next;
350 }
351 }
352}
353
Denis Vlasenko9b49a5e2007-10-11 10:05:36 +0000354int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko68404f12008-03-17 09:00:54 +0000355int telnetd_main(int argc ATTRIBUTE_UNUSED, char **argv)
Eric Andersen08a72202002-09-30 20:52:10 +0000356{
Eric Andersen08a72202002-09-30 20:52:10 +0000357 fd_set rdfdset, wrfdset;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000358 unsigned opt;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000359 int count;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000360 struct tsession *ts;
361#if ENABLE_FEATURE_TELNETD_STANDALONE
362#define IS_INETD (opt & OPT_INETD)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000363 int master_fd = master_fd; /* be happy, gcc */
Denis Vlasenko13858992006-10-08 12:49:22 +0000364 unsigned portnbr = 23;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000365 char *opt_bindaddr = NULL;
366 char *opt_portnbr;
367#else
368 enum {
369 IS_INETD = 1,
370 master_fd = -1,
371 portnbr = 23,
372 };
Glenn L McGrathc2b91862003-09-12 11:27:15 +0000373#endif
Denis Vlasenko2450c452007-10-15 22:09:15 +0000374 /* Even if !STANDALONE, we accept (and ignore) -i, thus people
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000375 * don't need to guess whether it's ok to pass -i to us */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000376 opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
Denis Vlasenko0e87d342006-09-22 08:50:29 +0000377 &issuefile, &loginpath
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000378 USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr));
Denis Vlasenko2450c452007-10-15 22:09:15 +0000379 if (!IS_INETD /*&& !re_execed*/) {
380 /* inform that we start in standalone mode?
381 * May be useful when people forget to give -i */
382 /*bb_error_msg("listening for connections");*/
383 if (!(opt & OPT_FOREGROUND)) {
384 /* DAEMON_CHDIR_ROOT was giving inconsistent
Denis Vlasenko008eda22007-10-16 10:47:27 +0000385 * behavior with/without -F, -i */
Denis Vlasenko018b1552007-11-06 01:38:46 +0000386 bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv);
Denis Vlasenko2450c452007-10-15 22:09:15 +0000387 }
388 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000389 /* Redirect log to syslog early, if needed */
390 if (IS_INETD || !(opt & OPT_FOREGROUND)) {
391 openlog(applet_name, 0, LOG_USER);
392 logmode = LOGMODE_SYSLOG;
393 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000394 USE_FEATURE_TELNETD_STANDALONE(
Denis Vlasenko2450c452007-10-15 22:09:15 +0000395 if (opt & OPT_PORT)
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000396 portnbr = xatou16(opt_portnbr);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000397 );
Eric Andersen08a72202002-09-30 20:52:10 +0000398
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000399 /* Used to check access(loginpath, X_OK) here. Pointless.
400 * exec will do this for us for free later. */
Glenn L McGrath9e5d6c02003-01-21 20:55:56 +0000401
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000402#if ENABLE_FEATURE_TELNETD_STANDALONE
403 if (IS_INETD) {
Denis Vlasenkof472b232007-10-16 21:35:17 +0000404 sessions = make_new_session(0);
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000405 if (!sessions) /* pty opening or vfork problem, exit */
406 return 1; /* make_new_session prints error message */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000407 } else {
Denis Vlasenko9de420c2007-01-10 09:28:01 +0000408 master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
Denis Vlasenkoc8717cd2006-11-22 16:10:39 +0000409 xlisten(master_fd, 1);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000410 }
Rob Landley00e76cb2005-05-10 23:53:33 +0000411#else
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000412 sessions = make_new_session();
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000413 if (!sessions) /* pty opening or vfork problem, exit */
414 return 1; /* make_new_session prints error message */
Rob Landley00e76cb2005-05-10 23:53:33 +0000415#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000416
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000417 /* We don't want to die if just one session is broken */
418 signal(SIGPIPE, SIG_IGN);
Eric Andersen08a72202002-09-30 20:52:10 +0000419
Denis Vlasenko2450c452007-10-15 22:09:15 +0000420 if (opt & OPT_WATCHCHILD)
421 signal(SIGCHLD, handle_sigchld);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000422 else /* prevent dead children from becoming zombies */
423 signal(SIGCHLD, SIG_IGN);
Denis Vlasenko2450c452007-10-15 22:09:15 +0000424
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000425/*
426 This is how the buffers are used. The arrows indicate the movement
427 of data.
428 +-------+ wridx1++ +------+ rdidx1++ +----------+
429 | | <-------------- | buf1 | <-------------- | |
430 | | size1-- +------+ size1++ | |
431 | pty | | socket |
432 | | rdidx2++ +------+ wridx2++ | |
433 | | --------------> | buf2 | --------------> | |
434 +-------+ size2++ +------+ size2-- +----------+
435
436 size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
437 size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"
438
439 Each session has got two buffers. Buffers are circular. If sizeN == 0,
440 buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
441 rdidxN == wridxN.
442*/
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000443 again:
444 FD_ZERO(&rdfdset);
445 FD_ZERO(&wrfdset);
Eric Andersen08a72202002-09-30 20:52:10 +0000446
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000447 /* Select on the master socket, all telnet sockets and their
448 * ptys if there is room in their session buffers.
449 * NB: scalability problem: we recalculate entire bitmap
450 * before each select. Can be a problem with 500+ connections. */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000451 ts = sessions;
452 while (ts) {
Denis Vlasenko2450c452007-10-15 22:09:15 +0000453 struct tsession *next = ts->next; /* in case we free ts. */
454 if (ts->shell_pid == -1) {
Denis Vlasenko018b1552007-11-06 01:38:46 +0000455 /* Child died and we detected that */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000456 free_session(ts);
457 } else {
458 if (ts->size1 > 0) /* can write to pty */
459 FD_SET(ts->ptyfd, &wrfdset);
460 if (ts->size1 < BUFSIZE) /* can read from socket */
461 FD_SET(ts->sockfd_read, &rdfdset);
462 if (ts->size2 > 0) /* can write to socket */
463 FD_SET(ts->sockfd_write, &wrfdset);
464 if (ts->size2 < BUFSIZE) /* can read from pty */
465 FD_SET(ts->ptyfd, &rdfdset);
466 }
467 ts = next;
468 }
469 if (!IS_INETD) {
470 FD_SET(master_fd, &rdfdset);
471 /* This is needed because free_session() does not
Denis Vlasenko018b1552007-11-06 01:38:46 +0000472 * take master_fd into account when it finds new
Denis Vlasenko2450c452007-10-15 22:09:15 +0000473 * maxfd among remaining fd's */
474 if (master_fd > maxfd)
475 maxfd = master_fd;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000476 }
477
Denis Vlasenko10916c52007-10-15 17:28:00 +0000478 count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
479 if (count < 0)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000480 goto again; /* EINTR or ENOMEM */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000481
482#if ENABLE_FEATURE_TELNETD_STANDALONE
483 /* First check for and accept new sessions. */
484 if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000485 int fd;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000486 struct tsession *new_ts;
487
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000488 fd = accept(master_fd, NULL, NULL);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000489 if (fd < 0)
490 goto again;
491 /* Create a new session and link it into our active list */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000492 new_ts = make_new_session(fd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000493 if (new_ts) {
494 new_ts->next = sessions;
495 sessions = new_ts;
496 } else {
497 close(fd);
Eric Andersen08a72202002-09-30 20:52:10 +0000498 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000499 }
500#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000501
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000502 /* Then check for data tunneling. */
503 ts = sessions;
504 while (ts) { /* For all sessions... */
505 struct tsession *next = ts->next; /* in case we free ts. */
Eric Andersen08a72202002-09-30 20:52:10 +0000506
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000507 if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000508 int num_totty;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000509 unsigned char *ptr;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000510 /* Write to pty from buffer 1. */
511 ptr = remove_iacs(ts, &num_totty);
Denis Vlasenko10916c52007-10-15 17:28:00 +0000512 count = safe_write(ts->ptyfd, ptr, num_totty);
513 if (count < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000514 if (errno == EAGAIN)
515 goto skip1;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000516 goto kill_session;
Eric Andersen08a72202002-09-30 20:52:10 +0000517 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000518 ts->size1 -= count;
519 ts->wridx1 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000520 if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
Eric Andersen08a72202002-09-30 20:52:10 +0000521 ts->wridx1 = 0;
Eric Andersen08a72202002-09-30 20:52:10 +0000522 }
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000523 skip1:
524 if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000525 /* Write to socket from buffer 2. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000526 count = MIN(BUFSIZE - ts->wridx2, ts->size2);
527 count = safe_write(ts->sockfd_write, TS_BUF2 + ts->wridx2, count);
528 if (count < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000529 if (errno == EAGAIN)
530 goto skip2;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000531 goto kill_session;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000532 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000533 ts->size2 -= count;
534 ts->wridx2 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000535 if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000536 ts->wridx2 = 0;
537 }
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000538 skip2:
Denis Vlasenko10916c52007-10-15 17:28:00 +0000539 /* Should not be needed, but... remove_iacs is actually buggy
540 * (it cannot process iacs which wrap around buffer's end)!
541 * Since properly fixing it requires writing bigger code,
Denis Vlasenkof472b232007-10-16 21:35:17 +0000542 * we rely instead on this code making it virtually impossible
Denis Vlasenko10916c52007-10-15 17:28:00 +0000543 * to have wrapped iac (people don't type at 2k/second).
544 * It also allows for bigger reads in common case. */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000545 if (ts->size1 == 0) {
546 ts->rdidx1 = 0;
547 ts->wridx1 = 0;
548 }
549 if (ts->size2 == 0) {
550 ts->rdidx2 = 0;
551 ts->wridx2 = 0;
552 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000553
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000554 if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
555 /* Read from socket to buffer 1. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000556 count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
557 count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count);
558 if (count <= 0) {
559 if (count < 0 && errno == EAGAIN)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000560 goto skip3;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000561 goto kill_session;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000562 }
563 /* Ignore trailing NUL if it is there */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000564 if (!TS_BUF1[ts->rdidx1 + count - 1]) {
Denis Vlasenkof472b232007-10-16 21:35:17 +0000565 --count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000566 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000567 ts->size1 += count;
568 ts->rdidx1 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000569 if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
570 ts->rdidx1 = 0;
571 }
572 skip3:
573 if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
574 /* Read from pty to buffer 2. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000575 count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
576 count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count);
577 if (count <= 0) {
578 if (count < 0 && errno == EAGAIN)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000579 goto skip4;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000580 goto kill_session;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000581 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000582 ts->size2 += count;
583 ts->rdidx2 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000584 if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
585 ts->rdidx2 = 0;
586 }
587 skip4:
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000588 ts = next;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000589 continue;
590 kill_session:
591 free_session(ts);
592 ts = next;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000593 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000594
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000595 goto again;
Eric Andersen08a72202002-09-30 20:52:10 +0000596}