1 /* 2 * Grammar for FTP commands. 3 * See RFC 765. 4 */ 5 6 %{ 7 8 #ifndef lint 9 static char sccsid[] = "@(#)ftpcmd.y 4.9 83/03/23"; 10 #endif 11 12 #include <sys/types.h> 13 #include <sys/socket.h> 14 15 #include <netinet/in.h> 16 17 #include <stdio.h> 18 #include <signal.h> 19 #include <ctype.h> 20 #include <pwd.h> 21 #include <setjmp.h> 22 #include "ftp.h" 23 24 extern struct sockaddr_in data_dest; 25 extern int logged_in; 26 extern struct passwd *pw; 27 extern int guest; 28 extern int logging; 29 extern int type; 30 extern int form; 31 extern int debug; 32 extern int timeout; 33 extern char hostname[]; 34 extern char *globerr; 35 extern int usedefault; 36 char **glob(); 37 38 static int cmd_type; 39 static int cmd_form; 40 static int cmd_bytesz; 41 42 char *index(); 43 %} 44 45 %token 46 A B C E F I 47 L N P R S T 48 49 SP CRLF COMMA STRING NUMBER 50 51 USER PASS ACCT REIN QUIT PORT 52 PASV TYPE STRU MODE RETR STOR 53 APPE MLFL MAIL MSND MSOM MSAM 54 MRSQ MRCP ALLO REST RNFR RNTO 55 ABOR DELE CWD LIST NLST SITE 56 STAT HELP NOOP XMKD XRMD XPWD 57 XCUP 58 59 LEXERR 60 61 %start cmd_list 62 63 %% 64 65 cmd_list: /* empty */ 66 | cmd_list cmd 67 ; 68 69 cmd: USER SP username CRLF 70 = { 71 extern struct passwd *getpwnam(); 72 73 if (strcmp($3, "ftp") == 0 || 74 strcmp($3, "anonymous") == 0) { 75 if ((pw = getpwnam("ftp")) != NULL) { 76 guest = 1; 77 reply(331, 78 "Guest login ok, send ident as password."); 79 } 80 } else if (checkuser($3)) { 81 guest = 0; 82 pw = getpwnam($3); 83 reply(331, "Password required for %s.", $3); 84 } 85 if (pw == NULL) 86 reply(530, "User %s unknown.", $3); 87 free($3); 88 } 89 | PASS SP password CRLF 90 = { 91 pass($3); 92 free($3); 93 } 94 | PORT SP host_port CRLF 95 = { 96 usedefault = 0; 97 ack($1); 98 } 99 | TYPE SP type_code CRLF 100 = { 101 switch (cmd_type) { 102 103 case TYPE_A: 104 if (cmd_form == FORM_N) { 105 reply(200, "Type set to A."); 106 type = cmd_type; 107 form = cmd_form; 108 } else 109 reply(504, "Form must be N."); 110 break; 111 112 case TYPE_E: 113 reply(504, "Type E not implemented."); 114 break; 115 116 case TYPE_I: 117 reply(200, "Type set to I."); 118 type = cmd_type; 119 break; 120 121 case TYPE_L: 122 if (cmd_bytesz == 8) { 123 reply(200, 124 "Type set to L (byte size 8)."); 125 type = cmd_type; 126 } else 127 reply(504, "Byte size must be 8."); 128 } 129 } 130 | STRU SP struct_code CRLF 131 = { 132 switch ($3) { 133 134 case STRU_F: 135 reply(200, "STRU F ok."); 136 break; 137 138 default: 139 reply(502, "Unimplemented STRU type."); 140 } 141 } 142 | MODE SP mode_code CRLF 143 = { 144 switch ($3) { 145 146 case MODE_S: 147 reply(200, "MODE S ok."); 148 break; 149 150 default: 151 reply(502, "Unimplemented MODE type."); 152 } 153 } 154 | ALLO SP NUMBER CRLF 155 = { 156 ack($1); 157 } 158 | RETR check_login SP pathname CRLF 159 = { 160 if ($2 && $4 != NULL) 161 retrieve(0, $4); 162 if ($4 != NULL) 163 free($4); 164 } 165 | STOR check_login SP pathname CRLF 166 = { 167 if ($2 && $4 != NULL) 168 store($4, "w"); 169 if ($4 != NULL) 170 free($4); 171 } 172 | APPE check_login SP pathname CRLF 173 = { 174 if ($2 && $4 != NULL) 175 store($4, "a"); 176 if ($4 != NULL) 177 free($4); 178 } 179 | NLST check_login CRLF 180 = { 181 if ($2) 182 retrieve("/bin/ls", ""); 183 } 184 | NLST check_login SP pathname CRLF 185 = { 186 if ($2 && $4 != NULL) 187 retrieve("/bin/ls %s", $4); 188 if ($4 != NULL) 189 free($4); 190 } 191 | LIST check_login CRLF 192 = { 193 if ($2) 194 retrieve("/bin/ls -lg", ""); 195 } 196 | LIST check_login SP pathname CRLF 197 = { 198 if ($2 && $4 != NULL) 199 retrieve("/bin/ls -lg %s", $4); 200 if ($4 != NULL) 201 free($4); 202 } 203 | DELE check_login SP pathname CRLF 204 = { 205 if ($2 && $4 != NULL) 206 delete($4); 207 if ($4 != NULL) 208 free($4); 209 } 210 | CWD check_login CRLF 211 = { 212 if ($2) 213 cwd(pw->pw_dir); 214 } 215 | CWD check_login SP pathname CRLF 216 = { 217 if ($2 && $4 != NULL) 218 cwd($4); 219 if ($4 != NULL) 220 free($4); 221 } 222 | rename_cmd 223 | HELP CRLF 224 = { 225 help(0); 226 } 227 | HELP SP STRING CRLF 228 = { 229 help($3); 230 } 231 | NOOP CRLF 232 = { 233 ack($1); 234 } 235 | XMKD check_login SP pathname CRLF 236 = { 237 if ($2 && $4 != NULL) 238 makedir($4); 239 if ($4 != NULL) 240 free($4); 241 } 242 | XRMD check_login SP pathname CRLF 243 = { 244 if ($2 && $4 != NULL) 245 removedir($4); 246 if ($4 != NULL) 247 free($4); 248 } 249 | XPWD check_login CRLF 250 = { 251 if ($2) 252 pwd(); 253 } 254 | XCUP check_login CRLF 255 = { 256 if ($2) 257 cwd(".."); 258 } 259 | QUIT CRLF 260 = { 261 reply(221, "Goodbye."); 262 exit(0); 263 } 264 | error CRLF 265 = { 266 yyerrok; 267 } 268 ; 269 270 username: STRING 271 ; 272 273 password: STRING 274 ; 275 276 byte_size: NUMBER 277 ; 278 279 host_port: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA 280 NUMBER COMMA NUMBER 281 = { 282 register char *a, *p; 283 284 a = (char *)&data_dest.sin_addr; 285 a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7; 286 p = (char *)&data_dest.sin_port; 287 p[0] = $9; p[1] = $11; 288 data_dest.sin_family = AF_INET; 289 } 290 ; 291 292 form_code: N 293 = { 294 $$ = FORM_N; 295 } 296 | T 297 = { 298 $$ = FORM_T; 299 } 300 | C 301 = { 302 $$ = FORM_C; 303 } 304 ; 305 306 type_code: A 307 = { 308 cmd_type = TYPE_A; 309 cmd_form = FORM_N; 310 } 311 | A SP form_code 312 = { 313 cmd_type = TYPE_A; 314 cmd_form = $3; 315 } 316 | E 317 = { 318 cmd_type = TYPE_E; 319 cmd_form = FORM_N; 320 } 321 | E SP form_code 322 = { 323 cmd_type = TYPE_E; 324 cmd_form = $3; 325 } 326 | I 327 = { 328 cmd_type = TYPE_I; 329 } 330 | L 331 = { 332 cmd_type = TYPE_L; 333 cmd_bytesz = 8; 334 } 335 | L SP byte_size 336 = { 337 cmd_type = TYPE_L; 338 cmd_bytesz = $3; 339 } 340 /* this is for a bug in the BBN ftp */ 341 | L byte_size 342 = { 343 cmd_type = TYPE_L; 344 cmd_bytesz = $2; 345 } 346 ; 347 348 struct_code: F 349 = { 350 $$ = STRU_F; 351 } 352 | R 353 = { 354 $$ = STRU_R; 355 } 356 | P 357 = { 358 $$ = STRU_P; 359 } 360 ; 361 362 mode_code: S 363 = { 364 $$ = MODE_S; 365 } 366 | B 367 = { 368 $$ = MODE_B; 369 } 370 | C 371 = { 372 $$ = MODE_C; 373 } 374 ; 375 376 pathname: pathstring 377 = { 378 if ($1 && strncmp($1, "~", 1) == 0) { 379 $$ = (int)*glob($1); 380 if (globerr != NULL) { 381 reply(550, globerr); 382 $$ = NULL; 383 } 384 free($1); 385 } else 386 $$ = $1; 387 } 388 ; 389 390 pathstring: STRING 391 ; 392 393 rename_cmd: rename_from rename_to 394 = { 395 if ($1 && $2) 396 renamecmd($1, $2); 397 else 398 reply(503, "Bad sequence of commands."); 399 if ($1) 400 free($1); 401 if ($2) 402 free($2); 403 } 404 ; 405 406 rename_from: RNFR check_login SP pathname CRLF 407 = { 408 char *from = 0, *renamefrom(); 409 410 if ($2 && $4) 411 from = renamefrom($4); 412 if (from == 0 && $4) 413 free($4); 414 $$ = (int)from; 415 } 416 ; 417 418 rename_to: RNTO SP pathname CRLF 419 = { 420 $$ = $3; 421 } 422 ; 423 424 check_login: /* empty */ 425 = { 426 if (logged_in) 427 $$ = 1; 428 else { 429 reply(530, "Please login with USER and PASS."); 430 $$ = 0; 431 } 432 } 433 ; 434 435 %% 436 437 extern jmp_buf errcatch; 438 439 #define CMD 0 /* beginning of command */ 440 #define ARGS 1 /* expect miscellaneous arguments */ 441 #define STR1 2 /* expect SP followed by STRING */ 442 #define STR2 3 /* expect STRING */ 443 #define OSTR 4 /* optional STRING */ 444 445 struct tab { 446 char *name; 447 short token; 448 short state; 449 short implemented; /* 1 if command is implemented */ 450 char *help; 451 }; 452 453 struct tab cmdtab[] = { /* In order defined in RFC 765 */ 454 { "USER", USER, STR1, 1, "<sp> username" }, 455 { "PASS", PASS, STR1, 1, "<sp> password" }, 456 { "ACCT", ACCT, STR1, 0, "(specify account)" }, 457 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" }, 458 { "QUIT", QUIT, ARGS, 1, "(terminate service)", }, 459 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" }, 460 { "PASV", PASV, ARGS, 0, "(set server in passive mode)" }, 461 { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" }, 462 { "STRU", STRU, ARGS, 1, "(specify file structure)" }, 463 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" }, 464 { "RETR", RETR, STR1, 1, "<sp> file-name" }, 465 { "STOR", STOR, STR1, 1, "<sp> file-name" }, 466 { "APPE", APPE, STR1, 1, "<sp> file-name" }, 467 { "MLFL", MLFL, OSTR, 0, "(mail file)" }, 468 { "MAIL", MAIL, OSTR, 0, "(mail to user)" }, 469 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" }, 470 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" }, 471 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" }, 472 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" }, 473 { "MRCP", MRCP, STR1, 0, "(mail recipient)" }, 474 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" }, 475 { "REST", REST, STR1, 0, "(restart command)" }, 476 { "RNFR", RNFR, STR1, 1, "<sp> file-name" }, 477 { "RNTO", RNTO, STR1, 1, "<sp> file-name" }, 478 { "ABOR", ABOR, ARGS, 0, "(abort operation)" }, 479 { "DELE", DELE, STR1, 1, "<sp> file-name" }, 480 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name]" }, 481 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" }, 482 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" }, 483 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" }, 484 { "SITE", SITE, STR1, 0, "(get site parameters)" }, 485 { "STAT", STAT, OSTR, 0, "(get server status)" }, 486 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" }, 487 { "NOOP", NOOP, ARGS, 1, "" }, 488 { "XMKD", XMKD, STR1, 1, "<sp> path-name" }, 489 { "XRMD", XRMD, STR1, 1, "<sp> path-name" }, 490 { "XPWD", XPWD, ARGS, 1, "(return current directory)" }, 491 { "XCUP", XCUP, ARGS, 1, "(change to parent directory)" }, 492 { NULL, 0, 0, 0, 0 } 493 }; 494 495 struct tab * 496 lookup(cmd) 497 char *cmd; 498 { 499 register struct tab *p; 500 501 for (p = cmdtab; p->name != NULL; p++) 502 if (strcmp(cmd, p->name) == 0) 503 return (p); 504 return (0); 505 } 506 507 #include "../telnet/telnet.h" 508 509 /* 510 * getline - a hacked up version of fgets to ignore TELNET escape codes. 511 */ 512 char * 513 getline(s, n, iop) 514 char *s; 515 register FILE *iop; 516 { 517 register c; 518 register char *cs; 519 520 cs = s; 521 while (--n > 0 && (c = getc(iop)) >= 0) { 522 while (c == IAC) { 523 c = getc(iop); /* skip command */ 524 c = getc(iop); /* try next char */ 525 } 526 *cs++ = c; 527 if (c=='\n') 528 break; 529 } 530 if (c < 0 && cs == s) 531 return (NULL); 532 *cs++ = '\0'; 533 if (debug) { 534 fprintf(stderr, "FTPD: command: %s", s); 535 if (c != '\n') 536 putc('\n', stderr); 537 fflush(stderr); 538 } 539 return (s); 540 } 541 542 static int 543 toolong() 544 { 545 long now; 546 extern char *ctime(); 547 548 reply(421, 549 "Timeout (%d seconds): closing control connection.", timeout); 550 time(&now); 551 if (logging) { 552 fprintf(stderr, 553 "FTPD: User %s timed out after %d seconds at %s", 554 (pw ? pw -> pw_name : "unknown"), timeout, ctime(&now)); 555 fflush(stderr); 556 } 557 exit(1); 558 } 559 560 yylex() 561 { 562 static char cbuf[512]; 563 static int cpos, state; 564 register char *cp; 565 register struct tab *p; 566 int n; 567 char c; 568 569 for (;;) { 570 switch (state) { 571 572 case CMD: 573 signal(SIGALRM, toolong); 574 alarm(timeout); 575 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) { 576 reply(221, "You could at least say goodbye."); 577 exit(0); 578 } 579 alarm(0); 580 if (index(cbuf, '\r')) { 581 cp = index(cbuf, '\r'); 582 cp[0] = '\n'; cp[1] = 0; 583 } 584 if (index(cbuf, ' ')) 585 cpos = index(cbuf, ' ') - cbuf; 586 else 587 cpos = 4; 588 c = cbuf[cpos]; 589 cbuf[cpos] = '\0'; 590 upper(cbuf); 591 p = lookup(cbuf); 592 cbuf[cpos] = c; 593 if (p != 0) { 594 if (p->implemented == 0) { 595 nack(p->name); 596 longjmp(errcatch); 597 /* NOTREACHED */ 598 } 599 state = p->state; 600 yylval = (int) p->name; 601 return (p->token); 602 } 603 break; 604 605 case OSTR: 606 if (cbuf[cpos] == '\n') { 607 state = CMD; 608 return (CRLF); 609 } 610 /* FALL THRU */ 611 612 case STR1: 613 if (cbuf[cpos] == ' ') { 614 cpos++; 615 state = STR2; 616 return (SP); 617 } 618 break; 619 620 case STR2: 621 cp = &cbuf[cpos]; 622 n = strlen(cp); 623 cpos += n - 1; 624 /* 625 * Make sure the string is nonempty and \n terminated. 626 */ 627 if (n > 1 && cbuf[cpos] == '\n') { 628 cbuf[cpos] = '\0'; 629 yylval = copy(cp); 630 cbuf[cpos] = '\n'; 631 state = ARGS; 632 return (STRING); 633 } 634 break; 635 636 case ARGS: 637 if (isdigit(cbuf[cpos])) { 638 cp = &cbuf[cpos]; 639 while (isdigit(cbuf[++cpos])) 640 ; 641 c = cbuf[cpos]; 642 cbuf[cpos] = '\0'; 643 yylval = atoi(cp); 644 cbuf[cpos] = c; 645 return (NUMBER); 646 } 647 switch (cbuf[cpos++]) { 648 649 case '\n': 650 state = CMD; 651 return (CRLF); 652 653 case ' ': 654 return (SP); 655 656 case ',': 657 return (COMMA); 658 659 case 'A': 660 case 'a': 661 return (A); 662 663 case 'B': 664 case 'b': 665 return (B); 666 667 case 'C': 668 case 'c': 669 return (C); 670 671 case 'E': 672 case 'e': 673 return (E); 674 675 case 'F': 676 case 'f': 677 return (F); 678 679 case 'I': 680 case 'i': 681 return (I); 682 683 case 'L': 684 case 'l': 685 return (L); 686 687 case 'N': 688 case 'n': 689 return (N); 690 691 case 'P': 692 case 'p': 693 return (P); 694 695 case 'R': 696 case 'r': 697 return (R); 698 699 case 'S': 700 case 's': 701 return (S); 702 703 case 'T': 704 case 't': 705 return (T); 706 707 } 708 break; 709 710 default: 711 fatal("Unknown state in scanner."); 712 } 713 yyerror(); 714 state = CMD; 715 longjmp(errcatch); 716 } 717 } 718 719 upper(s) 720 char *s; 721 { 722 while (*s != '\0') { 723 if (islower(*s)) 724 *s = toupper(*s); 725 s++; 726 } 727 } 728 729 copy(s) 730 char *s; 731 { 732 char *p; 733 extern char *malloc(); 734 735 p = malloc(strlen(s) + 1); 736 if (p == NULL) 737 fatal("Ran out of memory."); 738 strcpy(p, s); 739 return ((int)p); 740 } 741 742 help(s) 743 char *s; 744 { 745 register struct tab *c; 746 register int width, NCMDS; 747 748 width = 0, NCMDS = 0; 749 for (c = cmdtab; c->name != NULL; c++) { 750 int len = strlen(c->name); 751 752 if (c->implemented == 0) 753 len++; 754 if (len > width) 755 width = len; 756 NCMDS++; 757 } 758 width = (width + 8) &~ 7; 759 if (s == 0) { 760 register int i, j, w; 761 int columns, lines; 762 763 lreply(214, 764 "The following commands are recognized (* =>'s unimplemented)."); 765 columns = 76 / width; 766 if (columns == 0) 767 columns = 1; 768 lines = (NCMDS + columns - 1) / columns; 769 for (i = 0; i < lines; i++) { 770 printf(" "); 771 for (j = 0; j < columns; j++) { 772 c = cmdtab + j * lines + i; 773 printf("%s%c", c->name, 774 c->implemented ? ' ' : '*'); 775 if (c + lines >= &cmdtab[NCMDS]) 776 break; 777 w = strlen(c->name); 778 while (w < width) { 779 putchar(' '); 780 w++; 781 } 782 } 783 printf("\r\n"); 784 } 785 fflush(stdout); 786 reply(214, "Direct comments to ftp-bugs@%s.", hostname); 787 return; 788 } 789 upper(s); 790 c = lookup(s); 791 if (c == (struct tab *)0) { 792 reply(504, "Unknown command %s.", s); 793 return; 794 } 795 if (c->implemented) 796 reply(214, "Syntax: %s %s", c->name, c->help); 797 else 798 reply(214, "%-*s\t%s; unimplemented.", width, c->name, c->help); 799 } 800