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