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