blob: 5188edbab97cfb3f0cbbc21b8dffddbb003404fe [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. */
Denis Vlasenko85c24712008-03-17 09:04:04 +0000167 fd = getpty(tty_name);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000168 if (fd < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000169 bb_error_msg("can't create pty");
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000170 return NULL;
Eric Andersen08a72202002-09-30 20:52:10 +0000171 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000172 if (fd > maxfd)
173 maxfd = fd;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000174 ts->ptyfd = fd;
175 ndelay_on(fd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000176#if ENABLE_FEATURE_TELNETD_STANDALONE
Denis Vlasenkof472b232007-10-16 21:35:17 +0000177 ts->sockfd_read = sock;
178 ndelay_on(sock);
Denis Vlasenko9e237672007-10-17 11:18:49 +0000179 if (!sock) { /* We are called with fd 0 - we are in inetd mode */
180 sock++; /* so use fd 1 for output */
181 ndelay_on(sock);
182 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000183 ts->sockfd_write = sock;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000184 if (sock > maxfd)
185 maxfd = sock;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000186#else
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000187 /* ts->sockfd_read = 0; - done by xzalloc */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000188 ts->sockfd_write = 1;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000189 ndelay_on(0);
190 ndelay_on(1);
191#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000192 /* Make the telnet client understand we will echo characters so it
193 * should not do it locally. We don't tell the client to run linemode,
194 * because we want to handle line editing and tab completion and other
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000195 * stuff that requires char-by-char support. */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000196 {
197 static const char iacs_to_send[] ALIGN1 = {
198 IAC, DO, TELOPT_ECHO,
199 IAC, DO, TELOPT_NAWS,
200 IAC, DO, TELOPT_LFLOW,
201 IAC, WILL, TELOPT_ECHO,
202 IAC, WILL, TELOPT_SGA
203 };
204 memcpy(TS_BUF2, iacs_to_send, sizeof(iacs_to_send));
205 ts->rdidx2 = sizeof(iacs_to_send);
206 ts->size2 = sizeof(iacs_to_send);
207 }
Eric Andersen08a72202002-09-30 20:52:10 +0000208
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000209 fflush(NULL); /* flush all streams */
210 pid = vfork(); /* NOMMU-friendly */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000211 if (pid < 0) {
212 free(ts);
213 close(fd);
Denis Vlasenko23c81282007-10-16 22:01:23 +0000214 /* sock will be closed by caller */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000215 bb_perror_msg("vfork");
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000216 return NULL;
Eric Andersen08a72202002-09-30 20:52:10 +0000217 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000218 if (pid > 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000219 /* Parent */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000220 ts->shell_pid = pid;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000221 return ts;
Eric Andersen08a72202002-09-30 20:52:10 +0000222 }
Denis Vlasenkof7996f32007-01-11 17:20:00 +0000223
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000224 /* Child */
225 /* Careful - we are after vfork! */
Eric Andersen08a72202002-09-30 20:52:10 +0000226
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000227 /* make new session and process group */
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000228 setsid();
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000229
Denis Vlasenko018b1552007-11-06 01:38:46 +0000230 /* Restore default signal handling */
Denis Vlasenko25591c32008-02-16 22:58:56 +0000231 bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000232
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000233 /* open the child's side of the tty. */
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000234 /* NB: setsid() disconnects from any previous ctty's. Therefore
235 * we must open child's side of the tty AFTER setsid! */
236 fd = xopen(tty_name, O_RDWR); /* becomes our ctty */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000237 dup2(fd, 0);
238 dup2(fd, 1);
239 dup2(fd, 2);
240 while (fd > 2) close(fd--);
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000241 tcsetpgrp(0, getpid()); /* switch this tty's process group to us */
Eric Andersen08a72202002-09-30 20:52:10 +0000242
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000243 /* The pseudo-terminal allocated to the client is configured to operate in
244 * cooked mode, and with XTABS CRMOD enabled (see tty(4)). */
245 tcgetattr(0, &termbuf);
246 termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000247 termbuf.c_oflag |= ONLCR | XTABS;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000248 termbuf.c_iflag |= ICRNL;
249 termbuf.c_iflag &= ~IXOFF;
250 /*termbuf.c_lflag &= ~ICANON;*/
251 tcsetattr(0, TCSANOW, &termbuf);
252
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000253 /* Uses FILE-based I/O to stdout, but does fflush(stdout),
254 * so should be safe with vfork.
255 * I fear, though, that some users will have ridiculously big
Denis Vlasenko018b1552007-11-06 01:38:46 +0000256 * issue files, and they may block writing to fd 1,
257 * (parent is supposed to read it, but parent waits
258 * for vforked child to exec!) */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000259 print_login_issue(issuefile, NULL);
260
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000261 /* Exec shell / login / whatever */
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000262 login_argv[0] = loginpath;
263 login_argv[1] = NULL;
Denis Vlasenko018b1552007-11-06 01:38:46 +0000264 /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
265 * exec external program */
266 BB_EXECVP(loginpath, (char **)login_argv);
267 /* _exit is safer with vfork, and we shouldn't send message
Denis Vlasenko2450c452007-10-15 22:09:15 +0000268 * to remote clients anyway */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000269 _exit(1); /*bb_perror_msg_and_die("execv %s", loginpath);*/
Eric Andersen08a72202002-09-30 20:52:10 +0000270}
271
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000272/* Must match getopt32 string */
273enum {
274 OPT_WATCHCHILD = (1 << 2), /* -K */
275 OPT_INETD = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
276 OPT_PORT = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */
277 OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
278};
279
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000280#if ENABLE_FEATURE_TELNETD_STANDALONE
281
Eric Andersen08a72202002-09-30 20:52:10 +0000282static void
283free_session(struct tsession *ts)
284{
285 struct tsession *t = sessions;
286
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000287 if (option_mask32 & OPT_INETD)
288 exit(0);
289
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000290 /* Unlink this telnet session from the session list */
Mike Frysinger731f81c2006-05-10 15:23:12 +0000291 if (t == ts)
Eric Andersen08a72202002-09-30 20:52:10 +0000292 sessions = ts->next;
293 else {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000294 while (t->next != ts)
Eric Andersen08a72202002-09-30 20:52:10 +0000295 t = t->next;
296 t->next = ts->next;
297 }
298
Denis Vlasenkof472b232007-10-16 21:35:17 +0000299#if 0
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000300 /* It was said that "normal" telnetd just closes ptyfd,
301 * doesn't send SIGKILL. When we close ptyfd,
302 * kernel sends SIGHUP to processes having slave side opened. */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000303 kill(ts->shell_pid, SIGKILL);
304 wait4(ts->shell_pid, NULL, 0, NULL);
305#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000306 close(ts->ptyfd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000307 close(ts->sockfd_read);
Denis Vlasenkof472b232007-10-16 21:35:17 +0000308 /* We do not need to close(ts->sockfd_write), it's the same
309 * as sockfd_read unless we are in inetd mode. But in inetd mode
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000310 * we do not reach this */
Eric Andersen08a72202002-09-30 20:52:10 +0000311 free(ts);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000312
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000313 /* Scan all sessions and find new maxfd */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000314 maxfd = 0;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000315 ts = sessions;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000316 while (ts) {
317 if (maxfd < ts->ptyfd)
318 maxfd = ts->ptyfd;
319 if (maxfd < ts->sockfd_read)
320 maxfd = ts->sockfd_read;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000321#if 0
322 /* Again, sockfd_write == sockfd_read here */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000323 if (maxfd < ts->sockfd_write)
324 maxfd = ts->sockfd_write;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000325#endif
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000326 ts = ts->next;
327 }
Eric Andersen08a72202002-09-30 20:52:10 +0000328}
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000329
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000330#else /* !FEATURE_TELNETD_STANDALONE */
331
Denis Vlasenko018b1552007-11-06 01:38:46 +0000332/* Used in main() only, thus "return 0" actually is exit(0). */
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000333#define free_session(ts) return 0
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000334
335#endif
336
Denis Vlasenko68404f12008-03-17 09:00:54 +0000337static void handle_sigchld(int sig ATTRIBUTE_UNUSED)
Denis Vlasenko2450c452007-10-15 22:09:15 +0000338{
339 pid_t pid;
340 struct tsession *ts;
341
Denis Vlasenko018b1552007-11-06 01:38:46 +0000342 /* Looping: more than one child may have exited */
343 while (1) {
Denis Vlasenkofb0eba72008-01-02 19:55:04 +0000344 pid = wait_any_nohang(NULL);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000345 if (pid <= 0)
346 break;
Denis Vlasenko2450c452007-10-15 22:09:15 +0000347 ts = sessions;
348 while (ts) {
349 if (ts->shell_pid == pid) {
350 ts->shell_pid = -1;
Denis Vlasenko018b1552007-11-06 01:38:46 +0000351 break;
Denis Vlasenko2450c452007-10-15 22:09:15 +0000352 }
353 ts = ts->next;
354 }
355 }
356}
357
Denis Vlasenko9b49a5e2007-10-11 10:05:36 +0000358int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko68404f12008-03-17 09:00:54 +0000359int telnetd_main(int argc ATTRIBUTE_UNUSED, char **argv)
Eric Andersen08a72202002-09-30 20:52:10 +0000360{
Eric Andersen08a72202002-09-30 20:52:10 +0000361 fd_set rdfdset, wrfdset;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000362 unsigned opt;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000363 int count;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000364 struct tsession *ts;
365#if ENABLE_FEATURE_TELNETD_STANDALONE
366#define IS_INETD (opt & OPT_INETD)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000367 int master_fd = master_fd; /* be happy, gcc */
Denis Vlasenko13858992006-10-08 12:49:22 +0000368 unsigned portnbr = 23;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000369 char *opt_bindaddr = NULL;
370 char *opt_portnbr;
371#else
372 enum {
373 IS_INETD = 1,
374 master_fd = -1,
375 portnbr = 23,
376 };
Glenn L McGrathc2b91862003-09-12 11:27:15 +0000377#endif
Denis Vlasenko2450c452007-10-15 22:09:15 +0000378 /* Even if !STANDALONE, we accept (and ignore) -i, thus people
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000379 * don't need to guess whether it's ok to pass -i to us */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000380 opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
Denis Vlasenko0e87d342006-09-22 08:50:29 +0000381 &issuefile, &loginpath
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000382 USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr));
Denis Vlasenko2450c452007-10-15 22:09:15 +0000383 if (!IS_INETD /*&& !re_execed*/) {
384 /* inform that we start in standalone mode?
385 * May be useful when people forget to give -i */
386 /*bb_error_msg("listening for connections");*/
387 if (!(opt & OPT_FOREGROUND)) {
388 /* DAEMON_CHDIR_ROOT was giving inconsistent
Denis Vlasenko008eda22007-10-16 10:47:27 +0000389 * behavior with/without -F, -i */
Denis Vlasenko018b1552007-11-06 01:38:46 +0000390 bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv);
Denis Vlasenko2450c452007-10-15 22:09:15 +0000391 }
392 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000393 /* Redirect log to syslog early, if needed */
394 if (IS_INETD || !(opt & OPT_FOREGROUND)) {
395 openlog(applet_name, 0, LOG_USER);
396 logmode = LOGMODE_SYSLOG;
397 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000398 USE_FEATURE_TELNETD_STANDALONE(
Denis Vlasenko2450c452007-10-15 22:09:15 +0000399 if (opt & OPT_PORT)
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000400 portnbr = xatou16(opt_portnbr);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000401 );
Eric Andersen08a72202002-09-30 20:52:10 +0000402
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000403 /* Used to check access(loginpath, X_OK) here. Pointless.
404 * exec will do this for us for free later. */
Glenn L McGrath9e5d6c02003-01-21 20:55:56 +0000405
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000406#if ENABLE_FEATURE_TELNETD_STANDALONE
407 if (IS_INETD) {
Denis Vlasenkof472b232007-10-16 21:35:17 +0000408 sessions = make_new_session(0);
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000409 if (!sessions) /* pty opening or vfork problem, exit */
410 return 1; /* make_new_session prints error message */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000411 } else {
Denis Vlasenko9de420c2007-01-10 09:28:01 +0000412 master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
Denis Vlasenkoc8717cd2006-11-22 16:10:39 +0000413 xlisten(master_fd, 1);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000414 }
Rob Landley00e76cb2005-05-10 23:53:33 +0000415#else
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000416 sessions = make_new_session();
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000417 if (!sessions) /* pty opening or vfork problem, exit */
418 return 1; /* make_new_session prints error message */
Rob Landley00e76cb2005-05-10 23:53:33 +0000419#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000420
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000421 /* We don't want to die if just one session is broken */
422 signal(SIGPIPE, SIG_IGN);
Eric Andersen08a72202002-09-30 20:52:10 +0000423
Denis Vlasenko2450c452007-10-15 22:09:15 +0000424 if (opt & OPT_WATCHCHILD)
425 signal(SIGCHLD, handle_sigchld);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000426 else /* prevent dead children from becoming zombies */
427 signal(SIGCHLD, SIG_IGN);
Denis Vlasenko2450c452007-10-15 22:09:15 +0000428
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000429/*
430 This is how the buffers are used. The arrows indicate the movement
431 of data.
432 +-------+ wridx1++ +------+ rdidx1++ +----------+
433 | | <-------------- | buf1 | <-------------- | |
434 | | size1-- +------+ size1++ | |
435 | pty | | socket |
436 | | rdidx2++ +------+ wridx2++ | |
437 | | --------------> | buf2 | --------------> | |
438 +-------+ size2++ +------+ size2-- +----------+
439
440 size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
441 size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"
442
443 Each session has got two buffers. Buffers are circular. If sizeN == 0,
444 buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
445 rdidxN == wridxN.
446*/
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000447 again:
448 FD_ZERO(&rdfdset);
449 FD_ZERO(&wrfdset);
Eric Andersen08a72202002-09-30 20:52:10 +0000450
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000451 /* Select on the master socket, all telnet sockets and their
452 * ptys if there is room in their session buffers.
453 * NB: scalability problem: we recalculate entire bitmap
454 * before each select. Can be a problem with 500+ connections. */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000455 ts = sessions;
456 while (ts) {
Denis Vlasenko2450c452007-10-15 22:09:15 +0000457 struct tsession *next = ts->next; /* in case we free ts. */
458 if (ts->shell_pid == -1) {
Denis Vlasenko018b1552007-11-06 01:38:46 +0000459 /* Child died and we detected that */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000460 free_session(ts);
461 } else {
462 if (ts->size1 > 0) /* can write to pty */
463 FD_SET(ts->ptyfd, &wrfdset);
464 if (ts->size1 < BUFSIZE) /* can read from socket */
465 FD_SET(ts->sockfd_read, &rdfdset);
466 if (ts->size2 > 0) /* can write to socket */
467 FD_SET(ts->sockfd_write, &wrfdset);
468 if (ts->size2 < BUFSIZE) /* can read from pty */
469 FD_SET(ts->ptyfd, &rdfdset);
470 }
471 ts = next;
472 }
473 if (!IS_INETD) {
474 FD_SET(master_fd, &rdfdset);
475 /* This is needed because free_session() does not
Denis Vlasenko018b1552007-11-06 01:38:46 +0000476 * take master_fd into account when it finds new
Denis Vlasenko2450c452007-10-15 22:09:15 +0000477 * maxfd among remaining fd's */
478 if (master_fd > maxfd)
479 maxfd = master_fd;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000480 }
481
Denis Vlasenko10916c52007-10-15 17:28:00 +0000482 count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
483 if (count < 0)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000484 goto again; /* EINTR or ENOMEM */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000485
486#if ENABLE_FEATURE_TELNETD_STANDALONE
487 /* First check for and accept new sessions. */
488 if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000489 int fd;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000490 struct tsession *new_ts;
491
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000492 fd = accept(master_fd, NULL, NULL);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000493 if (fd < 0)
494 goto again;
495 /* Create a new session and link it into our active list */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000496 new_ts = make_new_session(fd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000497 if (new_ts) {
498 new_ts->next = sessions;
499 sessions = new_ts;
500 } else {
501 close(fd);
Eric Andersen08a72202002-09-30 20:52:10 +0000502 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000503 }
504#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000505
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000506 /* Then check for data tunneling. */
507 ts = sessions;
508 while (ts) { /* For all sessions... */
509 struct tsession *next = ts->next; /* in case we free ts. */
Eric Andersen08a72202002-09-30 20:52:10 +0000510
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000511 if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000512 int num_totty;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000513 unsigned char *ptr;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000514 /* Write to pty from buffer 1. */
515 ptr = remove_iacs(ts, &num_totty);
Denis Vlasenko10916c52007-10-15 17:28:00 +0000516 count = safe_write(ts->ptyfd, ptr, num_totty);
517 if (count < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000518 if (errno == EAGAIN)
519 goto skip1;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000520 goto kill_session;
Eric Andersen08a72202002-09-30 20:52:10 +0000521 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000522 ts->size1 -= count;
523 ts->wridx1 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000524 if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
Eric Andersen08a72202002-09-30 20:52:10 +0000525 ts->wridx1 = 0;
Eric Andersen08a72202002-09-30 20:52:10 +0000526 }
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000527 skip1:
528 if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000529 /* Write to socket from buffer 2. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000530 count = MIN(BUFSIZE - ts->wridx2, ts->size2);
531 count = safe_write(ts->sockfd_write, TS_BUF2 + ts->wridx2, count);
532 if (count < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000533 if (errno == EAGAIN)
534 goto skip2;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000535 goto kill_session;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000536 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000537 ts->size2 -= count;
538 ts->wridx2 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000539 if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000540 ts->wridx2 = 0;
541 }
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000542 skip2:
Denis Vlasenko10916c52007-10-15 17:28:00 +0000543 /* Should not be needed, but... remove_iacs is actually buggy
544 * (it cannot process iacs which wrap around buffer's end)!
545 * Since properly fixing it requires writing bigger code,
Denis Vlasenkof472b232007-10-16 21:35:17 +0000546 * we rely instead on this code making it virtually impossible
Denis Vlasenko10916c52007-10-15 17:28:00 +0000547 * to have wrapped iac (people don't type at 2k/second).
548 * It also allows for bigger reads in common case. */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000549 if (ts->size1 == 0) {
550 ts->rdidx1 = 0;
551 ts->wridx1 = 0;
552 }
553 if (ts->size2 == 0) {
554 ts->rdidx2 = 0;
555 ts->wridx2 = 0;
556 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000557
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000558 if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
559 /* Read from socket to buffer 1. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000560 count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
561 count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count);
562 if (count <= 0) {
563 if (count < 0 && errno == EAGAIN)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000564 goto skip3;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000565 goto kill_session;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000566 }
567 /* Ignore trailing NUL if it is there */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000568 if (!TS_BUF1[ts->rdidx1 + count - 1]) {
Denis Vlasenkof472b232007-10-16 21:35:17 +0000569 --count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000570 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000571 ts->size1 += count;
572 ts->rdidx1 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000573 if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
574 ts->rdidx1 = 0;
575 }
576 skip3:
577 if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
578 /* Read from pty to buffer 2. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000579 count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
580 count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count);
581 if (count <= 0) {
582 if (count < 0 && errno == EAGAIN)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000583 goto skip4;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000584 goto kill_session;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000585 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000586 ts->size2 += count;
587 ts->rdidx2 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000588 if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
589 ts->rdidx2 = 0;
590 }
591 skip4:
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000592 ts = next;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000593 continue;
594 kill_session:
595 free_session(ts);
596 ts = next;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000597 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000598
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000599 goto again;
Eric Andersen08a72202002-09-30 20:52:10 +0000600}