New telnet features:
[vde.git] / vdetelweb / telnet.c
blobebf2c0417a13781855fac733cc2b403703284982
1 /*
2 * VDETELWEB: VDE telnet and WEB interface
4 * telnet.c: telnet module
5 *
6 * Copyright 2005,2007 Renzo Davoli University of Bologna - Italy
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, version 2 of the License.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 * $Id$
25 #define _GNU_SOURCE
26 #include <config.h>
27 #include <stdio.h>
28 #include <signal.h>
29 #include <stdarg.h>
30 #include <syslog.h>
31 #include <fcntl.h>
32 #include <errno.h>
33 #include <sys/types.h>
34 #include <sys/socket.h>
35 #include <sys/poll.h>
36 #include <linux/un.h>
37 #include <netinet/in.h>
38 #include <arpa/inet.h>
39 #include <arpa/telnet.h>
40 #include <string.h>
41 #include <getopt.h>
42 #include "vdetelweb.h"
43 #include <lwipv6.h>
45 #define TELNET_TCP_PORT 23
46 #define TELNET_LOGIN 0x0
47 #define TELNET_COMMAND 0x1
48 #define TELNET_PASSWD 0x80
49 #define HISTORYSIZE 32
51 static char **commandlist;
53 struct telnetstat {
54 unsigned char status;
55 unsigned char echo;
56 unsigned char telnetprotocol;
57 unsigned char edited; /* the linebuf has been modified (left/right arrow)*/
58 unsigned char vindata; /* 1 when in_data... (0000 end with .)*/
59 char lastchar; /* for double tag*/
60 char linebuf[BUFSIZE]; /*line buffer from the user*/
61 int bufindex; /*current editing position on the buf */
62 char vlinebuf[BUFSIZE+1]; /*line buffer from vde*/
63 int vbufindex; /*current editing position on the buf */
64 char *history[HISTORYSIZE]; /*history of the previous commands*/
65 int histindex; /* index on the history (changed with up/down arrows) */
66 int lwipfd; /* fd to the network */
67 int vdemgmtfd; /* mgmt fd to vde_switch */
70 void telnet_close(int fn,int fd)
72 struct telnetstat *st=status[fn];
73 int i;
74 for (i=0;i<HISTORYSIZE;i++)
75 if(st->history[i])
76 free(st->history[i]);
77 delpfd(pfdsearch(st->lwipfd));
78 lwip_close(st->lwipfd);
79 if (st->vdemgmtfd >= 0) {
80 delpfd(pfdsearch(st->vdemgmtfd));
81 close(st->vdemgmtfd);
83 free(st);
86 #define CC_HEADER 0
87 #define CC_BODY 1
88 #define CC_TERM 2
89 #define MAX_KEYWORDS 128
91 static int commonprefix(char *x, char *y,int maxlen)
93 int len=0;
94 while (*(x++)==*(y++) && len<maxlen)
95 len++;
96 return len;
99 static void showexpand(char *linebuf,int bufindex, int fd)
101 char *buf;
102 int bufsize;
103 FILE *ms=open_memstream(&buf,&bufsize);
104 int nmatches=0;
105 if (ms) {
106 if (commandlist && bufindex>0) {
107 char **s=commandlist;
108 char *match;
109 while (*s) {
110 if (strncmp(linebuf,*s,bufindex)==0) {
111 nmatches++;
112 fprintf(ms,"%s ",*s);
114 s++;
116 fprintf(ms,"\r\n");
118 fclose(ms);
119 if (nmatches > 1)
120 lwip_write(fd,buf,strlen(buf));
121 free(buf);
125 static int tabexpand(char *linebuf,int bufindex,int maxlength)
127 if (commandlist && bufindex>0) {
128 char **s=commandlist;
129 int nmatches=0,len;
130 char *match;
131 while (*s) {
132 if (strncmp(linebuf,*s,bufindex)==0) {
133 nmatches++;
134 if (nmatches == 1) {
135 match=*s;
136 len=strlen(match);
137 } else
138 len=commonprefix(match,*s,len);
140 s++;
142 if (len > 0) {
143 int alreadymatch=commonprefix(linebuf,match,len);
144 //fprintf(stderr,"TAB %s %d -> %s %d already %d\n",linebuf,bufindex,match,len,alreadymatch);
145 if ((len-alreadymatch)+strlen(linebuf) < maxlength) {
146 memmove(linebuf+len,linebuf+alreadymatch,
147 strlen(linebuf+alreadymatch)+1);
148 memcpy(linebuf+alreadymatch,match+alreadymatch,len-alreadymatch);
149 if (nmatches == 1 && linebuf[len] != ' ' && strlen(linebuf)+1 < maxlength) {
150 memmove(linebuf+len+1,linebuf+len,
151 strlen(linebuf+len)+1);
152 linebuf[len]=' ';
153 len++;
155 bufindex=len;
159 return bufindex;
162 static int qstrcmp(const void *a,const void *b)
164 return strcmp(*(char * const *)a,*(char * const *)b);
166 static void create_commandlist()
168 int vdefd=openextravdem();
169 char buf[BUFSIZE];
170 char linebuf[BUFSIZE];
171 char *localclist[MAX_KEYWORDS];
172 int nkeywords=0;
173 int i,j;
174 if (vdefd) {
175 int status=CC_HEADER;
176 FILE *in=fdopen(vdefd,"r");
177 write(vdefd,"help\n",5);
178 while (status != CC_TERM && fgets(linebuf,BUFSIZE,in) != NULL) {
179 if (status == CC_HEADER) {
180 if (strncmp(linebuf,"------------",12) == 0)
181 status=CC_BODY;
182 } else {
183 if (strncmp(linebuf,".\n",2) == 0)
184 status=CC_TERM;
185 else {
186 char *s=linebuf;
187 while (*s!=' ' && *s != 0)
188 s++;
189 *s=0; /* take the first token */
190 //fprintf(stderr,"%s\n",linebuf);
191 localclist[nkeywords]=strdup(linebuf);
192 if (nkeywords<MAX_KEYWORDS) nkeywords++;
193 char *thiskeyword=strdup(linebuf);
197 qsort(localclist,nkeywords,sizeof(char *),qstrcmp);
198 for (i=j=0; i<nkeywords-1; i++)
199 if (strncmp(localclist[i],localclist[i+1],strlen(localclist[i]))==0 &&
200 localclist[i+1][strlen(localclist[i])] == '/') {
201 free(localclist[i]); /*avoid menu*/
202 } else {
203 localclist[j]=localclist[i];
204 j++;
206 nkeywords=j;
207 close(vdefd);
209 nkeywords++;
210 commandlist=malloc(nkeywords*sizeof(char *));
211 if (commandlist) {
212 for (i=0;i<nkeywords;i++)
213 commandlist[i]=localclist[i];
214 commandlist[i]=NULL;
218 static void erase_line(struct telnetstat *st,int prompt_too)
220 int j;
221 int size=st->bufindex+(prompt_too != 0)*strlen(prompt);
222 char *buf;
223 int bufsize;
224 FILE *ms=open_memstream(&buf,&bufsize);
225 if (ms) {
226 for (j=0;j<size;j++)
227 fputc('\010',ms);
228 size=strlen(st->linebuf)+(prompt_too != 0)*strlen(prompt);
229 for (j=0;j<size;j++)
230 fputc(' ',ms);
231 for (j=0;j<size;j++)
232 fputc('\010',ms);
233 fclose(ms);
234 if (buf)
235 lwip_write(st->lwipfd,buf,bufsize);
236 free(buf);
240 static void redraw_line(struct telnetstat *st,int prompt_too)
242 int j;
243 int tail=strlen(st->linebuf)-st->bufindex;
244 char *buf;
245 int bufsize;
246 FILE *ms=open_memstream(&buf,&bufsize);
247 if (ms) {
248 if (prompt_too)
249 fprintf(ms,"%s%s",prompt,st->linebuf);
250 else
251 fprintf(ms,"%s",st->linebuf);
252 for (j=0;j<tail;j++)
253 fputc('\010',ms);
254 fclose(ms);
255 if (buf)
256 lwip_write(st->lwipfd,buf,bufsize);
257 free(buf);
261 void telnet_getanswer(struct telnetstat *st)
263 char buf[BUFSIZE+1];
264 int n=0,ib=0;
265 n=read(st->vdemgmtfd,buf,BUFSIZE);
266 buf[n]=0;
267 while (n>0) {
268 for(ib=0;ib<n;ib++)
270 st->vlinebuf[(st->vbufindex)++]=buf[ib];
271 if (buf[ib] == '\n') {
272 st->vlinebuf[(st->vbufindex)-1]='\r';
273 st->vlinebuf[(st->vbufindex)]='\n';
274 st->vlinebuf[(st->vbufindex)+1]='\0';
275 (st->vbufindex)++;
276 if (st->vindata) {
277 if (st->vlinebuf[0]=='.' && st->vlinebuf[1]=='\r')
278 st->vindata=0;
279 else
280 lwip_write(st->lwipfd,st->vlinebuf,(st->vbufindex));
281 } else {
282 char *message=st->vlinebuf;
283 //fprintf(stderr,"MSG1 \"%s\"\n",message);
284 while (*message != '\0' &&
285 !(isdigit(message[0]) &&
286 isdigit(message[1]) &&
287 isdigit(message[2]) &&
288 isdigit(message[3])))
289 message++;
290 //fprintf(stderr,"MSG2 \"%s\"\n",message);
291 if (strncmp(message,"0000",4)==0)
292 st->vindata=1;
293 else if(message[0]=='1' &&
294 isdigit(message[1]) &&
295 isdigit(message[2]) &&
296 isdigit(message[3])) {
297 message+=5;
298 lwip_write(st->lwipfd,message,strlen(message));
299 } else if (message[0]=='3' &&
300 isdigit(message[1]) &&
301 isdigit(message[2]) &&
302 isdigit(message[3])) {
303 message+=5;
304 lwip_write(st->lwipfd,"** DBG MSG: ",12);
305 lwip_write(st->lwipfd,(message),strlen(message));
308 (st->vbufindex)=0;
311 n=read(st->vdemgmtfd,buf,BUFSIZE);
315 int vdedata(int fn,int fd,int vdefd)
317 struct telnetstat *st=status[fn];
318 erase_line(st,1);
319 if (st->vdemgmtfd)
320 telnet_getanswer(st);
321 redraw_line(st,1);
324 void telnet_core(int fn,int fd,int vdefd)
326 struct telnetstat *st=status[fn];
328 switch (st->status) {
329 case TELNET_LOGIN:
330 while (st->linebuf[st->bufindex-1] == '\n')
331 st->linebuf[--st->bufindex]=0;
332 if (strcmp(st->linebuf,"admin") != 0) {
333 lwip_write(fd,"login incorrect\r\n\r\nLogin: ",26);
334 } else {
335 lwip_write(fd,"Password: ",11);
336 st->status=TELNET_PASSWD;
338 break;
339 case TELNET_PASSWD:
340 case TELNET_PASSWD+1:
341 case TELNET_PASSWD+2:
342 while (st->linebuf[st->bufindex-1] == '\n')
343 st->linebuf[--st->bufindex]=0;
344 if (strcmp(st->linebuf,passwd) != 0) {
345 st->status++;
346 if (st->status < TELNET_PASSWD + 3)
347 lwip_write(fd,"\r\nlogin incorrect\r\n\r\nPassword: ",30);
348 else
349 telnet_close(fn,fd);
350 } else {
351 int newfn;
352 int flags;
353 st->vdemgmtfd=openextravdem();
354 flags = fcntl(st->vdemgmtfd, F_GETFL);
355 flags |= O_NONBLOCK;
356 fcntl(st->vdemgmtfd, F_SETFL, flags);
357 if (st->vdemgmtfd >= 0) {
358 newfn=addpfd(st->vdemgmtfd,vdedata);
359 status[newfn]=st;
360 } else
361 telnet_close(fn,fd);
362 st->status=TELNET_COMMAND;
363 lwip_write(fd,"\r\n",2);
364 lwip_write(fd,prompt,strlen(prompt));
366 break;
367 case TELNET_COMMAND:
369 char *cmd=st->linebuf;
370 while (*cmd == ' ' || *cmd == '\t')
371 cmd++;
372 if (strncmp(cmd,"logout",6)==0)
373 telnet_close(fn,fd);
374 else {
375 if (*cmd != 0) {
376 write(st->vdemgmtfd,st->linebuf,st->bufindex);
377 if (strncmp(cmd,"shutdown",8)==0) {
378 telnet_close(fn,fd);
379 exit(0);
380 } /*else
381 telnet_getanswer(fd,st->vdemgmtfd);*/
383 lwip_write(fd,"\r\n",2);
384 lwip_write(fd,prompt,strlen(prompt));
386 break;
391 static void telnet_option_send3(int fd,int action,int object)
393 char opt[3];
394 opt[0]=0xff;
395 opt[1]=action;
396 opt[2]=object;
397 lwip_write(fd,opt,3);
400 static int telnet_options(int fn,int fd,unsigned char *s)
402 struct telnetstat *st=status[fn];
403 register int action_n_object;
404 if (st->telnetprotocol == 0) {
405 st->telnetprotocol=1;
406 telnet_option_send3(fd,WILL,TELOPT_ECHO);
408 int skip=2;
409 s++;
410 action_n_object=((*s)<<8) + (*(s+1));
411 switch (action_n_object) {
412 case (DO<<8) + TELOPT_ECHO:
413 //printf("okay echo\n");
414 st->echo=1;
415 break;
416 case (WILL<<8) + TELOPT_ECHO:
417 telnet_option_send3(fd,DONT,TELOPT_ECHO);
418 telnet_option_send3(fd,WILL,TELOPT_ECHO);
419 break;
420 case (DO<<8) + TELOPT_SGA:
421 //printf("do sga -> okay will sga\n");
422 telnet_option_send3(fd,WILL,TELOPT_SGA);
423 break;
424 case (WILL<<8) + TELOPT_TTYPE:
425 //printf("will tty -> dont tty\n");
426 telnet_option_send3(fd,DONT,TELOPT_TTYPE);
427 break;
428 default:
429 //printf("not managed yet %x %x\n",*s,*(s+1));
430 if (*s == WILL)
431 telnet_option_send3(fd,DONT,*(s+1));
432 else if (*s == DO)
433 telnet_option_send3(fd,WONT,*(s+1));
435 return skip;
439 static void erase_line(int fd, struct telnetstat *st)
441 int j;
442 for (j=0;j<st->bufindex;j++)
443 lwip_write(fd,"\033[D",3);
444 for (j=0;j<strlen(st->linebuf);j++)
445 lwip_write(fd,"\033[P",3);
449 static void put_history(struct telnetstat *st)
451 if(st->history[st->histindex])
452 free(st->history[st->histindex]);
453 st->history[st->histindex]=strdup(st->linebuf);
456 static void get_history(int change,struct telnetstat *st)
458 st->histindex += change;
459 if(st->histindex < 0) st->histindex=0;
460 if(st->histindex >= HISTORYSIZE) st->histindex=HISTORYSIZE-1;
461 if(st->history[st->histindex] == NULL) (st->histindex)--;
462 strcpy(st->linebuf,st->history[st->histindex]);
463 st->bufindex=strlen(st->linebuf);
466 static void shift_history(struct telnetstat *st)
468 if (st->history[HISTORYSIZE-1] != NULL)
469 free(st->history[HISTORYSIZE-1]);
470 memmove(st->history+1,st->history,(HISTORYSIZE-1)*sizeof(char *));
471 st->history[0]=NULL;
474 int telnetdata(int fn,int fd,int vdefd)
476 unsigned char buf[BUFSIZE];
477 int n,i;
478 struct telnetstat *st=status[fn];
479 n=lwip_read(fd,buf,BUFSIZE);
480 //printf("N%d %x %x %x %x\n",n,buf[0],buf[1],buf[2],buf[3]);
481 if (n==0)
482 telnet_close(fn,fd);
483 else if (n<0)
484 printlog(LOG_ERR,"telnet read err: %s",strerror(errno));
485 else {
486 for (i=0;i<n && strlen(st->linebuf)<BUFSIZE;i++) {
487 if (buf[i] == 0xff && buf[i+1] == 0xff)
488 i++;
489 if(buf[i]==0) buf[i]='\n'; /*telnet encode \n as a 0 when in raw mode*/
490 if (buf[i] == 0xff && buf[i+1] != 0xff) {
491 i+=telnet_options(fn,fd,buf+i);
492 } else if(buf[i] == 0x1b) {
493 /* ESCAPE! */
494 if (buf[i+1]=='[' && st->status == TELNET_COMMAND) {
495 st->edited=1;
496 switch (buf[i+2]) {
497 case 'A': //fprintf(stderr,"UP\n");
498 erase_line(st,0);
499 put_history(st);
500 get_history(1,st);
501 redraw_line(st,0);
502 //lwip_write(fd,st->linebuf,st->bufindex);
503 st->bufindex=strlen(st->linebuf);
504 break;
505 case 'B': //fprintf(stderr,"DOWN\n");
506 erase_line(st,0);
507 put_history(st);
508 get_history(-1,st);
509 redraw_line(st,0);
510 //lwip_write(fd,st->linebuf,st->bufindex);
511 break;
512 case 'C': //fprintf(stderr,"RIGHT\n");
513 if (st->linebuf[st->bufindex] != '\0') {
514 lwip_write(fd,"\033[C",3);
515 (st->bufindex)++;
517 break;
518 case 'D': //fprintf(stderr,"LEFT\n");
519 if (st->bufindex > 0) {
520 lwip_write(fd,"\033[D",3);
521 (st->bufindex)--;
523 break;
525 i+=3;
527 else
528 i+=2;/* ignored */
529 } else if(buf[i] < 0x20 && !(buf[i] == '\n' || buf[i] == '\r')) {
530 /*ctrl*/
531 if (buf[i] == 4) /*ctrl D is a shortcut for UNIX people! */ {
532 telnet_close(fn,fd);
533 break;
535 if (buf[i] == 3) /*ctrl C cleans the current buffer */ {
536 erase_line(st,0);
537 st->bufindex=0;
538 st->linebuf[(st->bufindex)]=0;
539 break;
541 if (buf[i] == 12) /* ctrl L redraw */ {
542 erase_line(st,1);
543 redraw_line(st,1);
545 if (buf[i] == '\t') /* tab */ {
546 if (st->lastchar== '\t') {
547 erase_line(st,1);
548 showexpand(st->linebuf,st->bufindex,fd);
549 redraw_line(st,1);
550 } else {
551 erase_line(st,0);
552 st->bufindex=tabexpand(st->linebuf,st->bufindex,BUFSIZE);
553 redraw_line(st,0);
556 } else if(buf[i] == 0x7f) {
557 if(st->bufindex > 0) {
558 char *x;
559 (st->bufindex)--;
560 x=st->linebuf+st->bufindex;
561 memmove(x,x+1,strlen(x));
562 if (st->echo && st->status<TELNET_PASSWD) {
563 if (st->edited)
564 lwip_write(fd,"\010\033[P",4);
565 else
566 lwip_write(fd,"\010 \010",3);
569 } else {
570 if (st->echo && st->status<TELNET_PASSWD) {
571 if (st->edited && buf[i] >= ' ')
572 lwip_write(fd,"\033[@",3);
573 lwip_write(fd,&(buf[i]),1);
575 if (buf[i] != '\r') {
576 if (buf[i]=='\n') {
577 if (st->status == TELNET_COMMAND) {
578 st->histindex=0;
579 put_history(st);
580 if (strlen(st->linebuf) > 0)
581 shift_history(st);
583 st->bufindex=strlen(st->linebuf);
584 telnet_core(fn,fd,vdefd);
585 st->bufindex=st->edited=st->histindex=0;
586 st->linebuf[(st->bufindex)]=0;
587 } else {
588 char *x;
589 x=st->linebuf+st->bufindex;
590 memmove(x+1,x,strlen(x)+1);
591 st->linebuf[(st->bufindex)++]=buf[i];
595 st->lastchar=buf[i];
600 int telnetaccept(int fn,int fd,int vdefd)
602 struct sockaddr_in cli_addr;
603 int newsockfd;
604 unsigned int clilen;
605 struct telnetstat *st;
606 int newfn;
607 int i;
609 clilen = sizeof(cli_addr);
610 newsockfd = lwip_accept(fd, (struct sockaddr *) &cli_addr, &clilen);
612 if (newsockfd < 0) {
613 printlog(LOG_ERR,"telnet accept err: %s",strerror(errno));
616 newfn=addpfd(newsockfd,telnetdata);
617 status[newfn]=st=malloc(sizeof(struct telnetstat));
618 st->status=TELNET_LOGIN;
619 st->echo=0;
620 st->telnetprotocol=0;
621 st->bufindex=st->edited=st->histindex=st->vbufindex=st->vindata=st->lastchar=0;
622 st->lwipfd=newsockfd;
623 st->linebuf[(st->bufindex)]=0;
624 st->vlinebuf[(st->vbufindex)]=0;
625 st->vdemgmtfd=-1;
626 for (i=0;i<HISTORYSIZE;i++)
627 st->history[i]=0;
628 lwip_write(newsockfd,banner,strlen(banner));
629 lwip_write(newsockfd,"\r\nLogin: ",9);
630 return 0;
633 void telnet_init(int vdefd)
635 int sockfd;
636 int one=1;
637 struct sockaddr_in serv_addr;
638 sockfd=lwip_socket(AF_INET, SOCK_STREAM, 0);
640 if (!sockfd) {
641 printlog(LOG_ERR,"telnet socket err: %s",strerror(errno));
643 if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *) &one,
644 sizeof(one)) < 0){
645 printlog(LOG_ERR,"telnet setsockopt: %s",strerror(errno));
646 return;
648 if(fcntl(sockfd, F_SETFL, O_NONBLOCK) < 0){
649 printlog(LOG_ERR,"Setting O_NONBLOCK telnet: %s",strerror(errno));
650 return;
653 bzero((char *) &serv_addr, sizeof(serv_addr));
654 serv_addr.sin_family = AF_INET;
655 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
656 serv_addr.sin_port = htons(TELNET_TCP_PORT);
658 if (lwip_bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
659 printlog(LOG_ERR,"telnet bind err: %s",strerror(errno));
662 lwip_listen(sockfd, 5);
664 addpfd(sockfd,telnetaccept);
665 create_commandlist();