xref: /csrg-svn/libexec/ftpd/ftpcmd.y (revision 10302)
110276Ssam /*
210276Ssam  * Grammar for FTP commands.
310276Ssam  * See RFC 765.
410276Ssam  */
510276Ssam 
610276Ssam %{
710276Ssam 
810276Ssam #ifndef lint
9*10302Ssam static char sccsid[] = "@(#)ftpcmd.y	4.2 83/01/15";
1010276Ssam #endif
1110276Ssam 
1210276Ssam #include <sys/types.h>
1310276Ssam #include <sys/socket.h>
1410276Ssam 
1510276Ssam #include <netinet/in.h>
1610276Ssam 
1710276Ssam #include <stdio.h>
1810276Ssam #include <ctype.h>
1910276Ssam #include <pwd.h>
2010276Ssam #include <setjmp.h>
2110276Ssam #include "ftp.h"
2210276Ssam 
2310276Ssam extern	struct sockaddr_in data_dest;
2410276Ssam extern	int logged_in;
2510276Ssam extern	struct passwd *pw;
2610276Ssam extern	int guest;
2710276Ssam extern	int logging;
2810276Ssam extern	int type;
2910276Ssam extern	int form;
3010276Ssam extern	int debug;
3110276Ssam extern	char hostname[];
3210276Ssam extern	char *globerr;
3310276Ssam char	**glob();
3410276Ssam 
3510276Ssam static	int cmd_type;
3610276Ssam static	int cmd_form;
3710276Ssam static	int cmd_bytesz;
3810276Ssam 
3910276Ssam static	struct passwd nobody = { "?", "?" };
4010276Ssam 
4110276Ssam char	*index();
4210276Ssam %}
4310276Ssam 
4410276Ssam %token
4510276Ssam 	A	B	C	E	F	I
4610276Ssam 	L	N	P	R	S	T
4710276Ssam 
4810276Ssam 	SP	CRLF	COMMA	STRING	NUMBER
4910276Ssam 
5010276Ssam 	USER	PASS	ACCT	REIN	QUIT	PORT
5110276Ssam 	PASV	TYPE	STRU	MODE	RETR	STOR
5210276Ssam 	APPE	MLFL	MAIL	MSND	MSOM	MSAM
5310276Ssam 	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
5410276Ssam 	ABOR	DELE	CWD	LIST	NLST	SITE
5510276Ssam 	STAT	HELP	NOOP	XMKD	XRMD	XPWD
5610276Ssam 	XCUP
5710276Ssam 
5810276Ssam 	LEXERR
5910276Ssam 
6010276Ssam %start	cmd_list
6110276Ssam 
6210276Ssam %%
6310276Ssam 
6410276Ssam cmd_list:	/* empty */
6510276Ssam 	|	cmd_list cmd
6610276Ssam 	;
6710276Ssam 
6810276Ssam cmd:		USER SP username CRLF
6910276Ssam 		= {
7010276Ssam 			extern struct passwd *getpwnam();
7110276Ssam 
7210276Ssam 			if (strcmp($3, "ftp") == 0 ||
7310276Ssam 			    strcmp($3, "anonymous") == 0) {
7410276Ssam 				guest = 1;
7510276Ssam 				pw = getpwnam("ftp");
7610276Ssam 				reply(331,
7710276Ssam 				  "Guest login ok, send ident as password.");
7810276Ssam 			} else {
7910276Ssam 				guest = 0;
8010276Ssam 				pw = getpwnam($3);
8110276Ssam 				reply(331, "Password required for %s.", $3);
8210276Ssam 			}
8310276Ssam 			free($3);
8410276Ssam 			if (pw == NULL)
8510276Ssam 				pw = &nobody;
8610276Ssam 		}
8710276Ssam 	|	PASS SP password CRLF
8810276Ssam 		= {
8910276Ssam 			pass($3);
9010276Ssam 			free($3);
9110276Ssam 		}
9210276Ssam 	|	PORT SP host_port CRLF
9310276Ssam 		= {
9410276Ssam 			ack($1);
9510276Ssam 		}
9610276Ssam 	|	TYPE SP type_code CRLF
9710276Ssam 		= {
9810276Ssam 			switch (cmd_type) {
9910276Ssam 
10010276Ssam 			case TYPE_A:
10110276Ssam 				if (cmd_form == FORM_N) {
10210276Ssam 					reply(200, "Type set to A.");
10310276Ssam 					type = cmd_type;
10410276Ssam 					form = cmd_form;
10510276Ssam 				} else
10610276Ssam 					reply(504, "Form must be N.");
10710276Ssam 				break;
10810276Ssam 
10910276Ssam 			case TYPE_E:
11010276Ssam 				reply(504, "Type E not implemented.");
11110276Ssam 				break;
11210276Ssam 
11310276Ssam 			case TYPE_I:
11410276Ssam 				reply(200, "Type set to I.");
11510276Ssam 				type = cmd_type;
11610276Ssam 				break;
11710276Ssam 
11810276Ssam 			case TYPE_L:
11910276Ssam 				if (cmd_bytesz == 8) {
12010276Ssam 					reply(200,
12110276Ssam 					    "Type set to L (byte size 8).");
12210276Ssam 					type = cmd_type;
12310276Ssam 				} else
12410276Ssam 					reply(504, "Byte size must be 8.");
12510276Ssam 			}
12610276Ssam 		}
12710276Ssam 	|	STRU SP struct_code CRLF
12810276Ssam 		= {
12910276Ssam 			switch ($3) {
13010276Ssam 
13110276Ssam 			case STRU_F:
13210276Ssam 				reply(200, "STRU F ok.");
13310276Ssam 				break;
13410276Ssam 
13510276Ssam 			default:
13610276Ssam 				reply(502, "Unimplemented STRU type.");
13710276Ssam 			}
13810276Ssam 		}
13910276Ssam 	|	MODE SP mode_code CRLF
14010276Ssam 		= {
14110276Ssam 			switch ($3) {
14210276Ssam 
14310276Ssam 			case MODE_S:
14410276Ssam 				reply(200, "MODE S ok.");
14510276Ssam 				break;
14610276Ssam 
14710276Ssam 			default:
14810276Ssam 				reply(502, "Unimplemented MODE type.");
14910276Ssam 			}
15010276Ssam 		}
15110276Ssam 	|	ALLO SP NUMBER CRLF
15210276Ssam 		= {
15310276Ssam 			ack($1);
15410276Ssam 		}
15510276Ssam 	|	RETR check_login SP pathname CRLF
15610276Ssam 		= {
157*10302Ssam 			if ($2 && $4 != NULL)
15810276Ssam 				retrieve(0, $4);
159*10302Ssam 			if ($4 != NULL)
160*10302Ssam 				free($4);
16110276Ssam 		}
16210276Ssam 	|	STOR check_login SP pathname CRLF
16310276Ssam 		= {
164*10302Ssam 			if ($2 && $4 != NULL)
16510276Ssam 				store($4, "w");
166*10302Ssam 			if ($4 != NULL)
167*10302Ssam 				free($4);
16810276Ssam 		}
16910276Ssam 	|	APPE check_login SP pathname CRLF
17010276Ssam 		= {
171*10302Ssam 			if ($2 && $4 != NULL)
17210276Ssam 				store($4, "a");
173*10302Ssam 			if ($4 != NULL)
174*10302Ssam 				free($4);
17510276Ssam 		}
17610276Ssam 	|	NLST check_login CRLF
17710276Ssam 		= {
17810276Ssam 			if ($2)
179*10302Ssam 				retrieve("ls -C", "");
18010276Ssam 		}
18110276Ssam 	|	NLST check_login SP pathname CRLF
18210276Ssam 		= {
183*10302Ssam 			if ($2 && $4 != NULL)
184*10302Ssam 				retrieve("ls -C %s", $4);
185*10302Ssam 			if ($4 != NULL)
186*10302Ssam 				free($4);
18710276Ssam 		}
18810276Ssam 	|	LIST check_login CRLF
18910276Ssam 		= {
19010276Ssam 			if ($2)
191*10302Ssam 				retrieve("ls -lg", "");
19210276Ssam 		}
19310276Ssam 	|	LIST check_login SP pathname CRLF
19410276Ssam 		= {
195*10302Ssam 			if ($2 && $4 != NULL)
196*10302Ssam 				retrieve("ls -lg %s", $4);
197*10302Ssam 			if ($4 != NULL)
198*10302Ssam 				free($4);
19910276Ssam 		}
20010276Ssam 	|	DELE check_login SP pathname CRLF
20110276Ssam 		= {
202*10302Ssam 			if ($2 && $4 != NULL)
20310276Ssam 				delete($4);
204*10302Ssam 			if ($4 != NULL)
205*10302Ssam 				free($4);
20610276Ssam 		}
20710276Ssam 	|	CWD check_login CRLF
20810276Ssam 		= {
20910276Ssam 			if ($2)
21010276Ssam 				cwd(pw->pw_dir);
21110276Ssam 		}
21210276Ssam 	|	CWD check_login SP pathname CRLF
21310276Ssam 		= {
214*10302Ssam 			if ($2 && $4 != NULL)
21510276Ssam 				cwd($4);
216*10302Ssam 			if ($4 != NULL)
217*10302Ssam 				free($4);
21810276Ssam 		}
21910276Ssam 	|	rename_cmd
22010276Ssam 	|	HELP CRLF
22110276Ssam 		= {
22210276Ssam 			help(0);
22310276Ssam 		}
22410276Ssam 	|	HELP SP STRING CRLF
22510276Ssam 		= {
22610276Ssam 			help($3);
22710276Ssam 		}
22810276Ssam 	|	NOOP CRLF
22910276Ssam 		= {
23010276Ssam 			ack($1);
23110276Ssam 		}
23210276Ssam 	|	XMKD check_login SP pathname CRLF
23310276Ssam 		= {
234*10302Ssam 			if ($2 && $4 != NULL)
235*10302Ssam 				makedir($4);
236*10302Ssam 			if ($4 != NULL)
237*10302Ssam 				free($4);
23810276Ssam 		}
23910276Ssam 	|	XRMD check_login SP pathname CRLF
24010276Ssam 		= {
241*10302Ssam 			if ($2 && $4 != NULL)
242*10302Ssam 				removedir($4);
243*10302Ssam 			if ($4 != NULL)
244*10302Ssam 				free($4);
24510276Ssam 		}
24610276Ssam 	|	XPWD check_login CRLF
24710276Ssam 		= {
24810276Ssam 			if ($2)
249*10302Ssam 				pwd();
25010276Ssam 		}
25110276Ssam 	|	XCUP check_login CRLF
25210276Ssam 		= {
25310276Ssam 			if ($2)
25410276Ssam 				cwd("..");
25510276Ssam 		}
25610276Ssam 	|	QUIT CRLF
25710276Ssam 		= {
25810276Ssam 			reply(221, "Goodbye.");
25910276Ssam 			exit(0);
26010276Ssam 		}
26110276Ssam 	|	error CRLF
26210276Ssam 		= {
26310276Ssam 			yyerrok;
26410276Ssam 		}
26510276Ssam 	;
26610276Ssam 
26710276Ssam username:	STRING
26810276Ssam 	;
26910276Ssam 
27010276Ssam password:	STRING
27110276Ssam 	;
27210276Ssam 
27310276Ssam byte_size:	NUMBER
27410276Ssam 	;
27510276Ssam 
27610276Ssam host_port:	NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
27710276Ssam 		NUMBER COMMA NUMBER
27810276Ssam 		= {
27910276Ssam 			register char *a, *p;
28010276Ssam 
28110276Ssam 			a = (char *)&data_dest.sin_addr;
28210276Ssam 			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
28310276Ssam 			p = (char *)&data_dest.sin_port;
28410276Ssam 			p[0] = $9; p[1] = $11;
28510276Ssam 		}
28610276Ssam 	;
28710276Ssam 
28810276Ssam form_code:	N
28910276Ssam 	= {
29010276Ssam 		$$ = FORM_N;
29110276Ssam 	}
29210276Ssam 	|	T
29310276Ssam 	= {
29410276Ssam 		$$ = FORM_T;
29510276Ssam 	}
29610276Ssam 	|	C
29710276Ssam 	= {
29810276Ssam 		$$ = FORM_C;
29910276Ssam 	}
30010276Ssam 	;
30110276Ssam 
30210276Ssam type_code:	A
30310276Ssam 	= {
30410276Ssam 		cmd_type = TYPE_A;
30510276Ssam 		cmd_form = FORM_N;
30610276Ssam 	}
30710276Ssam 	|	A SP form_code
30810276Ssam 	= {
30910276Ssam 		cmd_type = TYPE_A;
31010276Ssam 		cmd_form = $3;
31110276Ssam 	}
31210276Ssam 	|	E
31310276Ssam 	= {
31410276Ssam 		cmd_type = TYPE_E;
31510276Ssam 		cmd_form = FORM_N;
31610276Ssam 	}
31710276Ssam 	|	E SP form_code
31810276Ssam 	= {
31910276Ssam 		cmd_type = TYPE_E;
32010276Ssam 		cmd_form = $3;
32110276Ssam 	}
32210276Ssam 	|	I
32310276Ssam 	= {
32410276Ssam 		cmd_type = TYPE_I;
32510276Ssam 	}
32610276Ssam 	|	L
32710276Ssam 	= {
32810276Ssam 		cmd_type = TYPE_L;
32910276Ssam 		cmd_bytesz = 8;
33010276Ssam 	}
33110276Ssam 	|	L SP byte_size
33210276Ssam 	= {
33310276Ssam 		cmd_type = TYPE_L;
33410276Ssam 		cmd_bytesz = $3;
33510276Ssam 	}
33610276Ssam 	/* this is for a bug in the BBN ftp */
33710276Ssam 	|	L byte_size
33810276Ssam 	= {
33910276Ssam 		cmd_type = TYPE_L;
34010276Ssam 		cmd_bytesz = $2;
34110276Ssam 	}
34210276Ssam 	;
34310276Ssam 
34410276Ssam struct_code:	F
34510276Ssam 	= {
34610276Ssam 		$$ = STRU_F;
34710276Ssam 	}
34810276Ssam 	|	R
34910276Ssam 	= {
35010276Ssam 		$$ = STRU_R;
35110276Ssam 	}
35210276Ssam 	|	P
35310276Ssam 	= {
35410276Ssam 		$$ = STRU_P;
35510276Ssam 	}
35610276Ssam 	;
35710276Ssam 
35810276Ssam mode_code:	S
35910276Ssam 	= {
36010276Ssam 		$$ = MODE_S;
36110276Ssam 	}
36210276Ssam 	|	B
36310276Ssam 	= {
36410276Ssam 		$$ = MODE_B;
36510276Ssam 	}
36610276Ssam 	|	C
36710276Ssam 	= {
36810276Ssam 		$$ = MODE_C;
36910276Ssam 	}
37010276Ssam 	;
37110276Ssam 
37210276Ssam pathname:	pathstring
37310276Ssam 	= {
37410276Ssam 		if ($1 && strncmp($1, "~", 1) == 0) {
37510276Ssam 			$$ = (int)*glob($1);
376*10302Ssam 			if (globerr != NULL) {
37710276Ssam 				reply(550, globerr);
378*10302Ssam 				$$ = NULL;
379*10302Ssam 			}
38010276Ssam 			free($1);
38110276Ssam 		} else
38210276Ssam 			$$ = $1;
38310276Ssam 	}
38410276Ssam 	;
38510276Ssam 
38610276Ssam pathstring:	STRING
38710276Ssam 	;
38810276Ssam 
38910276Ssam rename_cmd:	rename_from rename_to
39010276Ssam 	= {
39110276Ssam 		if ($1 && $2)
39210276Ssam 			renamecmd($1, $2);
39310276Ssam 		else
39410276Ssam 			reply(503, "Bad sequence of commands.");
39510276Ssam 		if ($1)
39610276Ssam 			free($1);
39710276Ssam 		if ($2)
39810276Ssam 			free($2);
39910276Ssam 	}
40010276Ssam 	;
40110276Ssam 
40210276Ssam rename_from:	RNFR check_login SP pathname CRLF
40310276Ssam 	= {
40410276Ssam 		char *from = 0, *renamefrom();
40510276Ssam 
406*10302Ssam 		if ($2 && $4)
40710276Ssam 			from = renamefrom($4);
408*10302Ssam 		if (from == 0 && $4)
40910276Ssam 			free($4);
41010276Ssam 		$$ = (int)from;
41110276Ssam 	}
41210276Ssam 	;
41310276Ssam 
41410276Ssam rename_to:	RNTO SP pathname CRLF
41510276Ssam 	= {
41610276Ssam 		$$ = $3;
41710276Ssam 	}
41810276Ssam 	;
41910276Ssam 
42010276Ssam check_login:	/* empty */
42110276Ssam 	= {
42210276Ssam 		if (logged_in)
42310276Ssam 			$$ = 1;
42410276Ssam 		else {
42510276Ssam 			reply(530, "Please login with USER and PASS.");
42610276Ssam 			$$ = 0;
42710276Ssam 		}
42810276Ssam 	}
42910276Ssam 	;
43010276Ssam 
43110276Ssam %%
43210276Ssam 
43310276Ssam extern jmp_buf errcatch;
43410276Ssam 
43510276Ssam #define	CMD	0	/* beginning of command */
43610276Ssam #define	ARGS	1	/* expect miscellaneous arguments */
43710276Ssam #define	STR1	2	/* expect SP followed by STRING */
43810276Ssam #define	STR2	3	/* expect STRING */
43910276Ssam #define	OSTR	4	/* optional STRING */
44010276Ssam 
44110276Ssam struct tab {
44210276Ssam 	char	*name;
44310276Ssam 	short	token;
44410276Ssam 	short	state;
44510276Ssam 	short	implemented;	/* 1 if command is implemented */
44610276Ssam 	char	*help;
44710276Ssam };
44810276Ssam 
44910276Ssam struct tab cmdtab[] = {		/* In order defined in RFC 765 */
45010276Ssam 	{ "USER", USER, STR1, 1,	"<sp> username" },
45110276Ssam 	{ "PASS", PASS, STR1, 1,	"<sp> password" },
45210276Ssam 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
45310276Ssam 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
45410276Ssam 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
45510276Ssam 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
45610276Ssam 	{ "PASV", PASV, ARGS, 0,	"(set server in passive mode)" },
45710276Ssam 	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
45810276Ssam 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
45910276Ssam 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
46010276Ssam 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
46110276Ssam 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
46210276Ssam 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
46310276Ssam 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
46410276Ssam 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
46510276Ssam 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
46610276Ssam 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
46710276Ssam 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
46810276Ssam 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
46910276Ssam 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
47010276Ssam 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
47110276Ssam 	{ "REST", REST, STR1, 0,	"(restart command)" },
47210276Ssam 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
47310276Ssam 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
47410276Ssam 	{ "ABOR", ABOR, ARGS, 0,	"(abort operation)" },
47510276Ssam 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
47610276Ssam 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name]" },
47710276Ssam 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
47810276Ssam 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
47910276Ssam 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
48010276Ssam 	{ "SITE", SITE, STR1, 0,	"(get site parameters)" },
48110276Ssam 	{ "STAT", STAT, OSTR, 0,	"(get server status)" },
48210276Ssam 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
48310276Ssam 	{ "NOOP", NOOP, ARGS, 1,	"" },
48410276Ssam 	{ "XMKD", XMKD, STR1, 1,	"<sp> path-name" },
48510276Ssam 	{ "XRMD", XRMD, STR1, 1,	"<sp> path-name" },
48610276Ssam 	{ "XPWD", XPWD, ARGS, 1,	"(return current directory)" },
48710276Ssam 	{ "XCUP", XCUP, ARGS, 1,	"(change to parent directory)" },
48810276Ssam 	{ NULL,   0,    0,    0,	0 }
48910276Ssam };
49010276Ssam 
49110276Ssam struct tab *
49210276Ssam lookup(cmd)
49310276Ssam 	char *cmd;
49410276Ssam {
49510276Ssam 	register struct tab *p;
49610276Ssam 
49710276Ssam 	for (p = cmdtab; p->name != NULL; p++)
49810276Ssam 		if (strcmp(cmd, p->name) == 0)
49910276Ssam 			return (p);
50010276Ssam 	return (0);
50110276Ssam }
50210276Ssam 
50310276Ssam #include "../telnet/telnet.h"
50410276Ssam 
50510276Ssam /*
50610276Ssam  * getline - a hacked up version of fgets to ignore TELNET escape codes.
50710276Ssam  */
50810276Ssam char *
50910276Ssam getline(s, n, iop)
51010276Ssam 	char *s;
51110276Ssam 	register FILE *iop;
51210276Ssam {
51310276Ssam 	register c;
51410276Ssam 	register char *cs;
51510276Ssam 
51610276Ssam 	cs = s;
51710276Ssam 	while (--n > 0 && (c = getc(iop)) >= 0) {
51810276Ssam 		while (c == IAC) {
51910276Ssam 			c = getc(iop);	/* skip command */
52010276Ssam 			c = getc(iop);	/* try next char */
52110276Ssam 		}
52210276Ssam 		*cs++ = c;
52310276Ssam 		if (c=='\n')
52410276Ssam 			break;
52510276Ssam 	}
52610276Ssam 	if (c < 0 && cs == s)
52710276Ssam 		return (NULL);
52810276Ssam 	*cs++ = '\0';
529*10302Ssam 	fprintf(stderr, "FTPD: command: %s", s);
530*10302Ssam 	if (c != '\n')
531*10302Ssam 		putc('\n', stderr);
53210276Ssam 	fflush(stderr);
53310276Ssam 	return (s);
53410276Ssam }
53510276Ssam 
53610276Ssam yylex()
53710276Ssam {
53810276Ssam 	static char cbuf[512];
53910276Ssam 	static int cpos, state;
54010276Ssam 	register char *cp;
54110276Ssam 	register struct tab *p;
54210276Ssam 	int n;
54310276Ssam 	char c;
54410276Ssam 
54510276Ssam 	for (;;) {
54610276Ssam 		switch (state) {
54710276Ssam 
54810276Ssam 		case CMD:
54910276Ssam 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
55010276Ssam 				reply(221, "You could at least say goodbye.");
55110276Ssam 				exit(0);
55210276Ssam 			}
55310276Ssam 			if (index(cbuf, '\r')) {
55410276Ssam 				cp = index(cbuf, '\r');
55510276Ssam 				cp[0] = '\n'; cp[1] = 0;
55610276Ssam 			}
55710276Ssam 			if (index(cbuf, ' '))
55810276Ssam 				cpos = index(cbuf, ' ') - cbuf;
55910276Ssam 			else
56010276Ssam 				cpos = 4;
56110276Ssam 			c = cbuf[cpos];
56210276Ssam 			cbuf[cpos] = '\0';
56310276Ssam 			upper(cbuf);
56410276Ssam 			p = lookup(cbuf);
56510276Ssam 			cbuf[cpos] = c;
56610276Ssam 			if (p != 0) {
56710276Ssam 				if (p->implemented == 0) {
56810276Ssam 					nack(p->name);
56910276Ssam 					longjmp(errcatch);
57010276Ssam 					/* NOTREACHED */
57110276Ssam 				}
57210276Ssam 				state = p->state;
57310276Ssam 				yylval = (int) p->name;
57410276Ssam 				return (p->token);
57510276Ssam 			}
57610276Ssam 			break;
57710276Ssam 
57810276Ssam 		case OSTR:
57910276Ssam 			if (cbuf[cpos] == '\n') {
58010276Ssam 				state = CMD;
58110276Ssam 				return (CRLF);
58210276Ssam 			}
58310276Ssam 			/* FALL THRU */
58410276Ssam 
58510276Ssam 		case STR1:
58610276Ssam 			if (cbuf[cpos] == ' ') {
58710276Ssam 				cpos++;
58810276Ssam 				state = STR2;
58910276Ssam 				return (SP);
59010276Ssam 			}
59110276Ssam 			break;
59210276Ssam 
59310276Ssam 		case STR2:
59410276Ssam 			cp = &cbuf[cpos];
59510276Ssam 			n = strlen(cp);
59610276Ssam 			cpos += n - 1;
59710276Ssam 			/*
59810276Ssam 			 * Make sure the string is nonempty and \n terminated.
59910276Ssam 			 */
60010276Ssam 			if (n > 1 && cbuf[cpos] == '\n') {
60110276Ssam 				cbuf[cpos] = '\0';
60210276Ssam 				yylval = copy(cp);
60310276Ssam 				cbuf[cpos] = '\n';
60410276Ssam 				state = ARGS;
60510276Ssam 				return (STRING);
60610276Ssam 			}
60710276Ssam 			break;
60810276Ssam 
60910276Ssam 		case ARGS:
61010276Ssam 			if (isdigit(cbuf[cpos])) {
61110276Ssam 				cp = &cbuf[cpos];
61210276Ssam 				while (isdigit(cbuf[++cpos]))
61310276Ssam 					;
61410276Ssam 				c = cbuf[cpos];
61510276Ssam 				cbuf[cpos] = '\0';
61610276Ssam 				yylval = atoi(cp);
61710276Ssam 				cbuf[cpos] = c;
61810276Ssam 				return (NUMBER);
61910276Ssam 			}
62010276Ssam 			switch (cbuf[cpos++]) {
62110276Ssam 
62210276Ssam 			case '\n':
62310276Ssam 				state = CMD;
62410276Ssam 				return (CRLF);
62510276Ssam 
62610276Ssam 			case ' ':
62710276Ssam 				return (SP);
62810276Ssam 
62910276Ssam 			case ',':
63010276Ssam 				return (COMMA);
63110276Ssam 
63210276Ssam 			case 'A':
63310276Ssam 			case 'a':
63410276Ssam 				return (A);
63510276Ssam 
63610276Ssam 			case 'B':
63710276Ssam 			case 'b':
63810276Ssam 				return (B);
63910276Ssam 
64010276Ssam 			case 'C':
64110276Ssam 			case 'c':
64210276Ssam 				return (C);
64310276Ssam 
64410276Ssam 			case 'E':
64510276Ssam 			case 'e':
64610276Ssam 				return (E);
64710276Ssam 
64810276Ssam 			case 'F':
64910276Ssam 			case 'f':
65010276Ssam 				return (F);
65110276Ssam 
65210276Ssam 			case 'I':
65310276Ssam 			case 'i':
65410276Ssam 				return (I);
65510276Ssam 
65610276Ssam 			case 'L':
65710276Ssam 			case 'l':
65810276Ssam 				return (L);
65910276Ssam 
66010276Ssam 			case 'N':
66110276Ssam 			case 'n':
66210276Ssam 				return (N);
66310276Ssam 
66410276Ssam 			case 'P':
66510276Ssam 			case 'p':
66610276Ssam 				return (P);
66710276Ssam 
66810276Ssam 			case 'R':
66910276Ssam 			case 'r':
67010276Ssam 				return (R);
67110276Ssam 
67210276Ssam 			case 'S':
67310276Ssam 			case 's':
67410276Ssam 				return (S);
67510276Ssam 
67610276Ssam 			case 'T':
67710276Ssam 			case 't':
67810276Ssam 				return (T);
67910276Ssam 
68010276Ssam 			}
68110276Ssam 			break;
68210276Ssam 
68310276Ssam 		default:
68410276Ssam 			fatal("Unknown state in scanner.");
68510276Ssam 		}
68610276Ssam 		yyerror();
68710276Ssam 		state = CMD;
68810276Ssam 		longjmp(errcatch);
68910276Ssam 	}
69010276Ssam }
69110276Ssam 
69210276Ssam upper(s)
69310276Ssam 	char *s;
69410276Ssam {
69510276Ssam 	while (*s != '\0') {
69610276Ssam 		if (islower(*s))
69710276Ssam 			*s = toupper(*s);
69810276Ssam 		s++;
69910276Ssam 	}
70010276Ssam }
70110276Ssam 
70210276Ssam copy(s)
70310276Ssam 	char *s;
70410276Ssam {
70510276Ssam 	char *p;
70610276Ssam 	extern char *malloc();
70710276Ssam 
70810276Ssam 	p = malloc(strlen(s) + 1);
70910276Ssam 	if (p == NULL)
71010276Ssam 		fatal("Ran out of memory.");
71110276Ssam 	strcpy(p, s);
71210276Ssam 	return ((int)p);
71310276Ssam }
71410276Ssam 
71510276Ssam help(s)
71610276Ssam 	char *s;
71710276Ssam {
71810276Ssam 	register struct tab *c;
71910276Ssam 	register int width, NCMDS;
72010276Ssam 
72110276Ssam 	width = 0, NCMDS = 0;
72210276Ssam 	for (c = cmdtab; c->name != NULL; c++) {
72310276Ssam 		int len = strlen(c->name);
72410276Ssam 
72510276Ssam 		if (c->implemented == 0)
72610276Ssam 			len++;
72710276Ssam 		if (len > width)
72810276Ssam 			width = len;
72910276Ssam 		NCMDS++;
73010276Ssam 	}
73110276Ssam 	width = (width + 8) &~ 7;
73210276Ssam 	if (s == 0) {
73310276Ssam 		register int i, j, w;
73410276Ssam 		int columns, lines;
73510276Ssam 
73610276Ssam 		lreply(214,
73710276Ssam 	  "The following commands are recognized (* =>'s unimplemented).");
73810276Ssam 		columns = 76 / width;
73910276Ssam 		if (columns == 0)
74010276Ssam 			columns = 1;
74110276Ssam 		lines = (NCMDS + columns - 1) / columns;
74210276Ssam 		for (i = 0; i < lines; i++) {
74310276Ssam 			printf("    ");
74410276Ssam 			for (j = 0; j < columns; j++) {
74510276Ssam 				c = cmdtab + j * lines + i;
74610276Ssam 				printf("%s%c", c->name,
74710276Ssam 					c->implemented ? ' ' : '*');
748*10302Ssam 				if (c + lines >= &cmdtab[NCMDS])
74910276Ssam 					break;
75010276Ssam 				w = strlen(c->name);
75110276Ssam 				while (w < width) {
75210276Ssam 					putchar(' ');
75310276Ssam 					w++;
75410276Ssam 				}
75510276Ssam 			}
75610276Ssam 			printf("\r\n");
75710276Ssam 		}
75810276Ssam 		fflush(stdout);
75910276Ssam 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
76010276Ssam 		return;
76110276Ssam 	}
76210276Ssam 	upper(s);
76310276Ssam 	c = lookup(s);
76410276Ssam 	if (c == (struct tab *)0) {
76510276Ssam 		reply(504, "Unknown command %s.", s);
76610276Ssam 		return;
76710276Ssam 	}
76810276Ssam 	if (c->implemented)
76910276Ssam 		reply(214, "Syntax: %s %s", c->name, c->help);
77010276Ssam 	else
77110276Ssam 		reply(214, "%-*s\t%s; unimplemented.", width, c->name, c->help);
77210276Ssam }
773