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