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