GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: libexec/ftpd/ftpcmd.y Lines: 0 247 0.0 %
Date: 2017-11-07 Branches: 0 222 0.0 %

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