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