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