ftpcmd.y revision 31940
122347Spst/* ftpcmd.y: yacc parser for the FTP daemon.
222347Spst
329964Sache%%% portions-copyright-cmetz-96
429964SachePortions of this software are Copyright 1996-1997 by Craig Metz, All Rights
522347SpstReserved. The Inner Net License Version 2 applies to these portions of
622347Spstthe software.
722347SpstYou should have received a copy of the license with this software. If
822347Spstyou didn't get a copy, you may request one from <license@inner.net>.
922347Spst
1022347Spst	History:
1122347Spst
1222347Spst	Modified by cmetz for OPIE 2.3. Moved LS_COMMAND here.
1322347Spst        Modified by cmetz for OPIE 2.2. Fixed a *lot* of warnings.
1422347Spst                Use FUNCTION declaration et al. Removed useless strings.
1522347Spst                Changed some char []s to char *s. Deleted comment address.
1622347Spst                Changed tmpline references to be more pure-pointer
1722347Spst                references. Changed tmpline declaration back to char [].
1822347Spst	Modified at NRL for OPIE 2.1. Minor changes for autoconf.
1922347Spst        Modified at NRL for OPIE 2.01. Added forward declaration for sitetab[]
2022347Spst                -- fixes problems experienced by bison users. Merged in new
2122347Spst                PORT attack fixes from Hobbit.
2222347Spst	Modified at NRL for OPIE 2.0.
2322347Spst	Originally from BSD.
2422347Spst*/
2522347Spst/*
2622347Spst * Copyright (c) 1985, 1988 Regents of the University of California.
2722347Spst * All rights reserved.
2822347Spst *
2922347Spst * Redistribution and use in source and binary forms, with or without
3022347Spst * modification, are permitted provided that the following conditions
3122347Spst * are met:
3222347Spst * 1. Redistributions of source code must retain the above copyright
3322347Spst *    notice, this list of conditions and the following disclaimer.
3422347Spst * 2. Redistributions in binary form must reproduce the above copyright
3522347Spst *    notice, this list of conditions and the following disclaimer in the
3622347Spst *    documentation and/or other materials provided with the distribution.
3722347Spst * 3. All advertising materials mentioning features or use of this software
3822347Spst *    must display the following acknowledgement:
3922347Spst *	This product includes software developed by the University of
4022347Spst *	California, Berkeley and its contributors.
4122347Spst * 4. Neither the name of the University nor the names of its contributors
4222347Spst *    may be used to endorse or promote products derived from this software
4322347Spst *    without specific prior written permission.
4422347Spst *
4522347Spst * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
4622347Spst * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
4722347Spst * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
4822347Spst * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
4922347Spst * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
5022347Spst * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
5122347Spst * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
5222347Spst * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
5322347Spst * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
5422347Spst * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
5522347Spst * SUCH DAMAGE.
5622347Spst *
5722347Spst *	@(#)ftpcmd.y	5.24 (Berkeley) 2/25/91
5822347Spst */
5922347Spst
6022347Spst/*
6122347Spst * Grammar for FTP commands.
6222347Spst * See RFC 959.
6322347Spst */
6422347Spst
6522347Spst%{
6622347Spst#include "opie_cfg.h"
6722347Spst
6822347Spst#include <sys/param.h>
6922347Spst#include <sys/types.h>
7022347Spst#include <sys/socket.h>
7122347Spst#include <sys/stat.h>
7222347Spst#include <netinet/in.h>
7322347Spst#include <arpa/ftp.h>
7422347Spst#include <signal.h>
7522347Spst#include <setjmp.h>
7622347Spst#include <syslog.h>
7722347Spst#if TM_IN_SYS_TIME
7822347Spst#include <sys/time.h>
7922347Spst#else /* TM_IN_SYS_TIME */
8022347Spst#include <time.h>
8122347Spst#endif /* TM_IN_SYS_TIME */
8222347Spst#include <pwd.h>
8322347Spst#include <unistd.h>
8422347Spst#include <stdio.h>
8522347Spst#include <ctype.h>
8622347Spst#include <stdlib.h>
8722347Spst#include <string.h>
8822347Spst
8922347Spst#include "opie.h"
9022347Spst
9122347Spst#if HAVE_LS_G_FLAG
9222347Spst#define LS_COMMAND "/bin/ls -lgA"
9322347Spst#else /* HAVE_LS_G_FLAG */
9422347Spst#define LS_COMMAND "/bin/ls -lA"
9522347Spst#endif /* HAVE_LS_G_FLAG */
9622347Spst
9722347Spstextern	struct sockaddr_in data_dest;
9822347Spstextern  struct sockaddr_in his_addr;
9922347Spstextern	int logged_in;
10022347Spstextern	struct passwd *pw;
10122347Spstextern	int guest;
10222347Spstextern	int type;
10322347Spstextern	int form;
10422347Spstextern	int debug;
10522347Spstextern	int timeout;
10622347Spstextern	int maxtimeout;
10722347Spstextern  int pdata;
10822347Spstextern	char *remotehost;
10922347Spstextern	char *proctitle;
11022347Spstextern	char *globerr;
11122347Spstextern	int usedefault;
11222347Spstextern  int transflag;
11322347Spstextern  char tmpline[];
11422347Spstchar	**ftpglob();
11522347Spst
11622347SpstVOIDRET dologout __P((int));
11722347SpstVOIDRET upper __P((char *));
11822347SpstVOIDRET nack __P((char *));
11922347SpstVOIDRET opiefatal __P((char *));
12022347Spst
12122347SpstVOIDRET pass __P((char *));
12222347Spstint user __P((char *));
12322347SpstVOIDRET passive __P((void));
12422347SpstVOIDRET retrieve __P((char *, char *));
12522347SpstVOIDRET store __P((char *, char *, int));
12622347SpstVOIDRET send_file_list __P((char *));
12722347SpstVOIDRET statfilecmd __P((char *));
12822347SpstVOIDRET statcmd __P((void));
12922347SpstVOIDRET delete __P((char *));
13022347SpstVOIDRET renamecmd __P((char *, char *));
13122347SpstVOIDRET cwd __P((char *));
13222347SpstVOIDRET makedir __P((char *));
13322347SpstVOIDRET removedir __P((char *));
13422347SpstVOIDRET pwd __P((void));
13522347Spst
13622347SpstVOIDRET sizecmd __P((char *));
13722347Spst
13822347Spstoff_t	restart_point;
13922347Spst
14022347Spststatic	int cmd_type;
14122347Spststatic	int cmd_form;
14222347Spststatic	int cmd_bytesz;
14322347Spststatic  unsigned short cliport = 0;
14422347Spstchar	cbuf[512];
14522347Spstchar	*fromname;
14622347Spst
14722347Spststruct tab {
14822347Spst	char	*name;
14922347Spst	short	token;
15022347Spst	short	state;
15122347Spst	short	implemented;	/* 1 if command is implemented */
15222347Spst	char	*help;
15322347Spst};
15422347Spst
15522347SpstVOIDRET help __P((struct tab *, char *));
15622347Spst
15722347Spststruct tab cmdtab[], sitetab[];
15822347Spst
15922347Spst%}
16022347Spst
16122347Spst%token
16222347Spst	A	B	C	E	F	I
16322347Spst	L	N	P	R	S	T
16422347Spst
16522347Spst	SP	CRLF	COMMA	STRING	NUMBER
16622347Spst
16722347Spst	USER	PASS	ACCT	REIN	QUIT	PORT
16822347Spst	PASV	TYPE	STRU	MODE	RETR	STOR
16922347Spst	APPE	MLFL	MAIL	MSND	MSOM	MSAM
17022347Spst	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
17122347Spst	ABOR	DELE	CWD	LIST	NLST	SITE
17222347Spst	STAT	HELP	NOOP	MKD	RMD	PWD
17322347Spst	CDUP	STOU	SMNT	SYST	SIZE	MDTM
17422347Spst
17522347Spst	UMASK	IDLE	CHMOD
17622347Spst
17722347Spst	LEXERR
17822347Spst
17922347Spst%start	cmd_list
18022347Spst
18122347Spst%%
18222347Spst
18322347Spstcmd_list:	/* empty */
18422347Spst	|	cmd_list cmd
18522347Spst		= {
18622347Spst			fromname = (char *) 0;
18722347Spst			restart_point = (off_t) 0;
18822347Spst		}
18922347Spst	|	cmd_list rcmd
19022347Spst	;
19122347Spst
19222347Spstcmd:		USER SP username CRLF
19322347Spst		= {
19422347Spst			user((char *) $3);
19522347Spst			free((char *) $3);
19622347Spst		}
19722347Spst	|	PASS SP password CRLF
19822347Spst		= {
19922347Spst			pass((char *) $3);
20022347Spst			free((char *) $3);
20122347Spst		}
20222347Spst        |   PORT check_login SP host_port CRLF
20322347Spst                = {
20422347Spst             usedefault = 0;
20522347Spst             if (pdata >= 0) {
20622347Spst                 (void) close(pdata);
20722347Spst                 pdata = -1;
20822347Spst             }
20922347Spst/* H* port fix, part B: admonish the twit.
21022347Spst   Also require login before PORT works */
21122347Spst            if ($2) {
21222347Spst              if ((cliport > 1023) && (data_dest.sin_addr.s_addr > 0)) {
21322347Spst                reply(200, "PORT command successful.");
21422347Spst              } else {
21522347Spst                syslog (LOG_WARNING, "refused %s from %s",
21622347Spst                       cbuf, remotehost);
21722347Spst                reply(500, "You've GOT to be joking.");
21822347Spst              }
21922347Spst            }
22022347Spst                }
22122347Spst/*	|	PASV CRLF
22222347Spst		= {
22322347Spst			passive();
22422347Spst		} */
22522347Spst    |   PASV check_login CRLF
22622347Spst        = {
22722347Spst/* Require login for PASV, too.  This actually fixes a bug -- telnet to an
22822347Spst   unfixed wu-ftpd and type PASV first off, and it crashes! */
22922347Spst            if ($2) {
23022347Spst                passive();
23122347Spst            }
23222347Spst        }
23322347Spst	|	TYPE SP type_code CRLF
23422347Spst		= {
23522347Spst			switch (cmd_type) {
23622347Spst
23722347Spst			case TYPE_A:
23822347Spst				if (cmd_form == FORM_N) {
23922347Spst					reply(200, "Type set to A.");
24022347Spst					type = cmd_type;
24122347Spst					form = cmd_form;
24222347Spst				} else
24322347Spst					reply(504, "Form must be N.");
24422347Spst				break;
24522347Spst
24622347Spst			case TYPE_E:
24722347Spst				reply(504, "Type E not implemented.");
24822347Spst				break;
24922347Spst
25022347Spst			case TYPE_I:
25122347Spst				reply(200, "Type set to I.");
25222347Spst				type = cmd_type;
25322347Spst				break;
25422347Spst
25522347Spst			case TYPE_L:
25622347Spst#if NBBY == 8
25722347Spst				if (cmd_bytesz == 8) {
25822347Spst					reply(200,
25922347Spst					    "Type set to L (byte size 8).");
26022347Spst					type = cmd_type;
26122347Spst				} else
26222347Spst					reply(504, "Byte size must be 8.");
26322347Spst#else /* NBBY == 8 */
26422347Spst				UNIMPLEMENTED for NBBY != 8
26522347Spst#endif /* NBBY == 8 */
26622347Spst			}
26722347Spst		}
26822347Spst	|	STRU SP struct_code CRLF
26922347Spst		= {
27022347Spst			switch ($3) {
27122347Spst
27222347Spst			case STRU_F:
27322347Spst				reply(200, "STRU F ok.");
27422347Spst				break;
27522347Spst
27622347Spst			default:
27722347Spst				reply(504, "Unimplemented STRU type.");
27822347Spst			}
27922347Spst		}
28022347Spst	|	MODE SP mode_code CRLF
28122347Spst		= {
28222347Spst			switch ($3) {
28322347Spst
28422347Spst			case MODE_S:
28522347Spst				reply(200, "MODE S ok.");
28622347Spst				break;
28722347Spst
28822347Spst			default:
28922347Spst				reply(502, "Unimplemented MODE type.");
29022347Spst			}
29122347Spst		}
29222347Spst	|	ALLO SP NUMBER CRLF
29322347Spst		= {
29422347Spst			reply(202, "ALLO command ignored.");
29522347Spst		}
29622347Spst	|	ALLO SP NUMBER SP R SP NUMBER CRLF
29722347Spst		= {
29822347Spst			reply(202, "ALLO command ignored.");
29922347Spst		}
30022347Spst	|	RETR check_login SP pathname CRLF
30122347Spst		= {
30222347Spst			if ($2 && $4)
30322347Spst				retrieve((char *) 0, (char *) $4);
30422347Spst			if ($4)
30522347Spst				free((char *) $4);
30622347Spst		}
30722347Spst	|	STOR check_login SP pathname CRLF
30822347Spst		= {
30922347Spst			if ($2 && $4)
31022347Spst				store((char *) $4, "w", 0);
31122347Spst			if ($4)
31222347Spst				free((char *) $4);
31322347Spst		}
31422347Spst	|	APPE check_login SP pathname CRLF
31522347Spst		= {
31622347Spst			if ($2 && $4)
31722347Spst				store((char *) $4, "a", 0);
31822347Spst			if ($4)
31922347Spst				free((char *) $4);
32022347Spst		}
32122347Spst	|	NLST check_login CRLF
32222347Spst		= {
32322347Spst			if ($2)
32422347Spst				send_file_list(".");
32522347Spst		}
32622347Spst	|	NLST check_login SP STRING CRLF
32722347Spst		= {
32822347Spst			if ($2 && $4)
32922347Spst				send_file_list((char *) $4);
33022347Spst			if ($4)
33122347Spst				free((char *) $4);
33222347Spst		}
33322347Spst	|	LIST check_login CRLF
33422347Spst		= {
33522347Spst			if ($2)
33622347Spst				retrieve(LS_COMMAND, "");
33722347Spst		}
33822347Spst	|	LIST check_login SP pathname CRLF
33922347Spst		= {
34022347Spst			if ($2 && $4)
34122347Spst                                {
34222347Spst                                char buffer[sizeof(LS_COMMAND)+3];
34322347Spst                                strcpy(buffer, LS_COMMAND);
34422347Spst                                strcat(buffer, " %s");
34522347Spst				retrieve(buffer, (char *) $4);
34622347Spst                                }
34722347Spst			if ($4)
34822347Spst				free((char *) $4);
34922347Spst		}
35022347Spst	|	STAT check_login SP pathname CRLF
35122347Spst		= {
35222347Spst			if ($2 && $4)
35322347Spst				statfilecmd((char *) $4);
35422347Spst			if ($4)
35522347Spst				free((char *) $4);
35622347Spst		}
35722347Spst	|	STAT CRLF
35822347Spst		= {
35922347Spst			statcmd();
36022347Spst		}
36122347Spst	|	DELE check_login SP pathname CRLF
36222347Spst		= {
36322347Spst			if ($2 && $4)
36422347Spst				delete((char *) $4);
36522347Spst			if ($4)
36622347Spst				free((char *) $4);
36722347Spst		}
36822347Spst	|	RNTO SP pathname CRLF
36922347Spst		= {
37022347Spst			if (fromname) {
37122347Spst				renamecmd(fromname, (char *) $3);
37222347Spst				free(fromname);
37322347Spst				fromname = (char *) 0;
37422347Spst			} else {
37522347Spst				reply(503, "Bad sequence of commands.");
37622347Spst			}
37722347Spst			free((char *) $3);
37822347Spst		}
37922347Spst	|	ABOR CRLF
38022347Spst		= {
38122347Spst			reply(225, "ABOR command successful.");
38222347Spst		}
38322347Spst	|	CWD check_login CRLF
38422347Spst		= {
38522347Spst			if ($2)
38622347Spst				cwd(pw->pw_dir);
38722347Spst		}
38822347Spst	|	CWD check_login SP pathname CRLF
38922347Spst		= {
39022347Spst			if ($2 && $4)
39122347Spst				cwd((char *) $4);
39222347Spst			if ($4)
39322347Spst				free((char *) $4);
39422347Spst		}
39522347Spst	|	HELP CRLF
39622347Spst		= {
39722347Spst			help(cmdtab, (char *) 0);
39822347Spst		}
39922347Spst	|	HELP SP STRING CRLF
40022347Spst		= {
40122347Spst			register char *cp = (char *)$3;
40222347Spst
40322347Spst			if (strncasecmp(cp, "SITE", 4) == 0) {
40422347Spst				cp = (char *)$3 + 4;
40522347Spst				if (*cp == ' ')
40622347Spst					cp++;
40722347Spst				if (*cp)
40822347Spst					help(sitetab, cp);
40922347Spst				else
41022347Spst					help(sitetab, (char *) 0);
41122347Spst			} else
41222347Spst				help(cmdtab, (char *) $3);
41322347Spst		}
41422347Spst	|	NOOP CRLF
41522347Spst		= {
41622347Spst			reply(200, "NOOP command successful.");
41722347Spst		}
41822347Spst	|	MKD check_login SP pathname CRLF
41922347Spst		= {
42022347Spst			if ($2 && $4)
42122347Spst				makedir((char *) $4);
42222347Spst			if ($4)
42322347Spst				free((char *) $4);
42422347Spst		}
42522347Spst	|	RMD check_login SP pathname CRLF
42622347Spst		= {
42722347Spst			if ($2 && $4)
42822347Spst				removedir((char *) $4);
42922347Spst			if ($4)
43022347Spst				free((char *) $4);
43122347Spst		}
43222347Spst	|	PWD check_login CRLF
43322347Spst		= {
43422347Spst			if ($2)
43522347Spst				pwd();
43622347Spst		}
43722347Spst	|	CDUP check_login CRLF
43822347Spst		= {
43922347Spst			if ($2)
44022347Spst				cwd("..");
44122347Spst		}
44222347Spst	|	SITE SP HELP CRLF
44322347Spst		= {
44422347Spst			help(sitetab, (char *) 0);
44522347Spst		}
44622347Spst	|	SITE SP HELP SP STRING CRLF
44722347Spst		= {
44822347Spst			help(sitetab, (char *) $5);
44922347Spst		}
45022347Spst	|	SITE SP UMASK check_login CRLF
45122347Spst		= {
45222347Spst			int oldmask;
45322347Spst
45422347Spst			if ($4) {
45522347Spst				oldmask = umask(0);
45622347Spst				(void) umask(oldmask);
45722347Spst				reply(200, "Current UMASK is %03o", oldmask);
45822347Spst			}
45922347Spst		}
46022347Spst	|	SITE SP UMASK check_login SP octal_number CRLF
46122347Spst		= {
46222347Spst			int oldmask;
46322347Spst
46422347Spst			if ($4) {
46522347Spst				if (($6 == -1) || ($6 > 0777)) {
46622347Spst					reply(501, "Bad UMASK value");
46722347Spst				} else {
46822347Spst					oldmask = umask($6);
46922347Spst					reply(200,
47022347Spst					    "UMASK set to %03o (was %03o)",
47122347Spst					    $6, oldmask);
47222347Spst				}
47322347Spst			}
47422347Spst		}
47522347Spst	|	SITE SP CHMOD check_login SP octal_number SP pathname CRLF
47622347Spst		= {
47722347Spst			if ($4 && $8) {
47822347Spst				if ($6 > 0777)
47922347Spst					reply(501,
48022347Spst				"CHMOD: Mode value must be between 0 and 0777");
48122347Spst				else if (chmod((char *) $8, $6) < 0)
48222347Spst					perror_reply(550, (char *) $8);
48322347Spst				else
48422347Spst					reply(200, "CHMOD command successful.");
48522347Spst			}
48622347Spst			if ($8)
48722347Spst				free((char *) $8);
48822347Spst		}
48922347Spst	|	SITE SP IDLE CRLF
49022347Spst		= {
49122347Spst			reply(200,
49222347Spst			    "Current IDLE time limit is %d seconds; max %d",
49322347Spst				timeout, maxtimeout);
49422347Spst		}
49522347Spst	|	SITE SP IDLE SP NUMBER CRLF
49622347Spst		= {
49722347Spst			if ($5 < 30 || $5 > maxtimeout) {
49822347Spst				reply(501,
49922347Spst			"Maximum IDLE time must be between 30 and %d seconds",
50022347Spst				    maxtimeout);
50122347Spst			} else {
50222347Spst				timeout = $5;
50322347Spst				(void) alarm((unsigned) timeout);
50422347Spst				reply(200,
50522347Spst				    "Maximum IDLE time set to %d seconds",
50622347Spst				    timeout);
50722347Spst			}
50822347Spst		}
50922347Spst	|	STOU check_login SP pathname CRLF
51022347Spst		= {
51122347Spst			if ($2 && $4)
51222347Spst				store((char *) $4, "w", 1);
51322347Spst			if ($4)
51422347Spst				free((char *) $4);
51522347Spst		}
51622347Spst	|	SYST CRLF
51722347Spst		= {
51822347Spst#ifdef unix
51922347Spst#ifdef BSD
52022347Spst			reply(215, "UNIX Type: L%d Version: BSD-%d",
52122347Spst				NBBY, BSD);
52222347Spst#else /* BSD */
52322347Spst			reply(215, "UNIX Type: L%d", NBBY);
52422347Spst#endif /* BSD */
52522347Spst#else /* unix */
52622347Spst			reply(215, "UNKNOWN Type: L%d", NBBY);
52722347Spst#endif /* unix */
52822347Spst		}
52922347Spst
53022347Spst		/*
53122347Spst		 * SIZE is not in RFC959, but Postel has blessed it and
53222347Spst		 * it will be in the updated RFC.
53322347Spst		 *
53422347Spst		 * Return size of file in a format suitable for
53522347Spst		 * using with RESTART (we just count bytes).
53622347Spst		 */
53722347Spst	|	SIZE check_login SP pathname CRLF
53822347Spst		= {
53922347Spst			if ($2 && $4)
54022347Spst				sizecmd((char *) $4);
54122347Spst			if ($4)
54222347Spst				free((char *) $4);
54322347Spst		}
54422347Spst
54522347Spst		/*
54622347Spst		 * MDTM is not in RFC959, but Postel has blessed it and
54722347Spst		 * it will be in the updated RFC.
54822347Spst		 *
54922347Spst		 * Return modification time of file as an ISO 3307
55022347Spst		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
55122347Spst		 * where xxx is the fractional second (of any precision,
55222347Spst		 * not necessarily 3 digits)
55322347Spst		 */
55422347Spst	|	MDTM check_login SP pathname CRLF
55522347Spst		= {
55622347Spst			if ($2 && $4) {
55722347Spst				struct stat stbuf;
55822347Spst				if (stat((char *) $4, &stbuf) < 0)
55922347Spst					perror_reply(550, (char *) $4);
56022347Spst				else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
56122347Spst					reply(550, "%s: not a plain file.",
56222347Spst						(char *) $4);
56322347Spst				} else {
56422347Spst					register struct tm *t;
56522347Spst					struct tm *gmtime();
56622347Spst					t = gmtime(&stbuf.st_mtime);
56722347Spst					reply(213,
56831940Salex					    "%d%02d%02d%02d%02d%02d",
56931940Salex					    t->tm_year+1900, t->tm_mon+1, t->tm_mday,
57022347Spst					    t->tm_hour, t->tm_min, t->tm_sec);
57122347Spst				}
57222347Spst			}
57322347Spst			if ($4)
57422347Spst				free((char *) $4);
57522347Spst		}
57622347Spst	|	QUIT CRLF
57722347Spst		= {
57822347Spst			reply(221, "Goodbye.");
57922347Spst			dologout(0);
58022347Spst		}
58122347Spst	|	error CRLF
58222347Spst		= {
58322347Spst			yyerrok;
58422347Spst		}
58522347Spst	;
58622347Spstrcmd:		RNFR check_login SP pathname CRLF
58722347Spst		= {
58822347Spst			char *renamefrom();
58922347Spst
59022347Spst			restart_point = (off_t) 0;
59122347Spst			if ($2 && $4) {
59222347Spst				fromname = renamefrom((char *) $4);
59322347Spst				if (fromname == (char *) 0 && $4) {
59422347Spst					free((char *) $4);
59522347Spst				}
59622347Spst			}
59722347Spst		}
59822347Spst	|	REST SP byte_size CRLF
59922347Spst		= {
60022347Spst			long atol();
60122347Spst
60222347Spst			fromname = (char *) 0;
60322347Spst			restart_point = $3;
60422347Spst			reply(350, "Restarting at %ld. %s", restart_point,
60522347Spst			    "Send STORE or RETRIEVE to initiate transfer.");
60622347Spst		}
60722347Spst	;
60822347Spst
60922347Spstusername:	STRING
61022347Spst	;
61122347Spst
61222347Spstpassword:	/* empty */
61322347Spst		= {
61422347Spst			*(char **)&($$) = (char *)calloc(1, sizeof(char));
61522347Spst		}
61622347Spst	|	STRING
61722347Spst	;
61822347Spst
61922347Spstbyte_size:	NUMBER
62022347Spst	;
62122347Spst
62222347Spsthost_port:	NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
62322347Spst		NUMBER COMMA NUMBER
62422347Spst		= {
62522347Spst			register char *a, *p;
62622347Spst
62722347Spst			a = (char *)&data_dest.sin_addr;
62822347Spst			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
62922347Spst
63022347Spst/* H* port fix, part A-1: Check the args against the client addr */
63122347Spst            p = (char *)&his_addr.sin_addr;
63222347Spst             if (memcmp (a, p, sizeof (data_dest.sin_addr)))
63322347Spst                 memset (a, 0, sizeof (data_dest.sin_addr));     /* XXX */
63422347Spst
63522347Spst			p = (char *)&data_dest.sin_port;
63622347Spst
63722347Spst/* H* port fix, part A-2: only allow client ports in "user space" */
63822347Spst            p[0] = 0; p[1] = 0;
63922347Spst            cliport = ($9 << 8) + $11;
64022347Spst            if (cliport > 1023) {
64122347Spst                 p[0] = $9; p[1] = $11;
64222347Spst            }
64322347Spst
64422347Spst			p[0] = $9; p[1] = $11;
64522347Spst			data_dest.sin_family = AF_INET;
64622347Spst		}
64722347Spst	;
64822347Spst
64922347Spstform_code:	N
65022347Spst	= {
65122347Spst		$$ = FORM_N;
65222347Spst	}
65322347Spst	|	T
65422347Spst	= {
65522347Spst		$$ = FORM_T;
65622347Spst	}
65722347Spst	|	C
65822347Spst	= {
65922347Spst		$$ = FORM_C;
66022347Spst	}
66122347Spst	;
66222347Spst
66322347Spsttype_code:	A
66422347Spst	= {
66522347Spst		cmd_type = TYPE_A;
66622347Spst		cmd_form = FORM_N;
66722347Spst	}
66822347Spst	|	A SP form_code
66922347Spst	= {
67022347Spst		cmd_type = TYPE_A;
67122347Spst		cmd_form = $3;
67222347Spst	}
67322347Spst	|	E
67422347Spst	= {
67522347Spst		cmd_type = TYPE_E;
67622347Spst		cmd_form = FORM_N;
67722347Spst	}
67822347Spst	|	E SP form_code
67922347Spst	= {
68022347Spst		cmd_type = TYPE_E;
68122347Spst		cmd_form = $3;
68222347Spst	}
68322347Spst	|	I
68422347Spst	= {
68522347Spst		cmd_type = TYPE_I;
68622347Spst	}
68722347Spst	|	L
68822347Spst	= {
68922347Spst		cmd_type = TYPE_L;
69022347Spst		cmd_bytesz = NBBY;
69122347Spst	}
69222347Spst	|	L SP byte_size
69322347Spst	= {
69422347Spst		cmd_type = TYPE_L;
69522347Spst		cmd_bytesz = $3;
69622347Spst	}
69722347Spst	/* this is for a bug in the BBN ftp */
69822347Spst	|	L byte_size
69922347Spst	= {
70022347Spst		cmd_type = TYPE_L;
70122347Spst		cmd_bytesz = $2;
70222347Spst	}
70322347Spst	;
70422347Spst
70522347Spststruct_code:	F
70622347Spst	= {
70722347Spst		$$ = STRU_F;
70822347Spst	}
70922347Spst	|	R
71022347Spst	= {
71122347Spst		$$ = STRU_R;
71222347Spst	}
71322347Spst	|	P
71422347Spst	= {
71522347Spst		$$ = STRU_P;
71622347Spst	}
71722347Spst	;
71822347Spst
71922347Spstmode_code:	S
72022347Spst	= {
72122347Spst		$$ = MODE_S;
72222347Spst	}
72322347Spst	|	B
72422347Spst	= {
72522347Spst		$$ = MODE_B;
72622347Spst	}
72722347Spst	|	C
72822347Spst	= {
72922347Spst		$$ = MODE_C;
73022347Spst	}
73122347Spst	;
73222347Spst
73322347Spstpathname:	pathstring
73422347Spst	= {
73522347Spst		/*
73622347Spst		 * Problem: this production is used for all pathname
73722347Spst		 * processing, but only gives a 550 error reply.
73822347Spst		 * This is a valid reply in some cases but not in others.
73922347Spst		 */
74022347Spst		if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) {
74122347Spst			*(char **)&($$) = *ftpglob((char *) $1);
74222347Spst			if (globerr != NULL) {
74322347Spst				reply(550, globerr);
74422347Spst/*				$$ = NULL; */
74522347Spst				$$ = 0;
74622347Spst			}
74722347Spst			free((char *) $1);
74822347Spst		} else
74922347Spst			$$ = $1;
75022347Spst	}
75122347Spst	;
75222347Spst
75322347Spstpathstring:	STRING
75422347Spst	;
75522347Spst
75622347Spstoctal_number:	NUMBER
75722347Spst	= {
75822347Spst		register int ret, dec, multby, digit;
75922347Spst
76022347Spst		/*
76122347Spst		 * Convert a number that was read as decimal number
76222347Spst		 * to what it would be if it had been read as octal.
76322347Spst		 */
76422347Spst		dec = $1;
76522347Spst		multby = 1;
76622347Spst		ret = 0;
76722347Spst		while (dec) {
76822347Spst			digit = dec%10;
76922347Spst			if (digit > 7) {
77022347Spst				ret = -1;
77122347Spst				break;
77222347Spst			}
77322347Spst			ret += digit * multby;
77422347Spst			multby *= 8;
77522347Spst			dec /= 10;
77622347Spst		}
77722347Spst		$$ = ret;
77822347Spst	}
77922347Spst	;
78022347Spst
78122347Spstcheck_login:	/* empty */
78222347Spst	= {
78322347Spst		if (logged_in)
78422347Spst			$$ = 1;
78522347Spst		else {
78622347Spst			reply(530, "Please login with USER and PASS.");
78722347Spst			$$ = 0;
78822347Spst		}
78922347Spst	}
79022347Spst	;
79122347Spst
79222347Spst%%
79322347Spst
79422347Spstextern jmp_buf errcatch;
79522347Spst
79622347Spst#define	CMD	0	/* beginning of command */
79722347Spst#define	ARGS	1	/* expect miscellaneous arguments */
79822347Spst#define	STR1	2	/* expect SP followed by STRING */
79922347Spst#define	STR2	3	/* expect STRING */
80022347Spst#define	OSTR	4	/* optional SP then STRING */
80122347Spst#define	ZSTR1	5	/* SP then optional STRING */
80222347Spst#define	ZSTR2	6	/* optional STRING after SP */
80322347Spst#define	SITECMD	7	/* SITE command */
80422347Spst#define	NSTR	8	/* Number followed by a string */
80522347Spst
80622347Spststruct tab cmdtab[] = {		/* In order defined in RFC 765 */
80722347Spst	{ "USER", USER, STR1, 1,	"<sp> username" },
80822347Spst	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
80922347Spst	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
81022347Spst	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
81122347Spst	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
81222347Spst	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
81322347Spst	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
81422347Spst	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
81522347Spst	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
81622347Spst	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
81722347Spst	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
81822347Spst	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
81922347Spst	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
82022347Spst	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
82122347Spst	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
82222347Spst	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
82322347Spst	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
82422347Spst	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
82522347Spst	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
82622347Spst	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
82722347Spst	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
82822347Spst	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
82922347Spst	{ "REST", REST, ARGS, 1,	"(restart command)" },
83022347Spst	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
83122347Spst	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
83222347Spst	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
83322347Spst	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
83422347Spst	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
83522347Spst	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
83622347Spst	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
83722347Spst	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
83822347Spst	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
83922347Spst	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
84022347Spst	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
84122347Spst	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
84222347Spst	{ "NOOP", NOOP, ARGS, 1,	"" },
84322347Spst	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
84422347Spst	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
84522347Spst	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
84622347Spst	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
84722347Spst	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
84822347Spst	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
84922347Spst	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
85022347Spst	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
85122347Spst	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
85222347Spst	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
85322347Spst	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
85422347Spst	{ NULL,   0,    0,    0,	0 }
85522347Spst};
85622347Spst
85722347Spststruct tab sitetab[] = {
85822347Spst	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
85922347Spst	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
86022347Spst	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
86122347Spst	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
86222347Spst	{ NULL,   0,    0,    0,	0 }
86322347Spst};
86422347Spst
86522347Spststruct tab *lookup FUNCTION((p, cmd), register struct tab *p AND char *cmd)
86622347Spst{
86722347Spst
86822347Spst	for (; p->name != NULL; p++)
86922347Spst		if (strcmp(cmd, p->name) == 0)
87022347Spst			return (p);
87122347Spst	return (0);
87222347Spst}
87322347Spst
87422347Spst#include <arpa/telnet.h>
87522347Spst
87622347Spst/*
87722347Spst * getline - a hacked up version of fgets to ignore TELNET escape codes.
87822347Spst */
87922347Spstchar *getline FUNCTION((s, n, iop), char *s AND int n AND FILE *iop)
88022347Spst{
88122347Spst	register c;
88222347Spst	register char *cs;
88322347Spst
88422347Spst	cs = s;
88522347Spst/* tmpline may contain saved command from urgent mode interruption */
88622347Spst	for (c = 0; *(tmpline + c) && --n > 0; ++c) {
88722347Spst		*cs++ = *(tmpline + c);
88822347Spst		if (*(tmpline + c) == '\n') {
88922347Spst			*cs++ = '\0';
89022347Spst			if (debug)
89122347Spst				syslog(LOG_DEBUG, "command: %s", s);
89222347Spst			*tmpline = '\0';
89322347Spst			return(s);
89422347Spst		}
89522347Spst		if (c == 0)
89622347Spst			*tmpline = '\0';
89722347Spst	}
89822347Spst	while ((c = getc(iop)) != EOF) {
89922347Spst		c &= 0377;
90022347Spst		if (c == IAC) {
90122347Spst		    if ((c = getc(iop)) != EOF) {
90222347Spst			c &= 0377;
90322347Spst			switch (c) {
90422347Spst			case WILL:
90522347Spst			case WONT:
90622347Spst				c = getc(iop);
90722347Spst				printf("%c%c%c", IAC, DONT, 0377&c);
90822347Spst				(void) fflush(stdout);
90922347Spst				continue;
91022347Spst			case DO:
91122347Spst			case DONT:
91222347Spst				c = getc(iop);
91322347Spst				printf("%c%c%c", IAC, WONT, 0377&c);
91422347Spst				(void) fflush(stdout);
91522347Spst				continue;
91622347Spst			case IAC:
91722347Spst				break;
91822347Spst			default:
91922347Spst				continue;	/* ignore command */
92022347Spst			}
92122347Spst		    }
92222347Spst		}
92322347Spst		*cs++ = c;
92422347Spst		if (--n <= 0 || c == '\n')
92522347Spst			break;
92622347Spst	}
92722347Spst	if (c == EOF && cs == s)
92822347Spst		return (NULL);
92922347Spst	*cs++ = '\0';
93022347Spst	if (debug)
93122347Spst		syslog(LOG_DEBUG, "command: %s", s);
93222347Spst	return (s);
93322347Spst}
93422347Spst
93522347Spststatic VOIDRET toolong FUNCTION((input), int input)
93622347Spst{
93722347Spst	time_t now;
93822347Spst
93922347Spst	reply(421, "Timeout (%d seconds): closing control connection.", timeout);
94022347Spst	(void) time(&now);
94122347Spst        syslog(LOG_INFO, "User %s timed out after %d seconds at %s",
94222347Spst          (pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
94322347Spst	dologout(1);
94422347Spst}
94522347Spst
94622347Spstint yylex FUNCTION_NOARGS
94722347Spst{
94822347Spst	static int cpos, state;
94922347Spst	register char *cp, *cp2;
95022347Spst	register struct tab *p;
95122347Spst	int n;
95222347Spst	char c, *copy();
95322347Spst
95422347Spst	for (;;) {
95522347Spst		switch (state) {
95622347Spst
95722347Spst		case CMD:
95822347Spst			(void) signal(SIGALRM, toolong);
95922347Spst			(void) alarm((unsigned) timeout);
96022347Spst			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
96122347Spst				reply(221, "You could at least say goodbye.");
96222347Spst				dologout(0);
96322347Spst			}
96422347Spst			(void) alarm(0);
96522347Spst#ifdef SETPROCTITLE
96622347Spst			if (strncasecmp(cbuf, "PASS", 4) != NULL)
96722347Spst				setproctitle("%s: %s", proctitle, cbuf);
96822347Spst#endif /* SETPROCTITLE */
96922347Spst			if ((cp = strchr(cbuf, '\r'))) {
97022347Spst				*cp++ = '\n';
97122347Spst				*cp = '\0';
97222347Spst			}
97322347Spst			if ((cp = strpbrk(cbuf, " \n")))
97422347Spst				cpos = cp - cbuf;
97522347Spst			if (cpos == 0)
97622347Spst				cpos = 4;
97722347Spst			c = cbuf[cpos];
97822347Spst			cbuf[cpos] = '\0';
97922347Spst			upper(cbuf);
98022347Spst			p = lookup(cmdtab, cbuf);
98122347Spst			cbuf[cpos] = c;
98222347Spst			if (p != 0) {
98322347Spst				if (p->implemented == 0) {
98422347Spst					nack(p->name);
98522347Spst					longjmp(errcatch,0);
98622347Spst					/* NOTREACHED */
98722347Spst				}
98822347Spst				state = p->state;
98922347Spst				*(char **)&yylval = p->name;
99022347Spst				return (p->token);
99122347Spst			}
99222347Spst			break;
99322347Spst
99422347Spst		case SITECMD:
99522347Spst			if (cbuf[cpos] == ' ') {
99622347Spst				cpos++;
99722347Spst				return (SP);
99822347Spst			}
99922347Spst			cp = &cbuf[cpos];
100022347Spst			if ((cp2 = strpbrk(cp, " \n")))
100122347Spst				cpos = cp2 - cbuf;
100222347Spst			c = cbuf[cpos];
100322347Spst			cbuf[cpos] = '\0';
100422347Spst			upper(cp);
100522347Spst			p = lookup(sitetab, cp);
100622347Spst			cbuf[cpos] = c;
100722347Spst			if (p != 0) {
100822347Spst				if (p->implemented == 0) {
100922347Spst					state = CMD;
101022347Spst					nack(p->name);
101122347Spst					longjmp(errcatch,0);
101222347Spst					/* NOTREACHED */
101322347Spst				}
101422347Spst				state = p->state;
101522347Spst				*(char **)&yylval = p->name;
101622347Spst				return (p->token);
101722347Spst			}
101822347Spst			state = CMD;
101922347Spst			break;
102022347Spst
102122347Spst		case OSTR:
102222347Spst			if (cbuf[cpos] == '\n') {
102322347Spst				state = CMD;
102422347Spst				return (CRLF);
102522347Spst			}
102622347Spst			/* FALLTHROUGH */
102722347Spst
102822347Spst		case STR1:
102922347Spst		case ZSTR1:
103022347Spst		dostr1:
103122347Spst			if (cbuf[cpos] == ' ') {
103222347Spst				cpos++;
103322347Spst				state = state == OSTR ? STR2 : ++state;
103422347Spst				return (SP);
103522347Spst			}
103622347Spst			break;
103722347Spst
103822347Spst		case ZSTR2:
103922347Spst			if (cbuf[cpos] == '\n') {
104022347Spst				state = CMD;
104122347Spst				return (CRLF);
104222347Spst			}
104322347Spst			/* FALLTHROUGH */
104422347Spst
104522347Spst		case STR2:
104622347Spst			cp = &cbuf[cpos];
104722347Spst			n = strlen(cp);
104822347Spst			cpos += n - 1;
104922347Spst			/*
105022347Spst			 * Make sure the string is nonempty and \n terminated.
105122347Spst			 */
105222347Spst			if (n > 1 && cbuf[cpos] == '\n') {
105322347Spst				cbuf[cpos] = '\0';
105422347Spst				*(char **)&yylval = copy(cp);
105522347Spst				cbuf[cpos] = '\n';
105622347Spst				state = ARGS;
105722347Spst				return (STRING);
105822347Spst			}
105922347Spst			break;
106022347Spst
106122347Spst		case NSTR:
106222347Spst			if (cbuf[cpos] == ' ') {
106322347Spst				cpos++;
106422347Spst				return (SP);
106522347Spst			}
106622347Spst			if (isdigit(cbuf[cpos])) {
106722347Spst				cp = &cbuf[cpos];
106822347Spst				while (isdigit(cbuf[++cpos]))
106922347Spst					;
107022347Spst				c = cbuf[cpos];
107122347Spst				cbuf[cpos] = '\0';
107222347Spst				yylval = atoi(cp);
107322347Spst				cbuf[cpos] = c;
107422347Spst				state = STR1;
107522347Spst				return (NUMBER);
107622347Spst			}
107722347Spst			state = STR1;
107822347Spst			goto dostr1;
107922347Spst
108022347Spst		case ARGS:
108122347Spst			if (isdigit(cbuf[cpos])) {
108222347Spst				cp = &cbuf[cpos];
108322347Spst				while (isdigit(cbuf[++cpos]))
108422347Spst					;
108522347Spst				c = cbuf[cpos];
108622347Spst				cbuf[cpos] = '\0';
108722347Spst				yylval = atoi(cp);
108822347Spst				cbuf[cpos] = c;
108922347Spst				return (NUMBER);
109022347Spst			}
109122347Spst			switch (cbuf[cpos++]) {
109222347Spst
109322347Spst			case '\n':
109422347Spst				state = CMD;
109522347Spst				return (CRLF);
109622347Spst
109722347Spst			case ' ':
109822347Spst				return (SP);
109922347Spst
110022347Spst			case ',':
110122347Spst				return (COMMA);
110222347Spst
110322347Spst			case 'A':
110422347Spst			case 'a':
110522347Spst				return (A);
110622347Spst
110722347Spst			case 'B':
110822347Spst			case 'b':
110922347Spst				return (B);
111022347Spst
111122347Spst			case 'C':
111222347Spst			case 'c':
111322347Spst				return (C);
111422347Spst
111522347Spst			case 'E':
111622347Spst			case 'e':
111722347Spst				return (E);
111822347Spst
111922347Spst			case 'F':
112022347Spst			case 'f':
112122347Spst				return (F);
112222347Spst
112322347Spst			case 'I':
112422347Spst			case 'i':
112522347Spst				return (I);
112622347Spst
112722347Spst			case 'L':
112822347Spst			case 'l':
112922347Spst				return (L);
113022347Spst
113122347Spst			case 'N':
113222347Spst			case 'n':
113322347Spst				return (N);
113422347Spst
113522347Spst			case 'P':
113622347Spst			case 'p':
113722347Spst				return (P);
113822347Spst
113922347Spst			case 'R':
114022347Spst			case 'r':
114122347Spst				return (R);
114222347Spst
114322347Spst			case 'S':
114422347Spst			case 's':
114522347Spst				return (S);
114622347Spst
114722347Spst			case 'T':
114822347Spst			case 't':
114922347Spst				return (T);
115022347Spst
115122347Spst			}
115222347Spst			break;
115322347Spst
115422347Spst		default:
115522347Spst			opiefatal("Unknown state in scanner.");
115622347Spst		}
115722347Spst		yyerror((char *) 0);
115822347Spst		state = CMD;
115922347Spst		longjmp(errcatch,0);
116022347Spst	}
116122347Spst}
116222347Spst
116322347SpstVOIDRET upper FUNCTION((s), char *s)
116422347Spst{
116522347Spst	while (*s != '\0') {
116622347Spst		if (islower(*s))
116722347Spst			*s = toupper(*s);
116822347Spst		s++;
116922347Spst	}
117022347Spst}
117122347Spst
117222347Spstchar *copy FUNCTION((s), char *s)
117322347Spst{
117422347Spst	char *p;
117522347Spst
117622347Spst	p = malloc((unsigned) strlen(s) + 1);
117722347Spst	if (p == NULL)
117822347Spst		opiefatal("Ran out of memory.");
117922347Spst	(void) strcpy(p, s);
118022347Spst	return (p);
118122347Spst}
118222347Spst
118322347SpstVOIDRET help FUNCTION((ctab, s), struct tab *ctab AND char *s)
118422347Spst{
118522347Spst	register struct tab *c;
118622347Spst	register int width, NCMDS;
118722347Spst	char *type;
118822347Spst
118922347Spst	if (ctab == sitetab)
119022347Spst		type = "SITE ";
119122347Spst	else
119222347Spst		type = "";
119322347Spst	width = 0, NCMDS = 0;
119422347Spst	for (c = ctab; c->name != NULL; c++) {
119522347Spst		int len = strlen(c->name);
119622347Spst
119722347Spst		if (len > width)
119822347Spst			width = len;
119922347Spst		NCMDS++;
120022347Spst	}
120122347Spst	width = (width + 8) &~ 7;
120222347Spst	if (s == 0) {
120322347Spst		register int i, j, w;
120422347Spst		int columns, lines;
120522347Spst
120622347Spst		lreply(214, "The following %scommands are recognized %s.",
120722347Spst		    type, "(* =>'s unimplemented)");
120822347Spst		columns = 76 / width;
120922347Spst		if (columns == 0)
121022347Spst			columns = 1;
121122347Spst		lines = (NCMDS + columns - 1) / columns;
121222347Spst		for (i = 0; i < lines; i++) {
121322347Spst			printf("   ");
121422347Spst			for (j = 0; j < columns; j++) {
121522347Spst				c = ctab + j * lines + i;
121622347Spst				printf("%s%c", c->name,
121722347Spst					c->implemented ? ' ' : '*');
121822347Spst				if (c + lines >= &ctab[NCMDS])
121922347Spst					break;
122022347Spst				w = strlen(c->name) + 1;
122122347Spst				while (w < width) {
122222347Spst					putchar(' ');
122322347Spst					w++;
122422347Spst				}
122522347Spst			}
122622347Spst			printf("\r\n");
122722347Spst		}
122822347Spst		(void) fflush(stdout);
122929964Sache		reply(214, " ");
123022347Spst		return;
123122347Spst	}
123222347Spst	upper(s);
123322347Spst	c = lookup(ctab, s);
123422347Spst	if (c == (struct tab *)0) {
123522347Spst		reply(502, "Unknown command %s.", s);
123622347Spst		return;
123722347Spst	}
123822347Spst	if (c->implemented)
123922347Spst		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
124022347Spst	else
124122347Spst		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
124222347Spst		    c->name, c->help);
124322347Spst}
124422347Spst
124522347SpstVOIDRET sizecmd FUNCTION((filename), char *filename)
124622347Spst{
124722347Spst	switch (type) {
124822347Spst	case TYPE_L:
124922347Spst	case TYPE_I: {
125022347Spst		struct stat stbuf;
125122347Spst		if (stat(filename, &stbuf) < 0 ||
125222347Spst		    (stbuf.st_mode&S_IFMT) != S_IFREG)
125322347Spst			reply(550, "%s: not a plain file.", filename);
125422347Spst		else
125522347Spst			reply(213, "%lu", stbuf.st_size);
125622347Spst		break;}
125722347Spst	case TYPE_A: {
125822347Spst		FILE *fin;
125922347Spst		register int c;
126022347Spst		register long count;
126122347Spst		struct stat stbuf;
126222347Spst		fin = fopen(filename, "r");
126322347Spst		if (fin == NULL) {
126422347Spst			perror_reply(550, filename);
126522347Spst			return;
126622347Spst		}
126722347Spst		if (fstat(fileno(fin), &stbuf) < 0 ||
126822347Spst		    (stbuf.st_mode&S_IFMT) != S_IFREG) {
126922347Spst			reply(550, "%s: not a plain file.", filename);
127022347Spst			(void) fclose(fin);
127122347Spst			return;
127222347Spst		}
127322347Spst
127422347Spst		count = 0;
127522347Spst		while((c=getc(fin)) != EOF) {
127622347Spst			if (c == '\n')	/* will get expanded to \r\n */
127722347Spst				count++;
127822347Spst			count++;
127922347Spst		}
128022347Spst		(void) fclose(fin);
128122347Spst
128222347Spst		reply(213, "%ld", count);
128322347Spst		break;}
128422347Spst	default:
128522347Spst		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
128622347Spst	}
128722347Spst}
1288