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