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