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