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