Home | History | Annotate | Line # | Download | only in ftpd
ftpcmd.y revision 1.24
      1  1.24     lukem /*	$NetBSD: ftpcmd.y,v 1.24 1998/12/28 04:54:01 lukem Exp $	*/
      2   1.5       cgd 
      3   1.1       cgd /*
      4   1.4   deraadt  * Copyright (c) 1985, 1988, 1993, 1994
      5   1.4   deraadt  *	The Regents of the University of California.  All rights reserved.
      6   1.1       cgd  *
      7   1.1       cgd  * Redistribution and use in source and binary forms, with or without
      8   1.1       cgd  * modification, are permitted provided that the following conditions
      9   1.1       cgd  * are met:
     10   1.1       cgd  * 1. Redistributions of source code must retain the above copyright
     11   1.1       cgd  *    notice, this list of conditions and the following disclaimer.
     12   1.1       cgd  * 2. Redistributions in binary form must reproduce the above copyright
     13   1.1       cgd  *    notice, this list of conditions and the following disclaimer in the
     14   1.1       cgd  *    documentation and/or other materials provided with the distribution.
     15   1.1       cgd  * 3. All advertising materials mentioning features or use of this software
     16   1.1       cgd  *    must display the following acknowledgement:
     17   1.1       cgd  *	This product includes software developed by the University of
     18   1.1       cgd  *	California, Berkeley and its contributors.
     19   1.1       cgd  * 4. Neither the name of the University nor the names of its contributors
     20   1.1       cgd  *    may be used to endorse or promote products derived from this software
     21   1.1       cgd  *    without specific prior written permission.
     22   1.1       cgd  *
     23   1.1       cgd  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     24   1.1       cgd  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     25   1.1       cgd  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     26   1.1       cgd  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     27   1.1       cgd  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     28   1.1       cgd  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     29   1.1       cgd  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     30   1.1       cgd  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     31   1.1       cgd  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     32   1.1       cgd  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     33   1.1       cgd  * SUCH DAMAGE.
     34   1.1       cgd  *
     35   1.4   deraadt  *	@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94
     36   1.1       cgd  */
     37   1.1       cgd 
     38   1.1       cgd /*
     39   1.1       cgd  * Grammar for FTP commands.
     40   1.1       cgd  * See RFC 959.
     41   1.1       cgd  */
     42   1.1       cgd 
     43   1.1       cgd %{
     44  1.13  christos #include <sys/cdefs.h>
     45   1.1       cgd 
     46   1.1       cgd #ifndef lint
     47   1.5       cgd #if 0
     48   1.4   deraadt static char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94";
     49   1.5       cgd #else
     50  1.24     lukem __RCSID("$NetBSD: ftpcmd.y,v 1.24 1998/12/28 04:54:01 lukem Exp $");
     51   1.5       cgd #endif
     52   1.1       cgd #endif /* not lint */
     53   1.1       cgd 
     54   1.1       cgd #include <sys/param.h>
     55   1.1       cgd #include <sys/socket.h>
     56   1.1       cgd #include <sys/stat.h>
     57   1.4   deraadt 
     58   1.1       cgd #include <netinet/in.h>
     59   1.1       cgd #include <arpa/ftp.h>
     60  1.15       mrg #include <arpa/inet.h>
     61   1.4   deraadt 
     62   1.4   deraadt #include <ctype.h>
     63   1.4   deraadt #include <errno.h>
     64   1.4   deraadt #include <glob.h>
     65   1.4   deraadt #include <pwd.h>
     66   1.4   deraadt #include <setjmp.h>
     67   1.1       cgd #include <signal.h>
     68   1.4   deraadt #include <stdio.h>
     69   1.4   deraadt #include <stdlib.h>
     70   1.4   deraadt #include <string.h>
     71   1.1       cgd #include <syslog.h>
     72   1.1       cgd #include <time.h>
     73  1.18     lukem #include <tzfile.h>
     74   1.1       cgd #include <unistd.h>
     75   1.4   deraadt 
     76   1.4   deraadt #include "extern.h"
     77   1.1       cgd 
     78   1.1       cgd off_t	restart_point;
     79   1.1       cgd 
     80   1.1       cgd static	int cmd_type;
     81   1.1       cgd static	int cmd_form;
     82   1.1       cgd static	int cmd_bytesz;
     83   1.1       cgd char	cbuf[512];
     84   1.1       cgd char	*fromname;
     85  1.22     lukem int	hasyyerrored;
     86   1.1       cgd 
     87  1.24     lukem extern	jmp_buf		errcatch;
     88  1.24     lukem 
     89   1.1       cgd %}
     90   1.1       cgd 
     91   1.4   deraadt %union {
     92   1.4   deraadt 	int	i;
     93   1.4   deraadt 	char   *s;
     94   1.4   deraadt }
     95   1.4   deraadt 
     96   1.1       cgd %token
     97   1.1       cgd 	A	B	C	E	F	I
     98   1.1       cgd 	L	N	P	R	S	T
     99   1.1       cgd 
    100   1.4   deraadt 	SP	CRLF	COMMA
    101   1.1       cgd 
    102  1.23     lukem 	USER	PASS	ACCT	CWD	CDUP	SMNT
    103  1.23     lukem 	QUIT	REIN	PORT	PASV	TYPE	STRU
    104  1.23     lukem 	MODE	RETR	STOR	STOU	APPE	ALLO
    105  1.23     lukem 	REST	RNFR	RNTO	ABOR	DELE	RMD
    106  1.23     lukem 	MKD	PWD	LIST	NLST	SITE	SYST
    107  1.23     lukem 	STAT	HELP	NOOP
    108  1.23     lukem 
    109  1.23     lukem 	FEAT	OPTS
    110  1.21     lukem 
    111  1.21     lukem 	SIZE	MDTM
    112   1.1       cgd 
    113  1.23     lukem 	MAIL	MLFL	MRCP	MRSQ	MSAM	MSND
    114  1.23     lukem 	MSOM
    115  1.23     lukem 
    116   1.1       cgd 	UMASK	IDLE	CHMOD
    117   1.1       cgd 
    118   1.1       cgd 	LEXERR
    119   1.1       cgd 
    120   1.4   deraadt %token	<s> STRING
    121   1.4   deraadt %token	<i> NUMBER
    122   1.4   deraadt 
    123  1.12     lukem %type	<i> check_login check_modify octal_number byte_size
    124   1.4   deraadt %type	<i> struct_code mode_code type_code form_code
    125   1.4   deraadt %type	<s> pathstring pathname password username
    126   1.4   deraadt 
    127   1.1       cgd %start	cmd_list
    128   1.1       cgd 
    129   1.1       cgd %%
    130   1.1       cgd 
    131   1.4   deraadt cmd_list
    132   1.4   deraadt 	: /* empty */
    133  1.23     lukem 
    134   1.4   deraadt 	| cmd_list cmd
    135   1.4   deraadt 		{
    136  1.22     lukem 			fromname = NULL;
    137   1.1       cgd 			restart_point = (off_t) 0;
    138   1.1       cgd 		}
    139  1.23     lukem 
    140   1.4   deraadt 	| cmd_list rcmd
    141  1.23     lukem 
    142   1.1       cgd 	;
    143   1.1       cgd 
    144   1.4   deraadt cmd
    145  1.23     lukem 						/* RFC 959 */
    146   1.4   deraadt 	: USER SP username CRLF
    147   1.4   deraadt 		{
    148   1.4   deraadt 			user($3);
    149   1.4   deraadt 			free($3);
    150   1.4   deraadt 		}
    151  1.23     lukem 
    152   1.4   deraadt 	| PASS SP password CRLF
    153   1.4   deraadt 		{
    154   1.4   deraadt 			pass($3);
    155   1.4   deraadt 			free($3);
    156   1.1       cgd 		}
    157  1.23     lukem 
    158  1.23     lukem 	| CWD check_login CRLF
    159  1.23     lukem 		{
    160  1.23     lukem 			if ($2)
    161  1.23     lukem 				cwd(pw->pw_dir);
    162  1.23     lukem 		}
    163  1.23     lukem 
    164  1.23     lukem 	| CWD check_login SP pathname CRLF
    165  1.23     lukem 		{
    166  1.23     lukem 			if ($2 && $4 != NULL)
    167  1.23     lukem 				cwd($4);
    168  1.23     lukem 			if ($4 != NULL)
    169  1.23     lukem 				free($4);
    170  1.23     lukem 		}
    171  1.23     lukem 
    172  1.23     lukem 	| CDUP check_login CRLF
    173  1.23     lukem 		{
    174  1.23     lukem 			if ($2)
    175  1.23     lukem 				cwd("..");
    176  1.23     lukem 		}
    177  1.23     lukem 
    178  1.23     lukem 	| QUIT CRLF
    179  1.23     lukem 		{
    180  1.23     lukem 			reply(221, "Goodbye.");
    181  1.23     lukem 			dologout(0);
    182  1.23     lukem 		}
    183  1.23     lukem 
    184  1.15       mrg 	| PORT check_login SP host_port CRLF
    185   1.4   deraadt 		{
    186  1.22     lukem 			if ($2) {
    187  1.22     lukem 					/* be paranoid, if told so */
    188  1.16       mrg 			if (curclass.checkportcmd &&
    189  1.15       mrg 			    ((ntohs(data_dest.sin_port) < IPPORT_RESERVED) ||
    190  1.15       mrg 			    memcmp(&data_dest.sin_addr, &his_addr.sin_addr,
    191  1.15       mrg 			    sizeof(data_dest.sin_addr)) != 0)) {
    192  1.22     lukem 				reply(500,
    193  1.22     lukem 				    "Illegal PORT command rejected");
    194  1.22     lukem 			} else {
    195  1.22     lukem 				usedefault = 0;
    196  1.22     lukem 				if (pdata >= 0) {
    197  1.22     lukem 					(void) close(pdata);
    198  1.22     lukem 					pdata = -1;
    199  1.22     lukem 				}
    200  1.22     lukem 				reply(200, "PORT command successful.");
    201  1.15       mrg 			}
    202   1.1       cgd 			}
    203   1.1       cgd 		}
    204  1.23     lukem 
    205  1.20        tv 	| PASV check_login CRLF
    206   1.4   deraadt 		{
    207  1.20        tv 			if (curclass.passive) {
    208  1.20        tv 				passive();
    209  1.20        tv 			} else {
    210  1.20        tv 				reply(500, "PASV mode not available.");
    211  1.20        tv 			}
    212   1.1       cgd 		}
    213  1.23     lukem 
    214   1.4   deraadt 	| TYPE SP type_code CRLF
    215   1.4   deraadt 		{
    216   1.1       cgd 			switch (cmd_type) {
    217   1.1       cgd 
    218   1.1       cgd 			case TYPE_A:
    219   1.1       cgd 				if (cmd_form == FORM_N) {
    220   1.1       cgd 					reply(200, "Type set to A.");
    221   1.1       cgd 					type = cmd_type;
    222   1.1       cgd 					form = cmd_form;
    223   1.1       cgd 				} else
    224   1.1       cgd 					reply(504, "Form must be N.");
    225   1.1       cgd 				break;
    226   1.1       cgd 
    227   1.1       cgd 			case TYPE_E:
    228   1.1       cgd 				reply(504, "Type E not implemented.");
    229   1.1       cgd 				break;
    230   1.1       cgd 
    231   1.1       cgd 			case TYPE_I:
    232   1.1       cgd 				reply(200, "Type set to I.");
    233   1.1       cgd 				type = cmd_type;
    234   1.1       cgd 				break;
    235   1.1       cgd 
    236   1.1       cgd 			case TYPE_L:
    237   1.1       cgd #if NBBY == 8
    238   1.1       cgd 				if (cmd_bytesz == 8) {
    239   1.1       cgd 					reply(200,
    240   1.1       cgd 					    "Type set to L (byte size 8).");
    241   1.1       cgd 					type = cmd_type;
    242   1.1       cgd 				} else
    243   1.1       cgd 					reply(504, "Byte size must be 8.");
    244   1.1       cgd #else /* NBBY == 8 */
    245   1.1       cgd 				UNIMPLEMENTED for NBBY != 8
    246   1.1       cgd #endif /* NBBY == 8 */
    247   1.1       cgd 			}
    248   1.1       cgd 		}
    249  1.23     lukem 
    250   1.4   deraadt 	| STRU SP struct_code CRLF
    251   1.4   deraadt 		{
    252   1.1       cgd 			switch ($3) {
    253   1.1       cgd 
    254   1.1       cgd 			case STRU_F:
    255   1.1       cgd 				reply(200, "STRU F ok.");
    256   1.1       cgd 				break;
    257   1.1       cgd 
    258   1.1       cgd 			default:
    259   1.1       cgd 				reply(504, "Unimplemented STRU type.");
    260   1.1       cgd 			}
    261   1.1       cgd 		}
    262  1.23     lukem 
    263   1.4   deraadt 	| MODE SP mode_code CRLF
    264   1.4   deraadt 		{
    265   1.1       cgd 			switch ($3) {
    266   1.1       cgd 
    267   1.1       cgd 			case MODE_S:
    268   1.1       cgd 				reply(200, "MODE S ok.");
    269   1.1       cgd 				break;
    270   1.1       cgd 
    271   1.1       cgd 			default:
    272   1.1       cgd 				reply(502, "Unimplemented MODE type.");
    273   1.1       cgd 			}
    274   1.1       cgd 		}
    275  1.23     lukem 
    276   1.4   deraadt 	| RETR check_login SP pathname CRLF
    277   1.4   deraadt 		{
    278   1.1       cgd 			if ($2 && $4 != NULL)
    279  1.22     lukem 				retrieve(NULL, $4);
    280   1.1       cgd 			if ($4 != NULL)
    281   1.4   deraadt 				free($4);
    282   1.1       cgd 		}
    283  1.23     lukem 
    284   1.4   deraadt 	| STOR check_login SP pathname CRLF
    285   1.4   deraadt 		{
    286   1.1       cgd 			if ($2 && $4 != NULL)
    287   1.4   deraadt 				store($4, "w", 0);
    288   1.1       cgd 			if ($4 != NULL)
    289   1.4   deraadt 				free($4);
    290   1.1       cgd 		}
    291  1.23     lukem 
    292  1.23     lukem 	| STOU check_login SP pathname CRLF
    293   1.4   deraadt 		{
    294   1.1       cgd 			if ($2 && $4 != NULL)
    295  1.23     lukem 				store($4, "w", 1);
    296   1.1       cgd 			if ($4 != NULL)
    297   1.4   deraadt 				free($4);
    298   1.1       cgd 		}
    299  1.23     lukem 
    300  1.23     lukem 	| APPE check_login SP pathname CRLF
    301   1.4   deraadt 		{
    302   1.4   deraadt 			if ($2 && $4 != NULL)
    303  1.23     lukem 				store($4, "a", 0);
    304   1.1       cgd 			if ($4 != NULL)
    305   1.4   deraadt 				free($4);
    306   1.1       cgd 		}
    307  1.23     lukem 
    308  1.23     lukem 	| ALLO SP NUMBER CRLF
    309   1.4   deraadt 		{
    310  1.23     lukem 			reply(202, "ALLO command ignored.");
    311   1.1       cgd 		}
    312  1.23     lukem 
    313  1.23     lukem 	| ALLO SP NUMBER SP R SP NUMBER CRLF
    314   1.4   deraadt 		{
    315  1.23     lukem 			reply(202, "ALLO command ignored.");
    316   1.1       cgd 		}
    317  1.23     lukem 
    318   1.4   deraadt 	| RNTO SP pathname CRLF
    319   1.4   deraadt 		{
    320   1.1       cgd 			if (fromname) {
    321   1.4   deraadt 				renamecmd(fromname, $3);
    322   1.1       cgd 				free(fromname);
    323  1.22     lukem 				fromname = NULL;
    324   1.1       cgd 			} else {
    325   1.1       cgd 				reply(503, "Bad sequence of commands.");
    326   1.1       cgd 			}
    327   1.4   deraadt 			free($3);
    328   1.1       cgd 		}
    329  1.23     lukem 
    330   1.4   deraadt 	| ABOR CRLF
    331   1.4   deraadt 		{
    332   1.1       cgd 			reply(225, "ABOR command successful.");
    333   1.1       cgd 		}
    334  1.23     lukem 
    335  1.23     lukem 	| DELE check_modify SP pathname CRLF
    336   1.4   deraadt 		{
    337  1.23     lukem 			if ($2 && $4 != NULL)
    338  1.23     lukem 				delete($4);
    339  1.23     lukem 			if ($4 != NULL)
    340  1.23     lukem 				free($4);
    341   1.1       cgd 		}
    342  1.23     lukem 
    343  1.23     lukem 	| RMD check_modify SP pathname CRLF
    344   1.4   deraadt 		{
    345   1.1       cgd 			if ($2 && $4 != NULL)
    346  1.23     lukem 				removedir($4);
    347   1.1       cgd 			if ($4 != NULL)
    348   1.4   deraadt 				free($4);
    349   1.1       cgd 		}
    350   1.1       cgd 
    351  1.12     lukem 	| MKD check_modify SP pathname CRLF
    352   1.4   deraadt 		{
    353   1.1       cgd 			if ($2 && $4 != NULL)
    354   1.4   deraadt 				makedir($4);
    355   1.1       cgd 			if ($4 != NULL)
    356   1.4   deraadt 				free($4);
    357   1.1       cgd 		}
    358  1.23     lukem 
    359  1.23     lukem 	| PWD check_login CRLF
    360  1.23     lukem 		{
    361  1.23     lukem 			if ($2)
    362  1.23     lukem 				pwd();
    363  1.23     lukem 		}
    364  1.23     lukem 
    365  1.23     lukem 	| LIST check_login CRLF
    366  1.23     lukem 		{
    367  1.23     lukem 			if ($2)
    368  1.23     lukem 				retrieve("/bin/ls -lgA", "");
    369  1.23     lukem 		}
    370  1.23     lukem 
    371  1.23     lukem 	| LIST check_login SP pathname CRLF
    372   1.4   deraadt 		{
    373   1.1       cgd 			if ($2 && $4 != NULL)
    374  1.23     lukem 				retrieve("/bin/ls -lgA %s", $4);
    375   1.1       cgd 			if ($4 != NULL)
    376   1.4   deraadt 				free($4);
    377   1.1       cgd 		}
    378  1.23     lukem 
    379  1.23     lukem 	| NLST check_login CRLF
    380   1.4   deraadt 		{
    381   1.1       cgd 			if ($2)
    382  1.23     lukem 				send_file_list(".");
    383   1.1       cgd 		}
    384  1.23     lukem 
    385  1.23     lukem 	| NLST check_login SP STRING CRLF
    386   1.4   deraadt 		{
    387  1.23     lukem 			if ($2 && $4 != NULL)
    388  1.23     lukem 				send_file_list($4);
    389  1.23     lukem 			if ($4 != NULL)
    390  1.23     lukem 				free($4);
    391   1.1       cgd 		}
    392  1.23     lukem 
    393   1.4   deraadt 	| SITE SP HELP CRLF
    394   1.4   deraadt 		{
    395  1.22     lukem 			help(sitetab, NULL);
    396   1.1       cgd 		}
    397  1.23     lukem 
    398   1.4   deraadt 	| SITE SP HELP SP STRING CRLF
    399   1.4   deraadt 		{
    400   1.4   deraadt 			help(sitetab, $5);
    401   1.1       cgd 		}
    402  1.23     lukem 
    403   1.4   deraadt 	| SITE SP UMASK check_login CRLF
    404   1.4   deraadt 		{
    405   1.1       cgd 			int oldmask;
    406   1.1       cgd 
    407   1.1       cgd 			if ($4) {
    408   1.1       cgd 				oldmask = umask(0);
    409   1.1       cgd 				(void) umask(oldmask);
    410   1.1       cgd 				reply(200, "Current UMASK is %03o", oldmask);
    411   1.1       cgd 			}
    412   1.1       cgd 		}
    413  1.23     lukem 
    414  1.12     lukem 	| SITE SP UMASK check_modify SP octal_number CRLF
    415   1.4   deraadt 		{
    416   1.1       cgd 			int oldmask;
    417   1.1       cgd 
    418   1.1       cgd 			if ($4) {
    419   1.1       cgd 				if (($6 == -1) || ($6 > 0777)) {
    420   1.1       cgd 					reply(501, "Bad UMASK value");
    421   1.1       cgd 				} else {
    422   1.1       cgd 					oldmask = umask($6);
    423   1.1       cgd 					reply(200,
    424   1.1       cgd 					    "UMASK set to %03o (was %03o)",
    425   1.1       cgd 					    $6, oldmask);
    426   1.1       cgd 				}
    427   1.1       cgd 			}
    428   1.1       cgd 		}
    429  1.23     lukem 
    430  1.12     lukem 	| SITE SP CHMOD check_modify SP octal_number SP pathname CRLF
    431   1.4   deraadt 		{
    432   1.1       cgd 			if ($4 && ($8 != NULL)) {
    433   1.1       cgd 				if ($6 > 0777)
    434   1.1       cgd 					reply(501,
    435   1.1       cgd 				"CHMOD: Mode value must be between 0 and 0777");
    436   1.4   deraadt 				else if (chmod($8, $6) < 0)
    437   1.4   deraadt 					perror_reply(550, $8);
    438   1.1       cgd 				else
    439   1.1       cgd 					reply(200, "CHMOD command successful.");
    440   1.1       cgd 			}
    441   1.1       cgd 			if ($8 != NULL)
    442   1.4   deraadt 				free($8);
    443   1.1       cgd 		}
    444  1.23     lukem 
    445   1.4   deraadt 	| SITE SP IDLE CRLF
    446   1.4   deraadt 		{
    447   1.1       cgd 			reply(200,
    448   1.1       cgd 			    "Current IDLE time limit is %d seconds; max %d",
    449  1.12     lukem 				curclass.timeout, curclass.maxtimeout);
    450   1.1       cgd 		}
    451  1.23     lukem 
    452   1.4   deraadt 	| SITE SP IDLE SP NUMBER CRLF
    453   1.4   deraadt 		{
    454  1.12     lukem 			if ($5 < 30 || $5 > curclass.maxtimeout) {
    455   1.1       cgd 				reply(501,
    456  1.12     lukem 			"IDLE time limit must be between 30 and %d seconds",
    457  1.12     lukem 				    curclass.maxtimeout);
    458   1.1       cgd 			} else {
    459  1.12     lukem 				curclass.timeout = $5;
    460  1.12     lukem 				(void) alarm(curclass.timeout);
    461   1.1       cgd 				reply(200,
    462  1.12     lukem 				    "IDLE time limit set to %d seconds",
    463  1.12     lukem 				    curclass.timeout);
    464   1.1       cgd 			}
    465   1.1       cgd 		}
    466  1.23     lukem 
    467  1.23     lukem 	| SYST CRLF
    468  1.23     lukem 		{
    469  1.23     lukem 			reply(215, "UNIX Type: L%d %s", NBBY, version);
    470  1.23     lukem 		}
    471  1.23     lukem 
    472  1.23     lukem 	| STAT check_login SP pathname CRLF
    473   1.4   deraadt 		{
    474   1.1       cgd 			if ($2 && $4 != NULL)
    475  1.23     lukem 				statfilecmd($4);
    476   1.1       cgd 			if ($4 != NULL)
    477   1.4   deraadt 				free($4);
    478   1.1       cgd 		}
    479  1.23     lukem 
    480  1.23     lukem 	| STAT CRLF
    481  1.23     lukem 		{
    482  1.23     lukem 			statcmd();
    483  1.23     lukem 		}
    484  1.23     lukem 
    485  1.23     lukem 	| HELP CRLF
    486  1.23     lukem 		{
    487  1.23     lukem 			help(cmdtab, NULL);
    488  1.23     lukem 		}
    489  1.23     lukem 
    490  1.23     lukem 	| HELP SP STRING CRLF
    491  1.23     lukem 		{
    492  1.23     lukem 			char *cp = $3;
    493  1.23     lukem 
    494  1.23     lukem 			if (strncasecmp(cp, "SITE", 4) == 0) {
    495  1.23     lukem 				cp = $3 + 4;
    496  1.23     lukem 				if (*cp == ' ')
    497  1.23     lukem 					cp++;
    498  1.23     lukem 				if (*cp)
    499  1.23     lukem 					help(sitetab, cp);
    500  1.23     lukem 				else
    501  1.23     lukem 					help(sitetab, NULL);
    502  1.23     lukem 			} else
    503  1.23     lukem 				help(cmdtab, $3);
    504  1.23     lukem 		}
    505  1.23     lukem 
    506  1.23     lukem 	| NOOP CRLF
    507  1.23     lukem 		{
    508  1.23     lukem 			reply(200, "NOOP command successful.");
    509  1.23     lukem 		}
    510  1.23     lukem 
    511  1.23     lukem 						/* RFC 2389 */
    512  1.23     lukem 	| FEAT CRLF
    513  1.23     lukem 		{
    514  1.23     lukem 			lreply(211, "Features supported");
    515  1.24     lukem 			printf(" MDTM\r\n");
    516  1.24     lukem 			printf(" REST STREAM\r\n");
    517  1.23     lukem 			printf(" SIZE\r\n");
    518  1.23     lukem 			reply(211, "End");
    519  1.23     lukem 		}
    520  1.23     lukem 
    521  1.23     lukem 	| OPTS SP STRING CRLF
    522   1.4   deraadt 		{
    523  1.23     lukem 
    524  1.23     lukem 			opts($3);
    525   1.1       cgd 		}
    526   1.1       cgd 
    527  1.23     lukem 
    528  1.23     lukem 						/* BSD extensions */
    529  1.23     lukem 
    530   1.1       cgd 		/*
    531  1.21     lukem 		 * SIZE is not in RFC 959, but Postel has blessed it and
    532   1.1       cgd 		 * it will be in the updated RFC.
    533   1.1       cgd 		 *
    534   1.1       cgd 		 * Return size of file in a format suitable for
    535   1.1       cgd 		 * using with RESTART (we just count bytes).
    536   1.1       cgd 		 */
    537   1.4   deraadt 	| SIZE check_login SP pathname CRLF
    538   1.4   deraadt 		{
    539   1.1       cgd 			if ($2 && $4 != NULL)
    540   1.4   deraadt 				sizecmd($4);
    541   1.1       cgd 			if ($4 != NULL)
    542   1.4   deraadt 				free($4);
    543   1.1       cgd 		}
    544   1.1       cgd 
    545   1.1       cgd 		/*
    546  1.21     lukem 		 * MDTM is not in RFC 959, but Postel has blessed it and
    547   1.1       cgd 		 * it will be in the updated RFC.
    548   1.1       cgd 		 *
    549   1.1       cgd 		 * Return modification time of file as an ISO 3307
    550   1.1       cgd 		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
    551   1.1       cgd 		 * where xxx is the fractional second (of any precision,
    552   1.1       cgd 		 * not necessarily 3 digits)
    553   1.1       cgd 		 */
    554   1.4   deraadt 	| MDTM check_login SP pathname CRLF
    555   1.4   deraadt 		{
    556   1.1       cgd 			if ($2 && $4 != NULL) {
    557   1.1       cgd 				struct stat stbuf;
    558   1.4   deraadt 				if (stat($4, &stbuf) < 0)
    559  1.22     lukem 					perror_reply(550, $4);
    560   1.4   deraadt 				else if (!S_ISREG(stbuf.st_mode)) {
    561   1.4   deraadt 					reply(550, "%s: not a plain file.", $4);
    562   1.1       cgd 				} else {
    563   1.4   deraadt 					struct tm *t;
    564   1.1       cgd 					t = gmtime(&stbuf.st_mtime);
    565   1.1       cgd 					reply(213,
    566   1.7       jtc 					    "%04d%02d%02d%02d%02d%02d",
    567  1.18     lukem 					    TM_YEAR_BASE + t->tm_year,
    568   1.7       jtc 					    t->tm_mon+1, t->tm_mday,
    569   1.1       cgd 					    t->tm_hour, t->tm_min, t->tm_sec);
    570   1.1       cgd 				}
    571   1.1       cgd 			}
    572   1.1       cgd 			if ($4 != NULL)
    573   1.4   deraadt 				free($4);
    574   1.1       cgd 		}
    575  1.23     lukem 
    576   1.4   deraadt 	| error CRLF
    577   1.4   deraadt 		{
    578   1.1       cgd 			yyerrok;
    579   1.1       cgd 		}
    580   1.1       cgd 	;
    581  1.21     lukem 
    582   1.4   deraadt rcmd
    583  1.23     lukem 	: REST SP byte_size CRLF
    584  1.23     lukem 		{
    585  1.23     lukem 			fromname = NULL;
    586  1.23     lukem 			restart_point = $3;	/* XXX $3 is only "int" */
    587  1.23     lukem 			reply(350, "Restarting at %qd. %s", restart_point,
    588  1.23     lukem 			    "Send STORE or RETRIEVE to initiate transfer.");
    589  1.23     lukem 		}
    590  1.23     lukem 	| RNFR check_modify SP pathname CRLF
    591   1.4   deraadt 		{
    592   1.1       cgd 			restart_point = (off_t) 0;
    593   1.1       cgd 			if ($2 && $4) {
    594   1.4   deraadt 				fromname = renamefrom($4);
    595  1.22     lukem 				if (fromname == NULL && $4) {
    596   1.4   deraadt 					free($4);
    597   1.1       cgd 				}
    598   1.1       cgd 			}
    599   1.1       cgd 		}
    600   1.1       cgd 	;
    601   1.4   deraadt 
    602   1.4   deraadt username
    603   1.4   deraadt 	: STRING
    604   1.1       cgd 	;
    605   1.1       cgd 
    606   1.4   deraadt password
    607   1.4   deraadt 	: /* empty */
    608   1.4   deraadt 		{
    609   1.4   deraadt 			$$ = (char *)calloc(1, sizeof(char));
    610   1.1       cgd 		}
    611  1.23     lukem 
    612   1.4   deraadt 	| STRING
    613   1.1       cgd 	;
    614   1.1       cgd 
    615   1.4   deraadt byte_size
    616   1.4   deraadt 	: NUMBER
    617   1.1       cgd 	;
    618   1.1       cgd 
    619   1.4   deraadt host_port
    620   1.4   deraadt 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
    621   1.1       cgd 		NUMBER COMMA NUMBER
    622   1.4   deraadt 		{
    623   1.4   deraadt 			char *a, *p;
    624   1.1       cgd 
    625   1.6   mycroft 			data_dest.sin_len = sizeof(struct sockaddr_in);
    626   1.6   mycroft 			data_dest.sin_family = AF_INET;
    627   1.6   mycroft 			p = (char *)&data_dest.sin_port;
    628   1.6   mycroft 			p[0] = $9; p[1] = $11;
    629   1.1       cgd 			a = (char *)&data_dest.sin_addr;
    630   1.1       cgd 			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
    631   1.1       cgd 		}
    632   1.1       cgd 	;
    633   1.1       cgd 
    634   1.4   deraadt form_code
    635   1.4   deraadt 	: N
    636   1.4   deraadt 		{
    637   1.4   deraadt 			$$ = FORM_N;
    638   1.4   deraadt 		}
    639  1.23     lukem 
    640   1.4   deraadt 	| T
    641   1.4   deraadt 		{
    642   1.4   deraadt 			$$ = FORM_T;
    643   1.4   deraadt 		}
    644  1.23     lukem 
    645   1.4   deraadt 	| C
    646   1.4   deraadt 		{
    647   1.4   deraadt 			$$ = FORM_C;
    648   1.4   deraadt 		}
    649   1.1       cgd 	;
    650   1.1       cgd 
    651   1.4   deraadt type_code
    652   1.4   deraadt 	: A
    653   1.4   deraadt 		{
    654   1.4   deraadt 			cmd_type = TYPE_A;
    655   1.4   deraadt 			cmd_form = FORM_N;
    656   1.4   deraadt 		}
    657  1.23     lukem 
    658   1.4   deraadt 	| A SP form_code
    659   1.4   deraadt 		{
    660   1.4   deraadt 			cmd_type = TYPE_A;
    661   1.4   deraadt 			cmd_form = $3;
    662   1.4   deraadt 		}
    663  1.23     lukem 
    664   1.4   deraadt 	| E
    665   1.4   deraadt 		{
    666   1.4   deraadt 			cmd_type = TYPE_E;
    667   1.4   deraadt 			cmd_form = FORM_N;
    668   1.4   deraadt 		}
    669  1.23     lukem 
    670   1.4   deraadt 	| E SP form_code
    671   1.4   deraadt 		{
    672   1.4   deraadt 			cmd_type = TYPE_E;
    673   1.4   deraadt 			cmd_form = $3;
    674   1.4   deraadt 		}
    675  1.23     lukem 
    676   1.4   deraadt 	| I
    677   1.4   deraadt 		{
    678   1.4   deraadt 			cmd_type = TYPE_I;
    679   1.4   deraadt 		}
    680  1.23     lukem 
    681   1.4   deraadt 	| L
    682   1.4   deraadt 		{
    683   1.4   deraadt 			cmd_type = TYPE_L;
    684   1.4   deraadt 			cmd_bytesz = NBBY;
    685   1.4   deraadt 		}
    686  1.23     lukem 
    687   1.4   deraadt 	| L SP byte_size
    688   1.4   deraadt 		{
    689   1.4   deraadt 			cmd_type = TYPE_L;
    690   1.4   deraadt 			cmd_bytesz = $3;
    691   1.4   deraadt 		}
    692  1.23     lukem 
    693   1.4   deraadt 		/* this is for a bug in the BBN ftp */
    694   1.4   deraadt 	| L byte_size
    695   1.4   deraadt 		{
    696   1.4   deraadt 			cmd_type = TYPE_L;
    697   1.4   deraadt 			cmd_bytesz = $2;
    698   1.4   deraadt 		}
    699   1.1       cgd 	;
    700   1.1       cgd 
    701   1.4   deraadt struct_code
    702   1.4   deraadt 	: F
    703   1.4   deraadt 		{
    704   1.4   deraadt 			$$ = STRU_F;
    705   1.4   deraadt 		}
    706  1.23     lukem 
    707   1.4   deraadt 	| R
    708   1.4   deraadt 		{
    709   1.4   deraadt 			$$ = STRU_R;
    710   1.4   deraadt 		}
    711  1.23     lukem 
    712   1.4   deraadt 	| P
    713   1.4   deraadt 		{
    714   1.4   deraadt 			$$ = STRU_P;
    715   1.4   deraadt 		}
    716   1.1       cgd 	;
    717   1.1       cgd 
    718   1.4   deraadt mode_code
    719   1.4   deraadt 	: S
    720   1.4   deraadt 		{
    721   1.4   deraadt 			$$ = MODE_S;
    722   1.4   deraadt 		}
    723  1.23     lukem 
    724   1.4   deraadt 	| B
    725   1.4   deraadt 		{
    726   1.4   deraadt 			$$ = MODE_B;
    727   1.4   deraadt 		}
    728  1.23     lukem 
    729   1.4   deraadt 	| C
    730   1.4   deraadt 		{
    731   1.4   deraadt 			$$ = MODE_C;
    732   1.4   deraadt 		}
    733   1.1       cgd 	;
    734   1.1       cgd 
    735   1.4   deraadt pathname
    736   1.4   deraadt 	: pathstring
    737   1.4   deraadt 		{
    738   1.4   deraadt 			/*
    739   1.4   deraadt 			 * Problem: this production is used for all pathname
    740   1.4   deraadt 			 * processing, but only gives a 550 error reply.
    741   1.9     lukem 			 * This is a valid reply in some cases but not in
    742   1.9     lukem 			 * others.
    743   1.4   deraadt 			 */
    744   1.4   deraadt 			if (logged_in && $1 && *$1 == '~') {
    745   1.4   deraadt 				glob_t gl;
    746   1.4   deraadt 				int flags =
    747  1.19    kleink 				 GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
    748   1.4   deraadt 
    749   1.9     lukem 				if ($1[1] == '\0')
    750  1.23     lukem 					$$ = xstrdup(pw->pw_dir);
    751   1.9     lukem 				else {
    752   1.9     lukem 					memset(&gl, 0, sizeof(gl));
    753   1.9     lukem 					if (glob($1, flags, NULL, &gl) ||
    754   1.9     lukem 					    gl.gl_pathc == 0) {
    755   1.9     lukem 						reply(550, "not found");
    756   1.9     lukem 						$$ = NULL;
    757   1.9     lukem 					} else
    758  1.23     lukem 						$$ = xstrdup(gl.gl_pathv[0]);
    759   1.9     lukem 					globfree(&gl);
    760   1.4   deraadt 				}
    761   1.4   deraadt 				free($1);
    762   1.4   deraadt 			} else
    763   1.4   deraadt 				$$ = $1;
    764   1.4   deraadt 		}
    765   1.1       cgd 	;
    766   1.1       cgd 
    767   1.4   deraadt pathstring
    768   1.4   deraadt 	: STRING
    769   1.1       cgd 	;
    770   1.1       cgd 
    771   1.4   deraadt octal_number
    772   1.4   deraadt 	: NUMBER
    773   1.4   deraadt 		{
    774   1.4   deraadt 			int ret, dec, multby, digit;
    775   1.1       cgd 
    776   1.4   deraadt 			/*
    777   1.4   deraadt 			 * Convert a number that was read as decimal number
    778   1.4   deraadt 			 * to what it would be if it had been read as octal.
    779   1.4   deraadt 			 */
    780   1.4   deraadt 			dec = $1;
    781   1.4   deraadt 			multby = 1;
    782   1.4   deraadt 			ret = 0;
    783   1.4   deraadt 			while (dec) {
    784   1.4   deraadt 				digit = dec%10;
    785   1.4   deraadt 				if (digit > 7) {
    786   1.4   deraadt 					ret = -1;
    787   1.4   deraadt 					break;
    788   1.4   deraadt 				}
    789   1.4   deraadt 				ret += digit * multby;
    790   1.4   deraadt 				multby *= 8;
    791   1.4   deraadt 				dec /= 10;
    792   1.1       cgd 			}
    793   1.4   deraadt 			$$ = ret;
    794   1.1       cgd 		}
    795   1.1       cgd 	;
    796   1.1       cgd 
    797   1.4   deraadt check_login
    798   1.4   deraadt 	: /* empty */
    799   1.4   deraadt 		{
    800   1.4   deraadt 			if (logged_in)
    801   1.4   deraadt 				$$ = 1;
    802   1.4   deraadt 			else {
    803   1.4   deraadt 				reply(530, "Please login with USER and PASS.");
    804   1.4   deraadt 				$$ = 0;
    805  1.22     lukem 				hasyyerrored = 1;
    806   1.4   deraadt 			}
    807   1.1       cgd 		}
    808   1.1       cgd 	;
    809  1.21     lukem 
    810  1.12     lukem check_modify
    811   1.8       cjs 	: /* empty */
    812   1.8       cjs 		{
    813  1.22     lukem 			if (logged_in) {
    814  1.22     lukem 				if (curclass.modify)
    815  1.12     lukem 					$$ = 1;
    816  1.22     lukem 				else {
    817   1.8       cjs 					reply(502,
    818  1.12     lukem 					"No permission to use this command.");
    819   1.8       cjs 					$$ = 0;
    820  1.22     lukem 					hasyyerrored = 1;
    821  1.14   hannken 				}
    822   1.8       cjs 			} else {
    823   1.8       cjs 				reply(530, "Please login with USER and PASS.");
    824   1.8       cjs 				$$ = 0;
    825  1.22     lukem 				hasyyerrored = 1;
    826   1.8       cjs 			}
    827   1.8       cjs 		}
    828   1.1       cgd 
    829   1.1       cgd %%
    830   1.1       cgd 
    831   1.1       cgd #define	CMD	0	/* beginning of command */
    832   1.1       cgd #define	ARGS	1	/* expect miscellaneous arguments */
    833   1.1       cgd #define	STR1	2	/* expect SP followed by STRING */
    834   1.1       cgd #define	STR2	3	/* expect STRING */
    835   1.1       cgd #define	OSTR	4	/* optional SP then STRING */
    836   1.1       cgd #define	ZSTR1	5	/* SP then optional STRING */
    837   1.1       cgd #define	ZSTR2	6	/* optional STRING after SP */
    838   1.1       cgd #define	SITECMD	7	/* SITE command */
    839   1.1       cgd #define	NSTR	8	/* Number followed by a string */
    840  1.21     lukem #define NOARGS	9	/* No arguments allowed */
    841   1.1       cgd 
    842   1.1       cgd struct tab {
    843   1.1       cgd 	char	*name;
    844  1.23     lukem 	short	 token;
    845  1.23     lukem 	short	 state;
    846  1.23     lukem 	short	 implemented;	/* 1 if command is implemented */
    847  1.23     lukem 	short	 hasopts;	/* 1 if command takes options */
    848   1.1       cgd 	char	*help;
    849  1.23     lukem 	char	*options;
    850   1.1       cgd };
    851   1.1       cgd 
    852  1.21     lukem struct tab cmdtab[] = {
    853  1.21     lukem 				/* From RFC 959, in order defined (5.3.1) */
    854  1.23     lukem 	{ "USER", USER, STR1,	1, 0,	"<sp> username" },
    855  1.23     lukem 	{ "PASS", PASS, ZSTR1,	1, 0,	"<sp> password" },
    856  1.23     lukem 	{ "ACCT", ACCT, STR1,	0, 0,	"(specify account)" },
    857  1.23     lukem 	{ "CWD",  CWD,  OSTR,	1, 0,	"[ <sp> directory-name ]" },
    858  1.23     lukem 	{ "CDUP", CDUP, NOARGS,	1, 0,	"(change to parent directory)" },
    859  1.23     lukem 	{ "SMNT", SMNT, ARGS,	0, 0,	"(structure mount)" },
    860  1.23     lukem 	{ "QUIT", QUIT, NOARGS,	1, 0,	"(terminate service)", },
    861  1.23     lukem 	{ "REIN", REIN, NOARGS,	0, 0,	"(reinitialize server state)" },
    862  1.23     lukem 	{ "PORT", PORT, ARGS,	1, 0,	"<sp> b0, b1, b2, b3, b4" },
    863  1.23     lukem 	{ "PASV", PASV, NOARGS,	1, 0,	"(set server in passive mode)" },
    864  1.23     lukem 	{ "TYPE", TYPE, ARGS,	1, 0,	"<sp> [ A | E | I | L ]" },
    865  1.23     lukem 	{ "STRU", STRU, ARGS,	1, 0,	"(specify file structure)" },
    866  1.23     lukem 	{ "MODE", MODE, ARGS,	1, 0,	"(specify transfer mode)" },
    867  1.23     lukem 	{ "RETR", RETR, STR1,	1, 0,	"<sp> file-name" },
    868  1.23     lukem 	{ "STOR", STOR, STR1,	1, 0,	"<sp> file-name" },
    869  1.23     lukem 	{ "STOU", STOU, STR1,	1, 0,	"<sp> file-name" },
    870  1.23     lukem 	{ "APPE", APPE, STR1,	1, 0,	"<sp> file-name" },
    871  1.23     lukem 	{ "ALLO", ALLO, ARGS,	1, 0,	"allocate storage (vacuously)" },
    872  1.23     lukem 	{ "REST", REST, ARGS,	1, 0,	"<sp> offset (restart command)" },
    873  1.23     lukem 	{ "RNFR", RNFR, STR1,	1, 0,	"<sp> file-name" },
    874  1.23     lukem 	{ "RNTO", RNTO, STR1,	1, 0,	"<sp> file-name" },
    875  1.23     lukem 	{ "ABOR", ABOR, NOARGS,	1, 0,	"(abort operation)" },
    876  1.23     lukem 	{ "DELE", DELE, STR1,	1, 0,	"<sp> file-name" },
    877  1.23     lukem 	{ "RMD",  RMD,  STR1,	1, 0,	"<sp> path-name" },
    878  1.23     lukem 	{ "MKD",  MKD,  STR1,	1, 0,	"<sp> path-name" },
    879  1.23     lukem 	{ "PWD",  PWD,  NOARGS,	1, 0,	"(return current directory)" },
    880  1.23     lukem 	{ "LIST", LIST, OSTR,	1, 0,	"[ <sp> path-name ]" },
    881  1.23     lukem 	{ "NLST", NLST, OSTR,	1, 0,	"[ <sp> path-name ]" },
    882  1.23     lukem 	{ "SITE", SITE, SITECMD, 1, 0,	"site-cmd [ <sp> arguments ]" },
    883  1.23     lukem 	{ "SYST", SYST, NOARGS,	1, 0,	"(get type of operating system)" },
    884  1.23     lukem 	{ "STAT", STAT, OSTR,	1, 0,	"[ <sp> path-name ]" },
    885  1.23     lukem 	{ "HELP", HELP, OSTR,	1, 0,	"[ <sp> <string> ]" },
    886  1.23     lukem 	{ "NOOP", NOOP, NOARGS,	1, 1,	"" },
    887  1.23     lukem 
    888  1.23     lukem 				/* From RFC-2389, in order defined */
    889  1.23     lukem 	{ "FEAT", FEAT, NOARGS,	1, 0,	"(display extended features)" },
    890  1.23     lukem 	{ "OPTS", OPTS, STR1,	1, 0,	"<sp> command [ <sp> options ]" },
    891  1.23     lukem 
    892  1.23     lukem 				/* Non standardized extensions */
    893  1.23     lukem 	{ "SIZE", SIZE, OSTR,	1, 0,	"<sp> path-name" },
    894  1.23     lukem 	{ "MDTM", MDTM, OSTR,	1, 0,	"<sp> path-name" },
    895  1.21     lukem 
    896  1.21     lukem 				/* obsolete commands */
    897  1.23     lukem 	{ "MAIL", MAIL, OSTR,	0, 0,	"(mail to user)" },
    898  1.23     lukem 	{ "MLFL", MLFL, OSTR,	0, 0,	"(mail file)" },
    899  1.23     lukem 	{ "MRCP", MRCP, STR1,	0, 0,	"(mail recipient)" },
    900  1.23     lukem 	{ "MRSQ", MRSQ, OSTR,	0, 0,	"(mail recipient scheme question)" },
    901  1.23     lukem 	{ "MSAM", MSAM, OSTR,	0, 0,	"(mail send to terminal and mailbox)" },
    902  1.23     lukem 	{ "MSND", MSND, OSTR,	0, 0,	"(mail send to terminal)" },
    903  1.23     lukem 	{ "MSOM", MSOM, OSTR,	0, 0,	"(mail send to terminal or mailbox)" },
    904  1.23     lukem 	{ "XCUP", CDUP, NOARGS,	1, 0,	"(change to parent directory)" },
    905  1.23     lukem 	{ "XCWD", CWD,	OSTR,	1, 0,	"[ <sp> directory-name ]" },
    906  1.23     lukem 	{ "XMKD", MKD,  STR1,	1, 0,	"<sp> path-name" },
    907  1.23     lukem 	{ "XPWD", PWD,  NOARGS,	1, 0,	"(return current directory)" },
    908  1.23     lukem 	{ "XRMD", RMD,  STR1,	1, 0,	"<sp> path-name" },
    909  1.21     lukem 
    910  1.23     lukem 	{ NULL,   0,    0,	0, 0,	0 }
    911   1.1       cgd };
    912   1.1       cgd 
    913   1.1       cgd struct tab sitetab[] = {
    914  1.23     lukem 	{ "UMASK", UMASK, ARGS,	1, 0,	"[ <sp> umask ]" },
    915  1.23     lukem 	{ "IDLE",  IDLE,  ARGS,	1, 0,	"[ <sp> maximum-idle-time ]" },
    916  1.23     lukem 	{ "CHMOD", CHMOD, NSTR,	1, 0,	"<sp> mode <sp> file-name" },
    917  1.23     lukem 	{ "HELP",  HELP,  OSTR,	1, 0,	"[ <sp> <string> ]" },
    918  1.23     lukem 	{ NULL,    0,     0,	0, 0,	0 }
    919   1.1       cgd };
    920   1.1       cgd 
    921  1.23     lukem static	void	 	help __P((struct tab *, char *));
    922  1.23     lukem static	struct tab     *lookup __P((struct tab *, const char *));
    923  1.23     lukem static	void		opts __P((const char *));
    924  1.23     lukem static	void		sizecmd __P((char *));
    925  1.23     lukem static	void		toolong __P((int));
    926  1.23     lukem static	int		yylex __P((void));
    927   1.4   deraadt 
    928   1.4   deraadt static struct tab *
    929   1.1       cgd lookup(p, cmd)
    930   1.4   deraadt 	struct tab *p;
    931  1.23     lukem 	const char *cmd;
    932   1.1       cgd {
    933   1.1       cgd 
    934   1.1       cgd 	for (; p->name != NULL; p++)
    935  1.23     lukem 		if (strcasecmp(cmd, p->name) == 0)
    936   1.1       cgd 			return (p);
    937   1.1       cgd 	return (0);
    938   1.1       cgd }
    939   1.1       cgd 
    940   1.1       cgd #include <arpa/telnet.h>
    941   1.1       cgd 
    942   1.1       cgd /*
    943   1.1       cgd  * getline - a hacked up version of fgets to ignore TELNET escape codes.
    944   1.1       cgd  */
    945   1.1       cgd char *
    946   1.1       cgd getline(s, n, iop)
    947   1.1       cgd 	char *s;
    948   1.4   deraadt 	int n;
    949   1.4   deraadt 	FILE *iop;
    950   1.1       cgd {
    951   1.4   deraadt 	int c;
    952  1.21     lukem 	char *cs;
    953   1.1       cgd 
    954   1.1       cgd 	cs = s;
    955   1.1       cgd /* tmpline may contain saved command from urgent mode interruption */
    956   1.1       cgd 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
    957   1.1       cgd 		*cs++ = tmpline[c];
    958   1.1       cgd 		if (tmpline[c] == '\n') {
    959   1.1       cgd 			*cs++ = '\0';
    960   1.1       cgd 			if (debug)
    961   1.1       cgd 				syslog(LOG_DEBUG, "command: %s", s);
    962   1.1       cgd 			tmpline[0] = '\0';
    963   1.1       cgd 			return(s);
    964   1.1       cgd 		}
    965   1.1       cgd 		if (c == 0)
    966   1.1       cgd 			tmpline[0] = '\0';
    967   1.1       cgd 	}
    968   1.1       cgd 	while ((c = getc(iop)) != EOF) {
    969   1.1       cgd 		c &= 0377;
    970   1.1       cgd 		if (c == IAC) {
    971   1.1       cgd 		    if ((c = getc(iop)) != EOF) {
    972   1.1       cgd 			c &= 0377;
    973   1.1       cgd 			switch (c) {
    974   1.1       cgd 			case WILL:
    975   1.1       cgd 			case WONT:
    976   1.1       cgd 				c = getc(iop);
    977   1.1       cgd 				printf("%c%c%c", IAC, DONT, 0377&c);
    978   1.1       cgd 				(void) fflush(stdout);
    979   1.1       cgd 				continue;
    980   1.1       cgd 			case DO:
    981   1.1       cgd 			case DONT:
    982   1.1       cgd 				c = getc(iop);
    983   1.1       cgd 				printf("%c%c%c", IAC, WONT, 0377&c);
    984   1.1       cgd 				(void) fflush(stdout);
    985   1.1       cgd 				continue;
    986   1.1       cgd 			case IAC:
    987   1.1       cgd 				break;
    988   1.1       cgd 			default:
    989   1.1       cgd 				continue;	/* ignore command */
    990   1.1       cgd 			}
    991   1.1       cgd 		    }
    992   1.1       cgd 		}
    993   1.1       cgd 		*cs++ = c;
    994   1.1       cgd 		if (--n <= 0 || c == '\n')
    995   1.1       cgd 			break;
    996   1.1       cgd 	}
    997   1.1       cgd 	if (c == EOF && cs == s)
    998   1.1       cgd 		return (NULL);
    999   1.1       cgd 	*cs++ = '\0';
   1000   1.4   deraadt 	if (debug) {
   1001   1.4   deraadt 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
   1002   1.4   deraadt 			/* Don't syslog passwords */
   1003   1.4   deraadt 			syslog(LOG_DEBUG, "command: %.5s ???", s);
   1004   1.4   deraadt 		} else {
   1005  1.21     lukem 			char *cp;
   1006  1.21     lukem 			int len;
   1007   1.4   deraadt 
   1008   1.4   deraadt 			/* Don't syslog trailing CR-LF */
   1009   1.4   deraadt 			len = strlen(s);
   1010   1.4   deraadt 			cp = s + len - 1;
   1011   1.4   deraadt 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
   1012   1.4   deraadt 				--cp;
   1013   1.4   deraadt 				--len;
   1014   1.4   deraadt 			}
   1015   1.4   deraadt 			syslog(LOG_DEBUG, "command: %.*s", len, s);
   1016   1.4   deraadt 		}
   1017   1.4   deraadt 	}
   1018   1.1       cgd 	return (s);
   1019   1.1       cgd }
   1020   1.1       cgd 
   1021   1.1       cgd static void
   1022   1.4   deraadt toolong(signo)
   1023   1.4   deraadt 	int signo;
   1024   1.1       cgd {
   1025   1.1       cgd 
   1026   1.1       cgd 	reply(421,
   1027  1.12     lukem 	    "Timeout (%d seconds): closing control connection.",
   1028  1.12     lukem 	    curclass.timeout);
   1029   1.4   deraadt 	if (logging)
   1030   1.4   deraadt 		syslog(LOG_INFO, "User %s timed out after %d seconds",
   1031  1.12     lukem 		    (pw ? pw -> pw_name : "unknown"), curclass.timeout);
   1032   1.1       cgd 	dologout(1);
   1033   1.1       cgd }
   1034   1.1       cgd 
   1035   1.4   deraadt static int
   1036   1.1       cgd yylex()
   1037   1.1       cgd {
   1038   1.1       cgd 	static int cpos, state;
   1039   1.4   deraadt 	char *cp, *cp2;
   1040   1.4   deraadt 	struct tab *p;
   1041  1.22     lukem 	int n;
   1042   1.4   deraadt 	char c;
   1043   1.1       cgd 
   1044  1.23     lukem 	switch (state) {
   1045   1.1       cgd 
   1046  1.23     lukem 	case CMD:
   1047  1.23     lukem 		hasyyerrored = 0;
   1048  1.23     lukem 		(void) signal(SIGALRM, toolong);
   1049  1.23     lukem 		(void) alarm(curclass.timeout);
   1050  1.23     lukem 		if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
   1051  1.23     lukem 			reply(221, "You could at least say goodbye.");
   1052  1.23     lukem 			dologout(0);
   1053  1.23     lukem 		}
   1054  1.23     lukem 		(void) alarm(0);
   1055   1.3       cgd #ifdef HASSETPROCTITLE
   1056  1.23     lukem 		if (strncasecmp(cbuf, "PASS", 4) != 0)
   1057  1.23     lukem 			setproctitle("%s: %s", proctitle, cbuf);
   1058   1.3       cgd #endif /* HASSETPROCTITLE */
   1059  1.23     lukem 		if ((cp = strchr(cbuf, '\r'))) {
   1060  1.23     lukem 			*cp++ = '\n';
   1061  1.23     lukem 			*cp = '\0';
   1062  1.23     lukem 		}
   1063  1.23     lukem 		if ((cp = strpbrk(cbuf, " \n")))
   1064  1.23     lukem 			cpos = cp - cbuf;
   1065  1.23     lukem 		if (cpos == 0)
   1066  1.23     lukem 			cpos = 4;
   1067  1.23     lukem 		c = cbuf[cpos];
   1068  1.23     lukem 		cbuf[cpos] = '\0';
   1069  1.23     lukem 		p = lookup(cmdtab, cbuf);
   1070  1.23     lukem 		cbuf[cpos] = c;
   1071  1.23     lukem 		if (p != NULL) {
   1072  1.23     lukem 			if (p->implemented == 0) {
   1073  1.23     lukem 				reply(502, "%s command not implemented.",
   1074  1.23     lukem 				    p->name);
   1075  1.23     lukem 				hasyyerrored = 1;
   1076  1.23     lukem 				break;
   1077  1.23     lukem 			}
   1078  1.23     lukem 			state = p->state;
   1079  1.23     lukem 			yylval.s = p->name;
   1080  1.23     lukem 			return (p->token);
   1081  1.23     lukem 		}
   1082  1.23     lukem 		break;
   1083  1.23     lukem 
   1084  1.23     lukem 	case SITECMD:
   1085  1.23     lukem 		if (cbuf[cpos] == ' ') {
   1086  1.23     lukem 			cpos++;
   1087  1.23     lukem 			return (SP);
   1088  1.23     lukem 		}
   1089  1.23     lukem 		cp = &cbuf[cpos];
   1090  1.23     lukem 		if ((cp2 = strpbrk(cp, " \n")))
   1091  1.23     lukem 			cpos = cp2 - cbuf;
   1092  1.23     lukem 		c = cbuf[cpos];
   1093  1.23     lukem 		cbuf[cpos] = '\0';
   1094  1.23     lukem 		p = lookup(sitetab, cp);
   1095  1.23     lukem 		cbuf[cpos] = c;
   1096  1.23     lukem 		if (p != NULL) {
   1097  1.23     lukem 			if (p->implemented == 0) {
   1098  1.23     lukem 				reply(502, "SITE %s command not implemented.",
   1099  1.23     lukem 				    p->name);
   1100  1.23     lukem 				hasyyerrored = 1;
   1101  1.23     lukem 				break;
   1102  1.23     lukem 			}
   1103  1.23     lukem 			state = p->state;
   1104  1.23     lukem 			yylval.s = p->name;
   1105  1.23     lukem 			return (p->token);
   1106  1.23     lukem 		}
   1107  1.23     lukem 		break;
   1108  1.23     lukem 
   1109  1.23     lukem 	case OSTR:
   1110  1.23     lukem 		if (cbuf[cpos] == '\n') {
   1111  1.23     lukem 			state = CMD;
   1112  1.23     lukem 			return (CRLF);
   1113  1.23     lukem 		}
   1114  1.23     lukem 		/* FALLTHROUGH */
   1115  1.23     lukem 
   1116  1.23     lukem 	case STR1:
   1117  1.23     lukem 	case ZSTR1:
   1118  1.23     lukem 	dostr1:
   1119  1.23     lukem 		if (cbuf[cpos] == ' ') {
   1120  1.23     lukem 			cpos++;
   1121  1.23     lukem 			state = state == OSTR ? STR2 : ++state;
   1122  1.23     lukem 			return (SP);
   1123  1.23     lukem 		}
   1124  1.23     lukem 		break;
   1125  1.23     lukem 
   1126  1.23     lukem 	case ZSTR2:
   1127  1.23     lukem 		if (cbuf[cpos] == '\n') {
   1128  1.23     lukem 			state = CMD;
   1129  1.23     lukem 			return (CRLF);
   1130  1.23     lukem 		}
   1131  1.23     lukem 		/* FALLTHROUGH */
   1132  1.23     lukem 
   1133  1.23     lukem 	case STR2:
   1134  1.23     lukem 		cp = &cbuf[cpos];
   1135  1.23     lukem 		n = strlen(cp);
   1136  1.23     lukem 		cpos += n - 1;
   1137  1.23     lukem 		/*
   1138  1.23     lukem 		 * Make sure the string is nonempty and \n terminated.
   1139  1.23     lukem 		 */
   1140  1.23     lukem 		if (n > 1 && cbuf[cpos] == '\n') {
   1141  1.23     lukem 			cbuf[cpos] = '\0';
   1142  1.23     lukem 			yylval.s = xstrdup(cp);
   1143  1.23     lukem 			cbuf[cpos] = '\n';
   1144  1.23     lukem 			state = ARGS;
   1145  1.23     lukem 			return (STRING);
   1146  1.23     lukem 		}
   1147  1.23     lukem 		break;
   1148  1.23     lukem 
   1149  1.23     lukem 	case NSTR:
   1150  1.23     lukem 		if (cbuf[cpos] == ' ') {
   1151  1.23     lukem 			cpos++;
   1152  1.23     lukem 			return (SP);
   1153  1.23     lukem 		}
   1154  1.23     lukem 		if (isdigit(cbuf[cpos])) {
   1155  1.23     lukem 			cp = &cbuf[cpos];
   1156  1.23     lukem 			while (isdigit(cbuf[++cpos]))
   1157  1.23     lukem 				;
   1158   1.1       cgd 			c = cbuf[cpos];
   1159   1.1       cgd 			cbuf[cpos] = '\0';
   1160  1.23     lukem 			yylval.i = atoi(cp);
   1161   1.1       cgd 			cbuf[cpos] = c;
   1162  1.23     lukem 			state = STR1;
   1163  1.23     lukem 			return (NUMBER);
   1164  1.23     lukem 		}
   1165  1.23     lukem 		state = STR1;
   1166  1.23     lukem 		goto dostr1;
   1167   1.1       cgd 
   1168  1.23     lukem 	case ARGS:
   1169  1.23     lukem 		if (isdigit(cbuf[cpos])) {
   1170   1.1       cgd 			cp = &cbuf[cpos];
   1171  1.23     lukem 			while (isdigit(cbuf[++cpos]))
   1172  1.23     lukem 				;
   1173   1.1       cgd 			c = cbuf[cpos];
   1174   1.1       cgd 			cbuf[cpos] = '\0';
   1175  1.23     lukem 			yylval.i = atoi(cp);
   1176   1.1       cgd 			cbuf[cpos] = c;
   1177  1.23     lukem 			return (NUMBER);
   1178  1.23     lukem 		}
   1179  1.23     lukem 		switch (cbuf[cpos++]) {
   1180  1.23     lukem 
   1181  1.23     lukem 		case '\n':
   1182   1.1       cgd 			state = CMD;
   1183  1.23     lukem 			return (CRLF);
   1184  1.23     lukem 
   1185  1.23     lukem 		case ' ':
   1186  1.23     lukem 			return (SP);
   1187  1.23     lukem 
   1188  1.23     lukem 		case ',':
   1189  1.23     lukem 			return (COMMA);
   1190  1.23     lukem 
   1191  1.23     lukem 		case 'A':
   1192  1.23     lukem 		case 'a':
   1193  1.23     lukem 			return (A);
   1194  1.23     lukem 
   1195  1.23     lukem 		case 'B':
   1196  1.23     lukem 		case 'b':
   1197  1.23     lukem 			return (B);
   1198  1.23     lukem 
   1199  1.23     lukem 		case 'C':
   1200  1.23     lukem 		case 'c':
   1201  1.23     lukem 			return (C);
   1202  1.23     lukem 
   1203  1.23     lukem 		case 'E':
   1204  1.23     lukem 		case 'e':
   1205  1.23     lukem 			return (E);
   1206  1.23     lukem 
   1207  1.23     lukem 		case 'F':
   1208  1.23     lukem 		case 'f':
   1209  1.23     lukem 			return (F);
   1210  1.23     lukem 
   1211  1.23     lukem 		case 'I':
   1212  1.23     lukem 		case 'i':
   1213  1.23     lukem 			return (I);
   1214   1.1       cgd 
   1215  1.23     lukem 		case 'L':
   1216  1.23     lukem 		case 'l':
   1217  1.23     lukem 			return (L);
   1218   1.1       cgd 
   1219  1.23     lukem 		case 'N':
   1220  1.23     lukem 		case 'n':
   1221  1.23     lukem 			return (N);
   1222   1.1       cgd 
   1223  1.23     lukem 		case 'P':
   1224  1.23     lukem 		case 'p':
   1225  1.23     lukem 			return (P);
   1226   1.1       cgd 
   1227  1.23     lukem 		case 'R':
   1228  1.23     lukem 		case 'r':
   1229  1.23     lukem 			return (R);
   1230   1.1       cgd 
   1231  1.23     lukem 		case 'S':
   1232  1.23     lukem 		case 's':
   1233  1.23     lukem 			return (S);
   1234   1.1       cgd 
   1235  1.23     lukem 		case 'T':
   1236  1.23     lukem 		case 't':
   1237  1.23     lukem 			return (T);
   1238   1.1       cgd 
   1239  1.23     lukem 		}
   1240  1.23     lukem 		break;
   1241  1.21     lukem 
   1242  1.23     lukem 	case NOARGS:
   1243  1.23     lukem 		if (cbuf[cpos] == '\n') {
   1244  1.23     lukem 			state = CMD;
   1245  1.23     lukem 			return (CRLF);
   1246   1.1       cgd 		}
   1247  1.23     lukem 		c = cbuf[cpos];
   1248  1.23     lukem 		cbuf[cpos] = '\0';
   1249  1.23     lukem 		reply(501, "'%s' command does not take any arguments.", cbuf);
   1250  1.23     lukem 		hasyyerrored = 1;
   1251  1.23     lukem 		cbuf[cpos] = c;
   1252  1.23     lukem 		break;
   1253  1.23     lukem 
   1254  1.23     lukem 	default:
   1255  1.23     lukem 		fatal("Unknown state in scanner.");
   1256   1.1       cgd 	}
   1257  1.23     lukem 	yyerror(NULL);
   1258  1.23     lukem 	state = CMD;
   1259  1.23     lukem 	longjmp(errcatch, 0);
   1260  1.23     lukem 	/* NOTREACHED */
   1261   1.1       cgd }
   1262   1.1       cgd 
   1263  1.22     lukem /* ARGSUSED */
   1264  1.22     lukem void
   1265  1.22     lukem yyerror(s)
   1266  1.22     lukem 	char *s;
   1267  1.22     lukem {
   1268  1.22     lukem 	char *cp;
   1269  1.22     lukem 
   1270  1.22     lukem 	if (hasyyerrored)
   1271  1.22     lukem 		return;
   1272  1.22     lukem 	if ((cp = strchr(cbuf,'\n')) != NULL)
   1273  1.22     lukem 		*cp = '\0';
   1274  1.22     lukem 	reply(500, "'%s': command not understood.", cbuf);
   1275  1.22     lukem 	hasyyerrored = 1;
   1276  1.22     lukem }
   1277  1.22     lukem 
   1278   1.4   deraadt static void
   1279   1.1       cgd help(ctab, s)
   1280   1.1       cgd 	struct tab *ctab;
   1281   1.1       cgd 	char *s;
   1282   1.1       cgd {
   1283   1.4   deraadt 	struct tab *c;
   1284   1.4   deraadt 	int width, NCMDS;
   1285   1.1       cgd 	char *type;
   1286   1.1       cgd 
   1287   1.1       cgd 	if (ctab == sitetab)
   1288   1.1       cgd 		type = "SITE ";
   1289   1.1       cgd 	else
   1290   1.1       cgd 		type = "";
   1291   1.1       cgd 	width = 0, NCMDS = 0;
   1292   1.1       cgd 	for (c = ctab; c->name != NULL; c++) {
   1293   1.1       cgd 		int len = strlen(c->name);
   1294   1.1       cgd 
   1295   1.1       cgd 		if (len > width)
   1296   1.1       cgd 			width = len;
   1297   1.1       cgd 		NCMDS++;
   1298   1.1       cgd 	}
   1299   1.1       cgd 	width = (width + 8) &~ 7;
   1300   1.1       cgd 	if (s == 0) {
   1301   1.4   deraadt 		int i, j, w;
   1302   1.1       cgd 		int columns, lines;
   1303   1.1       cgd 
   1304  1.23     lukem 		lreply(214, "The following %scommands are recognized.", type);
   1305  1.24     lukem 		lreply(214, "(`-' = not implemented, `+' = supports options)");
   1306   1.1       cgd 		columns = 76 / width;
   1307   1.1       cgd 		if (columns == 0)
   1308   1.1       cgd 			columns = 1;
   1309   1.1       cgd 		lines = (NCMDS + columns - 1) / columns;
   1310   1.1       cgd 		for (i = 0; i < lines; i++) {
   1311   1.1       cgd 			printf("   ");
   1312   1.1       cgd 			for (j = 0; j < columns; j++) {
   1313   1.1       cgd 				c = ctab + j * lines + i;
   1314  1.23     lukem 				fputs(c->name, stdout);
   1315  1.23     lukem 				w = strlen(c->name);
   1316  1.23     lukem 				if (! c->implemented) {
   1317  1.24     lukem 					putchar('-');
   1318  1.23     lukem 					w++;
   1319  1.23     lukem 				}
   1320  1.23     lukem 				if (c->hasopts) {
   1321  1.23     lukem 					putchar('+');
   1322  1.23     lukem 					w++;
   1323  1.23     lukem 				}
   1324   1.1       cgd 				if (c + lines >= &ctab[NCMDS])
   1325   1.1       cgd 					break;
   1326   1.1       cgd 				while (w < width) {
   1327   1.1       cgd 					putchar(' ');
   1328   1.1       cgd 					w++;
   1329   1.1       cgd 				}
   1330   1.1       cgd 			}
   1331   1.1       cgd 			printf("\r\n");
   1332   1.1       cgd 		}
   1333   1.1       cgd 		(void) fflush(stdout);
   1334   1.1       cgd 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
   1335   1.1       cgd 		return;
   1336   1.1       cgd 	}
   1337   1.1       cgd 	c = lookup(ctab, s);
   1338   1.1       cgd 	if (c == (struct tab *)0) {
   1339   1.1       cgd 		reply(502, "Unknown command %s.", s);
   1340   1.1       cgd 		return;
   1341   1.1       cgd 	}
   1342   1.1       cgd 	if (c->implemented)
   1343   1.1       cgd 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
   1344   1.1       cgd 	else
   1345  1.23     lukem 		reply(214, "%s%-*s\t%s; not implemented.", type, width,
   1346   1.1       cgd 		    c->name, c->help);
   1347   1.1       cgd }
   1348   1.1       cgd 
   1349   1.4   deraadt static void
   1350   1.1       cgd sizecmd(filename)
   1351   1.4   deraadt 	char *filename;
   1352   1.1       cgd {
   1353   1.1       cgd 	switch (type) {
   1354   1.1       cgd 	case TYPE_L:
   1355   1.1       cgd 	case TYPE_I: {
   1356   1.1       cgd 		struct stat stbuf;
   1357   1.4   deraadt 		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
   1358   1.1       cgd 			reply(550, "%s: not a plain file.", filename);
   1359   1.1       cgd 		else
   1360   1.4   deraadt 			reply(213, "%qu", stbuf.st_size);
   1361   1.4   deraadt 		break; }
   1362   1.1       cgd 	case TYPE_A: {
   1363   1.1       cgd 		FILE *fin;
   1364   1.4   deraadt 		int c;
   1365   1.4   deraadt 		off_t count;
   1366   1.1       cgd 		struct stat stbuf;
   1367   1.1       cgd 		fin = fopen(filename, "r");
   1368   1.1       cgd 		if (fin == NULL) {
   1369   1.1       cgd 			perror_reply(550, filename);
   1370   1.1       cgd 			return;
   1371   1.1       cgd 		}
   1372   1.4   deraadt 		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
   1373   1.1       cgd 			reply(550, "%s: not a plain file.", filename);
   1374   1.1       cgd 			(void) fclose(fin);
   1375   1.1       cgd 			return;
   1376   1.1       cgd 		}
   1377   1.1       cgd 
   1378   1.1       cgd 		count = 0;
   1379   1.1       cgd 		while((c=getc(fin)) != EOF) {
   1380   1.1       cgd 			if (c == '\n')	/* will get expanded to \r\n */
   1381   1.1       cgd 				count++;
   1382   1.1       cgd 			count++;
   1383   1.1       cgd 		}
   1384   1.1       cgd 		(void) fclose(fin);
   1385   1.1       cgd 
   1386   1.4   deraadt 		reply(213, "%qd", count);
   1387   1.4   deraadt 		break; }
   1388   1.1       cgd 	default:
   1389   1.1       cgd 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
   1390   1.1       cgd 	}
   1391   1.1       cgd }
   1392  1.22     lukem 
   1393  1.23     lukem static void
   1394  1.23     lukem opts(command)
   1395  1.23     lukem 	const char *command;
   1396  1.23     lukem {
   1397  1.23     lukem 	struct tab *c;
   1398  1.23     lukem 	char *ep;
   1399  1.23     lukem 
   1400  1.23     lukem 	if ((ep = strchr(command, ' ')) != NULL)
   1401  1.23     lukem 		*ep++ = '\0';
   1402  1.23     lukem 	c = lookup(cmdtab, command);
   1403  1.23     lukem 	if (c == NULL) {
   1404  1.23     lukem 		reply(502, "Unknown command %s.", command);
   1405  1.23     lukem 		return;
   1406  1.23     lukem 	}
   1407  1.23     lukem 	if (c->implemented == 0) {
   1408  1.23     lukem 		reply(502, "%s command not implemented.", c->name);
   1409  1.23     lukem 		return;
   1410  1.23     lukem 	}
   1411  1.23     lukem 	if (c->hasopts == 0) {
   1412  1.23     lukem 		reply(501, "%s command does not support persistent options.",
   1413  1.23     lukem 		    c->name);
   1414  1.23     lukem 		return;
   1415  1.23     lukem 	}
   1416  1.23     lukem 
   1417  1.23     lukem 	if (ep != NULL && *ep != '\0') {
   1418  1.23     lukem 		if (c->options != NULL)
   1419  1.23     lukem 			free(c->options);
   1420  1.23     lukem 		c->options = xstrdup(ep);
   1421  1.23     lukem 	}
   1422  1.23     lukem 	if (c->options != NULL)
   1423  1.23     lukem 		reply(200, "Options for %s are '%s'.", c->name, c->options);
   1424  1.23     lukem 	else
   1425  1.23     lukem 		reply(200, "No options defined for %s.", c->name);
   1426  1.23     lukem }
   1427