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