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