blob: 20c57925f4d45ec37fd69ab3abb483931130aa25 [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 +000059#if ENABLE_LOGIN
60static const char *loginpath = "/bin/login";
61#else
62static const char *loginpath = DEFAULT_SHELL;
63#endif
64static const char *issuefile = "/etc/issue.net";
Eric Andersen08a72202002-09-30 20:52:10 +000065
66
67/*
Denis Vlasenko59d7c432007-10-15 15:19:36 +000068 Remove all IAC's from buf1 (received IACs are ignored and must be removed
69 so as to not be interpreted by the terminal). Make an uninterrupted
70 string of characters fit for the terminal. Do this by packing
71 all characters meant for the terminal sequentially towards the end of buf.
Eric Andersen08a72202002-09-30 20:52:10 +000072
73 Return a pointer to the beginning of the characters meant for the terminal.
74 and make *num_totty the number of characters that should be sent to
75 the terminal.
76
77 Note - If an IAC (3 byte quantity) starts before (bf + len) but extends
Denis Vlasenko59d7c432007-10-15 15:19:36 +000078 past (bf + len) then that IAC will be left unprocessed and *processed
79 will be less than len.
Eric Andersen08a72202002-09-30 20:52:10 +000080
81 FIXME - if we mean to send 0xFF to the terminal then it will be escaped,
82 what is the escape character? We aren't handling that situation here.
83
Denis Vlasenko10916c52007-10-15 17:28:00 +000084 CR-LF ->'s CR mapping is also done here, for convenience.
85
86 NB: may fail to remove iacs which wrap around buffer!
Denis Vlasenko75f8d082006-11-22 15:54:52 +000087 */
Denis Vlasenko10916c52007-10-15 17:28:00 +000088static unsigned char *
Denis Vlasenko75f8d082006-11-22 15:54:52 +000089remove_iacs(struct tsession *ts, int *pnum_totty)
90{
Denis Vlasenko10916c52007-10-15 17:28:00 +000091 unsigned char *ptr0 = TS_BUF1 + ts->wridx1;
Eric Andersen08a72202002-09-30 20:52:10 +000092 unsigned char *ptr = ptr0;
93 unsigned char *totty = ptr;
94 unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
Eric Andersen08a72202002-09-30 20:52:10 +000095 int num_totty;
96
97 while (ptr < end) {
98 if (*ptr != IAC) {
Denis Vlasenko10916c52007-10-15 17:28:00 +000099 char c = *ptr;
100
101 *totty++ = c;
102 ptr++;
Eric Andersen3752d332003-12-19 11:30:13 +0000103 /* We now map \r\n ==> \r for pragmatic reasons.
104 * Many client implementations send \r\n when
105 * the user hits the CarriageReturn key.
106 */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000107 if (c == '\r' && ptr < end && (*ptr == '\n' || *ptr == '\0'))
Eric Andersen3752d332003-12-19 11:30:13 +0000108 ptr++;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000109 } else {
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000110 /*
111 * TELOPT_NAWS support!
112 */
113 if ((ptr+2) >= end) {
Eric Andersen08a72202002-09-30 20:52:10 +0000114 /* only the beginning of the IAC is in the
115 buffer we were asked to process, we can't
116 process this char. */
117 break;
118 }
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000119
120 /*
121 * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE
122 */
123 else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
124 struct winsize ws;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000125
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000126 if ((ptr+8) >= end)
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000127 break; /* incomplete, can't process */
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000128 ws.ws_col = (ptr[3] << 8) | ptr[4];
129 ws.ws_row = (ptr[5] << 8) | ptr[6];
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000130 ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000131 ptr += 9;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000132 } else {
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000133 /* skip 3-byte IAC non-SB cmd */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000134#if DEBUG
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000135 fprintf(stderr, "Ignoring IAC %s,%s\n",
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000136 TELCMD(ptr[1]), TELOPT(ptr[2]));
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000137#endif
138 ptr += 3;
139 }
Eric Andersen08a72202002-09-30 20:52:10 +0000140 }
141 }
142
Denis Vlasenko0de37e12007-10-17 11:08:53 +0000143 num_totty = totty - ptr0;
Eric Andersen08a72202002-09-30 20:52:10 +0000144 *pnum_totty = num_totty;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000145 /* the difference between ptr and totty is number of iacs
146 we removed from the stream. Adjust buf1 accordingly. */
147 if ((ptr - totty) == 0) /* 99.999% of cases */
148 return ptr0;
149 ts->wridx1 += ptr - totty;
150 ts->size1 -= ptr - totty;
151 /* move chars meant for the terminal towards the end of the buffer */
Eric Andersen08a72202002-09-30 20:52:10 +0000152 return memmove(ptr - num_totty, ptr0, num_totty);
153}
154
155
Eric Andersen08a72202002-09-30 20:52:10 +0000156static struct tsession *
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000157make_new_session(
Denis Vlasenkof472b232007-10-16 21:35:17 +0000158 USE_FEATURE_TELNETD_STANDALONE(int sock)
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000159 SKIP_FEATURE_TELNETD_STANDALONE(void)
160) {
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000161 const char *login_argv[2];
Eric Andersen08a72202002-09-30 20:52:10 +0000162 struct termios termbuf;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000163 int fd, pid;
Eric Andersen08a72202002-09-30 20:52:10 +0000164 char tty_name[32];
Rob Landley1ec5b292006-05-29 07:42:02 +0000165 struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2);
Eric Andersen08a72202002-09-30 20:52:10 +0000166
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000167 /*ts->buf1 = (char *)(ts + 1);*/
168 /*ts->buf2 = ts->buf1 + BUFSIZE;*/
Eric Andersen08a72202002-09-30 20:52:10 +0000169
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000170 /* Got a new connection, set up a tty. */
171 fd = getpty(tty_name, 32);
172 if (fd < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000173 bb_error_msg("can't create pty");
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000174 return NULL;
Eric Andersen08a72202002-09-30 20:52:10 +0000175 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000176 if (fd > maxfd)
177 maxfd = fd;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000178 ts->ptyfd = fd;
179 ndelay_on(fd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000180#if ENABLE_FEATURE_TELNETD_STANDALONE
Denis Vlasenkof472b232007-10-16 21:35:17 +0000181 ts->sockfd_read = sock;
182 ndelay_on(sock);
Denis Vlasenko9e237672007-10-17 11:18:49 +0000183 if (!sock) { /* We are called with fd 0 - we are in inetd mode */
184 sock++; /* so use fd 1 for output */
185 ndelay_on(sock);
186 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000187 ts->sockfd_write = sock;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000188 if (sock > maxfd)
189 maxfd = sock;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000190#else
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000191 /* ts->sockfd_read = 0; - done by xzalloc */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000192 ts->sockfd_write = 1;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000193 ndelay_on(0);
194 ndelay_on(1);
195#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000196 /* Make the telnet client understand we will echo characters so it
197 * should not do it locally. We don't tell the client to run linemode,
198 * because we want to handle line editing and tab completion and other
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000199 * stuff that requires char-by-char support. */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000200 {
201 static const char iacs_to_send[] ALIGN1 = {
202 IAC, DO, TELOPT_ECHO,
203 IAC, DO, TELOPT_NAWS,
204 IAC, DO, TELOPT_LFLOW,
205 IAC, WILL, TELOPT_ECHO,
206 IAC, WILL, TELOPT_SGA
207 };
208 memcpy(TS_BUF2, iacs_to_send, sizeof(iacs_to_send));
209 ts->rdidx2 = sizeof(iacs_to_send);
210 ts->size2 = sizeof(iacs_to_send);
211 }
Eric Andersen08a72202002-09-30 20:52:10 +0000212
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000213 fflush(NULL); /* flush all streams */
214 pid = vfork(); /* NOMMU-friendly */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000215 if (pid < 0) {
216 free(ts);
217 close(fd);
Denis Vlasenko23c81282007-10-16 22:01:23 +0000218 /* sock will be closed by caller */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000219 bb_perror_msg("vfork");
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000220 return NULL;
Eric Andersen08a72202002-09-30 20:52:10 +0000221 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000222 if (pid > 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000223 /* Parent */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000224 ts->shell_pid = pid;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000225 return ts;
Eric Andersen08a72202002-09-30 20:52:10 +0000226 }
Denis Vlasenkof7996f32007-01-11 17:20:00 +0000227
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000228 /* Child */
229 /* Careful - we are after vfork! */
Eric Andersen08a72202002-09-30 20:52:10 +0000230
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000231 /* make new session and process group */
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000232 setsid();
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000233
Denis Vlasenko018b1552007-11-06 01:38:46 +0000234 /* Restore default signal handling */
Denis Vlasenko25591c32008-02-16 22:58:56 +0000235 bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000236
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000237 /* open the child's side of the tty. */
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000238 /* NB: setsid() disconnects from any previous ctty's. Therefore
239 * we must open child's side of the tty AFTER setsid! */
240 fd = xopen(tty_name, O_RDWR); /* becomes our ctty */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000241 dup2(fd, 0);
242 dup2(fd, 1);
243 dup2(fd, 2);
244 while (fd > 2) close(fd--);
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000245 tcsetpgrp(0, getpid()); /* switch this tty's process group to us */
Eric Andersen08a72202002-09-30 20:52:10 +0000246
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000247 /* The pseudo-terminal allocated to the client is configured to operate in
248 * cooked mode, and with XTABS CRMOD enabled (see tty(4)). */
249 tcgetattr(0, &termbuf);
250 termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000251 termbuf.c_oflag |= ONLCR | XTABS;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000252 termbuf.c_iflag |= ICRNL;
253 termbuf.c_iflag &= ~IXOFF;
254 /*termbuf.c_lflag &= ~ICANON;*/
255 tcsetattr(0, TCSANOW, &termbuf);
256
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000257 /* Uses FILE-based I/O to stdout, but does fflush(stdout),
258 * so should be safe with vfork.
259 * I fear, though, that some users will have ridiculously big
Denis Vlasenko018b1552007-11-06 01:38:46 +0000260 * issue files, and they may block writing to fd 1,
261 * (parent is supposed to read it, but parent waits
262 * for vforked child to exec!) */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000263 print_login_issue(issuefile, NULL);
264
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000265 /* Exec shell / login / whatever */
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000266 login_argv[0] = loginpath;
267 login_argv[1] = NULL;
Denis Vlasenko018b1552007-11-06 01:38:46 +0000268 /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
269 * exec external program */
270 BB_EXECVP(loginpath, (char **)login_argv);
271 /* _exit is safer with vfork, and we shouldn't send message
Denis Vlasenko2450c452007-10-15 22:09:15 +0000272 * to remote clients anyway */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000273 _exit(1); /*bb_perror_msg_and_die("execv %s", loginpath);*/
Eric Andersen08a72202002-09-30 20:52:10 +0000274}
275
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000276/* Must match getopt32 string */
277enum {
278 OPT_WATCHCHILD = (1 << 2), /* -K */
279 OPT_INETD = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
280 OPT_PORT = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */
281 OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
282};
283
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000284#if ENABLE_FEATURE_TELNETD_STANDALONE
285
Eric Andersen08a72202002-09-30 20:52:10 +0000286static void
287free_session(struct tsession *ts)
288{
289 struct tsession *t = sessions;
290
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000291 if (option_mask32 & OPT_INETD)
292 exit(0);
293
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000294 /* Unlink this telnet session from the session list */
Mike Frysinger731f81c2006-05-10 15:23:12 +0000295 if (t == ts)
Eric Andersen08a72202002-09-30 20:52:10 +0000296 sessions = ts->next;
297 else {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000298 while (t->next != ts)
Eric Andersen08a72202002-09-30 20:52:10 +0000299 t = t->next;
300 t->next = ts->next;
301 }
302
Denis Vlasenkof472b232007-10-16 21:35:17 +0000303#if 0
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000304 /* It was said that "normal" telnetd just closes ptyfd,
305 * doesn't send SIGKILL. When we close ptyfd,
306 * kernel sends SIGHUP to processes having slave side opened. */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000307 kill(ts->shell_pid, SIGKILL);
308 wait4(ts->shell_pid, NULL, 0, NULL);
309#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000310 close(ts->ptyfd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000311 close(ts->sockfd_read);
Denis Vlasenkof472b232007-10-16 21:35:17 +0000312 /* We do not need to close(ts->sockfd_write), it's the same
313 * as sockfd_read unless we are in inetd mode. But in inetd mode
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000314 * we do not reach this */
Eric Andersen08a72202002-09-30 20:52:10 +0000315 free(ts);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000316
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000317 /* Scan all sessions and find new maxfd */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000318 maxfd = 0;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000319 ts = sessions;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000320 while (ts) {
321 if (maxfd < ts->ptyfd)
322 maxfd = ts->ptyfd;
323 if (maxfd < ts->sockfd_read)
324 maxfd = ts->sockfd_read;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000325#if 0
326 /* Again, sockfd_write == sockfd_read here */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000327 if (maxfd < ts->sockfd_write)
328 maxfd = ts->sockfd_write;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000329#endif
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000330 ts = ts->next;
331 }
Eric Andersen08a72202002-09-30 20:52:10 +0000332}
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000333
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000334#else /* !FEATURE_TELNETD_STANDALONE */
335
Denis Vlasenko018b1552007-11-06 01:38:46 +0000336/* Used in main() only, thus "return 0" actually is exit(0). */
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000337#define free_session(ts) return 0
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000338
339#endif
340
Denis Vlasenko68404f12008-03-17 09:00:54 +0000341static void handle_sigchld(int sig ATTRIBUTE_UNUSED)
Denis Vlasenko2450c452007-10-15 22:09:15 +0000342{
343 pid_t pid;
344 struct tsession *ts;
345
Denis Vlasenko018b1552007-11-06 01:38:46 +0000346 /* Looping: more than one child may have exited */
347 while (1) {
Denis Vlasenkofb0eba72008-01-02 19:55:04 +0000348 pid = wait_any_nohang(NULL);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000349 if (pid <= 0)
350 break;
Denis Vlasenko2450c452007-10-15 22:09:15 +0000351 ts = sessions;
352 while (ts) {
353 if (ts->shell_pid == pid) {
354 ts->shell_pid = -1;
Denis Vlasenko018b1552007-11-06 01:38:46 +0000355 break;
Denis Vlasenko2450c452007-10-15 22:09:15 +0000356 }
357 ts = ts->next;
358 }
359 }
360}
361
Denis Vlasenko9b49a5e2007-10-11 10:05:36 +0000362int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko68404f12008-03-17 09:00:54 +0000363int telnetd_main(int argc ATTRIBUTE_UNUSED, char **argv)
Eric Andersen08a72202002-09-30 20:52:10 +0000364{
Eric Andersen08a72202002-09-30 20:52:10 +0000365 fd_set rdfdset, wrfdset;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000366 unsigned opt;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000367 int count;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000368 struct tsession *ts;
369#if ENABLE_FEATURE_TELNETD_STANDALONE
370#define IS_INETD (opt & OPT_INETD)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000371 int master_fd = master_fd; /* be happy, gcc */
Denis Vlasenko13858992006-10-08 12:49:22 +0000372 unsigned portnbr = 23;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000373 char *opt_bindaddr = NULL;
374 char *opt_portnbr;
375#else
376 enum {
377 IS_INETD = 1,
378 master_fd = -1,
379 portnbr = 23,
380 };
Glenn L McGrathc2b91862003-09-12 11:27:15 +0000381#endif
Denis Vlasenko2450c452007-10-15 22:09:15 +0000382 /* Even if !STANDALONE, we accept (and ignore) -i, thus people
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000383 * don't need to guess whether it's ok to pass -i to us */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000384 opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
Denis Vlasenko0e87d342006-09-22 08:50:29 +0000385 &issuefile, &loginpath
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000386 USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr));
Denis Vlasenko2450c452007-10-15 22:09:15 +0000387 if (!IS_INETD /*&& !re_execed*/) {
388 /* inform that we start in standalone mode?
389 * May be useful when people forget to give -i */
390 /*bb_error_msg("listening for connections");*/
391 if (!(opt & OPT_FOREGROUND)) {
392 /* DAEMON_CHDIR_ROOT was giving inconsistent
Denis Vlasenko008eda22007-10-16 10:47:27 +0000393 * behavior with/without -F, -i */
Denis Vlasenko018b1552007-11-06 01:38:46 +0000394 bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv);
Denis Vlasenko2450c452007-10-15 22:09:15 +0000395 }
396 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000397 /* Redirect log to syslog early, if needed */
398 if (IS_INETD || !(opt & OPT_FOREGROUND)) {
399 openlog(applet_name, 0, LOG_USER);
400 logmode = LOGMODE_SYSLOG;
401 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000402 USE_FEATURE_TELNETD_STANDALONE(
Denis Vlasenko2450c452007-10-15 22:09:15 +0000403 if (opt & OPT_PORT)
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000404 portnbr = xatou16(opt_portnbr);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000405 );
Eric Andersen08a72202002-09-30 20:52:10 +0000406
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000407 /* Used to check access(loginpath, X_OK) here. Pointless.
408 * exec will do this for us for free later. */
Glenn L McGrath9e5d6c02003-01-21 20:55:56 +0000409
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000410#if ENABLE_FEATURE_TELNETD_STANDALONE
411 if (IS_INETD) {
Denis Vlasenkof472b232007-10-16 21:35:17 +0000412 sessions = make_new_session(0);
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 */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000415 } else {
Denis Vlasenko9de420c2007-01-10 09:28:01 +0000416 master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
Denis Vlasenkoc8717cd2006-11-22 16:10:39 +0000417 xlisten(master_fd, 1);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000418 }
Rob Landley00e76cb2005-05-10 23:53:33 +0000419#else
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000420 sessions = make_new_session();
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000421 if (!sessions) /* pty opening or vfork problem, exit */
422 return 1; /* make_new_session prints error message */
Rob Landley00e76cb2005-05-10 23:53:33 +0000423#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000424
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000425 /* We don't want to die if just one session is broken */
426 signal(SIGPIPE, SIG_IGN);
Eric Andersen08a72202002-09-30 20:52:10 +0000427
Denis Vlasenko2450c452007-10-15 22:09:15 +0000428 if (opt & OPT_WATCHCHILD)
429 signal(SIGCHLD, handle_sigchld);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000430 else /* prevent dead children from becoming zombies */
431 signal(SIGCHLD, SIG_IGN);
Denis Vlasenko2450c452007-10-15 22:09:15 +0000432
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000433/*
434 This is how the buffers are used. The arrows indicate the movement
435 of data.
436 +-------+ wridx1++ +------+ rdidx1++ +----------+
437 | | <-------------- | buf1 | <-------------- | |
438 | | size1-- +------+ size1++ | |
439 | pty | | socket |
440 | | rdidx2++ +------+ wridx2++ | |
441 | | --------------> | buf2 | --------------> | |
442 +-------+ size2++ +------+ size2-- +----------+
443
444 size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
445 size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"
446
447 Each session has got two buffers. Buffers are circular. If sizeN == 0,
448 buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
449 rdidxN == wridxN.
450*/
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000451 again:
452 FD_ZERO(&rdfdset);
453 FD_ZERO(&wrfdset);
Eric Andersen08a72202002-09-30 20:52:10 +0000454
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000455 /* Select on the master socket, all telnet sockets and their
456 * ptys if there is room in their session buffers.
457 * NB: scalability problem: we recalculate entire bitmap
458 * before each select. Can be a problem with 500+ connections. */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000459 ts = sessions;
460 while (ts) {
Denis Vlasenko2450c452007-10-15 22:09:15 +0000461 struct tsession *next = ts->next; /* in case we free ts. */
462 if (ts->shell_pid == -1) {
Denis Vlasenko018b1552007-11-06 01:38:46 +0000463 /* Child died and we detected that */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000464 free_session(ts);
465 } else {
466 if (ts->size1 > 0) /* can write to pty */
467 FD_SET(ts->ptyfd, &wrfdset);
468 if (ts->size1 < BUFSIZE) /* can read from socket */
469 FD_SET(ts->sockfd_read, &rdfdset);
470 if (ts->size2 > 0) /* can write to socket */
471 FD_SET(ts->sockfd_write, &wrfdset);
472 if (ts->size2 < BUFSIZE) /* can read from pty */
473 FD_SET(ts->ptyfd, &rdfdset);
474 }
475 ts = next;
476 }
477 if (!IS_INETD) {
478 FD_SET(master_fd, &rdfdset);
479 /* This is needed because free_session() does not
Denis Vlasenko018b1552007-11-06 01:38:46 +0000480 * take master_fd into account when it finds new
Denis Vlasenko2450c452007-10-15 22:09:15 +0000481 * maxfd among remaining fd's */
482 if (master_fd > maxfd)
483 maxfd = master_fd;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000484 }
485
Denis Vlasenko10916c52007-10-15 17:28:00 +0000486 count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
487 if (count < 0)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000488 goto again; /* EINTR or ENOMEM */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000489
490#if ENABLE_FEATURE_TELNETD_STANDALONE
491 /* First check for and accept new sessions. */
492 if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000493 int fd;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000494 struct tsession *new_ts;
495
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000496 fd = accept(master_fd, NULL, NULL);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000497 if (fd < 0)
498 goto again;
499 /* Create a new session and link it into our active list */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000500 new_ts = make_new_session(fd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000501 if (new_ts) {
502 new_ts->next = sessions;
503 sessions = new_ts;
504 } else {
505 close(fd);
Eric Andersen08a72202002-09-30 20:52:10 +0000506 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000507 }
508#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000509
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000510 /* Then check for data tunneling. */
511 ts = sessions;
512 while (ts) { /* For all sessions... */
513 struct tsession *next = ts->next; /* in case we free ts. */
Eric Andersen08a72202002-09-30 20:52:10 +0000514
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000515 if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000516 int num_totty;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000517 unsigned char *ptr;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000518 /* Write to pty from buffer 1. */
519 ptr = remove_iacs(ts, &num_totty);
Denis Vlasenko10916c52007-10-15 17:28:00 +0000520 count = safe_write(ts->ptyfd, ptr, num_totty);
521 if (count < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000522 if (errno == EAGAIN)
523 goto skip1;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000524 goto kill_session;
Eric Andersen08a72202002-09-30 20:52:10 +0000525 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000526 ts->size1 -= count;
527 ts->wridx1 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000528 if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
Eric Andersen08a72202002-09-30 20:52:10 +0000529 ts->wridx1 = 0;
Eric Andersen08a72202002-09-30 20:52:10 +0000530 }
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000531 skip1:
532 if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000533 /* Write to socket from buffer 2. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000534 count = MIN(BUFSIZE - ts->wridx2, ts->size2);
535 count = safe_write(ts->sockfd_write, TS_BUF2 + ts->wridx2, count);
536 if (count < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000537 if (errno == EAGAIN)
538 goto skip2;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000539 goto kill_session;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000540 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000541 ts->size2 -= count;
542 ts->wridx2 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000543 if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000544 ts->wridx2 = 0;
545 }
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000546 skip2:
Denis Vlasenko10916c52007-10-15 17:28:00 +0000547 /* Should not be needed, but... remove_iacs is actually buggy
548 * (it cannot process iacs which wrap around buffer's end)!
549 * Since properly fixing it requires writing bigger code,
Denis Vlasenkof472b232007-10-16 21:35:17 +0000550 * we rely instead on this code making it virtually impossible
Denis Vlasenko10916c52007-10-15 17:28:00 +0000551 * to have wrapped iac (people don't type at 2k/second).
552 * It also allows for bigger reads in common case. */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000553 if (ts->size1 == 0) {
554 ts->rdidx1 = 0;
555 ts->wridx1 = 0;
556 }
557 if (ts->size2 == 0) {
558 ts->rdidx2 = 0;
559 ts->wridx2 = 0;
560 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000561
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000562 if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
563 /* Read from socket to buffer 1. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000564 count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
565 count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count);
566 if (count <= 0) {
567 if (count < 0 && errno == EAGAIN)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000568 goto skip3;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000569 goto kill_session;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000570 }
571 /* Ignore trailing NUL if it is there */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000572 if (!TS_BUF1[ts->rdidx1 + count - 1]) {
Denis Vlasenkof472b232007-10-16 21:35:17 +0000573 --count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000574 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000575 ts->size1 += count;
576 ts->rdidx1 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000577 if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
578 ts->rdidx1 = 0;
579 }
580 skip3:
581 if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
582 /* Read from pty to buffer 2. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000583 count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
584 count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count);
585 if (count <= 0) {
586 if (count < 0 && errno == EAGAIN)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000587 goto skip4;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000588 goto kill_session;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000589 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000590 ts->size2 += count;
591 ts->rdidx2 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000592 if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
593 ts->rdidx2 = 0;
594 }
595 skip4:
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000596 ts = next;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000597 continue;
598 kill_session:
599 free_session(ts);
600 ts = next;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000601 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000602
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000603 goto again;
Eric Andersen08a72202002-09-30 20:52:10 +0000604}