1: /* 2: * Copyright (c) 1985 Regents of the University of California. 3: * All rights reserved. The Berkeley software License Agreement 4: * specifies the terms and conditions for redistribution. 5: */ 6: 7: /* 8: * Grammar for FTP commands. 9: * See RFC 765. 10: */ 11: 12: %{ 13: 14: #ifndef lint 15: static char sccsid[] = "@(#)ftpcmd.y 5.7 (Berkeley) 5/28/86"; 16: #endif 17: 18: #include <sys/types.h> 19: #include <sys/socket.h> 20: 21: #include <netinet/in.h> 22: 23: #include <arpa/ftp.h> 24: 25: #include <stdio.h> 26: #include <signal.h> 27: #include <ctype.h> 28: #include <pwd.h> 29: #include <setjmp.h> 30: #include <syslog.h> 31: 32: extern struct sockaddr_in data_dest; 33: extern int logged_in; 34: extern struct passwd *pw; 35: extern int guest; 36: extern int logging; 37: extern int type; 38: extern int form; 39: extern int debug; 40: extern int timeout; 41: extern int pdata; 42: extern char hostname[]; 43: extern char *globerr; 44: extern int usedefault; 45: extern int unique; 46: extern int transflag; 47: extern char tmpline[]; 48: char **glob(); 49: 50: static int cmd_type; 51: static int cmd_form; 52: static int cmd_bytesz; 53: char cbuf[512]; 54: 55: char *index(); 56: %} 57: 58: %token 59: A B C E F I 60: L N P R S T 61: 62: SP CRLF COMMA STRING NUMBER 63: 64: USER PASS ACCT REIN QUIT PORT 65: PASV TYPE STRU MODE RETR STOR 66: APPE MLFL MAIL MSND MSOM MSAM 67: MRSQ MRCP ALLO REST RNFR RNTO 68: ABOR DELE CWD LIST NLST SITE 69: STAT HELP NOOP XMKD XRMD XPWD 70: XCUP STOU 71: 72: LEXERR 73: 74: %start cmd_list 75: 76: %% 77: 78: cmd_list: /* empty */ 79: | cmd_list cmd 80: ; 81: 82: cmd: USER SP username CRLF 83: = { 84: extern struct passwd *getpwnam(); 85: 86: logged_in = 0; 87: if (strcmp((char *) $3, "ftp") == 0 || 88: strcmp((char *) $3, "anonymous") == 0) { 89: if ((pw = getpwnam("ftp")) != NULL) { 90: guest = 1; 91: reply(331, 92: "Guest login ok, send ident as password."); 93: } 94: else { 95: reply(530, "User %s unknown.", $3); 96: } 97: } else if (checkuser((char *) $3)) { 98: guest = 0; 99: pw = getpwnam((char *) $3); 100: if (pw == NULL) { 101: reply(530, "User %s unknown.", $3); 102: } 103: else { 104: reply(331, "Password required for %s.", $3); 105: } 106: } else { 107: reply(530, "User %s access denied.", $3); 108: } 109: free((char *) $3); 110: } 111: | PASS SP password CRLF 112: = { 113: pass((char *) $3); 114: free((char *) $3); 115: } 116: | PORT SP host_port CRLF 117: = { 118: usedefault = 0; 119: if (pdata > 0) { 120: (void) close(pdata); 121: } 122: pdata = -1; 123: reply(200, "PORT command successful."); 124: } 125: | PASV CRLF 126: = { 127: passive(); 128: } 129: | TYPE SP type_code CRLF 130: = { 131: switch (cmd_type) { 132: 133: case TYPE_A: 134: if (cmd_form == FORM_N) { 135: reply(200, "Type set to A."); 136: type = cmd_type; 137: form = cmd_form; 138: } else 139: reply(504, "Form must be N."); 140: break; 141: 142: case TYPE_E: 143: reply(504, "Type E not implemented."); 144: break; 145: 146: case TYPE_I: 147: reply(200, "Type set to I."); 148: type = cmd_type; 149: break; 150: 151: case TYPE_L: 152: if (cmd_bytesz == 8) { 153: reply(200, 154: "Type set to L (byte size 8)."); 155: type = cmd_type; 156: } else 157: reply(504, "Byte size must be 8."); 158: } 159: } 160: | STRU SP struct_code CRLF 161: = { 162: switch ($3) { 163: 164: case STRU_F: 165: reply(200, "STRU F ok."); 166: break; 167: 168: default: 169: reply(504, "Unimplemented STRU type."); 170: } 171: } 172: | MODE SP mode_code CRLF 173: = { 174: switch ($3) { 175: 176: case MODE_S: 177: reply(200, "MODE S ok."); 178: break; 179: 180: default: 181: reply(502, "Unimplemented MODE type."); 182: } 183: } 184: | ALLO SP NUMBER CRLF 185: = { 186: reply(202, "ALLO command ignored."); 187: } 188: | RETR check_login SP pathname CRLF 189: = { 190: if ($2 && $4 != NULL) 191: retrieve((char *) 0, (char *) $4); 192: if ($4 != NULL) 193: free((char *) $4); 194: } 195: | STOR check_login SP pathname CRLF 196: = { 197: if ($2 && $4 != NULL) 198: store((char *) $4, "w"); 199: if ($4 != NULL) 200: free((char *) $4); 201: } 202: | APPE check_login SP pathname CRLF 203: = { 204: if ($2 && $4 != NULL) 205: store((char *) $4, "a"); 206: if ($4 != NULL) 207: free((char *) $4); 208: } 209: | NLST check_login CRLF 210: = { 211: if ($2) 212: retrieve("/bin/ls", ""); 213: } 214: | NLST check_login SP pathname CRLF 215: = { 216: if ($2 && $4 != NULL) 217: retrieve("/bin/ls %s", (char *) $4); 218: if ($4 != NULL) 219: free((char *) $4); 220: } 221: | LIST check_login CRLF 222: = { 223: if ($2) 224: retrieve("/bin/ls -lg", ""); 225: } 226: | LIST check_login SP pathname CRLF 227: = { 228: if ($2 && $4 != NULL) 229: retrieve("/bin/ls -lg %s", (char *) $4); 230: if ($4 != NULL) 231: free((char *) $4); 232: } 233: | DELE check_login SP pathname CRLF 234: = { 235: if ($2 && $4 != NULL) 236: delete((char *) $4); 237: if ($4 != NULL) 238: free((char *) $4); 239: } 240: | ABOR CRLF 241: = { 242: reply(225, "ABOR command successful."); 243: } 244: | CWD check_login CRLF 245: = { 246: if ($2) 247: cwd(pw->pw_dir); 248: } 249: | CWD check_login SP pathname CRLF 250: = { 251: if ($2 && $4 != NULL) 252: cwd((char *) $4); 253: if ($4 != NULL) 254: free((char *) $4); 255: } 256: | rename_cmd 257: | HELP CRLF 258: = { 259: help((char *) 0); 260: } 261: | HELP SP STRING CRLF 262: = { 263: help((char *) $3); 264: } 265: | NOOP CRLF 266: = { 267: reply(200, "NOOP command successful."); 268: } 269: | XMKD check_login SP pathname CRLF 270: = { 271: if ($2 && $4 != NULL) 272: makedir((char *) $4); 273: if ($4 != NULL) 274: free((char *) $4); 275: } 276: | XRMD check_login SP pathname CRLF 277: = { 278: if ($2 && $4 != NULL) 279: removedir((char *) $4); 280: if ($4 != NULL) 281: free((char *) $4); 282: } 283: | XPWD check_login CRLF 284: = { 285: if ($2) 286: pwd(); 287: } 288: | XCUP check_login CRLF 289: = { 290: if ($2) 291: cwd(".."); 292: } 293: | STOU check_login SP pathname CRLF 294: = { 295: if ($2 && $4 != NULL) { 296: unique++; 297: store((char *) $4, "w"); 298: unique = 0; 299: } 300: if ($4 != NULL) 301: free((char *) $4); 302: } 303: | QUIT CRLF 304: = { 305: reply(221, "Goodbye."); 306: dologout(0); 307: } 308: | error CRLF 309: = { 310: yyerrok; 311: } 312: ; 313: 314: username: STRING 315: ; 316: 317: password: STRING 318: ; 319: 320: byte_size: NUMBER 321: ; 322: 323: host_port: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA 324: NUMBER COMMA NUMBER 325: = { 326: register char *a, *p; 327: 328: a = (char *)&data_dest.sin_addr; 329: a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7; 330: p = (char *)&data_dest.sin_port; 331: p[0] = $9; p[1] = $11; 332: data_dest.sin_family = AF_INET; 333: } 334: ; 335: 336: form_code: N 337: = { 338: $$ = FORM_N; 339: } 340: | T 341: = { 342: $$ = FORM_T; 343: } 344: | C 345: = { 346: $$ = FORM_C; 347: } 348: ; 349: 350: type_code: A 351: = { 352: cmd_type = TYPE_A; 353: cmd_form = FORM_N; 354: } 355: | A SP form_code 356: = { 357: cmd_type = TYPE_A; 358: cmd_form = $3; 359: } 360: | E 361: = { 362: cmd_type = TYPE_E; 363: cmd_form = FORM_N; 364: } 365: | E SP form_code 366: = { 367: cmd_type = TYPE_E; 368: cmd_form = $3; 369: } 370: | I 371: = { 372: cmd_type = TYPE_I; 373: } 374: | L 375: = { 376: cmd_type = TYPE_L; 377: cmd_bytesz = 8; 378: } 379: | L SP byte_size 380: = { 381: cmd_type = TYPE_L; 382: cmd_bytesz = $3; 383: } 384: /* this is for a bug in the BBN ftp */ 385: | L byte_size 386: = { 387: cmd_type = TYPE_L; 388: cmd_bytesz = $2; 389: } 390: ; 391: 392: struct_code: F 393: = { 394: $$ = STRU_F; 395: } 396: | R 397: = { 398: $$ = STRU_R; 399: } 400: | P 401: = { 402: $$ = STRU_P; 403: } 404: ; 405: 406: mode_code: S 407: = { 408: $$ = MODE_S; 409: } 410: | B 411: = { 412: $$ = MODE_B; 413: } 414: | C 415: = { 416: $$ = MODE_C; 417: } 418: ; 419: 420: pathname: pathstring 421: = { 422: /* 423: * Problem: this production is used for all pathname 424: * processing, but only gives a 550 error reply. 425: * This is a valid reply in some cases but not in others. 426: */ 427: if ($1 && strncmp((char *) $1, "~", 1) == 0) { 428: $$ = (int)*glob((char *) $1); 429: if (globerr != NULL) { 430: reply(550, globerr); 431: $$ = NULL; 432: } 433: free((char *) $1); 434: } else 435: $$ = $1; 436: } 437: ; 438: 439: pathstring: STRING 440: ; 441: 442: rename_cmd: rename_from rename_to 443: = { 444: if ($1 && $2) 445: renamecmd((char *) $1, (char *) $2); 446: else 447: reply(503, "Bad sequence of commands."); 448: if ($1) 449: free((char *) $1); 450: if ($2) 451: free((char *) $2); 452: } 453: ; 454: 455: rename_from: RNFR check_login SP pathname CRLF 456: = { 457: char *from = 0, *renamefrom(); 458: 459: if ($2 && $4) 460: from = renamefrom((char *) $4); 461: if (from == 0 && $4) 462: free((char *) $4); 463: $$ = (int)from; 464: } 465: ; 466: 467: rename_to: RNTO SP pathname CRLF 468: = { 469: $$ = $3; 470: } 471: ; 472: 473: check_login: /* empty */ 474: = { 475: if (logged_in) 476: $$ = 1; 477: else { 478: reply(530, "Please login with USER and PASS."); 479: $$ = 0; 480: } 481: } 482: ; 483: 484: %% 485: 486: extern jmp_buf errcatch; 487: 488: #define CMD 0 /* beginning of command */ 489: #define ARGS 1 /* expect miscellaneous arguments */ 490: #define STR1 2 /* expect SP followed by STRING */ 491: #define STR2 3 /* expect STRING */ 492: #define OSTR 4 /* optional STRING */ 493: 494: struct tab { 495: char *name; 496: short token; 497: short state; 498: short implemented; /* 1 if command is implemented */ 499: char *help; 500: }; 501: 502: struct tab cmdtab[] = { /* In order defined in RFC 765 */ 503: { "USER", USER, STR1, 1, "<sp> username" }, 504: { "PASS", PASS, STR1, 1, "<sp> password" }, 505: { "ACCT", ACCT, STR1, 0, "(specify account)" }, 506: { "REIN", REIN, ARGS, 0, "(reinitialize server state)" }, 507: { "QUIT", QUIT, ARGS, 1, "(terminate service)", }, 508: { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" }, 509: { "PASV", PASV, ARGS, 1, "(set server in passive mode)" }, 510: { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" }, 511: { "STRU", STRU, ARGS, 1, "(specify file structure)" }, 512: { "MODE", MODE, ARGS, 1, "(specify transfer mode)" }, 513: { "RETR", RETR, STR1, 1, "<sp> file-name" }, 514: { "STOR", STOR, STR1, 1, "<sp> file-name" }, 515: { "APPE", APPE, STR1, 1, "<sp> file-name" }, 516: { "MLFL", MLFL, OSTR, 0, "(mail file)" }, 517: { "MAIL", MAIL, OSTR, 0, "(mail to user)" }, 518: { "MSND", MSND, OSTR, 0, "(mail send to terminal)" }, 519: { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" }, 520: { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" }, 521: { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" }, 522: { "MRCP", MRCP, STR1, 0, "(mail recipient)" }, 523: { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" }, 524: { "REST", REST, STR1, 0, "(restart command)" }, 525: { "RNFR", RNFR, STR1, 1, "<sp> file-name" }, 526: { "RNTO", RNTO, STR1, 1, "<sp> file-name" }, 527: { "ABOR", ABOR, ARGS, 1, "(abort operation)" }, 528: { "DELE", DELE, STR1, 1, "<sp> file-name" }, 529: { "CWD", CWD, OSTR, 1, "[ <sp> directory-name]" }, 530: { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" }, 531: { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" }, 532: { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" }, 533: { "SITE", SITE, STR1, 0, "(get site parameters)" }, 534: { "STAT", STAT, OSTR, 0, "(get server status)" }, 535: { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" }, 536: { "NOOP", NOOP, ARGS, 1, "" }, 537: { "MKD", XMKD, STR1, 1, "<sp> path-name" }, 538: { "XMKD", XMKD, STR1, 1, "<sp> path-name" }, 539: { "RMD", XRMD, STR1, 1, "<sp> path-name" }, 540: { "XRMD", XRMD, STR1, 1, "<sp> path-name" }, 541: { "PWD", XPWD, ARGS, 1, "(return current directory)" }, 542: { "XPWD", XPWD, ARGS, 1, "(return current directory)" }, 543: { "CDUP", XCUP, ARGS, 1, "(change to parent directory)" }, 544: { "XCUP", XCUP, ARGS, 1, "(change to parent directory)" }, 545: { "STOU", STOU, STR1, 1, "<sp> file-name" }, 546: { NULL, 0, 0, 0, 0 } 547: }; 548: 549: struct tab * 550: lookup(cmd) 551: char *cmd; 552: { 553: register struct tab *p; 554: 555: for (p = cmdtab; p->name != NULL; p++) 556: if (strcmp(cmd, p->name) == 0) 557: return (p); 558: return (0); 559: } 560: 561: #include <arpa/telnet.h> 562: 563: /* 564: * getline - a hacked up version of fgets to ignore TELNET escape codes. 565: */ 566: char * 567: getline(s, n, iop) 568: char *s; 569: register FILE *iop; 570: { 571: register c; 572: register char *cs; 573: 574: cs = s; 575: /* tmpline may contain saved command from urgent mode interruption */ 576: for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) { 577: *cs++ = tmpline[c]; 578: if (tmpline[c] == '\n') { 579: *cs++ = '\0'; 580: if (debug) { 581: syslog(LOG_DEBUG, "FTPD: command: %s", s); 582: } 583: tmpline[0] = '\0'; 584: return(s); 585: } 586: if (c == 0) { 587: tmpline[0] = '\0'; 588: } 589: } 590: while (--n > 0 && (c = getc(iop)) != EOF) { 591: c = 0377 & c; 592: while (c == IAC) { 593: switch (c = 0377 & getc(iop)) { 594: case WILL: 595: case WONT: 596: c = 0377 & getc(iop); 597: printf("%c%c%c", IAC, WONT, c); 598: (void) fflush(stdout); 599: break; 600: case DO: 601: case DONT: 602: c = 0377 & getc(iop); 603: printf("%c%c%c", IAC, DONT, c); 604: (void) fflush(stdout); 605: break; 606: default: 607: break; 608: } 609: c = 0377 & getc(iop); /* try next character */ 610: } 611: *cs++ = c; 612: if (c=='\n') 613: break; 614: } 615: if (c == EOF && cs == s) 616: return (NULL); 617: *cs++ = '\0'; 618: if (debug) { 619: syslog(LOG_DEBUG, "FTPD: command: %s", s); 620: } 621: return (s); 622: } 623: 624: static int 625: toolong() 626: { 627: time_t now; 628: extern char *ctime(); 629: extern time_t time(); 630: 631: reply(421, 632: "Timeout (%d seconds): closing control connection.", timeout); 633: (void) time(&now); 634: if (logging) { 635: syslog(LOG_INFO, 636: "FTPD: User %s timed out after %d seconds at %s", 637: (pw ? pw -> pw_name : "unknown"), timeout, ctime(&now)); 638: } 639: dologout(1); 640: } 641: 642: yylex() 643: { 644: static int cpos, state; 645: register char *cp; 646: register struct tab *p; 647: int n; 648: char c; 649: 650: for (;;) { 651: switch (state) { 652: 653: case CMD: 654: (void) signal(SIGALRM, toolong); 655: (void) alarm((unsigned) timeout); 656: if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) { 657: reply(221, "You could at least say goodbye."); 658: dologout(0); 659: } 660: (void) alarm(0); 661: if (index(cbuf, '\r')) { 662: cp = index(cbuf, '\r'); 663: cp[0] = '\n'; cp[1] = 0; 664: } 665: if (index(cbuf, ' ')) 666: cpos = index(cbuf, ' ') - cbuf; 667: else 668: cpos = index(cbuf, '\n') - cbuf; 669: if (cpos == 0) { 670: cpos = 4; 671: } 672: c = cbuf[cpos]; 673: cbuf[cpos] = '\0'; 674: upper(cbuf); 675: p = lookup(cbuf); 676: cbuf[cpos] = c; 677: if (p != 0) { 678: if (p->implemented == 0) { 679: nack(p->name); 680: longjmp(errcatch,0); 681: /* NOTREACHED */ 682: } 683: state = p->state; 684: yylval = (int) p->name; 685: return (p->token); 686: } 687: break; 688: 689: case OSTR: 690: if (cbuf[cpos] == '\n') { 691: state = CMD; 692: return (CRLF); 693: } 694: /* FALL THRU */ 695: 696: case STR1: 697: if (cbuf[cpos] == ' ') { 698: cpos++; 699: state = STR2; 700: return (SP); 701: } 702: break; 703: 704: case STR2: 705: cp = &cbuf[cpos]; 706: n = strlen(cp); 707: cpos += n - 1; 708: /* 709: * Make sure the string is nonempty and \n terminated. 710: */ 711: if (n > 1 && cbuf[cpos] == '\n') { 712: cbuf[cpos] = '\0'; 713: yylval = copy(cp); 714: cbuf[cpos] = '\n'; 715: state = ARGS; 716: return (STRING); 717: } 718: break; 719: 720: case ARGS: 721: if (isdigit(cbuf[cpos])) { 722: cp = &cbuf[cpos]; 723: while (isdigit(cbuf[++cpos])) 724: ; 725: c = cbuf[cpos]; 726: cbuf[cpos] = '\0'; 727: yylval = atoi(cp); 728: cbuf[cpos] = c; 729: return (NUMBER); 730: } 731: switch (cbuf[cpos++]) { 732: 733: case '\n': 734: state = CMD; 735: return (CRLF); 736: 737: case ' ': 738: return (SP); 739: 740: case ',': 741: return (COMMA); 742: 743: case 'A': 744: case 'a': 745: return (A); 746: 747: case 'B': 748: case 'b': 749: return (B); 750: 751: case 'C': 752: case 'c': 753: return (C); 754: 755: case 'E': 756: case 'e': 757: return (E); 758: 759: case 'F': 760: case 'f': 761: return (F); 762: 763: case 'I': 764: case 'i': 765: return (I); 766: 767: case 'L': 768: case 'l': 769: return (L); 770: 771: case 'N': 772: case 'n': 773: return (N); 774: 775: case 'P': 776: case 'p': 777: return (P); 778: 779: case 'R': 780: case 'r': 781: return (R); 782: 783: case 'S': 784: case 's': 785: return (S); 786: 787: case 'T': 788: case 't': 789: return (T); 790: 791: } 792: break; 793: 794: default: 795: fatal("Unknown state in scanner."); 796: } 797: yyerror((char *) 0); 798: state = CMD; 799: longjmp(errcatch,0); 800: } 801: } 802: 803: upper(s) 804: char *s; 805: { 806: while (*s != '\0') { 807: if (islower(*s)) 808: *s = toupper(*s); 809: s++; 810: } 811: } 812: 813: copy(s) 814: char *s; 815: { 816: char *p; 817: extern char *malloc(), *strcpy(); 818: 819: p = malloc((unsigned) strlen(s) + 1); 820: if (p == NULL) 821: fatal("Ran out of memory."); 822: (void) strcpy(p, s); 823: return ((int)p); 824: } 825: 826: help(s) 827: char *s; 828: { 829: register struct tab *c; 830: register int width, NCMDS; 831: 832: width = 0, NCMDS = 0; 833: for (c = cmdtab; c->name != NULL; c++) { 834: int len = strlen(c->name); 835: 836: if (c->implemented == 0) 837: len++; 838: if (len > width) 839: width = len; 840: NCMDS++; 841: } 842: width = (width + 8) &~ 7; 843: if (s == 0) { 844: register int i, j, w; 845: int columns, lines; 846: 847: lreply(214, 848: "The following commands are recognized (* =>'s unimplemented)."); 849: columns = 76 / width; 850: if (columns == 0) 851: columns = 1; 852: lines = (NCMDS + columns - 1) / columns; 853: for (i = 0; i < lines; i++) { 854: printf(" "); 855: for (j = 0; j < columns; j++) { 856: c = cmdtab + j * lines + i; 857: printf("%s%c", c->name, 858: c->implemented ? ' ' : '*'); 859: if (c + lines >= &cmdtab[NCMDS]) 860: break; 861: w = strlen(c->name); 862: while (w < width) { 863: putchar(' '); 864: w++; 865: } 866: } 867: printf("\r\n"); 868: } 869: (void) fflush(stdout); 870: reply(214, "Direct comments to ftp-bugs@%s.", hostname); 871: return; 872: } 873: upper(s); 874: c = lookup(s); 875: if (c == (struct tab *)0) { 876: reply(502, "Unknown command %s.", s); 877: return; 878: } 879: if (c->implemented) 880: reply(214, "Syntax: %s %s", c->name, c->help); 881: else 882: reply(214, "%-*s\t%s; unimplemented.", width, c->name, c->help); 883: }