ftpcmd.y revision 1.8
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 int yylex __P((void)); 849 850static struct tab * 851lookup(p, cmd) 852 struct tab *p; 853 char *cmd; 854{ 855 856 for (; p->name != NULL; p++) 857 if (strcmp(cmd, p->name) == 0) 858 return (p); 859 return (0); 860} 861 862#include <arpa/telnet.h> 863 864/* 865 * getline - a hacked up version of fgets to ignore TELNET escape codes. 866 */ 867char * 868getline(s, n, iop) 869 char *s; 870 int n; 871 FILE *iop; 872{ 873 int c; 874 register char *cs; 875 876 cs = s; 877/* tmpline may contain saved command from urgent mode interruption */ 878 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) { 879 *cs++ = tmpline[c]; 880 if (tmpline[c] == '\n') { 881 *cs++ = '\0'; 882 if (debug) 883 syslog(LOG_DEBUG, "command: %s", s); 884 tmpline[0] = '\0'; 885 return(s); 886 } 887 if (c == 0) 888 tmpline[0] = '\0'; 889 } 890 while ((c = getc(iop)) != EOF) { 891 c &= 0377; 892 if (c == IAC) { 893 if ((c = getc(iop)) != EOF) { 894 c &= 0377; 895 switch (c) { 896 case WILL: 897 case WONT: 898 c = getc(iop); 899 printf("%c%c%c", IAC, DONT, 0377&c); 900 (void) fflush(stdout); 901 continue; 902 case DO: 903 case DONT: 904 c = getc(iop); 905 printf("%c%c%c", IAC, WONT, 0377&c); 906 (void) fflush(stdout); 907 continue; 908 case IAC: 909 break; 910 default: 911 continue; /* ignore command */ 912 } 913 } 914 } 915 *cs++ = c; 916 if (--n <= 0 || c == '\n') 917 break; 918 } 919 if (c == EOF && cs == s) 920 return (NULL); 921 *cs++ = '\0'; 922 if (debug) { 923 if (!guest && strncasecmp("pass ", s, 5) == 0) { 924 /* Don't syslog passwords */ 925 syslog(LOG_DEBUG, "command: %.5s ???", s); 926 } else { 927 register char *cp; 928 register int len; 929 930 /* Don't syslog trailing CR-LF */ 931 len = strlen(s); 932 cp = s + len - 1; 933 while (cp >= s && (*cp == '\n' || *cp == '\r')) { 934 --cp; 935 --len; 936 } 937 syslog(LOG_DEBUG, "command: %.*s", len, s); 938 } 939 } 940 return (s); 941} 942 943void 944toolong(signo) 945 int signo; 946{ 947 948 reply(421, 949 "Timeout (%d seconds): closing control connection.", timeout); 950 if (logging) 951 syslog(LOG_INFO, "User %s timed out after %d seconds", 952 (pw ? pw -> pw_name : "unknown"), timeout); 953 dologout(1); 954} 955 956static int 957yylex() 958{ 959 static int cpos, state; 960 char *cp, *cp2; 961 struct tab *p; 962 int n; 963 char c; 964 965 for (;;) { 966 switch (state) { 967 968 case CMD: 969 (void) signal(SIGALRM, toolong); 970 (void) alarm((unsigned) timeout); 971 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) { 972 reply(221, "You could at least say goodbye."); 973 dologout(0); 974 } 975 (void) alarm(0); 976 if ((cp = strchr(cbuf, '\r'))) { 977 *cp++ = '\n'; 978 *cp = '\0'; 979 } 980#ifdef HASSETPROCTITLE 981 if (strncasecmp(cbuf, "PASS", 4) != NULL) { 982 if ((cp = strpbrk(cbuf, "\n"))) { 983 c = *cp; 984 *cp = '\0'; 985 setproctitle("%s: %s", proctitle, cbuf); 986 *cp = c; 987 } 988 } else 989 setproctitle("%s: %s", proctitle, cbuf); 990#endif /* HASSETPROCTITLE */ 991 if ((cp = strpbrk(cbuf, " \n"))) 992 cpos = cp - cbuf; 993 if (cpos == 0) 994 cpos = 4; 995 c = cbuf[cpos]; 996 cbuf[cpos] = '\0'; 997 upper(cbuf); 998 p = lookup(cmdtab, cbuf); 999 cbuf[cpos] = c; 1000 if (p != 0) { 1001 if (p->implemented == 0) { 1002 nack(p->name); 1003 longjmp(errcatch,0); 1004 /* NOTREACHED */ 1005 } 1006 state = p->state; 1007 yylval.s = p->name; 1008 return (p->token); 1009 } 1010 break; 1011 1012 case SITECMD: 1013 if (cbuf[cpos] == ' ') { 1014 cpos++; 1015 return (SP); 1016 } 1017 cp = &cbuf[cpos]; 1018 if ((cp2 = strpbrk(cp, " \n"))) 1019 cpos = cp2 - cbuf; 1020 c = cbuf[cpos]; 1021 cbuf[cpos] = '\0'; 1022 upper(cp); 1023 p = lookup(sitetab, cp); 1024 cbuf[cpos] = c; 1025 if (p != 0) { 1026 if (p->implemented == 0) { 1027 state = CMD; 1028 nack(p->name); 1029 longjmp(errcatch,0); 1030 /* NOTREACHED */ 1031 } 1032 state = p->state; 1033 yylval.s = p->name; 1034 return (p->token); 1035 } 1036 state = CMD; 1037 break; 1038 1039 case OSTR: 1040 if (cbuf[cpos] == '\n') { 1041 state = CMD; 1042 return (CRLF); 1043 } 1044 /* FALLTHROUGH */ 1045 1046 case STR1: 1047 case ZSTR1: 1048 dostr1: 1049 if (cbuf[cpos] == ' ') { 1050 cpos++; 1051 state = state == OSTR ? STR2 : ++state; 1052 return (SP); 1053 } 1054 break; 1055 1056 case ZSTR2: 1057 if (cbuf[cpos] == '\n') { 1058 state = CMD; 1059 return (CRLF); 1060 } 1061 /* FALLTHROUGH */ 1062 1063 case STR2: 1064 cp = &cbuf[cpos]; 1065 n = strlen(cp); 1066 cpos += n - 1; 1067 /* 1068 * Make sure the string is nonempty and \n terminated. 1069 */ 1070 if (n > 1 && cbuf[cpos] == '\n') { 1071 cbuf[cpos] = '\0'; 1072 yylval.s = strdup(cp); 1073 if (yylval.s == NULL) 1074 fatal("Ran out of memory."); 1075 cbuf[cpos] = '\n'; 1076 state = ARGS; 1077 return (STRING); 1078 } 1079 break; 1080 1081 case NSTR: 1082 if (cbuf[cpos] == ' ') { 1083 cpos++; 1084 return (SP); 1085 } 1086 if (isdigit(cbuf[cpos])) { 1087 cp = &cbuf[cpos]; 1088 while (isdigit(cbuf[++cpos])) 1089 ; 1090 c = cbuf[cpos]; 1091 cbuf[cpos] = '\0'; 1092 yylval.i = atoi(cp); 1093 cbuf[cpos] = c; 1094 state = STR1; 1095 return (NUMBER); 1096 } 1097 state = STR1; 1098 goto dostr1; 1099 1100 case ARGS: 1101 if (isdigit(cbuf[cpos])) { 1102 cp = &cbuf[cpos]; 1103 while (isdigit(cbuf[++cpos])) 1104 ; 1105 c = cbuf[cpos]; 1106 cbuf[cpos] = '\0'; 1107 yylval.i = atoi(cp); 1108 cbuf[cpos] = c; 1109 return (NUMBER); 1110 } 1111 switch (cbuf[cpos++]) { 1112 1113 case '\n': 1114 state = CMD; 1115 return (CRLF); 1116 1117 case ' ': 1118 return (SP); 1119 1120 case ',': 1121 return (COMMA); 1122 1123 case 'A': 1124 case 'a': 1125 return (A); 1126 1127 case 'B': 1128 case 'b': 1129 return (B); 1130 1131 case 'C': 1132 case 'c': 1133 return (C); 1134 1135 case 'E': 1136 case 'e': 1137 return (E); 1138 1139 case 'F': 1140 case 'f': 1141 return (F); 1142 1143 case 'I': 1144 case 'i': 1145 return (I); 1146 1147 case 'L': 1148 case 'l': 1149 return (L); 1150 1151 case 'N': 1152 case 'n': 1153 return (N); 1154 1155 case 'P': 1156 case 'p': 1157 return (P); 1158 1159 case 'R': 1160 case 'r': 1161 return (R); 1162 1163 case 'S': 1164 case 's': 1165 return (S); 1166 1167 case 'T': 1168 case 't': 1169 return (T); 1170 1171 } 1172 break; 1173 1174 default: 1175 fatal("Unknown state in scanner."); 1176 } 1177 yyerror((char *) 0); 1178 state = CMD; 1179 longjmp(errcatch,0); 1180 } 1181} 1182 1183void 1184upper(s) 1185 char *s; 1186{ 1187 while (*s != '\0') { 1188 if (islower(*s)) 1189 *s = toupper(*s); 1190 s++; 1191 } 1192} 1193 1194static void 1195help(ctab, s) 1196 struct tab *ctab; 1197 char *s; 1198{ 1199 struct tab *c; 1200 int width, NCMDS; 1201 char *type; 1202 1203 if (ctab == sitetab) 1204 type = "SITE "; 1205 else 1206 type = ""; 1207 width = 0, NCMDS = 0; 1208 for (c = ctab; c->name != NULL; c++) { 1209 int len = strlen(c->name); 1210 1211 if (len > width) 1212 width = len; 1213 NCMDS++; 1214 } 1215 width = (width + 8) &~ 7; 1216 if (s == 0) { 1217 int i, j, w; 1218 int columns, lines; 1219 1220 lreply(214, "The following %scommands are recognized %s.", 1221 type, "(* =>'s unimplemented)"); 1222 columns = 76 / width; 1223 if (columns == 0) 1224 columns = 1; 1225 lines = (NCMDS + columns - 1) / columns; 1226 for (i = 0; i < lines; i++) { 1227 printf(" "); 1228 for (j = 0; j < columns; j++) { 1229 c = ctab + j * lines + i; 1230 printf("%s%c", c->name, 1231 c->implemented ? ' ' : '*'); 1232 if (c + lines >= &ctab[NCMDS]) 1233 break; 1234 w = strlen(c->name) + 1; 1235 while (w < width) { 1236 putchar(' '); 1237 w++; 1238 } 1239 } 1240 printf("\r\n"); 1241 } 1242 (void) fflush(stdout); 1243 reply(214, "Direct comments to ftp-bugs@%s.", hostname); 1244 return; 1245 } 1246 upper(s); 1247 c = lookup(ctab, s); 1248 if (c == (struct tab *)0) { 1249 reply(502, "Unknown command %s.", s); 1250 return; 1251 } 1252 if (c->implemented) 1253 reply(214, "Syntax: %s%s %s", type, c->name, c->help); 1254 else 1255 reply(214, "%s%-*s\t%s; unimplemented.", type, width, 1256 c->name, c->help); 1257} 1258 1259static void 1260sizecmd(filename) 1261 char *filename; 1262{ 1263 switch (type) { 1264 case TYPE_L: 1265 case TYPE_I: { 1266 struct stat stbuf; 1267 if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) 1268 reply(550, "%s: not a plain file.", filename); 1269 else 1270 reply(213, "%qu", stbuf.st_size); 1271 break; } 1272 case TYPE_A: { 1273 FILE *fin; 1274 int c; 1275 off_t count; 1276 struct stat stbuf; 1277 fin = fopen(filename, "r"); 1278 if (fin == NULL) { 1279 perror_reply(550, filename); 1280 return; 1281 } 1282 if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) { 1283 reply(550, "%s: not a plain file.", filename); 1284 (void) fclose(fin); 1285 return; 1286 } 1287 1288 count = 0; 1289 while((c=getc(fin)) != EOF) { 1290 if (c == '\n') /* will get expanded to \r\n */ 1291 count++; 1292 count++; 1293 } 1294 (void) fclose(fin); 1295 1296 reply(213, "%qd", count); 1297 break; } 1298 default: 1299 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]); 1300 } 1301} 1302