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