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