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