xref: /openbsd/libexec/ftpd/ftpcmd.y (revision 73fe6daa)
1 /*	$OpenBSD: ftpcmd.y,v 1.75 2024/04/28 16:42:53 florian 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 					if (t == NULL) {
617 						/* invalid time, use epoch */
618 						stbuf.st_mtime = 0;
619 						t = gmtime(&stbuf.st_mtime);
620 					}
621 					reply(213,
622 					    "%04d%02d%02d%02d%02d%02d",
623 					    1900 + t->tm_year,
624 					    t->tm_mon+1, t->tm_mday,
625 					    t->tm_hour, t->tm_min, t->tm_sec);
626 				}
627 			}
628 			if ($4 != NULL)
629 				free($4);
630 		}
631 	| QUIT CRLF
632 		{
633 			reply(221, "Goodbye.");
634 			dologout(0);
635 		}
636 	| error
637 		{
638 			yyclearin;		/* discard lookahead data */
639 			yyerrok;		/* clear error condition */
640 			state = 0;		/* reset lexer state */
641 		}
642 	;
643 rcmd
644 	: RNFR check_login SP pathname CRLF
645 		{
646 			restart_point = 0;
647 			if ($2 && $4) {
648 				if (fromname)
649 					free(fromname);
650 				fromname = renamefrom($4);
651 				if (fromname == NULL)
652 					free($4);
653 			} else if ($4) {
654 				free ($4);
655 			}
656 		}
657 
658 	| REST check_login SP file_size CRLF
659 		{
660 			if ($2) {
661 				if (fromname) {
662 					free(fromname);
663 					fromname = NULL;
664 				}
665 				restart_point = $4;
666 				reply(350, "Restarting at %lld. %s",
667 				    (long long)restart_point,
668 				    "Send STORE or RETRIEVE to initiate transfer.");
669 			}
670 		}
671 	;
672 
673 username
674 	: STRING
675 	;
676 
677 password
678 	: /* empty */
679 		{
680 			$$ = calloc(1, sizeof(char));
681 		}
682 	| STRING
683 	;
684 
685 byte_size
686 	: NUMBER
687 	;
688 
689 file_size
690 	: NUMBER
691 		{
692 			$$ = $1;
693 		}
694 	| BIGNUM
695 		{
696 			$$ = $1;
697 		}
698 	;
699 
700 host_port
701 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
702 		NUMBER COMMA NUMBER
703 		{
704 			char *a, *p;
705 
706 			if ($1 < 0 || $1 > 255 || $3 < 0 || $3 > 255 ||
707 			    $5 < 0 || $5 > 255 || $7 < 0 || $7 > 255 ||
708 			    $9 < 0 || $9 > 255 || $11 < 0 || $11 > 255) {
709 				$$ = 1;
710 			} else {
711 				data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
712 				data_dest.su_sin.sin_family = AF_INET;
713 				p = (char *)&data_dest.su_sin.sin_port;
714 				p[0] = $9; p[1] = $11;
715 				a = (char *)&data_dest.su_sin.sin_addr;
716 				a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
717 				$$ = 0;
718 			}
719 		}
720 	;
721 
722 host_long_port4
723 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
724 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
725 		NUMBER
726 		{
727 			char *a, *p;
728 
729 			/* reject invalid LPRT command */
730 			if ($1 != 4 || $3 != 4 ||
731 			    $5 < 0 || $5 > 255 || $7 < 0 || $7 > 255 ||
732 			    $9 < 0 || $9 > 255 || $11 < 0 || $11 > 255 ||
733 			    $13 != 2 ||
734 			    $15 < 0 || $15 > 255 || $17 < 0 || $17 > 255) {
735 				$$ = 1;
736 			} else {
737 				data_dest.su_sin.sin_len =
738 					sizeof(struct sockaddr_in);
739 				data_dest.su_family = AF_INET;
740 				p = (char *)&data_dest.su_port;
741 				p[0] = $15; p[1] = $17;
742 				a = (char *)&data_dest.su_sin.sin_addr;
743 				a[0] = $5; a[1] = $7; a[2] = $9; a[3] = $11;
744 				$$ = 0;
745 			}
746 		}
747 	;
748 
749 host_long_port6
750 	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
751 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
752 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
753 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
754 		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
755 		NUMBER
756 		{
757 			char *a, *p;
758 
759 			/* reject invalid LPRT command */
760 			if ($1 != 6 || $3 != 16 ||
761 			    $5 < 0 || $5 > 255 || $7 < 0 || $7 > 255 ||
762 			    $9 < 0 || $9 > 255 || $11 < 0 || $11 > 255 ||
763 			    $13 < 0 || $13 > 255 || $15 < 0 || $15 > 255 ||
764 			    $17 < 0 || $17 > 255 || $19 < 0 || $19 > 255 ||
765 			    $21 < 0 || $21 > 255 || $23 < 0 || $23 > 255 ||
766 			    $25 < 0 || $25 > 255 || $27 < 0 || $27 > 255 ||
767 			    $29 < 0 || $29 > 255 || $31 < 0 || $31 > 255 ||
768 			    $33 < 0 || $33 > 255 || $35 < 0 || $35 > 255 ||
769 			    $37 != 2 ||
770 			    $39 < 0 || $39 > 255 || $41 < 0 || $41 > 255) {
771 				$$ = 1;
772 			} else {
773 				data_dest.su_sin6.sin6_len =
774 					sizeof(struct sockaddr_in6);
775 				data_dest.su_family = AF_INET6;
776 				p = (char *)&data_dest.su_port;
777 				p[0] = $39; p[1] = $41;
778 				a = (char *)&data_dest.su_sin6.sin6_addr;
779 				 a[0] =  $5;  a[1] =  $7;
780 				 a[2] =  $9;  a[3] = $11;
781 				 a[4] = $13;  a[5] = $15;
782 				 a[6] = $17;  a[7] = $19;
783 				 a[8] = $21;  a[9] = $23;
784 				a[10] = $25; a[11] = $27;
785 				a[12] = $29; a[13] = $31;
786 				a[14] = $33; a[15] = $35;
787 				if (his_addr.su_family == AF_INET6) {
788 					/* XXX more sanity checks! */
789 					data_dest.su_sin6.sin6_scope_id =
790 					    his_addr.su_sin6.sin6_scope_id;
791 				}
792 
793 				$$ = 0;
794 			}
795 		}
796 	;
797 
798 form_code
799 	: N
800 		{
801 			$$ = FORM_N;
802 		}
803 	| T
804 		{
805 			$$ = FORM_T;
806 		}
807 	| C
808 		{
809 			$$ = FORM_C;
810 		}
811 	;
812 
813 type_code
814 	: A
815 		{
816 			cmd_type = TYPE_A;
817 			cmd_form = FORM_N;
818 		}
819 	| A SP form_code
820 		{
821 			cmd_type = TYPE_A;
822 			cmd_form = $3;
823 		}
824 	| E
825 		{
826 			cmd_type = TYPE_E;
827 			cmd_form = FORM_N;
828 		}
829 	| E SP form_code
830 		{
831 			cmd_type = TYPE_E;
832 			cmd_form = $3;
833 		}
834 	| I
835 		{
836 			cmd_type = TYPE_I;
837 		}
838 	| L
839 		{
840 			cmd_type = TYPE_L;
841 			cmd_bytesz = 8;
842 		}
843 	| L SP byte_size
844 		{
845 			cmd_type = TYPE_L;
846 			cmd_bytesz = $3;
847 		}
848 		/* this is for a bug in the BBN ftp */
849 	| L byte_size
850 		{
851 			cmd_type = TYPE_L;
852 			cmd_bytesz = $2;
853 		}
854 	;
855 
856 struct_code
857 	: F
858 		{
859 			$$ = STRU_F;
860 		}
861 	| R
862 		{
863 			$$ = STRU_R;
864 		}
865 	| P
866 		{
867 			$$ = STRU_P;
868 		}
869 	;
870 
871 mode_code
872 	: S
873 		{
874 			$$ = MODE_S;
875 		}
876 	| B
877 		{
878 			$$ = MODE_B;
879 		}
880 	| C
881 		{
882 			$$ = MODE_C;
883 		}
884 	;
885 
886 pathname
887 	: pathstring
888 		{
889 			/*
890 			 * Problem: this production is used for all pathname
891 			 * processing, but only gives a 550 error reply.
892 			 * This is a valid reply in some cases but not in others.
893 			 */
894 			if (logged_in && $1 && strchr($1, '~') != NULL) {
895 				glob_t gl;
896 				int flags =
897 				 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
898 				char *pptr = $1;
899 
900 				/*
901 				 * glob() will only find a leading ~, but
902 				 * Netscape kindly puts a slash in front of
903 				 * it for publish URLs.  There needs to be
904 				 * a flag for glob() that expands tildes
905 				 * anywhere in the string.
906 				 */
907 				if ((pptr[0] == '/') && (pptr[1] == '~'))
908 					pptr++;
909 
910 				memset(&gl, 0, sizeof(gl));
911 				if (glob(pptr, flags, NULL, &gl) ||
912 				    gl.gl_pathc == 0) {
913 					reply(550, "not found");
914 					$$ = NULL;
915 				} else {
916 					$$ = strdup(gl.gl_pathv[0]);
917 				}
918 				globfree(&gl);
919 				free($1);
920 			} else
921 				$$ = $1;
922 		}
923 	;
924 
925 pathstring
926 	: STRING
927 	;
928 
929 octal_number
930 	: NUMBER
931 		{
932 			int ret, dec, multby, digit;
933 
934 			/*
935 			 * Convert a number that was read as decimal number
936 			 * to what it would be if it had been read as octal.
937 			 */
938 			dec = $1;
939 			multby = 1;
940 			ret = 0;
941 			while (dec) {
942 				digit = dec%10;
943 				if (digit > 7) {
944 					ret = -1;
945 					break;
946 				}
947 				ret += digit * multby;
948 				multby *= 8;
949 				dec /= 10;
950 			}
951 			$$ = ret;
952 		}
953 	;
954 
955 
956 check_login
957 	: /* empty */
958 		{
959 			if (logged_in)
960 				$$ = 1;
961 			else {
962 				reply(530, "Please login with USER and PASS.");
963 				$$ = 0;
964 				state = 0;
965 				YYABORT;
966 			}
967 		}
968 	;
969 
970 check_login_epsvall
971 	: /* empty */
972 		{
973 			if (!logged_in) {
974 				reply(530, "Please login with USER and PASS.");
975 				$$ = 0;
976 				state = 0;
977 				YYABORT;
978 			} else if (epsvall) {
979 				reply(501, "the command is disallowed "
980 				    "after EPSV ALL");
981 				usedefault = 1;
982 				$$ = 0;
983 			} else
984 				$$ = 1;
985 		}
986 	;
987 
988 %%
989 
990 #define	CMD	0	/* beginning of command */
991 #define	ARGS	1	/* expect miscellaneous arguments */
992 #define	STR1	2	/* expect SP followed by STRING */
993 #define	STR2	3	/* expect STRING */
994 #define	OSTR	4	/* optional SP then STRING */
995 #define	ZSTR1	5	/* SP then optional STRING */
996 #define	ZSTR2	6	/* optional STRING after SP */
997 #define	SITECMD	7	/* SITE command */
998 #define	NSTR	8	/* Number followed by a string */
999 
1000 struct tab {
1001 	char	*name;
1002 	short	token;
1003 	short	state;
1004 	short	implemented;	/* 1 if command is implemented */
1005 	char	*help;
1006 };
1007 
1008 struct tab cmdtab[] = {		/* In order defined in RFC 765 */
1009 	{ "USER", USER, STR1, 1,	"<sp> username" },
1010 	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
1011 	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
1012 	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
1013 	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
1014 	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1015 	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
1016 	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1017 	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
1018 	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
1019 	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
1020 	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1021 	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
1022 	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
1023 	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
1024 	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
1025 	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
1026 	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
1027 	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
1028 	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
1029 	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
1030 	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
1031 	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
1032 	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
1033 	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
1034 	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
1035 	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
1036 	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
1037 	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
1038 	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
1039 	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
1040 	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
1041 	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
1042 	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
1043 	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
1044 	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
1045 	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
1046 	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
1047 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1048 	{ "NOOP", NOOP, ARGS, 1,	"" },
1049 	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
1050 	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
1051 	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
1052 	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
1053 	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
1054 	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
1055 	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1056 	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1057 	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
1058 	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
1059 	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
1060 	{ NULL,   0,    0,    0,	0 }
1061 };
1062 
1063 struct tab sitetab[] = {
1064 	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
1065 	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
1066 	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
1067 	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1068 	{ NULL,   0,    0,    0,	0 }
1069 };
1070 
1071 static void	 help(struct tab *, char *);
1072 static struct tab *
1073 		 lookup(struct tab *, const char *);
1074 static void	 sizecmd(const char *);
1075 static int	 yylex(void);
1076 
1077 extern int epsvall;
1078 
1079 static struct tab *
lookup(struct tab * p,const char * cmd)1080 lookup(struct tab *p, const char *cmd)
1081 {
1082 
1083 	for (; p->name != NULL; p++)
1084 		if (strcmp(cmd, p->name) == 0)
1085 			return (p);
1086 	return (NULL);
1087 }
1088 
1089 #include <arpa/telnet.h>
1090 
1091 /*
1092  * get_line - a hacked up version of fgets to ignore TELNET escape codes.
1093  */
1094 int
get_line(char * s,int n)1095 get_line(char *s, int n)
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(stdin)) != EOF) {
1115 		c &= 0377;
1116 		if (c == IAC) {
1117 		    if ((c = getc(stdin)) != EOF) {
1118 			c &= 0377;
1119 			switch (c) {
1120 			case WILL:
1121 			case WONT:
1122 				c = getc(stdin);
1123 				printf("%c%c%c", IAC, DONT, 0377&c);
1124 				(void) fflush(stdout);
1125 				continue;
1126 			case DO:
1127 			case DONT:
1128 				c = getc(stdin);
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(stdin)) != 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 void
toolong(int signo)1179 toolong(int signo)
1180 {
1181 	struct syslog_data sdata = SYSLOG_DATA_INIT;
1182 
1183 	reply_r(421,
1184 	    "Timeout (%d seconds): closing control connection.", timeout);
1185 	if (logging)
1186 		syslog_r(LOG_INFO, &sdata, "User %s timed out after %d seconds",
1187 		    (pw ? pw -> pw_name : "unknown"), timeout);
1188 	dologout(1);
1189 }
1190 
1191 static int
yylex(void)1192 yylex(void)
1193 {
1194 	static int cpos;
1195 	char *cp, *cp2;
1196 	struct tab *p;
1197 	int n;
1198 	char c;
1199 
1200 	for (;;) {
1201 		switch (state) {
1202 
1203 		case CMD:
1204 			(void) alarm((unsigned) timeout);
1205 			n = get_line(cbuf, sizeof(cbuf)-1);
1206 			if (n == -1) {
1207 				reply(221, "You could at least say goodbye.");
1208 				dologout(0);
1209 			} else if (n == -2) {
1210 				reply(500, "Command too long.");
1211 				alarm(0);
1212 				continue;
1213 			}
1214 			(void) alarm(0);
1215 			if ((cp = strchr(cbuf, '\r'))) {
1216 				*cp++ = '\n';
1217 				*cp = '\0';
1218 			}
1219 			if (strncasecmp(cbuf, "PASS", 4) != 0) {
1220 				if ((cp = strpbrk(cbuf, "\n"))) {
1221 					c = *cp;
1222 					*cp = '\0';
1223 					setproctitle("%s: %s", proctitle, cbuf);
1224 					*cp = c;
1225 				}
1226 			}
1227 			if ((cp = strpbrk(cbuf, " \n")))
1228 				cpos = cp - cbuf;
1229 			if (cpos == 0)
1230 				cpos = 4;
1231 			c = cbuf[cpos];
1232 			cbuf[cpos] = '\0';
1233 			upper(cbuf);
1234 			p = lookup(cmdtab, cbuf);
1235 			cbuf[cpos] = c;
1236 			if (p != NULL) {
1237 				if (p->implemented == 0) {
1238 					nack(p->name);
1239 					return (LEXERR);
1240 				}
1241 				state = p->state;
1242 				yylval.s = p->name;
1243 				return (p->token);
1244 			}
1245 			break;
1246 
1247 		case SITECMD:
1248 			if (cbuf[cpos] == ' ') {
1249 				cpos++;
1250 				return (SP);
1251 			}
1252 			cp = &cbuf[cpos];
1253 			if ((cp2 = strpbrk(cp, " \n")))
1254 				cpos = cp2 - cbuf;
1255 			c = cbuf[cpos];
1256 			cbuf[cpos] = '\0';
1257 			upper(cp);
1258 			p = lookup(sitetab, cp);
1259 			cbuf[cpos] = c;
1260 			if (p != NULL) {
1261 				if (p->implemented == 0) {
1262 					state = CMD;
1263 					nack(p->name);
1264 					return (LEXERR);
1265 				}
1266 				state = p->state;
1267 				yylval.s = p->name;
1268 				return (p->token);
1269 			}
1270 			state = CMD;
1271 			break;
1272 
1273 		case OSTR:
1274 			if (cbuf[cpos] == '\n') {
1275 				state = CMD;
1276 				return (CRLF);
1277 			}
1278 			/* FALLTHROUGH */
1279 
1280 		case STR1:
1281 		case ZSTR1:
1282 		dostr1:
1283 			if (cbuf[cpos] == ' ') {
1284 				cpos++;
1285 				state = state == OSTR ? STR2 : state+1;
1286 				return (SP);
1287 			}
1288 			break;
1289 
1290 		case ZSTR2:
1291 			if (cbuf[cpos] == '\n') {
1292 				state = CMD;
1293 				return (CRLF);
1294 			}
1295 			/* FALLTHROUGH */
1296 
1297 		case STR2:
1298 			cp = &cbuf[cpos];
1299 			n = strlen(cp);
1300 			cpos += n - 1;
1301 			/*
1302 			 * Make sure the string is nonempty and \n terminated.
1303 			 */
1304 			if (n > 1 && cbuf[cpos] == '\n') {
1305 				cbuf[cpos] = '\0';
1306 				yylval.s = strdup(cp);
1307 				if (yylval.s == NULL)
1308 					fatal("Ran out of memory.");
1309 				cbuf[cpos] = '\n';
1310 				state = ARGS;
1311 				return (STRING);
1312 			}
1313 			break;
1314 
1315 		case NSTR:
1316 			if (cbuf[cpos] == ' ') {
1317 				cpos++;
1318 				return (SP);
1319 			}
1320 			if (isdigit((unsigned char)cbuf[cpos])) {
1321 				cp = &cbuf[cpos];
1322 				while (isdigit((unsigned char)cbuf[++cpos]))
1323 					;
1324 				c = cbuf[cpos];
1325 				cbuf[cpos] = '\0';
1326 				yylval.i = atoi(cp);
1327 				cbuf[cpos] = c;
1328 				state = STR1;
1329 				return (NUMBER);
1330 			}
1331 			state = STR1;
1332 			goto dostr1;
1333 
1334 		case ARGS:
1335 			if (isdigit((unsigned char)cbuf[cpos])) {
1336 				long long llval;
1337 
1338 				cp = &cbuf[cpos];
1339 				errno = 0;
1340 				llval = strtoll(cp, &cp2, 10);
1341 				if (llval < 0 ||
1342 				    (errno == ERANGE && llval == LLONG_MAX))
1343 					break;
1344 
1345 				cpos = (int)(cp2 - cbuf);
1346 				if (llval > INT_MAX) {
1347 					yylval.o = llval;
1348 					return (BIGNUM);
1349 				} else {
1350 					yylval.i = (int)llval;
1351 					return (NUMBER);
1352 				}
1353 			}
1354 			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0 &&
1355 			    !isalnum((unsigned char)cbuf[cpos + 3])) {
1356 				cpos += 3;
1357 				return ALL;
1358 			}
1359 			switch (cbuf[cpos++]) {
1360 
1361 			case '\n':
1362 				state = CMD;
1363 				return (CRLF);
1364 
1365 			case ' ':
1366 				return (SP);
1367 
1368 			case ',':
1369 				return (COMMA);
1370 
1371 			case 'A':
1372 			case 'a':
1373 				return (A);
1374 
1375 			case 'B':
1376 			case 'b':
1377 				return (B);
1378 
1379 			case 'C':
1380 			case 'c':
1381 				return (C);
1382 
1383 			case 'E':
1384 			case 'e':
1385 				return (E);
1386 
1387 			case 'F':
1388 			case 'f':
1389 				return (F);
1390 
1391 			case 'I':
1392 			case 'i':
1393 				return (I);
1394 
1395 			case 'L':
1396 			case 'l':
1397 				return (L);
1398 
1399 			case 'N':
1400 			case 'n':
1401 				return (N);
1402 
1403 			case 'P':
1404 			case 'p':
1405 				return (P);
1406 
1407 			case 'R':
1408 			case 'r':
1409 				return (R);
1410 
1411 			case 'S':
1412 			case 's':
1413 				return (S);
1414 
1415 			case 'T':
1416 			case 't':
1417 				return (T);
1418 
1419 			}
1420 			break;
1421 
1422 		default:
1423 			fatal("Unknown state in scanner.");
1424 		}
1425 		state = CMD;
1426 		return (LEXERR);
1427 	}
1428 }
1429 
1430 void
upper(char * s)1431 upper(char *s)
1432 {
1433 	char *p;
1434 
1435 	for (p = s; *p; p++)
1436 		*p = (char)toupper((unsigned char)*p);
1437 }
1438 
1439 static void
help(struct tab * ctab,char * s)1440 help(struct tab *ctab, char *s)
1441 {
1442 	struct tab *c;
1443 	int width, NCMDS;
1444 	char *type;
1445 
1446 	if (ctab == sitetab)
1447 		type = "SITE ";
1448 	else
1449 		type = "";
1450 	width = 0, NCMDS = 0;
1451 	for (c = ctab; c->name != NULL; c++) {
1452 		int len = strlen(c->name);
1453 
1454 		if (len > width)
1455 			width = len;
1456 		NCMDS++;
1457 	}
1458 	width = (width + 8) &~ 7;
1459 	if (s == NULL) {
1460 		int i, j, w;
1461 		int columns, lines;
1462 
1463 		lreply(214, "The following %scommands are recognized %s.",
1464 		    type, "(* =>'s unimplemented)");
1465 		columns = 76 / width;
1466 		if (columns == 0)
1467 			columns = 1;
1468 		lines = (NCMDS + columns - 1) / columns;
1469 		for (i = 0; i < lines; i++) {
1470 			printf("   ");
1471 			for (j = 0; j < columns; j++) {
1472 				c = ctab + j * lines + i;
1473 				printf("%s%c", c->name,
1474 					c->implemented ? ' ' : '*');
1475 				if (c + lines >= &ctab[NCMDS])
1476 					break;
1477 				w = strlen(c->name) + 1;
1478 				while (w < width) {
1479 					putchar(' ');
1480 					w++;
1481 				}
1482 			}
1483 			printf("\r\n");
1484 		}
1485 		(void) fflush(stdout);
1486 		reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1487 		return;
1488 	}
1489 	upper(s);
1490 	c = lookup(ctab, s);
1491 	if (c == NULL) {
1492 		reply(502, "Unknown command %s.", s);
1493 		return;
1494 	}
1495 	if (c->implemented)
1496 		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1497 	else
1498 		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1499 		    c->name, c->help);
1500 }
1501 
1502 static void
sizecmd(const char * filename)1503 sizecmd(const char *filename)
1504 {
1505 	switch (type) {
1506 	case TYPE_L:
1507 	case TYPE_I: {
1508 		struct stat stbuf;
1509 		if (stat(filename, &stbuf) == -1 || !S_ISREG(stbuf.st_mode))
1510 			reply(550, "%s: not a plain file.", filename);
1511 		else
1512 			reply(213, "%lld", (long long)stbuf.st_size);
1513 		break; }
1514 	case TYPE_A: {
1515 		FILE *fin;
1516 		int c;
1517 		off_t count;
1518 		struct stat stbuf;
1519 		fin = fopen(filename, "r");
1520 		if (fin == NULL) {
1521 			perror_reply(550, filename);
1522 			return;
1523 		}
1524 		if (fstat(fileno(fin), &stbuf) == -1 || !S_ISREG(stbuf.st_mode)) {
1525 			reply(550, "%s: not a plain file.", filename);
1526 			(void) fclose(fin);
1527 			return;
1528 		}
1529 		if (stbuf.st_size > 10240) {
1530 			reply(550, "%s: file too large for SIZE.", filename);
1531 			(void) fclose(fin);
1532 			return;
1533 		}
1534 
1535 		count = 0;
1536 		while((c = getc(fin)) != EOF) {
1537 			if (c == '\n')	/* will get expanded to \r\n */
1538 				count++;
1539 			count++;
1540 		}
1541 		(void) fclose(fin);
1542 
1543 		reply(213, "%lld", (long long)count);
1544 		break; }
1545 	default:
1546 		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1547 	}
1548 }
1549