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