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