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