xref: /openbsd-src/libexec/ftpd/ftpcmd.y (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: ftpcmd.y,v 1.64 2016/08/26 06:32:10 tedu 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 			memset($3, 0, 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(NULL, $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("/bin/ls -lgA", "");
378 		}
379 	| LIST check_login SP pathname CRLF
380 		{
381 			if ($2 && $4 != NULL)
382 				retrieve("/bin/ls -lgA %s", $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) < 0)
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) < 0)
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 			}
960 		}
961 	;
962 
963 check_login_epsvall
964 	: /* empty */
965 		{
966 			if (!logged_in) {
967 				reply(530, "Please login with USER and PASS.");
968 				$$ = 0;
969 			} else if (epsvall) {
970 				reply(501, "the command is disallowed "
971 				    "after EPSV ALL");
972 				usedefault = 1;
973 				$$ = 0;
974 			} else
975 				$$ = 1;
976 		}
977 	;
978 
979 %%
980 
981 #define	CMD	0	/* beginning of command */
982 #define	ARGS	1	/* expect miscellaneous arguments */
983 #define	STR1	2	/* expect SP followed by STRING */
984 #define	STR2	3	/* expect STRING */
985 #define	OSTR	4	/* optional SP then STRING */
986 #define	ZSTR1	5	/* SP then optional STRING */
987 #define	ZSTR2	6	/* optional STRING after SP */
988 #define	SITECMD	7	/* SITE command */
989 #define	NSTR	8	/* Number followed by a string */
990 
991 struct tab {
992 	char	*name;
993 	short	token;
994 	short	state;
995 	short	implemented;	/* 1 if command is implemented */
996 	char	*help;
997 };
998 
999 struct tab cmdtab[] = {		/* In order defined in RFC 765 */
1000 	{ "USER", USER, STR1, 1,	"<sp> username" },
1001 	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
1002 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
1003 	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
1004 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
1005 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1006 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
1007 	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1008 	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
1009 	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
1010 	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
1011 	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1012 	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
1013 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
1014 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
1015 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
1016 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
1017 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
1018 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
1019 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
1020 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
1021 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
1022 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
1023 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
1024 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
1025 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
1026 	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
1027 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
1028 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
1029 	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
1030 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
1031 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
1032 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
1033 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
1034 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
1035 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
1036 	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
1037 	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
1038 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1039 	{ "NOOP", NOOP, ARGS, 1,	"" },
1040 	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
1041 	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
1042 	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
1043 	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
1044 	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
1045 	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
1046 	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1047 	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1048 	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
1049 	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
1050 	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
1051 	{ NULL,   0,    0,    0,	0 }
1052 };
1053 
1054 struct tab sitetab[] = {
1055 	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
1056 	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
1057 	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
1058 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1059 	{ NULL,   0,    0,    0,	0 }
1060 };
1061 
1062 static void	 help(struct tab *, char *);
1063 static struct tab *
1064 		 lookup(struct tab *, char *);
1065 static void	 sizecmd(char *);
1066 static int	 yylex(void);
1067 
1068 extern int epsvall;
1069 
1070 static struct tab *
1071 lookup(p, cmd)
1072 	struct tab *p;
1073 	char *cmd;
1074 {
1075 
1076 	for (; p->name != NULL; p++)
1077 		if (strcmp(cmd, p->name) == 0)
1078 			return (p);
1079 	return (NULL);
1080 }
1081 
1082 #include <arpa/telnet.h>
1083 
1084 /*
1085  * get_line - a hacked up version of fgets to ignore TELNET escape codes.
1086  */
1087 int
1088 get_line(s, n, iop)
1089 	char *s;
1090 	int n;
1091 	FILE *iop;
1092 {
1093 	int c;
1094 	char *cs;
1095 
1096 	cs = s;
1097 /* tmpline may contain saved command from urgent mode interruption */
1098 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1099 		*cs++ = tmpline[c];
1100 		if (tmpline[c] == '\n') {
1101 			*cs++ = '\0';
1102 			if (debug)
1103 				syslog(LOG_DEBUG, "command: %s", s);
1104 			tmpline[0] = '\0';
1105 			return(0);
1106 		}
1107 		if (c == 0)
1108 			tmpline[0] = '\0';
1109 	}
1110 	while ((c = getc(iop)) != EOF) {
1111 		c &= 0377;
1112 		if (c == IAC) {
1113 		    if ((c = getc(iop)) != EOF) {
1114 			c &= 0377;
1115 			switch (c) {
1116 			case WILL:
1117 			case WONT:
1118 				c = getc(iop);
1119 				printf("%c%c%c", IAC, DONT, 0377&c);
1120 				(void) fflush(stdout);
1121 				continue;
1122 			case DO:
1123 			case DONT:
1124 				c = getc(iop);
1125 				printf("%c%c%c", IAC, WONT, 0377&c);
1126 				(void) fflush(stdout);
1127 				continue;
1128 			case IAC:
1129 				break;
1130 			default:
1131 				continue;	/* ignore command */
1132 			}
1133 		    }
1134 		}
1135 		*cs++ = c;
1136 		if (--n <= 0) {
1137 			/*
1138 			 * If command doesn't fit into buffer, discard the
1139 			 * rest of the command and indicate truncation.
1140 			 * This prevents the command to be split up into
1141 			 * multiple commands.
1142 			 */
1143 			while (c != '\n' && (c = getc(iop)) != EOF)
1144 				;
1145 			return (-2);
1146 		}
1147 		if (c == '\n')
1148 			break;
1149 	}
1150 	if (c == EOF && cs == s)
1151 		return (-1);
1152 	*cs++ = '\0';
1153 	if (debug) {
1154 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
1155 			/* Don't syslog passwords */
1156 			syslog(LOG_DEBUG, "command: %.5s ???", s);
1157 		} else {
1158 			char *cp;
1159 			int len;
1160 
1161 			/* Don't syslog trailing CR-LF */
1162 			len = strlen(s);
1163 			cp = s + len - 1;
1164 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1165 				--cp;
1166 				--len;
1167 			}
1168 			syslog(LOG_DEBUG, "command: %.*s", len, s);
1169 		}
1170 	}
1171 	return (0);
1172 }
1173 
1174 /*ARGSUSED*/
1175 void
1176 toolong(signo)
1177 	int signo;
1178 {
1179 	struct syslog_data sdata = SYSLOG_DATA_INIT;
1180 
1181 	reply_r(421,
1182 	    "Timeout (%d seconds): closing control connection.", timeout);
1183 	if (logging)
1184 		syslog_r(LOG_INFO, &sdata, "User %s timed out after %d seconds",
1185 		    (pw ? pw -> pw_name : "unknown"), timeout);
1186 	dologout(1);
1187 }
1188 
1189 static int
1190 yylex()
1191 {
1192 	static int cpos;
1193 	char *cp, *cp2;
1194 	struct tab *p;
1195 	int n;
1196 	char c;
1197 
1198 	for (;;) {
1199 		switch (state) {
1200 
1201 		case CMD:
1202 			(void) alarm((unsigned) timeout);
1203 			n = get_line(cbuf, sizeof(cbuf)-1, stdin);
1204 			if (n == -1) {
1205 				reply(221, "You could at least say goodbye.");
1206 				dologout(0);
1207 			} else if (n == -2) {
1208 				reply(500, "Command too long.");
1209 				alarm(0);
1210 				continue;
1211 			}
1212 			(void) alarm(0);
1213 			if ((cp = strchr(cbuf, '\r'))) {
1214 				*cp++ = '\n';
1215 				*cp = '\0';
1216 			}
1217 			if (strncasecmp(cbuf, "PASS", 4) != 0) {
1218 				if ((cp = strpbrk(cbuf, "\n"))) {
1219 					c = *cp;
1220 					*cp = '\0';
1221 					setproctitle("%s: %s", proctitle, cbuf);
1222 					*cp = c;
1223 				}
1224 			}
1225 			if ((cp = strpbrk(cbuf, " \n")))
1226 				cpos = cp - cbuf;
1227 			if (cpos == 0)
1228 				cpos = 4;
1229 			c = cbuf[cpos];
1230 			cbuf[cpos] = '\0';
1231 			upper(cbuf);
1232 			p = lookup(cmdtab, cbuf);
1233 			cbuf[cpos] = c;
1234 			if (p != NULL) {
1235 				if (p->implemented == 0) {
1236 					nack(p->name);
1237 					return (LEXERR);
1238 				}
1239 				state = p->state;
1240 				yylval.s = p->name;
1241 				return (p->token);
1242 			}
1243 			break;
1244 
1245 		case SITECMD:
1246 			if (cbuf[cpos] == ' ') {
1247 				cpos++;
1248 				return (SP);
1249 			}
1250 			cp = &cbuf[cpos];
1251 			if ((cp2 = strpbrk(cp, " \n")))
1252 				cpos = cp2 - cbuf;
1253 			c = cbuf[cpos];
1254 			cbuf[cpos] = '\0';
1255 			upper(cp);
1256 			p = lookup(sitetab, cp);
1257 			cbuf[cpos] = c;
1258 			if (p != NULL) {
1259 				if (p->implemented == 0) {
1260 					state = CMD;
1261 					nack(p->name);
1262 					return (LEXERR);
1263 				}
1264 				state = p->state;
1265 				yylval.s = p->name;
1266 				return (p->token);
1267 			}
1268 			state = CMD;
1269 			break;
1270 
1271 		case OSTR:
1272 			if (cbuf[cpos] == '\n') {
1273 				state = CMD;
1274 				return (CRLF);
1275 			}
1276 			/* FALLTHROUGH */
1277 
1278 		case STR1:
1279 		case ZSTR1:
1280 		dostr1:
1281 			if (cbuf[cpos] == ' ') {
1282 				cpos++;
1283 				state = state == OSTR ? STR2 : state+1;
1284 				return (SP);
1285 			}
1286 			break;
1287 
1288 		case ZSTR2:
1289 			if (cbuf[cpos] == '\n') {
1290 				state = CMD;
1291 				return (CRLF);
1292 			}
1293 			/* FALLTHROUGH */
1294 
1295 		case STR2:
1296 			cp = &cbuf[cpos];
1297 			n = strlen(cp);
1298 			cpos += n - 1;
1299 			/*
1300 			 * Make sure the string is nonempty and \n terminated.
1301 			 */
1302 			if (n > 1 && cbuf[cpos] == '\n') {
1303 				cbuf[cpos] = '\0';
1304 				yylval.s = strdup(cp);
1305 				if (yylval.s == NULL)
1306 					fatal("Ran out of memory.");
1307 				cbuf[cpos] = '\n';
1308 				state = ARGS;
1309 				return (STRING);
1310 			}
1311 			break;
1312 
1313 		case NSTR:
1314 			if (cbuf[cpos] == ' ') {
1315 				cpos++;
1316 				return (SP);
1317 			}
1318 			if (isdigit((unsigned char)cbuf[cpos])) {
1319 				cp = &cbuf[cpos];
1320 				while (isdigit((unsigned char)cbuf[++cpos]))
1321 					;
1322 				c = cbuf[cpos];
1323 				cbuf[cpos] = '\0';
1324 				yylval.i = atoi(cp);
1325 				cbuf[cpos] = c;
1326 				state = STR1;
1327 				return (NUMBER);
1328 			}
1329 			state = STR1;
1330 			goto dostr1;
1331 
1332 		case ARGS:
1333 			if (isdigit((unsigned char)cbuf[cpos])) {
1334 				long long llval;
1335 
1336 				cp = &cbuf[cpos];
1337 				errno = 0;
1338 				llval = strtoll(cp, &cp2, 10);
1339 				if (llval < 0 ||
1340 				    (errno == ERANGE && llval == LLONG_MAX))
1341 					break;
1342 
1343 				cpos = (int)(cp2 - cbuf);
1344 				if (llval > INT_MAX) {
1345 					yylval.o = llval;
1346 					return (BIGNUM);
1347 				} else {
1348 					yylval.i = (int)llval;
1349 					return (NUMBER);
1350 				}
1351 			}
1352 			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0 &&
1353 			    !isalnum((unsigned char)cbuf[cpos + 3])) {
1354 				cpos += 3;
1355 				return ALL;
1356 			}
1357 			switch (cbuf[cpos++]) {
1358 
1359 			case '\n':
1360 				state = CMD;
1361 				return (CRLF);
1362 
1363 			case ' ':
1364 				return (SP);
1365 
1366 			case ',':
1367 				return (COMMA);
1368 
1369 			case 'A':
1370 			case 'a':
1371 				return (A);
1372 
1373 			case 'B':
1374 			case 'b':
1375 				return (B);
1376 
1377 			case 'C':
1378 			case 'c':
1379 				return (C);
1380 
1381 			case 'E':
1382 			case 'e':
1383 				return (E);
1384 
1385 			case 'F':
1386 			case 'f':
1387 				return (F);
1388 
1389 			case 'I':
1390 			case 'i':
1391 				return (I);
1392 
1393 			case 'L':
1394 			case 'l':
1395 				return (L);
1396 
1397 			case 'N':
1398 			case 'n':
1399 				return (N);
1400 
1401 			case 'P':
1402 			case 'p':
1403 				return (P);
1404 
1405 			case 'R':
1406 			case 'r':
1407 				return (R);
1408 
1409 			case 'S':
1410 			case 's':
1411 				return (S);
1412 
1413 			case 'T':
1414 			case 't':
1415 				return (T);
1416 
1417 			}
1418 			break;
1419 
1420 		default:
1421 			fatal("Unknown state in scanner.");
1422 		}
1423 		state = CMD;
1424 		return (LEXERR);
1425 	}
1426 }
1427 
1428 void
1429 upper(s)
1430 	char *s;
1431 {
1432 	char *p;
1433 
1434 	for (p = s; *p; p++) {
1435 		if (islower((unsigned char)*p))
1436 			*p = (char)toupper((unsigned char)*p);
1437 	}
1438 }
1439 
1440 static void
1441 help(ctab, s)
1442 	struct tab *ctab;
1443 	char *s;
1444 {
1445 	struct tab *c;
1446 	int width, NCMDS;
1447 	char *type;
1448 
1449 	if (ctab == sitetab)
1450 		type = "SITE ";
1451 	else
1452 		type = "";
1453 	width = 0, NCMDS = 0;
1454 	for (c = ctab; c->name != NULL; c++) {
1455 		int len = strlen(c->name);
1456 
1457 		if (len > width)
1458 			width = len;
1459 		NCMDS++;
1460 	}
1461 	width = (width + 8) &~ 7;
1462 	if (s == NULL) {
1463 		int i, j, w;
1464 		int columns, lines;
1465 
1466 		lreply(214, "The following %scommands are recognized %s.",
1467 		    type, "(* =>'s unimplemented)");
1468 		columns = 76 / width;
1469 		if (columns == 0)
1470 			columns = 1;
1471 		lines = (NCMDS + columns - 1) / columns;
1472 		for (i = 0; i < lines; i++) {
1473 			printf("   ");
1474 			for (j = 0; j < columns; j++) {
1475 				c = ctab + j * lines + i;
1476 				printf("%s%c", c->name,
1477 					c->implemented ? ' ' : '*');
1478 				if (c + lines >= &ctab[NCMDS])
1479 					break;
1480 				w = strlen(c->name) + 1;
1481 				while (w < width) {
1482 					putchar(' ');
1483 					w++;
1484 				}
1485 			}
1486 			printf("\r\n");
1487 		}
1488 		(void) fflush(stdout);
1489 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1490 		return;
1491 	}
1492 	upper(s);
1493 	c = lookup(ctab, s);
1494 	if (c == NULL) {
1495 		reply(502, "Unknown command %s.", s);
1496 		return;
1497 	}
1498 	if (c->implemented)
1499 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1500 	else
1501 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1502 		    c->name, c->help);
1503 }
1504 
1505 static void
1506 sizecmd(filename)
1507 	char *filename;
1508 {
1509 	switch (type) {
1510 	case TYPE_L:
1511 	case TYPE_I: {
1512 		struct stat stbuf;
1513 		if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1514 			reply(550, "%s: not a plain file.", filename);
1515 		else
1516 			reply(213, "%lld", (long long)stbuf.st_size);
1517 		break; }
1518 	case TYPE_A: {
1519 		FILE *fin;
1520 		int c;
1521 		off_t count;
1522 		struct stat stbuf;
1523 		fin = fopen(filename, "r");
1524 		if (fin == NULL) {
1525 			perror_reply(550, filename);
1526 			return;
1527 		}
1528 		if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1529 			reply(550, "%s: not a plain file.", filename);
1530 			(void) fclose(fin);
1531 			return;
1532 		}
1533 		if (stbuf.st_size > 10240) {
1534 			reply(550, "%s: file too large for SIZE.", filename);
1535 			(void) fclose(fin);
1536 			return;
1537 		}
1538 
1539 		count = 0;
1540 		while((c = getc(fin)) != EOF) {
1541 			if (c == '\n')	/* will get expanded to \r\n */
1542 				count++;
1543 			count++;
1544 		}
1545 		(void) fclose(fin);
1546 
1547 		reply(213, "%lld", (long long)count);
1548 		break; }
1549 	default:
1550 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1551 	}
1552 }
1553