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