xref: /freebsd/libexec/ftpd/ftpcmd.y (revision 10b59a9b4add0320d52c15ce057dd697261e7dfc)
1  /*
2   * Copyright (c) 1985, 1988, 1993, 1994
3   *	The Regents of the University of California.  All rights reserved.
4   *
5   * Redistribution and use in source and binary forms, with or without
6   * modification, are permitted provided that the following conditions
7   * are met:
8   * 1. Redistributions of source code must retain the above copyright
9   *    notice, this list of conditions and the following disclaimer.
10   * 2. Redistributions in binary form must reproduce the above copyright
11   *    notice, this list of conditions and the following disclaimer in the
12   *    documentation and/or other materials provided with the distribution.
13   * 3. All advertising materials mentioning features or use of this software
14   *    must display the following acknowledgement:
15   *	This product includes software developed by the University of
16   *	California, Berkeley and its contributors.
17   * 4. Neither the name of the University nor the names of its contributors
18   *    may be used to endorse or promote products derived from this software
19   *    without specific prior written permission.
20   *
21   * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24   * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27   * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31   * SUCH DAMAGE.
32   *
33   *	@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94
34   */
35  
36  /*
37   * Grammar for FTP commands.
38   * See RFC 959.
39   */
40  
41  %{
42  
43  #ifndef lint
44  #if 0
45  static char sccsid[] = "@(#)ftpcmd.y	8.3 (Berkeley) 4/6/94";
46  #endif
47  #endif /* not lint */
48  
49  #include <sys/cdefs.h>
50  __FBSDID("$FreeBSD$");
51  
52  #include <sys/param.h>
53  #include <sys/socket.h>
54  #include <sys/stat.h>
55  
56  #include <netinet/in.h>
57  #include <arpa/ftp.h>
58  
59  #include <ctype.h>
60  #include <errno.h>
61  #include <glob.h>
62  #include <libutil.h>
63  #include <limits.h>
64  #include <md5.h>
65  #include <netdb.h>
66  #include <pwd.h>
67  #include <signal.h>
68  #include <stdint.h>
69  #include <stdio.h>
70  #include <stdlib.h>
71  #include <string.h>
72  #include <syslog.h>
73  #include <time.h>
74  #include <unistd.h>
75  
76  #include "extern.h"
77  #include "pathnames.h"
78  
79  off_t	restart_point;
80  
81  static	int cmd_type;
82  static	int cmd_form;
83  static	int cmd_bytesz;
84  static	int state;
85  char	cbuf[512];
86  char	*fromname = NULL;
87  
88  %}
89  
90  %union {
91  	struct {
92  		off_t	o;
93  		int	i;
94  	} u;
95  	char   *s;
96  }
97  
98  %token
99  	A	B	C	E	F	I
100  	L	N	P	R	S	T
101  	ALL
102  
103  	SP	CRLF	COMMA
104  
105  	USER	PASS	ACCT	REIN	QUIT	PORT
106  	PASV	TYPE	STRU	MODE	RETR	STOR
107  	APPE	MLFL	MAIL	MSND	MSOM	MSAM
108  	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
109  	ABOR	DELE	CWD	LIST	NLST	SITE
110  	STAT	HELP	NOOP	MKD	RMD	PWD
111  	CDUP	STOU	SMNT	SYST	SIZE	MDTM
112  	LPRT	LPSV	EPRT	EPSV	FEAT
113  
114  	UMASK	IDLE	CHMOD	MDFIVE
115  
116  	LEXERR	NOTIMPL
117  
118  %token	<s> STRING
119  %token	<u> NUMBER
120  
121  %type	<u.i> check_login octal_number byte_size
122  %type	<u.i> check_login_ro check_login_epsv
123  %type	<u.i> struct_code mode_code type_code form_code
124  %type	<s> pathstring pathname password username
125  %type	<s> ALL NOTIMPL
126  
127  %start	cmd_list
128  
129  %%
130  
131  cmd_list
132  	: /* empty */
133  	| cmd_list cmd
134  		{
135  			if (fromname)
136  				free(fromname);
137  			fromname = NULL;
138  			restart_point = 0;
139  		}
140  	| cmd_list rcmd
141  	;
142  
143  cmd
144  	: USER SP username CRLF
145  		{
146  			user($3);
147  			free($3);
148  		}
149  	| PASS SP password CRLF
150  		{
151  			pass($3);
152  			free($3);
153  		}
154  	| PASS CRLF
155  		{
156  			pass("");
157  		}
158  	| PORT check_login SP host_port CRLF
159  		{
160  			if (epsvall) {
161  				reply(501, "No PORT allowed after EPSV ALL.");
162  				goto port_done;
163  			}
164  			if (!$2)
165  				goto port_done;
166  			if (port_check("PORT") == 1)
167  				goto port_done;
168  #ifdef INET6
169  			if ((his_addr.su_family != AF_INET6 ||
170  			     !IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))) {
171  				/* shoud never happen */
172  				usedefault = 1;
173  				reply(500, "Invalid address rejected.");
174  				goto port_done;
175  			}
176  			port_check_v6("pcmd");
177  #endif
178  		port_done:
179  			;
180  		}
181  	| LPRT check_login SP host_long_port CRLF
182  		{
183  			if (epsvall) {
184  				reply(501, "No LPRT allowed after EPSV ALL.");
185  				goto lprt_done;
186  			}
187  			if (!$2)
188  				goto lprt_done;
189  			if (port_check("LPRT") == 1)
190  				goto lprt_done;
191  #ifdef INET6
192  			if (his_addr.su_family != AF_INET6) {
193  				usedefault = 1;
194  				reply(500, "Invalid address rejected.");
195  				goto lprt_done;
196  			}
197  			if (port_check_v6("LPRT") == 1)
198  				goto lprt_done;
199  #endif
200  		lprt_done:
201  			;
202  		}
203  	| EPRT check_login SP STRING CRLF
204  		{
205  			char delim;
206  			char *tmp = NULL;
207  			char *p, *q;
208  			char *result[3];
209  			struct addrinfo hints;
210  			struct addrinfo *res;
211  			int i;
212  
213  			if (epsvall) {
214  				reply(501, "No EPRT allowed after EPSV ALL.");
215  				goto eprt_done;
216  			}
217  			if (!$2)
218  				goto eprt_done;
219  
220  			memset(&data_dest, 0, sizeof(data_dest));
221  			tmp = strdup($4);
222  			if (ftpdebug)
223  				syslog(LOG_DEBUG, "%s", tmp);
224  			if (!tmp) {
225  				fatalerror("not enough core");
226  				/*NOTREACHED*/
227  			}
228  			p = tmp;
229  			delim = p[0];
230  			p++;
231  			memset(result, 0, sizeof(result));
232  			for (i = 0; i < 3; i++) {
233  				q = strchr(p, delim);
234  				if (!q || *q != delim) {
235  		parsefail:
236  					reply(500,
237  						"Invalid argument, rejected.");
238  					if (tmp)
239  						free(tmp);
240  					usedefault = 1;
241  					goto eprt_done;
242  				}
243  				*q++ = '\0';
244  				result[i] = p;
245  				if (ftpdebug)
246  					syslog(LOG_DEBUG, "%d: %s", i, p);
247  				p = q;
248  			}
249  
250  			/* some more sanity check */
251  			p = result[0];
252  			while (*p) {
253  				if (!isdigit(*p))
254  					goto parsefail;
255  				p++;
256  			}
257  			p = result[2];
258  			while (*p) {
259  				if (!isdigit(*p))
260  					goto parsefail;
261  				p++;
262  			}
263  
264  			/* grab address */
265  			memset(&hints, 0, sizeof(hints));
266  			if (atoi(result[0]) == 1)
267  				hints.ai_family = PF_INET;
268  #ifdef INET6
269  			else if (atoi(result[0]) == 2)
270  				hints.ai_family = PF_INET6;
271  #endif
272  			else
273  				hints.ai_family = PF_UNSPEC;	/*XXX*/
274  			hints.ai_socktype = SOCK_STREAM;
275  			i = getaddrinfo(result[1], result[2], &hints, &res);
276  			if (i)
277  				goto parsefail;
278  			memcpy(&data_dest, res->ai_addr, res->ai_addrlen);
279  #ifdef INET6
280  			if (his_addr.su_family == AF_INET6
281  			    && data_dest.su_family == AF_INET6) {
282  				/* XXX more sanity checks! */
283  				data_dest.su_sin6.sin6_scope_id =
284  					his_addr.su_sin6.sin6_scope_id;
285  			}
286  #endif
287  			free(tmp);
288  			tmp = NULL;
289  
290  			if (port_check("EPRT") == 1)
291  				goto eprt_done;
292  #ifdef INET6
293  			if (his_addr.su_family != AF_INET6) {
294  				usedefault = 1;
295  				reply(500, "Invalid address rejected.");
296  				goto eprt_done;
297  			}
298  			if (port_check_v6("EPRT") == 1)
299  				goto eprt_done;
300  #endif
301  		eprt_done:
302  			free($4);
303  		}
304  	| PASV check_login CRLF
305  		{
306  			if (epsvall)
307  				reply(501, "No PASV allowed after EPSV ALL.");
308  			else if ($2)
309  				passive();
310  		}
311  	| LPSV check_login CRLF
312  		{
313  			if (epsvall)
314  				reply(501, "No LPSV allowed after EPSV ALL.");
315  			else if ($2)
316  				long_passive("LPSV", PF_UNSPEC);
317  		}
318  	| EPSV check_login_epsv SP NUMBER CRLF
319  		{
320  			if ($2) {
321  				int pf;
322  				switch ($4.i) {
323  				case 1:
324  					pf = PF_INET;
325  					break;
326  #ifdef INET6
327  				case 2:
328  					pf = PF_INET6;
329  					break;
330  #endif
331  				default:
332  					pf = -1;	/*junk value*/
333  					break;
334  				}
335  				long_passive("EPSV", pf);
336  			}
337  		}
338  	| EPSV check_login_epsv SP ALL CRLF
339  		{
340  			if ($2) {
341  				reply(200, "EPSV ALL command successful.");
342  				epsvall++;
343  			}
344  		}
345  	| EPSV check_login_epsv CRLF
346  		{
347  			if ($2)
348  				long_passive("EPSV", PF_UNSPEC);
349  		}
350  	| TYPE check_login SP type_code CRLF
351  		{
352  			if ($2) {
353  				switch (cmd_type) {
354  
355  				case TYPE_A:
356  					if (cmd_form == FORM_N) {
357  						reply(200, "Type set to A.");
358  						type = cmd_type;
359  						form = cmd_form;
360  					} else
361  						reply(504, "Form must be N.");
362  					break;
363  
364  				case TYPE_E:
365  					reply(504, "Type E not implemented.");
366  					break;
367  
368  				case TYPE_I:
369  					reply(200, "Type set to I.");
370  					type = cmd_type;
371  					break;
372  
373  				case TYPE_L:
374  #if CHAR_BIT == 8
375  					if (cmd_bytesz == 8) {
376  						reply(200,
377  						    "Type set to L (byte size 8).");
378  						type = cmd_type;
379  					} else
380  						reply(504, "Byte size must be 8.");
381  #else /* CHAR_BIT == 8 */
382  					UNIMPLEMENTED for CHAR_BIT != 8
383  #endif /* CHAR_BIT == 8 */
384  				}
385  			}
386  		}
387  	| STRU check_login SP struct_code CRLF
388  		{
389  			if ($2) {
390  				switch ($4) {
391  
392  				case STRU_F:
393  					reply(200, "STRU F accepted.");
394  					break;
395  
396  				default:
397  					reply(504, "Unimplemented STRU type.");
398  				}
399  			}
400  		}
401  	| MODE check_login SP mode_code CRLF
402  		{
403  			if ($2) {
404  				switch ($4) {
405  
406  				case MODE_S:
407  					reply(200, "MODE S accepted.");
408  					break;
409  
410  				default:
411  					reply(502, "Unimplemented MODE type.");
412  				}
413  			}
414  		}
415  	| ALLO check_login SP NUMBER CRLF
416  		{
417  			if ($2) {
418  				reply(202, "ALLO command ignored.");
419  			}
420  		}
421  	| ALLO check_login SP NUMBER SP R SP NUMBER CRLF
422  		{
423  			if ($2) {
424  				reply(202, "ALLO command ignored.");
425  			}
426  		}
427  	| RETR check_login SP pathname CRLF
428  		{
429  			if (noretr || (guest && noguestretr))
430  				reply(500, "RETR command disabled.");
431  			else if ($2 && $4 != NULL)
432  				retrieve(NULL, $4);
433  
434  			if ($4 != NULL)
435  				free($4);
436  		}
437  	| STOR check_login_ro SP pathname CRLF
438  		{
439  			if ($2 && $4 != NULL)
440  				store($4, "w", 0);
441  			if ($4 != NULL)
442  				free($4);
443  		}
444  	| APPE check_login_ro SP pathname CRLF
445  		{
446  			if ($2 && $4 != NULL)
447  				store($4, "a", 0);
448  			if ($4 != NULL)
449  				free($4);
450  		}
451  	| NLST check_login CRLF
452  		{
453  			if ($2)
454  				send_file_list(".");
455  		}
456  	| NLST check_login SP pathstring CRLF
457  		{
458  			if ($2)
459  				send_file_list($4);
460  			free($4);
461  		}
462  	| LIST check_login CRLF
463  		{
464  			if ($2)
465  				retrieve(_PATH_LS " -lgA", "");
466  		}
467  	| LIST check_login SP pathstring CRLF
468  		{
469  			if ($2)
470  				retrieve(_PATH_LS " -lgA %s", $4);
471  			free($4);
472  		}
473  	| STAT check_login SP pathname CRLF
474  		{
475  			if ($2 && $4 != NULL)
476  				statfilecmd($4);
477  			if ($4 != NULL)
478  				free($4);
479  		}
480  	| STAT check_login CRLF
481  		{
482  			if ($2) {
483  				statcmd();
484  			}
485  		}
486  	| DELE check_login_ro SP pathname CRLF
487  		{
488  			if ($2 && $4 != NULL)
489  				delete($4);
490  			if ($4 != NULL)
491  				free($4);
492  		}
493  	| RNTO check_login_ro SP pathname CRLF
494  		{
495  			if ($2 && $4 != NULL) {
496  				if (fromname) {
497  					renamecmd(fromname, $4);
498  					free(fromname);
499  					fromname = NULL;
500  				} else {
501  					reply(503, "Bad sequence of commands.");
502  				}
503  			}
504  			if ($4 != NULL)
505  				free($4);
506  		}
507  	| ABOR check_login CRLF
508  		{
509  			if ($2)
510  				reply(225, "ABOR command successful.");
511  		}
512  	| CWD check_login CRLF
513  		{
514  			if ($2) {
515  				cwd(homedir);
516  			}
517  		}
518  	| CWD check_login SP pathname CRLF
519  		{
520  			if ($2 && $4 != NULL)
521  				cwd($4);
522  			if ($4 != NULL)
523  				free($4);
524  		}
525  	| HELP CRLF
526  		{
527  			help(cmdtab, NULL);
528  		}
529  	| HELP SP STRING CRLF
530  		{
531  			char *cp = $3;
532  
533  			if (strncasecmp(cp, "SITE", 4) == 0) {
534  				cp = $3 + 4;
535  				if (*cp == ' ')
536  					cp++;
537  				if (*cp)
538  					help(sitetab, cp);
539  				else
540  					help(sitetab, NULL);
541  			} else
542  				help(cmdtab, $3);
543  			free($3);
544  		}
545  	| NOOP CRLF
546  		{
547  			reply(200, "NOOP command successful.");
548  		}
549  	| MKD check_login_ro SP pathname CRLF
550  		{
551  			if ($2 && $4 != NULL)
552  				makedir($4);
553  			if ($4 != NULL)
554  				free($4);
555  		}
556  	| RMD check_login_ro SP pathname CRLF
557  		{
558  			if ($2 && $4 != NULL)
559  				removedir($4);
560  			if ($4 != NULL)
561  				free($4);
562  		}
563  	| PWD check_login CRLF
564  		{
565  			if ($2)
566  				pwd();
567  		}
568  	| CDUP check_login CRLF
569  		{
570  			if ($2)
571  				cwd("..");
572  		}
573  	| SITE SP HELP CRLF
574  		{
575  			help(sitetab, NULL);
576  		}
577  	| SITE SP HELP SP STRING CRLF
578  		{
579  			help(sitetab, $5);
580  			free($5);
581  		}
582  	| SITE SP MDFIVE check_login SP pathname CRLF
583  		{
584  			char p[64], *q;
585  
586  			if ($4 && $6) {
587  				q = MD5File($6, p);
588  				if (q != NULL)
589  					reply(200, "MD5(%s) = %s", $6, p);
590  				else
591  					perror_reply(550, $6);
592  			}
593  			if ($6)
594  				free($6);
595  		}
596  	| SITE SP UMASK check_login CRLF
597  		{
598  			int oldmask;
599  
600  			if ($4) {
601  				oldmask = umask(0);
602  				(void) umask(oldmask);
603  				reply(200, "Current UMASK is %03o.", oldmask);
604  			}
605  		}
606  	| SITE SP UMASK check_login SP octal_number CRLF
607  		{
608  			int oldmask;
609  
610  			if ($4) {
611  				if (($6 == -1) || ($6 > 0777)) {
612  					reply(501, "Bad UMASK value.");
613  				} else {
614  					oldmask = umask($6);
615  					reply(200,
616  					    "UMASK set to %03o (was %03o).",
617  					    $6, oldmask);
618  				}
619  			}
620  		}
621  	| SITE SP CHMOD check_login_ro SP octal_number SP pathname CRLF
622  		{
623  			if ($4 && ($8 != NULL)) {
624  				if (($6 == -1 ) || ($6 > 0777))
625  					reply(501, "Bad mode value.");
626  				else if (chmod($8, $6) < 0)
627  					perror_reply(550, $8);
628  				else
629  					reply(200, "CHMOD command successful.");
630  			}
631  			if ($8 != NULL)
632  				free($8);
633  		}
634  	| SITE SP check_login IDLE CRLF
635  		{
636  			if ($3)
637  				reply(200,
638  			    	    "Current IDLE time limit is %d seconds; max %d.",
639  				    timeout, maxtimeout);
640  		}
641  	| SITE SP check_login IDLE SP NUMBER CRLF
642  		{
643  			if ($3) {
644  				if ($6.i < 30 || $6.i > maxtimeout) {
645  					reply(501,
646  					    "Maximum IDLE time must be between 30 and %d seconds.",
647  					    maxtimeout);
648  				} else {
649  					timeout = $6.i;
650  					(void) alarm(timeout);
651  					reply(200,
652  					    "Maximum IDLE time set to %d seconds.",
653  					    timeout);
654  				}
655  			}
656  		}
657  	| STOU check_login_ro SP pathname CRLF
658  		{
659  			if ($2 && $4 != NULL)
660  				store($4, "w", 1);
661  			if ($4 != NULL)
662  				free($4);
663  		}
664  	| FEAT CRLF
665  		{
666  			lreply(211, "Extensions supported:");
667  #if 0
668  			/* XXX these two keywords are non-standard */
669  			printf(" EPRT\r\n");
670  			if (!noepsv)
671  				printf(" EPSV\r\n");
672  #endif
673  			printf(" MDTM\r\n");
674  			printf(" REST STREAM\r\n");
675  			printf(" SIZE\r\n");
676  			if (assumeutf8) {
677  				/* TVFS requires UTF8, see RFC 3659 */
678  				printf(" TVFS\r\n");
679  				printf(" UTF8\r\n");
680  			}
681  			reply(211, "End.");
682  		}
683  	| SYST check_login CRLF
684  		{
685  			if ($2) {
686  				if (hostinfo)
687  #ifdef BSD
688  					reply(215, "UNIX Type: L%d Version: BSD-%d",
689  					      CHAR_BIT, BSD);
690  #else /* BSD */
691  					reply(215, "UNIX Type: L%d", CHAR_BIT);
692  #endif /* BSD */
693  				else
694  					reply(215, "UNKNOWN Type: L%d", CHAR_BIT);
695  			}
696  		}
697  
698  		/*
699  		 * SIZE is not in RFC959, but Postel has blessed it and
700  		 * it will be in the updated RFC.
701  		 *
702  		 * Return size of file in a format suitable for
703  		 * using with RESTART (we just count bytes).
704  		 */
705  	| SIZE check_login SP pathname CRLF
706  		{
707  			if ($2 && $4 != NULL)
708  				sizecmd($4);
709  			if ($4 != NULL)
710  				free($4);
711  		}
712  
713  		/*
714  		 * MDTM is not in RFC959, but Postel has blessed it and
715  		 * it will be in the updated RFC.
716  		 *
717  		 * Return modification time of file as an ISO 3307
718  		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
719  		 * where xxx is the fractional second (of any precision,
720  		 * not necessarily 3 digits)
721  		 */
722  	| MDTM check_login SP pathname CRLF
723  		{
724  			if ($2 && $4 != NULL) {
725  				struct stat stbuf;
726  				if (stat($4, &stbuf) < 0)
727  					perror_reply(550, $4);
728  				else if (!S_ISREG(stbuf.st_mode)) {
729  					reply(550, "%s: not a plain file.", $4);
730  				} else {
731  					struct tm *t;
732  					t = gmtime(&stbuf.st_mtime);
733  					reply(213,
734  					    "%04d%02d%02d%02d%02d%02d",
735  					    1900 + t->tm_year,
736  					    t->tm_mon+1, t->tm_mday,
737  					    t->tm_hour, t->tm_min, t->tm_sec);
738  				}
739  			}
740  			if ($4 != NULL)
741  				free($4);
742  		}
743  	| QUIT CRLF
744  		{
745  			reply(221, "Goodbye.");
746  			dologout(0);
747  		}
748  	| NOTIMPL
749  		{
750  			nack($1);
751  		}
752  	| error
753  		{
754  			yyclearin;		/* discard lookahead data */
755  			yyerrok;		/* clear error condition */
756  			state = CMD;		/* reset lexer state */
757  		}
758  	;
759  rcmd
760  	: RNFR check_login_ro SP pathname CRLF
761  		{
762  			restart_point = 0;
763  			if ($2 && $4) {
764  				if (fromname)
765  					free(fromname);
766  				fromname = NULL;
767  				if (renamefrom($4))
768  					fromname = $4;
769  				else
770  					free($4);
771  			} else if ($4) {
772  				free($4);
773  			}
774  		}
775  	| REST check_login SP NUMBER CRLF
776  		{
777  			if ($2) {
778  				if (fromname)
779  					free(fromname);
780  				fromname = NULL;
781  				restart_point = $4.o;
782  				reply(350, "Restarting at %jd. %s",
783  				    (intmax_t)restart_point,
784  				    "Send STORE or RETRIEVE to initiate transfer.");
785  			}
786  		}
787  	;
788  
789  username
790  	: STRING
791  	;
792  
793  password
794  	: /* empty */
795  		{
796  			$$ = (char *)calloc(1, sizeof(char));
797  		}
798  	| STRING
799  	;
800  
801  byte_size
802  	: NUMBER
803  		{
804  			$$ = $1.i;
805  		}
806  	;
807  
808  host_port
809  	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
810  		NUMBER COMMA NUMBER
811  		{
812  			char *a, *p;
813  
814  			data_dest.su_len = sizeof(struct sockaddr_in);
815  			data_dest.su_family = AF_INET;
816  			p = (char *)&data_dest.su_sin.sin_port;
817  			p[0] = $9.i; p[1] = $11.i;
818  			a = (char *)&data_dest.su_sin.sin_addr;
819  			a[0] = $1.i; a[1] = $3.i; a[2] = $5.i; a[3] = $7.i;
820  		}
821  	;
822  
823  host_long_port
824  	: NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
825  		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
826  		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
827  		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
828  		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
829  		NUMBER
830  		{
831  			char *a, *p;
832  
833  			memset(&data_dest, 0, sizeof(data_dest));
834  			data_dest.su_len = sizeof(struct sockaddr_in6);
835  			data_dest.su_family = AF_INET6;
836  			p = (char *)&data_dest.su_port;
837  			p[0] = $39.i; p[1] = $41.i;
838  			a = (char *)&data_dest.su_sin6.sin6_addr;
839  			a[0] = $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
840  			a[4] = $13.i; a[5] = $15.i; a[6] = $17.i; a[7] = $19.i;
841  			a[8] = $21.i; a[9] = $23.i; a[10] = $25.i; a[11] = $27.i;
842  			a[12] = $29.i; a[13] = $31.i; a[14] = $33.i; a[15] = $35.i;
843  			if (his_addr.su_family == AF_INET6) {
844  				/* XXX more sanity checks! */
845  				data_dest.su_sin6.sin6_scope_id =
846  					his_addr.su_sin6.sin6_scope_id;
847  			}
848  			if ($1.i != 6 || $3.i != 16 || $37.i != 2)
849  				memset(&data_dest, 0, sizeof(data_dest));
850  		}
851  	| NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
852  		NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
853  		NUMBER
854  		{
855  			char *a, *p;
856  
857  			memset(&data_dest, 0, sizeof(data_dest));
858  			data_dest.su_sin.sin_len = sizeof(struct sockaddr_in);
859  			data_dest.su_family = AF_INET;
860  			p = (char *)&data_dest.su_port;
861  			p[0] = $15.i; p[1] = $17.i;
862  			a = (char *)&data_dest.su_sin.sin_addr;
863  			a[0] =  $5.i; a[1] = $7.i; a[2] = $9.i; a[3] = $11.i;
864  			if ($1.i != 4 || $3.i != 4 || $13.i != 2)
865  				memset(&data_dest, 0, sizeof(data_dest));
866  		}
867  	;
868  
869  form_code
870  	: N
871  		{
872  			$$ = FORM_N;
873  		}
874  	| T
875  		{
876  			$$ = FORM_T;
877  		}
878  	| C
879  		{
880  			$$ = FORM_C;
881  		}
882  	;
883  
884  type_code
885  	: A
886  		{
887  			cmd_type = TYPE_A;
888  			cmd_form = FORM_N;
889  		}
890  	| A SP form_code
891  		{
892  			cmd_type = TYPE_A;
893  			cmd_form = $3;
894  		}
895  	| E
896  		{
897  			cmd_type = TYPE_E;
898  			cmd_form = FORM_N;
899  		}
900  	| E SP form_code
901  		{
902  			cmd_type = TYPE_E;
903  			cmd_form = $3;
904  		}
905  	| I
906  		{
907  			cmd_type = TYPE_I;
908  		}
909  	| L
910  		{
911  			cmd_type = TYPE_L;
912  			cmd_bytesz = CHAR_BIT;
913  		}
914  	| L SP byte_size
915  		{
916  			cmd_type = TYPE_L;
917  			cmd_bytesz = $3;
918  		}
919  		/* this is for a bug in the BBN ftp */
920  	| L byte_size
921  		{
922  			cmd_type = TYPE_L;
923  			cmd_bytesz = $2;
924  		}
925  	;
926  
927  struct_code
928  	: F
929  		{
930  			$$ = STRU_F;
931  		}
932  	| R
933  		{
934  			$$ = STRU_R;
935  		}
936  	| P
937  		{
938  			$$ = STRU_P;
939  		}
940  	;
941  
942  mode_code
943  	: S
944  		{
945  			$$ = MODE_S;
946  		}
947  	| B
948  		{
949  			$$ = MODE_B;
950  		}
951  	| C
952  		{
953  			$$ = MODE_C;
954  		}
955  	;
956  
957  pathname
958  	: pathstring
959  		{
960  			if (logged_in && $1) {
961  				char *p;
962  
963  				/*
964  				 * Expand ~user manually since glob(3)
965  				 * will return the unexpanded pathname
966  				 * if the corresponding file/directory
967  				 * doesn't exist yet.  Using sole glob(3)
968  				 * would break natural commands like
969  				 * MKD ~user/newdir
970  				 * or
971  				 * RNTO ~/newfile
972  				 */
973  				if ((p = exptilde($1)) != NULL) {
974  					$$ = expglob(p);
975  					free(p);
976  				} else
977  					$$ = NULL;
978  				free($1);
979  			} else
980  				$$ = $1;
981  		}
982  	;
983  
984  pathstring
985  	: STRING
986  	;
987  
988  octal_number
989  	: NUMBER
990  		{
991  			int ret, dec, multby, digit;
992  
993  			/*
994  			 * Convert a number that was read as decimal number
995  			 * to what it would be if it had been read as octal.
996  			 */
997  			dec = $1.i;
998  			multby = 1;
999  			ret = 0;
1000  			while (dec) {
1001  				digit = dec%10;
1002  				if (digit > 7) {
1003  					ret = -1;
1004  					break;
1005  				}
1006  				ret += digit * multby;
1007  				multby *= 8;
1008  				dec /= 10;
1009  			}
1010  			$$ = ret;
1011  		}
1012  	;
1013  
1014  
1015  check_login
1016  	: /* empty */
1017  		{
1018  		$$ = check_login1();
1019  		}
1020  	;
1021  
1022  check_login_epsv
1023  	: /* empty */
1024  		{
1025  		if (noepsv) {
1026  			reply(500, "EPSV command disabled.");
1027  			$$ = 0;
1028  		}
1029  		else
1030  			$$ = check_login1();
1031  		}
1032  	;
1033  
1034  check_login_ro
1035  	: /* empty */
1036  		{
1037  		if (readonly) {
1038  			reply(550, "Permission denied.");
1039  			$$ = 0;
1040  		}
1041  		else
1042  			$$ = check_login1();
1043  		}
1044  	;
1045  
1046  %%
1047  
1048  #define	CMD	0	/* beginning of command */
1049  #define	ARGS	1	/* expect miscellaneous arguments */
1050  #define	STR1	2	/* expect SP followed by STRING */
1051  #define	STR2	3	/* expect STRING */
1052  #define	OSTR	4	/* optional SP then STRING */
1053  #define	ZSTR1	5	/* optional SP then optional STRING */
1054  #define	ZSTR2	6	/* optional STRING after SP */
1055  #define	SITECMD	7	/* SITE command */
1056  #define	NSTR	8	/* Number followed by a string */
1057  
1058  #define	MAXGLOBARGS	1000
1059  
1060  #define	MAXASIZE	10240	/* Deny ASCII SIZE on files larger than that */
1061  
1062  struct tab {
1063  	char	*name;
1064  	short	token;
1065  	short	state;
1066  	short	implemented;	/* 1 if command is implemented */
1067  	char	*help;
1068  };
1069  
1070  struct tab cmdtab[] = {		/* In order defined in RFC 765 */
1071  	{ "USER", USER, STR1, 1,	"<sp> username" },
1072  	{ "PASS", PASS, ZSTR1, 1,	"[<sp> [password]]" },
1073  	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
1074  	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
1075  	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
1076  	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
1077  	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4, b5" },
1078  	{ "LPRT", LPRT, ARGS, 1,	"<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1079  	{ "EPRT", EPRT, STR1, 1,	"<sp> |af|addr|port|" },
1080  	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
1081  	{ "LPSV", LPSV, ARGS, 1,	"(set server in passive mode)" },
1082  	{ "EPSV", EPSV, ARGS, 1,	"[<sp> af|ALL]" },
1083  	{ "TYPE", TYPE, ARGS, 1,	"<sp> { A | E | I | L }" },
1084  	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
1085  	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
1086  	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
1087  	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
1088  	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
1089  	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
1090  	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
1091  	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
1092  	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
1093  	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
1094  	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
1095  	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
1096  	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
1097  	{ "REST", REST, ARGS, 1,	"<sp> offset (restart command)" },
1098  	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
1099  	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
1100  	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
1101  	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
1102  	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
1103  	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
1104  	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
1105  	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
1106  	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
1107  	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
1108  	{ "FEAT", FEAT, ARGS, 1,	"(get extended features)" },
1109  	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
1110  	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1111  	{ "NOOP", NOOP, ARGS, 1,	"" },
1112  	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
1113  	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
1114  	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
1115  	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
1116  	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
1117  	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
1118  	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1119  	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
1120  	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
1121  	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
1122  	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
1123  	{ NULL,   0,    0,    0,	0 }
1124  };
1125  
1126  struct tab sitetab[] = {
1127  	{ "MD5", MDFIVE, STR1, 1,	"[ <sp> file-name ]" },
1128  	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
1129  	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
1130  	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
1131  	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
1132  	{ NULL,   0,    0,    0,	0 }
1133  };
1134  
1135  static char	*copy(char *);
1136  static char	*expglob(char *);
1137  static char	*exptilde(char *);
1138  static void	 help(struct tab *, char *);
1139  static struct tab *
1140  		 lookup(struct tab *, char *);
1141  static int	 port_check(const char *);
1142  #ifdef INET6
1143  static int	 port_check_v6(const char *);
1144  #endif
1145  static void	 sizecmd(char *);
1146  static void	 toolong(int);
1147  #ifdef INET6
1148  static void	 v4map_data_dest(void);
1149  #endif
1150  static int	 yylex(void);
1151  
1152  static struct tab *
1153  lookup(struct tab *p, char *cmd)
1154  {
1155  
1156  	for (; p->name != NULL; p++)
1157  		if (strcmp(cmd, p->name) == 0)
1158  			return (p);
1159  	return (0);
1160  }
1161  
1162  #include <arpa/telnet.h>
1163  
1164  /*
1165   * getline - a hacked up version of fgets to ignore TELNET escape codes.
1166   */
1167  int
1168  getline(char *s, int n, FILE *iop)
1169  {
1170  	int c;
1171  	register char *cs;
1172  	sigset_t sset, osset;
1173  
1174  	cs = s;
1175  /* tmpline may contain saved command from urgent mode interruption */
1176  	for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1177  		*cs++ = tmpline[c];
1178  		if (tmpline[c] == '\n') {
1179  			*cs++ = '\0';
1180  			if (ftpdebug)
1181  				syslog(LOG_DEBUG, "command: %s", s);
1182  			tmpline[0] = '\0';
1183  			return(0);
1184  		}
1185  		if (c == 0)
1186  			tmpline[0] = '\0';
1187  	}
1188  	/* SIGURG would interrupt stdio if not blocked during the read loop */
1189  	sigemptyset(&sset);
1190  	sigaddset(&sset, SIGURG);
1191  	sigprocmask(SIG_BLOCK, &sset, &osset);
1192  	while ((c = getc(iop)) != EOF) {
1193  		c &= 0377;
1194  		if (c == IAC) {
1195  			if ((c = getc(iop)) == EOF)
1196  				goto got_eof;
1197  			c &= 0377;
1198  			switch (c) {
1199  			case WILL:
1200  			case WONT:
1201  				if ((c = getc(iop)) == EOF)
1202  					goto got_eof;
1203  				printf("%c%c%c", IAC, DONT, 0377&c);
1204  				(void) fflush(stdout);
1205  				continue;
1206  			case DO:
1207  			case DONT:
1208  				if ((c = getc(iop)) == EOF)
1209  					goto got_eof;
1210  				printf("%c%c%c", IAC, WONT, 0377&c);
1211  				(void) fflush(stdout);
1212  				continue;
1213  			case IAC:
1214  				break;
1215  			default:
1216  				continue;	/* ignore command */
1217  			}
1218  		}
1219  		*cs++ = c;
1220  		if (--n <= 0) {
1221  			/*
1222  			 * If command doesn't fit into buffer, discard the
1223  			 * rest of the command and indicate truncation.
1224  			 * This prevents the command to be split up into
1225  			 * multiple commands.
1226  			 */
1227  			while (c != '\n' && (c = getc(iop)) != EOF)
1228  				;
1229  			return (-2);
1230  		}
1231  		if (c == '\n')
1232  			break;
1233  	}
1234  got_eof:
1235  	sigprocmask(SIG_SETMASK, &osset, NULL);
1236  	if (c == EOF && cs == s)
1237  		return (-1);
1238  	*cs++ = '\0';
1239  	if (ftpdebug) {
1240  		if (!guest && strncasecmp("pass ", s, 5) == 0) {
1241  			/* Don't syslog passwords */
1242  			syslog(LOG_DEBUG, "command: %.5s ???", s);
1243  		} else {
1244  			register char *cp;
1245  			register int len;
1246  
1247  			/* Don't syslog trailing CR-LF */
1248  			len = strlen(s);
1249  			cp = s + len - 1;
1250  			while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1251  				--cp;
1252  				--len;
1253  			}
1254  			syslog(LOG_DEBUG, "command: %.*s", len, s);
1255  		}
1256  	}
1257  	return (0);
1258  }
1259  
1260  static void
1261  toolong(int signo)
1262  {
1263  
1264  	reply(421,
1265  	    "Timeout (%d seconds): closing control connection.", timeout);
1266  	if (logging)
1267  		syslog(LOG_INFO, "User %s timed out after %d seconds",
1268  		    (pw ? pw -> pw_name : "unknown"), timeout);
1269  	dologout(1);
1270  }
1271  
1272  static int
1273  yylex(void)
1274  {
1275  	static int cpos;
1276  	char *cp, *cp2;
1277  	struct tab *p;
1278  	int n;
1279  	char c;
1280  
1281  	for (;;) {
1282  		switch (state) {
1283  
1284  		case CMD:
1285  			(void) signal(SIGALRM, toolong);
1286  			(void) alarm(timeout);
1287  			n = getline(cbuf, sizeof(cbuf)-1, stdin);
1288  			if (n == -1) {
1289  				reply(221, "You could at least say goodbye.");
1290  				dologout(0);
1291  			} else if (n == -2) {
1292  				reply(500, "Command too long.");
1293  				(void) alarm(0);
1294  				continue;
1295  			}
1296  			(void) alarm(0);
1297  #ifdef SETPROCTITLE
1298  			if (strncasecmp(cbuf, "PASS", 4) != 0)
1299  				setproctitle("%s: %s", proctitle, cbuf);
1300  #endif /* SETPROCTITLE */
1301  			if ((cp = strchr(cbuf, '\r'))) {
1302  				*cp++ = '\n';
1303  				*cp = '\0';
1304  			}
1305  			if ((cp = strpbrk(cbuf, " \n")))
1306  				cpos = cp - cbuf;
1307  			if (cpos == 0)
1308  				cpos = 4;
1309  			c = cbuf[cpos];
1310  			cbuf[cpos] = '\0';
1311  			upper(cbuf);
1312  			p = lookup(cmdtab, cbuf);
1313  			cbuf[cpos] = c;
1314  			if (p != 0) {
1315  				yylval.s = p->name;
1316  				if (!p->implemented)
1317  					return (NOTIMPL); /* state remains CMD */
1318  				state = p->state;
1319  				return (p->token);
1320  			}
1321  			break;
1322  
1323  		case SITECMD:
1324  			if (cbuf[cpos] == ' ') {
1325  				cpos++;
1326  				return (SP);
1327  			}
1328  			cp = &cbuf[cpos];
1329  			if ((cp2 = strpbrk(cp, " \n")))
1330  				cpos = cp2 - cbuf;
1331  			c = cbuf[cpos];
1332  			cbuf[cpos] = '\0';
1333  			upper(cp);
1334  			p = lookup(sitetab, cp);
1335  			cbuf[cpos] = c;
1336  			if (guest == 0 && p != 0) {
1337  				yylval.s = p->name;
1338  				if (!p->implemented) {
1339  					state = CMD;
1340  					return (NOTIMPL);
1341  				}
1342  				state = p->state;
1343  				return (p->token);
1344  			}
1345  			state = CMD;
1346  			break;
1347  
1348  		case ZSTR1:
1349  		case OSTR:
1350  			if (cbuf[cpos] == '\n') {
1351  				state = CMD;
1352  				return (CRLF);
1353  			}
1354  			/* FALLTHROUGH */
1355  
1356  		case STR1:
1357  		dostr1:
1358  			if (cbuf[cpos] == ' ') {
1359  				cpos++;
1360  				state = state == OSTR ? STR2 : state+1;
1361  				return (SP);
1362  			}
1363  			break;
1364  
1365  		case ZSTR2:
1366  			if (cbuf[cpos] == '\n') {
1367  				state = CMD;
1368  				return (CRLF);
1369  			}
1370  			/* FALLTHROUGH */
1371  
1372  		case STR2:
1373  			cp = &cbuf[cpos];
1374  			n = strlen(cp);
1375  			cpos += n - 1;
1376  			/*
1377  			 * Make sure the string is nonempty and \n terminated.
1378  			 */
1379  			if (n > 1 && cbuf[cpos] == '\n') {
1380  				cbuf[cpos] = '\0';
1381  				yylval.s = copy(cp);
1382  				cbuf[cpos] = '\n';
1383  				state = ARGS;
1384  				return (STRING);
1385  			}
1386  			break;
1387  
1388  		case NSTR:
1389  			if (cbuf[cpos] == ' ') {
1390  				cpos++;
1391  				return (SP);
1392  			}
1393  			if (isdigit(cbuf[cpos])) {
1394  				cp = &cbuf[cpos];
1395  				while (isdigit(cbuf[++cpos]))
1396  					;
1397  				c = cbuf[cpos];
1398  				cbuf[cpos] = '\0';
1399  				yylval.u.i = atoi(cp);
1400  				cbuf[cpos] = c;
1401  				state = STR1;
1402  				return (NUMBER);
1403  			}
1404  			state = STR1;
1405  			goto dostr1;
1406  
1407  		case ARGS:
1408  			if (isdigit(cbuf[cpos])) {
1409  				cp = &cbuf[cpos];
1410  				while (isdigit(cbuf[++cpos]))
1411  					;
1412  				c = cbuf[cpos];
1413  				cbuf[cpos] = '\0';
1414  				yylval.u.i = atoi(cp);
1415  				yylval.u.o = strtoull(cp, NULL, 10);
1416  				cbuf[cpos] = c;
1417  				return (NUMBER);
1418  			}
1419  			if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1420  			 && !isalnum(cbuf[cpos + 3])) {
1421  				cpos += 3;
1422  				return ALL;
1423  			}
1424  			switch (cbuf[cpos++]) {
1425  
1426  			case '\n':
1427  				state = CMD;
1428  				return (CRLF);
1429  
1430  			case ' ':
1431  				return (SP);
1432  
1433  			case ',':
1434  				return (COMMA);
1435  
1436  			case 'A':
1437  			case 'a':
1438  				return (A);
1439  
1440  			case 'B':
1441  			case 'b':
1442  				return (B);
1443  
1444  			case 'C':
1445  			case 'c':
1446  				return (C);
1447  
1448  			case 'E':
1449  			case 'e':
1450  				return (E);
1451  
1452  			case 'F':
1453  			case 'f':
1454  				return (F);
1455  
1456  			case 'I':
1457  			case 'i':
1458  				return (I);
1459  
1460  			case 'L':
1461  			case 'l':
1462  				return (L);
1463  
1464  			case 'N':
1465  			case 'n':
1466  				return (N);
1467  
1468  			case 'P':
1469  			case 'p':
1470  				return (P);
1471  
1472  			case 'R':
1473  			case 'r':
1474  				return (R);
1475  
1476  			case 'S':
1477  			case 's':
1478  				return (S);
1479  
1480  			case 'T':
1481  			case 't':
1482  				return (T);
1483  
1484  			}
1485  			break;
1486  
1487  		default:
1488  			fatalerror("Unknown state in scanner.");
1489  		}
1490  		state = CMD;
1491  		return (LEXERR);
1492  	}
1493  }
1494  
1495  void
1496  upper(char *s)
1497  {
1498  	while (*s != '\0') {
1499  		if (islower(*s))
1500  			*s = toupper(*s);
1501  		s++;
1502  	}
1503  }
1504  
1505  static char *
1506  copy(char *s)
1507  {
1508  	char *p;
1509  
1510  	p = malloc(strlen(s) + 1);
1511  	if (p == NULL)
1512  		fatalerror("Ran out of memory.");
1513  	(void) strcpy(p, s);
1514  	return (p);
1515  }
1516  
1517  static void
1518  help(struct tab *ctab, char *s)
1519  {
1520  	struct tab *c;
1521  	int width, NCMDS;
1522  	char *type;
1523  
1524  	if (ctab == sitetab)
1525  		type = "SITE ";
1526  	else
1527  		type = "";
1528  	width = 0, NCMDS = 0;
1529  	for (c = ctab; c->name != NULL; c++) {
1530  		int len = strlen(c->name);
1531  
1532  		if (len > width)
1533  			width = len;
1534  		NCMDS++;
1535  	}
1536  	width = (width + 8) &~ 7;
1537  	if (s == 0) {
1538  		int i, j, w;
1539  		int columns, lines;
1540  
1541  		lreply(214, "The following %scommands are recognized %s.",
1542  		    type, "(* =>'s unimplemented)");
1543  		columns = 76 / width;
1544  		if (columns == 0)
1545  			columns = 1;
1546  		lines = (NCMDS + columns - 1) / columns;
1547  		for (i = 0; i < lines; i++) {
1548  			printf("   ");
1549  			for (j = 0; j < columns; j++) {
1550  				c = ctab + j * lines + i;
1551  				printf("%s%c", c->name,
1552  					c->implemented ? ' ' : '*');
1553  				if (c + lines >= &ctab[NCMDS])
1554  					break;
1555  				w = strlen(c->name) + 1;
1556  				while (w < width) {
1557  					putchar(' ');
1558  					w++;
1559  				}
1560  			}
1561  			printf("\r\n");
1562  		}
1563  		(void) fflush(stdout);
1564  		if (hostinfo)
1565  			reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1566  		else
1567  			reply(214, "End.");
1568  		return;
1569  	}
1570  	upper(s);
1571  	c = lookup(ctab, s);
1572  	if (c == NULL) {
1573  		reply(502, "Unknown command %s.", s);
1574  		return;
1575  	}
1576  	if (c->implemented)
1577  		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1578  	else
1579  		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1580  		    c->name, c->help);
1581  }
1582  
1583  static void
1584  sizecmd(char *filename)
1585  {
1586  	switch (type) {
1587  	case TYPE_L:
1588  	case TYPE_I: {
1589  		struct stat stbuf;
1590  		if (stat(filename, &stbuf) < 0)
1591  			perror_reply(550, filename);
1592  		else if (!S_ISREG(stbuf.st_mode))
1593  			reply(550, "%s: not a plain file.", filename);
1594  		else
1595  			reply(213, "%jd", (intmax_t)stbuf.st_size);
1596  		break; }
1597  	case TYPE_A: {
1598  		FILE *fin;
1599  		int c;
1600  		off_t count;
1601  		struct stat stbuf;
1602  		fin = fopen(filename, "r");
1603  		if (fin == NULL) {
1604  			perror_reply(550, filename);
1605  			return;
1606  		}
1607  		if (fstat(fileno(fin), &stbuf) < 0) {
1608  			perror_reply(550, filename);
1609  			(void) fclose(fin);
1610  			return;
1611  		} else if (!S_ISREG(stbuf.st_mode)) {
1612  			reply(550, "%s: not a plain file.", filename);
1613  			(void) fclose(fin);
1614  			return;
1615  		} else if (stbuf.st_size > MAXASIZE) {
1616  			reply(550, "%s: too large for type A SIZE.", filename);
1617  			(void) fclose(fin);
1618  			return;
1619  		}
1620  
1621  		count = 0;
1622  		while((c=getc(fin)) != EOF) {
1623  			if (c == '\n')	/* will get expanded to \r\n */
1624  				count++;
1625  			count++;
1626  		}
1627  		(void) fclose(fin);
1628  
1629  		reply(213, "%jd", (intmax_t)count);
1630  		break; }
1631  	default:
1632  		reply(504, "SIZE not implemented for type %s.",
1633  		           typenames[type]);
1634  	}
1635  }
1636  
1637  /* Return 1, if port check is done. Return 0, if not yet. */
1638  static int
1639  port_check(const char *pcmd)
1640  {
1641  	if (his_addr.su_family == AF_INET) {
1642  		if (data_dest.su_family != AF_INET) {
1643  			usedefault = 1;
1644  			reply(500, "Invalid address rejected.");
1645  			return 1;
1646  		}
1647  		if (paranoid &&
1648  		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1649  		     memcmp(&data_dest.su_sin.sin_addr,
1650  			    &his_addr.su_sin.sin_addr,
1651  			    sizeof(data_dest.su_sin.sin_addr)))) {
1652  			usedefault = 1;
1653  			reply(500, "Illegal PORT range rejected.");
1654  		} else {
1655  			usedefault = 0;
1656  			if (pdata >= 0) {
1657  				(void) close(pdata);
1658  				pdata = -1;
1659  			}
1660  			reply(200, "%s command successful.", pcmd);
1661  		}
1662  		return 1;
1663  	}
1664  	return 0;
1665  }
1666  
1667  static int
1668  check_login1(void)
1669  {
1670  	if (logged_in)
1671  		return 1;
1672  	else {
1673  		reply(530, "Please login with USER and PASS.");
1674  		return 0;
1675  	}
1676  }
1677  
1678  /*
1679   * Replace leading "~user" in a pathname by the user's login directory.
1680   * Returned string will be in a freshly malloced buffer unless it's NULL.
1681   */
1682  static char *
1683  exptilde(char *s)
1684  {
1685  	char *p, *q;
1686  	char *path, *user;
1687  	struct passwd *ppw;
1688  
1689  	if ((p = strdup(s)) == NULL)
1690  		return (NULL);
1691  	if (*p != '~')
1692  		return (p);
1693  
1694  	user = p + 1;	/* skip tilde */
1695  	if ((path = strchr(p, '/')) != NULL)
1696  		*(path++) = '\0'; /* separate ~user from the rest of path */
1697  	if (*user == '\0') /* no user specified, use the current user */
1698  		user = pw->pw_name;
1699  	/* read passwd even for the current user since we may be chrooted */
1700  	if ((ppw = getpwnam(user)) != NULL) {
1701  		/* user found, substitute login directory for ~user */
1702  		if (path)
1703  			asprintf(&q, "%s/%s", ppw->pw_dir, path);
1704  		else
1705  			q = strdup(ppw->pw_dir);
1706  		free(p);
1707  		p = q;
1708  	} else {
1709  		/* user not found, undo the damage */
1710  		if (path)
1711  			path[-1] = '/';
1712  	}
1713  	return (p);
1714  }
1715  
1716  /*
1717   * Expand glob(3) patterns possibly present in a pathname.
1718   * Avoid expanding to a pathname including '\r' or '\n' in order to
1719   * not disrupt the FTP protocol.
1720   * The expansion found must be unique.
1721   * Return the result as a malloced string, or NULL if an error occured.
1722   *
1723   * Problem: this production is used for all pathname
1724   * processing, but only gives a 550 error reply.
1725   * This is a valid reply in some cases but not in others.
1726   */
1727  static char *
1728  expglob(char *s)
1729  {
1730  	char *p, **pp, *rval;
1731  	int flags = GLOB_BRACE | GLOB_NOCHECK;
1732  	int n;
1733  	glob_t gl;
1734  
1735  	memset(&gl, 0, sizeof(gl));
1736  	flags |= GLOB_LIMIT;
1737  	gl.gl_matchc = MAXGLOBARGS;
1738  	if (glob(s, flags, NULL, &gl) == 0 && gl.gl_pathc != 0) {
1739  		for (pp = gl.gl_pathv, p = NULL, n = 0; *pp; pp++)
1740  			if (*(*pp + strcspn(*pp, "\r\n")) == '\0') {
1741  				p = *pp;
1742  				n++;
1743  			}
1744  		if (n == 0)
1745  			rval = strdup(s);
1746  		else if (n == 1)
1747  			rval = strdup(p);
1748  		else {
1749  			reply(550, "Wildcard is ambiguous.");
1750  			rval = NULL;
1751  		}
1752  	} else {
1753  		reply(550, "Wildcard expansion error.");
1754  		rval = NULL;
1755  	}
1756  	globfree(&gl);
1757  	return (rval);
1758  }
1759  
1760  #ifdef INET6
1761  /* Return 1, if port check is done. Return 0, if not yet. */
1762  static int
1763  port_check_v6(const char *pcmd)
1764  {
1765  	if (his_addr.su_family == AF_INET6) {
1766  		if (IN6_IS_ADDR_V4MAPPED(&his_addr.su_sin6.sin6_addr))
1767  			/* Convert data_dest into v4 mapped sockaddr.*/
1768  			v4map_data_dest();
1769  		if (data_dest.su_family != AF_INET6) {
1770  			usedefault = 1;
1771  			reply(500, "Invalid address rejected.");
1772  			return 1;
1773  		}
1774  		if (paranoid &&
1775  		    ((ntohs(data_dest.su_port) < IPPORT_RESERVED) ||
1776  		     memcmp(&data_dest.su_sin6.sin6_addr,
1777  			    &his_addr.su_sin6.sin6_addr,
1778  			    sizeof(data_dest.su_sin6.sin6_addr)))) {
1779  			usedefault = 1;
1780  			reply(500, "Illegal PORT range rejected.");
1781  		} else {
1782  			usedefault = 0;
1783  			if (pdata >= 0) {
1784  				(void) close(pdata);
1785  				pdata = -1;
1786  			}
1787  			reply(200, "%s command successful.", pcmd);
1788  		}
1789  		return 1;
1790  	}
1791  	return 0;
1792  }
1793  
1794  static void
1795  v4map_data_dest(void)
1796  {
1797  	struct in_addr savedaddr;
1798  	int savedport;
1799  
1800  	if (data_dest.su_family != AF_INET) {
1801  		usedefault = 1;
1802  		reply(500, "Invalid address rejected.");
1803  		return;
1804  	}
1805  
1806  	savedaddr = data_dest.su_sin.sin_addr;
1807  	savedport = data_dest.su_port;
1808  
1809  	memset(&data_dest, 0, sizeof(data_dest));
1810  	data_dest.su_sin6.sin6_len = sizeof(struct sockaddr_in6);
1811  	data_dest.su_sin6.sin6_family = AF_INET6;
1812  	data_dest.su_sin6.sin6_port = savedport;
1813  	memset((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[10], 0xff, 2);
1814  	memcpy((caddr_t)&data_dest.su_sin6.sin6_addr.s6_addr[12],
1815  	       (caddr_t)&savedaddr, sizeof(savedaddr));
1816  }
1817  #endif
1818