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