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