Another patch for ill-behaving firewalls.
[oftpd.git] / src / telnet_session.c
1 /*
2  * $Id$
3  */
4
5 #include <config.h>
6
7 #if TIME_WITH_SYS_TIME
8 # include <sys/time.h>
9 # include <time.h>
10 #else
11 # if HAVE_SYS_TIME_H
12 #  include <sys/time.h>
13 # else
14 #  include <time.h>
15 # endif
16 #endif
17
18 #include <sys/types.h>
19 #include <unistd.h>
20 #include <errno.h>
21 #include <string.h>
22 #include <pthread.h>
23 #include "daemon_assert.h"
24 #include "telnet_session.h"
25
26 /* characters to process */
27 #define SE         240
28 #define NOP        241
29 #define DATA_MARK  242
30 #define BRK        243
31 #define IP         244
32 #define AO         245
33 #define AYT        246
34 #define EC         247
35 #define EL         248
36 #define GA         249
37 #define SB         250
38 #define WILL       251
39 #define WONT       252
40 #define DO         253
41 #define DONT       254
42 #define IAC        255
43
44 /* input states */
45 #define NORMAL    0
46 #define GOT_IAC   1
47 #define GOT_WILL  2
48 #define GOT_WONT  3
49 #define GOT_DO    4
50 #define GOT_DONT  5
51 #define GOT_CR    6
52
53 /* prototypes */
54 static int invariant(const telnet_session_t *t);
55 static void process_data(telnet_session_t *t, int wait_flag);
56 static void read_incoming_data(telnet_session_t *t);
57 static void process_incoming_char(telnet_session_t *t, int c);
58 static void add_incoming_char(telnet_session_t *t, int c);
59 static int use_incoming_char(telnet_session_t *t);
60 static void write_outgoing_data(telnet_session_t *t);
61 static void add_outgoing_char(telnet_session_t *t, int c);
62 static int max_input_read(telnet_session_t *t);
63
64 /* initialize a telnet session */
65 void telnet_session_init(telnet_session_t *t, int in, int out)
66 {
67     daemon_assert(t != NULL);
68     daemon_assert(in >= 0);
69     daemon_assert(out >= 0);
70
71     t->in_fd = in;
72     t->in_errno = 0;
73     t->in_eof = 0;
74     t->in_take = t->in_add = 0;
75     t->in_buflen = 0;
76
77     t->in_status = NORMAL;
78
79     t->out_fd = out;
80     t->out_errno = 0;
81     t->out_eof = 0;
82     t->out_take = t->out_add = 0;
83     t->out_buflen = 0;
84
85     process_data(t, 0);
86
87     daemon_assert(invariant(t));
88 }
89
90 /* print output */
91 static int do_telnet_session_print(telnet_session_t *t, 
92                                    const char *s0,
93                                    const char *s, 
94                                    const char *s2)
95 {
96     int len, len0, len2;
97     int amt_printed, amt_printed0, amt_printed2;
98
99     daemon_assert(invariant(t));
100
101     len = strlen(s);
102     if (len == 0) {
103         daemon_assert(invariant(t));
104         return 1;
105     }
106     len0 = strlen(s0);
107     len2 = strlen(s2);
108
109     amt_printed = amt_printed0 = amt_printed2 = 0;
110     do {
111         if ((t->out_errno != 0) || (t->out_eof != 0)) {
112             daemon_assert(invariant(t));
113             return 0;
114         }
115         while ((amt_printed0 < len0) && (t->out_buflen < BUF_LEN))
116           {
117             daemon_assert(s0[amt_printed0] != '\0');
118             add_outgoing_char(t, s0[amt_printed0]);
119             amt_printed0++;
120           }
121         if (amt_printed0 == len0)
122           {
123             while ((amt_printed < len) && (t->out_buflen < BUF_LEN))
124               {
125                 daemon_assert(s[amt_printed] != '\0');
126                 add_outgoing_char(t, s[amt_printed]);
127                 amt_printed++;
128               }
129             if (amt_printed == len)
130               {
131                 while ((amt_printed2 < len2) && (t->out_buflen < BUF_LEN))
132                   {
133                     daemon_assert(s2[amt_printed2] != '\0');
134                     add_outgoing_char(t, s2[amt_printed2]);
135                     amt_printed2++;
136                   }
137               }
138           }
139         process_data(t, 1);
140     } while (amt_printed < len && amt_printed2 < len2);
141
142     while (t->out_buflen > 0) {
143         if ((t->out_errno != 0) || (t->out_eof != 0)) {
144             daemon_assert(invariant(t));
145             return 0;
146         }
147         process_data(t, 1);
148     } 
149
150     daemon_assert(invariant(t));
151     return 1;
152 }
153
154
155 /* print output */
156 int telnet_session_print(telnet_session_t *t, const char *s)
157 {
158   return do_telnet_session_print (t, "", s, "");
159 }
160
161 int telnet_session_print_with_prefix(telnet_session_t *t,
162                                      const char *prefix, const char *s)
163 {
164   return do_telnet_session_print (t, prefix, s, "");
165 }
166
167
168 /* print a line output */
169 int telnet_session_println(telnet_session_t *t, const char *s)
170 {
171     daemon_assert(invariant(t));
172     if (!do_telnet_session_print(t, "", s, "\015\012")) {
173         daemon_assert(invariant(t));
174         return 0;
175     }
176     daemon_assert(invariant(t));
177     return 1;
178 }
179
180 /* print a line output */
181 int telnet_session_println_with_prefix(telnet_session_t *t, 
182                                        const char *prefix, const char *s)
183 {
184     daemon_assert(invariant(t));
185     if (!do_telnet_session_print(t, prefix, s, "\015\012")) {
186         daemon_assert(invariant(t));
187         return 0;
188     }
189     daemon_assert(invariant(t));
190     return 1;
191 }
192
193 /* read a line */
194 int telnet_session_readln(telnet_session_t *t, char *buf, int buflen) 
195 {
196     int amt_read;
197
198     daemon_assert(invariant(t));
199     amt_read = 0;
200     for (;;) {
201         if ((t->in_errno != 0) || (t->in_eof != 0)) {
202             daemon_assert(invariant(t));
203             return 0;
204         }
205         while (t->in_buflen > 0) {
206             if (amt_read == buflen-1) {
207                 buf[amt_read] = '\0';
208                 daemon_assert(invariant(t));
209                 return 1;
210             }
211             daemon_assert(amt_read >= 0);
212             daemon_assert(amt_read < buflen);
213             buf[amt_read] = use_incoming_char(t);
214             if (buf[amt_read] == '\012') {
215                 daemon_assert(amt_read+1 >= 0);
216                 daemon_assert(amt_read+1 < buflen);
217                 buf[amt_read+1] = '\0';
218                 daemon_assert(invariant(t));
219                 return 1;
220             }
221             amt_read++;
222         }
223         process_data(t, 1);
224     }
225 }
226
227 void telnet_session_destroy(telnet_session_t *t)
228 {
229     daemon_assert(invariant(t));
230
231     close(t->in_fd);
232     if (t->out_fd != t->in_fd) {
233         close(t->out_fd);
234     }
235     t->in_fd = -1;
236     t->out_fd = -1;
237 }
238
239 /* receive any incoming data, send any pending data */
240 static void process_data(telnet_session_t *t, int wait_flag)
241 {
242     fd_set readfds;
243     fd_set writefds;
244     fd_set exceptfds;
245     struct timeval tv_zero;
246     struct timeval *tv;
247     int state;
248
249     /* set up our select() variables */
250     FD_ZERO(&readfds);
251     FD_ZERO(&writefds);
252     FD_ZERO(&exceptfds);
253     if (wait_flag) {
254         tv = NULL;
255     } else {
256         tv_zero.tv_sec = 0;
257         tv_zero.tv_usec = 0;
258         tv = &tv_zero;
259     }
260
261     /* only check for input if we can accept input */
262     if ((t->in_errno == 0) && 
263         (t->in_eof == 0) && 
264         (max_input_read(t) > 0)) 
265     {
266         FD_SET(t->in_fd, &readfds);
267         FD_SET(t->in_fd, &exceptfds);
268     }
269
270     /* only check for output if we have pending output */
271     if ((t->out_errno == 0) && (t->out_eof == 0) && (t->out_buflen > 0)) {
272         FD_SET(t->out_fd, &writefds);
273         FD_SET(t->out_fd, &exceptfds);
274     }
275
276     pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, &state);
277
278     /* see if there's anything to do */
279     if (select(FD_SETSIZE, &readfds, &writefds, &exceptfds, tv) > 0) {
280
281         if (FD_ISSET(t->in_fd, &exceptfds)) {
282             t->in_eof = 1;
283         } else if (FD_ISSET(t->in_fd, &readfds)) {
284             read_incoming_data(t);
285         }
286
287         if (FD_ISSET(t->out_fd, &exceptfds)) {
288             t->out_eof = 1;
289         } else if (FD_ISSET(t->out_fd, &writefds)) {
290             write_outgoing_data(t);
291         }
292     }
293
294     pthread_setcancelstate (state, NULL);
295 }
296
297 static void read_incoming_data(telnet_session_t *t)
298 {
299     size_t read_ret;
300     char buf[BUF_LEN];
301     size_t i;
302
303     /* read as much data as we have buffer space for */
304     daemon_assert(max_input_read(t) <= BUF_LEN);
305     read_ret = read(t->in_fd, buf, max_input_read(t));
306
307     /* handle three possible read results */
308     if (read_ret == -1) {
309         t->in_errno = errno;
310     } else if (read_ret == 0) {
311         t->in_eof = 1;
312     } else {
313         for (i=0; i<read_ret; i++) {
314             process_incoming_char(t, (unsigned char)buf[i]);
315         }
316     }
317 }
318
319
320 /* process a single character */
321 static void process_incoming_char(telnet_session_t *t, int c)
322 {
323     switch (t->in_status) {
324         case GOT_IAC:
325             switch (c) {
326                 case WILL:
327                     t->in_status = GOT_WILL;
328                     break;
329                 case WONT:
330                     t->in_status = GOT_WONT;
331                     break;
332                 case DO:
333                     t->in_status = GOT_DO;
334                     break;
335                 case DONT:
336                     t->in_status = GOT_DONT;
337                     break;
338                 case IAC:
339                     add_incoming_char(t, IAC);
340                     t->in_status = NORMAL;
341                     break;
342                 default:
343                     t->in_status = NORMAL;
344             }
345             break;
346         case GOT_WILL:
347             add_outgoing_char(t, IAC);
348             add_outgoing_char(t, DONT);
349             add_outgoing_char(t, c);
350             t->in_status = NORMAL;
351             break;
352         case GOT_WONT:
353             t->in_status = NORMAL;
354             break;
355         case GOT_DO:
356             add_outgoing_char(t, IAC);
357             add_outgoing_char(t, WONT);
358             add_outgoing_char(t, c);
359             t->in_status = NORMAL;
360             break;
361         case GOT_DONT:
362             t->in_status = NORMAL;
363             break;
364         case GOT_CR:
365             add_incoming_char(t, '\012');
366             if (c != '\012') {
367                 add_incoming_char(t, c);
368             }
369             t->in_status = NORMAL;
370             break;
371         case NORMAL:
372             if (c == IAC) {
373                 t->in_status = GOT_IAC;
374             } else if (c == '\015') {
375                 t->in_status = GOT_CR;
376             } else {
377                 add_incoming_char(t, c);
378             }
379     }
380 }
381
382 /* add a single character, wrapping buffer if necessary (should never occur) */
383 static void add_incoming_char(telnet_session_t *t, int c)
384 {
385     daemon_assert(t->in_add >= 0);
386     daemon_assert(t->in_add < BUF_LEN);
387     t->in_buf[t->in_add] = c;
388     t->in_add++;
389     if (t->in_add == BUF_LEN) {
390         t->in_add = 0;
391     }
392     if (t->in_buflen == BUF_LEN) {
393         t->in_take++;
394         if (t->in_take == BUF_LEN) {
395             t->in_take = 0;
396         }
397     } else {
398         t->in_buflen++;
399     }
400 }
401
402 /* remove a single character */
403 static int use_incoming_char(telnet_session_t *t)
404 {
405     int c;
406
407     daemon_assert(t->in_take >= 0);
408     daemon_assert(t->in_take < BUF_LEN);
409     c = t->in_buf[t->in_take];
410     t->in_take++;
411     if (t->in_take == BUF_LEN) {
412         t->in_take = 0;
413     }
414     t->in_buflen--;
415
416     return c;
417 }
418
419 /* add a single character, hopefully will never happen :) */
420 static void add_outgoing_char(telnet_session_t *t, int c)
421 {
422     daemon_assert(t->out_add >= 0);
423     daemon_assert(t->out_add < BUF_LEN);
424     t->out_buf[t->out_add] = c;
425     t->out_add++;
426     if (t->out_add == BUF_LEN) {
427         t->out_add = 0;
428     }
429     if (t->out_buflen == BUF_LEN) {
430         t->out_take++;
431         if (t->out_take == BUF_LEN) {
432             t->out_take = 0;
433         }
434     } else {
435         t->out_buflen++;
436     }
437 }
438
439
440 static void write_outgoing_data(telnet_session_t *t)
441 {
442     size_t write_ret;
443
444     if (t->out_take < t->out_add) {
445         /* handle a buffer that looks like this:       */
446         /*     |-- empty --|-- data --|-- empty --|    */
447         daemon_assert(t->out_take >= 0);
448         daemon_assert(t->out_take < BUF_LEN);
449         daemon_assert(t->out_buflen > 0);
450         daemon_assert(t->out_buflen + t->out_take <= BUF_LEN);
451         write_ret = write(t->out_fd, t->out_buf + t->out_take, t->out_buflen);
452     } else {
453         /* handle a buffer that looks like this:       */
454         /*     |-- data --|-- empty --|-- data --|     */
455         daemon_assert(t->out_take >= 0);
456         daemon_assert(t->out_take < BUF_LEN);
457         daemon_assert((BUF_LEN - t->out_take) > 0);
458         write_ret = write(t->out_fd, 
459                           t->out_buf + t->out_take, 
460                           BUF_LEN - t->out_take);
461     }
462
463     /* handle three possible write results */
464     if (write_ret == -1) {
465         t->out_errno = errno;
466     } else if (write_ret == 0) {
467         t->out_eof = 1;
468     } else {
469         t->out_buflen -= write_ret;
470         t->out_take += write_ret;
471         if (t->out_take >= BUF_LEN) {
472             t->out_take -= BUF_LEN;
473         }
474     }
475 }
476
477 /* return the amount of a read */
478 static int max_input_read(telnet_session_t *t)
479 {
480     int max_in;
481     int max_out;
482
483     daemon_assert(invariant(t));
484
485     /* figure out how much space is available in the input buffer */
486     if (t->in_buflen < BUF_LEN) {
487         max_in = BUF_LEN - t->in_buflen;
488     } else {
489         max_in = 0;
490     }
491
492     /* worry about space in the output buffer (for DONT/WONT replies) */
493     if (t->out_buflen < BUF_LEN) {
494         max_out = BUF_LEN - t->out_buflen;
495     } else {
496         max_out = 0;
497     }
498
499     daemon_assert(invariant(t));
500
501     /* return the minimum of the two values */
502     return (max_in < max_out) ? max_in : max_out;
503 }
504
505 #ifndef NDEBUG
506 static int invariant(const telnet_session_t *t) 
507 {
508     if (t == NULL) {
509         return 0;
510     }
511     if (t->in_fd < 0) {
512         return 0;
513     }
514     if ((t->in_take < 0) || (t->in_take >= BUF_LEN)) {
515         return 0;
516     }
517     if ((t->in_add < 0) || (t->in_add >= BUF_LEN)) {
518         return 0;
519     }
520     if ((t->in_buflen < 0) || (t->in_buflen > BUF_LEN)) {
521         return 0;
522     }
523
524     switch (t->in_status) {
525         case NORMAL:
526             break;
527         case GOT_IAC:
528             break;
529         case GOT_WILL:
530             break;
531         case GOT_WONT:
532             break;
533         case GOT_DO:
534             break;
535         case GOT_DONT:
536             break;
537         case GOT_CR:
538             break;
539         default:
540             return 0;
541     }
542
543     if (t->out_fd < 0) {
544         return 0;
545     }
546     if ((t->out_take < 0) || (t->out_take >= BUF_LEN)) {
547         return 0;
548     }
549     if ((t->out_add < 0) || (t->out_add >= BUF_LEN)) {
550         return 0;
551     }
552     if ((t->out_buflen < 0) || (t->out_buflen > BUF_LEN)) {
553         return 0;
554     }
555
556     return 1;
557 }
558 #endif /* NDEBUG */
559
560 #ifdef STUB_TEST
561 #include <sys/socket.h>
562 #include <netinet/in.h>
563 #include <signal.h>
564 #include <ctype.h>
565
566 int main()
567 {
568     telnet_session_t t;
569     char buf[64];
570     int fd;
571     int val;
572     struct sockaddr_in addr;
573     int newfd;
574     struct sockaddr_in newaddr;
575     unsigned newaddrlen;
576     int i;
577
578     daemon_assert((fd = socket(AF_INET, SOCK_STREAM, 0)) != -1);
579     val = 1;
580     daemon_assert(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) 
581         == 0);
582     addr.sin_port = htons(23);
583     addr.sin_addr.s_addr = INADDR_ANY;
584     daemon_assert(bind(fd, &addr, sizeof(addr)) == 0);
585     daemon_assert(listen(fd, SOMAXCONN) == 0);
586
587     signal(SIGPIPE, SIG_IGN);
588     while ((newfd = accept(fd, &newaddr, &newaddrlen)) >= 0) {
589         telnet_session_init(&t, newfd, newfd);
590         telnet_session_print(&t, "Password:");
591         telnet_session_readln(&t, buf, sizeof(buf));
592         for (i=0; buf[i] != '\0'; i++) {
593             printf("[%02d]: 0x%02X ", i, buf[i] & 0xFF);
594             if (isprint(buf[i])) {
595                 printf("'%c'\n", buf[i]);
596             } else {
597                 printf("'\\x%02X'\n", buf[i] & 0xFF);
598             }
599         }
600         telnet_session_println(&t, "Hello, world.");
601         telnet_session_println(&t, "Ipso, facto");
602         telnet_session_println(&t, "quid pro quo");
603         sleep(1);
604         close(newfd);
605     }
606     return 0;
607 }
608 #endif /* STUB_TEST */
609