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