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