ftpcmd.y revision 1.22
1/*	$OpenBSD: ftpcmd.y,v 1.22 2000/11/13 15:39:08 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.22 2000/11/13 15:39:08 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_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 check_epsvall SP host_port CRLF
170		{
171			if ($2 && $3) {
172				if ($5) {
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 check_epsvall SP host_long_port4 CRLF
199		{
200			if ($2 && $3) {
201				/* reject invalid host_long_port4 */
202				if ($5) {
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 check_epsvall SP host_long_port6 CRLF
218		{
219			if ($2 && $3) {
220				/* reject invalid host_long_port6 */
221				if ($5) {
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 check_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 && $3) { /* 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($5);
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 check_epsvall CRLF
328		{
329			if ($2 && $3)
330				passive();
331		}
332	| LPSV check_login check_epsvall CRLF
333		{
334			if ($2 && $3)
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_epsvall
1060	: /* empty */
1061		{
1062			if (epsvall) {
1063				reply(501, "the command is disallowed "
1064				    "after EPSV ALL");
1065				usedefault = 1;
1066				$$ = 0;
1067			} else
1068				$$ = 1;
1069		}
1070	;
1071
1072%%
1073
1074extern jmp_buf errcatch;
1075
1076#define	CMD	0	/* beginning of command */
1077#define	ARGS	1	/* expect miscellaneous arguments */
1078#define	STR1	2	/* expect SP followed by STRING */
1079#define	STR2	3	/* expect STRING */
1080#define	OSTR	4	/* optional SP then STRING */
1081#define	ZSTR1	5	/* SP then optional STRING */
1082#define	ZSTR2	6	/* optional STRING after SP */
1083#define	SITECMD	7	/* SITE command */
1084#define	NSTR	8	/* Number followed by a string */
1085
1086struct tab {
1087	char	*name;
1088	short	token;
1089	short	state;
1090	short	implemented;	/* 1 if command is implemented */
1091	char	*help;
1092};
1093
1094struct tab cmdtab[] = {		/* In order defined in RFC 765 */
1095	{ "USER", USER, STR1, 1,	"<sp> username" },
1096	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
1097	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
1098	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
1099	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
1100	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1101	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
1102	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1103	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
1104	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
1105	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
1106	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1107	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
1108	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
1109	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
1110	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
1111	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
1112	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
1113	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
1114	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
1115	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
1116	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
1117	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
1118	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
1119	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
1120	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
1121	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
1122	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
1123	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
1124	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
1125	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
1126	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
1127	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
1128	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
1129	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
1130	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
1131	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
1132	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
1133	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1134	{ "NOOP", NOOP, ARGS, 1,	"" },
1135	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
1136	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
1137	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
1138	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
1139	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
1140	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
1141	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1142	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1143	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
1144	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
1145	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
1146	{ NULL,   0,    0,    0,	0 }
1147};
1148
1149struct tab sitetab[] = {
1150	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
1151	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
1152	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
1153	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1154	{ NULL,   0,    0,    0,	0 }
1155};
1156
1157static void	 help __P((struct tab *, char *));
1158static struct tab *
1159		 lookup __P((struct tab *, char *));
1160static void	 sizecmd __P((char *));
1161static int	 yylex __P((void));
1162
1163extern int epsvall;
1164
1165static struct tab *
1166lookup(p, cmd)
1167	struct tab *p;
1168	char *cmd;
1169{
1170
1171	for (; p->name != NULL; p++)
1172		if (strcmp(cmd, p->name) == 0)
1173			return (p);
1174	return (0);
1175}
1176
1177#include <arpa/telnet.h>
1178
1179/*
1180 * getline - a hacked up version of fgets to ignore TELNET escape codes.
1181 */
1182char *
1183getline(s, n, iop)
1184	char *s;
1185	int n;
1186	FILE *iop;
1187{
1188	int c;
1189	register char *cs;
1190
1191	cs = s;
1192/* tmpline may contain saved command from urgent mode interruption */
1193	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1194		*cs++ = tmpline[c];
1195		if (tmpline[c] == '\n') {
1196			*cs++ = '\0';
1197			if (debug)
1198				syslog(LOG_DEBUG, "command: %s", s);
1199			tmpline[0] = '\0';
1200			return(s);
1201		}
1202		if (c == 0)
1203			tmpline[0] = '\0';
1204	}
1205	while ((c = getc(iop)) != EOF) {
1206		c &= 0377;
1207		if (c == IAC) {
1208		    if ((c = getc(iop)) != EOF) {
1209			c &= 0377;
1210			switch (c) {
1211			case WILL:
1212			case WONT:
1213				c = getc(iop);
1214				printf("%c%c%c", IAC, DONT, 0377&c);
1215				(void) fflush(stdout);
1216				continue;
1217			case DO:
1218			case DONT:
1219				c = getc(iop);
1220				printf("%c%c%c", IAC, WONT, 0377&c);
1221				(void) fflush(stdout);
1222				continue;
1223			case IAC:
1224				break;
1225			default:
1226				continue;	/* ignore command */
1227			}
1228		    }
1229		}
1230		*cs++ = c;
1231		if (--n <= 0 || c == '\n')
1232			break;
1233	}
1234	if (c == EOF && cs == s)
1235		return (NULL);
1236	*cs++ = '\0';
1237	if (debug) {
1238		if (!guest && strncasecmp("pass ", s, 5) == 0) {
1239			/* Don't syslog passwords */
1240			syslog(LOG_DEBUG, "command: %.5s ???", s);
1241		} else {
1242			register char *cp;
1243			register int len;
1244
1245			/* Don't syslog trailing CR-LF */
1246			len = strlen(s);
1247			cp = s + len - 1;
1248			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1249				--cp;
1250				--len;
1251			}
1252			syslog(LOG_DEBUG, "command: %.*s", len, s);
1253		}
1254	}
1255	return (s);
1256}
1257
1258void
1259toolong(signo)
1260	int signo;
1261{
1262
1263	reply(421,
1264	    "Timeout (%d seconds): closing control connection.", timeout);
1265	if (logging)
1266		syslog(LOG_INFO, "User %s timed out after %d seconds",
1267		    (pw ? pw -> pw_name : "unknown"), timeout);
1268	dologout(1);
1269}
1270
1271static int
1272yylex()
1273{
1274	static int cpos, state;
1275	char *cp, *cp2;
1276	struct tab *p;
1277	int n;
1278	char c;
1279
1280	for (;;) {
1281		switch (state) {
1282
1283		case CMD:
1284			(void) signal(SIGALRM, toolong);
1285			(void) alarm((unsigned) timeout);
1286			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
1287				reply(221, "You could at least say goodbye.");
1288				dologout(0);
1289			}
1290			(void) alarm(0);
1291			if ((cp = strchr(cbuf, '\r'))) {
1292				*cp++ = '\n';
1293				*cp = '\0';
1294			}
1295#ifdef HASSETPROCTITLE
1296			if (strncasecmp(cbuf, "PASS", 4) != 0) {
1297				if ((cp = strpbrk(cbuf, "\n"))) {
1298					c = *cp;
1299					*cp = '\0';
1300					setproctitle("%s: %s", proctitle, cbuf);
1301					*cp = c;
1302				}
1303			}
1304#endif /* HASSETPROCTITLE */
1305			if ((cp = strpbrk(cbuf, " \n")))
1306				cpos = cp - cbuf;
1307			if (cpos == 0)
1308				cpos = 4;
1309			c = cbuf[cpos];
1310			cbuf[cpos] = '\0';
1311			upper(cbuf);
1312			p = lookup(cmdtab, cbuf);
1313			cbuf[cpos] = c;
1314			if (p != 0) {
1315				if (p->implemented == 0) {
1316					nack(p->name);
1317					longjmp(errcatch,0);
1318					/* NOTREACHED */
1319				}
1320				state = p->state;
1321				yylval.s = p->name;
1322				return (p->token);
1323			}
1324			break;
1325
1326		case SITECMD:
1327			if (cbuf[cpos] == ' ') {
1328				cpos++;
1329				return (SP);
1330			}
1331			cp = &cbuf[cpos];
1332			if ((cp2 = strpbrk(cp, " \n")))
1333				cpos = cp2 - cbuf;
1334			c = cbuf[cpos];
1335			cbuf[cpos] = '\0';
1336			upper(cp);
1337			p = lookup(sitetab, cp);
1338			cbuf[cpos] = c;
1339			if (p != 0) {
1340				if (p->implemented == 0) {
1341					state = CMD;
1342					nack(p->name);
1343					longjmp(errcatch,0);
1344					/* NOTREACHED */
1345				}
1346				state = p->state;
1347				yylval.s = p->name;
1348				return (p->token);
1349			}
1350			state = CMD;
1351			break;
1352
1353		case OSTR:
1354			if (cbuf[cpos] == '\n') {
1355				state = CMD;
1356				return (CRLF);
1357			}
1358			/* FALLTHROUGH */
1359
1360		case STR1:
1361		case ZSTR1:
1362		dostr1:
1363			if (cbuf[cpos] == ' ') {
1364				cpos++;
1365				state = state == OSTR ? STR2 : state+1;
1366				return (SP);
1367			}
1368			break;
1369
1370		case ZSTR2:
1371			if (cbuf[cpos] == '\n') {
1372				state = CMD;
1373				return (CRLF);
1374			}
1375			/* FALLTHROUGH */
1376
1377		case STR2:
1378			cp = &cbuf[cpos];
1379			n = strlen(cp);
1380			cpos += n - 1;
1381			/*
1382			 * Make sure the string is nonempty and \n terminated.
1383			 */
1384			if (n > 1 && cbuf[cpos] == '\n') {
1385				cbuf[cpos] = '\0';
1386				yylval.s = strdup(cp);
1387				if (yylval.s == NULL)
1388					fatal("Ran out of memory.");
1389				cbuf[cpos] = '\n';
1390				state = ARGS;
1391				return (STRING);
1392			}
1393			break;
1394
1395		case NSTR:
1396			if (cbuf[cpos] == ' ') {
1397				cpos++;
1398				return (SP);
1399			}
1400			if (isdigit(cbuf[cpos])) {
1401				cp = &cbuf[cpos];
1402				while (isdigit(cbuf[++cpos]))
1403					;
1404				c = cbuf[cpos];
1405				cbuf[cpos] = '\0';
1406				yylval.i = atoi(cp);
1407				cbuf[cpos] = c;
1408				state = STR1;
1409				return (NUMBER);
1410			}
1411			state = STR1;
1412			goto dostr1;
1413
1414		case ARGS:
1415			if (isdigit(cbuf[cpos])) {
1416				cp = &cbuf[cpos];
1417				while (isdigit(cbuf[++cpos]))
1418					;
1419				c = cbuf[cpos];
1420				cbuf[cpos] = '\0';
1421				yylval.i = atoi(cp);
1422				cbuf[cpos] = c;
1423				return (NUMBER);
1424			}
1425			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1426			 && !isalnum(cbuf[cpos + 3])) {
1427				yylval.s = strdup("ALL");
1428				cpos += 3;
1429				return ALL;
1430			}
1431			switch (cbuf[cpos++]) {
1432
1433			case '\n':
1434				state = CMD;
1435				return (CRLF);
1436
1437			case ' ':
1438				return (SP);
1439
1440			case ',':
1441				return (COMMA);
1442
1443			case 'A':
1444			case 'a':
1445				return (A);
1446
1447			case 'B':
1448			case 'b':
1449				return (B);
1450
1451			case 'C':
1452			case 'c':
1453				return (C);
1454
1455			case 'E':
1456			case 'e':
1457				return (E);
1458
1459			case 'F':
1460			case 'f':
1461				return (F);
1462
1463			case 'I':
1464			case 'i':
1465				return (I);
1466
1467			case 'L':
1468			case 'l':
1469				return (L);
1470
1471			case 'N':
1472			case 'n':
1473				return (N);
1474
1475			case 'P':
1476			case 'p':
1477				return (P);
1478
1479			case 'R':
1480			case 'r':
1481				return (R);
1482
1483			case 'S':
1484			case 's':
1485				return (S);
1486
1487			case 'T':
1488			case 't':
1489				return (T);
1490
1491			}
1492			break;
1493
1494		default:
1495			fatal("Unknown state in scanner.");
1496		}
1497		yyerror((char *) 0);
1498		state = CMD;
1499		longjmp(errcatch,0);
1500	}
1501}
1502
1503void
1504upper(s)
1505	char *s;
1506{
1507	while (*s != '\0') {
1508		if (islower(*s))
1509			*s = toupper(*s);
1510		s++;
1511	}
1512}
1513
1514static void
1515help(ctab, s)
1516	struct tab *ctab;
1517	char *s;
1518{
1519	struct tab *c;
1520	int width, NCMDS;
1521	char *type;
1522
1523	if (ctab == sitetab)
1524		type = "SITE ";
1525	else
1526		type = "";
1527	width = 0, NCMDS = 0;
1528	for (c = ctab; c->name != NULL; c++) {
1529		int len = strlen(c->name);
1530
1531		if (len > width)
1532			width = len;
1533		NCMDS++;
1534	}
1535	width = (width + 8) &~ 7;
1536	if (s == 0) {
1537		int i, j, w;
1538		int columns, lines;
1539
1540		lreply(214, "The following %scommands are recognized %s.",
1541		    type, "(* =>'s unimplemented)");
1542		columns = 76 / width;
1543		if (columns == 0)
1544			columns = 1;
1545		lines = (NCMDS + columns - 1) / columns;
1546		for (i = 0; i < lines; i++) {
1547			printf("   ");
1548			for (j = 0; j < columns; j++) {
1549				c = ctab + j * lines + i;
1550				printf("%s%c", c->name,
1551					c->implemented ? ' ' : '*');
1552				if (c + lines >= &ctab[NCMDS])
1553					break;
1554				w = strlen(c->name) + 1;
1555				while (w < width) {
1556					putchar(' ');
1557					w++;
1558				}
1559			}
1560			printf("\r\n");
1561		}
1562		(void) fflush(stdout);
1563		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1564		return;
1565	}
1566	upper(s);
1567	c = lookup(ctab, s);
1568	if (c == (struct tab *)0) {
1569		reply(502, "Unknown command %s.", s);
1570		return;
1571	}
1572	if (c->implemented)
1573		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1574	else
1575		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1576		    c->name, c->help);
1577}
1578
1579static void
1580sizecmd(filename)
1581	char *filename;
1582{
1583	switch (type) {
1584	case TYPE_L:
1585	case TYPE_I: {
1586		struct stat stbuf;
1587		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1588			reply(550, "%s: not a plain file.", filename);
1589		else
1590			reply(213, "%qu", stbuf.st_size);
1591		break; }
1592	case TYPE_A: {
1593		FILE *fin;
1594		int c;
1595		off_t count;
1596		struct stat stbuf;
1597		fin = fopen(filename, "r");
1598		if (fin == NULL) {
1599			perror_reply(550, filename);
1600			return;
1601		}
1602		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1603			reply(550, "%s: not a plain file.", filename);
1604			(void) fclose(fin);
1605			return;
1606		}
1607
1608		count = 0;
1609		while((c=getc(fin)) != EOF) {
1610			if (c == '\n')	/* will get expanded to \r\n */
1611				count++;
1612			count++;
1613		}
1614		(void) fclose(fin);
1615
1616		reply(213, "%qd", count);
1617		break; }
1618	default:
1619		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1620	}
1621}
1622