xref: /csrg-svn/libexec/ftpd/ftpcmd.y (revision 66711)
110276Ssam /*
2*66711Spendry  * Copyright (c) 1985, 1988, 1993, 1994
361425Sbostic  *	The Regents of the University of California.  All rights reserved.
433738Sbostic  *
542666Sbostic  * %sccs.include.redist.c%
634769Sbostic  *
7*66711Spendry  *	@(#)ftpcmd.y	8.2 (Berkeley) 04/04/94
822501Sdist  */
922501Sdist 
1022501Sdist /*
1110276Ssam  * Grammar for FTP commands.
1236933Skarels  * See RFC 959.
1310276Ssam  */
1410276Ssam 
1510276Ssam %{
1610276Ssam 
1710276Ssam #ifndef lint
18*66711Spendry static char sccsid[] = "@(#)ftpcmd.y	8.2 (Berkeley) 04/04/94";
1933738Sbostic #endif /* not lint */
2010276Ssam 
2136552Sbostic #include <sys/param.h>
2210276Ssam #include <sys/socket.h>
2346669Sbostic #include <sys/stat.h>
2454528Sandrew 
2510276Ssam #include <netinet/in.h>
2613033Ssam #include <arpa/ftp.h>
2754528Sandrew 
28*66711Spendry #include <ctype.h>
29*66711Spendry #include <errno.h>
30*66711Spendry #include <glob.h>
31*66711Spendry #include <pwd.h>
32*66711Spendry #include <setjmp.h>
3311652Ssam #include <signal.h>
34*66711Spendry #include <stdio.h>
35*66711Spendry #include <stdlib.h>
36*66711Spendry #include <string.h>
3726494Sminshall #include <syslog.h>
3836933Skarels #include <time.h>
3946669Sbostic #include <unistd.h>
40*66711Spendry 
4154528Sandrew #include "extern.h"
4210276Ssam 
4310276Ssam extern	struct sockaddr_in data_dest;
4410276Ssam extern	int logged_in;
4510276Ssam extern	struct passwd *pw;
4610276Ssam extern	int guest;
4710276Ssam extern	int logging;
4810276Ssam extern	int type;
4910276Ssam extern	int form;
5010276Ssam extern	int debug;
5111652Ssam extern	int timeout;
5236933Skarels extern	int maxtimeout;
5326045Sminshall extern  int pdata;
5436620Srick extern	char hostname[], remotehost[];
5536933Skarels extern	char proctitle[];
5610320Ssam extern	int usedefault;
5726045Sminshall extern  int transflag;
5826045Sminshall extern  char tmpline[];
5910276Ssam 
6038135Srick off_t	restart_point;
6138135Srick 
6210276Ssam static	int cmd_type;
6310276Ssam static	int cmd_form;
6410276Ssam static	int cmd_bytesz;
6536304Skarels char	cbuf[512];
6636304Skarels char	*fromname;
6710276Ssam 
6810276Ssam %}
6910276Ssam 
70*66711Spendry %union {
71*66711Spendry 	int	i;
72*66711Spendry 	char   *s;
73*66711Spendry }
74*66711Spendry 
7510276Ssam %token
7610276Ssam 	A	B	C	E	F	I
7710276Ssam 	L	N	P	R	S	T
7810276Ssam 
79*66711Spendry 	SP	CRLF	COMMA
8010276Ssam 
8110276Ssam 	USER	PASS	ACCT	REIN	QUIT	PORT
8210276Ssam 	PASV	TYPE	STRU	MODE	RETR	STOR
8310276Ssam 	APPE	MLFL	MAIL	MSND	MSOM	MSAM
8410276Ssam 	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
8510276Ssam 	ABOR	DELE	CWD	LIST	NLST	SITE
8636620Srick 	STAT	HELP	NOOP	MKD	RMD	PWD
8736933Skarels 	CDUP	STOU	SMNT	SYST	SIZE	MDTM
8810276Ssam 
8936933Skarels 	UMASK	IDLE	CHMOD
9036933Skarels 
9110276Ssam 	LEXERR
9210276Ssam 
93*66711Spendry %token	<s> STRING
94*66711Spendry %token	<i> NUMBER
95*66711Spendry 
96*66711Spendry %type	<i> check_login octal_number byte_size
97*66711Spendry %type	<i> struct_code mode_code type_code form_code
98*66711Spendry %type	<s> pathstring pathname password username
99*66711Spendry 
10010276Ssam %start	cmd_list
10110276Ssam 
10210276Ssam %%
10310276Ssam 
104*66711Spendry cmd_list
105*66711Spendry 	: /* empty */
106*66711Spendry 	| cmd_list cmd
107*66711Spendry 		{
10830945Scsvsj 			fromname = (char *) 0;
10938135Srick 			restart_point = (off_t) 0;
11030945Scsvsj 		}
111*66711Spendry 	| cmd_list rcmd
11210276Ssam 	;
11310276Ssam 
114*66711Spendry cmd
115*66711Spendry 	: USER SP username CRLF
116*66711Spendry 		{
117*66711Spendry 			user($3);
118*66711Spendry 			free($3);
11910276Ssam 		}
120*66711Spendry 	| PASS SP password CRLF
121*66711Spendry 		{
122*66711Spendry 			pass($3);
123*66711Spendry 			free($3);
12410276Ssam 		}
125*66711Spendry 	| PORT SP host_port CRLF
126*66711Spendry 		{
12710320Ssam 			usedefault = 0;
12836304Skarels 			if (pdata >= 0) {
12926045Sminshall 				(void) close(pdata);
13036304Skarels 				pdata = -1;
13126045Sminshall 			}
13227107Smckusick 			reply(200, "PORT command successful.");
13310276Ssam 		}
134*66711Spendry 	| PASV CRLF
135*66711Spendry 		{
13626045Sminshall 			passive();
13726045Sminshall 		}
138*66711Spendry 	| TYPE SP type_code CRLF
139*66711Spendry 		{
14010276Ssam 			switch (cmd_type) {
14110276Ssam 
14210276Ssam 			case TYPE_A:
14310276Ssam 				if (cmd_form == FORM_N) {
14410276Ssam 					reply(200, "Type set to A.");
14510276Ssam 					type = cmd_type;
14610276Ssam 					form = cmd_form;
14710276Ssam 				} else
14810276Ssam 					reply(504, "Form must be N.");
14910276Ssam 				break;
15010276Ssam 
15110276Ssam 			case TYPE_E:
15210276Ssam 				reply(504, "Type E not implemented.");
15310276Ssam 				break;
15410276Ssam 
15510276Ssam 			case TYPE_I:
15610276Ssam 				reply(200, "Type set to I.");
15710276Ssam 				type = cmd_type;
15810276Ssam 				break;
15910276Ssam 
16010276Ssam 			case TYPE_L:
16136933Skarels #if NBBY == 8
16236933Skarels 				if (cmd_bytesz == 8) {
16310276Ssam 					reply(200,
16436933Skarels 					    "Type set to L (byte size 8).");
16510276Ssam 					type = cmd_type;
16610276Ssam 				} else
16736933Skarels 					reply(504, "Byte size must be 8.");
16836933Skarels #else /* NBBY == 8 */
16936933Skarels 				UNIMPLEMENTED for NBBY != 8
17036933Skarels #endif /* NBBY == 8 */
17110276Ssam 			}
17210276Ssam 		}
173*66711Spendry 	| STRU SP struct_code CRLF
174*66711Spendry 		{
17510276Ssam 			switch ($3) {
17610276Ssam 
17710276Ssam 			case STRU_F:
17810276Ssam 				reply(200, "STRU F ok.");
17910276Ssam 				break;
18010276Ssam 
18110276Ssam 			default:
18227107Smckusick 				reply(504, "Unimplemented STRU type.");
18310276Ssam 			}
18410276Ssam 		}
185*66711Spendry 	| MODE SP mode_code CRLF
186*66711Spendry 		{
18710276Ssam 			switch ($3) {
18810276Ssam 
18910276Ssam 			case MODE_S:
19010276Ssam 				reply(200, "MODE S ok.");
19110276Ssam 				break;
19210276Ssam 
19310276Ssam 			default:
19410276Ssam 				reply(502, "Unimplemented MODE type.");
19510276Ssam 			}
19610276Ssam 		}
197*66711Spendry 	| ALLO SP NUMBER CRLF
198*66711Spendry 		{
19927107Smckusick 			reply(202, "ALLO command ignored.");
20010276Ssam 		}
201*66711Spendry 	| ALLO SP NUMBER SP R SP NUMBER CRLF
202*66711Spendry 		{
20336933Skarels 			reply(202, "ALLO command ignored.");
20436933Skarels 		}
205*66711Spendry 	| RETR check_login SP pathname CRLF
206*66711Spendry 		{
20710302Ssam 			if ($2 && $4 != NULL)
208*66711Spendry 				retrieve((char *) 0, $4);
20910302Ssam 			if ($4 != NULL)
210*66711Spendry 				free($4);
21110276Ssam 		}
212*66711Spendry 	| STOR check_login SP pathname CRLF
213*66711Spendry 		{
21410302Ssam 			if ($2 && $4 != NULL)
215*66711Spendry 				store($4, "w", 0);
21610302Ssam 			if ($4 != NULL)
217*66711Spendry 				free($4);
21810276Ssam 		}
219*66711Spendry 	| APPE check_login SP pathname CRLF
220*66711Spendry 		{
22110302Ssam 			if ($2 && $4 != NULL)
222*66711Spendry 				store($4, "a", 0);
22310302Ssam 			if ($4 != NULL)
224*66711Spendry 				free($4);
22510276Ssam 		}
226*66711Spendry 	| NLST check_login CRLF
227*66711Spendry 		{
22810276Ssam 			if ($2)
22936620Srick 				send_file_list(".");
23010276Ssam 		}
231*66711Spendry 	| NLST check_login SP STRING CRLF
232*66711Spendry 		{
23352999Sbostic 			if ($2 && $4 != NULL)
234*66711Spendry 				send_file_list($4);
23510302Ssam 			if ($4 != NULL)
236*66711Spendry 				free($4);
23710276Ssam 		}
238*66711Spendry 	| LIST check_login CRLF
239*66711Spendry 		{
24010276Ssam 			if ($2)
24136620Srick 				retrieve("/bin/ls -lgA", "");
24210276Ssam 		}
243*66711Spendry 	| LIST check_login SP pathname CRLF
244*66711Spendry 		{
24510302Ssam 			if ($2 && $4 != NULL)
246*66711Spendry 				retrieve("/bin/ls -lgA %s", $4);
24710302Ssam 			if ($4 != NULL)
248*66711Spendry 				free($4);
24910276Ssam 		}
250*66711Spendry 	| STAT check_login SP pathname CRLF
251*66711Spendry 		{
25236933Skarels 			if ($2 && $4 != NULL)
253*66711Spendry 				statfilecmd($4);
25436933Skarels 			if ($4 != NULL)
255*66711Spendry 				free($4);
25636933Skarels 		}
257*66711Spendry 	| STAT CRLF
258*66711Spendry 		{
25936933Skarels 			statcmd();
26036933Skarels 		}
261*66711Spendry 	| DELE check_login SP pathname CRLF
262*66711Spendry 		{
26310302Ssam 			if ($2 && $4 != NULL)
264*66711Spendry 				delete($4);
26510302Ssam 			if ($4 != NULL)
266*66711Spendry 				free($4);
26710276Ssam 		}
268*66711Spendry 	| RNTO SP pathname CRLF
269*66711Spendry 		{
27030945Scsvsj 			if (fromname) {
271*66711Spendry 				renamecmd(fromname, $3);
27230945Scsvsj 				free(fromname);
27330945Scsvsj 				fromname = (char *) 0;
27430945Scsvsj 			} else {
27530945Scsvsj 				reply(503, "Bad sequence of commands.");
27630945Scsvsj 			}
277*66711Spendry 			free($3);
27830945Scsvsj 		}
279*66711Spendry 	| ABOR CRLF
280*66711Spendry 		{
28127107Smckusick 			reply(225, "ABOR command successful.");
28226045Sminshall 		}
283*66711Spendry 	| CWD check_login CRLF
284*66711Spendry 		{
28510276Ssam 			if ($2)
28610276Ssam 				cwd(pw->pw_dir);
28710276Ssam 		}
288*66711Spendry 	| CWD check_login SP pathname CRLF
289*66711Spendry 		{
29010302Ssam 			if ($2 && $4 != NULL)
291*66711Spendry 				cwd($4);
29210302Ssam 			if ($4 != NULL)
293*66711Spendry 				free($4);
29410276Ssam 		}
295*66711Spendry 	| HELP CRLF
296*66711Spendry 		{
29736933Skarels 			help(cmdtab, (char *) 0);
29810276Ssam 		}
299*66711Spendry 	| HELP SP STRING CRLF
300*66711Spendry 		{
301*66711Spendry 			char *cp = $3;
30236933Skarels 
30336933Skarels 			if (strncasecmp(cp, "SITE", 4) == 0) {
304*66711Spendry 				cp = $3 + 4;
30536933Skarels 				if (*cp == ' ')
30636933Skarels 					cp++;
30736933Skarels 				if (*cp)
30836933Skarels 					help(sitetab, cp);
30936933Skarels 				else
31036933Skarels 					help(sitetab, (char *) 0);
31136933Skarels 			} else
312*66711Spendry 				help(cmdtab, $3);
31310276Ssam 		}
314*66711Spendry 	| NOOP CRLF
315*66711Spendry 		{
31627107Smckusick 			reply(200, "NOOP command successful.");
31710276Ssam 		}
318*66711Spendry 	| MKD check_login SP pathname CRLF
319*66711Spendry 		{
32010302Ssam 			if ($2 && $4 != NULL)
321*66711Spendry 				makedir($4);
32210302Ssam 			if ($4 != NULL)
323*66711Spendry 				free($4);
32410276Ssam 		}
325*66711Spendry 	| RMD check_login SP pathname CRLF
326*66711Spendry 		{
32710302Ssam 			if ($2 && $4 != NULL)
328*66711Spendry 				removedir($4);
32910302Ssam 			if ($4 != NULL)
330*66711Spendry 				free($4);
33110276Ssam 		}
332*66711Spendry 	| PWD check_login CRLF
333*66711Spendry 		{
33410276Ssam 			if ($2)
33510302Ssam 				pwd();
33610276Ssam 		}
337*66711Spendry 	| CDUP check_login CRLF
338*66711Spendry 		{
33910276Ssam 			if ($2)
34010276Ssam 				cwd("..");
34110276Ssam 		}
342*66711Spendry 	| SITE SP HELP CRLF
343*66711Spendry 		{
34436933Skarels 			help(sitetab, (char *) 0);
34536933Skarels 		}
346*66711Spendry 	| SITE SP HELP SP STRING CRLF
347*66711Spendry 		{
348*66711Spendry 			help(sitetab, $5);
34936933Skarels 		}
350*66711Spendry 	| SITE SP UMASK check_login CRLF
351*66711Spendry 		{
35236933Skarels 			int oldmask;
35336933Skarels 
35436933Skarels 			if ($4) {
35536933Skarels 				oldmask = umask(0);
35636933Skarels 				(void) umask(oldmask);
35736933Skarels 				reply(200, "Current UMASK is %03o", oldmask);
35836933Skarels 			}
35936933Skarels 		}
360*66711Spendry 	| SITE SP UMASK check_login SP octal_number CRLF
361*66711Spendry 		{
36236933Skarels 			int oldmask;
36336933Skarels 
36436933Skarels 			if ($4) {
36536933Skarels 				if (($6 == -1) || ($6 > 0777)) {
36636933Skarels 					reply(501, "Bad UMASK value");
36736933Skarels 				} else {
36836933Skarels 					oldmask = umask($6);
36936933Skarels 					reply(200,
37036933Skarels 					    "UMASK set to %03o (was %03o)",
37136933Skarels 					    $6, oldmask);
37236933Skarels 				}
37336933Skarels 			}
37436933Skarels 		}
375*66711Spendry 	| SITE SP CHMOD check_login SP octal_number SP pathname CRLF
376*66711Spendry 		{
37736933Skarels 			if ($4 && ($8 != NULL)) {
37836933Skarels 				if ($6 > 0777)
37936933Skarels 					reply(501,
38036933Skarels 				"CHMOD: Mode value must be between 0 and 0777");
381*66711Spendry 				else if (chmod($8, $6) < 0)
382*66711Spendry 					perror_reply(550, $8);
38336933Skarels 				else
38436933Skarels 					reply(200, "CHMOD command successful.");
38536933Skarels 			}
38636933Skarels 			if ($8 != NULL)
387*66711Spendry 				free($8);
38836933Skarels 		}
389*66711Spendry 	| SITE SP IDLE CRLF
390*66711Spendry 		{
39136933Skarels 			reply(200,
39236933Skarels 			    "Current IDLE time limit is %d seconds; max %d",
39336933Skarels 				timeout, maxtimeout);
39436933Skarels 		}
395*66711Spendry 	| SITE SP IDLE SP NUMBER CRLF
396*66711Spendry 		{
39736933Skarels 			if ($5 < 30 || $5 > maxtimeout) {
39836933Skarels 				reply(501,
39936933Skarels 			"Maximum IDLE time must be between 30 and %d seconds",
40036933Skarels 				    maxtimeout);
40136933Skarels 			} else {
40236933Skarels 				timeout = $5;
40336933Skarels 				(void) alarm((unsigned) timeout);
40436933Skarels 				reply(200,
40536933Skarels 				    "Maximum IDLE time set to %d seconds",
40636933Skarels 				    timeout);
40736933Skarels 			}
40836933Skarels 		}
409*66711Spendry 	| STOU check_login SP pathname CRLF
410*66711Spendry 		{
41136304Skarels 			if ($2 && $4 != NULL)
412*66711Spendry 				store($4, "w", 1);
41326045Sminshall 			if ($4 != NULL)
414*66711Spendry 				free($4);
41526045Sminshall 		}
416*66711Spendry 	| SYST CRLF
417*66711Spendry 		{
41836640Srick #ifdef unix
41936933Skarels #ifdef BSD
42036552Sbostic 			reply(215, "UNIX Type: L%d Version: BSD-%d",
42136552Sbostic 				NBBY, BSD);
42236933Skarels #else /* BSD */
42336933Skarels 			reply(215, "UNIX Type: L%d", NBBY);
42436933Skarels #endif /* BSD */
42536933Skarels #else /* unix */
42636933Skarels 			reply(215, "UNKNOWN Type: L%d", NBBY);
42736933Skarels #endif /* unix */
42836552Sbostic 		}
42936933Skarels 
43036933Skarels 		/*
43136933Skarels 		 * SIZE is not in RFC959, but Postel has blessed it and
43236933Skarels 		 * it will be in the updated RFC.
43336933Skarels 		 *
43436933Skarels 		 * Return size of file in a format suitable for
43536933Skarels 		 * using with RESTART (we just count bytes).
43636933Skarels 		 */
437*66711Spendry 	| SIZE check_login SP pathname CRLF
438*66711Spendry 		{
43936933Skarels 			if ($2 && $4 != NULL)
440*66711Spendry 				sizecmd($4);
44136933Skarels 			if ($4 != NULL)
442*66711Spendry 				free($4);
44336933Skarels 		}
44436933Skarels 
44536933Skarels 		/*
44636933Skarels 		 * MDTM is not in RFC959, but Postel has blessed it and
44736933Skarels 		 * it will be in the updated RFC.
44836933Skarels 		 *
44936933Skarels 		 * Return modification time of file as an ISO 3307
45036933Skarels 		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
45136933Skarels 		 * where xxx is the fractional second (of any precision,
45236933Skarels 		 * not necessarily 3 digits)
45336933Skarels 		 */
454*66711Spendry 	| MDTM check_login SP pathname CRLF
455*66711Spendry 		{
45636933Skarels 			if ($2 && $4 != NULL) {
45736933Skarels 				struct stat stbuf;
458*66711Spendry 				if (stat($4, &stbuf) < 0)
45954528Sandrew 					reply(550, "%s: %s",
460*66711Spendry 					    $4, strerror(errno));
461*66711Spendry 				else if (!S_ISREG(stbuf.st_mode)) {
462*66711Spendry 					reply(550, "%s: not a plain file.", $4);
46336933Skarels 				} else {
464*66711Spendry 					struct tm *t;
46536933Skarels 					t = gmtime(&stbuf.st_mtime);
46636933Skarels 					reply(213,
46736933Skarels 					    "19%02d%02d%02d%02d%02d%02d",
46836933Skarels 					    t->tm_year, t->tm_mon+1, t->tm_mday,
46936933Skarels 					    t->tm_hour, t->tm_min, t->tm_sec);
47036933Skarels 				}
47136933Skarels 			}
47236933Skarels 			if ($4 != NULL)
473*66711Spendry 				free($4);
47436933Skarels 		}
475*66711Spendry 	| QUIT CRLF
476*66711Spendry 		{
47710276Ssam 			reply(221, "Goodbye.");
47813246Ssam 			dologout(0);
47910276Ssam 		}
480*66711Spendry 	| error CRLF
481*66711Spendry 		{
48210276Ssam 			yyerrok;
48310276Ssam 		}
48410276Ssam 	;
485*66711Spendry rcmd
486*66711Spendry 	: RNFR check_login SP pathname CRLF
487*66711Spendry 		{
48830945Scsvsj 			char *renamefrom();
48930945Scsvsj 
49038135Srick 			restart_point = (off_t) 0;
49130945Scsvsj 			if ($2 && $4) {
492*66711Spendry 				fromname = renamefrom($4);
49330945Scsvsj 				if (fromname == (char *) 0 && $4) {
494*66711Spendry 					free($4);
49530945Scsvsj 				}
49630945Scsvsj 			}
49730945Scsvsj 		}
498*66711Spendry 	| REST SP byte_size CRLF
499*66711Spendry 		{
50038135Srick 			fromname = (char *) 0;
50138135Srick 			restart_point = $3;
50238135Srick 			reply(350, "Restarting at %ld. %s", restart_point,
50338135Srick 			    "Send STORE or RETRIEVE to initiate transfer.");
50438135Srick 		}
50530945Scsvsj 	;
50652999Sbostic 
507*66711Spendry username
508*66711Spendry 	: STRING
50910276Ssam 	;
51010276Ssam 
511*66711Spendry password
512*66711Spendry 	: /* empty */
513*66711Spendry 		{
514*66711Spendry 			$$ = (char *)calloc(1, sizeof(char));
51536304Skarels 		}
516*66711Spendry 	| STRING
51710276Ssam 	;
51810276Ssam 
519*66711Spendry byte_size
520*66711Spendry 	: NUMBER
52110276Ssam 	;
52210276Ssam 
523*66711Spendry host_port
524*66711Spendry 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
52510276Ssam 		NUMBER COMMA NUMBER
526*66711Spendry 		{
527*66711Spendry 			char *a, *p;
52810276Ssam 
52910276Ssam 			a = (char *)&data_dest.sin_addr;
53010276Ssam 			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
53110276Ssam 			p = (char *)&data_dest.sin_port;
53210276Ssam 			p[0] = $9; p[1] = $11;
53310324Ssam 			data_dest.sin_family = AF_INET;
53410276Ssam 		}
53510276Ssam 	;
53610276Ssam 
537*66711Spendry form_code
538*66711Spendry 	: N
539*66711Spendry 		{
540*66711Spendry 			$$ = FORM_N;
541*66711Spendry 		}
542*66711Spendry 	| T
543*66711Spendry 		{
544*66711Spendry 			$$ = FORM_T;
545*66711Spendry 		}
546*66711Spendry 	| C
547*66711Spendry 		{
548*66711Spendry 			$$ = FORM_C;
549*66711Spendry 		}
55010276Ssam 	;
55110276Ssam 
552*66711Spendry type_code
553*66711Spendry 	: A
554*66711Spendry 		{
555*66711Spendry 			cmd_type = TYPE_A;
556*66711Spendry 			cmd_form = FORM_N;
557*66711Spendry 		}
558*66711Spendry 	| A SP form_code
559*66711Spendry 		{
560*66711Spendry 			cmd_type = TYPE_A;
561*66711Spendry 			cmd_form = $3;
562*66711Spendry 		}
563*66711Spendry 	| E
564*66711Spendry 		{
565*66711Spendry 			cmd_type = TYPE_E;
566*66711Spendry 			cmd_form = FORM_N;
567*66711Spendry 		}
568*66711Spendry 	| E SP form_code
569*66711Spendry 		{
570*66711Spendry 			cmd_type = TYPE_E;
571*66711Spendry 			cmd_form = $3;
572*66711Spendry 		}
573*66711Spendry 	| I
574*66711Spendry 		{
575*66711Spendry 			cmd_type = TYPE_I;
576*66711Spendry 		}
577*66711Spendry 	| L
578*66711Spendry 		{
579*66711Spendry 			cmd_type = TYPE_L;
580*66711Spendry 			cmd_bytesz = NBBY;
581*66711Spendry 		}
582*66711Spendry 	| L SP byte_size
583*66711Spendry 		{
584*66711Spendry 			cmd_type = TYPE_L;
585*66711Spendry 			cmd_bytesz = $3;
586*66711Spendry 		}
587*66711Spendry 		/* this is for a bug in the BBN ftp */
588*66711Spendry 	| L byte_size
589*66711Spendry 		{
590*66711Spendry 			cmd_type = TYPE_L;
591*66711Spendry 			cmd_bytesz = $2;
592*66711Spendry 		}
59310276Ssam 	;
59410276Ssam 
595*66711Spendry struct_code
596*66711Spendry 	: F
597*66711Spendry 		{
598*66711Spendry 			$$ = STRU_F;
599*66711Spendry 		}
600*66711Spendry 	| R
601*66711Spendry 		{
602*66711Spendry 			$$ = STRU_R;
603*66711Spendry 		}
604*66711Spendry 	| P
605*66711Spendry 		{
606*66711Spendry 			$$ = STRU_P;
607*66711Spendry 		}
60810276Ssam 	;
60910276Ssam 
610*66711Spendry mode_code
611*66711Spendry 	: S
612*66711Spendry 		{
613*66711Spendry 			$$ = MODE_S;
614*66711Spendry 		}
615*66711Spendry 	| B
616*66711Spendry 		{
617*66711Spendry 			$$ = MODE_B;
618*66711Spendry 		}
619*66711Spendry 	| C
620*66711Spendry 		{
621*66711Spendry 			$$ = MODE_C;
622*66711Spendry 		}
62310276Ssam 	;
62410276Ssam 
625*66711Spendry pathname
626*66711Spendry 	: pathstring
627*66711Spendry 		{
628*66711Spendry 			/*
629*66711Spendry 			 * Problem: this production is used for all pathname
630*66711Spendry 			 * processing, but only gives a 550 error reply.
631*66711Spendry 			 * This is a valid reply in some cases but not in others.
632*66711Spendry 			 */
633*66711Spendry 			if (logged_in && $1 && *$1 == '~') {
634*66711Spendry 				glob_t gl;
635*66711Spendry 				int flags = GLOB_BRACE|GLOB_QUOTE|GLOB_TILDE;
636*66711Spendry 
637*66711Spendry 				memset(&gl, 0, sizeof(gl));
638*66711Spendry 				if (glob($1, flags, NULL, &gl)) {
639*66711Spendry 					reply(550, "not found");
640*66711Spendry 					$$ = NULL;
641*66711Spendry 				} else {
642*66711Spendry 					$$ = strdup(gl.gl_pathv[0]);
643*66711Spendry 				}
644*66711Spendry 				globfree(&gl);
645*66711Spendry 				free($1);
646*66711Spendry 			} else
647*66711Spendry 				$$ = $1;
648*66711Spendry 		}
64910276Ssam 	;
65010276Ssam 
651*66711Spendry pathstring
652*66711Spendry 	: STRING
65310276Ssam 	;
65410276Ssam 
655*66711Spendry octal_number
656*66711Spendry 	: NUMBER
657*66711Spendry 		{
658*66711Spendry 			int ret, dec, multby, digit;
65936933Skarels 
660*66711Spendry 			/*
661*66711Spendry 			 * Convert a number that was read as decimal number
662*66711Spendry 			 * to what it would be if it had been read as octal.
663*66711Spendry 			 */
664*66711Spendry 			dec = $1;
665*66711Spendry 			multby = 1;
666*66711Spendry 			ret = 0;
667*66711Spendry 			while (dec) {
668*66711Spendry 				digit = dec%10;
669*66711Spendry 				if (digit > 7) {
670*66711Spendry 					ret = -1;
671*66711Spendry 					break;
672*66711Spendry 				}
673*66711Spendry 				ret += digit * multby;
674*66711Spendry 				multby *= 8;
675*66711Spendry 				dec /= 10;
67636933Skarels 			}
677*66711Spendry 			$$ = ret;
67836933Skarels 		}
67936933Skarels 	;
68036933Skarels 
681*66711Spendry 
682*66711Spendry check_login
683*66711Spendry 	: /* empty */
684*66711Spendry 		{
685*66711Spendry 			if (logged_in)
686*66711Spendry 				$$ = 1;
687*66711Spendry 			else {
688*66711Spendry 				reply(530, "Please login with USER and PASS.");
689*66711Spendry 				$$ = 0;
690*66711Spendry 			}
69110276Ssam 		}
69210276Ssam 	;
69310276Ssam 
69410276Ssam %%
69510276Ssam 
69610276Ssam extern jmp_buf errcatch;
69710276Ssam 
69810276Ssam #define	CMD	0	/* beginning of command */
69910276Ssam #define	ARGS	1	/* expect miscellaneous arguments */
70010276Ssam #define	STR1	2	/* expect SP followed by STRING */
70110276Ssam #define	STR2	3	/* expect STRING */
70236304Skarels #define	OSTR	4	/* optional SP then STRING */
70336304Skarels #define	ZSTR1	5	/* SP then optional STRING */
70436304Skarels #define	ZSTR2	6	/* optional STRING after SP */
70536933Skarels #define	SITECMD	7	/* SITE command */
70636933Skarels #define	NSTR	8	/* Number followed by a string */
70710276Ssam 
70810276Ssam struct tab {
70910276Ssam 	char	*name;
71010276Ssam 	short	token;
71110276Ssam 	short	state;
71210276Ssam 	short	implemented;	/* 1 if command is implemented */
71310276Ssam 	char	*help;
71410276Ssam };
71510276Ssam 
71610276Ssam struct tab cmdtab[] = {		/* In order defined in RFC 765 */
71710276Ssam 	{ "USER", USER, STR1, 1,	"<sp> username" },
71836304Skarels 	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
71910276Ssam 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
72036933Skarels 	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
72110276Ssam 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
72210276Ssam 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
72310276Ssam 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
72426045Sminshall 	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
72510276Ssam 	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
72610276Ssam 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
72710276Ssam 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
72810276Ssam 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
72910276Ssam 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
73010276Ssam 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
73110276Ssam 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
73210276Ssam 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
73310276Ssam 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
73410276Ssam 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
73510276Ssam 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
73610276Ssam 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
73710276Ssam 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
73810276Ssam 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
73954058Sandrew 	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
74010276Ssam 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
74110276Ssam 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
74226045Sminshall 	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
74310276Ssam 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
74436304Skarels 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
74510276Ssam 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
74610276Ssam 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
74710276Ssam 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
74836933Skarels 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
74936552Sbostic 	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
75036933Skarels 	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
75110276Ssam 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
75210276Ssam 	{ "NOOP", NOOP, ARGS, 1,	"" },
75336620Srick 	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
75436620Srick 	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
75536620Srick 	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
75636620Srick 	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
75736620Srick 	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
75836620Srick 	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
75936620Srick 	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
76036620Srick 	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
76126045Sminshall 	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
76236933Skarels 	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
76336933Skarels 	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
76410276Ssam 	{ NULL,   0,    0,    0,	0 }
76510276Ssam };
76610276Ssam 
76736933Skarels struct tab sitetab[] = {
76836933Skarels 	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
76936933Skarels 	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
77036933Skarels 	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
77136933Skarels 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
77236933Skarels 	{ NULL,   0,    0,    0,	0 }
77336933Skarels };
77436933Skarels 
77554528Sandrew static char	*copy __P((char *));
77654528Sandrew static void	 help __P((struct tab *, char *));
77754528Sandrew static struct tab *
77854528Sandrew 		 lookup __P((struct tab *, char *));
77954528Sandrew static void	 sizecmd __P((char *));
78054528Sandrew static void	 toolong __P((int));
78154528Sandrew static int	 yylex __P((void));
78254528Sandrew 
78354528Sandrew static struct tab *
78436933Skarels lookup(p, cmd)
785*66711Spendry 	struct tab *p;
78610276Ssam 	char *cmd;
78710276Ssam {
78810276Ssam 
78936933Skarels 	for (; p->name != NULL; p++)
79010276Ssam 		if (strcmp(cmd, p->name) == 0)
79110276Ssam 			return (p);
79210276Ssam 	return (0);
79310276Ssam }
79410276Ssam 
79513033Ssam #include <arpa/telnet.h>
79610276Ssam 
79710276Ssam /*
79810276Ssam  * getline - a hacked up version of fgets to ignore TELNET escape codes.
79910276Ssam  */
80010276Ssam char *
80110276Ssam getline(s, n, iop)
80210276Ssam 	char *s;
80354528Sandrew 	int n;
804*66711Spendry 	FILE *iop;
80510276Ssam {
806*66711Spendry 	int c;
80726494Sminshall 	register char *cs;
80810276Ssam 
80910276Ssam 	cs = s;
81027751Sminshall /* tmpline may contain saved command from urgent mode interruption */
81126045Sminshall 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
81226045Sminshall 		*cs++ = tmpline[c];
81326045Sminshall 		if (tmpline[c] == '\n') {
81426045Sminshall 			*cs++ = '\0';
81536304Skarels 			if (debug)
81636304Skarels 				syslog(LOG_DEBUG, "command: %s", s);
81726045Sminshall 			tmpline[0] = '\0';
81826045Sminshall 			return(s);
81926045Sminshall 		}
82036304Skarels 		if (c == 0)
82126045Sminshall 			tmpline[0] = '\0';
82226045Sminshall 	}
82336304Skarels 	while ((c = getc(iop)) != EOF) {
82436304Skarels 		c &= 0377;
82536304Skarels 		if (c == IAC) {
82636304Skarels 		    if ((c = getc(iop)) != EOF) {
82736304Skarels 			c &= 0377;
82836304Skarels 			switch (c) {
82927751Sminshall 			case WILL:
83027751Sminshall 			case WONT:
83136277Sbostic 				c = getc(iop);
83236304Skarels 				printf("%c%c%c", IAC, DONT, 0377&c);
83327751Sminshall 				(void) fflush(stdout);
83436304Skarels 				continue;
83527751Sminshall 			case DO:
83627751Sminshall 			case DONT:
83736277Sbostic 				c = getc(iop);
83836304Skarels 				printf("%c%c%c", IAC, WONT, 0377&c);
83927751Sminshall 				(void) fflush(stdout);
84036304Skarels 				continue;
84136304Skarels 			case IAC:
84227751Sminshall 				break;
84327751Sminshall 			default:
84436304Skarels 				continue;	/* ignore command */
84527751Sminshall 			}
84636304Skarels 		    }
84710276Ssam 		}
84836304Skarels 		*cs++ = c;
84936304Skarels 		if (--n <= 0 || c == '\n')
85010276Ssam 			break;
85110276Ssam 	}
85227751Sminshall 	if (c == EOF && cs == s)
85318303Sralph 		return (NULL);
85410276Ssam 	*cs++ = '\0';
85552999Sbostic 	if (debug) {
85652999Sbostic 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
85752999Sbostic 			/* Don't syslog passwords */
85852999Sbostic 			syslog(LOG_DEBUG, "command: %.5s ???", s);
85952999Sbostic 		} else {
86052999Sbostic 			register char *cp;
86152999Sbostic 			register int len;
86252999Sbostic 
86352999Sbostic 			/* Don't syslog trailing CR-LF */
86452999Sbostic 			len = strlen(s);
86552999Sbostic 			cp = s + len - 1;
86652999Sbostic 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
86752999Sbostic 				--cp;
86852999Sbostic 				--len;
86952999Sbostic 			}
87052999Sbostic 			syslog(LOG_DEBUG, "command: %.*s", len, s);
87152999Sbostic 		}
87252999Sbostic 	}
87310276Ssam 	return (s);
87410276Ssam }
87510276Ssam 
87646669Sbostic static void
87754528Sandrew toolong(signo)
87854528Sandrew 	int signo;
87911652Ssam {
88011652Ssam 
88111652Ssam 	reply(421,
88252999Sbostic 	    "Timeout (%d seconds): closing control connection.", timeout);
88352999Sbostic 	if (logging)
88452999Sbostic 		syslog(LOG_INFO, "User %s timed out after %d seconds",
88552999Sbostic 		    (pw ? pw -> pw_name : "unknown"), timeout);
88613246Ssam 	dologout(1);
88711652Ssam }
88811652Ssam 
88954528Sandrew static int
89010276Ssam yylex()
89110276Ssam {
89210276Ssam 	static int cpos, state;
893*66711Spendry 	char *cp, *cp2;
894*66711Spendry 	struct tab *p;
89510276Ssam 	int n;
89654528Sandrew 	char c;
89710276Ssam 
89810276Ssam 	for (;;) {
89910276Ssam 		switch (state) {
90010276Ssam 
90110276Ssam 		case CMD:
90226494Sminshall 			(void) signal(SIGALRM, toolong);
90326494Sminshall 			(void) alarm((unsigned) timeout);
90410276Ssam 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
90510276Ssam 				reply(221, "You could at least say goodbye.");
90613246Ssam 				dologout(0);
90710276Ssam 			}
90826494Sminshall 			(void) alarm(0);
90936620Srick #ifdef SETPROCTITLE
91036933Skarels 			if (strncasecmp(cbuf, "PASS", 4) != NULL)
91136933Skarels 				setproctitle("%s: %s", proctitle, cbuf);
91236620Srick #endif /* SETPROCTITLE */
91360087Sbostic 			if ((cp = strchr(cbuf, '\r'))) {
91436324Sbostic 				*cp++ = '\n';
91536324Sbostic 				*cp = '\0';
91636324Sbostic 			}
91736277Sbostic 			if ((cp = strpbrk(cbuf, " \n")))
91836277Sbostic 				cpos = cp - cbuf;
91936304Skarels 			if (cpos == 0)
92010276Ssam 				cpos = 4;
92110276Ssam 			c = cbuf[cpos];
92210276Ssam 			cbuf[cpos] = '\0';
92310276Ssam 			upper(cbuf);
92436933Skarels 			p = lookup(cmdtab, cbuf);
92510276Ssam 			cbuf[cpos] = c;
92610276Ssam 			if (p != 0) {
92710276Ssam 				if (p->implemented == 0) {
92810276Ssam 					nack(p->name);
92926494Sminshall 					longjmp(errcatch,0);
93010276Ssam 					/* NOTREACHED */
93110276Ssam 				}
93210276Ssam 				state = p->state;
933*66711Spendry 				yylval.s = p->name;
93410276Ssam 				return (p->token);
93510276Ssam 			}
93610276Ssam 			break;
93710276Ssam 
93836933Skarels 		case SITECMD:
93936933Skarels 			if (cbuf[cpos] == ' ') {
94036933Skarels 				cpos++;
94136933Skarels 				return (SP);
94236933Skarels 			}
94336933Skarels 			cp = &cbuf[cpos];
94436933Skarels 			if ((cp2 = strpbrk(cp, " \n")))
94536933Skarels 				cpos = cp2 - cbuf;
94636933Skarels 			c = cbuf[cpos];
94736933Skarels 			cbuf[cpos] = '\0';
94836933Skarels 			upper(cp);
94936933Skarels 			p = lookup(sitetab, cp);
95036933Skarels 			cbuf[cpos] = c;
95136933Skarels 			if (p != 0) {
95236933Skarels 				if (p->implemented == 0) {
95336933Skarels 					state = CMD;
95436933Skarels 					nack(p->name);
95536933Skarels 					longjmp(errcatch,0);
95636933Skarels 					/* NOTREACHED */
95736933Skarels 				}
95836933Skarels 				state = p->state;
959*66711Spendry 				yylval.s = p->name;
96036933Skarels 				return (p->token);
96136933Skarels 			}
96236933Skarels 			state = CMD;
96336933Skarels 			break;
96436933Skarels 
96510276Ssam 		case OSTR:
96610276Ssam 			if (cbuf[cpos] == '\n') {
96710276Ssam 				state = CMD;
96810276Ssam 				return (CRLF);
96910276Ssam 			}
97036317Sbostic 			/* FALLTHROUGH */
97110276Ssam 
97210276Ssam 		case STR1:
97336304Skarels 		case ZSTR1:
97436933Skarels 		dostr1:
97510276Ssam 			if (cbuf[cpos] == ' ') {
97610276Ssam 				cpos++;
97736317Sbostic 				state = state == OSTR ? STR2 : ++state;
97810276Ssam 				return (SP);
97910276Ssam 			}
98010276Ssam 			break;
98110276Ssam 
98236304Skarels 		case ZSTR2:
98336304Skarels 			if (cbuf[cpos] == '\n') {
98436304Skarels 				state = CMD;
98536304Skarels 				return (CRLF);
98636304Skarels 			}
98736933Skarels 			/* FALLTHROUGH */
98836304Skarels 
98910276Ssam 		case STR2:
99010276Ssam 			cp = &cbuf[cpos];
99110276Ssam 			n = strlen(cp);
99210276Ssam 			cpos += n - 1;
99310276Ssam 			/*
99410276Ssam 			 * Make sure the string is nonempty and \n terminated.
99510276Ssam 			 */
99610276Ssam 			if (n > 1 && cbuf[cpos] == '\n') {
99710276Ssam 				cbuf[cpos] = '\0';
998*66711Spendry 				yylval.s = copy(cp);
99910276Ssam 				cbuf[cpos] = '\n';
100010276Ssam 				state = ARGS;
100110276Ssam 				return (STRING);
100210276Ssam 			}
100310276Ssam 			break;
100410276Ssam 
100536933Skarels 		case NSTR:
100636933Skarels 			if (cbuf[cpos] == ' ') {
100736933Skarels 				cpos++;
100836933Skarels 				return (SP);
100936933Skarels 			}
101036933Skarels 			if (isdigit(cbuf[cpos])) {
101136933Skarels 				cp = &cbuf[cpos];
101236933Skarels 				while (isdigit(cbuf[++cpos]))
101336933Skarels 					;
101436933Skarels 				c = cbuf[cpos];
101536933Skarels 				cbuf[cpos] = '\0';
1016*66711Spendry 				yylval.i = atoi(cp);
101736933Skarels 				cbuf[cpos] = c;
101836933Skarels 				state = STR1;
101936933Skarels 				return (NUMBER);
102036933Skarels 			}
102136933Skarels 			state = STR1;
102236933Skarels 			goto dostr1;
102336933Skarels 
102410276Ssam 		case ARGS:
102510276Ssam 			if (isdigit(cbuf[cpos])) {
102610276Ssam 				cp = &cbuf[cpos];
102710276Ssam 				while (isdigit(cbuf[++cpos]))
102810276Ssam 					;
102910276Ssam 				c = cbuf[cpos];
103010276Ssam 				cbuf[cpos] = '\0';
1031*66711Spendry 				yylval.i = atoi(cp);
103210276Ssam 				cbuf[cpos] = c;
103310276Ssam 				return (NUMBER);
103410276Ssam 			}
103510276Ssam 			switch (cbuf[cpos++]) {
103610276Ssam 
103710276Ssam 			case '\n':
103810276Ssam 				state = CMD;
103910276Ssam 				return (CRLF);
104010276Ssam 
104110276Ssam 			case ' ':
104210276Ssam 				return (SP);
104310276Ssam 
104410276Ssam 			case ',':
104510276Ssam 				return (COMMA);
104610276Ssam 
104710276Ssam 			case 'A':
104810276Ssam 			case 'a':
104910276Ssam 				return (A);
105010276Ssam 
105110276Ssam 			case 'B':
105210276Ssam 			case 'b':
105310276Ssam 				return (B);
105410276Ssam 
105510276Ssam 			case 'C':
105610276Ssam 			case 'c':
105710276Ssam 				return (C);
105810276Ssam 
105910276Ssam 			case 'E':
106010276Ssam 			case 'e':
106110276Ssam 				return (E);
106210276Ssam 
106310276Ssam 			case 'F':
106410276Ssam 			case 'f':
106510276Ssam 				return (F);
106610276Ssam 
106710276Ssam 			case 'I':
106810276Ssam 			case 'i':
106910276Ssam 				return (I);
107010276Ssam 
107110276Ssam 			case 'L':
107210276Ssam 			case 'l':
107310276Ssam 				return (L);
107410276Ssam 
107510276Ssam 			case 'N':
107610276Ssam 			case 'n':
107710276Ssam 				return (N);
107810276Ssam 
107910276Ssam 			case 'P':
108010276Ssam 			case 'p':
108110276Ssam 				return (P);
108210276Ssam 
108310276Ssam 			case 'R':
108410276Ssam 			case 'r':
108510276Ssam 				return (R);
108610276Ssam 
108710276Ssam 			case 'S':
108810276Ssam 			case 's':
108910276Ssam 				return (S);
109010276Ssam 
109110276Ssam 			case 'T':
109210276Ssam 			case 't':
109310276Ssam 				return (T);
109410276Ssam 
109510276Ssam 			}
109610276Ssam 			break;
109710276Ssam 
109810276Ssam 		default:
109910276Ssam 			fatal("Unknown state in scanner.");
110010276Ssam 		}
110126494Sminshall 		yyerror((char *) 0);
110210276Ssam 		state = CMD;
110326494Sminshall 		longjmp(errcatch,0);
110410276Ssam 	}
110510276Ssam }
110610276Ssam 
110754528Sandrew void
110810276Ssam upper(s)
1109*66711Spendry 	char *s;
111010276Ssam {
111110276Ssam 	while (*s != '\0') {
111210276Ssam 		if (islower(*s))
111310276Ssam 			*s = toupper(*s);
111410276Ssam 		s++;
111510276Ssam 	}
111610276Ssam }
111710276Ssam 
111854528Sandrew static char *
111910276Ssam copy(s)
112010276Ssam 	char *s;
112110276Ssam {
112210276Ssam 	char *p;
112310276Ssam 
112426494Sminshall 	p = malloc((unsigned) strlen(s) + 1);
112510276Ssam 	if (p == NULL)
112610276Ssam 		fatal("Ran out of memory.");
112726494Sminshall 	(void) strcpy(p, s);
112836933Skarels 	return (p);
112910276Ssam }
113010276Ssam 
113154528Sandrew static void
113236933Skarels help(ctab, s)
113336933Skarels 	struct tab *ctab;
113410276Ssam 	char *s;
113510276Ssam {
1136*66711Spendry 	struct tab *c;
1137*66711Spendry 	int width, NCMDS;
113836933Skarels 	char *type;
113910276Ssam 
114036933Skarels 	if (ctab == sitetab)
114136933Skarels 		type = "SITE ";
114236933Skarels 	else
114336933Skarels 		type = "";
114410276Ssam 	width = 0, NCMDS = 0;
114536933Skarels 	for (c = ctab; c->name != NULL; c++) {
114636933Skarels 		int len = strlen(c->name);
114710276Ssam 
114810276Ssam 		if (len > width)
114910276Ssam 			width = len;
115010276Ssam 		NCMDS++;
115110276Ssam 	}
115210276Ssam 	width = (width + 8) &~ 7;
115310276Ssam 	if (s == 0) {
1154*66711Spendry 		int i, j, w;
115510276Ssam 		int columns, lines;
115610276Ssam 
115736933Skarels 		lreply(214, "The following %scommands are recognized %s.",
115836933Skarels 		    type, "(* =>'s unimplemented)");
115910276Ssam 		columns = 76 / width;
116010276Ssam 		if (columns == 0)
116110276Ssam 			columns = 1;
116210276Ssam 		lines = (NCMDS + columns - 1) / columns;
116310276Ssam 		for (i = 0; i < lines; i++) {
116427107Smckusick 			printf("   ");
116510276Ssam 			for (j = 0; j < columns; j++) {
116636933Skarels 				c = ctab + j * lines + i;
116710276Ssam 				printf("%s%c", c->name,
116810276Ssam 					c->implemented ? ' ' : '*');
116936933Skarels 				if (c + lines >= &ctab[NCMDS])
117010276Ssam 					break;
117131132Smckusick 				w = strlen(c->name) + 1;
117210276Ssam 				while (w < width) {
117310276Ssam 					putchar(' ');
117410276Ssam 					w++;
117510276Ssam 				}
117610276Ssam 			}
117710276Ssam 			printf("\r\n");
117810276Ssam 		}
117926494Sminshall 		(void) fflush(stdout);
118010276Ssam 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
118110276Ssam 		return;
118210276Ssam 	}
118310276Ssam 	upper(s);
118436933Skarels 	c = lookup(ctab, s);
118510276Ssam 	if (c == (struct tab *)0) {
118627107Smckusick 		reply(502, "Unknown command %s.", s);
118710276Ssam 		return;
118810276Ssam 	}
118910276Ssam 	if (c->implemented)
119036933Skarels 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
119110276Ssam 	else
119236933Skarels 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
119336933Skarels 		    c->name, c->help);
119410276Ssam }
119536933Skarels 
119654528Sandrew static void
119736933Skarels sizecmd(filename)
119854528Sandrew 	char *filename;
119936933Skarels {
120036933Skarels 	switch (type) {
120136933Skarels 	case TYPE_L:
120236933Skarels 	case TYPE_I: {
120336933Skarels 		struct stat stbuf;
1204*66711Spendry 		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
120536933Skarels 			reply(550, "%s: not a plain file.", filename);
120636933Skarels 		else
1207*66711Spendry 			reply(213, "%qu", stbuf.st_size);
1208*66711Spendry 		break; }
120936933Skarels 	case TYPE_A: {
121036933Skarels 		FILE *fin;
1211*66711Spendry 		int c;
1212*66711Spendry 		off_t count;
121336933Skarels 		struct stat stbuf;
121436933Skarels 		fin = fopen(filename, "r");
121536933Skarels 		if (fin == NULL) {
121636933Skarels 			perror_reply(550, filename);
121736933Skarels 			return;
121836933Skarels 		}
1219*66711Spendry 		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
122036933Skarels 			reply(550, "%s: not a plain file.", filename);
122136933Skarels 			(void) fclose(fin);
122236933Skarels 			return;
122336933Skarels 		}
122436933Skarels 
122536933Skarels 		count = 0;
122636933Skarels 		while((c=getc(fin)) != EOF) {
122736933Skarels 			if (c == '\n')	/* will get expanded to \r\n */
122836933Skarels 				count++;
122936933Skarels 			count++;
123036933Skarels 		}
123136933Skarels 		(void) fclose(fin);
123236933Skarels 
1233*66711Spendry 		reply(213, "%qd", count);
1234*66711Spendry 		break; }
123536933Skarels 	default:
123636933Skarels 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
123736933Skarels 	}
123836933Skarels }
1239