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