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