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