xref: /csrg-svn/libexec/ftpd/ftpcmd.y (revision 52999)
110276Ssam /*
236304Skarels  * Copyright (c) 1985, 1988 Regents of the University of California.
333738Sbostic  * All rights reserved.
433738Sbostic  *
542666Sbostic  * %sccs.include.redist.c%
634769Sbostic  *
7*52999Sbostic  *	@(#)ftpcmd.y	5.25 (Berkeley) 03/18/92
822501Sdist  */
922501Sdist 
1022501Sdist /*
1110276Ssam  * Grammar for FTP commands.
1236933Skarels  * See RFC 959.
1310276Ssam  */
1410276Ssam 
1510276Ssam %{
1610276Ssam 
1710276Ssam #ifndef lint
18*52999Sbostic static char sccsid[] = "@(#)ftpcmd.y	5.25 (Berkeley) 03/18/92";
1933738Sbostic #endif /* not lint */
2010276Ssam 
2136552Sbostic #include <sys/param.h>
2210276Ssam #include <sys/socket.h>
2346669Sbostic #include <sys/stat.h>
2410276Ssam #include <netinet/in.h>
2513033Ssam #include <arpa/ftp.h>
2611652Ssam #include <signal.h>
2710276Ssam #include <setjmp.h>
2826494Sminshall #include <syslog.h>
2936933Skarels #include <time.h>
3046669Sbostic #include <pwd.h>
3146669Sbostic #include <unistd.h>
3246669Sbostic #include <stdio.h>
3346669Sbostic #include <ctype.h>
3446669Sbostic #include <stdlib.h>
3546669Sbostic #include <string.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[];
5446669Sbostic char	**ftpglob();
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 %}
6510276Ssam 
6610276Ssam %token
6710276Ssam 	A	B	C	E	F	I
6810276Ssam 	L	N	P	R	S	T
6910276Ssam 
7010276Ssam 	SP	CRLF	COMMA	STRING	NUMBER
7110276Ssam 
7210276Ssam 	USER	PASS	ACCT	REIN	QUIT	PORT
7310276Ssam 	PASV	TYPE	STRU	MODE	RETR	STOR
7410276Ssam 	APPE	MLFL	MAIL	MSND	MSOM	MSAM
7510276Ssam 	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
7610276Ssam 	ABOR	DELE	CWD	LIST	NLST	SITE
7736620Srick 	STAT	HELP	NOOP	MKD	RMD	PWD
7836933Skarels 	CDUP	STOU	SMNT	SYST	SIZE	MDTM
7910276Ssam 
8036933Skarels 	UMASK	IDLE	CHMOD
8136933Skarels 
8210276Ssam 	LEXERR
8310276Ssam 
8410276Ssam %start	cmd_list
8510276Ssam 
8610276Ssam %%
8710276Ssam 
8810276Ssam cmd_list:	/* empty */
8910276Ssam 	|	cmd_list cmd
9030945Scsvsj 		= {
9130945Scsvsj 			fromname = (char *) 0;
9238135Srick 			restart_point = (off_t) 0;
9330945Scsvsj 		}
9430945Scsvsj 	|	cmd_list rcmd
9510276Ssam 	;
9610276Ssam 
9710276Ssam cmd:		USER SP username CRLF
9810276Ssam 		= {
9936304Skarels 			user((char *) $3);
10026494Sminshall 			free((char *) $3);
10110276Ssam 		}
10210276Ssam 	|	PASS SP password CRLF
10310276Ssam 		= {
10426494Sminshall 			pass((char *) $3);
10526494Sminshall 			free((char *) $3);
10610276Ssam 		}
10710276Ssam 	|	PORT SP host_port CRLF
10810276Ssam 		= {
10910320Ssam 			usedefault = 0;
11036304Skarels 			if (pdata >= 0) {
11126045Sminshall 				(void) close(pdata);
11236304Skarels 				pdata = -1;
11326045Sminshall 			}
11427107Smckusick 			reply(200, "PORT command successful.");
11510276Ssam 		}
11626045Sminshall 	|	PASV CRLF
11726045Sminshall 		= {
11826045Sminshall 			passive();
11926045Sminshall 		}
12010276Ssam 	|	TYPE SP type_code CRLF
12110276Ssam 		= {
12210276Ssam 			switch (cmd_type) {
12310276Ssam 
12410276Ssam 			case TYPE_A:
12510276Ssam 				if (cmd_form == FORM_N) {
12610276Ssam 					reply(200, "Type set to A.");
12710276Ssam 					type = cmd_type;
12810276Ssam 					form = cmd_form;
12910276Ssam 				} else
13010276Ssam 					reply(504, "Form must be N.");
13110276Ssam 				break;
13210276Ssam 
13310276Ssam 			case TYPE_E:
13410276Ssam 				reply(504, "Type E not implemented.");
13510276Ssam 				break;
13610276Ssam 
13710276Ssam 			case TYPE_I:
13810276Ssam 				reply(200, "Type set to I.");
13910276Ssam 				type = cmd_type;
14010276Ssam 				break;
14110276Ssam 
14210276Ssam 			case TYPE_L:
14336933Skarels #if NBBY == 8
14436933Skarels 				if (cmd_bytesz == 8) {
14510276Ssam 					reply(200,
14636933Skarels 					    "Type set to L (byte size 8).");
14710276Ssam 					type = cmd_type;
14810276Ssam 				} else
14936933Skarels 					reply(504, "Byte size must be 8.");
15036933Skarels #else /* NBBY == 8 */
15136933Skarels 				UNIMPLEMENTED for NBBY != 8
15236933Skarels #endif /* NBBY == 8 */
15310276Ssam 			}
15410276Ssam 		}
15510276Ssam 	|	STRU SP struct_code CRLF
15610276Ssam 		= {
15710276Ssam 			switch ($3) {
15810276Ssam 
15910276Ssam 			case STRU_F:
16010276Ssam 				reply(200, "STRU F ok.");
16110276Ssam 				break;
16210276Ssam 
16310276Ssam 			default:
16427107Smckusick 				reply(504, "Unimplemented STRU type.");
16510276Ssam 			}
16610276Ssam 		}
16710276Ssam 	|	MODE SP mode_code CRLF
16810276Ssam 		= {
16910276Ssam 			switch ($3) {
17010276Ssam 
17110276Ssam 			case MODE_S:
17210276Ssam 				reply(200, "MODE S ok.");
17310276Ssam 				break;
17410276Ssam 
17510276Ssam 			default:
17610276Ssam 				reply(502, "Unimplemented MODE type.");
17710276Ssam 			}
17810276Ssam 		}
17910276Ssam 	|	ALLO SP NUMBER CRLF
18010276Ssam 		= {
18127107Smckusick 			reply(202, "ALLO command ignored.");
18210276Ssam 		}
18336933Skarels 	|	ALLO SP NUMBER SP R SP NUMBER CRLF
18436933Skarels 		= {
18536933Skarels 			reply(202, "ALLO command ignored.");
18636933Skarels 		}
18710276Ssam 	|	RETR check_login SP pathname CRLF
18810276Ssam 		= {
18910302Ssam 			if ($2 && $4 != NULL)
19036933Skarels 				retrieve((char *) 0, (char *) $4);
19110302Ssam 			if ($4 != NULL)
19226494Sminshall 				free((char *) $4);
19310276Ssam 		}
19410276Ssam 	|	STOR check_login SP pathname CRLF
19510276Ssam 		= {
19610302Ssam 			if ($2 && $4 != NULL)
19736304Skarels 				store((char *) $4, "w", 0);
19810302Ssam 			if ($4 != NULL)
19926494Sminshall 				free((char *) $4);
20010276Ssam 		}
20110276Ssam 	|	APPE check_login SP pathname CRLF
20210276Ssam 		= {
20310302Ssam 			if ($2 && $4 != NULL)
20436304Skarels 				store((char *) $4, "a", 0);
20510302Ssam 			if ($4 != NULL)
20626494Sminshall 				free((char *) $4);
20710276Ssam 		}
20810276Ssam 	|	NLST check_login CRLF
20910276Ssam 		= {
21010276Ssam 			if ($2)
21136620Srick 				send_file_list(".");
21210276Ssam 		}
21336620Srick 	|	NLST check_login SP STRING CRLF
21410276Ssam 		= {
215*52999Sbostic 			if ($2 && $4 != NULL)
21636620Srick 				send_file_list((char *) $4);
21710302Ssam 			if ($4 != NULL)
21826494Sminshall 				free((char *) $4);
21910276Ssam 		}
22010276Ssam 	|	LIST check_login CRLF
22110276Ssam 		= {
22210276Ssam 			if ($2)
22336620Srick 				retrieve("/bin/ls -lgA", "");
22410276Ssam 		}
22510276Ssam 	|	LIST check_login SP pathname CRLF
22610276Ssam 		= {
22710302Ssam 			if ($2 && $4 != NULL)
22836620Srick 				retrieve("/bin/ls -lgA %s", (char *) $4);
22910302Ssam 			if ($4 != NULL)
23026494Sminshall 				free((char *) $4);
23110276Ssam 		}
23236933Skarels 	|	STAT check_login SP pathname CRLF
23336933Skarels 		= {
23436933Skarels 			if ($2 && $4 != NULL)
23536933Skarels 				statfilecmd((char *) $4);
23636933Skarels 			if ($4 != NULL)
23736933Skarels 				free((char *) $4);
23836933Skarels 		}
23936933Skarels 	|	STAT CRLF
24036933Skarels 		= {
24136933Skarels 			statcmd();
24236933Skarels 		}
24310276Ssam 	|	DELE check_login SP pathname CRLF
24410276Ssam 		= {
24510302Ssam 			if ($2 && $4 != NULL)
24626494Sminshall 				delete((char *) $4);
24710302Ssam 			if ($4 != NULL)
24826494Sminshall 				free((char *) $4);
24910276Ssam 		}
25030945Scsvsj 	|	RNTO SP pathname CRLF
25130945Scsvsj 		= {
25230945Scsvsj 			if (fromname) {
25330945Scsvsj 				renamecmd(fromname, (char *) $3);
25430945Scsvsj 				free(fromname);
25530945Scsvsj 				fromname = (char *) 0;
25630945Scsvsj 			} else {
25730945Scsvsj 				reply(503, "Bad sequence of commands.");
25830945Scsvsj 			}
25930945Scsvsj 			free((char *) $3);
26030945Scsvsj 		}
26126045Sminshall 	|	ABOR CRLF
26226045Sminshall 		= {
26327107Smckusick 			reply(225, "ABOR command successful.");
26426045Sminshall 		}
26510276Ssam 	|	CWD check_login CRLF
26610276Ssam 		= {
26710276Ssam 			if ($2)
26810276Ssam 				cwd(pw->pw_dir);
26910276Ssam 		}
27010276Ssam 	|	CWD check_login SP pathname CRLF
27110276Ssam 		= {
27210302Ssam 			if ($2 && $4 != NULL)
27326494Sminshall 				cwd((char *) $4);
27410302Ssam 			if ($4 != NULL)
27526494Sminshall 				free((char *) $4);
27610276Ssam 		}
27710276Ssam 	|	HELP CRLF
27810276Ssam 		= {
27936933Skarels 			help(cmdtab, (char *) 0);
28010276Ssam 		}
28110276Ssam 	|	HELP SP STRING CRLF
28210276Ssam 		= {
28336933Skarels 			register char *cp = (char *)$3;
28436933Skarels 
28536933Skarels 			if (strncasecmp(cp, "SITE", 4) == 0) {
28636933Skarels 				cp = (char *)$3 + 4;
28736933Skarels 				if (*cp == ' ')
28836933Skarels 					cp++;
28936933Skarels 				if (*cp)
29036933Skarels 					help(sitetab, cp);
29136933Skarels 				else
29236933Skarels 					help(sitetab, (char *) 0);
29336933Skarels 			} else
29436933Skarels 				help(cmdtab, (char *) $3);
29510276Ssam 		}
29610276Ssam 	|	NOOP CRLF
29710276Ssam 		= {
29827107Smckusick 			reply(200, "NOOP command successful.");
29910276Ssam 		}
30036620Srick 	|	MKD check_login SP pathname CRLF
30110276Ssam 		= {
30210302Ssam 			if ($2 && $4 != NULL)
30326494Sminshall 				makedir((char *) $4);
30410302Ssam 			if ($4 != NULL)
30526494Sminshall 				free((char *) $4);
30610276Ssam 		}
30736620Srick 	|	RMD check_login SP pathname CRLF
30810276Ssam 		= {
30910302Ssam 			if ($2 && $4 != NULL)
31026494Sminshall 				removedir((char *) $4);
31110302Ssam 			if ($4 != NULL)
31226494Sminshall 				free((char *) $4);
31310276Ssam 		}
31436620Srick 	|	PWD check_login CRLF
31510276Ssam 		= {
31610276Ssam 			if ($2)
31710302Ssam 				pwd();
31810276Ssam 		}
31936620Srick 	|	CDUP check_login CRLF
32010276Ssam 		= {
32110276Ssam 			if ($2)
32210276Ssam 				cwd("..");
32310276Ssam 		}
32436933Skarels 	|	SITE SP HELP CRLF
32536933Skarels 		= {
32636933Skarels 			help(sitetab, (char *) 0);
32736933Skarels 		}
32836933Skarels 	|	SITE SP HELP SP STRING CRLF
32936933Skarels 		= {
33036933Skarels 			help(sitetab, (char *) $5);
33136933Skarels 		}
33236933Skarels 	|	SITE SP UMASK check_login CRLF
33336933Skarels 		= {
33436933Skarels 			int oldmask;
33536933Skarels 
33636933Skarels 			if ($4) {
33736933Skarels 				oldmask = umask(0);
33836933Skarels 				(void) umask(oldmask);
33936933Skarels 				reply(200, "Current UMASK is %03o", oldmask);
34036933Skarels 			}
34136933Skarels 		}
34236933Skarels 	|	SITE SP UMASK check_login SP octal_number CRLF
34336933Skarels 		= {
34436933Skarels 			int oldmask;
34536933Skarels 
34636933Skarels 			if ($4) {
34736933Skarels 				if (($6 == -1) || ($6 > 0777)) {
34836933Skarels 					reply(501, "Bad UMASK value");
34936933Skarels 				} else {
35036933Skarels 					oldmask = umask($6);
35136933Skarels 					reply(200,
35236933Skarels 					    "UMASK set to %03o (was %03o)",
35336933Skarels 					    $6, oldmask);
35436933Skarels 				}
35536933Skarels 			}
35636933Skarels 		}
35736933Skarels 	|	SITE SP CHMOD check_login SP octal_number SP pathname CRLF
35836933Skarels 		= {
35936933Skarels 			if ($4 && ($8 != NULL)) {
36036933Skarels 				if ($6 > 0777)
36136933Skarels 					reply(501,
36236933Skarels 				"CHMOD: Mode value must be between 0 and 0777");
36336933Skarels 				else if (chmod((char *) $8, $6) < 0)
36436933Skarels 					perror_reply(550, (char *) $8);
36536933Skarels 				else
36636933Skarels 					reply(200, "CHMOD command successful.");
36736933Skarels 			}
36836933Skarels 			if ($8 != NULL)
36936933Skarels 				free((char *) $8);
37036933Skarels 		}
37136933Skarels 	|	SITE SP IDLE CRLF
37236933Skarels 		= {
37336933Skarels 			reply(200,
37436933Skarels 			    "Current IDLE time limit is %d seconds; max %d",
37536933Skarels 				timeout, maxtimeout);
37636933Skarels 		}
37736933Skarels 	|	SITE SP IDLE SP NUMBER CRLF
37836933Skarels 		= {
37936933Skarels 			if ($5 < 30 || $5 > maxtimeout) {
38036933Skarels 				reply(501,
38136933Skarels 			"Maximum IDLE time must be between 30 and %d seconds",
38236933Skarels 				    maxtimeout);
38336933Skarels 			} else {
38436933Skarels 				timeout = $5;
38536933Skarels 				(void) alarm((unsigned) timeout);
38636933Skarels 				reply(200,
38736933Skarels 				    "Maximum IDLE time set to %d seconds",
38836933Skarels 				    timeout);
38936933Skarels 			}
39036933Skarels 		}
39126045Sminshall 	|	STOU check_login SP pathname CRLF
39226045Sminshall 		= {
39336304Skarels 			if ($2 && $4 != NULL)
39436304Skarels 				store((char *) $4, "w", 1);
39526045Sminshall 			if ($4 != NULL)
39626494Sminshall 				free((char *) $4);
39726045Sminshall 		}
39836552Sbostic 	|	SYST CRLF
39936552Sbostic 		= {
40036640Srick #ifdef unix
40136933Skarels #ifdef BSD
40236552Sbostic 			reply(215, "UNIX Type: L%d Version: BSD-%d",
40336552Sbostic 				NBBY, BSD);
40436933Skarels #else /* BSD */
40536933Skarels 			reply(215, "UNIX Type: L%d", NBBY);
40636933Skarels #endif /* BSD */
40736933Skarels #else /* unix */
40836933Skarels 			reply(215, "UNKNOWN Type: L%d", NBBY);
40936933Skarels #endif /* unix */
41036552Sbostic 		}
41136933Skarels 
41236933Skarels 		/*
41336933Skarels 		 * SIZE is not in RFC959, but Postel has blessed it and
41436933Skarels 		 * it will be in the updated RFC.
41536933Skarels 		 *
41636933Skarels 		 * Return size of file in a format suitable for
41736933Skarels 		 * using with RESTART (we just count bytes).
41836933Skarels 		 */
41936933Skarels 	|	SIZE check_login SP pathname CRLF
42036933Skarels 		= {
42136933Skarels 			if ($2 && $4 != NULL)
42236933Skarels 				sizecmd((char *) $4);
42336933Skarels 			if ($4 != NULL)
42436933Skarels 				free((char *) $4);
42536933Skarels 		}
42636933Skarels 
42736933Skarels 		/*
42836933Skarels 		 * MDTM is not in RFC959, but Postel has blessed it and
42936933Skarels 		 * it will be in the updated RFC.
43036933Skarels 		 *
43136933Skarels 		 * Return modification time of file as an ISO 3307
43236933Skarels 		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
43336933Skarels 		 * where xxx is the fractional second (of any precision,
43436933Skarels 		 * not necessarily 3 digits)
43536933Skarels 		 */
43636933Skarels 	|	MDTM check_login SP pathname CRLF
43736933Skarels 		= {
43836933Skarels 			if ($2 && $4 != NULL) {
43936933Skarels 				struct stat stbuf;
44036933Skarels 				if (stat((char *) $4, &stbuf) < 0)
44136933Skarels 					perror_reply(550, "%s", (char *) $4);
44236933Skarels 				else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
44336933Skarels 					reply(550, "%s: not a plain file.",
44436933Skarels 						(char *) $4);
44536933Skarels 				} else {
44636933Skarels 					register struct tm *t;
44736933Skarels 					struct tm *gmtime();
44836933Skarels 					t = gmtime(&stbuf.st_mtime);
44936933Skarels 					reply(213,
45036933Skarels 					    "19%02d%02d%02d%02d%02d%02d",
45136933Skarels 					    t->tm_year, t->tm_mon+1, t->tm_mday,
45236933Skarels 					    t->tm_hour, t->tm_min, t->tm_sec);
45336933Skarels 				}
45436933Skarels 			}
45536933Skarels 			if ($4 != NULL)
45636933Skarels 				free((char *) $4);
45736933Skarels 		}
45810276Ssam 	|	QUIT CRLF
45910276Ssam 		= {
46010276Ssam 			reply(221, "Goodbye.");
46113246Ssam 			dologout(0);
46210276Ssam 		}
46310276Ssam 	|	error CRLF
46410276Ssam 		= {
46510276Ssam 			yyerrok;
46610276Ssam 		}
46710276Ssam 	;
46830945Scsvsj rcmd:		RNFR check_login SP pathname CRLF
46930945Scsvsj 		= {
47030945Scsvsj 			char *renamefrom();
47130945Scsvsj 
47238135Srick 			restart_point = (off_t) 0;
47330945Scsvsj 			if ($2 && $4) {
47430945Scsvsj 				fromname = renamefrom((char *) $4);
47530945Scsvsj 				if (fromname == (char *) 0 && $4) {
47630945Scsvsj 					free((char *) $4);
47730945Scsvsj 				}
47830945Scsvsj 			}
47930945Scsvsj 		}
48038135Srick 	|	REST SP byte_size CRLF
48138135Srick 		= {
48238135Srick 			long atol();
48338135Srick 
48438135Srick 			fromname = (char *) 0;
48538135Srick 			restart_point = $3;
48638135Srick 			reply(350, "Restarting at %ld. %s", restart_point,
48738135Srick 			    "Send STORE or RETRIEVE to initiate transfer.");
48838135Srick 		}
48930945Scsvsj 	;
490*52999Sbostic 
49110276Ssam username:	STRING
49210276Ssam 	;
49310276Ssam 
49436304Skarels password:	/* empty */
49536304Skarels 		= {
49640184Smckusick 			*(char **)&($$) = (char *)calloc(1, sizeof(char));
49736304Skarels 		}
49836304Skarels 	|	STRING
49910276Ssam 	;
50010276Ssam 
50110276Ssam byte_size:	NUMBER
50210276Ssam 	;
50310276Ssam 
504*52999Sbostic host_port:	NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
50510276Ssam 		NUMBER COMMA NUMBER
50610276Ssam 		= {
50710276Ssam 			register char *a, *p;
50810276Ssam 
50910276Ssam 			a = (char *)&data_dest.sin_addr;
51010276Ssam 			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
51110276Ssam 			p = (char *)&data_dest.sin_port;
51210276Ssam 			p[0] = $9; p[1] = $11;
51310324Ssam 			data_dest.sin_family = AF_INET;
51410276Ssam 		}
51510276Ssam 	;
51610276Ssam 
51710276Ssam form_code:	N
51810276Ssam 	= {
51910276Ssam 		$$ = FORM_N;
52010276Ssam 	}
52110276Ssam 	|	T
52210276Ssam 	= {
52310276Ssam 		$$ = FORM_T;
52410276Ssam 	}
52510276Ssam 	|	C
52610276Ssam 	= {
52710276Ssam 		$$ = FORM_C;
52810276Ssam 	}
52910276Ssam 	;
53010276Ssam 
53110276Ssam type_code:	A
53210276Ssam 	= {
53310276Ssam 		cmd_type = TYPE_A;
53410276Ssam 		cmd_form = FORM_N;
53510276Ssam 	}
53610276Ssam 	|	A SP form_code
53710276Ssam 	= {
53810276Ssam 		cmd_type = TYPE_A;
53910276Ssam 		cmd_form = $3;
54010276Ssam 	}
54110276Ssam 	|	E
54210276Ssam 	= {
54310276Ssam 		cmd_type = TYPE_E;
54410276Ssam 		cmd_form = FORM_N;
54510276Ssam 	}
54610276Ssam 	|	E SP form_code
54710276Ssam 	= {
54810276Ssam 		cmd_type = TYPE_E;
54910276Ssam 		cmd_form = $3;
55010276Ssam 	}
55110276Ssam 	|	I
55210276Ssam 	= {
55310276Ssam 		cmd_type = TYPE_I;
55410276Ssam 	}
55510276Ssam 	|	L
55610276Ssam 	= {
55710276Ssam 		cmd_type = TYPE_L;
55836552Sbostic 		cmd_bytesz = NBBY;
55910276Ssam 	}
56010276Ssam 	|	L SP byte_size
56110276Ssam 	= {
56210276Ssam 		cmd_type = TYPE_L;
56310276Ssam 		cmd_bytesz = $3;
56410276Ssam 	}
56510276Ssam 	/* this is for a bug in the BBN ftp */
56610276Ssam 	|	L byte_size
56710276Ssam 	= {
56810276Ssam 		cmd_type = TYPE_L;
56910276Ssam 		cmd_bytesz = $2;
57010276Ssam 	}
57110276Ssam 	;
57210276Ssam 
57310276Ssam struct_code:	F
57410276Ssam 	= {
57510276Ssam 		$$ = STRU_F;
57610276Ssam 	}
57710276Ssam 	|	R
57810276Ssam 	= {
57910276Ssam 		$$ = STRU_R;
58010276Ssam 	}
58110276Ssam 	|	P
58210276Ssam 	= {
58310276Ssam 		$$ = STRU_P;
58410276Ssam 	}
58510276Ssam 	;
58610276Ssam 
58710276Ssam mode_code:	S
58810276Ssam 	= {
58910276Ssam 		$$ = MODE_S;
59010276Ssam 	}
59110276Ssam 	|	B
59210276Ssam 	= {
59310276Ssam 		$$ = MODE_B;
59410276Ssam 	}
59510276Ssam 	|	C
59610276Ssam 	= {
59710276Ssam 		$$ = MODE_C;
59810276Ssam 	}
59910276Ssam 	;
60010276Ssam 
60110276Ssam pathname:	pathstring
60210276Ssam 	= {
60327107Smckusick 		/*
60427107Smckusick 		 * Problem: this production is used for all pathname
60527107Smckusick 		 * processing, but only gives a 550 error reply.
60627107Smckusick 		 * This is a valid reply in some cases but not in others.
60727107Smckusick 		 */
60836304Skarels 		if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) {
60946669Sbostic 			*(char **)&($$) = *ftpglob((char *) $1);
61010302Ssam 			if (globerr != NULL) {
61110276Ssam 				reply(550, globerr);
61210302Ssam 				$$ = NULL;
61310302Ssam 			}
61426494Sminshall 			free((char *) $1);
61510276Ssam 		} else
61610276Ssam 			$$ = $1;
61710276Ssam 	}
61810276Ssam 	;
61910276Ssam 
62010276Ssam pathstring:	STRING
62110276Ssam 	;
62210276Ssam 
62336933Skarels octal_number:	NUMBER
62436933Skarels 	= {
62536933Skarels 		register int ret, dec, multby, digit;
62636933Skarels 
62736933Skarels 		/*
62836933Skarels 		 * Convert a number that was read as decimal number
62936933Skarels 		 * to what it would be if it had been read as octal.
63036933Skarels 		 */
63136933Skarels 		dec = $1;
63236933Skarels 		multby = 1;
63336933Skarels 		ret = 0;
63436933Skarels 		while (dec) {
63536933Skarels 			digit = dec%10;
63636933Skarels 			if (digit > 7) {
63736933Skarels 				ret = -1;
63836933Skarels 				break;
63936933Skarels 			}
64036933Skarels 			ret += digit * multby;
64136933Skarels 			multby *= 8;
64236933Skarels 			dec /= 10;
64336933Skarels 		}
64436933Skarels 		$$ = ret;
64536933Skarels 	}
64636933Skarels 	;
64736933Skarels 
64810276Ssam check_login:	/* empty */
64910276Ssam 	= {
65010276Ssam 		if (logged_in)
65110276Ssam 			$$ = 1;
65210276Ssam 		else {
65310276Ssam 			reply(530, "Please login with USER and PASS.");
65410276Ssam 			$$ = 0;
65510276Ssam 		}
65610276Ssam 	}
65710276Ssam 	;
65810276Ssam 
65910276Ssam %%
66010276Ssam 
66110276Ssam extern jmp_buf errcatch;
66210276Ssam 
66310276Ssam #define	CMD	0	/* beginning of command */
66410276Ssam #define	ARGS	1	/* expect miscellaneous arguments */
66510276Ssam #define	STR1	2	/* expect SP followed by STRING */
66610276Ssam #define	STR2	3	/* expect STRING */
66736304Skarels #define	OSTR	4	/* optional SP then STRING */
66836304Skarels #define	ZSTR1	5	/* SP then optional STRING */
66936304Skarels #define	ZSTR2	6	/* optional STRING after SP */
67036933Skarels #define	SITECMD	7	/* SITE command */
67136933Skarels #define	NSTR	8	/* Number followed by a string */
67210276Ssam 
67310276Ssam struct tab {
67410276Ssam 	char	*name;
67510276Ssam 	short	token;
67610276Ssam 	short	state;
67710276Ssam 	short	implemented;	/* 1 if command is implemented */
67810276Ssam 	char	*help;
67910276Ssam };
68010276Ssam 
68110276Ssam struct tab cmdtab[] = {		/* In order defined in RFC 765 */
68210276Ssam 	{ "USER", USER, STR1, 1,	"<sp> username" },
68336304Skarels 	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
68410276Ssam 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
68536933Skarels 	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
68610276Ssam 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
68710276Ssam 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
68810276Ssam 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
68926045Sminshall 	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
69010276Ssam 	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
69110276Ssam 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
69210276Ssam 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
69310276Ssam 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
69410276Ssam 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
69510276Ssam 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
69610276Ssam 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
69710276Ssam 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
69810276Ssam 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
69910276Ssam 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
70010276Ssam 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
70110276Ssam 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
70210276Ssam 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
70310276Ssam 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
70438135Srick 	{ "REST", REST, ARGS, 1,	"(restart command)" },
70510276Ssam 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
70610276Ssam 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
70726045Sminshall 	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
70810276Ssam 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
70936304Skarels 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
71010276Ssam 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
71110276Ssam 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
71210276Ssam 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
71336933Skarels 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
71436552Sbostic 	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
71536933Skarels 	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
71610276Ssam 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
71710276Ssam 	{ "NOOP", NOOP, ARGS, 1,	"" },
71836620Srick 	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
71936620Srick 	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
72036620Srick 	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
72136620Srick 	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
72236620Srick 	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
72336620Srick 	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
72436620Srick 	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
72536620Srick 	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
72626045Sminshall 	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
72736933Skarels 	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
72836933Skarels 	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
72910276Ssam 	{ NULL,   0,    0,    0,	0 }
73010276Ssam };
73110276Ssam 
73236933Skarels struct tab sitetab[] = {
73336933Skarels 	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
73436933Skarels 	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
73536933Skarels 	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
73636933Skarels 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
73736933Skarels 	{ NULL,   0,    0,    0,	0 }
73836933Skarels };
73936933Skarels 
74010276Ssam struct tab *
74136933Skarels lookup(p, cmd)
74236933Skarels 	register struct tab *p;
74310276Ssam 	char *cmd;
74410276Ssam {
74510276Ssam 
74636933Skarels 	for (; p->name != NULL; p++)
74710276Ssam 		if (strcmp(cmd, p->name) == 0)
74810276Ssam 			return (p);
74910276Ssam 	return (0);
75010276Ssam }
75110276Ssam 
75213033Ssam #include <arpa/telnet.h>
75310276Ssam 
75410276Ssam /*
75510276Ssam  * getline - a hacked up version of fgets to ignore TELNET escape codes.
75610276Ssam  */
75710276Ssam char *
75810276Ssam getline(s, n, iop)
75910276Ssam 	char *s;
76010276Ssam 	register FILE *iop;
76110276Ssam {
76210276Ssam 	register c;
76326494Sminshall 	register char *cs;
76410276Ssam 
76510276Ssam 	cs = s;
76627751Sminshall /* tmpline may contain saved command from urgent mode interruption */
76726045Sminshall 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
76826045Sminshall 		*cs++ = tmpline[c];
76926045Sminshall 		if (tmpline[c] == '\n') {
77026045Sminshall 			*cs++ = '\0';
77136304Skarels 			if (debug)
77236304Skarels 				syslog(LOG_DEBUG, "command: %s", s);
77326045Sminshall 			tmpline[0] = '\0';
77426045Sminshall 			return(s);
77526045Sminshall 		}
77636304Skarels 		if (c == 0)
77726045Sminshall 			tmpline[0] = '\0';
77826045Sminshall 	}
77936304Skarels 	while ((c = getc(iop)) != EOF) {
78036304Skarels 		c &= 0377;
78136304Skarels 		if (c == IAC) {
78236304Skarels 		    if ((c = getc(iop)) != EOF) {
78336304Skarels 			c &= 0377;
78436304Skarels 			switch (c) {
78527751Sminshall 			case WILL:
78627751Sminshall 			case WONT:
78736277Sbostic 				c = getc(iop);
78836304Skarels 				printf("%c%c%c", IAC, DONT, 0377&c);
78927751Sminshall 				(void) fflush(stdout);
79036304Skarels 				continue;
79127751Sminshall 			case DO:
79227751Sminshall 			case DONT:
79336277Sbostic 				c = getc(iop);
79436304Skarels 				printf("%c%c%c", IAC, WONT, 0377&c);
79527751Sminshall 				(void) fflush(stdout);
79636304Skarels 				continue;
79736304Skarels 			case IAC:
79827751Sminshall 				break;
79927751Sminshall 			default:
80036304Skarels 				continue;	/* ignore command */
80127751Sminshall 			}
80236304Skarels 		    }
80310276Ssam 		}
80436304Skarels 		*cs++ = c;
80536304Skarels 		if (--n <= 0 || c == '\n')
80610276Ssam 			break;
80710276Ssam 	}
80827751Sminshall 	if (c == EOF && cs == s)
80918303Sralph 		return (NULL);
81010276Ssam 	*cs++ = '\0';
811*52999Sbostic 	if (debug) {
812*52999Sbostic 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
813*52999Sbostic 			/* Don't syslog passwords */
814*52999Sbostic 			syslog(LOG_DEBUG, "command: %.5s ???", s);
815*52999Sbostic 		} else {
816*52999Sbostic 			register char *cp;
817*52999Sbostic 			register int len;
818*52999Sbostic 
819*52999Sbostic 			/* Don't syslog trailing CR-LF */
820*52999Sbostic 			len = strlen(s);
821*52999Sbostic 			cp = s + len - 1;
822*52999Sbostic 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
823*52999Sbostic 				--cp;
824*52999Sbostic 				--len;
825*52999Sbostic 			}
826*52999Sbostic 			syslog(LOG_DEBUG, "command: %.*s", len, s);
827*52999Sbostic 		}
828*52999Sbostic 	}
82910276Ssam 	return (s);
83010276Ssam }
83110276Ssam 
83246669Sbostic static void
83311652Ssam toolong()
83411652Ssam {
83511652Ssam 
83611652Ssam 	reply(421,
837*52999Sbostic 	    "Timeout (%d seconds): closing control connection.", timeout);
838*52999Sbostic 	if (logging)
839*52999Sbostic 		syslog(LOG_INFO, "User %s timed out after %d seconds",
840*52999Sbostic 		    (pw ? pw -> pw_name : "unknown"), timeout);
84113246Ssam 	dologout(1);
84211652Ssam }
84311652Ssam 
84410276Ssam yylex()
84510276Ssam {
84610276Ssam 	static int cpos, state;
84736933Skarels 	register char *cp, *cp2;
84810276Ssam 	register struct tab *p;
84910276Ssam 	int n;
85046669Sbostic 	char c, *copy();
85110276Ssam 
85210276Ssam 	for (;;) {
85310276Ssam 		switch (state) {
85410276Ssam 
85510276Ssam 		case CMD:
85626494Sminshall 			(void) signal(SIGALRM, toolong);
85726494Sminshall 			(void) alarm((unsigned) timeout);
85810276Ssam 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
85910276Ssam 				reply(221, "You could at least say goodbye.");
86013246Ssam 				dologout(0);
86110276Ssam 			}
86226494Sminshall 			(void) alarm(0);
86336620Srick #ifdef SETPROCTITLE
86436933Skarels 			if (strncasecmp(cbuf, "PASS", 4) != NULL)
86536933Skarels 				setproctitle("%s: %s", proctitle, cbuf);
86636620Srick #endif /* SETPROCTITLE */
86736324Sbostic 			if ((cp = index(cbuf, '\r'))) {
86836324Sbostic 				*cp++ = '\n';
86936324Sbostic 				*cp = '\0';
87036324Sbostic 			}
87136277Sbostic 			if ((cp = strpbrk(cbuf, " \n")))
87236277Sbostic 				cpos = cp - cbuf;
87336304Skarels 			if (cpos == 0)
87410276Ssam 				cpos = 4;
87510276Ssam 			c = cbuf[cpos];
87610276Ssam 			cbuf[cpos] = '\0';
87710276Ssam 			upper(cbuf);
87836933Skarels 			p = lookup(cmdtab, cbuf);
87910276Ssam 			cbuf[cpos] = c;
88010276Ssam 			if (p != 0) {
88110276Ssam 				if (p->implemented == 0) {
88210276Ssam 					nack(p->name);
88326494Sminshall 					longjmp(errcatch,0);
88410276Ssam 					/* NOTREACHED */
88510276Ssam 				}
88610276Ssam 				state = p->state;
88736933Skarels 				*(char **)&yylval = p->name;
88810276Ssam 				return (p->token);
88910276Ssam 			}
89010276Ssam 			break;
89110276Ssam 
89236933Skarels 		case SITECMD:
89336933Skarels 			if (cbuf[cpos] == ' ') {
89436933Skarels 				cpos++;
89536933Skarels 				return (SP);
89636933Skarels 			}
89736933Skarels 			cp = &cbuf[cpos];
89836933Skarels 			if ((cp2 = strpbrk(cp, " \n")))
89936933Skarels 				cpos = cp2 - cbuf;
90036933Skarels 			c = cbuf[cpos];
90136933Skarels 			cbuf[cpos] = '\0';
90236933Skarels 			upper(cp);
90336933Skarels 			p = lookup(sitetab, cp);
90436933Skarels 			cbuf[cpos] = c;
90536933Skarels 			if (p != 0) {
90636933Skarels 				if (p->implemented == 0) {
90736933Skarels 					state = CMD;
90836933Skarels 					nack(p->name);
90936933Skarels 					longjmp(errcatch,0);
91036933Skarels 					/* NOTREACHED */
91136933Skarels 				}
91236933Skarels 				state = p->state;
91336933Skarels 				*(char **)&yylval = p->name;
91436933Skarels 				return (p->token);
91536933Skarels 			}
91636933Skarels 			state = CMD;
91736933Skarels 			break;
91836933Skarels 
91910276Ssam 		case OSTR:
92010276Ssam 			if (cbuf[cpos] == '\n') {
92110276Ssam 				state = CMD;
92210276Ssam 				return (CRLF);
92310276Ssam 			}
92436317Sbostic 			/* FALLTHROUGH */
92510276Ssam 
92610276Ssam 		case STR1:
92736304Skarels 		case ZSTR1:
92836933Skarels 		dostr1:
92910276Ssam 			if (cbuf[cpos] == ' ') {
93010276Ssam 				cpos++;
93136317Sbostic 				state = state == OSTR ? STR2 : ++state;
93210276Ssam 				return (SP);
93310276Ssam 			}
93410276Ssam 			break;
93510276Ssam 
93636304Skarels 		case ZSTR2:
93736304Skarels 			if (cbuf[cpos] == '\n') {
93836304Skarels 				state = CMD;
93936304Skarels 				return (CRLF);
94036304Skarels 			}
94136933Skarels 			/* FALLTHROUGH */
94236304Skarels 
94310276Ssam 		case STR2:
94410276Ssam 			cp = &cbuf[cpos];
94510276Ssam 			n = strlen(cp);
94610276Ssam 			cpos += n - 1;
94710276Ssam 			/*
94810276Ssam 			 * Make sure the string is nonempty and \n terminated.
94910276Ssam 			 */
95010276Ssam 			if (n > 1 && cbuf[cpos] == '\n') {
95110276Ssam 				cbuf[cpos] = '\0';
95236933Skarels 				*(char **)&yylval = copy(cp);
95310276Ssam 				cbuf[cpos] = '\n';
95410276Ssam 				state = ARGS;
95510276Ssam 				return (STRING);
95610276Ssam 			}
95710276Ssam 			break;
95810276Ssam 
95936933Skarels 		case NSTR:
96036933Skarels 			if (cbuf[cpos] == ' ') {
96136933Skarels 				cpos++;
96236933Skarels 				return (SP);
96336933Skarels 			}
96436933Skarels 			if (isdigit(cbuf[cpos])) {
96536933Skarels 				cp = &cbuf[cpos];
96636933Skarels 				while (isdigit(cbuf[++cpos]))
96736933Skarels 					;
96836933Skarels 				c = cbuf[cpos];
96936933Skarels 				cbuf[cpos] = '\0';
97036933Skarels 				yylval = atoi(cp);
97136933Skarels 				cbuf[cpos] = c;
97236933Skarels 				state = STR1;
97336933Skarels 				return (NUMBER);
97436933Skarels 			}
97536933Skarels 			state = STR1;
97636933Skarels 			goto dostr1;
97736933Skarels 
97810276Ssam 		case ARGS:
97910276Ssam 			if (isdigit(cbuf[cpos])) {
98010276Ssam 				cp = &cbuf[cpos];
98110276Ssam 				while (isdigit(cbuf[++cpos]))
98210276Ssam 					;
98310276Ssam 				c = cbuf[cpos];
98410276Ssam 				cbuf[cpos] = '\0';
98510276Ssam 				yylval = atoi(cp);
98610276Ssam 				cbuf[cpos] = c;
98710276Ssam 				return (NUMBER);
98810276Ssam 			}
98910276Ssam 			switch (cbuf[cpos++]) {
99010276Ssam 
99110276Ssam 			case '\n':
99210276Ssam 				state = CMD;
99310276Ssam 				return (CRLF);
99410276Ssam 
99510276Ssam 			case ' ':
99610276Ssam 				return (SP);
99710276Ssam 
99810276Ssam 			case ',':
99910276Ssam 				return (COMMA);
100010276Ssam 
100110276Ssam 			case 'A':
100210276Ssam 			case 'a':
100310276Ssam 				return (A);
100410276Ssam 
100510276Ssam 			case 'B':
100610276Ssam 			case 'b':
100710276Ssam 				return (B);
100810276Ssam 
100910276Ssam 			case 'C':
101010276Ssam 			case 'c':
101110276Ssam 				return (C);
101210276Ssam 
101310276Ssam 			case 'E':
101410276Ssam 			case 'e':
101510276Ssam 				return (E);
101610276Ssam 
101710276Ssam 			case 'F':
101810276Ssam 			case 'f':
101910276Ssam 				return (F);
102010276Ssam 
102110276Ssam 			case 'I':
102210276Ssam 			case 'i':
102310276Ssam 				return (I);
102410276Ssam 
102510276Ssam 			case 'L':
102610276Ssam 			case 'l':
102710276Ssam 				return (L);
102810276Ssam 
102910276Ssam 			case 'N':
103010276Ssam 			case 'n':
103110276Ssam 				return (N);
103210276Ssam 
103310276Ssam 			case 'P':
103410276Ssam 			case 'p':
103510276Ssam 				return (P);
103610276Ssam 
103710276Ssam 			case 'R':
103810276Ssam 			case 'r':
103910276Ssam 				return (R);
104010276Ssam 
104110276Ssam 			case 'S':
104210276Ssam 			case 's':
104310276Ssam 				return (S);
104410276Ssam 
104510276Ssam 			case 'T':
104610276Ssam 			case 't':
104710276Ssam 				return (T);
104810276Ssam 
104910276Ssam 			}
105010276Ssam 			break;
105110276Ssam 
105210276Ssam 		default:
105310276Ssam 			fatal("Unknown state in scanner.");
105410276Ssam 		}
105526494Sminshall 		yyerror((char *) 0);
105610276Ssam 		state = CMD;
105726494Sminshall 		longjmp(errcatch,0);
105810276Ssam 	}
105910276Ssam }
106010276Ssam 
106110276Ssam upper(s)
106236277Sbostic 	register char *s;
106310276Ssam {
106410276Ssam 	while (*s != '\0') {
106510276Ssam 		if (islower(*s))
106610276Ssam 			*s = toupper(*s);
106710276Ssam 		s++;
106810276Ssam 	}
106910276Ssam }
107010276Ssam 
107136933Skarels char *
107210276Ssam copy(s)
107310276Ssam 	char *s;
107410276Ssam {
107510276Ssam 	char *p;
107610276Ssam 
107726494Sminshall 	p = malloc((unsigned) strlen(s) + 1);
107810276Ssam 	if (p == NULL)
107910276Ssam 		fatal("Ran out of memory.");
108026494Sminshall 	(void) strcpy(p, s);
108136933Skarels 	return (p);
108210276Ssam }
108310276Ssam 
108436933Skarels help(ctab, s)
108536933Skarels 	struct tab *ctab;
108610276Ssam 	char *s;
108710276Ssam {
108810276Ssam 	register struct tab *c;
108910276Ssam 	register int width, NCMDS;
109036933Skarels 	char *type;
109110276Ssam 
109236933Skarels 	if (ctab == sitetab)
109336933Skarels 		type = "SITE ";
109436933Skarels 	else
109536933Skarels 		type = "";
109610276Ssam 	width = 0, NCMDS = 0;
109736933Skarels 	for (c = ctab; c->name != NULL; c++) {
109836933Skarels 		int len = strlen(c->name);
109910276Ssam 
110010276Ssam 		if (len > width)
110110276Ssam 			width = len;
110210276Ssam 		NCMDS++;
110310276Ssam 	}
110410276Ssam 	width = (width + 8) &~ 7;
110510276Ssam 	if (s == 0) {
110610276Ssam 		register int i, j, w;
110710276Ssam 		int columns, lines;
110810276Ssam 
110936933Skarels 		lreply(214, "The following %scommands are recognized %s.",
111036933Skarels 		    type, "(* =>'s unimplemented)");
111110276Ssam 		columns = 76 / width;
111210276Ssam 		if (columns == 0)
111310276Ssam 			columns = 1;
111410276Ssam 		lines = (NCMDS + columns - 1) / columns;
111510276Ssam 		for (i = 0; i < lines; i++) {
111627107Smckusick 			printf("   ");
111710276Ssam 			for (j = 0; j < columns; j++) {
111836933Skarels 				c = ctab + j * lines + i;
111910276Ssam 				printf("%s%c", c->name,
112010276Ssam 					c->implemented ? ' ' : '*');
112136933Skarels 				if (c + lines >= &ctab[NCMDS])
112210276Ssam 					break;
112331132Smckusick 				w = strlen(c->name) + 1;
112410276Ssam 				while (w < width) {
112510276Ssam 					putchar(' ');
112610276Ssam 					w++;
112710276Ssam 				}
112810276Ssam 			}
112910276Ssam 			printf("\r\n");
113010276Ssam 		}
113126494Sminshall 		(void) fflush(stdout);
113210276Ssam 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
113310276Ssam 		return;
113410276Ssam 	}
113510276Ssam 	upper(s);
113636933Skarels 	c = lookup(ctab, s);
113710276Ssam 	if (c == (struct tab *)0) {
113827107Smckusick 		reply(502, "Unknown command %s.", s);
113910276Ssam 		return;
114010276Ssam 	}
114110276Ssam 	if (c->implemented)
114236933Skarels 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
114310276Ssam 	else
114436933Skarels 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
114536933Skarels 		    c->name, c->help);
114610276Ssam }
114736933Skarels 
114836933Skarels sizecmd(filename)
114936933Skarels char *filename;
115036933Skarels {
115136933Skarels 	switch (type) {
115236933Skarels 	case TYPE_L:
115336933Skarels 	case TYPE_I: {
115436933Skarels 		struct stat stbuf;
115536933Skarels 		if (stat(filename, &stbuf) < 0 ||
115636933Skarels 		    (stbuf.st_mode&S_IFMT) != S_IFREG)
115736933Skarels 			reply(550, "%s: not a plain file.", filename);
115836933Skarels 		else
115936933Skarels 			reply(213, "%lu", stbuf.st_size);
116036933Skarels 		break;}
116136933Skarels 	case TYPE_A: {
116236933Skarels 		FILE *fin;
116338135Srick 		register int c;
116438135Srick 		register long count;
116536933Skarels 		struct stat stbuf;
116636933Skarels 		fin = fopen(filename, "r");
116736933Skarels 		if (fin == NULL) {
116836933Skarels 			perror_reply(550, filename);
116936933Skarels 			return;
117036933Skarels 		}
117136933Skarels 		if (fstat(fileno(fin), &stbuf) < 0 ||
117236933Skarels 		    (stbuf.st_mode&S_IFMT) != S_IFREG) {
117336933Skarels 			reply(550, "%s: not a plain file.", filename);
117436933Skarels 			(void) fclose(fin);
117536933Skarels 			return;
117636933Skarels 		}
117736933Skarels 
117836933Skarels 		count = 0;
117936933Skarels 		while((c=getc(fin)) != EOF) {
118036933Skarels 			if (c == '\n')	/* will get expanded to \r\n */
118136933Skarels 				count++;
118236933Skarels 			count++;
118336933Skarels 		}
118436933Skarels 		(void) fclose(fin);
118536933Skarels 
118636933Skarels 		reply(213, "%ld", count);
118736933Skarels 		break;}
118836933Skarels 	default:
118936933Skarels 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
119036933Skarels 	}
119136933Skarels }
1192