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