ftpcmd.y revision 22347
1262685Sdelphij/* ftpcmd.y: yacc parser for the FTP daemon.
262449Speter
3262685Sdelphij%%% portions-copyright-cmetz
4166124SrafanPortions of this software are Copyright 1996 by Craig Metz, All Rights
5262685SdelphijReserved. The Inner Net License Version 2 applies to these portions of
6166124Srafanthe software.
7166124SrafanYou should have received a copy of the license with this software. If
8166124Srafanyou didn't get a copy, you may request one from <license@inner.net>.
9166124Srafan
10166124Srafan	History:
11166124Srafan
12166124Srafan	Modified by cmetz for OPIE 2.3. Moved LS_COMMAND here.
13166124Srafan        Modified by cmetz for OPIE 2.2. Fixed a *lot* of warnings.
14166124Srafan                Use FUNCTION declaration et al. Removed useless strings.
15166124Srafan                Changed some char []s to char *s. Deleted comment address.
16166124Srafan                Changed tmpline references to be more pure-pointer
17166124Srafan                references. Changed tmpline declaration back to char [].
18166124Srafan	Modified at NRL for OPIE 2.1. Minor changes for autoconf.
19166124Srafan        Modified at NRL for OPIE 2.01. Added forward declaration for sitetab[]
20166124Srafan                -- fixes problems experienced by bison users. Merged in new
21166124Srafan                PORT attack fixes from Hobbit.
22166124Srafan	Modified at NRL for OPIE 2.0.
23166124Srafan	Originally from BSD.
24166124Srafan*/
25166124Srafan/*
26166124Srafan * Copyright (c) 1985, 1988 Regents of the University of California.
27166124Srafan * All rights reserved.
28166124Srafan *
29166124Srafan * Redistribution and use in source and binary forms, with or without
30166124Srafan * modification, are permitted provided that the following conditions
3162449Speter * are met:
3262449Speter * 1. Redistributions of source code must retain the above copyright
3362449Speter *    notice, this list of conditions and the following disclaimer.
3462449Speter * 2. Redistributions in binary form must reproduce the above copyright
3562449Speter *    notice, this list of conditions and the following disclaimer in the
36166124Srafan *    documentation and/or other materials provided with the distribution.
3762449Speter * 3. All advertising materials mentioning features or use of this software
3862449Speter *    must display the following acknowledgement:
3962449Speter *	This product includes software developed by the University of
4062449Speter *	California, Berkeley and its contributors.
4162449Speter * 4. Neither the name of the University nor the names of its contributors
4262449Speter *    may be used to endorse or promote products derived from this software
4362449Speter *    without specific prior written permission.
4462449Speter *
4562449Speter * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
4662449Speter * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
4762449Speter * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
4862449Speter * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
4962449Speter * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
5062449Speter * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
5162449Speter * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
5262449Speter * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
5362449Speter * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
5462449Speter * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
5562449Speter * SUCH DAMAGE.
5662449Speter *
5762449Speter *	@(#)ftpcmd.y	5.24 (Berkeley) 2/25/91
5862449Speter */
5962449Speter
6062449Speter/*
6162449Speter * Grammar for FTP commands.
6262449Speter * See RFC 959.
6362449Speter */
6462449Speter
6562449Speter%{
6662449Speter#include "opie_cfg.h"
6762449Speter
6862449Speter#include <sys/param.h>
6962449Speter#include <sys/types.h>
7062449Speter#include <sys/socket.h>
7162449Speter#include <sys/stat.h>
7262449Speter#include <netinet/in.h>
7362449Speter#include <arpa/ftp.h>
7462449Speter#include <signal.h>
7562449Speter#include <setjmp.h>
7662449Speter#include <syslog.h>
7762449Speter#if TM_IN_SYS_TIME
7862449Speter#include <sys/time.h>
7962449Speter#else /* TM_IN_SYS_TIME */
8062449Speter#include <time.h>
8162449Speter#endif /* TM_IN_SYS_TIME */
8262449Speter#include <pwd.h>
8362449Speter#include <unistd.h>
8462449Speter#include <stdio.h>
8562449Speter#include <ctype.h>
8662449Speter#include <stdlib.h>
8762449Speter#include <string.h>
8862449Speter
8962449Speter#include "opie.h"
9062449Speter
9162449Speter#if HAVE_LS_G_FLAG
9262449Speter#define LS_COMMAND "/bin/ls -lgA"
9362449Speter#else /* HAVE_LS_G_FLAG */
9462449Speter#define LS_COMMAND "/bin/ls -lA"
9562449Speter#endif /* HAVE_LS_G_FLAG */
9662449Speter
9762449Speterextern	struct sockaddr_in data_dest;
9862449Speterextern  struct sockaddr_in his_addr;
9962449Speterextern	int logged_in;
10062449Speterextern	struct passwd *pw;
10162449Speterextern	int guest;
10262449Speterextern	int type;
10362449Speterextern	int form;
10462449Speterextern	int debug;
10562449Speterextern	int timeout;
10662449Speterextern	int maxtimeout;
10762449Speterextern  int pdata;
10862449Speterextern	char *remotehost;
10962449Speterextern	char *proctitle;
11062449Speterextern	char *globerr;
11162449Speterextern	int usedefault;
11262449Speterextern  int transflag;
11362449Speterextern  char tmpline[];
11462449Speterchar	**ftpglob();
11562449Speter
11662449SpeterVOIDRET dologout __P((int));
11762449SpeterVOIDRET upper __P((char *));
11862449SpeterVOIDRET nack __P((char *));
11962449SpeterVOIDRET opiefatal __P((char *));
12062449Speter
12162449SpeterVOIDRET pass __P((char *));
12262449Speterint user __P((char *));
12362449SpeterVOIDRET passive __P((void));
12462449SpeterVOIDRET retrieve __P((char *, char *));
12562449SpeterVOIDRET store __P((char *, char *, int));
12662449SpeterVOIDRET send_file_list __P((char *));
12762449SpeterVOIDRET statfilecmd __P((char *));
12862449SpeterVOIDRET statcmd __P((void));
12962449SpeterVOIDRET delete __P((char *));
13062449SpeterVOIDRET renamecmd __P((char *, char *));
13162449SpeterVOIDRET cwd __P((char *));
13262449SpeterVOIDRET makedir __P((char *));
13362449SpeterVOIDRET removedir __P((char *));
13462449SpeterVOIDRET pwd __P((void));
13562449Speter
13662449SpeterVOIDRET sizecmd __P((char *));
13762449Speter
13862449Speteroff_t	restart_point;
13962449Speter
14062449Speterstatic	int cmd_type;
14162449Speterstatic	int cmd_form;
14262449Speterstatic	int cmd_bytesz;
14362449Speterstatic  unsigned short cliport = 0;
14462449Speterchar	cbuf[512];
14562449Speterchar	*fromname;
14662449Speter
14762449Speterstruct tab {
14862449Speter	char	*name;
14962449Speter	short	token;
15062449Speter	short	state;
15162449Speter	short	implemented;	/* 1 if command is implemented */
15262449Speter	char	*help;
15362449Speter};
15462449Speter
15562449SpeterVOIDRET help __P((struct tab *, char *));
15662449Speter
15762449Speterstruct tab cmdtab[], sitetab[];
15862449Speter
15962449Speter%}
16062449Speter
16162449Speter%token
16262449Speter	A	B	C	E	F	I
16362449Speter	L	N	P	R	S	T
16462449Speter
16562449Speter	SP	CRLF	COMMA	STRING	NUMBER
16662449Speter
16762449Speter	USER	PASS	ACCT	REIN	QUIT	PORT
16862449Speter	PASV	TYPE	STRU	MODE	RETR	STOR
16962449Speter	APPE	MLFL	MAIL	MSND	MSOM	MSAM
17062449Speter	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
17162449Speter	ABOR	DELE	CWD	LIST	NLST	SITE
17262449Speter	STAT	HELP	NOOP	MKD	RMD	PWD
17362449Speter	CDUP	STOU	SMNT	SYST	SIZE	MDTM
17462449Speter
17562449Speter	UMASK	IDLE	CHMOD
17662449Speter
17762449Speter	LEXERR
17862449Speter
17962449Speter%start	cmd_list
18062449Speter
18162449Speter%%
18262449Speter
18362449Spetercmd_list:	/* empty */
18462449Speter	|	cmd_list cmd
18562449Speter		= {
18662449Speter			fromname = (char *) 0;
18762449Speter			restart_point = (off_t) 0;
18862449Speter		}
18962449Speter	|	cmd_list rcmd
19062449Speter	;
19162449Speter
19262449Spetercmd:		USER SP username CRLF
19362449Speter		= {
19462449Speter			user((char *) $3);
19562449Speter			free((char *) $3);
19662449Speter		}
19762449Speter	|	PASS SP password CRLF
19862449Speter		= {
19962449Speter			pass((char *) $3);
20062449Speter			free((char *) $3);
20162449Speter		}
20262449Speter        |   PORT check_login SP host_port CRLF
20362449Speter                = {
20462449Speter             usedefault = 0;
20562449Speter             if (pdata >= 0) {
20662449Speter                 (void) close(pdata);
20762449Speter                 pdata = -1;
20862449Speter             }
20962449Speter/* H* port fix, part B: admonish the twit.
21062449Speter   Also require login before PORT works */
21162449Speter            if ($2) {
21262449Speter              if ((cliport > 1023) && (data_dest.sin_addr.s_addr > 0)) {
21362449Speter                reply(200, "PORT command successful.");
21462449Speter              } else {
21562449Speter                syslog (LOG_WARNING, "refused %s from %s",
21662449Speter                       cbuf, remotehost);
21762449Speter                reply(500, "You've GOT to be joking.");
21862449Speter              }
21962449Speter            }
22062449Speter                }
22162449Speter/*	|	PASV CRLF
22262449Speter		= {
22362449Speter			passive();
22462449Speter		} */
22562449Speter    |   PASV check_login CRLF
22662449Speter        = {
22762449Speter/* Require login for PASV, too.  This actually fixes a bug -- telnet to an
22862449Speter   unfixed wu-ftpd and type PASV first off, and it crashes! */
22962449Speter            if ($2) {
23062449Speter                passive();
23162449Speter            }
23262449Speter        }
23362449Speter	|	TYPE SP type_code CRLF
23462449Speter		= {
23562449Speter			switch (cmd_type) {
23662449Speter
23762449Speter			case TYPE_A:
23862449Speter				if (cmd_form == FORM_N) {
23962449Speter					reply(200, "Type set to A.");
24062449Speter					type = cmd_type;
24162449Speter					form = cmd_form;
24262449Speter				} else
24362449Speter					reply(504, "Form must be N.");
24462449Speter				break;
24562449Speter
24662449Speter			case TYPE_E:
24762449Speter				reply(504, "Type E not implemented.");
24862449Speter				break;
24962449Speter
25062449Speter			case TYPE_I:
25162449Speter				reply(200, "Type set to I.");
25262449Speter				type = cmd_type;
25362449Speter				break;
25462449Speter
25562449Speter			case TYPE_L:
25662449Speter#if NBBY == 8
25762449Speter				if (cmd_bytesz == 8) {
25862449Speter					reply(200,
25962449Speter					    "Type set to L (byte size 8).");
26062449Speter					type = cmd_type;
26162449Speter				} else
26262449Speter					reply(504, "Byte size must be 8.");
26362449Speter#else /* NBBY == 8 */
26462449Speter				UNIMPLEMENTED for NBBY != 8
26562449Speter#endif /* NBBY == 8 */
26662449Speter			}
26762449Speter		}
26862449Speter	|	STRU SP struct_code CRLF
26962449Speter		= {
27062449Speter			switch ($3) {
27162449Speter
27262449Speter			case STRU_F:
27362449Speter				reply(200, "STRU F ok.");
27462449Speter				break;
27562449Speter
27662449Speter			default:
27762449Speter				reply(504, "Unimplemented STRU type.");
27862449Speter			}
27962449Speter		}
28062449Speter	|	MODE SP mode_code CRLF
28162449Speter		= {
28262449Speter			switch ($3) {
28362449Speter
28462449Speter			case MODE_S:
28562449Speter				reply(200, "MODE S ok.");
28662449Speter				break;
28762449Speter
28862449Speter			default:
28962449Speter				reply(502, "Unimplemented MODE type.");
29062449Speter			}
29162449Speter		}
29262449Speter	|	ALLO SP NUMBER CRLF
29362449Speter		= {
29462449Speter			reply(202, "ALLO command ignored.");
29562449Speter		}
29662449Speter	|	ALLO SP NUMBER SP R SP NUMBER CRLF
29762449Speter		= {
29862449Speter			reply(202, "ALLO command ignored.");
29962449Speter		}
30062449Speter	|	RETR check_login SP pathname CRLF
30162449Speter		= {
30262449Speter			if ($2 && $4)
30362449Speter				retrieve((char *) 0, (char *) $4);
30462449Speter			if ($4)
30562449Speter				free((char *) $4);
30662449Speter		}
30762449Speter	|	STOR check_login SP pathname CRLF
30862449Speter		= {
30962449Speter			if ($2 && $4)
31062449Speter				store((char *) $4, "w", 0);
31162449Speter			if ($4)
31262449Speter				free((char *) $4);
31362449Speter		}
31462449Speter	|	APPE check_login SP pathname CRLF
31562449Speter		= {
31662449Speter			if ($2 && $4)
31762449Speter				store((char *) $4, "a", 0);
31862449Speter			if ($4)
31962449Speter				free((char *) $4);
32062449Speter		}
32162449Speter	|	NLST check_login CRLF
32262449Speter		= {
32362449Speter			if ($2)
32462449Speter				send_file_list(".");
32562449Speter		}
32662449Speter	|	NLST check_login SP STRING CRLF
32762449Speter		= {
32862449Speter			if ($2 && $4)
32962449Speter				send_file_list((char *) $4);
33062449Speter			if ($4)
33162449Speter				free((char *) $4);
33262449Speter		}
33362449Speter	|	LIST check_login CRLF
33462449Speter		= {
33562449Speter			if ($2)
33662449Speter				retrieve(LS_COMMAND, "");
33762449Speter		}
33862449Speter	|	LIST check_login SP pathname CRLF
33962449Speter		= {
34062449Speter			if ($2 && $4)
34162449Speter                                {
34262449Speter                                char buffer[sizeof(LS_COMMAND)+3];
34362449Speter                                strcpy(buffer, LS_COMMAND);
34462449Speter                                strcat(buffer, " %s");
34562449Speter				retrieve(buffer, (char *) $4);
34662449Speter                                }
34762449Speter			if ($4)
34862449Speter				free((char *) $4);
34962449Speter		}
35062449Speter	|	STAT check_login SP pathname CRLF
35162449Speter		= {
35262449Speter			if ($2 && $4)
35362449Speter				statfilecmd((char *) $4);
35462449Speter			if ($4)
35562449Speter				free((char *) $4);
35662449Speter		}
35762449Speter	|	STAT CRLF
35862449Speter		= {
35962449Speter			statcmd();
36062449Speter		}
36162449Speter	|	DELE check_login SP pathname CRLF
36262449Speter		= {
36362449Speter			if ($2 && $4)
36462449Speter				delete((char *) $4);
36562449Speter			if ($4)
36662449Speter				free((char *) $4);
36762449Speter		}
36862449Speter	|	RNTO SP pathname CRLF
36962449Speter		= {
37062449Speter			if (fromname) {
37162449Speter				renamecmd(fromname, (char *) $3);
37262449Speter				free(fromname);
37362449Speter				fromname = (char *) 0;
37462449Speter			} else {
37562449Speter				reply(503, "Bad sequence of commands.");
37662449Speter			}
37762449Speter			free((char *) $3);
37862449Speter		}
37962449Speter	|	ABOR CRLF
38062449Speter		= {
38162449Speter			reply(225, "ABOR command successful.");
38262449Speter		}
38362449Speter	|	CWD check_login CRLF
38462449Speter		= {
38562449Speter			if ($2)
38662449Speter				cwd(pw->pw_dir);
38762449Speter		}
38862449Speter	|	CWD check_login SP pathname CRLF
38962449Speter		= {
39062449Speter			if ($2 && $4)
39162449Speter				cwd((char *) $4);
39262449Speter			if ($4)
39362449Speter				free((char *) $4);
39462449Speter		}
39562449Speter	|	HELP CRLF
39662449Speter		= {
39762449Speter			help(cmdtab, (char *) 0);
39862449Speter		}
39962449Speter	|	HELP SP STRING CRLF
40062449Speter		= {
40162449Speter			register char *cp = (char *)$3;
40262449Speter
40362449Speter			if (strncasecmp(cp, "SITE", 4) == 0) {
404166124Srafan				cp = (char *)$3 + 4;
40562449Speter				if (*cp == ' ')
40662449Speter					cp++;
40762449Speter				if (*cp)
40862449Speter					help(sitetab, cp);
40962449Speter				else
41062449Speter					help(sitetab, (char *) 0);
41162449Speter			} else
41262449Speter				help(cmdtab, (char *) $3);
41362449Speter		}
41462449Speter	|	NOOP CRLF
41562449Speter		= {
41662449Speter			reply(200, "NOOP command successful.");
41762449Speter		}
41862449Speter	|	MKD check_login SP pathname CRLF
41962449Speter		= {
42062449Speter			if ($2 && $4)
42162449Speter				makedir((char *) $4);
42262449Speter			if ($4)
42362449Speter				free((char *) $4);
42462449Speter		}
42562449Speter	|	RMD check_login SP pathname CRLF
42662449Speter		= {
42762449Speter			if ($2 && $4)
42862449Speter				removedir((char *) $4);
42962449Speter			if ($4)
43062449Speter				free((char *) $4);
43162449Speter		}
43262449Speter	|	PWD check_login CRLF
43362449Speter		= {
43462449Speter			if ($2)
43562449Speter				pwd();
43662449Speter		}
43762449Speter	|	CDUP check_login CRLF
43862449Speter		= {
43962449Speter			if ($2)
44062449Speter				cwd("..");
44162449Speter		}
44262449Speter	|	SITE SP HELP CRLF
44362449Speter		= {
44462449Speter			help(sitetab, (char *) 0);
44562449Speter		}
44662449Speter	|	SITE SP HELP SP STRING CRLF
44762449Speter		= {
44862449Speter			help(sitetab, (char *) $5);
44962449Speter		}
45062449Speter	|	SITE SP UMASK check_login CRLF
45162449Speter		= {
45262449Speter			int oldmask;
45362449Speter
45462449Speter			if ($4) {
45562449Speter				oldmask = umask(0);
45662449Speter				(void) umask(oldmask);
45762449Speter				reply(200, "Current UMASK is %03o", oldmask);
45862449Speter			}
45962449Speter		}
46062449Speter	|	SITE SP UMASK check_login SP octal_number CRLF
46162449Speter		= {
46262449Speter			int oldmask;
46362449Speter
46462449Speter			if ($4) {
46562449Speter				if (($6 == -1) || ($6 > 0777)) {
46662449Speter					reply(501, "Bad UMASK value");
46762449Speter				} else {
46862449Speter					oldmask = umask($6);
46962449Speter					reply(200,
47062449Speter					    "UMASK set to %03o (was %03o)",
47162449Speter					    $6, oldmask);
47262449Speter				}
47362449Speter			}
47462449Speter		}
47562449Speter	|	SITE SP CHMOD check_login SP octal_number SP pathname CRLF
47662449Speter		= {
47762449Speter			if ($4 && $8) {
47862449Speter				if ($6 > 0777)
47962449Speter					reply(501,
48062449Speter				"CHMOD: Mode value must be between 0 and 0777");
48162449Speter				else if (chmod((char *) $8, $6) < 0)
48262449Speter					perror_reply(550, (char *) $8);
48362449Speter				else
48462449Speter					reply(200, "CHMOD command successful.");
48562449Speter			}
48662449Speter			if ($8)
48762449Speter				free((char *) $8);
48862449Speter		}
48962449Speter	|	SITE SP IDLE CRLF
49062449Speter		= {
49162449Speter			reply(200,
49262449Speter			    "Current IDLE time limit is %d seconds; max %d",
49362449Speter				timeout, maxtimeout);
49462449Speter		}
49562449Speter	|	SITE SP IDLE SP NUMBER CRLF
49662449Speter		= {
49762449Speter			if ($5 < 30 || $5 > maxtimeout) {
49862449Speter				reply(501,
49962449Speter			"Maximum IDLE time must be between 30 and %d seconds",
50062449Speter				    maxtimeout);
50162449Speter			} else {
50262449Speter				timeout = $5;
50362449Speter				(void) alarm((unsigned) timeout);
50462449Speter				reply(200,
50562449Speter				    "Maximum IDLE time set to %d seconds",
50662449Speter				    timeout);
50762449Speter			}
50862449Speter		}
50962449Speter	|	STOU check_login SP pathname CRLF
51062449Speter		= {
51162449Speter			if ($2 && $4)
51262449Speter				store((char *) $4, "w", 1);
51362449Speter			if ($4)
51462449Speter				free((char *) $4);
51562449Speter		}
51662449Speter	|	SYST CRLF
51762449Speter		= {
51862449Speter#ifdef unix
51962449Speter#ifdef BSD
52062449Speter			reply(215, "UNIX Type: L%d Version: BSD-%d",
52162449Speter				NBBY, BSD);
52262449Speter#else /* BSD */
52362449Speter			reply(215, "UNIX Type: L%d", NBBY);
52462449Speter#endif /* BSD */
52562449Speter#else /* unix */
52662449Speter			reply(215, "UNKNOWN Type: L%d", NBBY);
52762449Speter#endif /* unix */
52862449Speter		}
52962449Speter
53062449Speter		/*
53162449Speter		 * SIZE is not in RFC959, but Postel has blessed it and
53262449Speter		 * it will be in the updated RFC.
53362449Speter		 *
53462449Speter		 * Return size of file in a format suitable for
53562449Speter		 * using with RESTART (we just count bytes).
53662449Speter		 */
53762449Speter	|	SIZE check_login SP pathname CRLF
53862449Speter		= {
53962449Speter			if ($2 && $4)
54062449Speter				sizecmd((char *) $4);
54162449Speter			if ($4)
54262449Speter				free((char *) $4);
54362449Speter		}
54462449Speter
54562449Speter		/*
54662449Speter		 * MDTM is not in RFC959, but Postel has blessed it and
54762449Speter		 * it will be in the updated RFC.
54862449Speter		 *
54962449Speter		 * Return modification time of file as an ISO 3307
55062449Speter		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
55162449Speter		 * where xxx is the fractional second (of any precision,
55262449Speter		 * not necessarily 3 digits)
55362449Speter		 */
55462449Speter	|	MDTM check_login SP pathname CRLF
55562449Speter		= {
55662449Speter			if ($2 && $4) {
55762449Speter				struct stat stbuf;
55862449Speter				if (stat((char *) $4, &stbuf) < 0)
55962449Speter					perror_reply(550, (char *) $4);
56062449Speter				else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
56162449Speter					reply(550, "%s: not a plain file.",
56262449Speter						(char *) $4);
56362449Speter				} else {
56462449Speter					register struct tm *t;
56562449Speter					struct tm *gmtime();
56662449Speter					t = gmtime(&stbuf.st_mtime);
56762449Speter					reply(213,
56862449Speter					    "19%02d%02d%02d%02d%02d%02d",
56962449Speter					    t->tm_year, t->tm_mon+1, t->tm_mday,
57062449Speter					    t->tm_hour, t->tm_min, t->tm_sec);
57162449Speter				}
57262449Speter			}
57362449Speter			if ($4)
57462449Speter				free((char *) $4);
57562449Speter		}
57662449Speter	|	QUIT CRLF
57762449Speter		= {
57862449Speter			reply(221, "Goodbye.");
57962449Speter			dologout(0);
58062449Speter		}
58162449Speter	|	error CRLF
58262449Speter		= {
58362449Speter			yyerrok;
58462449Speter		}
585166124Srafan	;
58662449Speterrcmd:		RNFR check_login SP pathname CRLF
58762449Speter		= {
58862449Speter			char *renamefrom();
58962449Speter
59062449Speter			restart_point = (off_t) 0;
59162449Speter			if ($2 && $4) {
59262449Speter				fromname = renamefrom((char *) $4);
59362449Speter				if (fromname == (char *) 0 && $4) {
59462449Speter					free((char *) $4);
59562449Speter				}
59662449Speter			}
59762449Speter		}
59862449Speter	|	REST SP byte_size CRLF
59962449Speter		= {
60062449Speter			long atol();
60162449Speter
60262449Speter			fromname = (char *) 0;
60362449Speter			restart_point = $3;
60462449Speter			reply(350, "Restarting at %ld. %s", restart_point,
60562449Speter			    "Send STORE or RETRIEVE to initiate transfer.");
60662449Speter		}
60762449Speter	;
60862449Speter
60962449Speterusername:	STRING
61062449Speter	;
61162449Speter
61262449Speterpassword:	/* empty */
61362449Speter		= {
61462449Speter			*(char **)&($$) = (char *)calloc(1, sizeof(char));
61562449Speter		}
61662449Speter	|	STRING
61762449Speter	;
61862449Speter
61962449Speterbyte_size:	NUMBER
62062449Speter	;
62162449Speter
62262449Speterhost_port:	NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
62362449Speter		NUMBER COMMA NUMBER
62462449Speter		= {
62562449Speter			register char *a, *p;
62662449Speter
62762449Speter			a = (char *)&data_dest.sin_addr;
62862449Speter			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
62962449Speter
63062449Speter/* H* port fix, part A-1: Check the args against the client addr */
63162449Speter            p = (char *)&his_addr.sin_addr;
63262449Speter             if (memcmp (a, p, sizeof (data_dest.sin_addr)))
63362449Speter                 memset (a, 0, sizeof (data_dest.sin_addr));     /* XXX */
63462449Speter
63562449Speter			p = (char *)&data_dest.sin_port;
63662449Speter
63762449Speter/* H* port fix, part A-2: only allow client ports in "user space" */
63862449Speter            p[0] = 0; p[1] = 0;
63962449Speter            cliport = ($9 << 8) + $11;
64062449Speter            if (cliport > 1023) {
64162449Speter                 p[0] = $9; p[1] = $11;
64262449Speter            }
64362449Speter
64462449Speter			p[0] = $9; p[1] = $11;
64562449Speter			data_dest.sin_family = AF_INET;
64662449Speter		}
64762449Speter	;
64862449Speter
64962449Speterform_code:	N
65062449Speter	= {
65162449Speter		$$ = FORM_N;
65262449Speter	}
65362449Speter	|	T
65462449Speter	= {
65562449Speter		$$ = FORM_T;
65662449Speter	}
65762449Speter	|	C
65862449Speter	= {
65962449Speter		$$ = FORM_C;
66062449Speter	}
66162449Speter	;
66262449Speter
66362449Spetertype_code:	A
66462449Speter	= {
66562449Speter		cmd_type = TYPE_A;
66662449Speter		cmd_form = FORM_N;
66762449Speter	}
66862449Speter	|	A SP form_code
66962449Speter	= {
67062449Speter		cmd_type = TYPE_A;
67162449Speter		cmd_form = $3;
67262449Speter	}
67362449Speter	|	E
67462449Speter	= {
67562449Speter		cmd_type = TYPE_E;
67662449Speter		cmd_form = FORM_N;
67762449Speter	}
67862449Speter	|	E SP form_code
67962449Speter	= {
68062449Speter		cmd_type = TYPE_E;
68162449Speter		cmd_form = $3;
68262449Speter	}
68362449Speter	|	I
68462449Speter	= {
68562449Speter		cmd_type = TYPE_I;
68662449Speter	}
68762449Speter	|	L
68862449Speter	= {
68962449Speter		cmd_type = TYPE_L;
69062449Speter		cmd_bytesz = NBBY;
69162449Speter	}
69262449Speter	|	L SP byte_size
69362449Speter	= {
69462449Speter		cmd_type = TYPE_L;
69562449Speter		cmd_bytesz = $3;
69662449Speter	}
69762449Speter	/* this is for a bug in the BBN ftp */
69862449Speter	|	L byte_size
69962449Speter	= {
70062449Speter		cmd_type = TYPE_L;
70162449Speter		cmd_bytesz = $2;
70262449Speter	}
70362449Speter	;
70462449Speter
70562449Speterstruct_code:	F
70662449Speter	= {
70762449Speter		$$ = STRU_F;
70862449Speter	}
70962449Speter	|	R
71062449Speter	= {
71162449Speter		$$ = STRU_R;
71262449Speter	}
71362449Speter	|	P
71462449Speter	= {
71562449Speter		$$ = STRU_P;
71662449Speter	}
71762449Speter	;
71862449Speter
71962449Spetermode_code:	S
72062449Speter	= {
72162449Speter		$$ = MODE_S;
72262449Speter	}
72362449Speter	|	B
72462449Speter	= {
72562449Speter		$$ = MODE_B;
72662449Speter	}
72762449Speter	|	C
72862449Speter	= {
72962449Speter		$$ = MODE_C;
73062449Speter	}
73162449Speter	;
73262449Speter
73362449Speterpathname:	pathstring
73462449Speter	= {
73562449Speter		/*
73662449Speter		 * Problem: this production is used for all pathname
73762449Speter		 * processing, but only gives a 550 error reply.
73862449Speter		 * This is a valid reply in some cases but not in others.
73962449Speter		 */
74062449Speter		if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) {
74162449Speter			*(char **)&($$) = *ftpglob((char *) $1);
74262449Speter			if (globerr != NULL) {
74362449Speter				reply(550, globerr);
74462449Speter/*				$$ = NULL; */
74562449Speter				$$ = 0;
74662449Speter			}
74762449Speter			free((char *) $1);
74862449Speter		} else
74962449Speter			$$ = $1;
75062449Speter	}
75162449Speter	;
75262449Speter
75362449Speterpathstring:	STRING
75462449Speter	;
75562449Speter
75662449Speteroctal_number:	NUMBER
75762449Speter	= {
75862449Speter		register int ret, dec, multby, digit;
75962449Speter
76062449Speter		/*
76162449Speter		 * Convert a number that was read as decimal number
76262449Speter		 * to what it would be if it had been read as octal.
76362449Speter		 */
76462449Speter		dec = $1;
76562449Speter		multby = 1;
76662449Speter		ret = 0;
76762449Speter		while (dec) {
76862449Speter			digit = dec%10;
76962449Speter			if (digit > 7) {
77062449Speter				ret = -1;
77162449Speter				break;
77262449Speter			}
77362449Speter			ret += digit * multby;
77462449Speter			multby *= 8;
77562449Speter			dec /= 10;
77662449Speter		}
77762449Speter		$$ = ret;
77862449Speter	}
77962449Speter	;
78062449Speter
78162449Spetercheck_login:	/* empty */
78262449Speter	= {
78362449Speter		if (logged_in)
78462449Speter			$$ = 1;
78562449Speter		else {
78662449Speter			reply(530, "Please login with USER and PASS.");
78762449Speter			$$ = 0;
78862449Speter		}
78962449Speter	}
79062449Speter	;
79162449Speter
79262449Speter%%
79362449Speter
79462449Speterextern jmp_buf errcatch;
79562449Speter
79662449Speter#define	CMD	0	/* beginning of command */
79762449Speter#define	ARGS	1	/* expect miscellaneous arguments */
79862449Speter#define	STR1	2	/* expect SP followed by STRING */
79962449Speter#define	STR2	3	/* expect STRING */
80062449Speter#define	OSTR	4	/* optional SP then STRING */
80162449Speter#define	ZSTR1	5	/* SP then optional STRING */
80262449Speter#define	ZSTR2	6	/* optional STRING after SP */
80362449Speter#define	SITECMD	7	/* SITE command */
80462449Speter#define	NSTR	8	/* Number followed by a string */
80562449Speter
80662449Speterstruct tab cmdtab[] = {		/* In order defined in RFC 765 */
80762449Speter	{ "USER", USER, STR1, 1,	"<sp> username" },
80862449Speter	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
80962449Speter	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
81062449Speter	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
81162449Speter	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
81262449Speter	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
81362449Speter	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
81462449Speter	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
81562449Speter	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
81662449Speter	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
81762449Speter	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
81862449Speter	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
81962449Speter	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
82062449Speter	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
82162449Speter	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
82262449Speter	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
82362449Speter	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
82462449Speter	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
82562449Speter	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
82662449Speter	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
82762449Speter	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
82862449Speter	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
82962449Speter	{ "REST", REST, ARGS, 1,	"(restart command)" },
83062449Speter	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
83162449Speter	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
83262449Speter	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
83362449Speter	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
83462449Speter	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
83562449Speter	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
83662449Speter	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
83762449Speter	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
83862449Speter	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
83962449Speter	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
84062449Speter	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
84162449Speter	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
84262449Speter	{ "NOOP", NOOP, ARGS, 1,	"" },
84362449Speter	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
84462449Speter	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
84562449Speter	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
84662449Speter	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
84762449Speter	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
84862449Speter	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
84962449Speter	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
85062449Speter	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
85162449Speter	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
85262449Speter	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
85362449Speter	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
85462449Speter	{ NULL,   0,    0,    0,	0 }
85562449Speter};
85662449Speter
85762449Speterstruct tab sitetab[] = {
85862449Speter	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
85962449Speter	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
86062449Speter	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
86162449Speter	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
86262449Speter	{ NULL,   0,    0,    0,	0 }
86362449Speter};
86462449Speter
86562449Speterstruct tab *lookup FUNCTION((p, cmd), register struct tab *p AND char *cmd)
86662449Speter{
86762449Speter
86862449Speter	for (; p->name != NULL; p++)
86962449Speter		if (strcmp(cmd, p->name) == 0)
87062449Speter			return (p);
87162449Speter	return (0);
87262449Speter}
87362449Speter
87462449Speter#include <arpa/telnet.h>
87562449Speter
87662449Speter/*
87762449Speter * getline - a hacked up version of fgets to ignore TELNET escape codes.
87862449Speter */
87962449Speterchar *getline FUNCTION((s, n, iop), char *s AND int n AND FILE *iop)
88062449Speter{
88162449Speter	register c;
88262449Speter	register char *cs;
88362449Speter
88462449Speter	cs = s;
88562449Speter/* tmpline may contain saved command from urgent mode interruption */
88662449Speter	for (c = 0; *(tmpline + c) && --n > 0; ++c) {
88762449Speter		*cs++ = *(tmpline + c);
88862449Speter		if (*(tmpline + c) == '\n') {
88962449Speter			*cs++ = '\0';
89062449Speter			if (debug)
89162449Speter				syslog(LOG_DEBUG, "command: %s", s);
89262449Speter			*tmpline = '\0';
89362449Speter			return(s);
89462449Speter		}
89562449Speter		if (c == 0)
89662449Speter			*tmpline = '\0';
89762449Speter	}
89862449Speter	while ((c = getc(iop)) != EOF) {
89962449Speter		c &= 0377;
90062449Speter		if (c == IAC) {
90162449Speter		    if ((c = getc(iop)) != EOF) {
90262449Speter			c &= 0377;
90362449Speter			switch (c) {
90462449Speter			case WILL:
90562449Speter			case WONT:
90662449Speter				c = getc(iop);
90762449Speter				printf("%c%c%c", IAC, DONT, 0377&c);
90862449Speter				(void) fflush(stdout);
90962449Speter				continue;
91062449Speter			case DO:
91162449Speter			case DONT:
91262449Speter				c = getc(iop);
91362449Speter				printf("%c%c%c", IAC, WONT, 0377&c);
91462449Speter				(void) fflush(stdout);
915				continue;
916			case IAC:
917				break;
918			default:
919				continue;	/* ignore command */
920			}
921		    }
922		}
923		*cs++ = c;
924		if (--n <= 0 || c == '\n')
925			break;
926	}
927	if (c == EOF && cs == s)
928		return (NULL);
929	*cs++ = '\0';
930	if (debug)
931		syslog(LOG_DEBUG, "command: %s", s);
932	return (s);
933}
934
935static VOIDRET toolong FUNCTION((input), int input)
936{
937	time_t now;
938
939	reply(421, "Timeout (%d seconds): closing control connection.", timeout);
940	(void) time(&now);
941        syslog(LOG_INFO, "User %s timed out after %d seconds at %s",
942          (pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
943	dologout(1);
944}
945
946int yylex FUNCTION_NOARGS
947{
948	static int cpos, state;
949	register char *cp, *cp2;
950	register struct tab *p;
951	int n;
952	char c, *copy();
953
954	for (;;) {
955		switch (state) {
956
957		case CMD:
958			(void) signal(SIGALRM, toolong);
959			(void) alarm((unsigned) timeout);
960			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
961				reply(221, "You could at least say goodbye.");
962				dologout(0);
963			}
964			(void) alarm(0);
965#ifdef SETPROCTITLE
966			if (strncasecmp(cbuf, "PASS", 4) != NULL)
967				setproctitle("%s: %s", proctitle, cbuf);
968#endif /* SETPROCTITLE */
969			if ((cp = strchr(cbuf, '\r'))) {
970				*cp++ = '\n';
971				*cp = '\0';
972			}
973			if ((cp = strpbrk(cbuf, " \n")))
974				cpos = cp - cbuf;
975			if (cpos == 0)
976				cpos = 4;
977			c = cbuf[cpos];
978			cbuf[cpos] = '\0';
979			upper(cbuf);
980			p = lookup(cmdtab, cbuf);
981			cbuf[cpos] = c;
982			if (p != 0) {
983				if (p->implemented == 0) {
984					nack(p->name);
985					longjmp(errcatch,0);
986					/* NOTREACHED */
987				}
988				state = p->state;
989				*(char **)&yylval = p->name;
990				return (p->token);
991			}
992			break;
993
994		case SITECMD:
995			if (cbuf[cpos] == ' ') {
996				cpos++;
997				return (SP);
998			}
999			cp = &cbuf[cpos];
1000			if ((cp2 = strpbrk(cp, " \n")))
1001				cpos = cp2 - cbuf;
1002			c = cbuf[cpos];
1003			cbuf[cpos] = '\0';
1004			upper(cp);
1005			p = lookup(sitetab, cp);
1006			cbuf[cpos] = c;
1007			if (p != 0) {
1008				if (p->implemented == 0) {
1009					state = CMD;
1010					nack(p->name);
1011					longjmp(errcatch,0);
1012					/* NOTREACHED */
1013				}
1014				state = p->state;
1015				*(char **)&yylval = p->name;
1016				return (p->token);
1017			}
1018			state = CMD;
1019			break;
1020
1021		case OSTR:
1022			if (cbuf[cpos] == '\n') {
1023				state = CMD;
1024				return (CRLF);
1025			}
1026			/* FALLTHROUGH */
1027
1028		case STR1:
1029		case ZSTR1:
1030		dostr1:
1031			if (cbuf[cpos] == ' ') {
1032				cpos++;
1033				state = state == OSTR ? STR2 : ++state;
1034				return (SP);
1035			}
1036			break;
1037
1038		case ZSTR2:
1039			if (cbuf[cpos] == '\n') {
1040				state = CMD;
1041				return (CRLF);
1042			}
1043			/* FALLTHROUGH */
1044
1045		case STR2:
1046			cp = &cbuf[cpos];
1047			n = strlen(cp);
1048			cpos += n - 1;
1049			/*
1050			 * Make sure the string is nonempty and \n terminated.
1051			 */
1052			if (n > 1 && cbuf[cpos] == '\n') {
1053				cbuf[cpos] = '\0';
1054				*(char **)&yylval = copy(cp);
1055				cbuf[cpos] = '\n';
1056				state = ARGS;
1057				return (STRING);
1058			}
1059			break;
1060
1061		case NSTR:
1062			if (cbuf[cpos] == ' ') {
1063				cpos++;
1064				return (SP);
1065			}
1066			if (isdigit(cbuf[cpos])) {
1067				cp = &cbuf[cpos];
1068				while (isdigit(cbuf[++cpos]))
1069					;
1070				c = cbuf[cpos];
1071				cbuf[cpos] = '\0';
1072				yylval = atoi(cp);
1073				cbuf[cpos] = c;
1074				state = STR1;
1075				return (NUMBER);
1076			}
1077			state = STR1;
1078			goto dostr1;
1079
1080		case ARGS:
1081			if (isdigit(cbuf[cpos])) {
1082				cp = &cbuf[cpos];
1083				while (isdigit(cbuf[++cpos]))
1084					;
1085				c = cbuf[cpos];
1086				cbuf[cpos] = '\0';
1087				yylval = atoi(cp);
1088				cbuf[cpos] = c;
1089				return (NUMBER);
1090			}
1091			switch (cbuf[cpos++]) {
1092
1093			case '\n':
1094				state = CMD;
1095				return (CRLF);
1096
1097			case ' ':
1098				return (SP);
1099
1100			case ',':
1101				return (COMMA);
1102
1103			case 'A':
1104			case 'a':
1105				return (A);
1106
1107			case 'B':
1108			case 'b':
1109				return (B);
1110
1111			case 'C':
1112			case 'c':
1113				return (C);
1114
1115			case 'E':
1116			case 'e':
1117				return (E);
1118
1119			case 'F':
1120			case 'f':
1121				return (F);
1122
1123			case 'I':
1124			case 'i':
1125				return (I);
1126
1127			case 'L':
1128			case 'l':
1129				return (L);
1130
1131			case 'N':
1132			case 'n':
1133				return (N);
1134
1135			case 'P':
1136			case 'p':
1137				return (P);
1138
1139			case 'R':
1140			case 'r':
1141				return (R);
1142
1143			case 'S':
1144			case 's':
1145				return (S);
1146
1147			case 'T':
1148			case 't':
1149				return (T);
1150
1151			}
1152			break;
1153
1154		default:
1155			opiefatal("Unknown state in scanner.");
1156		}
1157		yyerror((char *) 0);
1158		state = CMD;
1159		longjmp(errcatch,0);
1160	}
1161}
1162
1163VOIDRET upper FUNCTION((s), char *s)
1164{
1165	while (*s != '\0') {
1166		if (islower(*s))
1167			*s = toupper(*s);
1168		s++;
1169	}
1170}
1171
1172char *copy FUNCTION((s), char *s)
1173{
1174	char *p;
1175
1176	p = malloc((unsigned) strlen(s) + 1);
1177	if (p == NULL)
1178		opiefatal("Ran out of memory.");
1179	(void) strcpy(p, s);
1180	return (p);
1181}
1182
1183VOIDRET help FUNCTION((ctab, s), struct tab *ctab AND char *s)
1184{
1185	register struct tab *c;
1186	register int width, NCMDS;
1187	char *type;
1188
1189	if (ctab == sitetab)
1190		type = "SITE ";
1191	else
1192		type = "";
1193	width = 0, NCMDS = 0;
1194	for (c = ctab; c->name != NULL; c++) {
1195		int len = strlen(c->name);
1196
1197		if (len > width)
1198			width = len;
1199		NCMDS++;
1200	}
1201	width = (width + 8) &~ 7;
1202	if (s == 0) {
1203		register int i, j, w;
1204		int columns, lines;
1205
1206		lreply(214, "The following %scommands are recognized %s.",
1207		    type, "(* =>'s unimplemented)");
1208		columns = 76 / width;
1209		if (columns == 0)
1210			columns = 1;
1211		lines = (NCMDS + columns - 1) / columns;
1212		for (i = 0; i < lines; i++) {
1213			printf("   ");
1214			for (j = 0; j < columns; j++) {
1215				c = ctab + j * lines + i;
1216				printf("%s%c", c->name,
1217					c->implemented ? ' ' : '*');
1218				if (c + lines >= &ctab[NCMDS])
1219					break;
1220				w = strlen(c->name) + 1;
1221				while (w < width) {
1222					putchar(' ');
1223					w++;
1224				}
1225			}
1226			printf("\r\n");
1227		}
1228		(void) fflush(stdout);
1229		return;
1230	}
1231	upper(s);
1232	c = lookup(ctab, s);
1233	if (c == (struct tab *)0) {
1234		reply(502, "Unknown command %s.", s);
1235		return;
1236	}
1237	if (c->implemented)
1238		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1239	else
1240		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1241		    c->name, c->help);
1242}
1243
1244VOIDRET sizecmd FUNCTION((filename), char *filename)
1245{
1246	switch (type) {
1247	case TYPE_L:
1248	case TYPE_I: {
1249		struct stat stbuf;
1250		if (stat(filename, &stbuf) < 0 ||
1251		    (stbuf.st_mode&S_IFMT) != S_IFREG)
1252			reply(550, "%s: not a plain file.", filename);
1253		else
1254			reply(213, "%lu", stbuf.st_size);
1255		break;}
1256	case TYPE_A: {
1257		FILE *fin;
1258		register int c;
1259		register long count;
1260		struct stat stbuf;
1261		fin = fopen(filename, "r");
1262		if (fin == NULL) {
1263			perror_reply(550, filename);
1264			return;
1265		}
1266		if (fstat(fileno(fin), &stbuf) < 0 ||
1267		    (stbuf.st_mode&S_IFMT) != S_IFREG) {
1268			reply(550, "%s: not a plain file.", filename);
1269			(void) fclose(fin);
1270			return;
1271		}
1272
1273		count = 0;
1274		while((c=getc(fin)) != EOF) {
1275			if (c == '\n')	/* will get expanded to \r\n */
1276				count++;
1277			count++;
1278		}
1279		(void) fclose(fin);
1280
1281		reply(213, "%ld", count);
1282		break;}
1283	default:
1284		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1285	}
1286}
1287