xref: /csrg-svn/libexec/ftpd/ftpcmd.y (revision 38135)
110276Ssam /*
236304Skarels  * Copyright (c) 1985, 1988 Regents of the University of California.
333738Sbostic  * All rights reserved.
433738Sbostic  *
533738Sbostic  * Redistribution and use in source and binary forms are permitted
634769Sbostic  * provided that the above copyright notice and this paragraph are
734769Sbostic  * duplicated in all such forms and that any documentation,
834769Sbostic  * advertising materials, and other materials related to such
934769Sbostic  * distribution and use acknowledge that the software was developed
1034769Sbostic  * by the University of California, Berkeley.  The name of the
1134769Sbostic  * University may not be used to endorse or promote products derived
1234769Sbostic  * from this software without specific prior written permission.
1334769Sbostic  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
1434769Sbostic  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
1536933Skarels  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
1634769Sbostic  *
17*38135Srick  *	@(#)ftpcmd.y	5.21 (Berkeley) 05/26/89
1822501Sdist  */
1922501Sdist 
2022501Sdist /*
2110276Ssam  * Grammar for FTP commands.
2236933Skarels  * See RFC 959.
2310276Ssam  */
2410276Ssam 
2510276Ssam %{
2610276Ssam 
2710276Ssam #ifndef lint
28*38135Srick static char sccsid[] = "@(#)ftpcmd.y	5.21 (Berkeley) 05/26/89";
2933738Sbostic #endif /* not lint */
3010276Ssam 
3136552Sbostic #include <sys/param.h>
3210276Ssam #include <sys/socket.h>
3310276Ssam 
3410276Ssam #include <netinet/in.h>
3510276Ssam 
3613033Ssam #include <arpa/ftp.h>
3713033Ssam 
3810276Ssam #include <stdio.h>
3911652Ssam #include <signal.h>
4010276Ssam #include <ctype.h>
4110276Ssam #include <pwd.h>
4210276Ssam #include <setjmp.h>
4326494Sminshall #include <syslog.h>
4436933Skarels #include <sys/stat.h>
4536933Skarels #include <time.h>
4610276Ssam 
4710276Ssam extern	struct sockaddr_in data_dest;
4810276Ssam extern	int logged_in;
4910276Ssam extern	struct passwd *pw;
5010276Ssam extern	int guest;
5110276Ssam extern	int logging;
5210276Ssam extern	int type;
5310276Ssam extern	int form;
5410276Ssam extern	int debug;
5511652Ssam extern	int timeout;
5636933Skarels extern	int maxtimeout;
5726045Sminshall extern  int pdata;
5836620Srick extern	char hostname[], remotehost[];
5936933Skarels extern	char proctitle[];
6010276Ssam extern	char *globerr;
6110320Ssam extern	int usedefault;
6226045Sminshall extern  int transflag;
6326045Sminshall extern  char tmpline[];
6410276Ssam char	**glob();
6510276Ssam 
66*38135Srick off_t	restart_point;
67*38135Srick 
6810276Ssam static	int cmd_type;
6910276Ssam static	int cmd_form;
7010276Ssam static	int cmd_bytesz;
7136304Skarels char	cbuf[512];
7236304Skarels char	*fromname;
7310276Ssam 
7410276Ssam char	*index();
7510276Ssam %}
7610276Ssam 
7710276Ssam %token
7810276Ssam 	A	B	C	E	F	I
7910276Ssam 	L	N	P	R	S	T
8010276Ssam 
8110276Ssam 	SP	CRLF	COMMA	STRING	NUMBER
8210276Ssam 
8310276Ssam 	USER	PASS	ACCT	REIN	QUIT	PORT
8410276Ssam 	PASV	TYPE	STRU	MODE	RETR	STOR
8510276Ssam 	APPE	MLFL	MAIL	MSND	MSOM	MSAM
8610276Ssam 	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
8710276Ssam 	ABOR	DELE	CWD	LIST	NLST	SITE
8836620Srick 	STAT	HELP	NOOP	MKD	RMD	PWD
8936933Skarels 	CDUP	STOU	SMNT	SYST	SIZE	MDTM
9010276Ssam 
9136933Skarels 	UMASK	IDLE	CHMOD
9236933Skarels 
9310276Ssam 	LEXERR
9410276Ssam 
9510276Ssam %start	cmd_list
9610276Ssam 
9710276Ssam %%
9810276Ssam 
9910276Ssam cmd_list:	/* empty */
10010276Ssam 	|	cmd_list cmd
10130945Scsvsj 		= {
10230945Scsvsj 			fromname = (char *) 0;
103*38135Srick 			restart_point = (off_t) 0;
10430945Scsvsj 		}
10530945Scsvsj 	|	cmd_list rcmd
10610276Ssam 	;
10710276Ssam 
10810276Ssam cmd:		USER SP username CRLF
10910276Ssam 		= {
11036304Skarels 			user((char *) $3);
11126494Sminshall 			free((char *) $3);
11210276Ssam 		}
11310276Ssam 	|	PASS SP password CRLF
11410276Ssam 		= {
11526494Sminshall 			pass((char *) $3);
11626494Sminshall 			free((char *) $3);
11710276Ssam 		}
11810276Ssam 	|	PORT SP host_port CRLF
11910276Ssam 		= {
12010320Ssam 			usedefault = 0;
12136304Skarels 			if (pdata >= 0) {
12226045Sminshall 				(void) close(pdata);
12336304Skarels 				pdata = -1;
12426045Sminshall 			}
12527107Smckusick 			reply(200, "PORT command successful.");
12610276Ssam 		}
12726045Sminshall 	|	PASV CRLF
12826045Sminshall 		= {
12926045Sminshall 			passive();
13026045Sminshall 		}
13110276Ssam 	|	TYPE SP type_code CRLF
13210276Ssam 		= {
13310276Ssam 			switch (cmd_type) {
13410276Ssam 
13510276Ssam 			case TYPE_A:
13610276Ssam 				if (cmd_form == FORM_N) {
13710276Ssam 					reply(200, "Type set to A.");
13810276Ssam 					type = cmd_type;
13910276Ssam 					form = cmd_form;
14010276Ssam 				} else
14110276Ssam 					reply(504, "Form must be N.");
14210276Ssam 				break;
14310276Ssam 
14410276Ssam 			case TYPE_E:
14510276Ssam 				reply(504, "Type E not implemented.");
14610276Ssam 				break;
14710276Ssam 
14810276Ssam 			case TYPE_I:
14910276Ssam 				reply(200, "Type set to I.");
15010276Ssam 				type = cmd_type;
15110276Ssam 				break;
15210276Ssam 
15310276Ssam 			case TYPE_L:
15436933Skarels #if NBBY == 8
15536933Skarels 				if (cmd_bytesz == 8) {
15610276Ssam 					reply(200,
15736933Skarels 					    "Type set to L (byte size 8).");
15810276Ssam 					type = cmd_type;
15910276Ssam 				} else
16036933Skarels 					reply(504, "Byte size must be 8.");
16136933Skarels #else /* NBBY == 8 */
16236933Skarels 				UNIMPLEMENTED for NBBY != 8
16336933Skarels #endif /* NBBY == 8 */
16410276Ssam 			}
16510276Ssam 		}
16610276Ssam 	|	STRU SP struct_code CRLF
16710276Ssam 		= {
16810276Ssam 			switch ($3) {
16910276Ssam 
17010276Ssam 			case STRU_F:
17110276Ssam 				reply(200, "STRU F ok.");
17210276Ssam 				break;
17310276Ssam 
17410276Ssam 			default:
17527107Smckusick 				reply(504, "Unimplemented STRU type.");
17610276Ssam 			}
17710276Ssam 		}
17810276Ssam 	|	MODE SP mode_code CRLF
17910276Ssam 		= {
18010276Ssam 			switch ($3) {
18110276Ssam 
18210276Ssam 			case MODE_S:
18310276Ssam 				reply(200, "MODE S ok.");
18410276Ssam 				break;
18510276Ssam 
18610276Ssam 			default:
18710276Ssam 				reply(502, "Unimplemented MODE type.");
18810276Ssam 			}
18910276Ssam 		}
19010276Ssam 	|	ALLO SP NUMBER CRLF
19110276Ssam 		= {
19227107Smckusick 			reply(202, "ALLO command ignored.");
19310276Ssam 		}
19436933Skarels 	|	ALLO SP NUMBER SP R SP NUMBER CRLF
19536933Skarels 		= {
19636933Skarels 			reply(202, "ALLO command ignored.");
19736933Skarels 		}
19810276Ssam 	|	RETR check_login SP pathname CRLF
19910276Ssam 		= {
20010302Ssam 			if ($2 && $4 != NULL)
20136933Skarels 				retrieve((char *) 0, (char *) $4);
20210302Ssam 			if ($4 != NULL)
20326494Sminshall 				free((char *) $4);
20410276Ssam 		}
20510276Ssam 	|	STOR check_login SP pathname CRLF
20610276Ssam 		= {
20710302Ssam 			if ($2 && $4 != NULL)
20836304Skarels 				store((char *) $4, "w", 0);
20910302Ssam 			if ($4 != NULL)
21026494Sminshall 				free((char *) $4);
21110276Ssam 		}
21210276Ssam 	|	APPE check_login SP pathname CRLF
21310276Ssam 		= {
21410302Ssam 			if ($2 && $4 != NULL)
21536304Skarels 				store((char *) $4, "a", 0);
21610302Ssam 			if ($4 != NULL)
21726494Sminshall 				free((char *) $4);
21810276Ssam 		}
21910276Ssam 	|	NLST check_login CRLF
22010276Ssam 		= {
22110276Ssam 			if ($2)
22236620Srick 				send_file_list(".");
22310276Ssam 		}
22436620Srick 	|	NLST check_login SP STRING CRLF
22510276Ssam 		= {
22636620Srick 			if ($2 && $4 != NULL)
22736620Srick 				send_file_list((char *) $4);
22810302Ssam 			if ($4 != NULL)
22926494Sminshall 				free((char *) $4);
23010276Ssam 		}
23110276Ssam 	|	LIST check_login CRLF
23210276Ssam 		= {
23310276Ssam 			if ($2)
23436620Srick 				retrieve("/bin/ls -lgA", "");
23510276Ssam 		}
23610276Ssam 	|	LIST check_login SP pathname CRLF
23710276Ssam 		= {
23810302Ssam 			if ($2 && $4 != NULL)
23936620Srick 				retrieve("/bin/ls -lgA %s", (char *) $4);
24010302Ssam 			if ($4 != NULL)
24126494Sminshall 				free((char *) $4);
24210276Ssam 		}
24336933Skarels 	|	STAT check_login SP pathname CRLF
24436933Skarels 		= {
24536933Skarels 			if ($2 && $4 != NULL)
24636933Skarels 				statfilecmd((char *) $4);
24736933Skarels 			if ($4 != NULL)
24836933Skarels 				free((char *) $4);
24936933Skarels 		}
25036933Skarels 	|	STAT CRLF
25136933Skarels 		= {
25236933Skarels 			statcmd();
25336933Skarels 		}
25410276Ssam 	|	DELE check_login SP pathname CRLF
25510276Ssam 		= {
25610302Ssam 			if ($2 && $4 != NULL)
25726494Sminshall 				delete((char *) $4);
25810302Ssam 			if ($4 != NULL)
25926494Sminshall 				free((char *) $4);
26010276Ssam 		}
26130945Scsvsj 	|	RNTO SP pathname CRLF
26230945Scsvsj 		= {
26330945Scsvsj 			if (fromname) {
26430945Scsvsj 				renamecmd(fromname, (char *) $3);
26530945Scsvsj 				free(fromname);
26630945Scsvsj 				fromname = (char *) 0;
26730945Scsvsj 			} else {
26830945Scsvsj 				reply(503, "Bad sequence of commands.");
26930945Scsvsj 			}
27030945Scsvsj 			free((char *) $3);
27130945Scsvsj 		}
27226045Sminshall 	|	ABOR CRLF
27326045Sminshall 		= {
27427107Smckusick 			reply(225, "ABOR command successful.");
27526045Sminshall 		}
27610276Ssam 	|	CWD check_login CRLF
27710276Ssam 		= {
27810276Ssam 			if ($2)
27910276Ssam 				cwd(pw->pw_dir);
28010276Ssam 		}
28110276Ssam 	|	CWD check_login SP pathname CRLF
28210276Ssam 		= {
28310302Ssam 			if ($2 && $4 != NULL)
28426494Sminshall 				cwd((char *) $4);
28510302Ssam 			if ($4 != NULL)
28626494Sminshall 				free((char *) $4);
28710276Ssam 		}
28810276Ssam 	|	HELP CRLF
28910276Ssam 		= {
29036933Skarels 			help(cmdtab, (char *) 0);
29110276Ssam 		}
29210276Ssam 	|	HELP SP STRING CRLF
29310276Ssam 		= {
29436933Skarels 			register char *cp = (char *)$3;
29536933Skarels 
29636933Skarels 			if (strncasecmp(cp, "SITE", 4) == 0) {
29736933Skarels 				cp = (char *)$3 + 4;
29836933Skarels 				if (*cp == ' ')
29936933Skarels 					cp++;
30036933Skarels 				if (*cp)
30136933Skarels 					help(sitetab, cp);
30236933Skarels 				else
30336933Skarels 					help(sitetab, (char *) 0);
30436933Skarels 			} else
30536933Skarels 				help(cmdtab, (char *) $3);
30610276Ssam 		}
30710276Ssam 	|	NOOP CRLF
30810276Ssam 		= {
30927107Smckusick 			reply(200, "NOOP command successful.");
31010276Ssam 		}
31136620Srick 	|	MKD check_login SP pathname CRLF
31210276Ssam 		= {
31310302Ssam 			if ($2 && $4 != NULL)
31426494Sminshall 				makedir((char *) $4);
31510302Ssam 			if ($4 != NULL)
31626494Sminshall 				free((char *) $4);
31710276Ssam 		}
31836620Srick 	|	RMD check_login SP pathname CRLF
31910276Ssam 		= {
32010302Ssam 			if ($2 && $4 != NULL)
32126494Sminshall 				removedir((char *) $4);
32210302Ssam 			if ($4 != NULL)
32326494Sminshall 				free((char *) $4);
32410276Ssam 		}
32536620Srick 	|	PWD check_login CRLF
32610276Ssam 		= {
32710276Ssam 			if ($2)
32810302Ssam 				pwd();
32910276Ssam 		}
33036620Srick 	|	CDUP check_login CRLF
33110276Ssam 		= {
33210276Ssam 			if ($2)
33310276Ssam 				cwd("..");
33410276Ssam 		}
33536933Skarels 	|	SITE SP HELP CRLF
33636933Skarels 		= {
33736933Skarels 			help(sitetab, (char *) 0);
33836933Skarels 		}
33936933Skarels 	|	SITE SP HELP SP STRING CRLF
34036933Skarels 		= {
34136933Skarels 			help(sitetab, (char *) $5);
34236933Skarels 		}
34336933Skarels 	|	SITE SP UMASK check_login CRLF
34436933Skarels 		= {
34536933Skarels 			int oldmask;
34636933Skarels 
34736933Skarels 			if ($4) {
34836933Skarels 				oldmask = umask(0);
34936933Skarels 				(void) umask(oldmask);
35036933Skarels 				reply(200, "Current UMASK is %03o", oldmask);
35136933Skarels 			}
35236933Skarels 		}
35336933Skarels 	|	SITE SP UMASK check_login SP octal_number CRLF
35436933Skarels 		= {
35536933Skarels 			int oldmask;
35636933Skarels 
35736933Skarels 			if ($4) {
35836933Skarels 				if (($6 == -1) || ($6 > 0777)) {
35936933Skarels 					reply(501, "Bad UMASK value");
36036933Skarels 				} else {
36136933Skarels 					oldmask = umask($6);
36236933Skarels 					reply(200,
36336933Skarels 					    "UMASK set to %03o (was %03o)",
36436933Skarels 					    $6, oldmask);
36536933Skarels 				}
36636933Skarels 			}
36736933Skarels 		}
36836933Skarels 	|	SITE SP CHMOD check_login SP octal_number SP pathname CRLF
36936933Skarels 		= {
37036933Skarels 			if ($4 && ($8 != NULL)) {
37136933Skarels 				if ($6 > 0777)
37236933Skarels 					reply(501,
37336933Skarels 				"CHMOD: Mode value must be between 0 and 0777");
37436933Skarels 				else if (chmod((char *) $8, $6) < 0)
37536933Skarels 					perror_reply(550, (char *) $8);
37636933Skarels 				else
37736933Skarels 					reply(200, "CHMOD command successful.");
37836933Skarels 			}
37936933Skarels 			if ($8 != NULL)
38036933Skarels 				free((char *) $8);
38136933Skarels 		}
38236933Skarels 	|	SITE SP IDLE CRLF
38336933Skarels 		= {
38436933Skarels 			reply(200,
38536933Skarels 			    "Current IDLE time limit is %d seconds; max %d",
38636933Skarels 				timeout, maxtimeout);
38736933Skarels 		}
38836933Skarels 	|	SITE SP IDLE SP NUMBER CRLF
38936933Skarels 		= {
39036933Skarels 			if ($5 < 30 || $5 > maxtimeout) {
39136933Skarels 				reply(501,
39236933Skarels 			"Maximum IDLE time must be between 30 and %d seconds",
39336933Skarels 				    maxtimeout);
39436933Skarels 			} else {
39536933Skarels 				timeout = $5;
39636933Skarels 				(void) alarm((unsigned) timeout);
39736933Skarels 				reply(200,
39836933Skarels 				    "Maximum IDLE time set to %d seconds",
39936933Skarels 				    timeout);
40036933Skarels 			}
40136933Skarels 		}
40226045Sminshall 	|	STOU check_login SP pathname CRLF
40326045Sminshall 		= {
40436304Skarels 			if ($2 && $4 != NULL)
40536304Skarels 				store((char *) $4, "w", 1);
40626045Sminshall 			if ($4 != NULL)
40726494Sminshall 				free((char *) $4);
40826045Sminshall 		}
40936552Sbostic 	|	SYST CRLF
41036552Sbostic 		= {
41136640Srick #ifdef unix
41236933Skarels #ifdef BSD
41336552Sbostic 			reply(215, "UNIX Type: L%d Version: BSD-%d",
41436552Sbostic 				NBBY, BSD);
41536933Skarels #else /* BSD */
41636933Skarels 			reply(215, "UNIX Type: L%d", NBBY);
41736933Skarels #endif /* BSD */
41836933Skarels #else /* unix */
41936933Skarels 			reply(215, "UNKNOWN Type: L%d", NBBY);
42036933Skarels #endif /* unix */
42136552Sbostic 		}
42236933Skarels 
42336933Skarels 		/*
42436933Skarels 		 * SIZE is not in RFC959, but Postel has blessed it and
42536933Skarels 		 * it will be in the updated RFC.
42636933Skarels 		 *
42736933Skarels 		 * Return size of file in a format suitable for
42836933Skarels 		 * using with RESTART (we just count bytes).
42936933Skarels 		 */
43036933Skarels 	|	SIZE check_login SP pathname CRLF
43136933Skarels 		= {
43236933Skarels 			if ($2 && $4 != NULL)
43336933Skarels 				sizecmd((char *) $4);
43436933Skarels 			if ($4 != NULL)
43536933Skarels 				free((char *) $4);
43636933Skarels 		}
43736933Skarels 
43836933Skarels 		/*
43936933Skarels 		 * MDTM is not in RFC959, but Postel has blessed it and
44036933Skarels 		 * it will be in the updated RFC.
44136933Skarels 		 *
44236933Skarels 		 * Return modification time of file as an ISO 3307
44336933Skarels 		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
44436933Skarels 		 * where xxx is the fractional second (of any precision,
44536933Skarels 		 * not necessarily 3 digits)
44636933Skarels 		 */
44736933Skarels 	|	MDTM check_login SP pathname CRLF
44836933Skarels 		= {
44936933Skarels 			if ($2 && $4 != NULL) {
45036933Skarels 				struct stat stbuf;
45136933Skarels 				if (stat((char *) $4, &stbuf) < 0)
45236933Skarels 					perror_reply(550, "%s", (char *) $4);
45336933Skarels 				else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
45436933Skarels 					reply(550, "%s: not a plain file.",
45536933Skarels 						(char *) $4);
45636933Skarels 				} else {
45736933Skarels 					register struct tm *t;
45836933Skarels 					struct tm *gmtime();
45936933Skarels 					t = gmtime(&stbuf.st_mtime);
46036933Skarels 					reply(213,
46136933Skarels 					    "19%02d%02d%02d%02d%02d%02d",
46236933Skarels 					    t->tm_year, t->tm_mon+1, t->tm_mday,
46336933Skarels 					    t->tm_hour, t->tm_min, t->tm_sec);
46436933Skarels 				}
46536933Skarels 			}
46636933Skarels 			if ($4 != NULL)
46736933Skarels 				free((char *) $4);
46836933Skarels 		}
46910276Ssam 	|	QUIT CRLF
47010276Ssam 		= {
47110276Ssam 			reply(221, "Goodbye.");
47213246Ssam 			dologout(0);
47310276Ssam 		}
47410276Ssam 	|	error CRLF
47510276Ssam 		= {
47610276Ssam 			yyerrok;
47710276Ssam 		}
47810276Ssam 	;
47930945Scsvsj rcmd:		RNFR check_login SP pathname CRLF
48030945Scsvsj 		= {
48130945Scsvsj 			char *renamefrom();
48230945Scsvsj 
483*38135Srick 			restart_point = (off_t) 0;
48430945Scsvsj 			if ($2 && $4) {
48530945Scsvsj 				fromname = renamefrom((char *) $4);
48630945Scsvsj 				if (fromname == (char *) 0 && $4) {
48730945Scsvsj 					free((char *) $4);
48830945Scsvsj 				}
48930945Scsvsj 			}
49030945Scsvsj 		}
491*38135Srick 	|	REST SP byte_size CRLF
492*38135Srick 		= {
493*38135Srick 			long atol();
494*38135Srick 
495*38135Srick 			fromname = (char *) 0;
496*38135Srick 			restart_point = $3;
497*38135Srick 			reply(350, "Restarting at %ld. %s", restart_point,
498*38135Srick 			    "Send STORE or RETRIEVE to initiate transfer.");
499*38135Srick 		}
50030945Scsvsj 	;
50130945Scsvsj 
50210276Ssam username:	STRING
50310276Ssam 	;
50410276Ssam 
50536304Skarels password:	/* empty */
50636304Skarels 		= {
50736933Skarels 			*(char **)&($$) = "";
50836304Skarels 		}
50936304Skarels 	|	STRING
51010276Ssam 	;
51110276Ssam 
51210276Ssam byte_size:	NUMBER
51310276Ssam 	;
51410276Ssam 
51510276Ssam host_port:	NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
51610276Ssam 		NUMBER COMMA NUMBER
51710276Ssam 		= {
51810276Ssam 			register char *a, *p;
51910276Ssam 
52010276Ssam 			a = (char *)&data_dest.sin_addr;
52110276Ssam 			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
52210276Ssam 			p = (char *)&data_dest.sin_port;
52310276Ssam 			p[0] = $9; p[1] = $11;
52410324Ssam 			data_dest.sin_family = AF_INET;
52510276Ssam 		}
52610276Ssam 	;
52710276Ssam 
52810276Ssam form_code:	N
52910276Ssam 	= {
53010276Ssam 		$$ = FORM_N;
53110276Ssam 	}
53210276Ssam 	|	T
53310276Ssam 	= {
53410276Ssam 		$$ = FORM_T;
53510276Ssam 	}
53610276Ssam 	|	C
53710276Ssam 	= {
53810276Ssam 		$$ = FORM_C;
53910276Ssam 	}
54010276Ssam 	;
54110276Ssam 
54210276Ssam type_code:	A
54310276Ssam 	= {
54410276Ssam 		cmd_type = TYPE_A;
54510276Ssam 		cmd_form = FORM_N;
54610276Ssam 	}
54710276Ssam 	|	A SP form_code
54810276Ssam 	= {
54910276Ssam 		cmd_type = TYPE_A;
55010276Ssam 		cmd_form = $3;
55110276Ssam 	}
55210276Ssam 	|	E
55310276Ssam 	= {
55410276Ssam 		cmd_type = TYPE_E;
55510276Ssam 		cmd_form = FORM_N;
55610276Ssam 	}
55710276Ssam 	|	E SP form_code
55810276Ssam 	= {
55910276Ssam 		cmd_type = TYPE_E;
56010276Ssam 		cmd_form = $3;
56110276Ssam 	}
56210276Ssam 	|	I
56310276Ssam 	= {
56410276Ssam 		cmd_type = TYPE_I;
56510276Ssam 	}
56610276Ssam 	|	L
56710276Ssam 	= {
56810276Ssam 		cmd_type = TYPE_L;
56936552Sbostic 		cmd_bytesz = NBBY;
57010276Ssam 	}
57110276Ssam 	|	L SP byte_size
57210276Ssam 	= {
57310276Ssam 		cmd_type = TYPE_L;
57410276Ssam 		cmd_bytesz = $3;
57510276Ssam 	}
57610276Ssam 	/* this is for a bug in the BBN ftp */
57710276Ssam 	|	L byte_size
57810276Ssam 	= {
57910276Ssam 		cmd_type = TYPE_L;
58010276Ssam 		cmd_bytesz = $2;
58110276Ssam 	}
58210276Ssam 	;
58310276Ssam 
58410276Ssam struct_code:	F
58510276Ssam 	= {
58610276Ssam 		$$ = STRU_F;
58710276Ssam 	}
58810276Ssam 	|	R
58910276Ssam 	= {
59010276Ssam 		$$ = STRU_R;
59110276Ssam 	}
59210276Ssam 	|	P
59310276Ssam 	= {
59410276Ssam 		$$ = STRU_P;
59510276Ssam 	}
59610276Ssam 	;
59710276Ssam 
59810276Ssam mode_code:	S
59910276Ssam 	= {
60010276Ssam 		$$ = MODE_S;
60110276Ssam 	}
60210276Ssam 	|	B
60310276Ssam 	= {
60410276Ssam 		$$ = MODE_B;
60510276Ssam 	}
60610276Ssam 	|	C
60710276Ssam 	= {
60810276Ssam 		$$ = MODE_C;
60910276Ssam 	}
61010276Ssam 	;
61110276Ssam 
61210276Ssam pathname:	pathstring
61310276Ssam 	= {
61427107Smckusick 		/*
61527107Smckusick 		 * Problem: this production is used for all pathname
61627107Smckusick 		 * processing, but only gives a 550 error reply.
61727107Smckusick 		 * This is a valid reply in some cases but not in others.
61827107Smckusick 		 */
61936304Skarels 		if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) {
62036933Skarels 			*(char **)&($$) = *glob((char *) $1);
62110302Ssam 			if (globerr != NULL) {
62210276Ssam 				reply(550, globerr);
62310302Ssam 				$$ = NULL;
62410302Ssam 			}
62526494Sminshall 			free((char *) $1);
62610276Ssam 		} else
62710276Ssam 			$$ = $1;
62810276Ssam 	}
62910276Ssam 	;
63010276Ssam 
63110276Ssam pathstring:	STRING
63210276Ssam 	;
63310276Ssam 
63436933Skarels octal_number:	NUMBER
63536933Skarels 	= {
63636933Skarels 		register int ret, dec, multby, digit;
63736933Skarels 
63836933Skarels 		/*
63936933Skarels 		 * Convert a number that was read as decimal number
64036933Skarels 		 * to what it would be if it had been read as octal.
64136933Skarels 		 */
64236933Skarels 		dec = $1;
64336933Skarels 		multby = 1;
64436933Skarels 		ret = 0;
64536933Skarels 		while (dec) {
64636933Skarels 			digit = dec%10;
64736933Skarels 			if (digit > 7) {
64836933Skarels 				ret = -1;
64936933Skarels 				break;
65036933Skarels 			}
65136933Skarels 			ret += digit * multby;
65236933Skarels 			multby *= 8;
65336933Skarels 			dec /= 10;
65436933Skarels 		}
65536933Skarels 		$$ = ret;
65636933Skarels 	}
65736933Skarels 	;
65836933Skarels 
65910276Ssam check_login:	/* empty */
66010276Ssam 	= {
66110276Ssam 		if (logged_in)
66210276Ssam 			$$ = 1;
66310276Ssam 		else {
66410276Ssam 			reply(530, "Please login with USER and PASS.");
66510276Ssam 			$$ = 0;
66610276Ssam 		}
66710276Ssam 	}
66810276Ssam 	;
66910276Ssam 
67010276Ssam %%
67110276Ssam 
67210276Ssam extern jmp_buf errcatch;
67310276Ssam 
67410276Ssam #define	CMD	0	/* beginning of command */
67510276Ssam #define	ARGS	1	/* expect miscellaneous arguments */
67610276Ssam #define	STR1	2	/* expect SP followed by STRING */
67710276Ssam #define	STR2	3	/* expect STRING */
67836304Skarels #define	OSTR	4	/* optional SP then STRING */
67936304Skarels #define	ZSTR1	5	/* SP then optional STRING */
68036304Skarels #define	ZSTR2	6	/* optional STRING after SP */
68136933Skarels #define	SITECMD	7	/* SITE command */
68236933Skarels #define	NSTR	8	/* Number followed by a string */
68310276Ssam 
68410276Ssam struct tab {
68510276Ssam 	char	*name;
68610276Ssam 	short	token;
68710276Ssam 	short	state;
68810276Ssam 	short	implemented;	/* 1 if command is implemented */
68910276Ssam 	char	*help;
69010276Ssam };
69110276Ssam 
69210276Ssam struct tab cmdtab[] = {		/* In order defined in RFC 765 */
69310276Ssam 	{ "USER", USER, STR1, 1,	"<sp> username" },
69436304Skarels 	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
69510276Ssam 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
69636933Skarels 	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
69710276Ssam 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
69810276Ssam 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
69910276Ssam 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
70026045Sminshall 	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
70110276Ssam 	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
70210276Ssam 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
70310276Ssam 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
70410276Ssam 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
70510276Ssam 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
70610276Ssam 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
70710276Ssam 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
70810276Ssam 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
70910276Ssam 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
71010276Ssam 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
71110276Ssam 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
71210276Ssam 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
71310276Ssam 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
71410276Ssam 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
715*38135Srick 	{ "REST", REST, ARGS, 1,	"(restart command)" },
71610276Ssam 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
71710276Ssam 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
71826045Sminshall 	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
71910276Ssam 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
72036304Skarels 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
72110276Ssam 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
72210276Ssam 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
72310276Ssam 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
72436933Skarels 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
72536552Sbostic 	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
72636933Skarels 	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
72710276Ssam 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
72810276Ssam 	{ "NOOP", NOOP, ARGS, 1,	"" },
72936620Srick 	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
73036620Srick 	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
73136620Srick 	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
73236620Srick 	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
73336620Srick 	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
73436620Srick 	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
73536620Srick 	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
73636620Srick 	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
73726045Sminshall 	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
73836933Skarels 	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
73936933Skarels 	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
74010276Ssam 	{ NULL,   0,    0,    0,	0 }
74110276Ssam };
74210276Ssam 
74336933Skarels struct tab sitetab[] = {
74436933Skarels 	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
74536933Skarels 	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
74636933Skarels 	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
74736933Skarels 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
74836933Skarels 	{ NULL,   0,    0,    0,	0 }
74936933Skarels };
75036933Skarels 
75110276Ssam struct tab *
75236933Skarels lookup(p, cmd)
75336933Skarels 	register struct tab *p;
75410276Ssam 	char *cmd;
75510276Ssam {
75610276Ssam 
75736933Skarels 	for (; p->name != NULL; p++)
75810276Ssam 		if (strcmp(cmd, p->name) == 0)
75910276Ssam 			return (p);
76010276Ssam 	return (0);
76110276Ssam }
76210276Ssam 
76313033Ssam #include <arpa/telnet.h>
76410276Ssam 
76510276Ssam /*
76610276Ssam  * getline - a hacked up version of fgets to ignore TELNET escape codes.
76710276Ssam  */
76810276Ssam char *
76910276Ssam getline(s, n, iop)
77010276Ssam 	char *s;
77110276Ssam 	register FILE *iop;
77210276Ssam {
77310276Ssam 	register c;
77426494Sminshall 	register char *cs;
77510276Ssam 
77610276Ssam 	cs = s;
77727751Sminshall /* tmpline may contain saved command from urgent mode interruption */
77826045Sminshall 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
77926045Sminshall 		*cs++ = tmpline[c];
78026045Sminshall 		if (tmpline[c] == '\n') {
78126045Sminshall 			*cs++ = '\0';
78236304Skarels 			if (debug)
78336304Skarels 				syslog(LOG_DEBUG, "command: %s", s);
78426045Sminshall 			tmpline[0] = '\0';
78526045Sminshall 			return(s);
78626045Sminshall 		}
78736304Skarels 		if (c == 0)
78826045Sminshall 			tmpline[0] = '\0';
78926045Sminshall 	}
79036304Skarels 	while ((c = getc(iop)) != EOF) {
79136304Skarels 		c &= 0377;
79236304Skarels 		if (c == IAC) {
79336304Skarels 		    if ((c = getc(iop)) != EOF) {
79436304Skarels 			c &= 0377;
79536304Skarels 			switch (c) {
79627751Sminshall 			case WILL:
79727751Sminshall 			case WONT:
79836277Sbostic 				c = getc(iop);
79936304Skarels 				printf("%c%c%c", IAC, DONT, 0377&c);
80027751Sminshall 				(void) fflush(stdout);
80136304Skarels 				continue;
80227751Sminshall 			case DO:
80327751Sminshall 			case DONT:
80436277Sbostic 				c = getc(iop);
80536304Skarels 				printf("%c%c%c", IAC, WONT, 0377&c);
80627751Sminshall 				(void) fflush(stdout);
80736304Skarels 				continue;
80836304Skarels 			case IAC:
80927751Sminshall 				break;
81027751Sminshall 			default:
81136304Skarels 				continue;	/* ignore command */
81227751Sminshall 			}
81336304Skarels 		    }
81410276Ssam 		}
81536304Skarels 		*cs++ = c;
81636304Skarels 		if (--n <= 0 || c == '\n')
81710276Ssam 			break;
81810276Ssam 	}
81927751Sminshall 	if (c == EOF && cs == s)
82018303Sralph 		return (NULL);
82110276Ssam 	*cs++ = '\0';
82236304Skarels 	if (debug)
82336304Skarels 		syslog(LOG_DEBUG, "command: %s", s);
82410276Ssam 	return (s);
82510276Ssam }
82610276Ssam 
82711652Ssam static int
82811652Ssam toolong()
82911652Ssam {
83026494Sminshall 	time_t now;
83111652Ssam 	extern char *ctime();
83226494Sminshall 	extern time_t time();
83311652Ssam 
83411652Ssam 	reply(421,
83511652Ssam 	  "Timeout (%d seconds): closing control connection.", timeout);
83626494Sminshall 	(void) time(&now);
83711652Ssam 	if (logging) {
83826494Sminshall 		syslog(LOG_INFO,
83936304Skarels 			"User %s timed out after %d seconds at %s",
84011652Ssam 			(pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
84111652Ssam 	}
84213246Ssam 	dologout(1);
84311652Ssam }
84411652Ssam 
84510276Ssam yylex()
84610276Ssam {
84710276Ssam 	static int cpos, state;
84836933Skarels 	register char *cp, *cp2;
84910276Ssam 	register struct tab *p;
85010276Ssam 	int n;
85136277Sbostic 	char c, *strpbrk();
85236933Skarels 	char *copy();
85310276Ssam 
85410276Ssam 	for (;;) {
85510276Ssam 		switch (state) {
85610276Ssam 
85710276Ssam 		case CMD:
85826494Sminshall 			(void) signal(SIGALRM, toolong);
85926494Sminshall 			(void) alarm((unsigned) timeout);
86010276Ssam 			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
86110276Ssam 				reply(221, "You could at least say goodbye.");
86213246Ssam 				dologout(0);
86310276Ssam 			}
86426494Sminshall 			(void) alarm(0);
86536620Srick #ifdef SETPROCTITLE
86636933Skarels 			if (strncasecmp(cbuf, "PASS", 4) != NULL)
86736933Skarels 				setproctitle("%s: %s", proctitle, cbuf);
86836620Srick #endif /* SETPROCTITLE */
86936324Sbostic 			if ((cp = index(cbuf, '\r'))) {
87036324Sbostic 				*cp++ = '\n';
87136324Sbostic 				*cp = '\0';
87236324Sbostic 			}
87336277Sbostic 			if ((cp = strpbrk(cbuf, " \n")))
87436277Sbostic 				cpos = cp - cbuf;
87536304Skarels 			if (cpos == 0)
87610276Ssam 				cpos = 4;
87710276Ssam 			c = cbuf[cpos];
87810276Ssam 			cbuf[cpos] = '\0';
87910276Ssam 			upper(cbuf);
88036933Skarels 			p = lookup(cmdtab, cbuf);
88110276Ssam 			cbuf[cpos] = c;
88210276Ssam 			if (p != 0) {
88310276Ssam 				if (p->implemented == 0) {
88410276Ssam 					nack(p->name);
88526494Sminshall 					longjmp(errcatch,0);
88610276Ssam 					/* NOTREACHED */
88710276Ssam 				}
88810276Ssam 				state = p->state;
88936933Skarels 				*(char **)&yylval = p->name;
89010276Ssam 				return (p->token);
89110276Ssam 			}
89210276Ssam 			break;
89310276Ssam 
89436933Skarels 		case SITECMD:
89536933Skarels 			if (cbuf[cpos] == ' ') {
89636933Skarels 				cpos++;
89736933Skarels 				return (SP);
89836933Skarels 			}
89936933Skarels 			cp = &cbuf[cpos];
90036933Skarels 			if ((cp2 = strpbrk(cp, " \n")))
90136933Skarels 				cpos = cp2 - cbuf;
90236933Skarels 			c = cbuf[cpos];
90336933Skarels 			cbuf[cpos] = '\0';
90436933Skarels 			upper(cp);
90536933Skarels 			p = lookup(sitetab, cp);
90636933Skarels 			cbuf[cpos] = c;
90736933Skarels 			if (p != 0) {
90836933Skarels 				if (p->implemented == 0) {
90936933Skarels 					state = CMD;
91036933Skarels 					nack(p->name);
91136933Skarels 					longjmp(errcatch,0);
91236933Skarels 					/* NOTREACHED */
91336933Skarels 				}
91436933Skarels 				state = p->state;
91536933Skarels 				*(char **)&yylval = p->name;
91636933Skarels 				return (p->token);
91736933Skarels 			}
91836933Skarels 			state = CMD;
91936933Skarels 			break;
92036933Skarels 
92110276Ssam 		case OSTR:
92210276Ssam 			if (cbuf[cpos] == '\n') {
92310276Ssam 				state = CMD;
92410276Ssam 				return (CRLF);
92510276Ssam 			}
92636317Sbostic 			/* FALLTHROUGH */
92710276Ssam 
92810276Ssam 		case STR1:
92936304Skarels 		case ZSTR1:
93036933Skarels 		dostr1:
93110276Ssam 			if (cbuf[cpos] == ' ') {
93210276Ssam 				cpos++;
93336317Sbostic 				state = state == OSTR ? STR2 : ++state;
93410276Ssam 				return (SP);
93510276Ssam 			}
93610276Ssam 			break;
93710276Ssam 
93836304Skarels 		case ZSTR2:
93936304Skarels 			if (cbuf[cpos] == '\n') {
94036304Skarels 				state = CMD;
94136304Skarels 				return (CRLF);
94236304Skarels 			}
94336933Skarels 			/* FALLTHROUGH */
94436304Skarels 
94510276Ssam 		case STR2:
94610276Ssam 			cp = &cbuf[cpos];
94710276Ssam 			n = strlen(cp);
94810276Ssam 			cpos += n - 1;
94910276Ssam 			/*
95010276Ssam 			 * Make sure the string is nonempty and \n terminated.
95110276Ssam 			 */
95210276Ssam 			if (n > 1 && cbuf[cpos] == '\n') {
95310276Ssam 				cbuf[cpos] = '\0';
95436933Skarels 				*(char **)&yylval = copy(cp);
95510276Ssam 				cbuf[cpos] = '\n';
95610276Ssam 				state = ARGS;
95710276Ssam 				return (STRING);
95810276Ssam 			}
95910276Ssam 			break;
96010276Ssam 
96136933Skarels 		case NSTR:
96236933Skarels 			if (cbuf[cpos] == ' ') {
96336933Skarels 				cpos++;
96436933Skarels 				return (SP);
96536933Skarels 			}
96636933Skarels 			if (isdigit(cbuf[cpos])) {
96736933Skarels 				cp = &cbuf[cpos];
96836933Skarels 				while (isdigit(cbuf[++cpos]))
96936933Skarels 					;
97036933Skarels 				c = cbuf[cpos];
97136933Skarels 				cbuf[cpos] = '\0';
97236933Skarels 				yylval = atoi(cp);
97336933Skarels 				cbuf[cpos] = c;
97436933Skarels 				state = STR1;
97536933Skarels 				return (NUMBER);
97636933Skarels 			}
97736933Skarels 			state = STR1;
97836933Skarels 			goto dostr1;
97936933Skarels 
98010276Ssam 		case ARGS:
98110276Ssam 			if (isdigit(cbuf[cpos])) {
98210276Ssam 				cp = &cbuf[cpos];
98310276Ssam 				while (isdigit(cbuf[++cpos]))
98410276Ssam 					;
98510276Ssam 				c = cbuf[cpos];
98610276Ssam 				cbuf[cpos] = '\0';
98710276Ssam 				yylval = atoi(cp);
98810276Ssam 				cbuf[cpos] = c;
98910276Ssam 				return (NUMBER);
99010276Ssam 			}
99110276Ssam 			switch (cbuf[cpos++]) {
99210276Ssam 
99310276Ssam 			case '\n':
99410276Ssam 				state = CMD;
99510276Ssam 				return (CRLF);
99610276Ssam 
99710276Ssam 			case ' ':
99810276Ssam 				return (SP);
99910276Ssam 
100010276Ssam 			case ',':
100110276Ssam 				return (COMMA);
100210276Ssam 
100310276Ssam 			case 'A':
100410276Ssam 			case 'a':
100510276Ssam 				return (A);
100610276Ssam 
100710276Ssam 			case 'B':
100810276Ssam 			case 'b':
100910276Ssam 				return (B);
101010276Ssam 
101110276Ssam 			case 'C':
101210276Ssam 			case 'c':
101310276Ssam 				return (C);
101410276Ssam 
101510276Ssam 			case 'E':
101610276Ssam 			case 'e':
101710276Ssam 				return (E);
101810276Ssam 
101910276Ssam 			case 'F':
102010276Ssam 			case 'f':
102110276Ssam 				return (F);
102210276Ssam 
102310276Ssam 			case 'I':
102410276Ssam 			case 'i':
102510276Ssam 				return (I);
102610276Ssam 
102710276Ssam 			case 'L':
102810276Ssam 			case 'l':
102910276Ssam 				return (L);
103010276Ssam 
103110276Ssam 			case 'N':
103210276Ssam 			case 'n':
103310276Ssam 				return (N);
103410276Ssam 
103510276Ssam 			case 'P':
103610276Ssam 			case 'p':
103710276Ssam 				return (P);
103810276Ssam 
103910276Ssam 			case 'R':
104010276Ssam 			case 'r':
104110276Ssam 				return (R);
104210276Ssam 
104310276Ssam 			case 'S':
104410276Ssam 			case 's':
104510276Ssam 				return (S);
104610276Ssam 
104710276Ssam 			case 'T':
104810276Ssam 			case 't':
104910276Ssam 				return (T);
105010276Ssam 
105110276Ssam 			}
105210276Ssam 			break;
105310276Ssam 
105410276Ssam 		default:
105510276Ssam 			fatal("Unknown state in scanner.");
105610276Ssam 		}
105726494Sminshall 		yyerror((char *) 0);
105810276Ssam 		state = CMD;
105926494Sminshall 		longjmp(errcatch,0);
106010276Ssam 	}
106110276Ssam }
106210276Ssam 
106310276Ssam upper(s)
106436277Sbostic 	register char *s;
106510276Ssam {
106610276Ssam 	while (*s != '\0') {
106710276Ssam 		if (islower(*s))
106810276Ssam 			*s = toupper(*s);
106910276Ssam 		s++;
107010276Ssam 	}
107110276Ssam }
107210276Ssam 
107336933Skarels char *
107410276Ssam copy(s)
107510276Ssam 	char *s;
107610276Ssam {
107710276Ssam 	char *p;
107826494Sminshall 	extern char *malloc(), *strcpy();
107910276Ssam 
108026494Sminshall 	p = malloc((unsigned) strlen(s) + 1);
108110276Ssam 	if (p == NULL)
108210276Ssam 		fatal("Ran out of memory.");
108326494Sminshall 	(void) strcpy(p, s);
108436933Skarels 	return (p);
108510276Ssam }
108610276Ssam 
108736933Skarels help(ctab, s)
108836933Skarels 	struct tab *ctab;
108910276Ssam 	char *s;
109010276Ssam {
109110276Ssam 	register struct tab *c;
109210276Ssam 	register int width, NCMDS;
109336933Skarels 	char *type;
109410276Ssam 
109536933Skarels 	if (ctab == sitetab)
109636933Skarels 		type = "SITE ";
109736933Skarels 	else
109836933Skarels 		type = "";
109910276Ssam 	width = 0, NCMDS = 0;
110036933Skarels 	for (c = ctab; c->name != NULL; c++) {
110136933Skarels 		int len = strlen(c->name);
110210276Ssam 
110310276Ssam 		if (len > width)
110410276Ssam 			width = len;
110510276Ssam 		NCMDS++;
110610276Ssam 	}
110710276Ssam 	width = (width + 8) &~ 7;
110810276Ssam 	if (s == 0) {
110910276Ssam 		register int i, j, w;
111010276Ssam 		int columns, lines;
111110276Ssam 
111236933Skarels 		lreply(214, "The following %scommands are recognized %s.",
111336933Skarels 		    type, "(* =>'s unimplemented)");
111410276Ssam 		columns = 76 / width;
111510276Ssam 		if (columns == 0)
111610276Ssam 			columns = 1;
111710276Ssam 		lines = (NCMDS + columns - 1) / columns;
111810276Ssam 		for (i = 0; i < lines; i++) {
111927107Smckusick 			printf("   ");
112010276Ssam 			for (j = 0; j < columns; j++) {
112136933Skarels 				c = ctab + j * lines + i;
112210276Ssam 				printf("%s%c", c->name,
112310276Ssam 					c->implemented ? ' ' : '*');
112436933Skarels 				if (c + lines >= &ctab[NCMDS])
112510276Ssam 					break;
112631132Smckusick 				w = strlen(c->name) + 1;
112710276Ssam 				while (w < width) {
112810276Ssam 					putchar(' ');
112910276Ssam 					w++;
113010276Ssam 				}
113110276Ssam 			}
113210276Ssam 			printf("\r\n");
113310276Ssam 		}
113426494Sminshall 		(void) fflush(stdout);
113510276Ssam 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
113610276Ssam 		return;
113710276Ssam 	}
113810276Ssam 	upper(s);
113936933Skarels 	c = lookup(ctab, s);
114010276Ssam 	if (c == (struct tab *)0) {
114127107Smckusick 		reply(502, "Unknown command %s.", s);
114210276Ssam 		return;
114310276Ssam 	}
114410276Ssam 	if (c->implemented)
114536933Skarels 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
114610276Ssam 	else
114736933Skarels 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
114836933Skarels 		    c->name, c->help);
114910276Ssam }
115036933Skarels 
115136933Skarels sizecmd(filename)
115236933Skarels char *filename;
115336933Skarels {
115436933Skarels 	switch (type) {
115536933Skarels 	case TYPE_L:
115636933Skarels 	case TYPE_I: {
115736933Skarels 		struct stat stbuf;
115836933Skarels 		if (stat(filename, &stbuf) < 0 ||
115936933Skarels 		    (stbuf.st_mode&S_IFMT) != S_IFREG)
116036933Skarels 			reply(550, "%s: not a plain file.", filename);
116136933Skarels 		else
116236933Skarels 			reply(213, "%lu", stbuf.st_size);
116336933Skarels 		break;}
116436933Skarels 	case TYPE_A: {
116536933Skarels 		FILE *fin;
1166*38135Srick 		register int c;
1167*38135Srick 		register long count;
116836933Skarels 		struct stat stbuf;
116936933Skarels 		fin = fopen(filename, "r");
117036933Skarels 		if (fin == NULL) {
117136933Skarels 			perror_reply(550, filename);
117236933Skarels 			return;
117336933Skarels 		}
117436933Skarels 		if (fstat(fileno(fin), &stbuf) < 0 ||
117536933Skarels 		    (stbuf.st_mode&S_IFMT) != S_IFREG) {
117636933Skarels 			reply(550, "%s: not a plain file.", filename);
117736933Skarels 			(void) fclose(fin);
117836933Skarels 			return;
117936933Skarels 		}
118036933Skarels 
118136933Skarels 		count = 0;
118236933Skarels 		while((c=getc(fin)) != EOF) {
118336933Skarels 			if (c == '\n')	/* will get expanded to \r\n */
118436933Skarels 				count++;
118536933Skarels 			count++;
118636933Skarels 		}
118736933Skarels 		(void) fclose(fin);
118836933Skarels 
118936933Skarels 		reply(213, "%ld", count);
119036933Skarels 		break;}
119136933Skarels 	default:
119236933Skarels 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
119336933Skarels 	}
119436933Skarels }
1195