ftpcmd.y revision 29964
1/* ftpcmd.y: yacc parser for the FTP daemon. 2 3%%% portions-copyright-cmetz-96 4Portions of this software are Copyright 1996-1997 by Craig Metz, All Rights 5Reserved. The Inner Net License Version 2 applies to these portions of 6the software. 7You should have received a copy of the license with this software. If 8you didn't get a copy, you may request one from <license@inner.net>. 9 10 History: 11 12 Modified by cmetz for OPIE 2.3. Moved LS_COMMAND here. 13 Modified by cmetz for OPIE 2.2. Fixed a *lot* of warnings. 14 Use FUNCTION declaration et al. Removed useless strings. 15 Changed some char []s to char *s. Deleted comment address. 16 Changed tmpline references to be more pure-pointer 17 references. Changed tmpline declaration back to char []. 18 Modified at NRL for OPIE 2.1. Minor changes for autoconf. 19 Modified at NRL for OPIE 2.01. Added forward declaration for sitetab[] 20 -- fixes problems experienced by bison users. Merged in new 21 PORT attack fixes from Hobbit. 22 Modified at NRL for OPIE 2.0. 23 Originally from BSD. 24*/ 25/* 26 * Copyright (c) 1985, 1988 Regents of the University of California. 27 * All rights reserved. 28 * 29 * Redistribution and use in source and binary forms, with or without 30 * modification, are permitted provided that the following conditions 31 * are met: 32 * 1. Redistributions of source code must retain the above copyright 33 * notice, this list of conditions and the following disclaimer. 34 * 2. Redistributions in binary form must reproduce the above copyright 35 * notice, this list of conditions and the following disclaimer in the 36 * documentation and/or other materials provided with the distribution. 37 * 3. All advertising materials mentioning features or use of this software 38 * must display the following acknowledgement: 39 * This product includes software developed by the University of 40 * California, Berkeley and its contributors. 41 * 4. Neither the name of the University nor the names of its contributors 42 * may be used to endorse or promote products derived from this software 43 * without specific prior written permission. 44 * 45 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 46 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 47 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 48 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 49 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 50 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 51 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 52 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 53 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 54 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 55 * SUCH DAMAGE. 56 * 57 * @(#)ftpcmd.y 5.24 (Berkeley) 2/25/91 58 */ 59 60/* 61 * Grammar for FTP commands. 62 * See RFC 959. 63 */ 64 65%{ 66#include "opie_cfg.h" 67 68#include <sys/param.h> 69#include <sys/types.h> 70#include <sys/socket.h> 71#include <sys/stat.h> 72#include <netinet/in.h> 73#include <arpa/ftp.h> 74#include <signal.h> 75#include <setjmp.h> 76#include <syslog.h> 77#if TM_IN_SYS_TIME 78#include <sys/time.h> 79#else /* TM_IN_SYS_TIME */ 80#include <time.h> 81#endif /* TM_IN_SYS_TIME */ 82#include <pwd.h> 83#include <unistd.h> 84#include <stdio.h> 85#include <ctype.h> 86#include <stdlib.h> 87#include <string.h> 88 89#include "opie.h" 90 91#if HAVE_LS_G_FLAG 92#define LS_COMMAND "/bin/ls -lgA" 93#else /* HAVE_LS_G_FLAG */ 94#define LS_COMMAND "/bin/ls -lA" 95#endif /* HAVE_LS_G_FLAG */ 96 97extern struct sockaddr_in data_dest; 98extern struct sockaddr_in his_addr; 99extern int logged_in; 100extern struct passwd *pw; 101extern int guest; 102extern int type; 103extern int form; 104extern int debug; 105extern int timeout; 106extern int maxtimeout; 107extern int pdata; 108extern char *remotehost; 109extern char *proctitle; 110extern char *globerr; 111extern int usedefault; 112extern int transflag; 113extern char tmpline[]; 114char **ftpglob(); 115 116VOIDRET dologout __P((int)); 117VOIDRET upper __P((char *)); 118VOIDRET nack __P((char *)); 119VOIDRET opiefatal __P((char *)); 120 121VOIDRET pass __P((char *)); 122int user __P((char *)); 123VOIDRET passive __P((void)); 124VOIDRET retrieve __P((char *, char *)); 125VOIDRET store __P((char *, char *, int)); 126VOIDRET send_file_list __P((char *)); 127VOIDRET statfilecmd __P((char *)); 128VOIDRET statcmd __P((void)); 129VOIDRET delete __P((char *)); 130VOIDRET renamecmd __P((char *, char *)); 131VOIDRET cwd __P((char *)); 132VOIDRET makedir __P((char *)); 133VOIDRET removedir __P((char *)); 134VOIDRET pwd __P((void)); 135 136VOIDRET sizecmd __P((char *)); 137 138off_t restart_point; 139 140static int cmd_type; 141static int cmd_form; 142static int cmd_bytesz; 143static unsigned short cliport = 0; 144char cbuf[512]; 145char *fromname; 146 147struct tab { 148 char *name; 149 short token; 150 short state; 151 short implemented; /* 1 if command is implemented */ 152 char *help; 153}; 154 155VOIDRET help __P((struct tab *, char *)); 156 157struct tab cmdtab[], sitetab[]; 158 159%} 160 161%token 162 A B C E F I 163 L N P R S T 164 165 SP CRLF COMMA STRING NUMBER 166 167 USER PASS ACCT REIN QUIT PORT 168 PASV TYPE STRU MODE RETR STOR 169 APPE MLFL MAIL MSND MSOM MSAM 170 MRSQ MRCP ALLO REST RNFR RNTO 171 ABOR DELE CWD LIST NLST SITE 172 STAT HELP NOOP MKD RMD PWD 173 CDUP STOU SMNT SYST SIZE MDTM 174 175 UMASK IDLE CHMOD 176 177 LEXERR 178 179%start cmd_list 180 181%% 182 183cmd_list: /* empty */ 184 | cmd_list cmd 185 = { 186 fromname = (char *) 0; 187 restart_point = (off_t) 0; 188 } 189 | cmd_list rcmd 190 ; 191 192cmd: USER SP username CRLF 193 = { 194 user((char *) $3); 195 free((char *) $3); 196 } 197 | PASS SP password CRLF 198 = { 199 pass((char *) $3); 200 free((char *) $3); 201 } 202 | PORT check_login SP host_port CRLF 203 = { 204 usedefault = 0; 205 if (pdata >= 0) { 206 (void) close(pdata); 207 pdata = -1; 208 } 209/* H* port fix, part B: admonish the twit. 210 Also require login before PORT works */ 211 if ($2) { 212 if ((cliport > 1023) && (data_dest.sin_addr.s_addr > 0)) { 213 reply(200, "PORT command successful."); 214 } else { 215 syslog (LOG_WARNING, "refused %s from %s", 216 cbuf, remotehost); 217 reply(500, "You've GOT to be joking."); 218 } 219 } 220 } 221/* | PASV CRLF 222 = { 223 passive(); 224 } */ 225 | PASV check_login CRLF 226 = { 227/* Require login for PASV, too. This actually fixes a bug -- telnet to an 228 unfixed wu-ftpd and type PASV first off, and it crashes! */ 229 if ($2) { 230 passive(); 231 } 232 } 233 | TYPE SP type_code CRLF 234 = { 235 switch (cmd_type) { 236 237 case TYPE_A: 238 if (cmd_form == FORM_N) { 239 reply(200, "Type set to A."); 240 type = cmd_type; 241 form = cmd_form; 242 } else 243 reply(504, "Form must be N."); 244 break; 245 246 case TYPE_E: 247 reply(504, "Type E not implemented."); 248 break; 249 250 case TYPE_I: 251 reply(200, "Type set to I."); 252 type = cmd_type; 253 break; 254 255 case TYPE_L: 256#if NBBY == 8 257 if (cmd_bytesz == 8) { 258 reply(200, 259 "Type set to L (byte size 8)."); 260 type = cmd_type; 261 } else 262 reply(504, "Byte size must be 8."); 263#else /* NBBY == 8 */ 264 UNIMPLEMENTED for NBBY != 8 265#endif /* NBBY == 8 */ 266 } 267 } 268 | STRU SP struct_code CRLF 269 = { 270 switch ($3) { 271 272 case STRU_F: 273 reply(200, "STRU F ok."); 274 break; 275 276 default: 277 reply(504, "Unimplemented STRU type."); 278 } 279 } 280 | MODE SP mode_code CRLF 281 = { 282 switch ($3) { 283 284 case MODE_S: 285 reply(200, "MODE S ok."); 286 break; 287 288 default: 289 reply(502, "Unimplemented MODE type."); 290 } 291 } 292 | ALLO SP NUMBER CRLF 293 = { 294 reply(202, "ALLO command ignored."); 295 } 296 | ALLO SP NUMBER SP R SP NUMBER CRLF 297 = { 298 reply(202, "ALLO command ignored."); 299 } 300 | RETR check_login SP pathname CRLF 301 = { 302 if ($2 && $4) 303 retrieve((char *) 0, (char *) $4); 304 if ($4) 305 free((char *) $4); 306 } 307 | STOR check_login SP pathname CRLF 308 = { 309 if ($2 && $4) 310 store((char *) $4, "w", 0); 311 if ($4) 312 free((char *) $4); 313 } 314 | APPE check_login SP pathname CRLF 315 = { 316 if ($2 && $4) 317 store((char *) $4, "a", 0); 318 if ($4) 319 free((char *) $4); 320 } 321 | NLST check_login CRLF 322 = { 323 if ($2) 324 send_file_list("."); 325 } 326 | NLST check_login SP STRING CRLF 327 = { 328 if ($2 && $4) 329 send_file_list((char *) $4); 330 if ($4) 331 free((char *) $4); 332 } 333 | LIST check_login CRLF 334 = { 335 if ($2) 336 retrieve(LS_COMMAND, ""); 337 } 338 | LIST check_login SP pathname CRLF 339 = { 340 if ($2 && $4) 341 { 342 char buffer[sizeof(LS_COMMAND)+3]; 343 strcpy(buffer, LS_COMMAND); 344 strcat(buffer, " %s"); 345 retrieve(buffer, (char *) $4); 346 } 347 if ($4) 348 free((char *) $4); 349 } 350 | STAT check_login SP pathname CRLF 351 = { 352 if ($2 && $4) 353 statfilecmd((char *) $4); 354 if ($4) 355 free((char *) $4); 356 } 357 | STAT CRLF 358 = { 359 statcmd(); 360 } 361 | DELE check_login SP pathname CRLF 362 = { 363 if ($2 && $4) 364 delete((char *) $4); 365 if ($4) 366 free((char *) $4); 367 } 368 | RNTO SP pathname CRLF 369 = { 370 if (fromname) { 371 renamecmd(fromname, (char *) $3); 372 free(fromname); 373 fromname = (char *) 0; 374 } else { 375 reply(503, "Bad sequence of commands."); 376 } 377 free((char *) $3); 378 } 379 | ABOR CRLF 380 = { 381 reply(225, "ABOR command successful."); 382 } 383 | CWD check_login CRLF 384 = { 385 if ($2) 386 cwd(pw->pw_dir); 387 } 388 | CWD check_login SP pathname CRLF 389 = { 390 if ($2 && $4) 391 cwd((char *) $4); 392 if ($4) 393 free((char *) $4); 394 } 395 | HELP CRLF 396 = { 397 help(cmdtab, (char *) 0); 398 } 399 | HELP SP STRING CRLF 400 = { 401 register char *cp = (char *)$3; 402 403 if (strncasecmp(cp, "SITE", 4) == 0) { 404 cp = (char *)$3 + 4; 405 if (*cp == ' ') 406 cp++; 407 if (*cp) 408 help(sitetab, cp); 409 else 410 help(sitetab, (char *) 0); 411 } else 412 help(cmdtab, (char *) $3); 413 } 414 | NOOP CRLF 415 = { 416 reply(200, "NOOP command successful."); 417 } 418 | MKD check_login SP pathname CRLF 419 = { 420 if ($2 && $4) 421 makedir((char *) $4); 422 if ($4) 423 free((char *) $4); 424 } 425 | RMD check_login SP pathname CRLF 426 = { 427 if ($2 && $4) 428 removedir((char *) $4); 429 if ($4) 430 free((char *) $4); 431 } 432 | PWD check_login CRLF 433 = { 434 if ($2) 435 pwd(); 436 } 437 | CDUP check_login CRLF 438 = { 439 if ($2) 440 cwd(".."); 441 } 442 | SITE SP HELP CRLF 443 = { 444 help(sitetab, (char *) 0); 445 } 446 | SITE SP HELP SP STRING CRLF 447 = { 448 help(sitetab, (char *) $5); 449 } 450 | SITE SP UMASK check_login CRLF 451 = { 452 int oldmask; 453 454 if ($4) { 455 oldmask = umask(0); 456 (void) umask(oldmask); 457 reply(200, "Current UMASK is %03o", oldmask); 458 } 459 } 460 | SITE SP UMASK check_login SP octal_number CRLF 461 = { 462 int oldmask; 463 464 if ($4) { 465 if (($6 == -1) || ($6 > 0777)) { 466 reply(501, "Bad UMASK value"); 467 } else { 468 oldmask = umask($6); 469 reply(200, 470 "UMASK set to %03o (was %03o)", 471 $6, oldmask); 472 } 473 } 474 } 475 | SITE SP CHMOD check_login SP octal_number SP pathname CRLF 476 = { 477 if ($4 && $8) { 478 if ($6 > 0777) 479 reply(501, 480 "CHMOD: Mode value must be between 0 and 0777"); 481 else if (chmod((char *) $8, $6) < 0) 482 perror_reply(550, (char *) $8); 483 else 484 reply(200, "CHMOD command successful."); 485 } 486 if ($8) 487 free((char *) $8); 488 } 489 | SITE SP IDLE CRLF 490 = { 491 reply(200, 492 "Current IDLE time limit is %d seconds; max %d", 493 timeout, maxtimeout); 494 } 495 | SITE SP IDLE SP NUMBER CRLF 496 = { 497 if ($5 < 30 || $5 > maxtimeout) { 498 reply(501, 499 "Maximum IDLE time must be between 30 and %d seconds", 500 maxtimeout); 501 } else { 502 timeout = $5; 503 (void) alarm((unsigned) timeout); 504 reply(200, 505 "Maximum IDLE time set to %d seconds", 506 timeout); 507 } 508 } 509 | STOU check_login SP pathname CRLF 510 = { 511 if ($2 && $4) 512 store((char *) $4, "w", 1); 513 if ($4) 514 free((char *) $4); 515 } 516 | SYST CRLF 517 = { 518#ifdef unix 519#ifdef BSD 520 reply(215, "UNIX Type: L%d Version: BSD-%d", 521 NBBY, BSD); 522#else /* BSD */ 523 reply(215, "UNIX Type: L%d", NBBY); 524#endif /* BSD */ 525#else /* unix */ 526 reply(215, "UNKNOWN Type: L%d", NBBY); 527#endif /* unix */ 528 } 529 530 /* 531 * SIZE is not in RFC959, but Postel has blessed it and 532 * it will be in the updated RFC. 533 * 534 * Return size of file in a format suitable for 535 * using with RESTART (we just count bytes). 536 */ 537 | SIZE check_login SP pathname CRLF 538 = { 539 if ($2 && $4) 540 sizecmd((char *) $4); 541 if ($4) 542 free((char *) $4); 543 } 544 545 /* 546 * MDTM is not in RFC959, but Postel has blessed it and 547 * it will be in the updated RFC. 548 * 549 * Return modification time of file as an ISO 3307 550 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx 551 * where xxx is the fractional second (of any precision, 552 * not necessarily 3 digits) 553 */ 554 | MDTM check_login SP pathname CRLF 555 = { 556 if ($2 && $4) { 557 struct stat stbuf; 558 if (stat((char *) $4, &stbuf) < 0) 559 perror_reply(550, (char *) $4); 560 else if ((stbuf.st_mode&S_IFMT) != S_IFREG) { 561 reply(550, "%s: not a plain file.", 562 (char *) $4); 563 } else { 564 register struct tm *t; 565 struct tm *gmtime(); 566 t = gmtime(&stbuf.st_mtime); 567 reply(213, 568 "19%02d%02d%02d%02d%02d%02d", 569 t->tm_year, t->tm_mon+1, t->tm_mday, 570 t->tm_hour, t->tm_min, t->tm_sec); 571 } 572 } 573 if ($4) 574 free((char *) $4); 575 } 576 | QUIT CRLF 577 = { 578 reply(221, "Goodbye."); 579 dologout(0); 580 } 581 | error CRLF 582 = { 583 yyerrok; 584 } 585 ; 586rcmd: RNFR check_login SP pathname CRLF 587 = { 588 char *renamefrom(); 589 590 restart_point = (off_t) 0; 591 if ($2 && $4) { 592 fromname = renamefrom((char *) $4); 593 if (fromname == (char *) 0 && $4) { 594 free((char *) $4); 595 } 596 } 597 } 598 | REST SP byte_size CRLF 599 = { 600 long atol(); 601 602 fromname = (char *) 0; 603 restart_point = $3; 604 reply(350, "Restarting at %ld. %s", restart_point, 605 "Send STORE or RETRIEVE to initiate transfer."); 606 } 607 ; 608 609username: STRING 610 ; 611 612password: /* empty */ 613 = { 614 *(char **)&($$) = (char *)calloc(1, sizeof(char)); 615 } 616 | STRING 617 ; 618 619byte_size: NUMBER 620 ; 621 622host_port: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA 623 NUMBER COMMA NUMBER 624 = { 625 register char *a, *p; 626 627 a = (char *)&data_dest.sin_addr; 628 a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7; 629 630/* H* port fix, part A-1: Check the args against the client addr */ 631 p = (char *)&his_addr.sin_addr; 632 if (memcmp (a, p, sizeof (data_dest.sin_addr))) 633 memset (a, 0, sizeof (data_dest.sin_addr)); /* XXX */ 634 635 p = (char *)&data_dest.sin_port; 636 637/* H* port fix, part A-2: only allow client ports in "user space" */ 638 p[0] = 0; p[1] = 0; 639 cliport = ($9 << 8) + $11; 640 if (cliport > 1023) { 641 p[0] = $9; p[1] = $11; 642 } 643 644 p[0] = $9; p[1] = $11; 645 data_dest.sin_family = AF_INET; 646 } 647 ; 648 649form_code: N 650 = { 651 $$ = FORM_N; 652 } 653 | T 654 = { 655 $$ = FORM_T; 656 } 657 | C 658 = { 659 $$ = FORM_C; 660 } 661 ; 662 663type_code: A 664 = { 665 cmd_type = TYPE_A; 666 cmd_form = FORM_N; 667 } 668 | A SP form_code 669 = { 670 cmd_type = TYPE_A; 671 cmd_form = $3; 672 } 673 | E 674 = { 675 cmd_type = TYPE_E; 676 cmd_form = FORM_N; 677 } 678 | E SP form_code 679 = { 680 cmd_type = TYPE_E; 681 cmd_form = $3; 682 } 683 | I 684 = { 685 cmd_type = TYPE_I; 686 } 687 | L 688 = { 689 cmd_type = TYPE_L; 690 cmd_bytesz = NBBY; 691 } 692 | L SP byte_size 693 = { 694 cmd_type = TYPE_L; 695 cmd_bytesz = $3; 696 } 697 /* this is for a bug in the BBN ftp */ 698 | L byte_size 699 = { 700 cmd_type = TYPE_L; 701 cmd_bytesz = $2; 702 } 703 ; 704 705struct_code: F 706 = { 707 $$ = STRU_F; 708 } 709 | R 710 = { 711 $$ = STRU_R; 712 } 713 | P 714 = { 715 $$ = STRU_P; 716 } 717 ; 718 719mode_code: S 720 = { 721 $$ = MODE_S; 722 } 723 | B 724 = { 725 $$ = MODE_B; 726 } 727 | C 728 = { 729 $$ = MODE_C; 730 } 731 ; 732 733pathname: pathstring 734 = { 735 /* 736 * Problem: this production is used for all pathname 737 * processing, but only gives a 550 error reply. 738 * This is a valid reply in some cases but not in others. 739 */ 740 if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) { 741 *(char **)&($$) = *ftpglob((char *) $1); 742 if (globerr != NULL) { 743 reply(550, globerr); 744/* $$ = NULL; */ 745 $$ = 0; 746 } 747 free((char *) $1); 748 } else 749 $$ = $1; 750 } 751 ; 752 753pathstring: STRING 754 ; 755 756octal_number: NUMBER 757 = { 758 register int ret, dec, multby, digit; 759 760 /* 761 * Convert a number that was read as decimal number 762 * to what it would be if it had been read as octal. 763 */ 764 dec = $1; 765 multby = 1; 766 ret = 0; 767 while (dec) { 768 digit = dec%10; 769 if (digit > 7) { 770 ret = -1; 771 break; 772 } 773 ret += digit * multby; 774 multby *= 8; 775 dec /= 10; 776 } 777 $$ = ret; 778 } 779 ; 780 781check_login: /* empty */ 782 = { 783 if (logged_in) 784 $$ = 1; 785 else { 786 reply(530, "Please login with USER and PASS."); 787 $$ = 0; 788 } 789 } 790 ; 791 792%% 793 794extern jmp_buf errcatch; 795 796#define CMD 0 /* beginning of command */ 797#define ARGS 1 /* expect miscellaneous arguments */ 798#define STR1 2 /* expect SP followed by STRING */ 799#define STR2 3 /* expect STRING */ 800#define OSTR 4 /* optional SP then STRING */ 801#define ZSTR1 5 /* SP then optional STRING */ 802#define ZSTR2 6 /* optional STRING after SP */ 803#define SITECMD 7 /* SITE command */ 804#define NSTR 8 /* Number followed by a string */ 805 806struct tab cmdtab[] = { /* In order defined in RFC 765 */ 807 { "USER", USER, STR1, 1, "<sp> username" }, 808 { "PASS", PASS, ZSTR1, 1, "<sp> password" }, 809 { "ACCT", ACCT, STR1, 0, "(specify account)" }, 810 { "SMNT", SMNT, ARGS, 0, "(structure mount)" }, 811 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" }, 812 { "QUIT", QUIT, ARGS, 1, "(terminate service)", }, 813 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" }, 814 { "PASV", PASV, ARGS, 1, "(set server in passive mode)" }, 815 { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" }, 816 { "STRU", STRU, ARGS, 1, "(specify file structure)" }, 817 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" }, 818 { "RETR", RETR, STR1, 1, "<sp> file-name" }, 819 { "STOR", STOR, STR1, 1, "<sp> file-name" }, 820 { "APPE", APPE, STR1, 1, "<sp> file-name" }, 821 { "MLFL", MLFL, OSTR, 0, "(mail file)" }, 822 { "MAIL", MAIL, OSTR, 0, "(mail to user)" }, 823 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" }, 824 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" }, 825 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" }, 826 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" }, 827 { "MRCP", MRCP, STR1, 0, "(mail recipient)" }, 828 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" }, 829 { "REST", REST, ARGS, 1, "(restart command)" }, 830 { "RNFR", RNFR, STR1, 1, "<sp> file-name" }, 831 { "RNTO", RNTO, STR1, 1, "<sp> file-name" }, 832 { "ABOR", ABOR, ARGS, 1, "(abort operation)" }, 833 { "DELE", DELE, STR1, 1, "<sp> file-name" }, 834 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" }, 835 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" }, 836 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" }, 837 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" }, 838 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" }, 839 { "SYST", SYST, ARGS, 1, "(get type of operating system)" }, 840 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" }, 841 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" }, 842 { "NOOP", NOOP, ARGS, 1, "" }, 843 { "MKD", MKD, STR1, 1, "<sp> path-name" }, 844 { "XMKD", MKD, STR1, 1, "<sp> path-name" }, 845 { "RMD", RMD, STR1, 1, "<sp> path-name" }, 846 { "XRMD", RMD, STR1, 1, "<sp> path-name" }, 847 { "PWD", PWD, ARGS, 1, "(return current directory)" }, 848 { "XPWD", PWD, ARGS, 1, "(return current directory)" }, 849 { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" }, 850 { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" }, 851 { "STOU", STOU, STR1, 1, "<sp> file-name" }, 852 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" }, 853 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" }, 854 { NULL, 0, 0, 0, 0 } 855}; 856 857struct tab sitetab[] = { 858 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" }, 859 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" }, 860 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" }, 861 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" }, 862 { NULL, 0, 0, 0, 0 } 863}; 864 865struct tab *lookup FUNCTION((p, cmd), register struct tab *p AND char *cmd) 866{ 867 868 for (; p->name != NULL; p++) 869 if (strcmp(cmd, p->name) == 0) 870 return (p); 871 return (0); 872} 873 874#include <arpa/telnet.h> 875 876/* 877 * getline - a hacked up version of fgets to ignore TELNET escape codes. 878 */ 879char *getline FUNCTION((s, n, iop), char *s AND int n AND FILE *iop) 880{ 881 register c; 882 register char *cs; 883 884 cs = s; 885/* tmpline may contain saved command from urgent mode interruption */ 886 for (c = 0; *(tmpline + c) && --n > 0; ++c) { 887 *cs++ = *(tmpline + c); 888 if (*(tmpline + c) == '\n') { 889 *cs++ = '\0'; 890 if (debug) 891 syslog(LOG_DEBUG, "command: %s", s); 892 *tmpline = '\0'; 893 return(s); 894 } 895 if (c == 0) 896 *tmpline = '\0'; 897 } 898 while ((c = getc(iop)) != EOF) { 899 c &= 0377; 900 if (c == IAC) { 901 if ((c = getc(iop)) != EOF) { 902 c &= 0377; 903 switch (c) { 904 case WILL: 905 case WONT: 906 c = getc(iop); 907 printf("%c%c%c", IAC, DONT, 0377&c); 908 (void) fflush(stdout); 909 continue; 910 case DO: 911 case DONT: 912 c = getc(iop); 913 printf("%c%c%c", IAC, WONT, 0377&c); 914 (void) fflush(stdout); 915 continue; 916 case IAC: 917 break; 918 default: 919 continue; /* ignore command */ 920 } 921 } 922 } 923 *cs++ = c; 924 if (--n <= 0 || c == '\n') 925 break; 926 } 927 if (c == EOF && cs == s) 928 return (NULL); 929 *cs++ = '\0'; 930 if (debug) 931 syslog(LOG_DEBUG, "command: %s", s); 932 return (s); 933} 934 935static VOIDRET toolong FUNCTION((input), int input) 936{ 937 time_t now; 938 939 reply(421, "Timeout (%d seconds): closing control connection.", timeout); 940 (void) time(&now); 941 syslog(LOG_INFO, "User %s timed out after %d seconds at %s", 942 (pw ? pw -> pw_name : "unknown"), timeout, ctime(&now)); 943 dologout(1); 944} 945 946int yylex FUNCTION_NOARGS 947{ 948 static int cpos, state; 949 register char *cp, *cp2; 950 register struct tab *p; 951 int n; 952 char c, *copy(); 953 954 for (;;) { 955 switch (state) { 956 957 case CMD: 958 (void) signal(SIGALRM, toolong); 959 (void) alarm((unsigned) timeout); 960 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) { 961 reply(221, "You could at least say goodbye."); 962 dologout(0); 963 } 964 (void) alarm(0); 965#ifdef SETPROCTITLE 966 if (strncasecmp(cbuf, "PASS", 4) != NULL) 967 setproctitle("%s: %s", proctitle, cbuf); 968#endif /* SETPROCTITLE */ 969 if ((cp = strchr(cbuf, '\r'))) { 970 *cp++ = '\n'; 971 *cp = '\0'; 972 } 973 if ((cp = strpbrk(cbuf, " \n"))) 974 cpos = cp - cbuf; 975 if (cpos == 0) 976 cpos = 4; 977 c = cbuf[cpos]; 978 cbuf[cpos] = '\0'; 979 upper(cbuf); 980 p = lookup(cmdtab, cbuf); 981 cbuf[cpos] = c; 982 if (p != 0) { 983 if (p->implemented == 0) { 984 nack(p->name); 985 longjmp(errcatch,0); 986 /* NOTREACHED */ 987 } 988 state = p->state; 989 *(char **)&yylval = p->name; 990 return (p->token); 991 } 992 break; 993 994 case SITECMD: 995 if (cbuf[cpos] == ' ') { 996 cpos++; 997 return (SP); 998 } 999 cp = &cbuf[cpos]; 1000 if ((cp2 = strpbrk(cp, " \n"))) 1001 cpos = cp2 - cbuf; 1002 c = cbuf[cpos]; 1003 cbuf[cpos] = '\0'; 1004 upper(cp); 1005 p = lookup(sitetab, cp); 1006 cbuf[cpos] = c; 1007 if (p != 0) { 1008 if (p->implemented == 0) { 1009 state = CMD; 1010 nack(p->name); 1011 longjmp(errcatch,0); 1012 /* NOTREACHED */ 1013 } 1014 state = p->state; 1015 *(char **)&yylval = p->name; 1016 return (p->token); 1017 } 1018 state = CMD; 1019 break; 1020 1021 case OSTR: 1022 if (cbuf[cpos] == '\n') { 1023 state = CMD; 1024 return (CRLF); 1025 } 1026 /* FALLTHROUGH */ 1027 1028 case STR1: 1029 case ZSTR1: 1030 dostr1: 1031 if (cbuf[cpos] == ' ') { 1032 cpos++; 1033 state = state == OSTR ? STR2 : ++state; 1034 return (SP); 1035 } 1036 break; 1037 1038 case ZSTR2: 1039 if (cbuf[cpos] == '\n') { 1040 state = CMD; 1041 return (CRLF); 1042 } 1043 /* FALLTHROUGH */ 1044 1045 case STR2: 1046 cp = &cbuf[cpos]; 1047 n = strlen(cp); 1048 cpos += n - 1; 1049 /* 1050 * Make sure the string is nonempty and \n terminated. 1051 */ 1052 if (n > 1 && cbuf[cpos] == '\n') { 1053 cbuf[cpos] = '\0'; 1054 *(char **)&yylval = copy(cp); 1055 cbuf[cpos] = '\n'; 1056 state = ARGS; 1057 return (STRING); 1058 } 1059 break; 1060 1061 case NSTR: 1062 if (cbuf[cpos] == ' ') { 1063 cpos++; 1064 return (SP); 1065 } 1066 if (isdigit(cbuf[cpos])) { 1067 cp = &cbuf[cpos]; 1068 while (isdigit(cbuf[++cpos])) 1069 ; 1070 c = cbuf[cpos]; 1071 cbuf[cpos] = '\0'; 1072 yylval = atoi(cp); 1073 cbuf[cpos] = c; 1074 state = STR1; 1075 return (NUMBER); 1076 } 1077 state = STR1; 1078 goto dostr1; 1079 1080 case ARGS: 1081 if (isdigit(cbuf[cpos])) { 1082 cp = &cbuf[cpos]; 1083 while (isdigit(cbuf[++cpos])) 1084 ; 1085 c = cbuf[cpos]; 1086 cbuf[cpos] = '\0'; 1087 yylval = atoi(cp); 1088 cbuf[cpos] = c; 1089 return (NUMBER); 1090 } 1091 switch (cbuf[cpos++]) { 1092 1093 case '\n': 1094 state = CMD; 1095 return (CRLF); 1096 1097 case ' ': 1098 return (SP); 1099 1100 case ',': 1101 return (COMMA); 1102 1103 case 'A': 1104 case 'a': 1105 return (A); 1106 1107 case 'B': 1108 case 'b': 1109 return (B); 1110 1111 case 'C': 1112 case 'c': 1113 return (C); 1114 1115 case 'E': 1116 case 'e': 1117 return (E); 1118 1119 case 'F': 1120 case 'f': 1121 return (F); 1122 1123 case 'I': 1124 case 'i': 1125 return (I); 1126 1127 case 'L': 1128 case 'l': 1129 return (L); 1130 1131 case 'N': 1132 case 'n': 1133 return (N); 1134 1135 case 'P': 1136 case 'p': 1137 return (P); 1138 1139 case 'R': 1140 case 'r': 1141 return (R); 1142 1143 case 'S': 1144 case 's': 1145 return (S); 1146 1147 case 'T': 1148 case 't': 1149 return (T); 1150 1151 } 1152 break; 1153 1154 default: 1155 opiefatal("Unknown state in scanner."); 1156 } 1157 yyerror((char *) 0); 1158 state = CMD; 1159 longjmp(errcatch,0); 1160 } 1161} 1162 1163VOIDRET upper FUNCTION((s), char *s) 1164{ 1165 while (*s != '\0') { 1166 if (islower(*s)) 1167 *s = toupper(*s); 1168 s++; 1169 } 1170} 1171 1172char *copy FUNCTION((s), char *s) 1173{ 1174 char *p; 1175 1176 p = malloc((unsigned) strlen(s) + 1); 1177 if (p == NULL) 1178 opiefatal("Ran out of memory."); 1179 (void) strcpy(p, s); 1180 return (p); 1181} 1182 1183VOIDRET help FUNCTION((ctab, s), struct tab *ctab AND char *s) 1184{ 1185 register struct tab *c; 1186 register int width, NCMDS; 1187 char *type; 1188 1189 if (ctab == sitetab) 1190 type = "SITE "; 1191 else 1192 type = ""; 1193 width = 0, NCMDS = 0; 1194 for (c = ctab; c->name != NULL; c++) { 1195 int len = strlen(c->name); 1196 1197 if (len > width) 1198 width = len; 1199 NCMDS++; 1200 } 1201 width = (width + 8) &~ 7; 1202 if (s == 0) { 1203 register int i, j, w; 1204 int columns, lines; 1205 1206 lreply(214, "The following %scommands are recognized %s.", 1207 type, "(* =>'s unimplemented)"); 1208 columns = 76 / width; 1209 if (columns == 0) 1210 columns = 1; 1211 lines = (NCMDS + columns - 1) / columns; 1212 for (i = 0; i < lines; i++) { 1213 printf(" "); 1214 for (j = 0; j < columns; j++) { 1215 c = ctab + j * lines + i; 1216 printf("%s%c", c->name, 1217 c->implemented ? ' ' : '*'); 1218 if (c + lines >= &ctab[NCMDS]) 1219 break; 1220 w = strlen(c->name) + 1; 1221 while (w < width) { 1222 putchar(' '); 1223 w++; 1224 } 1225 } 1226 printf("\r\n"); 1227 } 1228 (void) fflush(stdout); 1229 reply(214, " "); 1230 return; 1231 } 1232 upper(s); 1233 c = lookup(ctab, s); 1234 if (c == (struct tab *)0) { 1235 reply(502, "Unknown command %s.", s); 1236 return; 1237 } 1238 if (c->implemented) 1239 reply(214, "Syntax: %s%s %s", type, c->name, c->help); 1240 else 1241 reply(214, "%s%-*s\t%s; unimplemented.", type, width, 1242 c->name, c->help); 1243} 1244 1245VOIDRET sizecmd FUNCTION((filename), char *filename) 1246{ 1247 switch (type) { 1248 case TYPE_L: 1249 case TYPE_I: { 1250 struct stat stbuf; 1251 if (stat(filename, &stbuf) < 0 || 1252 (stbuf.st_mode&S_IFMT) != S_IFREG) 1253 reply(550, "%s: not a plain file.", filename); 1254 else 1255 reply(213, "%lu", stbuf.st_size); 1256 break;} 1257 case TYPE_A: { 1258 FILE *fin; 1259 register int c; 1260 register long count; 1261 struct stat stbuf; 1262 fin = fopen(filename, "r"); 1263 if (fin == NULL) { 1264 perror_reply(550, filename); 1265 return; 1266 } 1267 if (fstat(fileno(fin), &stbuf) < 0 || 1268 (stbuf.st_mode&S_IFMT) != S_IFREG) { 1269 reply(550, "%s: not a plain file.", filename); 1270 (void) fclose(fin); 1271 return; 1272 } 1273 1274 count = 0; 1275 while((c=getc(fin)) != EOF) { 1276 if (c == '\n') /* will get expanded to \r\n */ 1277 count++; 1278 count++; 1279 } 1280 (void) fclose(fin); 1281 1282 reply(213, "%ld", count); 1283 break;} 1284 default: 1285 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]); 1286 } 1287} 1288