xref: /csrg-svn/libexec/ftpd/ftpcmd.y (revision 42666)
110276Ssam /*
236304Skarels  * Copyright (c) 1985, 1988 Regents of the University of California.
333738Sbostic  * All rights reserved.
433738Sbostic  *
5*42666Sbostic  * %sccs.include.redist.c%
634769Sbostic  *
7*42666Sbostic  *	@(#)ftpcmd.y	5.23 (Berkeley) 06/01/90
822501Sdist  */
922501Sdist 
1022501Sdist /*
1110276Ssam  * Grammar for FTP commands.
1236933Skarels  * See RFC 959.
1310276Ssam  */
1410276Ssam 
1510276Ssam %{
1610276Ssam 
1710276Ssam #ifndef lint
18*42666Sbostic static char sccsid[] = "@(#)ftpcmd.y	5.23 (Berkeley) 06/01/90";
1933738Sbostic #endif /* not lint */
2010276Ssam 
2136552Sbostic #include <sys/param.h>
2210276Ssam #include <sys/socket.h>
2310276Ssam 
2410276Ssam #include <netinet/in.h>
2510276Ssam 
2613033Ssam #include <arpa/ftp.h>
2713033Ssam 
2810276Ssam #include <stdio.h>
2911652Ssam #include <signal.h>
3010276Ssam #include <ctype.h>
3110276Ssam #include <pwd.h>
3210276Ssam #include <setjmp.h>
3326494Sminshall #include <syslog.h>
3436933Skarels #include <sys/stat.h>
3536933Skarels #include <time.h>
3610276Ssam 
3710276Ssam extern	struct sockaddr_in data_dest;
3810276Ssam extern	int logged_in;
3910276Ssam extern	struct passwd *pw;
4010276Ssam extern	int guest;
4110276Ssam extern	int logging;
4210276Ssam extern	int type;
4310276Ssam extern	int form;
4410276Ssam extern	int debug;
4511652Ssam extern	int timeout;
4636933Skarels extern	int maxtimeout;
4726045Sminshall extern  int pdata;
4836620Srick extern	char hostname[], remotehost[];
4936933Skarels extern	char proctitle[];
5010276Ssam extern	char *globerr;
5110320Ssam extern	int usedefault;
5226045Sminshall extern  int transflag;
5326045Sminshall extern  char tmpline[];
5410276Ssam char	**glob();
5510276Ssam 
5638135Srick off_t	restart_point;
5738135Srick 
5810276Ssam static	int cmd_type;
5910276Ssam static	int cmd_form;
6010276Ssam static	int cmd_bytesz;
6136304Skarels char	cbuf[512];
6236304Skarels char	*fromname;
6310276Ssam 
6410276Ssam char	*index();
6510276Ssam %}
6610276Ssam 
6710276Ssam %token
6810276Ssam 	A	B	C	E	F	I
6910276Ssam 	L	N	P	R	S	T
7010276Ssam 
7110276Ssam 	SP	CRLF	COMMA	STRING	NUMBER
7210276Ssam 
7310276Ssam 	USER	PASS	ACCT	REIN	QUIT	PORT
7410276Ssam 	PASV	TYPE	STRU	MODE	RETR	STOR
7510276Ssam 	APPE	MLFL	MAIL	MSND	MSOM	MSAM
7610276Ssam 	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
7710276Ssam 	ABOR	DELE	CWD	LIST	NLST	SITE
7836620Srick 	STAT	HELP	NOOP	MKD	RMD	PWD
7936933Skarels 	CDUP	STOU	SMNT	SYST	SIZE	MDTM
8010276Ssam 
8136933Skarels 	UMASK	IDLE	CHMOD
8236933Skarels 
8310276Ssam 	LEXERR
8410276Ssam 
8510276Ssam %start	cmd_list
8610276Ssam 
8710276Ssam %%
8810276Ssam 
8910276Ssam cmd_list:	/* empty */
9010276Ssam 	|	cmd_list cmd
9130945Scsvsj 		= {
9230945Scsvsj 			fromname = (char *) 0;
9338135Srick 			restart_point = (off_t) 0;
9430945Scsvsj 		}
9530945Scsvsj 	|	cmd_list rcmd
9610276Ssam 	;
9710276Ssam 
9810276Ssam cmd:		USER SP username CRLF
9910276Ssam 		= {
10036304Skarels 			user((char *) $3);
10126494Sminshall 			free((char *) $3);
10210276Ssam 		}
10310276Ssam 	|	PASS SP password CRLF
10410276Ssam 		= {
10526494Sminshall 			pass((char *) $3);
10626494Sminshall 			free((char *) $3);
10710276Ssam 		}
10810276Ssam 	|	PORT SP host_port CRLF
10910276Ssam 		= {
11010320Ssam 			usedefault = 0;
11136304Skarels 			if (pdata >= 0) {
11226045Sminshall 				(void) close(pdata);
11336304Skarels 				pdata = -1;
11426045Sminshall 			}
11527107Smckusick 			reply(200, "PORT command successful.");
11610276Ssam 		}
11726045Sminshall 	|	PASV CRLF
11826045Sminshall 		= {
11926045Sminshall 			passive();
12026045Sminshall 		}
12110276Ssam 	|	TYPE SP type_code CRLF
12210276Ssam 		= {
12310276Ssam 			switch (cmd_type) {
12410276Ssam 
12510276Ssam 			case TYPE_A:
12610276Ssam 				if (cmd_form == FORM_N) {
12710276Ssam 					reply(200, "Type set to A.");
12810276Ssam 					type = cmd_type;
12910276Ssam 					form = cmd_form;
13010276Ssam 				} else
13110276Ssam 					reply(504, "Form must be N.");
13210276Ssam 				break;
13310276Ssam 
13410276Ssam 			case TYPE_E:
13510276Ssam 				reply(504, "Type E not implemented.");
13610276Ssam 				break;
13710276Ssam 
13810276Ssam 			case TYPE_I:
13910276Ssam 				reply(200, "Type set to I.");
14010276Ssam 				type = cmd_type;
14110276Ssam 				break;
14210276Ssam 
14310276Ssam 			case TYPE_L:
14436933Skarels #if NBBY == 8
14536933Skarels 				if (cmd_bytesz == 8) {
14610276Ssam 					reply(200,
14736933Skarels 					    "Type set to L (byte size 8).");
14810276Ssam 					type = cmd_type;
14910276Ssam 				} else
15036933Skarels 					reply(504, "Byte size must be 8.");
15136933Skarels #else /* NBBY == 8 */
15236933Skarels 				UNIMPLEMENTED for NBBY != 8
15336933Skarels #endif /* NBBY == 8 */
15410276Ssam 			}
15510276Ssam 		}
15610276Ssam 	|	STRU SP struct_code CRLF
15710276Ssam 		= {
15810276Ssam 			switch ($3) {
15910276Ssam 
16010276Ssam 			case STRU_F:
16110276Ssam 				reply(200, "STRU F ok.");
16210276Ssam 				break;
16310276Ssam 
16410276Ssam 			default:
16527107Smckusick 				reply(504, "Unimplemented STRU type.");
16610276Ssam 			}
16710276Ssam 		}
16810276Ssam 	|	MODE SP mode_code CRLF
16910276Ssam 		= {
17010276Ssam 			switch ($3) {
17110276Ssam 
17210276Ssam 			case MODE_S:
17310276Ssam 				reply(200, "MODE S ok.");
17410276Ssam 				break;
17510276Ssam 
17610276Ssam 			default:
17710276Ssam 				reply(502, "Unimplemented MODE type.");
17810276Ssam 			}
17910276Ssam 		}
18010276Ssam 	|	ALLO SP NUMBER CRLF
18110276Ssam 		= {
18227107Smckusick 			reply(202, "ALLO command ignored.");
18310276Ssam 		}
18436933Skarels 	|	ALLO SP NUMBER SP R SP NUMBER CRLF
18536933Skarels 		= {
18636933Skarels 			reply(202, "ALLO command ignored.");
18736933Skarels 		}
18810276Ssam 	|	RETR check_login SP pathname CRLF
18910276Ssam 		= {
19010302Ssam 			if ($2 && $4 != NULL)
19136933Skarels 				retrieve((char *) 0, (char *) $4);
19210302Ssam 			if ($4 != NULL)
19326494Sminshall 				free((char *) $4);
19410276Ssam 		}
19510276Ssam 	|	STOR check_login SP pathname CRLF
19610276Ssam 		= {
19710302Ssam 			if ($2 && $4 != NULL)
19836304Skarels 				store((char *) $4, "w", 0);
19910302Ssam 			if ($4 != NULL)
20026494Sminshall 				free((char *) $4);
20110276Ssam 		}
20210276Ssam 	|	APPE check_login SP pathname CRLF
20310276Ssam 		= {
20410302Ssam 			if ($2 && $4 != NULL)
20536304Skarels 				store((char *) $4, "a", 0);
20610302Ssam 			if ($4 != NULL)
20726494Sminshall 				free((char *) $4);
20810276Ssam 		}
20910276Ssam 	|	NLST check_login CRLF
21010276Ssam 		= {
21110276Ssam 			if ($2)
21236620Srick 				send_file_list(".");
21310276Ssam 		}
21436620Srick 	|	NLST check_login SP STRING CRLF
21510276Ssam 		= {
21636620Srick 			if ($2 && $4 != NULL)
21736620Srick 				send_file_list((char *) $4);
21810302Ssam 			if ($4 != NULL)
21926494Sminshall 				free((char *) $4);
22010276Ssam 		}
22110276Ssam 	|	LIST check_login CRLF
22210276Ssam 		= {
22310276Ssam 			if ($2)
22436620Srick 				retrieve("/bin/ls -lgA", "");
22510276Ssam 		}
22610276Ssam 	|	LIST check_login SP pathname CRLF
22710276Ssam 		= {
22810302Ssam 			if ($2 && $4 != NULL)
22936620Srick 				retrieve("/bin/ls -lgA %s", (char *) $4);
23010302Ssam 			if ($4 != NULL)
23126494Sminshall 				free((char *) $4);
23210276Ssam 		}
23336933Skarels 	|	STAT check_login SP pathname CRLF
23436933Skarels 		= {
23536933Skarels 			if ($2 && $4 != NULL)
23636933Skarels 				statfilecmd((char *) $4);
23736933Skarels 			if ($4 != NULL)
23836933Skarels 				free((char *) $4);
23936933Skarels 		}
24036933Skarels 	|	STAT CRLF
24136933Skarels 		= {
24236933Skarels 			statcmd();
24336933Skarels 		}
24410276Ssam 	|	DELE check_login SP pathname CRLF
24510276Ssam 		= {
24610302Ssam 			if ($2 && $4 != NULL)
24726494Sminshall 				delete((char *) $4);
24810302Ssam 			if ($4 != NULL)
24926494Sminshall 				free((char *) $4);
25010276Ssam 		}
25130945Scsvsj 	|	RNTO SP pathname CRLF
25230945Scsvsj 		= {
25330945Scsvsj 			if (fromname) {
25430945Scsvsj 				renamecmd(fromname, (char *) $3);
25530945Scsvsj 				free(fromname);
25630945Scsvsj 				fromname = (char *) 0;
25730945Scsvsj 			} else {
25830945Scsvsj 				reply(503, "Bad sequence of commands.");
25930945Scsvsj 			}
26030945Scsvsj 			free((char *) $3);
26130945Scsvsj 		}
26226045Sminshall 	|	ABOR CRLF
26326045Sminshall 		= {
26427107Smckusick 			reply(225, "ABOR command successful.");
26526045Sminshall 		}
26610276Ssam 	|	CWD check_login CRLF
26710276Ssam 		= {
26810276Ssam 			if ($2)
26910276Ssam 				cwd(pw->pw_dir);
27010276Ssam 		}
27110276Ssam 	|	CWD check_login SP pathname CRLF
27210276Ssam 		= {
27310302Ssam 			if ($2 && $4 != NULL)
27426494Sminshall 				cwd((char *) $4);
27510302Ssam 			if ($4 != NULL)
27626494Sminshall 				free((char *) $4);
27710276Ssam 		}
27810276Ssam 	|	HELP CRLF
27910276Ssam 		= {
28036933Skarels 			help(cmdtab, (char *) 0);
28110276Ssam 		}
28210276Ssam 	|	HELP SP STRING CRLF
28310276Ssam 		= {
28436933Skarels 			register char *cp = (char *)$3;
28536933Skarels 
28636933Skarels 			if (strncasecmp(cp, "SITE", 4) == 0) {
28736933Skarels 				cp = (char *)$3 + 4;
28836933Skarels 				if (*cp == ' ')
28936933Skarels 					cp++;
29036933Skarels 				if (*cp)
29136933Skarels 					help(sitetab, cp);
29236933Skarels 				else
29336933Skarels 					help(sitetab, (char *) 0);
29436933Skarels 			} else
29536933Skarels 				help(cmdtab, (char *) $3);
29610276Ssam 		}
29710276Ssam 	|	NOOP CRLF
29810276Ssam 		= {
29927107Smckusick 			reply(200, "NOOP command successful.");
30010276Ssam 		}
30136620Srick 	|	MKD check_login SP pathname CRLF
30210276Ssam 		= {
30310302Ssam 			if ($2 && $4 != NULL)
30426494Sminshall 				makedir((char *) $4);
30510302Ssam 			if ($4 != NULL)
30626494Sminshall 				free((char *) $4);
30710276Ssam 		}
30836620Srick 	|	RMD check_login SP pathname CRLF
30910276Ssam 		= {
31010302Ssam 			if ($2 && $4 != NULL)
31126494Sminshall 				removedir((char *) $4);
31210302Ssam 			if ($4 != NULL)
31326494Sminshall 				free((char *) $4);
31410276Ssam 		}
31536620Srick 	|	PWD check_login CRLF
31610276Ssam 		= {
31710276Ssam 			if ($2)
31810302Ssam 				pwd();
31910276Ssam 		}
32036620Srick 	|	CDUP check_login CRLF
32110276Ssam 		= {
32210276Ssam 			if ($2)
32310276Ssam 				cwd("..");
32410276Ssam 		}
32536933Skarels 	|	SITE SP HELP CRLF
32636933Skarels 		= {
32736933Skarels 			help(sitetab, (char *) 0);
32836933Skarels 		}
32936933Skarels 	|	SITE SP HELP SP STRING CRLF
33036933Skarels 		= {
33136933Skarels 			help(sitetab, (char *) $5);
33236933Skarels 		}
33336933Skarels 	|	SITE SP UMASK check_login CRLF
33436933Skarels 		= {
33536933Skarels 			int oldmask;
33636933Skarels 
33736933Skarels 			if ($4) {
33836933Skarels 				oldmask = umask(0);
33936933Skarels 				(void) umask(oldmask);
34036933Skarels 				reply(200, "Current UMASK is %03o", oldmask);
34136933Skarels 			}
34236933Skarels 		}
34336933Skarels 	|	SITE SP UMASK check_login SP octal_number CRLF
34436933Skarels 		= {
34536933Skarels 			int oldmask;
34636933Skarels 
34736933Skarels 			if ($4) {
34836933Skarels 				if (($6 == -1) || ($6 > 0777)) {
34936933Skarels 					reply(501, "Bad UMASK value");
35036933Skarels 				} else {
35136933Skarels 					oldmask = umask($6);
35236933Skarels 					reply(200,
35336933Skarels 					    "UMASK set to %03o (was %03o)",
35436933Skarels 					    $6, oldmask);
35536933Skarels 				}
35636933Skarels 			}
35736933Skarels 		}
35836933Skarels 	|	SITE SP CHMOD check_login SP octal_number SP pathname CRLF
35936933Skarels 		= {
36036933Skarels 			if ($4 && ($8 != NULL)) {
36136933Skarels 				if ($6 > 0777)
36236933Skarels 					reply(501,
36336933Skarels 				"CHMOD: Mode value must be between 0 and 0777");
36436933Skarels 				else if (chmod((char *) $8, $6) < 0)
36536933Skarels 					perror_reply(550, (char *) $8);
36636933Skarels 				else
36736933Skarels 					reply(200, "CHMOD command successful.");
36836933Skarels 			}
36936933Skarels 			if ($8 != NULL)
37036933Skarels 				free((char *) $8);
37136933Skarels 		}
37236933Skarels 	|	SITE SP IDLE CRLF
37336933Skarels 		= {
37436933Skarels 			reply(200,
37536933Skarels 			    "Current IDLE time limit is %d seconds; max %d",
37636933Skarels 				timeout, maxtimeout);
37736933Skarels 		}
37836933Skarels 	|	SITE SP IDLE SP NUMBER CRLF
37936933Skarels 		= {
38036933Skarels 			if ($5 < 30 || $5 > maxtimeout) {
38136933Skarels 				reply(501,
38236933Skarels 			"Maximum IDLE time must be between 30 and %d seconds",
38336933Skarels 				    maxtimeout);
38436933Skarels 			} else {
38536933Skarels 				timeout = $5;
38636933Skarels 				(void) alarm((unsigned) timeout);
38736933Skarels 				reply(200,
38836933Skarels 				    "Maximum IDLE time set to %d seconds",
38936933Skarels 				    timeout);
39036933Skarels 			}
39136933Skarels 		}
39226045Sminshall 	|	STOU check_login SP pathname CRLF
39326045Sminshall 		= {
39436304Skarels 			if ($2 && $4 != NULL)
39536304Skarels 				store((char *) $4, "w", 1);
39626045Sminshall 			if ($4 != NULL)
39726494Sminshall 				free((char *) $4);
39826045Sminshall 		}
39936552Sbostic 	|	SYST CRLF
40036552Sbostic 		= {
40136640Srick #ifdef unix
40236933Skarels #ifdef BSD
40336552Sbostic 			reply(215, "UNIX Type: L%d Version: BSD-%d",
40436552Sbostic 				NBBY, BSD);
40536933Skarels #else /* BSD */
40636933Skarels 			reply(215, "UNIX Type: L%d", NBBY);
40736933Skarels #endif /* BSD */
40836933Skarels #else /* unix */
40936933Skarels 			reply(215, "UNKNOWN Type: L%d", NBBY);
41036933Skarels #endif /* unix */
41136552Sbostic 		}
41236933Skarels 
41336933Skarels 		/*
41436933Skarels 		 * SIZE is not in RFC959, but Postel has blessed it and
41536933Skarels 		 * it will be in the updated RFC.
41636933Skarels 		 *
41736933Skarels 		 * Return size of file in a format suitable for
41836933Skarels 		 * using with RESTART (we just count bytes).
41936933Skarels 		 */
42036933Skarels 	|	SIZE check_login SP pathname CRLF
42136933Skarels 		= {
42236933Skarels 			if ($2 && $4 != NULL)
42336933Skarels 				sizecmd((char *) $4);
42436933Skarels 			if ($4 != NULL)
42536933Skarels 				free((char *) $4);
42636933Skarels 		}
42736933Skarels 
42836933Skarels 		/*
42936933Skarels 		 * MDTM is not in RFC959, but Postel has blessed it and
43036933Skarels 		 * it will be in the updated RFC.
43136933Skarels 		 *
43236933Skarels 		 * Return modification time of file as an ISO 3307
43336933Skarels 		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
43436933Skarels 		 * where xxx is the fractional second (of any precision,
43536933Skarels 		 * not necessarily 3 digits)
43636933Skarels 		 */
43736933Skarels 	|	MDTM check_login SP pathname CRLF
43836933Skarels 		= {
43936933Skarels 			if ($2 && $4 != NULL) {
44036933Skarels 				struct stat stbuf;
44136933Skarels 				if (stat((char *) $4, &stbuf) < 0)
44236933Skarels 					perror_reply(550, "%s", (char *) $4);
44336933Skarels 				else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
44436933Skarels 					reply(550, "%s: not a plain file.",
44536933Skarels 						(char *) $4);
44636933Skarels 				} else {
44736933Skarels 					register struct tm *t;
44836933Skarels 					struct tm *gmtime();
44936933Skarels 					t = gmtime(&stbuf.st_mtime);
45036933Skarels 					reply(213,
45136933Skarels 					    "19%02d%02d%02d%02d%02d%02d",
45236933Skarels 					    t->tm_year, t->tm_mon+1, t->tm_mday,
45336933Skarels 					    t->tm_hour, t->tm_min, t->tm_sec);
45436933Skarels 				}
45536933Skarels 			}
45636933Skarels 			if ($4 != NULL)
45736933Skarels 				free((char *) $4);
45836933Skarels 		}
45910276Ssam 	|	QUIT CRLF
46010276Ssam 		= {
46110276Ssam 			reply(221, "Goodbye.");
46213246Ssam 			dologout(0);
46310276Ssam 		}
46410276Ssam 	|	error CRLF
46510276Ssam 		= {
46610276Ssam 			yyerrok;
46710276Ssam 		}
46810276Ssam 	;
46930945Scsvsj rcmd:		RNFR check_login SP pathname CRLF
47030945Scsvsj 		= {
47130945Scsvsj 			char *renamefrom();
47230945Scsvsj 
47338135Srick 			restart_point = (off_t) 0;
47430945Scsvsj 			if ($2 && $4) {
47530945Scsvsj 				fromname = renamefrom((char *) $4);
47630945Scsvsj 				if (fromname == (char *) 0 && $4) {
47730945Scsvsj 					free((char *) $4);
47830945Scsvsj 				}
47930945Scsvsj 			}
48030945Scsvsj 		}
48138135Srick 	|	REST SP byte_size CRLF
48238135Srick 		= {
48338135Srick 			long atol();
48438135Srick 
48538135Srick 			fromname = (char *) 0;
48638135Srick 			restart_point = $3;
48738135Srick 			reply(350, "Restarting at %ld. %s", restart_point,
48838135Srick 			    "Send STORE or RETRIEVE to initiate transfer.");
48938135Srick 		}
49030945Scsvsj 	;
49130945Scsvsj 
49210276Ssam username:	STRING
49310276Ssam 	;
49410276Ssam 
49536304Skarels password:	/* empty */
49636304Skarels 		= {
49740184Smckusick 			*(char **)&($$) = (char *)calloc(1, sizeof(char));
49836304Skarels 		}
49936304Skarels 	|	STRING
50010276Ssam 	;
50110276Ssam 
50210276Ssam byte_size:	NUMBER
50310276Ssam 	;
50410276Ssam 
50510276Ssam host_port:	NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
50610276Ssam 		NUMBER COMMA NUMBER
50710276Ssam 		= {
50810276Ssam 			register char *a, *p;
50910276Ssam 
51010276Ssam 			a = (char *)&data_dest.sin_addr;
51110276Ssam 			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
51210276Ssam 			p = (char *)&data_dest.sin_port;
51310276Ssam 			p[0] = $9; p[1] = $11;
51410324Ssam 			data_dest.sin_family = AF_INET;
51510276Ssam 		}
51610276Ssam 	;
51710276Ssam 
51810276Ssam form_code:	N
51910276Ssam 	= {
52010276Ssam 		$$ = FORM_N;
52110276Ssam 	}
52210276Ssam 	|	T
52310276Ssam 	= {
52410276Ssam 		$$ = FORM_T;
52510276Ssam 	}
52610276Ssam 	|	C
52710276Ssam 	= {
52810276Ssam 		$$ = FORM_C;
52910276Ssam 	}
53010276Ssam 	;
53110276Ssam 
53210276Ssam type_code:	A
53310276Ssam 	= {
53410276Ssam 		cmd_type = TYPE_A;
53510276Ssam 		cmd_form = FORM_N;
53610276Ssam 	}
53710276Ssam 	|	A SP form_code
53810276Ssam 	= {
53910276Ssam 		cmd_type = TYPE_A;
54010276Ssam 		cmd_form = $3;
54110276Ssam 	}
54210276Ssam 	|	E
54310276Ssam 	= {
54410276Ssam 		cmd_type = TYPE_E;
54510276Ssam 		cmd_form = FORM_N;
54610276Ssam 	}
54710276Ssam 	|	E SP form_code
54810276Ssam 	= {
54910276Ssam 		cmd_type = TYPE_E;
55010276Ssam 		cmd_form = $3;
55110276Ssam 	}
55210276Ssam 	|	I
55310276Ssam 	= {
55410276Ssam 		cmd_type = TYPE_I;
55510276Ssam 	}
55610276Ssam 	|	L
55710276Ssam 	= {
55810276Ssam 		cmd_type = TYPE_L;
55936552Sbostic 		cmd_bytesz = NBBY;
56010276Ssam 	}
56110276Ssam 	|	L SP byte_size
56210276Ssam 	= {
56310276Ssam 		cmd_type = TYPE_L;
56410276Ssam 		cmd_bytesz = $3;
56510276Ssam 	}
56610276Ssam 	/* this is for a bug in the BBN ftp */
56710276Ssam 	|	L byte_size
56810276Ssam 	= {
56910276Ssam 		cmd_type = TYPE_L;
57010276Ssam 		cmd_bytesz = $2;
57110276Ssam 	}
57210276Ssam 	;
57310276Ssam 
57410276Ssam struct_code:	F
57510276Ssam 	= {
57610276Ssam 		$$ = STRU_F;
57710276Ssam 	}
57810276Ssam 	|	R
57910276Ssam 	= {
58010276Ssam 		$$ = STRU_R;
58110276Ssam 	}
58210276Ssam 	|	P
58310276Ssam 	= {
58410276Ssam 		$$ = STRU_P;
58510276Ssam 	}
58610276Ssam 	;
58710276Ssam 
58810276Ssam mode_code:	S
58910276Ssam 	= {
59010276Ssam 		$$ = MODE_S;
59110276Ssam 	}
59210276Ssam 	|	B
59310276Ssam 	= {
59410276Ssam 		$$ = MODE_B;
59510276Ssam 	}
59610276Ssam 	|	C
59710276Ssam 	= {
59810276Ssam 		$$ = MODE_C;
59910276Ssam 	}
60010276Ssam 	;
60110276Ssam 
60210276Ssam pathname:	pathstring
60310276Ssam 	= {
60427107Smckusick 		/*
60527107Smckusick 		 * Problem: this production is used for all pathname
60627107Smckusick 		 * processing, but only gives a 550 error reply.
60727107Smckusick 		 * This is a valid reply in some cases but not in others.
60827107Smckusick 		 */
60936304Skarels 		if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) {
61036933Skarels 			*(char **)&($$) = *glob((char *) $1);
61110302Ssam 			if (globerr != NULL) {
61210276Ssam 				reply(550, globerr);
61310302Ssam 				$$ = NULL;
61410302Ssam 			}
61526494Sminshall 			free((char *) $1);
61610276Ssam 		} else
61710276Ssam 			$$ = $1;
61810276Ssam 	}
61910276Ssam 	;
62010276Ssam 
62110276Ssam pathstring:	STRING
62210276Ssam 	;
62310276Ssam 
62436933Skarels octal_number:	NUMBER
62536933Skarels 	= {
62636933Skarels 		register int ret, dec, multby, digit;
62736933Skarels 
62836933Skarels 		/*
62936933Skarels 		 * Convert a number that was read as decimal number
63036933Skarels 		 * to what it would be if it had been read as octal.
63136933Skarels 		 */
63236933Skarels 		dec = $1;
63336933Skarels 		multby = 1;
63436933Skarels 		ret = 0;
63536933Skarels 		while (dec) {
63636933Skarels 			digit = dec%10;
63736933Skarels 			if (digit > 7) {
63836933Skarels 				ret = -1;
63936933Skarels 				break;
64036933Skarels 			}
64136933Skarels 			ret += digit * multby;
64236933Skarels 			multby *= 8;
64336933Skarels 			dec /= 10;
64436933Skarels 		}
64536933Skarels 		$$ = ret;
64636933Skarels 	}
64736933Skarels 	;
64836933Skarels 
64910276Ssam check_login:	/* empty */
65010276Ssam 	= {
65110276Ssam 		if (logged_in)
65210276Ssam 			$$ = 1;
65310276Ssam 		else {
65410276Ssam 			reply(530, "Please login with USER and PASS.");
65510276Ssam 			$$ = 0;
65610276Ssam 		}
65710276Ssam 	}
65810276Ssam 	;
65910276Ssam 
66010276Ssam %%
66110276Ssam 
66210276Ssam extern jmp_buf errcatch;
66310276Ssam 
66410276Ssam #define	CMD	0	/* beginning of command */
66510276Ssam #define	ARGS	1	/* expect miscellaneous arguments */
66610276Ssam #define	STR1	2	/* expect SP followed by STRING */
66710276Ssam #define	STR2	3	/* expect STRING */
66836304Skarels #define	OSTR	4	/* optional SP then STRING */
66936304Skarels #define	ZSTR1	5	/* SP then optional STRING */
67036304Skarels #define	ZSTR2	6	/* optional STRING after SP */
67136933Skarels #define	SITECMD	7	/* SITE command */
67236933Skarels #define	NSTR	8	/* Number followed by a string */
67310276Ssam 
67410276Ssam struct tab {
67510276Ssam 	char	*name;
67610276Ssam 	short	token;
67710276Ssam 	short	state;
67810276Ssam 	short	implemented;	/* 1 if command is implemented */
67910276Ssam 	char	*help;
68010276Ssam };
68110276Ssam 
68210276Ssam struct tab cmdtab[] = {		/* In order defined in RFC 765 */
68310276Ssam 	{ "USER", USER, STR1, 1,	"<sp> username" },
68436304Skarels 	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
68510276Ssam 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
68636933Skarels 	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
68710276Ssam 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
68810276Ssam 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
68910276Ssam 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
69026045Sminshall 	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
69110276Ssam 	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
69210276Ssam 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
69310276Ssam 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
69410276Ssam 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
69510276Ssam 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
69610276Ssam 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
69710276Ssam 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
69810276Ssam 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
69910276Ssam 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
70010276Ssam 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
70110276Ssam 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
70210276Ssam 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
70310276Ssam 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
70410276Ssam 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
70538135Srick 	{ "REST", REST, ARGS, 1,	"(restart command)" },
70610276Ssam 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
70710276Ssam 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
70826045Sminshall 	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
70910276Ssam 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
71036304Skarels 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
71110276Ssam 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
71210276Ssam 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
71310276Ssam 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
71436933Skarels 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
71536552Sbostic 	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
71636933Skarels 	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
71710276Ssam 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
71810276Ssam 	{ "NOOP", NOOP, ARGS, 1,	"" },
71936620Srick 	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
72036620Srick 	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
72136620Srick 	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
72236620Srick 	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
72336620Srick 	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
72436620Srick 	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
72536620Srick 	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
72636620Srick 	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
72726045Sminshall 	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
72836933Skarels 	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
72936933Skarels 	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
73010276Ssam 	{ NULL,   0,    0,    0,	0 }
73110276Ssam };
73210276Ssam 
73336933Skarels struct tab sitetab[] = {
73436933Skarels 	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
73536933Skarels 	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
73636933Skarels 	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
73736933Skarels 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
73836933Skarels 	{ NULL,   0,    0,    0,	0 }
73936933Skarels };
74036933Skarels 
74110276Ssam struct tab *
74236933Skarels lookup(p, cmd)
74336933Skarels 	register struct tab *p;
74410276Ssam 	char *cmd;
74510276Ssam {
74610276Ssam 
74736933Skarels 	for (; p->name != NULL; p++)
74810276Ssam 		if (strcmp(cmd, p->name) == 0)
74910276Ssam 			return (p);
75010276Ssam 	return (0);
75110276Ssam }
75210276Ssam 
75313033Ssam #include <arpa/telnet.h>
75410276Ssam 
75510276Ssam /*
75610276Ssam  * getline - a hacked up version of fgets to ignore TELNET escape codes.
75710276Ssam  */
75810276Ssam char *
75910276Ssam getline(s, n, iop)
76010276Ssam 	char *s;
76110276Ssam 	register FILE *iop;
76210276Ssam {
76310276Ssam 	register c;
76426494Sminshall 	register char *cs;
76510276Ssam 
76610276Ssam 	cs = s;
76727751Sminshall /* tmpline may contain saved command from urgent mode interruption */
76826045Sminshall 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
76926045Sminshall 		*cs++ = tmpline[c];
77026045Sminshall 		if (tmpline[c] == '\n') {
77126045Sminshall 			*cs++ = '\0';
77236304Skarels 			if (debug)
77336304Skarels 				syslog(LOG_DEBUG, "command: %s", s);
77426045Sminshall 			tmpline[0] = '\0';
77526045Sminshall 			return(s);
77626045Sminshall 		}
77736304Skarels 		if (c == 0)
77826045Sminshall 			tmpline[0] = '\0';
77926045Sminshall 	}
78036304Skarels 	while ((c = getc(iop)) != EOF) {
78136304Skarels 		c &= 0377;
78236304Skarels 		if (c == IAC) {
78336304Skarels 		    if ((c = getc(iop)) != EOF) {
78436304Skarels 			c &= 0377;
78536304Skarels 			switch (c) {
78627751Sminshall 			case WILL:
78727751Sminshall 			case WONT:
78836277Sbostic 				c = getc(iop);
78936304Skarels 				printf("%c%c%c", IAC, DONT, 0377&c);
79027751Sminshall 				(void) fflush(stdout);
79136304Skarels 				continue;
79227751Sminshall 			case DO:
79327751Sminshall 			case DONT:
79436277Sbostic 				c = getc(iop);
79536304Skarels 				printf("%c%c%c", IAC, WONT, 0377&c);
79627751Sminshall 				(void) fflush(stdout);
79736304Skarels 				continue;
79836304Skarels 			case IAC:
79927751Sminshall 				break;
80027751Sminshall 			default:
80136304Skarels 				continue;	/* ignore command */
80227751Sminshall 			}
80336304Skarels 		    }
80410276Ssam 		}
80536304Skarels 		*cs++ = c;
80636304Skarels 		if (--n <= 0 || c == '\n')
80710276Ssam 			break;
80810276Ssam 	}
80927751Sminshall 	if (c == EOF && cs == s)
81018303Sralph 		return (NULL);
81110276Ssam 	*cs++ = '\0';
81236304Skarels 	if (debug)
81336304Skarels 		syslog(LOG_DEBUG, "command: %s", s);
81410276Ssam 	return (s);
81510276Ssam }
81610276Ssam 
81711652Ssam static int
81811652Ssam toolong()
81911652Ssam {
82026494Sminshall 	time_t now;
82111652Ssam 	extern char *ctime();
82226494Sminshall 	extern time_t time();
82311652Ssam 
82411652Ssam 	reply(421,
82511652Ssam 	  "Timeout (%d seconds): closing control connection.", timeout);
82626494Sminshall 	(void) time(&now);
82711652Ssam 	if (logging) {
82826494Sminshall 		syslog(LOG_INFO,
82936304Skarels 			"User %s timed out after %d seconds at %s",
83011652Ssam 			(pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
83111652Ssam 	}
83213246Ssam 	dologout(1);
83311652Ssam }
83411652Ssam 
83510276Ssam yylex()
83610276Ssam {
83710276Ssam 	static int cpos, state;
83836933Skarels 	register char *cp, *cp2;
83910276Ssam 	register struct tab *p;
84010276Ssam 	int n;
84136277Sbostic 	char c, *strpbrk();
84236933Skarels 	char *copy();
84310276Ssam 
84410276Ssam 	for (;;) {
84510276Ssam 		switch (state) {
84610276Ssam 
84710276Ssam 		case CMD:
84826494Sminshall 			(void) signal(SIGALRM, toolong);
84926494Sminshall 			(void) alarm((unsigned) timeout);
85010276Ssam 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
85110276Ssam 				reply(221, "You could at least say goodbye.");
85213246Ssam 				dologout(0);
85310276Ssam 			}
85426494Sminshall 			(void) alarm(0);
85536620Srick #ifdef SETPROCTITLE
85636933Skarels 			if (strncasecmp(cbuf, "PASS", 4) != NULL)
85736933Skarels 				setproctitle("%s: %s", proctitle, cbuf);
85836620Srick #endif /* SETPROCTITLE */
85936324Sbostic 			if ((cp = index(cbuf, '\r'))) {
86036324Sbostic 				*cp++ = '\n';
86136324Sbostic 				*cp = '\0';
86236324Sbostic 			}
86336277Sbostic 			if ((cp = strpbrk(cbuf, " \n")))
86436277Sbostic 				cpos = cp - cbuf;
86536304Skarels 			if (cpos == 0)
86610276Ssam 				cpos = 4;
86710276Ssam 			c = cbuf[cpos];
86810276Ssam 			cbuf[cpos] = '\0';
86910276Ssam 			upper(cbuf);
87036933Skarels 			p = lookup(cmdtab, cbuf);
87110276Ssam 			cbuf[cpos] = c;
87210276Ssam 			if (p != 0) {
87310276Ssam 				if (p->implemented == 0) {
87410276Ssam 					nack(p->name);
87526494Sminshall 					longjmp(errcatch,0);
87610276Ssam 					/* NOTREACHED */
87710276Ssam 				}
87810276Ssam 				state = p->state;
87936933Skarels 				*(char **)&yylval = p->name;
88010276Ssam 				return (p->token);
88110276Ssam 			}
88210276Ssam 			break;
88310276Ssam 
88436933Skarels 		case SITECMD:
88536933Skarels 			if (cbuf[cpos] == ' ') {
88636933Skarels 				cpos++;
88736933Skarels 				return (SP);
88836933Skarels 			}
88936933Skarels 			cp = &cbuf[cpos];
89036933Skarels 			if ((cp2 = strpbrk(cp, " \n")))
89136933Skarels 				cpos = cp2 - cbuf;
89236933Skarels 			c = cbuf[cpos];
89336933Skarels 			cbuf[cpos] = '\0';
89436933Skarels 			upper(cp);
89536933Skarels 			p = lookup(sitetab, cp);
89636933Skarels 			cbuf[cpos] = c;
89736933Skarels 			if (p != 0) {
89836933Skarels 				if (p->implemented == 0) {
89936933Skarels 					state = CMD;
90036933Skarels 					nack(p->name);
90136933Skarels 					longjmp(errcatch,0);
90236933Skarels 					/* NOTREACHED */
90336933Skarels 				}
90436933Skarels 				state = p->state;
90536933Skarels 				*(char **)&yylval = p->name;
90636933Skarels 				return (p->token);
90736933Skarels 			}
90836933Skarels 			state = CMD;
90936933Skarels 			break;
91036933Skarels 
91110276Ssam 		case OSTR:
91210276Ssam 			if (cbuf[cpos] == '\n') {
91310276Ssam 				state = CMD;
91410276Ssam 				return (CRLF);
91510276Ssam 			}
91636317Sbostic 			/* FALLTHROUGH */
91710276Ssam 
91810276Ssam 		case STR1:
91936304Skarels 		case ZSTR1:
92036933Skarels 		dostr1:
92110276Ssam 			if (cbuf[cpos] == ' ') {
92210276Ssam 				cpos++;
92336317Sbostic 				state = state == OSTR ? STR2 : ++state;
92410276Ssam 				return (SP);
92510276Ssam 			}
92610276Ssam 			break;
92710276Ssam 
92836304Skarels 		case ZSTR2:
92936304Skarels 			if (cbuf[cpos] == '\n') {
93036304Skarels 				state = CMD;
93136304Skarels 				return (CRLF);
93236304Skarels 			}
93336933Skarels 			/* FALLTHROUGH */
93436304Skarels 
93510276Ssam 		case STR2:
93610276Ssam 			cp = &cbuf[cpos];
93710276Ssam 			n = strlen(cp);
93810276Ssam 			cpos += n - 1;
93910276Ssam 			/*
94010276Ssam 			 * Make sure the string is nonempty and \n terminated.
94110276Ssam 			 */
94210276Ssam 			if (n > 1 && cbuf[cpos] == '\n') {
94310276Ssam 				cbuf[cpos] = '\0';
94436933Skarels 				*(char **)&yylval = copy(cp);
94510276Ssam 				cbuf[cpos] = '\n';
94610276Ssam 				state = ARGS;
94710276Ssam 				return (STRING);
94810276Ssam 			}
94910276Ssam 			break;
95010276Ssam 
95136933Skarels 		case NSTR:
95236933Skarels 			if (cbuf[cpos] == ' ') {
95336933Skarels 				cpos++;
95436933Skarels 				return (SP);
95536933Skarels 			}
95636933Skarels 			if (isdigit(cbuf[cpos])) {
95736933Skarels 				cp = &cbuf[cpos];
95836933Skarels 				while (isdigit(cbuf[++cpos]))
95936933Skarels 					;
96036933Skarels 				c = cbuf[cpos];
96136933Skarels 				cbuf[cpos] = '\0';
96236933Skarels 				yylval = atoi(cp);
96336933Skarels 				cbuf[cpos] = c;
96436933Skarels 				state = STR1;
96536933Skarels 				return (NUMBER);
96636933Skarels 			}
96736933Skarels 			state = STR1;
96836933Skarels 			goto dostr1;
96936933Skarels 
97010276Ssam 		case ARGS:
97110276Ssam 			if (isdigit(cbuf[cpos])) {
97210276Ssam 				cp = &cbuf[cpos];
97310276Ssam 				while (isdigit(cbuf[++cpos]))
97410276Ssam 					;
97510276Ssam 				c = cbuf[cpos];
97610276Ssam 				cbuf[cpos] = '\0';
97710276Ssam 				yylval = atoi(cp);
97810276Ssam 				cbuf[cpos] = c;
97910276Ssam 				return (NUMBER);
98010276Ssam 			}
98110276Ssam 			switch (cbuf[cpos++]) {
98210276Ssam 
98310276Ssam 			case '\n':
98410276Ssam 				state = CMD;
98510276Ssam 				return (CRLF);
98610276Ssam 
98710276Ssam 			case ' ':
98810276Ssam 				return (SP);
98910276Ssam 
99010276Ssam 			case ',':
99110276Ssam 				return (COMMA);
99210276Ssam 
99310276Ssam 			case 'A':
99410276Ssam 			case 'a':
99510276Ssam 				return (A);
99610276Ssam 
99710276Ssam 			case 'B':
99810276Ssam 			case 'b':
99910276Ssam 				return (B);
100010276Ssam 
100110276Ssam 			case 'C':
100210276Ssam 			case 'c':
100310276Ssam 				return (C);
100410276Ssam 
100510276Ssam 			case 'E':
100610276Ssam 			case 'e':
100710276Ssam 				return (E);
100810276Ssam 
100910276Ssam 			case 'F':
101010276Ssam 			case 'f':
101110276Ssam 				return (F);
101210276Ssam 
101310276Ssam 			case 'I':
101410276Ssam 			case 'i':
101510276Ssam 				return (I);
101610276Ssam 
101710276Ssam 			case 'L':
101810276Ssam 			case 'l':
101910276Ssam 				return (L);
102010276Ssam 
102110276Ssam 			case 'N':
102210276Ssam 			case 'n':
102310276Ssam 				return (N);
102410276Ssam 
102510276Ssam 			case 'P':
102610276Ssam 			case 'p':
102710276Ssam 				return (P);
102810276Ssam 
102910276Ssam 			case 'R':
103010276Ssam 			case 'r':
103110276Ssam 				return (R);
103210276Ssam 
103310276Ssam 			case 'S':
103410276Ssam 			case 's':
103510276Ssam 				return (S);
103610276Ssam 
103710276Ssam 			case 'T':
103810276Ssam 			case 't':
103910276Ssam 				return (T);
104010276Ssam 
104110276Ssam 			}
104210276Ssam 			break;
104310276Ssam 
104410276Ssam 		default:
104510276Ssam 			fatal("Unknown state in scanner.");
104610276Ssam 		}
104726494Sminshall 		yyerror((char *) 0);
104810276Ssam 		state = CMD;
104926494Sminshall 		longjmp(errcatch,0);
105010276Ssam 	}
105110276Ssam }
105210276Ssam 
105310276Ssam upper(s)
105436277Sbostic 	register char *s;
105510276Ssam {
105610276Ssam 	while (*s != '\0') {
105710276Ssam 		if (islower(*s))
105810276Ssam 			*s = toupper(*s);
105910276Ssam 		s++;
106010276Ssam 	}
106110276Ssam }
106210276Ssam 
106336933Skarels char *
106410276Ssam copy(s)
106510276Ssam 	char *s;
106610276Ssam {
106710276Ssam 	char *p;
106826494Sminshall 	extern char *malloc(), *strcpy();
106910276Ssam 
107026494Sminshall 	p = malloc((unsigned) strlen(s) + 1);
107110276Ssam 	if (p == NULL)
107210276Ssam 		fatal("Ran out of memory.");
107326494Sminshall 	(void) strcpy(p, s);
107436933Skarels 	return (p);
107510276Ssam }
107610276Ssam 
107736933Skarels help(ctab, s)
107836933Skarels 	struct tab *ctab;
107910276Ssam 	char *s;
108010276Ssam {
108110276Ssam 	register struct tab *c;
108210276Ssam 	register int width, NCMDS;
108336933Skarels 	char *type;
108410276Ssam 
108536933Skarels 	if (ctab == sitetab)
108636933Skarels 		type = "SITE ";
108736933Skarels 	else
108836933Skarels 		type = "";
108910276Ssam 	width = 0, NCMDS = 0;
109036933Skarels 	for (c = ctab; c->name != NULL; c++) {
109136933Skarels 		int len = strlen(c->name);
109210276Ssam 
109310276Ssam 		if (len > width)
109410276Ssam 			width = len;
109510276Ssam 		NCMDS++;
109610276Ssam 	}
109710276Ssam 	width = (width + 8) &~ 7;
109810276Ssam 	if (s == 0) {
109910276Ssam 		register int i, j, w;
110010276Ssam 		int columns, lines;
110110276Ssam 
110236933Skarels 		lreply(214, "The following %scommands are recognized %s.",
110336933Skarels 		    type, "(* =>'s unimplemented)");
110410276Ssam 		columns = 76 / width;
110510276Ssam 		if (columns == 0)
110610276Ssam 			columns = 1;
110710276Ssam 		lines = (NCMDS + columns - 1) / columns;
110810276Ssam 		for (i = 0; i < lines; i++) {
110927107Smckusick 			printf("   ");
111010276Ssam 			for (j = 0; j < columns; j++) {
111136933Skarels 				c = ctab + j * lines + i;
111210276Ssam 				printf("%s%c", c->name,
111310276Ssam 					c->implemented ? ' ' : '*');
111436933Skarels 				if (c + lines >= &ctab[NCMDS])
111510276Ssam 					break;
111631132Smckusick 				w = strlen(c->name) + 1;
111710276Ssam 				while (w < width) {
111810276Ssam 					putchar(' ');
111910276Ssam 					w++;
112010276Ssam 				}
112110276Ssam 			}
112210276Ssam 			printf("\r\n");
112310276Ssam 		}
112426494Sminshall 		(void) fflush(stdout);
112510276Ssam 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
112610276Ssam 		return;
112710276Ssam 	}
112810276Ssam 	upper(s);
112936933Skarels 	c = lookup(ctab, s);
113010276Ssam 	if (c == (struct tab *)0) {
113127107Smckusick 		reply(502, "Unknown command %s.", s);
113210276Ssam 		return;
113310276Ssam 	}
113410276Ssam 	if (c->implemented)
113536933Skarels 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
113610276Ssam 	else
113736933Skarels 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
113836933Skarels 		    c->name, c->help);
113910276Ssam }
114036933Skarels 
114136933Skarels sizecmd(filename)
114236933Skarels char *filename;
114336933Skarels {
114436933Skarels 	switch (type) {
114536933Skarels 	case TYPE_L:
114636933Skarels 	case TYPE_I: {
114736933Skarels 		struct stat stbuf;
114836933Skarels 		if (stat(filename, &stbuf) < 0 ||
114936933Skarels 		    (stbuf.st_mode&S_IFMT) != S_IFREG)
115036933Skarels 			reply(550, "%s: not a plain file.", filename);
115136933Skarels 		else
115236933Skarels 			reply(213, "%lu", stbuf.st_size);
115336933Skarels 		break;}
115436933Skarels 	case TYPE_A: {
115536933Skarels 		FILE *fin;
115638135Srick 		register int c;
115738135Srick 		register long count;
115836933Skarels 		struct stat stbuf;
115936933Skarels 		fin = fopen(filename, "r");
116036933Skarels 		if (fin == NULL) {
116136933Skarels 			perror_reply(550, filename);
116236933Skarels 			return;
116336933Skarels 		}
116436933Skarels 		if (fstat(fileno(fin), &stbuf) < 0 ||
116536933Skarels 		    (stbuf.st_mode&S_IFMT) != S_IFREG) {
116636933Skarels 			reply(550, "%s: not a plain file.", filename);
116736933Skarels 			(void) fclose(fin);
116836933Skarels 			return;
116936933Skarels 		}
117036933Skarels 
117136933Skarels 		count = 0;
117236933Skarels 		while((c=getc(fin)) != EOF) {
117336933Skarels 			if (c == '\n')	/* will get expanded to \r\n */
117436933Skarels 				count++;
117536933Skarels 			count++;
117636933Skarels 		}
117736933Skarels 		(void) fclose(fin);
117836933Skarels 
117936933Skarels 		reply(213, "%ld", count);
118036933Skarels 		break;}
118136933Skarels 	default:
118236933Skarels 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
118336933Skarels 	}
118436933Skarels }
1185