1 /* 2 * Copyright (c) 1985, 1988, 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by the University of 16 * California, Berkeley and its contributors. 17 * 4. Neither the name of the University nor the names of its contributors 18 * may be used to endorse or promote products derived from this software 19 * without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 * 33 * @(#)ftpcmd.y 8.3 (Berkeley) 4/6/94 34 */ 35 36 /* 37 * Grammar for FTP commands. 38 * See RFC 959. 39 */ 40 41 %{ 42 43 #ifndef lint 44 static char sccsid[] = "@(#)ftpcmd.y 8.3 (Berkeley) 4/6/94"; 45 #endif /* not lint */ 46 47 #include <sys/param.h> 48 #include <sys/socket.h> 49 #include <sys/stat.h> 50 51 #include <netinet/in.h> 52 #include <arpa/ftp.h> 53 54 #include <ctype.h> 55 #include <errno.h> 56 #include <glob.h> 57 #include <pwd.h> 58 #include <setjmp.h> 59 #include <signal.h> 60 #include <stdio.h> 61 #include <stdlib.h> 62 #include <string.h> 63 #include <syslog.h> 64 #include <time.h> 65 #include <unistd.h> 66 67 #include "extern.h" 68 69 extern struct sockaddr_in data_dest; 70 extern int logged_in; 71 extern struct passwd *pw; 72 extern int guest; 73 extern int logging; 74 extern int type; 75 extern int form; 76 extern int debug; 77 extern int timeout; 78 extern int maxtimeout; 79 extern int pdata; 80 extern char hostname[], remotehost[]; 81 extern char proctitle[]; 82 extern int usedefault; 83 extern int transflag; 84 extern char tmpline[]; 85 86 off_t restart_point; 87 88 static int cmd_type; 89 static int cmd_form; 90 static int cmd_bytesz; 91 char cbuf[512]; 92 char *fromname; 93 94 %} 95 96 %union { 97 int i; 98 char *s; 99 } 100 101 %token 102 A B C E F I 103 L N P R S T 104 105 SP CRLF COMMA 106 107 USER PASS ACCT REIN QUIT PORT 108 PASV TYPE STRU MODE RETR STOR 109 APPE MLFL MAIL MSND MSOM MSAM 110 MRSQ MRCP ALLO REST RNFR RNTO 111 ABOR DELE CWD LIST NLST SITE 112 STAT HELP NOOP MKD RMD PWD 113 CDUP STOU SMNT SYST SIZE MDTM 114 115 UMASK IDLE CHMOD 116 117 LEXERR 118 119 %token <s> STRING 120 %token <i> NUMBER 121 122 %type <i> check_login octal_number byte_size 123 %type <i> struct_code mode_code type_code form_code 124 %type <s> pathstring pathname password username 125 126 %start cmd_list 127 128 %% 129 130 cmd_list 131 : /* empty */ 132 | cmd_list cmd 133 { 134 fromname = (char *) 0; 135 restart_point = (off_t) 0; 136 } 137 | cmd_list rcmd 138 ; 139 140 cmd 141 : USER SP username CRLF 142 { 143 user($3); 144 free($3); 145 } 146 | PASS SP password CRLF 147 { 148 pass($3); 149 free($3); 150 } 151 | PORT SP host_port CRLF 152 { 153 usedefault = 0; 154 if (pdata >= 0) { 155 (void) close(pdata); 156 pdata = -1; 157 } 158 reply(200, "PORT command successful."); 159 } 160 | PASV CRLF 161 { 162 passive(); 163 } 164 | TYPE SP type_code CRLF 165 { 166 switch (cmd_type) { 167 168 case TYPE_A: 169 if (cmd_form == FORM_N) { 170 reply(200, "Type set to A."); 171 type = cmd_type; 172 form = cmd_form; 173 } else 174 reply(504, "Form must be N."); 175 break; 176 177 case TYPE_E: 178 reply(504, "Type E not implemented."); 179 break; 180 181 case TYPE_I: 182 reply(200, "Type set to I."); 183 type = cmd_type; 184 break; 185 186 case TYPE_L: 187 #if NBBY == 8 188 if (cmd_bytesz == 8) { 189 reply(200, 190 "Type set to L (byte size 8)."); 191 type = cmd_type; 192 } else 193 reply(504, "Byte size must be 8."); 194 #else /* NBBY == 8 */ 195 UNIMPLEMENTED for NBBY != 8 196 #endif /* NBBY == 8 */ 197 } 198 } 199 | STRU SP struct_code CRLF 200 { 201 switch ($3) { 202 203 case STRU_F: 204 reply(200, "STRU F ok."); 205 break; 206 207 default: 208 reply(504, "Unimplemented STRU type."); 209 } 210 } 211 | MODE SP mode_code CRLF 212 { 213 switch ($3) { 214 215 case MODE_S: 216 reply(200, "MODE S ok."); 217 break; 218 219 default: 220 reply(502, "Unimplemented MODE type."); 221 } 222 } 223 | ALLO SP NUMBER CRLF 224 { 225 reply(202, "ALLO command ignored."); 226 } 227 | ALLO SP NUMBER SP R SP NUMBER CRLF 228 { 229 reply(202, "ALLO command ignored."); 230 } 231 | RETR check_login SP pathname CRLF 232 { 233 if ($2 && $4 != NULL) 234 retrieve((char *) 0, $4); 235 if ($4 != NULL) 236 free($4); 237 } 238 | STOR check_login SP pathname CRLF 239 { 240 if ($2 && $4 != NULL) 241 store($4, "w", 0); 242 if ($4 != NULL) 243 free($4); 244 } 245 | APPE check_login SP pathname CRLF 246 { 247 if ($2 && $4 != NULL) 248 store($4, "a", 0); 249 if ($4 != NULL) 250 free($4); 251 } 252 | NLST check_login CRLF 253 { 254 if ($2) 255 send_file_list("."); 256 } 257 | NLST check_login SP STRING CRLF 258 { 259 if ($2 && $4 != NULL) 260 send_file_list($4); 261 if ($4 != NULL) 262 free($4); 263 } 264 | LIST check_login CRLF 265 { 266 if ($2) 267 retrieve("/bin/ls -lgA", ""); 268 } 269 | LIST check_login SP pathname CRLF 270 { 271 if ($2 && $4 != NULL) 272 retrieve("/bin/ls -lgA %s", $4); 273 if ($4 != NULL) 274 free($4); 275 } 276 | STAT check_login SP pathname CRLF 277 { 278 if ($2 && $4 != NULL) 279 statfilecmd($4); 280 if ($4 != NULL) 281 free($4); 282 } 283 | STAT CRLF 284 { 285 statcmd(); 286 } 287 | DELE check_login SP pathname CRLF 288 { 289 if ($2 && $4 != NULL) 290 delete($4); 291 if ($4 != NULL) 292 free($4); 293 } 294 | RNTO SP pathname CRLF 295 { 296 if (fromname) { 297 renamecmd(fromname, $3); 298 free(fromname); 299 fromname = (char *) 0; 300 } else { 301 reply(503, "Bad sequence of commands."); 302 } 303 free($3); 304 } 305 | ABOR CRLF 306 { 307 reply(225, "ABOR command successful."); 308 } 309 | CWD check_login CRLF 310 { 311 if ($2) 312 cwd(pw->pw_dir); 313 } 314 | CWD check_login SP pathname CRLF 315 { 316 if ($2 && $4 != NULL) 317 cwd($4); 318 if ($4 != NULL) 319 free($4); 320 } 321 | HELP CRLF 322 { 323 help(cmdtab, (char *) 0); 324 } 325 | HELP SP STRING CRLF 326 { 327 char *cp = $3; 328 329 if (strncasecmp(cp, "SITE", 4) == 0) { 330 cp = $3 + 4; 331 if (*cp == ' ') 332 cp++; 333 if (*cp) 334 help(sitetab, cp); 335 else 336 help(sitetab, (char *) 0); 337 } else 338 help(cmdtab, $3); 339 } 340 | NOOP CRLF 341 { 342 reply(200, "NOOP command successful."); 343 } 344 | MKD check_login SP pathname CRLF 345 { 346 if ($2 && $4 != NULL) 347 makedir($4); 348 if ($4 != NULL) 349 free($4); 350 } 351 | RMD check_login SP pathname CRLF 352 { 353 if ($2 && $4 != NULL) 354 removedir($4); 355 if ($4 != NULL) 356 free($4); 357 } 358 | PWD check_login CRLF 359 { 360 if ($2) 361 pwd(); 362 } 363 | CDUP check_login CRLF 364 { 365 if ($2) 366 cwd(".."); 367 } 368 | SITE SP HELP CRLF 369 { 370 help(sitetab, (char *) 0); 371 } 372 | SITE SP HELP SP STRING CRLF 373 { 374 help(sitetab, $5); 375 } 376 | SITE SP UMASK check_login CRLF 377 { 378 int oldmask; 379 380 if ($4) { 381 oldmask = umask(0); 382 (void) umask(oldmask); 383 reply(200, "Current UMASK is %03o", oldmask); 384 } 385 } 386 | SITE SP UMASK check_login SP octal_number CRLF 387 { 388 int oldmask; 389 390 if ($4) { 391 if (($6 == -1) || ($6 > 0777)) { 392 reply(501, "Bad UMASK value"); 393 } else { 394 oldmask = umask($6); 395 reply(200, 396 "UMASK set to %03o (was %03o)", 397 $6, oldmask); 398 } 399 } 400 } 401 | SITE SP CHMOD check_login SP octal_number SP pathname CRLF 402 { 403 if ($4 && ($8 != NULL)) { 404 if ($6 > 0777) 405 reply(501, 406 "CHMOD: Mode value must be between 0 and 0777"); 407 else if (chmod($8, $6) < 0) 408 perror_reply(550, $8); 409 else 410 reply(200, "CHMOD command successful."); 411 } 412 if ($8 != NULL) 413 free($8); 414 } 415 | SITE SP IDLE CRLF 416 { 417 reply(200, 418 "Current IDLE time limit is %d seconds; max %d", 419 timeout, maxtimeout); 420 } 421 | SITE SP IDLE SP NUMBER CRLF 422 { 423 if ($5 < 30 || $5 > maxtimeout) { 424 reply(501, 425 "Maximum IDLE time must be between 30 and %d seconds", 426 maxtimeout); 427 } else { 428 timeout = $5; 429 (void) alarm((unsigned) timeout); 430 reply(200, 431 "Maximum IDLE time set to %d seconds", 432 timeout); 433 } 434 } 435 | STOU check_login SP pathname CRLF 436 { 437 if ($2 && $4 != NULL) 438 store($4, "w", 1); 439 if ($4 != NULL) 440 free($4); 441 } 442 | SYST CRLF 443 { 444 #ifdef unix 445 #ifdef BSD 446 reply(215, "UNIX Type: L%d Version: BSD-%d", 447 NBBY, BSD); 448 #else /* BSD */ 449 reply(215, "UNIX Type: L%d", NBBY); 450 #endif /* BSD */ 451 #else /* unix */ 452 reply(215, "UNKNOWN Type: L%d", NBBY); 453 #endif /* unix */ 454 } 455 456 /* 457 * SIZE is not in RFC959, but Postel has blessed it and 458 * it will be in the updated RFC. 459 * 460 * Return size of file in a format suitable for 461 * using with RESTART (we just count bytes). 462 */ 463 | SIZE check_login SP pathname CRLF 464 { 465 if ($2 && $4 != NULL) 466 sizecmd($4); 467 if ($4 != NULL) 468 free($4); 469 } 470 471 /* 472 * MDTM is not in RFC959, but Postel has blessed it and 473 * it will be in the updated RFC. 474 * 475 * Return modification time of file as an ISO 3307 476 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx 477 * where xxx is the fractional second (of any precision, 478 * not necessarily 3 digits) 479 */ 480 | MDTM check_login SP pathname CRLF 481 { 482 if ($2 && $4 != NULL) { 483 struct stat stbuf; 484 if (stat($4, &stbuf) < 0) 485 reply(550, "%s: %s", 486 $4, strerror(errno)); 487 else if (!S_ISREG(stbuf.st_mode)) { 488 reply(550, "%s: not a plain file.", $4); 489 } else { 490 struct tm *t; 491 t = gmtime(&stbuf.st_mtime); 492 reply(213, 493 "19%02d%02d%02d%02d%02d%02d", 494 t->tm_year, t->tm_mon+1, t->tm_mday, 495 t->tm_hour, t->tm_min, t->tm_sec); 496 } 497 } 498 if ($4 != NULL) 499 free($4); 500 } 501 | QUIT CRLF 502 { 503 reply(221, "Goodbye."); 504 dologout(0); 505 } 506 | error CRLF 507 { 508 yyerrok; 509 } 510 ; 511 rcmd 512 : RNFR check_login SP pathname CRLF 513 { 514 char *renamefrom(); 515 516 restart_point = (off_t) 0; 517 if ($2 && $4) { 518 fromname = renamefrom($4); 519 if (fromname == (char *) 0 && $4) { 520 free($4); 521 } 522 } 523 } 524 | REST SP byte_size CRLF 525 { 526 fromname = (char *) 0; 527 restart_point = $3; /* XXX $3 is only "int" */ 528 reply(350, "Restarting at %qd. %s", restart_point, 529 "Send STORE or RETRIEVE to initiate transfer."); 530 } 531 ; 532 533 username 534 : STRING 535 ; 536 537 password 538 : /* empty */ 539 { 540 $$ = (char *)calloc(1, sizeof(char)); 541 } 542 | STRING 543 ; 544 545 byte_size 546 : NUMBER 547 ; 548 549 host_port 550 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA 551 NUMBER COMMA NUMBER 552 { 553 char *a, *p; 554 555 a = (char *)&data_dest.sin_addr; 556 a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7; 557 p = (char *)&data_dest.sin_port; 558 p[0] = $9; p[1] = $11; 559 data_dest.sin_family = AF_INET; 560 } 561 ; 562 563 form_code 564 : N 565 { 566 $$ = FORM_N; 567 } 568 | T 569 { 570 $$ = FORM_T; 571 } 572 | C 573 { 574 $$ = FORM_C; 575 } 576 ; 577 578 type_code 579 : A 580 { 581 cmd_type = TYPE_A; 582 cmd_form = FORM_N; 583 } 584 | A SP form_code 585 { 586 cmd_type = TYPE_A; 587 cmd_form = $3; 588 } 589 | E 590 { 591 cmd_type = TYPE_E; 592 cmd_form = FORM_N; 593 } 594 | E SP form_code 595 { 596 cmd_type = TYPE_E; 597 cmd_form = $3; 598 } 599 | I 600 { 601 cmd_type = TYPE_I; 602 } 603 | L 604 { 605 cmd_type = TYPE_L; 606 cmd_bytesz = NBBY; 607 } 608 | L SP byte_size 609 { 610 cmd_type = TYPE_L; 611 cmd_bytesz = $3; 612 } 613 /* this is for a bug in the BBN ftp */ 614 | L byte_size 615 { 616 cmd_type = TYPE_L; 617 cmd_bytesz = $2; 618 } 619 ; 620 621 struct_code 622 : F 623 { 624 $$ = STRU_F; 625 } 626 | R 627 { 628 $$ = STRU_R; 629 } 630 | P 631 { 632 $$ = STRU_P; 633 } 634 ; 635 636 mode_code 637 : S 638 { 639 $$ = MODE_S; 640 } 641 | B 642 { 643 $$ = MODE_B; 644 } 645 | C 646 { 647 $$ = MODE_C; 648 } 649 ; 650 651 pathname 652 : pathstring 653 { 654 /* 655 * Problem: this production is used for all pathname 656 * processing, but only gives a 550 error reply. 657 * This is a valid reply in some cases but not in others. 658 */ 659 if (logged_in && $1 && *$1 == '~') { 660 glob_t gl; 661 int flags = 662 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE; 663 664 memset(&gl, 0, sizeof(gl)); 665 if (glob($1, flags, NULL, &gl) || 666 gl.gl_pathc == 0) { 667 reply(550, "not found"); 668 $$ = NULL; 669 } else { 670 $$ = strdup(gl.gl_pathv[0]); 671 } 672 globfree(&gl); 673 free($1); 674 } else 675 $$ = $1; 676 } 677 ; 678 679 pathstring 680 : STRING 681 ; 682 683 octal_number 684 : NUMBER 685 { 686 int ret, dec, multby, digit; 687 688 /* 689 * Convert a number that was read as decimal number 690 * to what it would be if it had been read as octal. 691 */ 692 dec = $1; 693 multby = 1; 694 ret = 0; 695 while (dec) { 696 digit = dec%10; 697 if (digit > 7) { 698 ret = -1; 699 break; 700 } 701 ret += digit * multby; 702 multby *= 8; 703 dec /= 10; 704 } 705 $$ = ret; 706 } 707 ; 708 709 710 check_login 711 : /* empty */ 712 { 713 if (logged_in) 714 $$ = 1; 715 else { 716 reply(530, "Please login with USER and PASS."); 717 $$ = 0; 718 } 719 } 720 ; 721 722 %% 723 724 extern jmp_buf errcatch; 725 726 #define CMD 0 /* beginning of command */ 727 #define ARGS 1 /* expect miscellaneous arguments */ 728 #define STR1 2 /* expect SP followed by STRING */ 729 #define STR2 3 /* expect STRING */ 730 #define OSTR 4 /* optional SP then STRING */ 731 #define ZSTR1 5 /* SP then optional STRING */ 732 #define ZSTR2 6 /* optional STRING after SP */ 733 #define SITECMD 7 /* SITE command */ 734 #define NSTR 8 /* Number followed by a string */ 735 736 struct tab { 737 char *name; 738 short token; 739 short state; 740 short implemented; /* 1 if command is implemented */ 741 char *help; 742 }; 743 744 struct tab cmdtab[] = { /* In order defined in RFC 765 */ 745 { "USER", USER, STR1, 1, "<sp> username" }, 746 { "PASS", PASS, ZSTR1, 1, "<sp> password" }, 747 { "ACCT", ACCT, STR1, 0, "(specify account)" }, 748 { "SMNT", SMNT, ARGS, 0, "(structure mount)" }, 749 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" }, 750 { "QUIT", QUIT, ARGS, 1, "(terminate service)", }, 751 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" }, 752 { "PASV", PASV, ARGS, 1, "(set server in passive mode)" }, 753 { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" }, 754 { "STRU", STRU, ARGS, 1, "(specify file structure)" }, 755 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" }, 756 { "RETR", RETR, STR1, 1, "<sp> file-name" }, 757 { "STOR", STOR, STR1, 1, "<sp> file-name" }, 758 { "APPE", APPE, STR1, 1, "<sp> file-name" }, 759 { "MLFL", MLFL, OSTR, 0, "(mail file)" }, 760 { "MAIL", MAIL, OSTR, 0, "(mail to user)" }, 761 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" }, 762 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" }, 763 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" }, 764 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" }, 765 { "MRCP", MRCP, STR1, 0, "(mail recipient)" }, 766 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" }, 767 { "REST", REST, ARGS, 1, "<sp> offset (restart command)" }, 768 { "RNFR", RNFR, STR1, 1, "<sp> file-name" }, 769 { "RNTO", RNTO, STR1, 1, "<sp> file-name" }, 770 { "ABOR", ABOR, ARGS, 1, "(abort operation)" }, 771 { "DELE", DELE, STR1, 1, "<sp> file-name" }, 772 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" }, 773 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" }, 774 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" }, 775 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" }, 776 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" }, 777 { "SYST", SYST, ARGS, 1, "(get type of operating system)" }, 778 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" }, 779 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" }, 780 { "NOOP", NOOP, ARGS, 1, "" }, 781 { "MKD", MKD, STR1, 1, "<sp> path-name" }, 782 { "XMKD", MKD, STR1, 1, "<sp> path-name" }, 783 { "RMD", RMD, STR1, 1, "<sp> path-name" }, 784 { "XRMD", RMD, STR1, 1, "<sp> path-name" }, 785 { "PWD", PWD, ARGS, 1, "(return current directory)" }, 786 { "XPWD", PWD, ARGS, 1, "(return current directory)" }, 787 { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" }, 788 { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" }, 789 { "STOU", STOU, STR1, 1, "<sp> file-name" }, 790 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" }, 791 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" }, 792 { NULL, 0, 0, 0, 0 } 793 }; 794 795 struct tab sitetab[] = { 796 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" }, 797 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" }, 798 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" }, 799 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" }, 800 { NULL, 0, 0, 0, 0 } 801 }; 802 803 static char *copy __P((char *)); 804 static void help __P((struct tab *, char *)); 805 static struct tab * 806 lookup __P((struct tab *, char *)); 807 static void sizecmd __P((char *)); 808 static void toolong __P((int)); 809 static int yylex __P((void)); 810 811 static struct tab * 812 lookup(p, cmd) 813 struct tab *p; 814 char *cmd; 815 { 816 817 for (; p->name != NULL; p++) 818 if (strcmp(cmd, p->name) == 0) 819 return (p); 820 return (0); 821 } 822 823 #include <arpa/telnet.h> 824 825 /* 826 * getline - a hacked up version of fgets to ignore TELNET escape codes. 827 */ 828 char * 829 getline(s, n, iop) 830 char *s; 831 int n; 832 FILE *iop; 833 { 834 int c; 835 register char *cs; 836 837 cs = s; 838 /* tmpline may contain saved command from urgent mode interruption */ 839 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) { 840 *cs++ = tmpline[c]; 841 if (tmpline[c] == '\n') { 842 *cs++ = '\0'; 843 if (debug) 844 syslog(LOG_DEBUG, "command: %s", s); 845 tmpline[0] = '\0'; 846 return(s); 847 } 848 if (c == 0) 849 tmpline[0] = '\0'; 850 } 851 while ((c = getc(iop)) != EOF) { 852 c &= 0377; 853 if (c == IAC) { 854 if ((c = getc(iop)) != EOF) { 855 c &= 0377; 856 switch (c) { 857 case WILL: 858 case WONT: 859 c = getc(iop); 860 printf("%c%c%c", IAC, DONT, 0377&c); 861 (void) fflush(stdout); 862 continue; 863 case DO: 864 case DONT: 865 c = getc(iop); 866 printf("%c%c%c", IAC, WONT, 0377&c); 867 (void) fflush(stdout); 868 continue; 869 case IAC: 870 break; 871 default: 872 continue; /* ignore command */ 873 } 874 } 875 } 876 *cs++ = c; 877 if (--n <= 0 || c == '\n') 878 break; 879 } 880 if (c == EOF && cs == s) 881 return (NULL); 882 *cs++ = '\0'; 883 if (debug) { 884 if (!guest && strncasecmp("pass ", s, 5) == 0) { 885 /* Don't syslog passwords */ 886 syslog(LOG_DEBUG, "command: %.5s ???", s); 887 } else { 888 register char *cp; 889 register int len; 890 891 /* Don't syslog trailing CR-LF */ 892 len = strlen(s); 893 cp = s + len - 1; 894 while (cp >= s && (*cp == '\n' || *cp == '\r')) { 895 --cp; 896 --len; 897 } 898 syslog(LOG_DEBUG, "command: %.*s", len, s); 899 } 900 } 901 return (s); 902 } 903 904 static void 905 toolong(signo) 906 int signo; 907 { 908 909 reply(421, 910 "Timeout (%d seconds): closing control connection.", timeout); 911 if (logging) 912 syslog(LOG_INFO, "User %s timed out after %d seconds", 913 (pw ? pw -> pw_name : "unknown"), timeout); 914 dologout(1); 915 } 916 917 static int 918 yylex() 919 { 920 static int cpos, state; 921 char *cp, *cp2; 922 struct tab *p; 923 int n; 924 char c; 925 926 for (;;) { 927 switch (state) { 928 929 case CMD: 930 (void) signal(SIGALRM, toolong); 931 (void) alarm((unsigned) timeout); 932 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) { 933 reply(221, "You could at least say goodbye."); 934 dologout(0); 935 } 936 (void) alarm(0); 937 #ifdef SETPROCTITLE 938 if (strncasecmp(cbuf, "PASS", 4) != NULL) 939 setproctitle("%s: %s", proctitle, cbuf); 940 #endif /* SETPROCTITLE */ 941 if ((cp = strchr(cbuf, '\r'))) { 942 *cp++ = '\n'; 943 *cp = '\0'; 944 } 945 if ((cp = strpbrk(cbuf, " \n"))) 946 cpos = cp - cbuf; 947 if (cpos == 0) 948 cpos = 4; 949 c = cbuf[cpos]; 950 cbuf[cpos] = '\0'; 951 upper(cbuf); 952 p = lookup(cmdtab, cbuf); 953 cbuf[cpos] = c; 954 if (p != 0) { 955 if (p->implemented == 0) { 956 nack(p->name); 957 longjmp(errcatch,0); 958 /* NOTREACHED */ 959 } 960 state = p->state; 961 yylval.s = p->name; 962 return (p->token); 963 } 964 break; 965 966 case SITECMD: 967 if (cbuf[cpos] == ' ') { 968 cpos++; 969 return (SP); 970 } 971 cp = &cbuf[cpos]; 972 if ((cp2 = strpbrk(cp, " \n"))) 973 cpos = cp2 - cbuf; 974 c = cbuf[cpos]; 975 cbuf[cpos] = '\0'; 976 upper(cp); 977 p = lookup(sitetab, cp); 978 cbuf[cpos] = c; 979 if (p != 0) { 980 if (p->implemented == 0) { 981 state = CMD; 982 nack(p->name); 983 longjmp(errcatch,0); 984 /* NOTREACHED */ 985 } 986 state = p->state; 987 yylval.s = p->name; 988 return (p->token); 989 } 990 state = CMD; 991 break; 992 993 case OSTR: 994 if (cbuf[cpos] == '\n') { 995 state = CMD; 996 return (CRLF); 997 } 998 /* FALLTHROUGH */ 999 1000 case STR1: 1001 case ZSTR1: 1002 dostr1: 1003 if (cbuf[cpos] == ' ') { 1004 cpos++; 1005 state = state == OSTR ? STR2 : ++state; 1006 return (SP); 1007 } 1008 break; 1009 1010 case ZSTR2: 1011 if (cbuf[cpos] == '\n') { 1012 state = CMD; 1013 return (CRLF); 1014 } 1015 /* FALLTHROUGH */ 1016 1017 case STR2: 1018 cp = &cbuf[cpos]; 1019 n = strlen(cp); 1020 cpos += n - 1; 1021 /* 1022 * Make sure the string is nonempty and \n terminated. 1023 */ 1024 if (n > 1 && cbuf[cpos] == '\n') { 1025 cbuf[cpos] = '\0'; 1026 yylval.s = copy(cp); 1027 cbuf[cpos] = '\n'; 1028 state = ARGS; 1029 return (STRING); 1030 } 1031 break; 1032 1033 case NSTR: 1034 if (cbuf[cpos] == ' ') { 1035 cpos++; 1036 return (SP); 1037 } 1038 if (isdigit(cbuf[cpos])) { 1039 cp = &cbuf[cpos]; 1040 while (isdigit(cbuf[++cpos])) 1041 ; 1042 c = cbuf[cpos]; 1043 cbuf[cpos] = '\0'; 1044 yylval.i = atoi(cp); 1045 cbuf[cpos] = c; 1046 state = STR1; 1047 return (NUMBER); 1048 } 1049 state = STR1; 1050 goto dostr1; 1051 1052 case ARGS: 1053 if (isdigit(cbuf[cpos])) { 1054 cp = &cbuf[cpos]; 1055 while (isdigit(cbuf[++cpos])) 1056 ; 1057 c = cbuf[cpos]; 1058 cbuf[cpos] = '\0'; 1059 yylval.i = atoi(cp); 1060 cbuf[cpos] = c; 1061 return (NUMBER); 1062 } 1063 switch (cbuf[cpos++]) { 1064 1065 case '\n': 1066 state = CMD; 1067 return (CRLF); 1068 1069 case ' ': 1070 return (SP); 1071 1072 case ',': 1073 return (COMMA); 1074 1075 case 'A': 1076 case 'a': 1077 return (A); 1078 1079 case 'B': 1080 case 'b': 1081 return (B); 1082 1083 case 'C': 1084 case 'c': 1085 return (C); 1086 1087 case 'E': 1088 case 'e': 1089 return (E); 1090 1091 case 'F': 1092 case 'f': 1093 return (F); 1094 1095 case 'I': 1096 case 'i': 1097 return (I); 1098 1099 case 'L': 1100 case 'l': 1101 return (L); 1102 1103 case 'N': 1104 case 'n': 1105 return (N); 1106 1107 case 'P': 1108 case 'p': 1109 return (P); 1110 1111 case 'R': 1112 case 'r': 1113 return (R); 1114 1115 case 'S': 1116 case 's': 1117 return (S); 1118 1119 case 'T': 1120 case 't': 1121 return (T); 1122 1123 } 1124 break; 1125 1126 default: 1127 fatal("Unknown state in scanner."); 1128 } 1129 yyerror((char *) 0); 1130 state = CMD; 1131 longjmp(errcatch,0); 1132 } 1133 } 1134 1135 void 1136 upper(s) 1137 char *s; 1138 { 1139 while (*s != '\0') { 1140 if (islower(*s)) 1141 *s = toupper(*s); 1142 s++; 1143 } 1144 } 1145 1146 static char * 1147 copy(s) 1148 char *s; 1149 { 1150 char *p; 1151 1152 p = malloc((unsigned) strlen(s) + 1); 1153 if (p == NULL) 1154 fatal("Ran out of memory."); 1155 (void) strcpy(p, s); 1156 return (p); 1157 } 1158 1159 static void 1160 help(ctab, s) 1161 struct tab *ctab; 1162 char *s; 1163 { 1164 struct tab *c; 1165 int width, NCMDS; 1166 char *type; 1167 1168 if (ctab == sitetab) 1169 type = "SITE "; 1170 else 1171 type = ""; 1172 width = 0, NCMDS = 0; 1173 for (c = ctab; c->name != NULL; c++) { 1174 int len = strlen(c->name); 1175 1176 if (len > width) 1177 width = len; 1178 NCMDS++; 1179 } 1180 width = (width + 8) &~ 7; 1181 if (s == 0) { 1182 int i, j, w; 1183 int columns, lines; 1184 1185 lreply(214, "The following %scommands are recognized %s.", 1186 type, "(* =>'s unimplemented)"); 1187 columns = 76 / width; 1188 if (columns == 0) 1189 columns = 1; 1190 lines = (NCMDS + columns - 1) / columns; 1191 for (i = 0; i < lines; i++) { 1192 printf(" "); 1193 for (j = 0; j < columns; j++) { 1194 c = ctab + j * lines + i; 1195 printf("%s%c", c->name, 1196 c->implemented ? ' ' : '*'); 1197 if (c + lines >= &ctab[NCMDS]) 1198 break; 1199 w = strlen(c->name) + 1; 1200 while (w < width) { 1201 putchar(' '); 1202 w++; 1203 } 1204 } 1205 printf("\r\n"); 1206 } 1207 (void) fflush(stdout); 1208 reply(214, "Direct comments to ftp-bugs@%s.", hostname); 1209 return; 1210 } 1211 upper(s); 1212 c = lookup(ctab, s); 1213 if (c == (struct tab *)0) { 1214 reply(502, "Unknown command %s.", s); 1215 return; 1216 } 1217 if (c->implemented) 1218 reply(214, "Syntax: %s%s %s", type, c->name, c->help); 1219 else 1220 reply(214, "%s%-*s\t%s; unimplemented.", type, width, 1221 c->name, c->help); 1222 } 1223 1224 static void 1225 sizecmd(filename) 1226 char *filename; 1227 { 1228 switch (type) { 1229 case TYPE_L: 1230 case TYPE_I: { 1231 struct stat stbuf; 1232 if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) 1233 reply(550, "%s: not a plain file.", filename); 1234 else 1235 reply(213, "%qu", stbuf.st_size); 1236 break; } 1237 case TYPE_A: { 1238 FILE *fin; 1239 int c; 1240 off_t count; 1241 struct stat stbuf; 1242 fin = fopen(filename, "r"); 1243 if (fin == NULL) { 1244 perror_reply(550, filename); 1245 return; 1246 } 1247 if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) { 1248 reply(550, "%s: not a plain file.", filename); 1249 (void) fclose(fin); 1250 return; 1251 } 1252 1253 count = 0; 1254 while((c=getc(fin)) != EOF) { 1255 if (c == '\n') /* will get expanded to \r\n */ 1256 count++; 1257 count++; 1258 } 1259 (void) fclose(fin); 1260 1261 reply(213, "%qd", count); 1262 break; } 1263 default: 1264 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]); 1265 } 1266 } 1267