xref: /csrg-svn/libexec/ftpd/ftpcmd.y (revision 60087)
110276Ssam /*
236304Skarels  * Copyright (c) 1985, 1988 Regents of the University of California.
333738Sbostic  * All rights reserved.
433738Sbostic  *
542666Sbostic  * %sccs.include.redist.c%
634769Sbostic  *
7*60087Sbostic  *	@(#)ftpcmd.y	5.28 (Berkeley) 05/17/93
822501Sdist  */
922501Sdist 
1022501Sdist /*
1110276Ssam  * Grammar for FTP commands.
1236933Skarels  * See RFC 959.
1310276Ssam  */
1410276Ssam 
1510276Ssam %{
1610276Ssam 
1710276Ssam #ifndef lint
18*60087Sbostic static char sccsid[] = "@(#)ftpcmd.y	5.28 (Berkeley) 05/17/93";
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 
2811652Ssam #include <signal.h>
2910276Ssam #include <setjmp.h>
3026494Sminshall #include <syslog.h>
3136933Skarels #include <time.h>
3246669Sbostic #include <pwd.h>
3354528Sandrew #include <errno.h>
3446669Sbostic #include <unistd.h>
3546669Sbostic #include <stdio.h>
3646669Sbostic #include <ctype.h>
3746669Sbostic #include <stdlib.h>
3846669Sbostic #include <string.h>
3954528Sandrew #include "extern.h"
4010276Ssam 
4110276Ssam extern	struct sockaddr_in data_dest;
4210276Ssam extern	int logged_in;
4310276Ssam extern	struct passwd *pw;
4410276Ssam extern	int guest;
4510276Ssam extern	int logging;
4610276Ssam extern	int type;
4710276Ssam extern	int form;
4810276Ssam extern	int debug;
4911652Ssam extern	int timeout;
5036933Skarels extern	int maxtimeout;
5126045Sminshall extern  int pdata;
5236620Srick extern	char hostname[], remotehost[];
5336933Skarels extern	char proctitle[];
5410276Ssam extern	char *globerr;
5510320Ssam extern	int usedefault;
5626045Sminshall extern  int transflag;
5726045Sminshall extern  char tmpline[];
5810276Ssam 
5938135Srick off_t	restart_point;
6038135Srick 
6110276Ssam static	int cmd_type;
6210276Ssam static	int cmd_form;
6310276Ssam static	int cmd_bytesz;
6436304Skarels char	cbuf[512];
6536304Skarels char	*fromname;
6610276Ssam 
6710276Ssam %}
6810276Ssam 
6910276Ssam %token
7010276Ssam 	A	B	C	E	F	I
7110276Ssam 	L	N	P	R	S	T
7210276Ssam 
7310276Ssam 	SP	CRLF	COMMA	STRING	NUMBER
7410276Ssam 
7510276Ssam 	USER	PASS	ACCT	REIN	QUIT	PORT
7610276Ssam 	PASV	TYPE	STRU	MODE	RETR	STOR
7710276Ssam 	APPE	MLFL	MAIL	MSND	MSOM	MSAM
7810276Ssam 	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
7910276Ssam 	ABOR	DELE	CWD	LIST	NLST	SITE
8036620Srick 	STAT	HELP	NOOP	MKD	RMD	PWD
8136933Skarels 	CDUP	STOU	SMNT	SYST	SIZE	MDTM
8210276Ssam 
8336933Skarels 	UMASK	IDLE	CHMOD
8436933Skarels 
8510276Ssam 	LEXERR
8610276Ssam 
8710276Ssam %start	cmd_list
8810276Ssam 
8910276Ssam %%
9010276Ssam 
9110276Ssam cmd_list:	/* empty */
9210276Ssam 	|	cmd_list cmd
9330945Scsvsj 		= {
9430945Scsvsj 			fromname = (char *) 0;
9538135Srick 			restart_point = (off_t) 0;
9630945Scsvsj 		}
9730945Scsvsj 	|	cmd_list rcmd
9810276Ssam 	;
9910276Ssam 
10010276Ssam cmd:		USER SP username CRLF
10110276Ssam 		= {
10236304Skarels 			user((char *) $3);
10326494Sminshall 			free((char *) $3);
10410276Ssam 		}
10510276Ssam 	|	PASS SP password CRLF
10610276Ssam 		= {
10726494Sminshall 			pass((char *) $3);
10826494Sminshall 			free((char *) $3);
10910276Ssam 		}
11010276Ssam 	|	PORT SP host_port CRLF
11110276Ssam 		= {
11210320Ssam 			usedefault = 0;
11336304Skarels 			if (pdata >= 0) {
11426045Sminshall 				(void) close(pdata);
11536304Skarels 				pdata = -1;
11626045Sminshall 			}
11727107Smckusick 			reply(200, "PORT command successful.");
11810276Ssam 		}
11926045Sminshall 	|	PASV CRLF
12026045Sminshall 		= {
12126045Sminshall 			passive();
12226045Sminshall 		}
12310276Ssam 	|	TYPE SP type_code CRLF
12410276Ssam 		= {
12510276Ssam 			switch (cmd_type) {
12610276Ssam 
12710276Ssam 			case TYPE_A:
12810276Ssam 				if (cmd_form == FORM_N) {
12910276Ssam 					reply(200, "Type set to A.");
13010276Ssam 					type = cmd_type;
13110276Ssam 					form = cmd_form;
13210276Ssam 				} else
13310276Ssam 					reply(504, "Form must be N.");
13410276Ssam 				break;
13510276Ssam 
13610276Ssam 			case TYPE_E:
13710276Ssam 				reply(504, "Type E not implemented.");
13810276Ssam 				break;
13910276Ssam 
14010276Ssam 			case TYPE_I:
14110276Ssam 				reply(200, "Type set to I.");
14210276Ssam 				type = cmd_type;
14310276Ssam 				break;
14410276Ssam 
14510276Ssam 			case TYPE_L:
14636933Skarels #if NBBY == 8
14736933Skarels 				if (cmd_bytesz == 8) {
14810276Ssam 					reply(200,
14936933Skarels 					    "Type set to L (byte size 8).");
15010276Ssam 					type = cmd_type;
15110276Ssam 				} else
15236933Skarels 					reply(504, "Byte size must be 8.");
15336933Skarels #else /* NBBY == 8 */
15436933Skarels 				UNIMPLEMENTED for NBBY != 8
15536933Skarels #endif /* NBBY == 8 */
15610276Ssam 			}
15710276Ssam 		}
15810276Ssam 	|	STRU SP struct_code CRLF
15910276Ssam 		= {
16010276Ssam 			switch ($3) {
16110276Ssam 
16210276Ssam 			case STRU_F:
16310276Ssam 				reply(200, "STRU F ok.");
16410276Ssam 				break;
16510276Ssam 
16610276Ssam 			default:
16727107Smckusick 				reply(504, "Unimplemented STRU type.");
16810276Ssam 			}
16910276Ssam 		}
17010276Ssam 	|	MODE SP mode_code CRLF
17110276Ssam 		= {
17210276Ssam 			switch ($3) {
17310276Ssam 
17410276Ssam 			case MODE_S:
17510276Ssam 				reply(200, "MODE S ok.");
17610276Ssam 				break;
17710276Ssam 
17810276Ssam 			default:
17910276Ssam 				reply(502, "Unimplemented MODE type.");
18010276Ssam 			}
18110276Ssam 		}
18210276Ssam 	|	ALLO SP NUMBER CRLF
18310276Ssam 		= {
18427107Smckusick 			reply(202, "ALLO command ignored.");
18510276Ssam 		}
18636933Skarels 	|	ALLO SP NUMBER SP R SP NUMBER CRLF
18736933Skarels 		= {
18836933Skarels 			reply(202, "ALLO command ignored.");
18936933Skarels 		}
19010276Ssam 	|	RETR check_login SP pathname CRLF
19110276Ssam 		= {
19210302Ssam 			if ($2 && $4 != NULL)
19336933Skarels 				retrieve((char *) 0, (char *) $4);
19410302Ssam 			if ($4 != NULL)
19526494Sminshall 				free((char *) $4);
19610276Ssam 		}
19710276Ssam 	|	STOR check_login SP pathname CRLF
19810276Ssam 		= {
19910302Ssam 			if ($2 && $4 != NULL)
20036304Skarels 				store((char *) $4, "w", 0);
20110302Ssam 			if ($4 != NULL)
20226494Sminshall 				free((char *) $4);
20310276Ssam 		}
20410276Ssam 	|	APPE check_login SP pathname CRLF
20510276Ssam 		= {
20610302Ssam 			if ($2 && $4 != NULL)
20736304Skarels 				store((char *) $4, "a", 0);
20810302Ssam 			if ($4 != NULL)
20926494Sminshall 				free((char *) $4);
21010276Ssam 		}
21110276Ssam 	|	NLST check_login CRLF
21210276Ssam 		= {
21310276Ssam 			if ($2)
21436620Srick 				send_file_list(".");
21510276Ssam 		}
21636620Srick 	|	NLST check_login SP STRING CRLF
21710276Ssam 		= {
21852999Sbostic 			if ($2 && $4 != NULL)
21936620Srick 				send_file_list((char *) $4);
22010302Ssam 			if ($4 != NULL)
22126494Sminshall 				free((char *) $4);
22210276Ssam 		}
22310276Ssam 	|	LIST check_login CRLF
22410276Ssam 		= {
22510276Ssam 			if ($2)
22636620Srick 				retrieve("/bin/ls -lgA", "");
22710276Ssam 		}
22810276Ssam 	|	LIST check_login SP pathname CRLF
22910276Ssam 		= {
23010302Ssam 			if ($2 && $4 != NULL)
23136620Srick 				retrieve("/bin/ls -lgA %s", (char *) $4);
23210302Ssam 			if ($4 != NULL)
23326494Sminshall 				free((char *) $4);
23410276Ssam 		}
23536933Skarels 	|	STAT check_login SP pathname CRLF
23636933Skarels 		= {
23736933Skarels 			if ($2 && $4 != NULL)
23836933Skarels 				statfilecmd((char *) $4);
23936933Skarels 			if ($4 != NULL)
24036933Skarels 				free((char *) $4);
24136933Skarels 		}
24236933Skarels 	|	STAT CRLF
24336933Skarels 		= {
24436933Skarels 			statcmd();
24536933Skarels 		}
24610276Ssam 	|	DELE check_login SP pathname CRLF
24710276Ssam 		= {
24810302Ssam 			if ($2 && $4 != NULL)
24926494Sminshall 				delete((char *) $4);
25010302Ssam 			if ($4 != NULL)
25126494Sminshall 				free((char *) $4);
25210276Ssam 		}
25330945Scsvsj 	|	RNTO SP pathname CRLF
25430945Scsvsj 		= {
25530945Scsvsj 			if (fromname) {
25630945Scsvsj 				renamecmd(fromname, (char *) $3);
25730945Scsvsj 				free(fromname);
25830945Scsvsj 				fromname = (char *) 0;
25930945Scsvsj 			} else {
26030945Scsvsj 				reply(503, "Bad sequence of commands.");
26130945Scsvsj 			}
26230945Scsvsj 			free((char *) $3);
26330945Scsvsj 		}
26426045Sminshall 	|	ABOR CRLF
26526045Sminshall 		= {
26627107Smckusick 			reply(225, "ABOR command successful.");
26726045Sminshall 		}
26810276Ssam 	|	CWD check_login CRLF
26910276Ssam 		= {
27010276Ssam 			if ($2)
27110276Ssam 				cwd(pw->pw_dir);
27210276Ssam 		}
27310276Ssam 	|	CWD check_login SP pathname CRLF
27410276Ssam 		= {
27510302Ssam 			if ($2 && $4 != NULL)
27626494Sminshall 				cwd((char *) $4);
27710302Ssam 			if ($4 != NULL)
27826494Sminshall 				free((char *) $4);
27910276Ssam 		}
28010276Ssam 	|	HELP CRLF
28110276Ssam 		= {
28236933Skarels 			help(cmdtab, (char *) 0);
28310276Ssam 		}
28410276Ssam 	|	HELP SP STRING CRLF
28510276Ssam 		= {
28636933Skarels 			register char *cp = (char *)$3;
28736933Skarels 
28836933Skarels 			if (strncasecmp(cp, "SITE", 4) == 0) {
28936933Skarels 				cp = (char *)$3 + 4;
29036933Skarels 				if (*cp == ' ')
29136933Skarels 					cp++;
29236933Skarels 				if (*cp)
29336933Skarels 					help(sitetab, cp);
29436933Skarels 				else
29536933Skarels 					help(sitetab, (char *) 0);
29636933Skarels 			} else
29736933Skarels 				help(cmdtab, (char *) $3);
29810276Ssam 		}
29910276Ssam 	|	NOOP CRLF
30010276Ssam 		= {
30127107Smckusick 			reply(200, "NOOP command successful.");
30210276Ssam 		}
30336620Srick 	|	MKD check_login SP pathname CRLF
30410276Ssam 		= {
30510302Ssam 			if ($2 && $4 != NULL)
30626494Sminshall 				makedir((char *) $4);
30710302Ssam 			if ($4 != NULL)
30826494Sminshall 				free((char *) $4);
30910276Ssam 		}
31036620Srick 	|	RMD check_login SP pathname CRLF
31110276Ssam 		= {
31210302Ssam 			if ($2 && $4 != NULL)
31326494Sminshall 				removedir((char *) $4);
31410302Ssam 			if ($4 != NULL)
31526494Sminshall 				free((char *) $4);
31610276Ssam 		}
31736620Srick 	|	PWD check_login CRLF
31810276Ssam 		= {
31910276Ssam 			if ($2)
32010302Ssam 				pwd();
32110276Ssam 		}
32236620Srick 	|	CDUP check_login CRLF
32310276Ssam 		= {
32410276Ssam 			if ($2)
32510276Ssam 				cwd("..");
32610276Ssam 		}
32736933Skarels 	|	SITE SP HELP CRLF
32836933Skarels 		= {
32936933Skarels 			help(sitetab, (char *) 0);
33036933Skarels 		}
33136933Skarels 	|	SITE SP HELP SP STRING CRLF
33236933Skarels 		= {
33336933Skarels 			help(sitetab, (char *) $5);
33436933Skarels 		}
33536933Skarels 	|	SITE SP UMASK check_login CRLF
33636933Skarels 		= {
33736933Skarels 			int oldmask;
33836933Skarels 
33936933Skarels 			if ($4) {
34036933Skarels 				oldmask = umask(0);
34136933Skarels 				(void) umask(oldmask);
34236933Skarels 				reply(200, "Current UMASK is %03o", oldmask);
34336933Skarels 			}
34436933Skarels 		}
34536933Skarels 	|	SITE SP UMASK check_login SP octal_number CRLF
34636933Skarels 		= {
34736933Skarels 			int oldmask;
34836933Skarels 
34936933Skarels 			if ($4) {
35036933Skarels 				if (($6 == -1) || ($6 > 0777)) {
35136933Skarels 					reply(501, "Bad UMASK value");
35236933Skarels 				} else {
35336933Skarels 					oldmask = umask($6);
35436933Skarels 					reply(200,
35536933Skarels 					    "UMASK set to %03o (was %03o)",
35636933Skarels 					    $6, oldmask);
35736933Skarels 				}
35836933Skarels 			}
35936933Skarels 		}
36036933Skarels 	|	SITE SP CHMOD check_login SP octal_number SP pathname CRLF
36136933Skarels 		= {
36236933Skarels 			if ($4 && ($8 != NULL)) {
36336933Skarels 				if ($6 > 0777)
36436933Skarels 					reply(501,
36536933Skarels 				"CHMOD: Mode value must be between 0 and 0777");
36636933Skarels 				else if (chmod((char *) $8, $6) < 0)
36736933Skarels 					perror_reply(550, (char *) $8);
36836933Skarels 				else
36936933Skarels 					reply(200, "CHMOD command successful.");
37036933Skarels 			}
37136933Skarels 			if ($8 != NULL)
37236933Skarels 				free((char *) $8);
37336933Skarels 		}
37436933Skarels 	|	SITE SP IDLE CRLF
37536933Skarels 		= {
37636933Skarels 			reply(200,
37736933Skarels 			    "Current IDLE time limit is %d seconds; max %d",
37836933Skarels 				timeout, maxtimeout);
37936933Skarels 		}
38036933Skarels 	|	SITE SP IDLE SP NUMBER CRLF
38136933Skarels 		= {
38236933Skarels 			if ($5 < 30 || $5 > maxtimeout) {
38336933Skarels 				reply(501,
38436933Skarels 			"Maximum IDLE time must be between 30 and %d seconds",
38536933Skarels 				    maxtimeout);
38636933Skarels 			} else {
38736933Skarels 				timeout = $5;
38836933Skarels 				(void) alarm((unsigned) timeout);
38936933Skarels 				reply(200,
39036933Skarels 				    "Maximum IDLE time set to %d seconds",
39136933Skarels 				    timeout);
39236933Skarels 			}
39336933Skarels 		}
39426045Sminshall 	|	STOU check_login SP pathname CRLF
39526045Sminshall 		= {
39636304Skarels 			if ($2 && $4 != NULL)
39736304Skarels 				store((char *) $4, "w", 1);
39826045Sminshall 			if ($4 != NULL)
39926494Sminshall 				free((char *) $4);
40026045Sminshall 		}
40136552Sbostic 	|	SYST CRLF
40236552Sbostic 		= {
40336640Srick #ifdef unix
40436933Skarels #ifdef BSD
40536552Sbostic 			reply(215, "UNIX Type: L%d Version: BSD-%d",
40636552Sbostic 				NBBY, BSD);
40736933Skarels #else /* BSD */
40836933Skarels 			reply(215, "UNIX Type: L%d", NBBY);
40936933Skarels #endif /* BSD */
41036933Skarels #else /* unix */
41136933Skarels 			reply(215, "UNKNOWN Type: L%d", NBBY);
41236933Skarels #endif /* unix */
41336552Sbostic 		}
41436933Skarels 
41536933Skarels 		/*
41636933Skarels 		 * SIZE is not in RFC959, but Postel has blessed it and
41736933Skarels 		 * it will be in the updated RFC.
41836933Skarels 		 *
41936933Skarels 		 * Return size of file in a format suitable for
42036933Skarels 		 * using with RESTART (we just count bytes).
42136933Skarels 		 */
42236933Skarels 	|	SIZE check_login SP pathname CRLF
42336933Skarels 		= {
42436933Skarels 			if ($2 && $4 != NULL)
42536933Skarels 				sizecmd((char *) $4);
42636933Skarels 			if ($4 != NULL)
42736933Skarels 				free((char *) $4);
42836933Skarels 		}
42936933Skarels 
43036933Skarels 		/*
43136933Skarels 		 * MDTM is not in RFC959, but Postel has blessed it and
43236933Skarels 		 * it will be in the updated RFC.
43336933Skarels 		 *
43436933Skarels 		 * Return modification time of file as an ISO 3307
43536933Skarels 		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
43636933Skarels 		 * where xxx is the fractional second (of any precision,
43736933Skarels 		 * not necessarily 3 digits)
43836933Skarels 		 */
43936933Skarels 	|	MDTM check_login SP pathname CRLF
44036933Skarels 		= {
44136933Skarels 			if ($2 && $4 != NULL) {
44236933Skarels 				struct stat stbuf;
44336933Skarels 				if (stat((char *) $4, &stbuf) < 0)
44454528Sandrew 					reply(550, "%s: %s",
44554528Sandrew 					    (char *)$4, strerror(errno));
44636933Skarels 				else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
44736933Skarels 					reply(550, "%s: not a plain file.",
44836933Skarels 						(char *) $4);
44936933Skarels 				} else {
45036933Skarels 					register struct tm *t;
45136933Skarels 					t = gmtime(&stbuf.st_mtime);
45236933Skarels 					reply(213,
45336933Skarels 					    "19%02d%02d%02d%02d%02d%02d",
45436933Skarels 					    t->tm_year, t->tm_mon+1, t->tm_mday,
45536933Skarels 					    t->tm_hour, t->tm_min, t->tm_sec);
45636933Skarels 				}
45736933Skarels 			}
45836933Skarels 			if ($4 != NULL)
45936933Skarels 				free((char *) $4);
46036933Skarels 		}
46110276Ssam 	|	QUIT CRLF
46210276Ssam 		= {
46310276Ssam 			reply(221, "Goodbye.");
46413246Ssam 			dologout(0);
46510276Ssam 		}
46610276Ssam 	|	error CRLF
46710276Ssam 		= {
46810276Ssam 			yyerrok;
46910276Ssam 		}
47010276Ssam 	;
47130945Scsvsj rcmd:		RNFR check_login SP pathname CRLF
47230945Scsvsj 		= {
47330945Scsvsj 			char *renamefrom();
47430945Scsvsj 
47538135Srick 			restart_point = (off_t) 0;
47630945Scsvsj 			if ($2 && $4) {
47730945Scsvsj 				fromname = renamefrom((char *) $4);
47830945Scsvsj 				if (fromname == (char *) 0 && $4) {
47930945Scsvsj 					free((char *) $4);
48030945Scsvsj 				}
48130945Scsvsj 			}
48230945Scsvsj 		}
48338135Srick 	|	REST SP byte_size CRLF
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 	;
49152999Sbostic 
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 
50552999Sbostic 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) {
61046669Sbostic 			*(char **)&($$) = *ftpglob((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)" },
70554058Sandrew 	{ "REST", REST, ARGS, 1,	"<sp> offset (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 
74154528Sandrew static char	*copy __P((char *));
74254528Sandrew static void	 help __P((struct tab *, char *));
74354528Sandrew static struct tab *
74454528Sandrew 		 lookup __P((struct tab *, char *));
74554528Sandrew static void	 sizecmd __P((char *));
74654528Sandrew static void	 toolong __P((int));
74754528Sandrew static int	 yylex __P((void));
74854528Sandrew 
74954528Sandrew static struct tab *
75036933Skarels lookup(p, cmd)
75136933Skarels 	register struct tab *p;
75210276Ssam 	char *cmd;
75310276Ssam {
75410276Ssam 
75536933Skarels 	for (; p->name != NULL; p++)
75610276Ssam 		if (strcmp(cmd, p->name) == 0)
75710276Ssam 			return (p);
75810276Ssam 	return (0);
75910276Ssam }
76010276Ssam 
76113033Ssam #include <arpa/telnet.h>
76210276Ssam 
76310276Ssam /*
76410276Ssam  * getline - a hacked up version of fgets to ignore TELNET escape codes.
76510276Ssam  */
76610276Ssam char *
76710276Ssam getline(s, n, iop)
76810276Ssam 	char *s;
76954528Sandrew 	int n;
77010276Ssam 	register FILE *iop;
77110276Ssam {
77210276Ssam 	register c;
77326494Sminshall 	register char *cs;
77410276Ssam 
77510276Ssam 	cs = s;
77627751Sminshall /* tmpline may contain saved command from urgent mode interruption */
77726045Sminshall 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
77826045Sminshall 		*cs++ = tmpline[c];
77926045Sminshall 		if (tmpline[c] == '\n') {
78026045Sminshall 			*cs++ = '\0';
78136304Skarels 			if (debug)
78236304Skarels 				syslog(LOG_DEBUG, "command: %s", s);
78326045Sminshall 			tmpline[0] = '\0';
78426045Sminshall 			return(s);
78526045Sminshall 		}
78636304Skarels 		if (c == 0)
78726045Sminshall 			tmpline[0] = '\0';
78826045Sminshall 	}
78936304Skarels 	while ((c = getc(iop)) != EOF) {
79036304Skarels 		c &= 0377;
79136304Skarels 		if (c == IAC) {
79236304Skarels 		    if ((c = getc(iop)) != EOF) {
79336304Skarels 			c &= 0377;
79436304Skarels 			switch (c) {
79527751Sminshall 			case WILL:
79627751Sminshall 			case WONT:
79736277Sbostic 				c = getc(iop);
79836304Skarels 				printf("%c%c%c", IAC, DONT, 0377&c);
79927751Sminshall 				(void) fflush(stdout);
80036304Skarels 				continue;
80127751Sminshall 			case DO:
80227751Sminshall 			case DONT:
80336277Sbostic 				c = getc(iop);
80436304Skarels 				printf("%c%c%c", IAC, WONT, 0377&c);
80527751Sminshall 				(void) fflush(stdout);
80636304Skarels 				continue;
80736304Skarels 			case IAC:
80827751Sminshall 				break;
80927751Sminshall 			default:
81036304Skarels 				continue;	/* ignore command */
81127751Sminshall 			}
81236304Skarels 		    }
81310276Ssam 		}
81436304Skarels 		*cs++ = c;
81536304Skarels 		if (--n <= 0 || c == '\n')
81610276Ssam 			break;
81710276Ssam 	}
81827751Sminshall 	if (c == EOF && cs == s)
81918303Sralph 		return (NULL);
82010276Ssam 	*cs++ = '\0';
82152999Sbostic 	if (debug) {
82252999Sbostic 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
82352999Sbostic 			/* Don't syslog passwords */
82452999Sbostic 			syslog(LOG_DEBUG, "command: %.5s ???", s);
82552999Sbostic 		} else {
82652999Sbostic 			register char *cp;
82752999Sbostic 			register int len;
82852999Sbostic 
82952999Sbostic 			/* Don't syslog trailing CR-LF */
83052999Sbostic 			len = strlen(s);
83152999Sbostic 			cp = s + len - 1;
83252999Sbostic 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
83352999Sbostic 				--cp;
83452999Sbostic 				--len;
83552999Sbostic 			}
83652999Sbostic 			syslog(LOG_DEBUG, "command: %.*s", len, s);
83752999Sbostic 		}
83852999Sbostic 	}
83910276Ssam 	return (s);
84010276Ssam }
84110276Ssam 
84246669Sbostic static void
84354528Sandrew toolong(signo)
84454528Sandrew 	int signo;
84511652Ssam {
84611652Ssam 
84711652Ssam 	reply(421,
84852999Sbostic 	    "Timeout (%d seconds): closing control connection.", timeout);
84952999Sbostic 	if (logging)
85052999Sbostic 		syslog(LOG_INFO, "User %s timed out after %d seconds",
85152999Sbostic 		    (pw ? pw -> pw_name : "unknown"), timeout);
85213246Ssam 	dologout(1);
85311652Ssam }
85411652Ssam 
85554528Sandrew static int
85610276Ssam yylex()
85710276Ssam {
85810276Ssam 	static int cpos, state;
85936933Skarels 	register char *cp, *cp2;
86010276Ssam 	register struct tab *p;
86110276Ssam 	int n;
86254528Sandrew 	char c;
86310276Ssam 
86410276Ssam 	for (;;) {
86510276Ssam 		switch (state) {
86610276Ssam 
86710276Ssam 		case CMD:
86826494Sminshall 			(void) signal(SIGALRM, toolong);
86926494Sminshall 			(void) alarm((unsigned) timeout);
87010276Ssam 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
87110276Ssam 				reply(221, "You could at least say goodbye.");
87213246Ssam 				dologout(0);
87310276Ssam 			}
87426494Sminshall 			(void) alarm(0);
87536620Srick #ifdef SETPROCTITLE
87636933Skarels 			if (strncasecmp(cbuf, "PASS", 4) != NULL)
87736933Skarels 				setproctitle("%s: %s", proctitle, cbuf);
87836620Srick #endif /* SETPROCTITLE */
879*60087Sbostic 			if ((cp = strchr(cbuf, '\r'))) {
88036324Sbostic 				*cp++ = '\n';
88136324Sbostic 				*cp = '\0';
88236324Sbostic 			}
88336277Sbostic 			if ((cp = strpbrk(cbuf, " \n")))
88436277Sbostic 				cpos = cp - cbuf;
88536304Skarels 			if (cpos == 0)
88610276Ssam 				cpos = 4;
88710276Ssam 			c = cbuf[cpos];
88810276Ssam 			cbuf[cpos] = '\0';
88910276Ssam 			upper(cbuf);
89036933Skarels 			p = lookup(cmdtab, cbuf);
89110276Ssam 			cbuf[cpos] = c;
89210276Ssam 			if (p != 0) {
89310276Ssam 				if (p->implemented == 0) {
89410276Ssam 					nack(p->name);
89526494Sminshall 					longjmp(errcatch,0);
89610276Ssam 					/* NOTREACHED */
89710276Ssam 				}
89810276Ssam 				state = p->state;
89936933Skarels 				*(char **)&yylval = p->name;
90010276Ssam 				return (p->token);
90110276Ssam 			}
90210276Ssam 			break;
90310276Ssam 
90436933Skarels 		case SITECMD:
90536933Skarels 			if (cbuf[cpos] == ' ') {
90636933Skarels 				cpos++;
90736933Skarels 				return (SP);
90836933Skarels 			}
90936933Skarels 			cp = &cbuf[cpos];
91036933Skarels 			if ((cp2 = strpbrk(cp, " \n")))
91136933Skarels 				cpos = cp2 - cbuf;
91236933Skarels 			c = cbuf[cpos];
91336933Skarels 			cbuf[cpos] = '\0';
91436933Skarels 			upper(cp);
91536933Skarels 			p = lookup(sitetab, cp);
91636933Skarels 			cbuf[cpos] = c;
91736933Skarels 			if (p != 0) {
91836933Skarels 				if (p->implemented == 0) {
91936933Skarels 					state = CMD;
92036933Skarels 					nack(p->name);
92136933Skarels 					longjmp(errcatch,0);
92236933Skarels 					/* NOTREACHED */
92336933Skarels 				}
92436933Skarels 				state = p->state;
92536933Skarels 				*(char **)&yylval = p->name;
92636933Skarels 				return (p->token);
92736933Skarels 			}
92836933Skarels 			state = CMD;
92936933Skarels 			break;
93036933Skarels 
93110276Ssam 		case OSTR:
93210276Ssam 			if (cbuf[cpos] == '\n') {
93310276Ssam 				state = CMD;
93410276Ssam 				return (CRLF);
93510276Ssam 			}
93636317Sbostic 			/* FALLTHROUGH */
93710276Ssam 
93810276Ssam 		case STR1:
93936304Skarels 		case ZSTR1:
94036933Skarels 		dostr1:
94110276Ssam 			if (cbuf[cpos] == ' ') {
94210276Ssam 				cpos++;
94336317Sbostic 				state = state == OSTR ? STR2 : ++state;
94410276Ssam 				return (SP);
94510276Ssam 			}
94610276Ssam 			break;
94710276Ssam 
94836304Skarels 		case ZSTR2:
94936304Skarels 			if (cbuf[cpos] == '\n') {
95036304Skarels 				state = CMD;
95136304Skarels 				return (CRLF);
95236304Skarels 			}
95336933Skarels 			/* FALLTHROUGH */
95436304Skarels 
95510276Ssam 		case STR2:
95610276Ssam 			cp = &cbuf[cpos];
95710276Ssam 			n = strlen(cp);
95810276Ssam 			cpos += n - 1;
95910276Ssam 			/*
96010276Ssam 			 * Make sure the string is nonempty and \n terminated.
96110276Ssam 			 */
96210276Ssam 			if (n > 1 && cbuf[cpos] == '\n') {
96310276Ssam 				cbuf[cpos] = '\0';
96436933Skarels 				*(char **)&yylval = copy(cp);
96510276Ssam 				cbuf[cpos] = '\n';
96610276Ssam 				state = ARGS;
96710276Ssam 				return (STRING);
96810276Ssam 			}
96910276Ssam 			break;
97010276Ssam 
97136933Skarels 		case NSTR:
97236933Skarels 			if (cbuf[cpos] == ' ') {
97336933Skarels 				cpos++;
97436933Skarels 				return (SP);
97536933Skarels 			}
97636933Skarels 			if (isdigit(cbuf[cpos])) {
97736933Skarels 				cp = &cbuf[cpos];
97836933Skarels 				while (isdigit(cbuf[++cpos]))
97936933Skarels 					;
98036933Skarels 				c = cbuf[cpos];
98136933Skarels 				cbuf[cpos] = '\0';
98236933Skarels 				yylval = atoi(cp);
98336933Skarels 				cbuf[cpos] = c;
98436933Skarels 				state = STR1;
98536933Skarels 				return (NUMBER);
98636933Skarels 			}
98736933Skarels 			state = STR1;
98836933Skarels 			goto dostr1;
98936933Skarels 
99010276Ssam 		case ARGS:
99110276Ssam 			if (isdigit(cbuf[cpos])) {
99210276Ssam 				cp = &cbuf[cpos];
99310276Ssam 				while (isdigit(cbuf[++cpos]))
99410276Ssam 					;
99510276Ssam 				c = cbuf[cpos];
99610276Ssam 				cbuf[cpos] = '\0';
99710276Ssam 				yylval = atoi(cp);
99810276Ssam 				cbuf[cpos] = c;
99910276Ssam 				return (NUMBER);
100010276Ssam 			}
100110276Ssam 			switch (cbuf[cpos++]) {
100210276Ssam 
100310276Ssam 			case '\n':
100410276Ssam 				state = CMD;
100510276Ssam 				return (CRLF);
100610276Ssam 
100710276Ssam 			case ' ':
100810276Ssam 				return (SP);
100910276Ssam 
101010276Ssam 			case ',':
101110276Ssam 				return (COMMA);
101210276Ssam 
101310276Ssam 			case 'A':
101410276Ssam 			case 'a':
101510276Ssam 				return (A);
101610276Ssam 
101710276Ssam 			case 'B':
101810276Ssam 			case 'b':
101910276Ssam 				return (B);
102010276Ssam 
102110276Ssam 			case 'C':
102210276Ssam 			case 'c':
102310276Ssam 				return (C);
102410276Ssam 
102510276Ssam 			case 'E':
102610276Ssam 			case 'e':
102710276Ssam 				return (E);
102810276Ssam 
102910276Ssam 			case 'F':
103010276Ssam 			case 'f':
103110276Ssam 				return (F);
103210276Ssam 
103310276Ssam 			case 'I':
103410276Ssam 			case 'i':
103510276Ssam 				return (I);
103610276Ssam 
103710276Ssam 			case 'L':
103810276Ssam 			case 'l':
103910276Ssam 				return (L);
104010276Ssam 
104110276Ssam 			case 'N':
104210276Ssam 			case 'n':
104310276Ssam 				return (N);
104410276Ssam 
104510276Ssam 			case 'P':
104610276Ssam 			case 'p':
104710276Ssam 				return (P);
104810276Ssam 
104910276Ssam 			case 'R':
105010276Ssam 			case 'r':
105110276Ssam 				return (R);
105210276Ssam 
105310276Ssam 			case 'S':
105410276Ssam 			case 's':
105510276Ssam 				return (S);
105610276Ssam 
105710276Ssam 			case 'T':
105810276Ssam 			case 't':
105910276Ssam 				return (T);
106010276Ssam 
106110276Ssam 			}
106210276Ssam 			break;
106310276Ssam 
106410276Ssam 		default:
106510276Ssam 			fatal("Unknown state in scanner.");
106610276Ssam 		}
106726494Sminshall 		yyerror((char *) 0);
106810276Ssam 		state = CMD;
106926494Sminshall 		longjmp(errcatch,0);
107010276Ssam 	}
107110276Ssam }
107210276Ssam 
107354528Sandrew void
107410276Ssam upper(s)
107536277Sbostic 	register char *s;
107610276Ssam {
107710276Ssam 	while (*s != '\0') {
107810276Ssam 		if (islower(*s))
107910276Ssam 			*s = toupper(*s);
108010276Ssam 		s++;
108110276Ssam 	}
108210276Ssam }
108310276Ssam 
108454528Sandrew static char *
108510276Ssam copy(s)
108610276Ssam 	char *s;
108710276Ssam {
108810276Ssam 	char *p;
108910276Ssam 
109026494Sminshall 	p = malloc((unsigned) strlen(s) + 1);
109110276Ssam 	if (p == NULL)
109210276Ssam 		fatal("Ran out of memory.");
109326494Sminshall 	(void) strcpy(p, s);
109436933Skarels 	return (p);
109510276Ssam }
109610276Ssam 
109754528Sandrew static void
109836933Skarels help(ctab, s)
109936933Skarels 	struct tab *ctab;
110010276Ssam 	char *s;
110110276Ssam {
110210276Ssam 	register struct tab *c;
110310276Ssam 	register int width, NCMDS;
110436933Skarels 	char *type;
110510276Ssam 
110636933Skarels 	if (ctab == sitetab)
110736933Skarels 		type = "SITE ";
110836933Skarels 	else
110936933Skarels 		type = "";
111010276Ssam 	width = 0, NCMDS = 0;
111136933Skarels 	for (c = ctab; c->name != NULL; c++) {
111236933Skarels 		int len = strlen(c->name);
111310276Ssam 
111410276Ssam 		if (len > width)
111510276Ssam 			width = len;
111610276Ssam 		NCMDS++;
111710276Ssam 	}
111810276Ssam 	width = (width + 8) &~ 7;
111910276Ssam 	if (s == 0) {
112010276Ssam 		register int i, j, w;
112110276Ssam 		int columns, lines;
112210276Ssam 
112336933Skarels 		lreply(214, "The following %scommands are recognized %s.",
112436933Skarels 		    type, "(* =>'s unimplemented)");
112510276Ssam 		columns = 76 / width;
112610276Ssam 		if (columns == 0)
112710276Ssam 			columns = 1;
112810276Ssam 		lines = (NCMDS + columns - 1) / columns;
112910276Ssam 		for (i = 0; i < lines; i++) {
113027107Smckusick 			printf("   ");
113110276Ssam 			for (j = 0; j < columns; j++) {
113236933Skarels 				c = ctab + j * lines + i;
113310276Ssam 				printf("%s%c", c->name,
113410276Ssam 					c->implemented ? ' ' : '*');
113536933Skarels 				if (c + lines >= &ctab[NCMDS])
113610276Ssam 					break;
113731132Smckusick 				w = strlen(c->name) + 1;
113810276Ssam 				while (w < width) {
113910276Ssam 					putchar(' ');
114010276Ssam 					w++;
114110276Ssam 				}
114210276Ssam 			}
114310276Ssam 			printf("\r\n");
114410276Ssam 		}
114526494Sminshall 		(void) fflush(stdout);
114610276Ssam 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
114710276Ssam 		return;
114810276Ssam 	}
114910276Ssam 	upper(s);
115036933Skarels 	c = lookup(ctab, s);
115110276Ssam 	if (c == (struct tab *)0) {
115227107Smckusick 		reply(502, "Unknown command %s.", s);
115310276Ssam 		return;
115410276Ssam 	}
115510276Ssam 	if (c->implemented)
115636933Skarels 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
115710276Ssam 	else
115836933Skarels 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
115936933Skarels 		    c->name, c->help);
116010276Ssam }
116136933Skarels 
116254528Sandrew static void
116336933Skarels sizecmd(filename)
116454528Sandrew 	char *filename;
116536933Skarels {
116636933Skarels 	switch (type) {
116736933Skarels 	case TYPE_L:
116836933Skarels 	case TYPE_I: {
116936933Skarels 		struct stat stbuf;
117036933Skarels 		if (stat(filename, &stbuf) < 0 ||
117136933Skarels 		    (stbuf.st_mode&S_IFMT) != S_IFREG)
117236933Skarels 			reply(550, "%s: not a plain file.", filename);
117336933Skarels 		else
117436933Skarels 			reply(213, "%lu", stbuf.st_size);
117536933Skarels 		break;}
117636933Skarels 	case TYPE_A: {
117736933Skarels 		FILE *fin;
117838135Srick 		register int c;
117938135Srick 		register long count;
118036933Skarels 		struct stat stbuf;
118136933Skarels 		fin = fopen(filename, "r");
118236933Skarels 		if (fin == NULL) {
118336933Skarels 			perror_reply(550, filename);
118436933Skarels 			return;
118536933Skarels 		}
118636933Skarels 		if (fstat(fileno(fin), &stbuf) < 0 ||
118736933Skarels 		    (stbuf.st_mode&S_IFMT) != S_IFREG) {
118836933Skarels 			reply(550, "%s: not a plain file.", filename);
118936933Skarels 			(void) fclose(fin);
119036933Skarels 			return;
119136933Skarels 		}
119236933Skarels 
119336933Skarels 		count = 0;
119436933Skarels 		while((c=getc(fin)) != EOF) {
119536933Skarels 			if (c == '\n')	/* will get expanded to \r\n */
119636933Skarels 				count++;
119736933Skarels 			count++;
119836933Skarels 		}
119936933Skarels 		(void) fclose(fin);
120036933Skarels 
120136933Skarels 		reply(213, "%ld", count);
120236933Skarels 		break;}
120336933Skarels 	default:
120436933Skarels 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
120536933Skarels 	}
120636933Skarels }
1207