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