xref: /csrg-svn/libexec/ftpd/ftpcmd.y (revision 66727)
110276Ssam /*
266711Spendry  * 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*66727Spendry  *	@(#)ftpcmd.y	8.3 (Berkeley) 04/06/94
822501Sdist  */
922501Sdist 
1022501Sdist /*
1110276Ssam  * Grammar for FTP commands.
1236933Skarels  * See RFC 959.
1310276Ssam  */
1410276Ssam 
1510276Ssam %{
1610276Ssam 
1710276Ssam #ifndef lint
18*66727Spendry static char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 04/06/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 
2866711Spendry #include <ctype.h>
2966711Spendry #include <errno.h>
3066711Spendry #include <glob.h>
3166711Spendry #include <pwd.h>
3266711Spendry #include <setjmp.h>
3311652Ssam #include <signal.h>
3466711Spendry #include <stdio.h>
3566711Spendry #include <stdlib.h>
3666711Spendry #include <string.h>
3726494Sminshall #include <syslog.h>
3836933Skarels #include <time.h>
3946669Sbostic #include <unistd.h>
4066711Spendry 
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 
7066711Spendry %union {
7166711Spendry 	int	i;
7266711Spendry 	char   *s;
7366711Spendry }
7466711Spendry 
7510276Ssam %token
7610276Ssam 	A	B	C	E	F	I
7710276Ssam 	L	N	P	R	S	T
7810276Ssam 
7966711Spendry 	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 
9366711Spendry %token	<s> STRING
9466711Spendry %token	<i> NUMBER
9566711Spendry 
9666711Spendry %type	<i> check_login octal_number byte_size
9766711Spendry %type	<i> struct_code mode_code type_code form_code
9866711Spendry %type	<s> pathstring pathname password username
9966711Spendry 
10010276Ssam %start	cmd_list
10110276Ssam 
10210276Ssam %%
10310276Ssam 
10466711Spendry cmd_list
10566711Spendry 	: /* empty */
10666711Spendry 	| cmd_list cmd
10766711Spendry 		{
10830945Scsvsj 			fromname = (char *) 0;
10938135Srick 			restart_point = (off_t) 0;
11030945Scsvsj 		}
11166711Spendry 	| cmd_list rcmd
11210276Ssam 	;
11310276Ssam 
11466711Spendry cmd
11566711Spendry 	: USER SP username CRLF
11666711Spendry 		{
11766711Spendry 			user($3);
11866711Spendry 			free($3);
11910276Ssam 		}
12066711Spendry 	| PASS SP password CRLF
12166711Spendry 		{
12266711Spendry 			pass($3);
12366711Spendry 			free($3);
12410276Ssam 		}
12566711Spendry 	| PORT SP host_port CRLF
12666711Spendry 		{
12710320Ssam 			usedefault = 0;
12836304Skarels 			if (pdata >= 0) {
12926045Sminshall 				(void) close(pdata);
13036304Skarels 				pdata = -1;
13126045Sminshall 			}
13227107Smckusick 			reply(200, "PORT command successful.");
13310276Ssam 		}
13466711Spendry 	| PASV CRLF
13566711Spendry 		{
13626045Sminshall 			passive();
13726045Sminshall 		}
13866711Spendry 	| TYPE SP type_code CRLF
13966711Spendry 		{
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 		}
17366711Spendry 	| STRU SP struct_code CRLF
17466711Spendry 		{
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 		}
18566711Spendry 	| MODE SP mode_code CRLF
18666711Spendry 		{
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 		}
19766711Spendry 	| ALLO SP NUMBER CRLF
19866711Spendry 		{
19927107Smckusick 			reply(202, "ALLO command ignored.");
20010276Ssam 		}
20166711Spendry 	| ALLO SP NUMBER SP R SP NUMBER CRLF
20266711Spendry 		{
20336933Skarels 			reply(202, "ALLO command ignored.");
20436933Skarels 		}
20566711Spendry 	| RETR check_login SP pathname CRLF
20666711Spendry 		{
20710302Ssam 			if ($2 && $4 != NULL)
20866711Spendry 				retrieve((char *) 0, $4);
20910302Ssam 			if ($4 != NULL)
21066711Spendry 				free($4);
21110276Ssam 		}
21266711Spendry 	| STOR check_login SP pathname CRLF
21366711Spendry 		{
21410302Ssam 			if ($2 && $4 != NULL)
21566711Spendry 				store($4, "w", 0);
21610302Ssam 			if ($4 != NULL)
21766711Spendry 				free($4);
21810276Ssam 		}
21966711Spendry 	| APPE check_login SP pathname CRLF
22066711Spendry 		{
22110302Ssam 			if ($2 && $4 != NULL)
22266711Spendry 				store($4, "a", 0);
22310302Ssam 			if ($4 != NULL)
22466711Spendry 				free($4);
22510276Ssam 		}
22666711Spendry 	| NLST check_login CRLF
22766711Spendry 		{
22810276Ssam 			if ($2)
22936620Srick 				send_file_list(".");
23010276Ssam 		}
23166711Spendry 	| NLST check_login SP STRING CRLF
23266711Spendry 		{
23352999Sbostic 			if ($2 && $4 != NULL)
23466711Spendry 				send_file_list($4);
23510302Ssam 			if ($4 != NULL)
23666711Spendry 				free($4);
23710276Ssam 		}
23866711Spendry 	| LIST check_login CRLF
23966711Spendry 		{
24010276Ssam 			if ($2)
24136620Srick 				retrieve("/bin/ls -lgA", "");
24210276Ssam 		}
24366711Spendry 	| LIST check_login SP pathname CRLF
24466711Spendry 		{
24510302Ssam 			if ($2 && $4 != NULL)
24666711Spendry 				retrieve("/bin/ls -lgA %s", $4);
24710302Ssam 			if ($4 != NULL)
24866711Spendry 				free($4);
24910276Ssam 		}
25066711Spendry 	| STAT check_login SP pathname CRLF
25166711Spendry 		{
25236933Skarels 			if ($2 && $4 != NULL)
25366711Spendry 				statfilecmd($4);
25436933Skarels 			if ($4 != NULL)
25566711Spendry 				free($4);
25636933Skarels 		}
25766711Spendry 	| STAT CRLF
25866711Spendry 		{
25936933Skarels 			statcmd();
26036933Skarels 		}
26166711Spendry 	| DELE check_login SP pathname CRLF
26266711Spendry 		{
26310302Ssam 			if ($2 && $4 != NULL)
26466711Spendry 				delete($4);
26510302Ssam 			if ($4 != NULL)
26666711Spendry 				free($4);
26710276Ssam 		}
26866711Spendry 	| RNTO SP pathname CRLF
26966711Spendry 		{
27030945Scsvsj 			if (fromname) {
27166711Spendry 				renamecmd(fromname, $3);
27230945Scsvsj 				free(fromname);
27330945Scsvsj 				fromname = (char *) 0;
27430945Scsvsj 			} else {
27530945Scsvsj 				reply(503, "Bad sequence of commands.");
27630945Scsvsj 			}
27766711Spendry 			free($3);
27830945Scsvsj 		}
27966711Spendry 	| ABOR CRLF
28066711Spendry 		{
28127107Smckusick 			reply(225, "ABOR command successful.");
28226045Sminshall 		}
28366711Spendry 	| CWD check_login CRLF
28466711Spendry 		{
28510276Ssam 			if ($2)
28610276Ssam 				cwd(pw->pw_dir);
28710276Ssam 		}
28866711Spendry 	| CWD check_login SP pathname CRLF
28966711Spendry 		{
29010302Ssam 			if ($2 && $4 != NULL)
29166711Spendry 				cwd($4);
29210302Ssam 			if ($4 != NULL)
29366711Spendry 				free($4);
29410276Ssam 		}
29566711Spendry 	| HELP CRLF
29666711Spendry 		{
29736933Skarels 			help(cmdtab, (char *) 0);
29810276Ssam 		}
29966711Spendry 	| HELP SP STRING CRLF
30066711Spendry 		{
30166711Spendry 			char *cp = $3;
30236933Skarels 
30336933Skarels 			if (strncasecmp(cp, "SITE", 4) == 0) {
30466711Spendry 				cp = $3 + 4;
30536933Skarels 				if (*cp == ' ')
30636933Skarels 					cp++;
30736933Skarels 				if (*cp)
30836933Skarels 					help(sitetab, cp);
30936933Skarels 				else
31036933Skarels 					help(sitetab, (char *) 0);
31136933Skarels 			} else
31266711Spendry 				help(cmdtab, $3);
31310276Ssam 		}
31466711Spendry 	| NOOP CRLF
31566711Spendry 		{
31627107Smckusick 			reply(200, "NOOP command successful.");
31710276Ssam 		}
31866711Spendry 	| MKD check_login SP pathname CRLF
31966711Spendry 		{
32010302Ssam 			if ($2 && $4 != NULL)
32166711Spendry 				makedir($4);
32210302Ssam 			if ($4 != NULL)
32366711Spendry 				free($4);
32410276Ssam 		}
32566711Spendry 	| RMD check_login SP pathname CRLF
32666711Spendry 		{
32710302Ssam 			if ($2 && $4 != NULL)
32866711Spendry 				removedir($4);
32910302Ssam 			if ($4 != NULL)
33066711Spendry 				free($4);
33110276Ssam 		}
33266711Spendry 	| PWD check_login CRLF
33366711Spendry 		{
33410276Ssam 			if ($2)
33510302Ssam 				pwd();
33610276Ssam 		}
33766711Spendry 	| CDUP check_login CRLF
33866711Spendry 		{
33910276Ssam 			if ($2)
34010276Ssam 				cwd("..");
34110276Ssam 		}
34266711Spendry 	| SITE SP HELP CRLF
34366711Spendry 		{
34436933Skarels 			help(sitetab, (char *) 0);
34536933Skarels 		}
34666711Spendry 	| SITE SP HELP SP STRING CRLF
34766711Spendry 		{
34866711Spendry 			help(sitetab, $5);
34936933Skarels 		}
35066711Spendry 	| SITE SP UMASK check_login CRLF
35166711Spendry 		{
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 		}
36066711Spendry 	| SITE SP UMASK check_login SP octal_number CRLF
36166711Spendry 		{
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 		}
37566711Spendry 	| SITE SP CHMOD check_login SP octal_number SP pathname CRLF
37666711Spendry 		{
37736933Skarels 			if ($4 && ($8 != NULL)) {
37836933Skarels 				if ($6 > 0777)
37936933Skarels 					reply(501,
38036933Skarels 				"CHMOD: Mode value must be between 0 and 0777");
38166711Spendry 				else if (chmod($8, $6) < 0)
38266711Spendry 					perror_reply(550, $8);
38336933Skarels 				else
38436933Skarels 					reply(200, "CHMOD command successful.");
38536933Skarels 			}
38636933Skarels 			if ($8 != NULL)
38766711Spendry 				free($8);
38836933Skarels 		}
38966711Spendry 	| SITE SP IDLE CRLF
39066711Spendry 		{
39136933Skarels 			reply(200,
39236933Skarels 			    "Current IDLE time limit is %d seconds; max %d",
39336933Skarels 				timeout, maxtimeout);
39436933Skarels 		}
39566711Spendry 	| SITE SP IDLE SP NUMBER CRLF
39666711Spendry 		{
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 		}
40966711Spendry 	| STOU check_login SP pathname CRLF
41066711Spendry 		{
41136304Skarels 			if ($2 && $4 != NULL)
41266711Spendry 				store($4, "w", 1);
41326045Sminshall 			if ($4 != NULL)
41466711Spendry 				free($4);
41526045Sminshall 		}
41666711Spendry 	| SYST CRLF
41766711Spendry 		{
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 		 */
43766711Spendry 	| SIZE check_login SP pathname CRLF
43866711Spendry 		{
43936933Skarels 			if ($2 && $4 != NULL)
44066711Spendry 				sizecmd($4);
44136933Skarels 			if ($4 != NULL)
44266711Spendry 				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 		 */
45466711Spendry 	| MDTM check_login SP pathname CRLF
45566711Spendry 		{
45636933Skarels 			if ($2 && $4 != NULL) {
45736933Skarels 				struct stat stbuf;
45866711Spendry 				if (stat($4, &stbuf) < 0)
45954528Sandrew 					reply(550, "%s: %s",
46066711Spendry 					    $4, strerror(errno));
46166711Spendry 				else if (!S_ISREG(stbuf.st_mode)) {
46266711Spendry 					reply(550, "%s: not a plain file.", $4);
46336933Skarels 				} else {
46466711Spendry 					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)
47366711Spendry 				free($4);
47436933Skarels 		}
47566711Spendry 	| QUIT CRLF
47666711Spendry 		{
47710276Ssam 			reply(221, "Goodbye.");
47813246Ssam 			dologout(0);
47910276Ssam 		}
48066711Spendry 	| error CRLF
48166711Spendry 		{
48210276Ssam 			yyerrok;
48310276Ssam 		}
48410276Ssam 	;
48566711Spendry rcmd
48666711Spendry 	: RNFR check_login SP pathname CRLF
48766711Spendry 		{
48830945Scsvsj 			char *renamefrom();
48930945Scsvsj 
49038135Srick 			restart_point = (off_t) 0;
49130945Scsvsj 			if ($2 && $4) {
49266711Spendry 				fromname = renamefrom($4);
49330945Scsvsj 				if (fromname == (char *) 0 && $4) {
49466711Spendry 					free($4);
49530945Scsvsj 				}
49630945Scsvsj 			}
49730945Scsvsj 		}
49866711Spendry 	| REST SP byte_size CRLF
49966711Spendry 		{
50038135Srick 			fromname = (char *) 0;
501*66727Spendry 			restart_point = $3;	/* XXX $3 is only "int" */
502*66727Spendry 			reply(350, "Restarting at %qd. %s", restart_point,
50338135Srick 			    "Send STORE or RETRIEVE to initiate transfer.");
50438135Srick 		}
50530945Scsvsj 	;
50652999Sbostic 
50766711Spendry username
50866711Spendry 	: STRING
50910276Ssam 	;
51010276Ssam 
51166711Spendry password
51266711Spendry 	: /* empty */
51366711Spendry 		{
51466711Spendry 			$$ = (char *)calloc(1, sizeof(char));
51536304Skarels 		}
51666711Spendry 	| STRING
51710276Ssam 	;
51810276Ssam 
51966711Spendry byte_size
52066711Spendry 	: NUMBER
52110276Ssam 	;
52210276Ssam 
52366711Spendry host_port
52466711Spendry 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
52510276Ssam 		NUMBER COMMA NUMBER
52666711Spendry 		{
52766711Spendry 			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 
53766711Spendry form_code
53866711Spendry 	: N
53966711Spendry 		{
54066711Spendry 			$$ = FORM_N;
54166711Spendry 		}
54266711Spendry 	| T
54366711Spendry 		{
54466711Spendry 			$$ = FORM_T;
54566711Spendry 		}
54666711Spendry 	| C
54766711Spendry 		{
54866711Spendry 			$$ = FORM_C;
54966711Spendry 		}
55010276Ssam 	;
55110276Ssam 
55266711Spendry type_code
55366711Spendry 	: A
55466711Spendry 		{
55566711Spendry 			cmd_type = TYPE_A;
55666711Spendry 			cmd_form = FORM_N;
55766711Spendry 		}
55866711Spendry 	| A SP form_code
55966711Spendry 		{
56066711Spendry 			cmd_type = TYPE_A;
56166711Spendry 			cmd_form = $3;
56266711Spendry 		}
56366711Spendry 	| E
56466711Spendry 		{
56566711Spendry 			cmd_type = TYPE_E;
56666711Spendry 			cmd_form = FORM_N;
56766711Spendry 		}
56866711Spendry 	| E SP form_code
56966711Spendry 		{
57066711Spendry 			cmd_type = TYPE_E;
57166711Spendry 			cmd_form = $3;
57266711Spendry 		}
57366711Spendry 	| I
57466711Spendry 		{
57566711Spendry 			cmd_type = TYPE_I;
57666711Spendry 		}
57766711Spendry 	| L
57866711Spendry 		{
57966711Spendry 			cmd_type = TYPE_L;
58066711Spendry 			cmd_bytesz = NBBY;
58166711Spendry 		}
58266711Spendry 	| L SP byte_size
58366711Spendry 		{
58466711Spendry 			cmd_type = TYPE_L;
58566711Spendry 			cmd_bytesz = $3;
58666711Spendry 		}
58766711Spendry 		/* this is for a bug in the BBN ftp */
58866711Spendry 	| L byte_size
58966711Spendry 		{
59066711Spendry 			cmd_type = TYPE_L;
59166711Spendry 			cmd_bytesz = $2;
59266711Spendry 		}
59310276Ssam 	;
59410276Ssam 
59566711Spendry struct_code
59666711Spendry 	: F
59766711Spendry 		{
59866711Spendry 			$$ = STRU_F;
59966711Spendry 		}
60066711Spendry 	| R
60166711Spendry 		{
60266711Spendry 			$$ = STRU_R;
60366711Spendry 		}
60466711Spendry 	| P
60566711Spendry 		{
60666711Spendry 			$$ = STRU_P;
60766711Spendry 		}
60810276Ssam 	;
60910276Ssam 
61066711Spendry mode_code
61166711Spendry 	: S
61266711Spendry 		{
61366711Spendry 			$$ = MODE_S;
61466711Spendry 		}
61566711Spendry 	| B
61666711Spendry 		{
61766711Spendry 			$$ = MODE_B;
61866711Spendry 		}
61966711Spendry 	| C
62066711Spendry 		{
62166711Spendry 			$$ = MODE_C;
62266711Spendry 		}
62310276Ssam 	;
62410276Ssam 
62566711Spendry pathname
62666711Spendry 	: pathstring
62766711Spendry 		{
62866711Spendry 			/*
62966711Spendry 			 * Problem: this production is used for all pathname
63066711Spendry 			 * processing, but only gives a 550 error reply.
63166711Spendry 			 * This is a valid reply in some cases but not in others.
63266711Spendry 			 */
63366711Spendry 			if (logged_in && $1 && *$1 == '~') {
63466711Spendry 				glob_t gl;
635*66727Spendry 				int flags =
636*66727Spendry 				 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
63766711Spendry 
63866711Spendry 				memset(&gl, 0, sizeof(gl));
639*66727Spendry 				if (glob($1, flags, NULL, &gl) ||
640*66727Spendry 				    gl.gl_pathc == 0) {
64166711Spendry 					reply(550, "not found");
64266711Spendry 					$$ = NULL;
64366711Spendry 				} else {
64466711Spendry 					$$ = strdup(gl.gl_pathv[0]);
64566711Spendry 				}
64666711Spendry 				globfree(&gl);
64766711Spendry 				free($1);
64866711Spendry 			} else
64966711Spendry 				$$ = $1;
65066711Spendry 		}
65110276Ssam 	;
65210276Ssam 
65366711Spendry pathstring
65466711Spendry 	: STRING
65510276Ssam 	;
65610276Ssam 
65766711Spendry octal_number
65866711Spendry 	: NUMBER
65966711Spendry 		{
66066711Spendry 			int ret, dec, multby, digit;
66136933Skarels 
66266711Spendry 			/*
66366711Spendry 			 * Convert a number that was read as decimal number
66466711Spendry 			 * to what it would be if it had been read as octal.
66566711Spendry 			 */
66666711Spendry 			dec = $1;
66766711Spendry 			multby = 1;
66866711Spendry 			ret = 0;
66966711Spendry 			while (dec) {
67066711Spendry 				digit = dec%10;
67166711Spendry 				if (digit > 7) {
67266711Spendry 					ret = -1;
67366711Spendry 					break;
67466711Spendry 				}
67566711Spendry 				ret += digit * multby;
67666711Spendry 				multby *= 8;
67766711Spendry 				dec /= 10;
67836933Skarels 			}
67966711Spendry 			$$ = ret;
68036933Skarels 		}
68136933Skarels 	;
68236933Skarels 
68366711Spendry 
68466711Spendry check_login
68566711Spendry 	: /* empty */
68666711Spendry 		{
68766711Spendry 			if (logged_in)
68866711Spendry 				$$ = 1;
68966711Spendry 			else {
69066711Spendry 				reply(530, "Please login with USER and PASS.");
69166711Spendry 				$$ = 0;
69266711Spendry 			}
69310276Ssam 		}
69410276Ssam 	;
69510276Ssam 
69610276Ssam %%
69710276Ssam 
69810276Ssam extern jmp_buf errcatch;
69910276Ssam 
70010276Ssam #define	CMD	0	/* beginning of command */
70110276Ssam #define	ARGS	1	/* expect miscellaneous arguments */
70210276Ssam #define	STR1	2	/* expect SP followed by STRING */
70310276Ssam #define	STR2	3	/* expect STRING */
70436304Skarels #define	OSTR	4	/* optional SP then STRING */
70536304Skarels #define	ZSTR1	5	/* SP then optional STRING */
70636304Skarels #define	ZSTR2	6	/* optional STRING after SP */
70736933Skarels #define	SITECMD	7	/* SITE command */
70836933Skarels #define	NSTR	8	/* Number followed by a string */
70910276Ssam 
71010276Ssam struct tab {
71110276Ssam 	char	*name;
71210276Ssam 	short	token;
71310276Ssam 	short	state;
71410276Ssam 	short	implemented;	/* 1 if command is implemented */
71510276Ssam 	char	*help;
71610276Ssam };
71710276Ssam 
71810276Ssam struct tab cmdtab[] = {		/* In order defined in RFC 765 */
71910276Ssam 	{ "USER", USER, STR1, 1,	"<sp> username" },
72036304Skarels 	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
72110276Ssam 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
72236933Skarels 	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
72310276Ssam 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
72410276Ssam 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
72510276Ssam 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
72626045Sminshall 	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
72710276Ssam 	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
72810276Ssam 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
72910276Ssam 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
73010276Ssam 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
73110276Ssam 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
73210276Ssam 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
73310276Ssam 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
73410276Ssam 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
73510276Ssam 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
73610276Ssam 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
73710276Ssam 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
73810276Ssam 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
73910276Ssam 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
74010276Ssam 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
74154058Sandrew 	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
74210276Ssam 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
74310276Ssam 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
74426045Sminshall 	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
74510276Ssam 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
74636304Skarels 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
74710276Ssam 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
74810276Ssam 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
74910276Ssam 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
75036933Skarels 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
75136552Sbostic 	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
75236933Skarels 	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
75310276Ssam 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
75410276Ssam 	{ "NOOP", NOOP, ARGS, 1,	"" },
75536620Srick 	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
75636620Srick 	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
75736620Srick 	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
75836620Srick 	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
75936620Srick 	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
76036620Srick 	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
76136620Srick 	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
76236620Srick 	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
76326045Sminshall 	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
76436933Skarels 	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
76536933Skarels 	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
76610276Ssam 	{ NULL,   0,    0,    0,	0 }
76710276Ssam };
76810276Ssam 
76936933Skarels struct tab sitetab[] = {
77036933Skarels 	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
77136933Skarels 	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
77236933Skarels 	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
77336933Skarels 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
77436933Skarels 	{ NULL,   0,    0,    0,	0 }
77536933Skarels };
77636933Skarels 
77754528Sandrew static char	*copy __P((char *));
77854528Sandrew static void	 help __P((struct tab *, char *));
77954528Sandrew static struct tab *
78054528Sandrew 		 lookup __P((struct tab *, char *));
78154528Sandrew static void	 sizecmd __P((char *));
78254528Sandrew static void	 toolong __P((int));
78354528Sandrew static int	 yylex __P((void));
78454528Sandrew 
78554528Sandrew static struct tab *
78636933Skarels lookup(p, cmd)
78766711Spendry 	struct tab *p;
78810276Ssam 	char *cmd;
78910276Ssam {
79010276Ssam 
79136933Skarels 	for (; p->name != NULL; p++)
79210276Ssam 		if (strcmp(cmd, p->name) == 0)
79310276Ssam 			return (p);
79410276Ssam 	return (0);
79510276Ssam }
79610276Ssam 
79713033Ssam #include <arpa/telnet.h>
79810276Ssam 
79910276Ssam /*
80010276Ssam  * getline - a hacked up version of fgets to ignore TELNET escape codes.
80110276Ssam  */
80210276Ssam char *
80310276Ssam getline(s, n, iop)
80410276Ssam 	char *s;
80554528Sandrew 	int n;
80666711Spendry 	FILE *iop;
80710276Ssam {
80866711Spendry 	int c;
80926494Sminshall 	register char *cs;
81010276Ssam 
81110276Ssam 	cs = s;
81227751Sminshall /* tmpline may contain saved command from urgent mode interruption */
81326045Sminshall 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
81426045Sminshall 		*cs++ = tmpline[c];
81526045Sminshall 		if (tmpline[c] == '\n') {
81626045Sminshall 			*cs++ = '\0';
81736304Skarels 			if (debug)
81836304Skarels 				syslog(LOG_DEBUG, "command: %s", s);
81926045Sminshall 			tmpline[0] = '\0';
82026045Sminshall 			return(s);
82126045Sminshall 		}
82236304Skarels 		if (c == 0)
82326045Sminshall 			tmpline[0] = '\0';
82426045Sminshall 	}
82536304Skarels 	while ((c = getc(iop)) != EOF) {
82636304Skarels 		c &= 0377;
82736304Skarels 		if (c == IAC) {
82836304Skarels 		    if ((c = getc(iop)) != EOF) {
82936304Skarels 			c &= 0377;
83036304Skarels 			switch (c) {
83127751Sminshall 			case WILL:
83227751Sminshall 			case WONT:
83336277Sbostic 				c = getc(iop);
83436304Skarels 				printf("%c%c%c", IAC, DONT, 0377&c);
83527751Sminshall 				(void) fflush(stdout);
83636304Skarels 				continue;
83727751Sminshall 			case DO:
83827751Sminshall 			case DONT:
83936277Sbostic 				c = getc(iop);
84036304Skarels 				printf("%c%c%c", IAC, WONT, 0377&c);
84127751Sminshall 				(void) fflush(stdout);
84236304Skarels 				continue;
84336304Skarels 			case IAC:
84427751Sminshall 				break;
84527751Sminshall 			default:
84636304Skarels 				continue;	/* ignore command */
84727751Sminshall 			}
84836304Skarels 		    }
84910276Ssam 		}
85036304Skarels 		*cs++ = c;
85136304Skarels 		if (--n <= 0 || c == '\n')
85210276Ssam 			break;
85310276Ssam 	}
85427751Sminshall 	if (c == EOF && cs == s)
85518303Sralph 		return (NULL);
85610276Ssam 	*cs++ = '\0';
85752999Sbostic 	if (debug) {
85852999Sbostic 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
85952999Sbostic 			/* Don't syslog passwords */
86052999Sbostic 			syslog(LOG_DEBUG, "command: %.5s ???", s);
86152999Sbostic 		} else {
86252999Sbostic 			register char *cp;
86352999Sbostic 			register int len;
86452999Sbostic 
86552999Sbostic 			/* Don't syslog trailing CR-LF */
86652999Sbostic 			len = strlen(s);
86752999Sbostic 			cp = s + len - 1;
86852999Sbostic 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
86952999Sbostic 				--cp;
87052999Sbostic 				--len;
87152999Sbostic 			}
87252999Sbostic 			syslog(LOG_DEBUG, "command: %.*s", len, s);
87352999Sbostic 		}
87452999Sbostic 	}
87510276Ssam 	return (s);
87610276Ssam }
87710276Ssam 
87846669Sbostic static void
87954528Sandrew toolong(signo)
88054528Sandrew 	int signo;
88111652Ssam {
88211652Ssam 
88311652Ssam 	reply(421,
88452999Sbostic 	    "Timeout (%d seconds): closing control connection.", timeout);
88552999Sbostic 	if (logging)
88652999Sbostic 		syslog(LOG_INFO, "User %s timed out after %d seconds",
88752999Sbostic 		    (pw ? pw -> pw_name : "unknown"), timeout);
88813246Ssam 	dologout(1);
88911652Ssam }
89011652Ssam 
89154528Sandrew static int
89210276Ssam yylex()
89310276Ssam {
89410276Ssam 	static int cpos, state;
89566711Spendry 	char *cp, *cp2;
89666711Spendry 	struct tab *p;
89710276Ssam 	int n;
89854528Sandrew 	char c;
89910276Ssam 
90010276Ssam 	for (;;) {
90110276Ssam 		switch (state) {
90210276Ssam 
90310276Ssam 		case CMD:
90426494Sminshall 			(void) signal(SIGALRM, toolong);
90526494Sminshall 			(void) alarm((unsigned) timeout);
90610276Ssam 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
90710276Ssam 				reply(221, "You could at least say goodbye.");
90813246Ssam 				dologout(0);
90910276Ssam 			}
91026494Sminshall 			(void) alarm(0);
91136620Srick #ifdef SETPROCTITLE
91236933Skarels 			if (strncasecmp(cbuf, "PASS", 4) != NULL)
91336933Skarels 				setproctitle("%s: %s", proctitle, cbuf);
91436620Srick #endif /* SETPROCTITLE */
91560087Sbostic 			if ((cp = strchr(cbuf, '\r'))) {
91636324Sbostic 				*cp++ = '\n';
91736324Sbostic 				*cp = '\0';
91836324Sbostic 			}
91936277Sbostic 			if ((cp = strpbrk(cbuf, " \n")))
92036277Sbostic 				cpos = cp - cbuf;
92136304Skarels 			if (cpos == 0)
92210276Ssam 				cpos = 4;
92310276Ssam 			c = cbuf[cpos];
92410276Ssam 			cbuf[cpos] = '\0';
92510276Ssam 			upper(cbuf);
92636933Skarels 			p = lookup(cmdtab, cbuf);
92710276Ssam 			cbuf[cpos] = c;
92810276Ssam 			if (p != 0) {
92910276Ssam 				if (p->implemented == 0) {
93010276Ssam 					nack(p->name);
93126494Sminshall 					longjmp(errcatch,0);
93210276Ssam 					/* NOTREACHED */
93310276Ssam 				}
93410276Ssam 				state = p->state;
93566711Spendry 				yylval.s = p->name;
93610276Ssam 				return (p->token);
93710276Ssam 			}
93810276Ssam 			break;
93910276Ssam 
94036933Skarels 		case SITECMD:
94136933Skarels 			if (cbuf[cpos] == ' ') {
94236933Skarels 				cpos++;
94336933Skarels 				return (SP);
94436933Skarels 			}
94536933Skarels 			cp = &cbuf[cpos];
94636933Skarels 			if ((cp2 = strpbrk(cp, " \n")))
94736933Skarels 				cpos = cp2 - cbuf;
94836933Skarels 			c = cbuf[cpos];
94936933Skarels 			cbuf[cpos] = '\0';
95036933Skarels 			upper(cp);
95136933Skarels 			p = lookup(sitetab, cp);
95236933Skarels 			cbuf[cpos] = c;
95336933Skarels 			if (p != 0) {
95436933Skarels 				if (p->implemented == 0) {
95536933Skarels 					state = CMD;
95636933Skarels 					nack(p->name);
95736933Skarels 					longjmp(errcatch,0);
95836933Skarels 					/* NOTREACHED */
95936933Skarels 				}
96036933Skarels 				state = p->state;
96166711Spendry 				yylval.s = p->name;
96236933Skarels 				return (p->token);
96336933Skarels 			}
96436933Skarels 			state = CMD;
96536933Skarels 			break;
96636933Skarels 
96710276Ssam 		case OSTR:
96810276Ssam 			if (cbuf[cpos] == '\n') {
96910276Ssam 				state = CMD;
97010276Ssam 				return (CRLF);
97110276Ssam 			}
97236317Sbostic 			/* FALLTHROUGH */
97310276Ssam 
97410276Ssam 		case STR1:
97536304Skarels 		case ZSTR1:
97636933Skarels 		dostr1:
97710276Ssam 			if (cbuf[cpos] == ' ') {
97810276Ssam 				cpos++;
97936317Sbostic 				state = state == OSTR ? STR2 : ++state;
98010276Ssam 				return (SP);
98110276Ssam 			}
98210276Ssam 			break;
98310276Ssam 
98436304Skarels 		case ZSTR2:
98536304Skarels 			if (cbuf[cpos] == '\n') {
98636304Skarels 				state = CMD;
98736304Skarels 				return (CRLF);
98836304Skarels 			}
98936933Skarels 			/* FALLTHROUGH */
99036304Skarels 
99110276Ssam 		case STR2:
99210276Ssam 			cp = &cbuf[cpos];
99310276Ssam 			n = strlen(cp);
99410276Ssam 			cpos += n - 1;
99510276Ssam 			/*
99610276Ssam 			 * Make sure the string is nonempty and \n terminated.
99710276Ssam 			 */
99810276Ssam 			if (n > 1 && cbuf[cpos] == '\n') {
99910276Ssam 				cbuf[cpos] = '\0';
100066711Spendry 				yylval.s = copy(cp);
100110276Ssam 				cbuf[cpos] = '\n';
100210276Ssam 				state = ARGS;
100310276Ssam 				return (STRING);
100410276Ssam 			}
100510276Ssam 			break;
100610276Ssam 
100736933Skarels 		case NSTR:
100836933Skarels 			if (cbuf[cpos] == ' ') {
100936933Skarels 				cpos++;
101036933Skarels 				return (SP);
101136933Skarels 			}
101236933Skarels 			if (isdigit(cbuf[cpos])) {
101336933Skarels 				cp = &cbuf[cpos];
101436933Skarels 				while (isdigit(cbuf[++cpos]))
101536933Skarels 					;
101636933Skarels 				c = cbuf[cpos];
101736933Skarels 				cbuf[cpos] = '\0';
101866711Spendry 				yylval.i = atoi(cp);
101936933Skarels 				cbuf[cpos] = c;
102036933Skarels 				state = STR1;
102136933Skarels 				return (NUMBER);
102236933Skarels 			}
102336933Skarels 			state = STR1;
102436933Skarels 			goto dostr1;
102536933Skarels 
102610276Ssam 		case ARGS:
102710276Ssam 			if (isdigit(cbuf[cpos])) {
102810276Ssam 				cp = &cbuf[cpos];
102910276Ssam 				while (isdigit(cbuf[++cpos]))
103010276Ssam 					;
103110276Ssam 				c = cbuf[cpos];
103210276Ssam 				cbuf[cpos] = '\0';
103366711Spendry 				yylval.i = atoi(cp);
103410276Ssam 				cbuf[cpos] = c;
103510276Ssam 				return (NUMBER);
103610276Ssam 			}
103710276Ssam 			switch (cbuf[cpos++]) {
103810276Ssam 
103910276Ssam 			case '\n':
104010276Ssam 				state = CMD;
104110276Ssam 				return (CRLF);
104210276Ssam 
104310276Ssam 			case ' ':
104410276Ssam 				return (SP);
104510276Ssam 
104610276Ssam 			case ',':
104710276Ssam 				return (COMMA);
104810276Ssam 
104910276Ssam 			case 'A':
105010276Ssam 			case 'a':
105110276Ssam 				return (A);
105210276Ssam 
105310276Ssam 			case 'B':
105410276Ssam 			case 'b':
105510276Ssam 				return (B);
105610276Ssam 
105710276Ssam 			case 'C':
105810276Ssam 			case 'c':
105910276Ssam 				return (C);
106010276Ssam 
106110276Ssam 			case 'E':
106210276Ssam 			case 'e':
106310276Ssam 				return (E);
106410276Ssam 
106510276Ssam 			case 'F':
106610276Ssam 			case 'f':
106710276Ssam 				return (F);
106810276Ssam 
106910276Ssam 			case 'I':
107010276Ssam 			case 'i':
107110276Ssam 				return (I);
107210276Ssam 
107310276Ssam 			case 'L':
107410276Ssam 			case 'l':
107510276Ssam 				return (L);
107610276Ssam 
107710276Ssam 			case 'N':
107810276Ssam 			case 'n':
107910276Ssam 				return (N);
108010276Ssam 
108110276Ssam 			case 'P':
108210276Ssam 			case 'p':
108310276Ssam 				return (P);
108410276Ssam 
108510276Ssam 			case 'R':
108610276Ssam 			case 'r':
108710276Ssam 				return (R);
108810276Ssam 
108910276Ssam 			case 'S':
109010276Ssam 			case 's':
109110276Ssam 				return (S);
109210276Ssam 
109310276Ssam 			case 'T':
109410276Ssam 			case 't':
109510276Ssam 				return (T);
109610276Ssam 
109710276Ssam 			}
109810276Ssam 			break;
109910276Ssam 
110010276Ssam 		default:
110110276Ssam 			fatal("Unknown state in scanner.");
110210276Ssam 		}
110326494Sminshall 		yyerror((char *) 0);
110410276Ssam 		state = CMD;
110526494Sminshall 		longjmp(errcatch,0);
110610276Ssam 	}
110710276Ssam }
110810276Ssam 
110954528Sandrew void
111010276Ssam upper(s)
111166711Spendry 	char *s;
111210276Ssam {
111310276Ssam 	while (*s != '\0') {
111410276Ssam 		if (islower(*s))
111510276Ssam 			*s = toupper(*s);
111610276Ssam 		s++;
111710276Ssam 	}
111810276Ssam }
111910276Ssam 
112054528Sandrew static char *
112110276Ssam copy(s)
112210276Ssam 	char *s;
112310276Ssam {
112410276Ssam 	char *p;
112510276Ssam 
112626494Sminshall 	p = malloc((unsigned) strlen(s) + 1);
112710276Ssam 	if (p == NULL)
112810276Ssam 		fatal("Ran out of memory.");
112926494Sminshall 	(void) strcpy(p, s);
113036933Skarels 	return (p);
113110276Ssam }
113210276Ssam 
113354528Sandrew static void
113436933Skarels help(ctab, s)
113536933Skarels 	struct tab *ctab;
113610276Ssam 	char *s;
113710276Ssam {
113866711Spendry 	struct tab *c;
113966711Spendry 	int width, NCMDS;
114036933Skarels 	char *type;
114110276Ssam 
114236933Skarels 	if (ctab == sitetab)
114336933Skarels 		type = "SITE ";
114436933Skarels 	else
114536933Skarels 		type = "";
114610276Ssam 	width = 0, NCMDS = 0;
114736933Skarels 	for (c = ctab; c->name != NULL; c++) {
114836933Skarels 		int len = strlen(c->name);
114910276Ssam 
115010276Ssam 		if (len > width)
115110276Ssam 			width = len;
115210276Ssam 		NCMDS++;
115310276Ssam 	}
115410276Ssam 	width = (width + 8) &~ 7;
115510276Ssam 	if (s == 0) {
115666711Spendry 		int i, j, w;
115710276Ssam 		int columns, lines;
115810276Ssam 
115936933Skarels 		lreply(214, "The following %scommands are recognized %s.",
116036933Skarels 		    type, "(* =>'s unimplemented)");
116110276Ssam 		columns = 76 / width;
116210276Ssam 		if (columns == 0)
116310276Ssam 			columns = 1;
116410276Ssam 		lines = (NCMDS + columns - 1) / columns;
116510276Ssam 		for (i = 0; i < lines; i++) {
116627107Smckusick 			printf("   ");
116710276Ssam 			for (j = 0; j < columns; j++) {
116836933Skarels 				c = ctab + j * lines + i;
116910276Ssam 				printf("%s%c", c->name,
117010276Ssam 					c->implemented ? ' ' : '*');
117136933Skarels 				if (c + lines >= &ctab[NCMDS])
117210276Ssam 					break;
117331132Smckusick 				w = strlen(c->name) + 1;
117410276Ssam 				while (w < width) {
117510276Ssam 					putchar(' ');
117610276Ssam 					w++;
117710276Ssam 				}
117810276Ssam 			}
117910276Ssam 			printf("\r\n");
118010276Ssam 		}
118126494Sminshall 		(void) fflush(stdout);
118210276Ssam 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
118310276Ssam 		return;
118410276Ssam 	}
118510276Ssam 	upper(s);
118636933Skarels 	c = lookup(ctab, s);
118710276Ssam 	if (c == (struct tab *)0) {
118827107Smckusick 		reply(502, "Unknown command %s.", s);
118910276Ssam 		return;
119010276Ssam 	}
119110276Ssam 	if (c->implemented)
119236933Skarels 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
119310276Ssam 	else
119436933Skarels 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
119536933Skarels 		    c->name, c->help);
119610276Ssam }
119736933Skarels 
119854528Sandrew static void
119936933Skarels sizecmd(filename)
120054528Sandrew 	char *filename;
120136933Skarels {
120236933Skarels 	switch (type) {
120336933Skarels 	case TYPE_L:
120436933Skarels 	case TYPE_I: {
120536933Skarels 		struct stat stbuf;
120666711Spendry 		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
120736933Skarels 			reply(550, "%s: not a plain file.", filename);
120836933Skarels 		else
120966711Spendry 			reply(213, "%qu", stbuf.st_size);
121066711Spendry 		break; }
121136933Skarels 	case TYPE_A: {
121236933Skarels 		FILE *fin;
121366711Spendry 		int c;
121466711Spendry 		off_t count;
121536933Skarels 		struct stat stbuf;
121636933Skarels 		fin = fopen(filename, "r");
121736933Skarels 		if (fin == NULL) {
121836933Skarels 			perror_reply(550, filename);
121936933Skarels 			return;
122036933Skarels 		}
122166711Spendry 		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
122236933Skarels 			reply(550, "%s: not a plain file.", filename);
122336933Skarels 			(void) fclose(fin);
122436933Skarels 			return;
122536933Skarels 		}
122636933Skarels 
122736933Skarels 		count = 0;
122836933Skarels 		while((c=getc(fin)) != EOF) {
122936933Skarels 			if (c == '\n')	/* will get expanded to \r\n */
123036933Skarels 				count++;
123136933Skarels 			count++;
123236933Skarels 		}
123336933Skarels 		(void) fclose(fin);
123436933Skarels 
123566711Spendry 		reply(213, "%qd", count);
123666711Spendry 		break; }
123736933Skarels 	default:
123836933Skarels 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
123936933Skarels 	}
124036933Skarels }
1241