xref: /openbsd/libexec/ftpd/ftpcmd.y (revision 73471bf0)
1 /*	$OpenBSD: ftpcmd.y,v 1.73 2021/05/31 16:18:01 jan 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 *, const char *);
1069 static void	 sizecmd(const char *);
1070 static int	 yylex(void);
1071 
1072 extern int epsvall;
1073 
1074 static struct tab *
1075 lookup(struct tab *p, const char *cmd)
1076 {
1077 
1078 	for (; p->name != NULL; p++)
1079 		if (strcmp(cmd, p->name) == 0)
1080 			return (p);
1081 	return (NULL);
1082 }
1083 
1084 #include <arpa/telnet.h>
1085 
1086 /*
1087  * get_line - a hacked up version of fgets to ignore TELNET escape codes.
1088  */
1089 int
1090 get_line(char *s, int n)
1091 {
1092 	int c;
1093 	char *cs;
1094 
1095 	cs = s;
1096 /* tmpline may contain saved command from urgent mode interruption */
1097 	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1098 		*cs++ = tmpline[c];
1099 		if (tmpline[c] == '\n') {
1100 			*cs++ = '\0';
1101 			if (debug)
1102 				syslog(LOG_DEBUG, "command: %s", s);
1103 			tmpline[0] = '\0';
1104 			return(0);
1105 		}
1106 		if (c == 0)
1107 			tmpline[0] = '\0';
1108 	}
1109 	while ((c = getc(stdin)) != EOF) {
1110 		c &= 0377;
1111 		if (c == IAC) {
1112 		    if ((c = getc(stdin)) != EOF) {
1113 			c &= 0377;
1114 			switch (c) {
1115 			case WILL:
1116 			case WONT:
1117 				c = getc(stdin);
1118 				printf("%c%c%c", IAC, DONT, 0377&c);
1119 				(void) fflush(stdout);
1120 				continue;
1121 			case DO:
1122 			case DONT:
1123 				c = getc(stdin);
1124 				printf("%c%c%c", IAC, WONT, 0377&c);
1125 				(void) fflush(stdout);
1126 				continue;
1127 			case IAC:
1128 				break;
1129 			default:
1130 				continue;	/* ignore command */
1131 			}
1132 		    }
1133 		}
1134 		*cs++ = c;
1135 		if (--n <= 0) {
1136 			/*
1137 			 * If command doesn't fit into buffer, discard the
1138 			 * rest of the command and indicate truncation.
1139 			 * This prevents the command to be split up into
1140 			 * multiple commands.
1141 			 */
1142 			while (c != '\n' && (c = getc(stdin)) != EOF)
1143 				;
1144 			return (-2);
1145 		}
1146 		if (c == '\n')
1147 			break;
1148 	}
1149 	if (c == EOF && cs == s)
1150 		return (-1);
1151 	*cs++ = '\0';
1152 	if (debug) {
1153 		if (!guest && strncasecmp("pass ", s, 5) == 0) {
1154 			/* Don't syslog passwords */
1155 			syslog(LOG_DEBUG, "command: %.5s ???", s);
1156 		} else {
1157 			char *cp;
1158 			int len;
1159 
1160 			/* Don't syslog trailing CR-LF */
1161 			len = strlen(s);
1162 			cp = s + len - 1;
1163 			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1164 				--cp;
1165 				--len;
1166 			}
1167 			syslog(LOG_DEBUG, "command: %.*s", len, s);
1168 		}
1169 	}
1170 	return (0);
1171 }
1172 
1173 /*ARGSUSED*/
1174 void
1175 toolong(int signo)
1176 {
1177 	struct syslog_data sdata = SYSLOG_DATA_INIT;
1178 
1179 	reply_r(421,
1180 	    "Timeout (%d seconds): closing control connection.", timeout);
1181 	if (logging)
1182 		syslog_r(LOG_INFO, &sdata, "User %s timed out after %d seconds",
1183 		    (pw ? pw -> pw_name : "unknown"), timeout);
1184 	dologout(1);
1185 }
1186 
1187 static int
1188 yylex(void)
1189 {
1190 	static int cpos;
1191 	char *cp, *cp2;
1192 	struct tab *p;
1193 	int n;
1194 	char c;
1195 
1196 	for (;;) {
1197 		switch (state) {
1198 
1199 		case CMD:
1200 			(void) alarm((unsigned) timeout);
1201 			n = get_line(cbuf, sizeof(cbuf)-1);
1202 			if (n == -1) {
1203 				reply(221, "You could at least say goodbye.");
1204 				dologout(0);
1205 			} else if (n == -2) {
1206 				reply(500, "Command too long.");
1207 				alarm(0);
1208 				continue;
1209 			}
1210 			(void) alarm(0);
1211 			if ((cp = strchr(cbuf, '\r'))) {
1212 				*cp++ = '\n';
1213 				*cp = '\0';
1214 			}
1215 			if (strncasecmp(cbuf, "PASS", 4) != 0) {
1216 				if ((cp = strpbrk(cbuf, "\n"))) {
1217 					c = *cp;
1218 					*cp = '\0';
1219 					setproctitle("%s: %s", proctitle, cbuf);
1220 					*cp = c;
1221 				}
1222 			}
1223 			if ((cp = strpbrk(cbuf, " \n")))
1224 				cpos = cp - cbuf;
1225 			if (cpos == 0)
1226 				cpos = 4;
1227 			c = cbuf[cpos];
1228 			cbuf[cpos] = '\0';
1229 			upper(cbuf);
1230 			p = lookup(cmdtab, cbuf);
1231 			cbuf[cpos] = c;
1232 			if (p != NULL) {
1233 				if (p->implemented == 0) {
1234 					nack(p->name);
1235 					return (LEXERR);
1236 				}
1237 				state = p->state;
1238 				yylval.s = p->name;
1239 				return (p->token);
1240 			}
1241 			break;
1242 
1243 		case SITECMD:
1244 			if (cbuf[cpos] == ' ') {
1245 				cpos++;
1246 				return (SP);
1247 			}
1248 			cp = &cbuf[cpos];
1249 			if ((cp2 = strpbrk(cp, " \n")))
1250 				cpos = cp2 - cbuf;
1251 			c = cbuf[cpos];
1252 			cbuf[cpos] = '\0';
1253 			upper(cp);
1254 			p = lookup(sitetab, cp);
1255 			cbuf[cpos] = c;
1256 			if (p != NULL) {
1257 				if (p->implemented == 0) {
1258 					state = CMD;
1259 					nack(p->name);
1260 					return (LEXERR);
1261 				}
1262 				state = p->state;
1263 				yylval.s = p->name;
1264 				return (p->token);
1265 			}
1266 			state = CMD;
1267 			break;
1268 
1269 		case OSTR:
1270 			if (cbuf[cpos] == '\n') {
1271 				state = CMD;
1272 				return (CRLF);
1273 			}
1274 			/* FALLTHROUGH */
1275 
1276 		case STR1:
1277 		case ZSTR1:
1278 		dostr1:
1279 			if (cbuf[cpos] == ' ') {
1280 				cpos++;
1281 				state = state == OSTR ? STR2 : state+1;
1282 				return (SP);
1283 			}
1284 			break;
1285 
1286 		case ZSTR2:
1287 			if (cbuf[cpos] == '\n') {
1288 				state = CMD;
1289 				return (CRLF);
1290 			}
1291 			/* FALLTHROUGH */
1292 
1293 		case STR2:
1294 			cp = &cbuf[cpos];
1295 			n = strlen(cp);
1296 			cpos += n - 1;
1297 			/*
1298 			 * Make sure the string is nonempty and \n terminated.
1299 			 */
1300 			if (n > 1 && cbuf[cpos] == '\n') {
1301 				cbuf[cpos] = '\0';
1302 				yylval.s = strdup(cp);
1303 				if (yylval.s == NULL)
1304 					fatal("Ran out of memory.");
1305 				cbuf[cpos] = '\n';
1306 				state = ARGS;
1307 				return (STRING);
1308 			}
1309 			break;
1310 
1311 		case NSTR:
1312 			if (cbuf[cpos] == ' ') {
1313 				cpos++;
1314 				return (SP);
1315 			}
1316 			if (isdigit((unsigned char)cbuf[cpos])) {
1317 				cp = &cbuf[cpos];
1318 				while (isdigit((unsigned char)cbuf[++cpos]))
1319 					;
1320 				c = cbuf[cpos];
1321 				cbuf[cpos] = '\0';
1322 				yylval.i = atoi(cp);
1323 				cbuf[cpos] = c;
1324 				state = STR1;
1325 				return (NUMBER);
1326 			}
1327 			state = STR1;
1328 			goto dostr1;
1329 
1330 		case ARGS:
1331 			if (isdigit((unsigned char)cbuf[cpos])) {
1332 				long long llval;
1333 
1334 				cp = &cbuf[cpos];
1335 				errno = 0;
1336 				llval = strtoll(cp, &cp2, 10);
1337 				if (llval < 0 ||
1338 				    (errno == ERANGE && llval == LLONG_MAX))
1339 					break;
1340 
1341 				cpos = (int)(cp2 - cbuf);
1342 				if (llval > INT_MAX) {
1343 					yylval.o = llval;
1344 					return (BIGNUM);
1345 				} else {
1346 					yylval.i = (int)llval;
1347 					return (NUMBER);
1348 				}
1349 			}
1350 			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0 &&
1351 			    !isalnum((unsigned char)cbuf[cpos + 3])) {
1352 				cpos += 3;
1353 				return ALL;
1354 			}
1355 			switch (cbuf[cpos++]) {
1356 
1357 			case '\n':
1358 				state = CMD;
1359 				return (CRLF);
1360 
1361 			case ' ':
1362 				return (SP);
1363 
1364 			case ',':
1365 				return (COMMA);
1366 
1367 			case 'A':
1368 			case 'a':
1369 				return (A);
1370 
1371 			case 'B':
1372 			case 'b':
1373 				return (B);
1374 
1375 			case 'C':
1376 			case 'c':
1377 				return (C);
1378 
1379 			case 'E':
1380 			case 'e':
1381 				return (E);
1382 
1383 			case 'F':
1384 			case 'f':
1385 				return (F);
1386 
1387 			case 'I':
1388 			case 'i':
1389 				return (I);
1390 
1391 			case 'L':
1392 			case 'l':
1393 				return (L);
1394 
1395 			case 'N':
1396 			case 'n':
1397 				return (N);
1398 
1399 			case 'P':
1400 			case 'p':
1401 				return (P);
1402 
1403 			case 'R':
1404 			case 'r':
1405 				return (R);
1406 
1407 			case 'S':
1408 			case 's':
1409 				return (S);
1410 
1411 			case 'T':
1412 			case 't':
1413 				return (T);
1414 
1415 			}
1416 			break;
1417 
1418 		default:
1419 			fatal("Unknown state in scanner.");
1420 		}
1421 		state = CMD;
1422 		return (LEXERR);
1423 	}
1424 }
1425 
1426 void
1427 upper(char *s)
1428 {
1429 	char *p;
1430 
1431 	for (p = s; *p; p++)
1432 		*p = (char)toupper((unsigned char)*p);
1433 }
1434 
1435 static void
1436 help(struct tab *ctab, char *s)
1437 {
1438 	struct tab *c;
1439 	int width, NCMDS;
1440 	char *type;
1441 
1442 	if (ctab == sitetab)
1443 		type = "SITE ";
1444 	else
1445 		type = "";
1446 	width = 0, NCMDS = 0;
1447 	for (c = ctab; c->name != NULL; c++) {
1448 		int len = strlen(c->name);
1449 
1450 		if (len > width)
1451 			width = len;
1452 		NCMDS++;
1453 	}
1454 	width = (width + 8) &~ 7;
1455 	if (s == NULL) {
1456 		int i, j, w;
1457 		int columns, lines;
1458 
1459 		lreply(214, "The following %scommands are recognized %s.",
1460 		    type, "(* =>'s unimplemented)");
1461 		columns = 76 / width;
1462 		if (columns == 0)
1463 			columns = 1;
1464 		lines = (NCMDS + columns - 1) / columns;
1465 		for (i = 0; i < lines; i++) {
1466 			printf("   ");
1467 			for (j = 0; j < columns; j++) {
1468 				c = ctab + j * lines + i;
1469 				printf("%s%c", c->name,
1470 					c->implemented ? ' ' : '*');
1471 				if (c + lines >= &ctab[NCMDS])
1472 					break;
1473 				w = strlen(c->name) + 1;
1474 				while (w < width) {
1475 					putchar(' ');
1476 					w++;
1477 				}
1478 			}
1479 			printf("\r\n");
1480 		}
1481 		(void) fflush(stdout);
1482 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1483 		return;
1484 	}
1485 	upper(s);
1486 	c = lookup(ctab, s);
1487 	if (c == NULL) {
1488 		reply(502, "Unknown command %s.", s);
1489 		return;
1490 	}
1491 	if (c->implemented)
1492 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1493 	else
1494 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1495 		    c->name, c->help);
1496 }
1497 
1498 static void
1499 sizecmd(const char *filename)
1500 {
1501 	switch (type) {
1502 	case TYPE_L:
1503 	case TYPE_I: {
1504 		struct stat stbuf;
1505 		if (stat(filename, &stbuf) == -1 || !S_ISREG(stbuf.st_mode))
1506 			reply(550, "%s: not a plain file.", filename);
1507 		else
1508 			reply(213, "%lld", (long long)stbuf.st_size);
1509 		break; }
1510 	case TYPE_A: {
1511 		FILE *fin;
1512 		int c;
1513 		off_t count;
1514 		struct stat stbuf;
1515 		fin = fopen(filename, "r");
1516 		if (fin == NULL) {
1517 			perror_reply(550, filename);
1518 			return;
1519 		}
1520 		if (fstat(fileno(fin), &stbuf) == -1 || !S_ISREG(stbuf.st_mode)) {
1521 			reply(550, "%s: not a plain file.", filename);
1522 			(void) fclose(fin);
1523 			return;
1524 		}
1525 		if (stbuf.st_size > 10240) {
1526 			reply(550, "%s: file too large for SIZE.", filename);
1527 			(void) fclose(fin);
1528 			return;
1529 		}
1530 
1531 		count = 0;
1532 		while((c = getc(fin)) != EOF) {
1533 			if (c == '\n')	/* will get expanded to \r\n */
1534 				count++;
1535 			count++;
1536 		}
1537 		(void) fclose(fin);
1538 
1539 		reply(213, "%lld", (long long)count);
1540 		break; }
1541 	default:
1542 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1543 	}
1544 }
1545