ftpcmd.y revision 29965
1210389Sgabor/* ftpcmd.y: yacc parser for the FTP daemon.
2210389Sgabor
3210389Sgabor%%% portions-copyright-cmetz-96
4211496SdesPortions of this software are Copyright 1996-1997 by Craig Metz, All Rights
5210389SgaborReserved. The Inner Net License Version 2 applies to these portions of
6210389Sgaborthe software.
7210389SgaborYou should have received a copy of the license with this software. If
8210389Sgaboryou didn't get a copy, you may request one from <license@inner.net>.
9210389Sgabor
10210389Sgabor	History:
11210389Sgabor
12210389Sgabor	Modified by cmetz for OPIE 2.3. Moved LS_COMMAND here.
13210389Sgabor        Modified by cmetz for OPIE 2.2. Fixed a *lot* of warnings.
14210389Sgabor                Use FUNCTION declaration et al. Removed useless strings.
15210389Sgabor                Changed some char []s to char *s. Deleted comment address.
16210389Sgabor                Changed tmpline references to be more pure-pointer
17210389Sgabor                references. Changed tmpline declaration back to char [].
18210389Sgabor	Modified at NRL for OPIE 2.1. Minor changes for autoconf.
19210389Sgabor        Modified at NRL for OPIE 2.01. Added forward declaration for sitetab[]
20210389Sgabor                -- fixes problems experienced by bison users. Merged in new
21210389Sgabor                PORT attack fixes from Hobbit.
22210389Sgabor	Modified at NRL for OPIE 2.0.
23210389Sgabor	Originally from BSD.
24210389Sgabor*/
25210389Sgabor/*
26210389Sgabor * Copyright (c) 1985, 1988 Regents of the University of California.
27210389Sgabor * All rights reserved.
28210389Sgabor *
29210389Sgabor * Redistribution and use in source and binary forms, with or without
30210389Sgabor * modification, are permitted provided that the following conditions
31210389Sgabor * are met:
32210389Sgabor * 1. Redistributions of source code must retain the above copyright
33210389Sgabor *    notice, this list of conditions and the following disclaimer.
34210389Sgabor * 2. Redistributions in binary form must reproduce the above copyright
35210389Sgabor *    notice, this list of conditions and the following disclaimer in the
36210389Sgabor *    documentation and/or other materials provided with the distribution.
37210389Sgabor * 3. All advertising materials mentioning features or use of this software
38210389Sgabor *    must display the following acknowledgement:
39210389Sgabor *	This product includes software developed by the University of
40210389Sgabor *	California, Berkeley and its contributors.
41210389Sgabor * 4. Neither the name of the University nor the names of its contributors
42210578Sgabor *    may be used to endorse or promote products derived from this software
43210389Sgabor *    without specific prior written permission.
44210389Sgabor *
45210389Sgabor * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
46210389Sgabor * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
47210389Sgabor * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
48210389Sgabor * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
49210389Sgabor * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
50210389Sgabor * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
51210389Sgabor * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
52210389Sgabor * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
53210389Sgabor * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
54210389Sgabor * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
55210578Sgabor * SUCH DAMAGE.
56210578Sgabor *
57210578Sgabor *	@(#)ftpcmd.y	5.24 (Berkeley) 2/25/91
58220421Sgabor */
59210578Sgabor
60210578Sgabor/*
61210578Sgabor * Grammar for FTP commands.
62220421Sgabor * See RFC 959.
63210578Sgabor */
64210578Sgabor
65220421Sgabor%{
66220421Sgabor#include "opie_cfg.h"
67210578Sgabor
68210578Sgabor#include <sys/param.h>
69210578Sgabor#include <sys/types.h>
70210578Sgabor#include <sys/socket.h>
71210578Sgabor#include <sys/stat.h>
72210578Sgabor#include <netinet/in.h>
73210578Sgabor#include <arpa/ftp.h>
74210578Sgabor#include <signal.h>
75210578Sgabor#include <setjmp.h>
76211364Sgabor#include <syslog.h>
77210578Sgabor#if TM_IN_SYS_TIME
78210578Sgabor#include <sys/time.h>
79210578Sgabor#else /* TM_IN_SYS_TIME */
80210578Sgabor#include <time.h>
81210578Sgabor#endif /* TM_IN_SYS_TIME */
82210578Sgabor#include <pwd.h>
83210578Sgabor#include <unistd.h>
84210578Sgabor#include <stdio.h>
85210578Sgabor#include <ctype.h>
86210578Sgabor#include <stdlib.h>
87210578Sgabor#include <string.h>
88210578Sgabor
89210578Sgabor#include "opie.h"
90210578Sgabor
91210578Sgabor#if HAVE_LS_G_FLAG
92210578Sgabor#define LS_COMMAND "/bin/ls -lgA"
93210578Sgabor#else /* HAVE_LS_G_FLAG */
94210578Sgabor#define LS_COMMAND "/bin/ls -lA"
95210389Sgabor#endif /* HAVE_LS_G_FLAG */
96210389Sgabor
97210389Sgaborextern	struct sockaddr_in data_dest;
98210389Sgaborextern  struct sockaddr_in his_addr;
99210389Sgaborextern	int logged_in;
100210389Sgaborextern	struct passwd *pw;
101210389Sgaborextern	int guest;
102210389Sgaborextern	int type;
103210389Sgaborextern	int form;
104210430Sdelphijextern	int debug;
105210389Sgaborextern	int timeout;
106210389Sgaborextern	int maxtimeout;
107210389Sgaborextern  int pdata;
108210389Sgaborextern	char *remotehost;
109210389Sgaborextern	char *proctitle;
110210389Sgaborextern	char *globerr;
111210389Sgaborextern	int usedefault;
112210389Sgaborextern  int transflag;
113210389Sgaborextern  char tmpline[];
114210389Sgaborchar	**ftpglob();
115210389Sgabor
116210389SgaborVOIDRET dologout __P((int));
117210389SgaborVOIDRET upper __P((char *));
118210389SgaborVOIDRET nack __P((char *));
119210389SgaborVOIDRET opiefatal __P((char *));
120210389Sgabor
121210389SgaborVOIDRET pass __P((char *));
122210389Sgaborint user __P((char *));
123210389SgaborVOIDRET passive __P((void));
124210389SgaborVOIDRET retrieve __P((char *, char *));
125210430SdelphijVOIDRET store __P((char *, char *, int));
126210389SgaborVOIDRET send_file_list __P((char *));
127210389SgaborVOIDRET statfilecmd __P((char *));
128210389SgaborVOIDRET statcmd __P((void));
129210389SgaborVOIDRET delete __P((char *));
130210389SgaborVOIDRET renamecmd __P((char *, char *));
131210389SgaborVOIDRET cwd __P((char *));
132210389SgaborVOIDRET makedir __P((char *));
133210389SgaborVOIDRET removedir __P((char *));
134210389SgaborVOIDRET pwd __P((void));
135210389Sgabor
136210389SgaborVOIDRET sizecmd __P((char *));
137210389Sgabor
138210389Sgaboroff_t	restart_point;
139210389Sgabor
140210389Sgaborstatic	int cmd_type;
141210389Sgaborstatic	int cmd_form;
142210389Sgaborstatic	int cmd_bytesz;
143210389Sgaborstatic  unsigned short cliport = 0;
144210389Sgaborchar	cbuf[512];
145210578Sgaborchar	*fromname;
146210430Sdelphij
147210430Sdelphijstruct tab {
148210430Sdelphij	char	*name;
149211364Sgabor	short	token;
150211364Sgabor	short	state;
151211364Sgabor	short	implemented;	/* 1 if command is implemented */
152210430Sdelphij	char	*help;
153210578Sgabor};
154210430Sdelphij
155210430SdelphijVOIDRET help __P((struct tab *, char *));
156210389Sgabor
157210578Sgaborstruct tab cmdtab[], sitetab[];
158210578Sgabor
159210389Sgabor%}
160210389Sgabor
161210389Sgabor%token
162210389Sgabor	A	B	C	E	F	I
163210389Sgabor	L	N	P	R	S	T
164210389Sgabor
165210389Sgabor	SP	CRLF	COMMA	STRING	NUMBER
166210430Sdelphij
167210389Sgabor	USER	PASS	ACCT	REIN	QUIT	PORT
168210389Sgabor	PASV	TYPE	STRU	MODE	RETR	STOR
169210389Sgabor	APPE	MLFL	MAIL	MSND	MSOM	MSAM
170210389Sgabor	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
171210389Sgabor	ABOR	DELE	CWD	LIST	NLST	SITE
172210389Sgabor	STAT	HELP	NOOP	MKD	RMD	PWD
173210389Sgabor	CDUP	STOU	SMNT	SYST	SIZE	MDTM
174210389Sgabor
175210389Sgabor	UMASK	IDLE	CHMOD
176210389Sgabor
177210389Sgabor	LEXERR
178210389Sgabor
179210389Sgabor%start	cmd_list
180210389Sgabor
181210389Sgabor%%
182210389Sgabor
183210389Sgaborcmd_list:	/* empty */
184210389Sgabor	|	cmd_list cmd
185210389Sgabor		= {
186210389Sgabor			fromname = (char *) 0;
187210389Sgabor			restart_point = (off_t) 0;
188211463Sgabor		}
189210389Sgabor	|	cmd_list rcmd
190210389Sgabor	;
191210389Sgabor
192210389Sgaborcmd:		USER SP username CRLF
193210389Sgabor		= {
194210389Sgabor			user((char *) $3);
195210389Sgabor			free((char *) $3);
196210389Sgabor		}
197210389Sgabor	|	PASS SP password CRLF
198210389Sgabor		= {
199210389Sgabor			pass((char *) $3);
200210389Sgabor			free((char *) $3);
201210389Sgabor		}
202210389Sgabor        |   PORT check_login SP host_port CRLF
203210389Sgabor                = {
204210389Sgabor             usedefault = 0;
205210389Sgabor             if (pdata >= 0) {
206210389Sgabor                 (void) close(pdata);
207210389Sgabor                 pdata = -1;
208210389Sgabor             }
209210389Sgabor/* H* port fix, part B: admonish the twit.
210210389Sgabor   Also require login before PORT works */
211210389Sgabor            if ($2) {
212210389Sgabor              if ((cliport > 1023) && (data_dest.sin_addr.s_addr > 0)) {
213210389Sgabor                reply(200, "PORT command successful.");
214210389Sgabor              } else {
215210389Sgabor                syslog (LOG_WARNING, "refused %s from %s",
216210389Sgabor                       cbuf, remotehost);
217210389Sgabor                reply(500, "You've GOT to be joking.");
218210389Sgabor              }
219211463Sgabor            }
220210389Sgabor                }
221210389Sgabor/*	|	PASV CRLF
222210389Sgabor		= {
223210389Sgabor			passive();
224210389Sgabor		} */
225210389Sgabor    |   PASV check_login CRLF
226210389Sgabor        = {
227210389Sgabor/* Require login for PASV, too.  This actually fixes a bug -- telnet to an
228210389Sgabor   unfixed wu-ftpd and type PASV first off, and it crashes! */
229210389Sgabor            if ($2) {
230210389Sgabor                passive();
231210389Sgabor            }
232210430Sdelphij        }
233210389Sgabor	|	TYPE SP type_code CRLF
234210389Sgabor		= {
235210389Sgabor			switch (cmd_type) {
236210389Sgabor
237210389Sgabor			case TYPE_A:
238210389Sgabor				if (cmd_form == FORM_N) {
239210389Sgabor					reply(200, "Type set to A.");
240210389Sgabor					type = cmd_type;
241210389Sgabor					form = cmd_form;
242210389Sgabor				} else
243210389Sgabor					reply(504, "Form must be N.");
244210389Sgabor				break;
245210389Sgabor
246210389Sgabor			case TYPE_E:
247210389Sgabor				reply(504, "Type E not implemented.");
248210389Sgabor				break;
249210389Sgabor
250210389Sgabor			case TYPE_I:
251210389Sgabor				reply(200, "Type set to I.");
252210389Sgabor				type = cmd_type;
253210389Sgabor				break;
254210389Sgabor
255210389Sgabor			case TYPE_L:
256210389Sgabor#if NBBY == 8
257210389Sgabor				if (cmd_bytesz == 8) {
258210389Sgabor					reply(200,
259210461Sgabor					    "Type set to L (byte size 8).");
260210389Sgabor					type = cmd_type;
261210461Sgabor				} else
262210389Sgabor					reply(504, "Byte size must be 8.");
263210389Sgabor#else /* NBBY == 8 */
264210389Sgabor				UNIMPLEMENTED for NBBY != 8
265210622Sgabor#endif /* NBBY == 8 */
266210389Sgabor			}
267210430Sdelphij		}
268210389Sgabor	|	STRU SP struct_code CRLF
269210389Sgabor		= {
270210389Sgabor			switch ($3) {
271210389Sgabor
272210389Sgabor			case STRU_F:
273210389Sgabor				reply(200, "STRU F ok.");
274210389Sgabor				break;
275210389Sgabor
276210389Sgabor			default:
277210389Sgabor				reply(504, "Unimplemented STRU type.");
278210389Sgabor			}
279210389Sgabor		}
280210389Sgabor	|	MODE SP mode_code CRLF
281220421Sgabor		= {
282210389Sgabor			switch ($3) {
283210389Sgabor
284210389Sgabor			case MODE_S:
285210389Sgabor				reply(200, "MODE S ok.");
286210389Sgabor				break;
287210389Sgabor
288210389Sgabor			default:
289210389Sgabor				reply(502, "Unimplemented MODE type.");
290210389Sgabor			}
291210389Sgabor		}
292210389Sgabor	|	ALLO SP NUMBER CRLF
293210389Sgabor		= {
294210389Sgabor			reply(202, "ALLO command ignored.");
295210389Sgabor		}
296210389Sgabor	|	ALLO SP NUMBER SP R SP NUMBER CRLF
297210389Sgabor		= {
298210389Sgabor			reply(202, "ALLO command ignored.");
299210389Sgabor		}
300210389Sgabor	|	RETR check_login SP pathname CRLF
301210389Sgabor		= {
302210389Sgabor			if ($2 && $4)
303210389Sgabor				retrieve((char *) 0, (char *) $4);
304210389Sgabor			if ($4)
305210389Sgabor				free((char *) $4);
306210389Sgabor		}
307210389Sgabor	|	STOR check_login SP pathname CRLF
308210389Sgabor		= {
309210389Sgabor			if ($2 && $4)
310210389Sgabor				store((char *) $4, "w", 0);
311210389Sgabor			if ($4)
312210389Sgabor				free((char *) $4);
313210389Sgabor		}
314210389Sgabor	|	APPE check_login SP pathname CRLF
315210389Sgabor		= {
316210389Sgabor			if ($2 && $4)
317210389Sgabor				store((char *) $4, "a", 0);
318210389Sgabor			if ($4)
319210389Sgabor				free((char *) $4);
320210389Sgabor		}
321210389Sgabor	|	NLST check_login CRLF
322220421Sgabor		= {
323220421Sgabor			if ($2)
324211364Sgabor				send_file_list(".");
325210389Sgabor		}
326211364Sgabor	|	NLST check_login SP STRING CRLF
327211364Sgabor		= {
328211364Sgabor			if ($2 && $4)
329211364Sgabor				send_file_list((char *) $4);
330210389Sgabor			if ($4)
331211364Sgabor				free((char *) $4);
332211364Sgabor		}
333211364Sgabor	|	LIST check_login CRLF
334211364Sgabor		= {
335211364Sgabor			if ($2)
336211364Sgabor				retrieve(LS_COMMAND, "");
337210389Sgabor		}
338210389Sgabor	|	LIST check_login SP pathname CRLF
339210389Sgabor		= {
340210389Sgabor			if ($2 && $4)
341210389Sgabor                                {
342210389Sgabor                                char buffer[sizeof(LS_COMMAND)+3];
343210389Sgabor                                strcpy(buffer, LS_COMMAND);
344210461Sgabor                                strcat(buffer, " %s");
345210461Sgabor				retrieve(buffer, (char *) $4);
346210389Sgabor                                }
347210389Sgabor			if ($4)
348210389Sgabor				free((char *) $4);
349210389Sgabor		}
350210389Sgabor	|	STAT check_login SP pathname CRLF
351210389Sgabor		= {
352210389Sgabor			if ($2 && $4)
353210389Sgabor				statfilecmd((char *) $4);
354210461Sgabor			if ($4)
355210389Sgabor				free((char *) $4);
356210389Sgabor		}
357210389Sgabor	|	STAT CRLF
358210389Sgabor		= {
359210389Sgabor			statcmd();
360210389Sgabor		}
361210389Sgabor	|	DELE check_login SP pathname CRLF
362210389Sgabor		= {
363210389Sgabor			if ($2 && $4)
364210389Sgabor				delete((char *) $4);
365210389Sgabor			if ($4)
366210389Sgabor				free((char *) $4);
367210479Sgabor		}
368210389Sgabor	|	RNTO SP pathname CRLF
369210389Sgabor		= {
370210389Sgabor			if (fromname) {
371210389Sgabor				renamecmd(fromname, (char *) $3);
372210389Sgabor				free(fromname);
373210389Sgabor				fromname = (char *) 0;
374210389Sgabor			} else {
375210389Sgabor				reply(503, "Bad sequence of commands.");
376210389Sgabor			}
377210389Sgabor			free((char *) $3);
378210389Sgabor		}
379210389Sgabor	|	ABOR CRLF
380210389Sgabor		= {
381210389Sgabor			reply(225, "ABOR command successful.");
382210389Sgabor		}
383210389Sgabor	|	CWD check_login CRLF
384210389Sgabor		= {
385210389Sgabor			if ($2)
386210389Sgabor				cwd(pw->pw_dir);
387210389Sgabor		}
388210389Sgabor	|	CWD check_login SP pathname CRLF
389210389Sgabor		= {
390210389Sgabor			if ($2 && $4)
391210389Sgabor				cwd((char *) $4);
392210389Sgabor			if ($4)
393210389Sgabor				free((char *) $4);
394210389Sgabor		}
395210389Sgabor	|	HELP CRLF
396210389Sgabor		= {
397210389Sgabor			help(cmdtab, (char *) 0);
398210389Sgabor		}
399210389Sgabor	|	HELP SP STRING CRLF
400210389Sgabor		= {
401210389Sgabor			register char *cp = (char *)$3;
402210389Sgabor
403210389Sgabor			if (strncasecmp(cp, "SITE", 4) == 0) {
404210389Sgabor				cp = (char *)$3 + 4;
405210389Sgabor				if (*cp == ' ')
406210389Sgabor					cp++;
407210389Sgabor				if (*cp)
408210389Sgabor					help(sitetab, cp);
409210389Sgabor				else
410210389Sgabor					help(sitetab, (char *) 0);
411210389Sgabor			} else
412210389Sgabor				help(cmdtab, (char *) $3);
413210389Sgabor		}
414210389Sgabor	|	NOOP CRLF
415210389Sgabor		= {
416210389Sgabor			reply(200, "NOOP command successful.");
417210389Sgabor		}
418210389Sgabor	|	MKD check_login SP pathname CRLF
419210389Sgabor		= {
420210389Sgabor			if ($2 && $4)
421210389Sgabor				makedir((char *) $4);
422210389Sgabor			if ($4)
423210389Sgabor				free((char *) $4);
424210389Sgabor		}
425210389Sgabor	|	RMD check_login SP pathname CRLF
426210389Sgabor		= {
427210389Sgabor			if ($2 && $4)
428210389Sgabor				removedir((char *) $4);
429210389Sgabor			if ($4)
430210389Sgabor				free((char *) $4);
431210389Sgabor		}
432210389Sgabor	|	PWD check_login CRLF
433210578Sgabor		= {
434210578Sgabor			if ($2)
435210578Sgabor				pwd();
436210578Sgabor		}
437210578Sgabor	|	CDUP check_login CRLF
438210578Sgabor		= {
439210578Sgabor			if ($2)
440210578Sgabor				cwd("..");
441210578Sgabor		}
442210578Sgabor	|	SITE SP HELP CRLF
443210578Sgabor		= {
444210578Sgabor			help(sitetab, (char *) 0);
445210578Sgabor		}
446210389Sgabor	|	SITE SP HELP SP STRING CRLF
447210389Sgabor		= {
448210389Sgabor			help(sitetab, (char *) $5);
449210389Sgabor		}
450210389Sgabor	|	SITE SP UMASK check_login CRLF
451210389Sgabor		= {
452210389Sgabor			int oldmask;
453210389Sgabor
454210389Sgabor			if ($4) {
455210389Sgabor				oldmask = umask(0);
456210389Sgabor				(void) umask(oldmask);
457210389Sgabor				reply(200, "Current UMASK is %03o", oldmask);
458210389Sgabor			}
459210389Sgabor		}
460210389Sgabor	|	SITE SP UMASK check_login SP octal_number CRLF
461210389Sgabor		= {
462210389Sgabor			int oldmask;
463210389Sgabor
464210389Sgabor			if ($4) {
465210389Sgabor				if (($6 == -1) || ($6 > 0777)) {
466210389Sgabor					reply(501, "Bad UMASK value");
467210389Sgabor				} else {
468210389Sgabor					oldmask = umask($6);
469210389Sgabor					reply(200,
470210389Sgabor					    "UMASK set to %03o (was %03o)",
471210389Sgabor					    $6, oldmask);
472210389Sgabor				}
473210389Sgabor			}
474210389Sgabor		}
475210389Sgabor	|	SITE SP CHMOD check_login SP octal_number SP pathname CRLF
476210389Sgabor		= {
477210389Sgabor			if ($4 && $8) {
478210389Sgabor				if ($6 > 0777)
479210389Sgabor					reply(501,
480210389Sgabor				"CHMOD: Mode value must be between 0 and 0777");
481210389Sgabor				else if (chmod((char *) $8, $6) < 0)
482210389Sgabor					perror_reply(550, (char *) $8);
483210389Sgabor				else
484210389Sgabor					reply(200, "CHMOD command successful.");
485210389Sgabor			}
486210389Sgabor			if ($8)
487210389Sgabor				free((char *) $8);
488210389Sgabor		}
489210389Sgabor	|	SITE SP IDLE CRLF
490210389Sgabor		= {
491210389Sgabor			reply(200,
492210389Sgabor			    "Current IDLE time limit is %d seconds; max %d",
493210389Sgabor				timeout, maxtimeout);
494210389Sgabor		}
495210389Sgabor	|	SITE SP IDLE SP NUMBER CRLF
496210389Sgabor		= {
497210389Sgabor			if ($5 < 30 || $5 > maxtimeout) {
498210389Sgabor				reply(501,
499210389Sgabor			"Maximum IDLE time must be between 30 and %d seconds",
500210389Sgabor				    maxtimeout);
501210389Sgabor			} else {
502210389Sgabor				timeout = $5;
503210389Sgabor				(void) alarm((unsigned) timeout);
504210389Sgabor				reply(200,
505				    "Maximum IDLE time set to %d seconds",
506				    timeout);
507			}
508		}
509	|	STOU check_login SP pathname CRLF
510		= {
511			if ($2 && $4)
512				store((char *) $4, "w", 1);
513			if ($4)
514				free((char *) $4);
515		}
516	|	SYST CRLF
517		= {
518#ifdef unix
519#ifdef BSD
520			reply(215, "UNIX Type: L%d Version: BSD-%d",
521				NBBY, BSD);
522#else /* BSD */
523			reply(215, "UNIX Type: L%d", NBBY);
524#endif /* BSD */
525#else /* unix */
526			reply(215, "UNKNOWN Type: L%d", NBBY);
527#endif /* unix */
528		}
529
530		/*
531		 * SIZE is not in RFC959, but Postel has blessed it and
532		 * it will be in the updated RFC.
533		 *
534		 * Return size of file in a format suitable for
535		 * using with RESTART (we just count bytes).
536		 */
537	|	SIZE check_login SP pathname CRLF
538		= {
539			if ($2 && $4)
540				sizecmd((char *) $4);
541			if ($4)
542				free((char *) $4);
543		}
544
545		/*
546		 * MDTM is not in RFC959, but Postel has blessed it and
547		 * it will be in the updated RFC.
548		 *
549		 * Return modification time of file as an ISO 3307
550		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
551		 * where xxx is the fractional second (of any precision,
552		 * not necessarily 3 digits)
553		 */
554	|	MDTM check_login SP pathname CRLF
555		= {
556			if ($2 && $4) {
557				struct stat stbuf;
558				if (stat((char *) $4, &stbuf) < 0)
559					perror_reply(550, (char *) $4);
560				else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
561					reply(550, "%s: not a plain file.",
562						(char *) $4);
563				} else {
564					register struct tm *t;
565					struct tm *gmtime();
566					t = gmtime(&stbuf.st_mtime);
567					reply(213,
568					    "19%02d%02d%02d%02d%02d%02d",
569					    t->tm_year, t->tm_mon+1, t->tm_mday,
570					    t->tm_hour, t->tm_min, t->tm_sec);
571				}
572			}
573			if ($4)
574				free((char *) $4);
575		}
576	|	QUIT CRLF
577		= {
578			reply(221, "Goodbye.");
579			dologout(0);
580		}
581	|	error CRLF
582		= {
583			yyerrok;
584		}
585	;
586rcmd:		RNFR check_login SP pathname CRLF
587		= {
588			char *renamefrom();
589
590			restart_point = (off_t) 0;
591			if ($2 && $4) {
592				fromname = renamefrom((char *) $4);
593				if (fromname == (char *) 0 && $4) {
594					free((char *) $4);
595				}
596			}
597		}
598	|	REST SP byte_size CRLF
599		= {
600			long atol();
601
602			fromname = (char *) 0;
603			restart_point = $3;
604			reply(350, "Restarting at %ld. %s", restart_point,
605			    "Send STORE or RETRIEVE to initiate transfer.");
606		}
607	;
608
609username:	STRING
610	;
611
612password:	/* empty */
613		= {
614			*(char **)&($$) = (char *)calloc(1, sizeof(char));
615		}
616	|	STRING
617	;
618
619byte_size:	NUMBER
620	;
621
622host_port:	NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
623		NUMBER COMMA NUMBER
624		= {
625			register char *a, *p;
626
627			a = (char *)&data_dest.sin_addr;
628			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
629
630/* H* port fix, part A-1: Check the args against the client addr */
631            p = (char *)&his_addr.sin_addr;
632             if (memcmp (a, p, sizeof (data_dest.sin_addr)))
633                 memset (a, 0, sizeof (data_dest.sin_addr));     /* XXX */
634
635			p = (char *)&data_dest.sin_port;
636
637/* H* port fix, part A-2: only allow client ports in "user space" */
638            p[0] = 0; p[1] = 0;
639            cliport = ($9 << 8) + $11;
640            if (cliport > 1023) {
641                 p[0] = $9; p[1] = $11;
642            }
643
644			p[0] = $9; p[1] = $11;
645			data_dest.sin_family = AF_INET;
646		}
647	;
648
649form_code:	N
650	= {
651		$$ = FORM_N;
652	}
653	|	T
654	= {
655		$$ = FORM_T;
656	}
657	|	C
658	= {
659		$$ = FORM_C;
660	}
661	;
662
663type_code:	A
664	= {
665		cmd_type = TYPE_A;
666		cmd_form = FORM_N;
667	}
668	|	A SP form_code
669	= {
670		cmd_type = TYPE_A;
671		cmd_form = $3;
672	}
673	|	E
674	= {
675		cmd_type = TYPE_E;
676		cmd_form = FORM_N;
677	}
678	|	E SP form_code
679	= {
680		cmd_type = TYPE_E;
681		cmd_form = $3;
682	}
683	|	I
684	= {
685		cmd_type = TYPE_I;
686	}
687	|	L
688	= {
689		cmd_type = TYPE_L;
690		cmd_bytesz = NBBY;
691	}
692	|	L SP byte_size
693	= {
694		cmd_type = TYPE_L;
695		cmd_bytesz = $3;
696	}
697	/* this is for a bug in the BBN ftp */
698	|	L byte_size
699	= {
700		cmd_type = TYPE_L;
701		cmd_bytesz = $2;
702	}
703	;
704
705struct_code:	F
706	= {
707		$$ = STRU_F;
708	}
709	|	R
710	= {
711		$$ = STRU_R;
712	}
713	|	P
714	= {
715		$$ = STRU_P;
716	}
717	;
718
719mode_code:	S
720	= {
721		$$ = MODE_S;
722	}
723	|	B
724	= {
725		$$ = MODE_B;
726	}
727	|	C
728	= {
729		$$ = MODE_C;
730	}
731	;
732
733pathname:	pathstring
734	= {
735		/*
736		 * Problem: this production is used for all pathname
737		 * processing, but only gives a 550 error reply.
738		 * This is a valid reply in some cases but not in others.
739		 */
740		if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) {
741			*(char **)&($$) = *ftpglob((char *) $1);
742			if (globerr != NULL) {
743				reply(550, globerr);
744/*				$$ = NULL; */
745				$$ = 0;
746			}
747			free((char *) $1);
748		} else
749			$$ = $1;
750	}
751	;
752
753pathstring:	STRING
754	;
755
756octal_number:	NUMBER
757	= {
758		register int ret, dec, multby, digit;
759
760		/*
761		 * Convert a number that was read as decimal number
762		 * to what it would be if it had been read as octal.
763		 */
764		dec = $1;
765		multby = 1;
766		ret = 0;
767		while (dec) {
768			digit = dec%10;
769			if (digit > 7) {
770				ret = -1;
771				break;
772			}
773			ret += digit * multby;
774			multby *= 8;
775			dec /= 10;
776		}
777		$$ = ret;
778	}
779	;
780
781check_login:	/* empty */
782	= {
783		if (logged_in)
784			$$ = 1;
785		else {
786			reply(530, "Please login with USER and PASS.");
787			$$ = 0;
788		}
789	}
790	;
791
792%%
793
794extern jmp_buf errcatch;
795
796#define	CMD	0	/* beginning of command */
797#define	ARGS	1	/* expect miscellaneous arguments */
798#define	STR1	2	/* expect SP followed by STRING */
799#define	STR2	3	/* expect STRING */
800#define	OSTR	4	/* optional SP then STRING */
801#define	ZSTR1	5	/* SP then optional STRING */
802#define	ZSTR2	6	/* optional STRING after SP */
803#define	SITECMD	7	/* SITE command */
804#define	NSTR	8	/* Number followed by a string */
805
806struct tab cmdtab[] = {		/* In order defined in RFC 765 */
807	{ "USER", USER, STR1, 1,	"<sp> username" },
808	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
809	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
810	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
811	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
812	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
813	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
814	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
815	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
816	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
817	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
818	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
819	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
820	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
821	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
822	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
823	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
824	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
825	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
826	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
827	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
828	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
829	{ "REST", REST, ARGS, 1,	"(restart command)" },
830	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
831	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
832	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
833	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
834	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
835	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
836	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
837	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
838	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
839	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
840	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
841	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
842	{ "NOOP", NOOP, ARGS, 1,	"" },
843	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
844	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
845	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
846	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
847	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
848	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
849	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
850	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
851	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
852	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
853	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
854	{ NULL,   0,    0,    0,	0 }
855};
856
857struct tab sitetab[] = {
858	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
859	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
860	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
861	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
862	{ NULL,   0,    0,    0,	0 }
863};
864
865struct tab *lookup FUNCTION((p, cmd), register struct tab *p AND char *cmd)
866{
867
868	for (; p->name != NULL; p++)
869		if (strcmp(cmd, p->name) == 0)
870			return (p);
871	return (0);
872}
873
874#include <arpa/telnet.h>
875
876/*
877 * getline - a hacked up version of fgets to ignore TELNET escape codes.
878 */
879char *getline FUNCTION((s, n, iop), char *s AND int n AND FILE *iop)
880{
881	register c;
882	register char *cs;
883
884	cs = s;
885/* tmpline may contain saved command from urgent mode interruption */
886	for (c = 0; *(tmpline + c) && --n > 0; ++c) {
887		*cs++ = *(tmpline + c);
888		if (*(tmpline + c) == '\n') {
889			*cs++ = '\0';
890			if (debug)
891				syslog(LOG_DEBUG, "command: %s", s);
892			*tmpline = '\0';
893			return(s);
894		}
895		if (c == 0)
896			*tmpline = '\0';
897	}
898	while ((c = getc(iop)) != EOF) {
899		c &= 0377;
900		if (c == IAC) {
901		    if ((c = getc(iop)) != EOF) {
902			c &= 0377;
903			switch (c) {
904			case WILL:
905			case WONT:
906				c = getc(iop);
907				printf("%c%c%c", IAC, DONT, 0377&c);
908				(void) fflush(stdout);
909				continue;
910			case DO:
911			case DONT:
912				c = getc(iop);
913				printf("%c%c%c", IAC, WONT, 0377&c);
914				(void) fflush(stdout);
915				continue;
916			case IAC:
917				break;
918			default:
919				continue;	/* ignore command */
920			}
921		    }
922		}
923		*cs++ = c;
924		if (--n <= 0 || c == '\n')
925			break;
926	}
927	if (c == EOF && cs == s)
928		return (NULL);
929	*cs++ = '\0';
930	if (debug)
931		syslog(LOG_DEBUG, "command: %s", s);
932	return (s);
933}
934
935static VOIDRET toolong FUNCTION((input), int input)
936{
937	time_t now;
938
939	reply(421, "Timeout (%d seconds): closing control connection.", timeout);
940	(void) time(&now);
941        syslog(LOG_INFO, "User %s timed out after %d seconds at %s",
942          (pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
943	dologout(1);
944}
945
946int yylex FUNCTION_NOARGS
947{
948	static int cpos, state;
949	register char *cp, *cp2;
950	register struct tab *p;
951	int n;
952	char c, *copy();
953
954	for (;;) {
955		switch (state) {
956
957		case CMD:
958			(void) signal(SIGALRM, toolong);
959			(void) alarm((unsigned) timeout);
960			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
961				reply(221, "You could at least say goodbye.");
962				dologout(0);
963			}
964			(void) alarm(0);
965#ifdef SETPROCTITLE
966			if (strncasecmp(cbuf, "PASS", 4) != NULL)
967				setproctitle("%s: %s", proctitle, cbuf);
968#endif /* SETPROCTITLE */
969			if ((cp = strchr(cbuf, '\r'))) {
970				*cp++ = '\n';
971				*cp = '\0';
972			}
973			if ((cp = strpbrk(cbuf, " \n")))
974				cpos = cp - cbuf;
975			if (cpos == 0)
976				cpos = 4;
977			c = cbuf[cpos];
978			cbuf[cpos] = '\0';
979			upper(cbuf);
980			p = lookup(cmdtab, cbuf);
981			cbuf[cpos] = c;
982			if (p != 0) {
983				if (p->implemented == 0) {
984					nack(p->name);
985					longjmp(errcatch,0);
986					/* NOTREACHED */
987				}
988				state = p->state;
989				*(char **)&yylval = p->name;
990				return (p->token);
991			}
992			break;
993
994		case SITECMD:
995			if (cbuf[cpos] == ' ') {
996				cpos++;
997				return (SP);
998			}
999			cp = &cbuf[cpos];
1000			if ((cp2 = strpbrk(cp, " \n")))
1001				cpos = cp2 - cbuf;
1002			c = cbuf[cpos];
1003			cbuf[cpos] = '\0';
1004			upper(cp);
1005			p = lookup(sitetab, cp);
1006			cbuf[cpos] = c;
1007			if (p != 0) {
1008				if (p->implemented == 0) {
1009					state = CMD;
1010					nack(p->name);
1011					longjmp(errcatch,0);
1012					/* NOTREACHED */
1013				}
1014				state = p->state;
1015				*(char **)&yylval = p->name;
1016				return (p->token);
1017			}
1018			state = CMD;
1019			break;
1020
1021		case OSTR:
1022			if (cbuf[cpos] == '\n') {
1023				state = CMD;
1024				return (CRLF);
1025			}
1026			/* FALLTHROUGH */
1027
1028		case STR1:
1029		case ZSTR1:
1030		dostr1:
1031			if (cbuf[cpos] == ' ') {
1032				cpos++;
1033				state = state == OSTR ? STR2 : ++state;
1034				return (SP);
1035			}
1036			break;
1037
1038		case ZSTR2:
1039			if (cbuf[cpos] == '\n') {
1040				state = CMD;
1041				return (CRLF);
1042			}
1043			/* FALLTHROUGH */
1044
1045		case STR2:
1046			cp = &cbuf[cpos];
1047			n = strlen(cp);
1048			cpos += n - 1;
1049			/*
1050			 * Make sure the string is nonempty and \n terminated.
1051			 */
1052			if (n > 1 && cbuf[cpos] == '\n') {
1053				cbuf[cpos] = '\0';
1054				*(char **)&yylval = copy(cp);
1055				cbuf[cpos] = '\n';
1056				state = ARGS;
1057				return (STRING);
1058			}
1059			break;
1060
1061		case NSTR:
1062			if (cbuf[cpos] == ' ') {
1063				cpos++;
1064				return (SP);
1065			}
1066			if (isdigit(cbuf[cpos])) {
1067				cp = &cbuf[cpos];
1068				while (isdigit(cbuf[++cpos]))
1069					;
1070				c = cbuf[cpos];
1071				cbuf[cpos] = '\0';
1072				yylval = atoi(cp);
1073				cbuf[cpos] = c;
1074				state = STR1;
1075				return (NUMBER);
1076			}
1077			state = STR1;
1078			goto dostr1;
1079
1080		case ARGS:
1081			if (isdigit(cbuf[cpos])) {
1082				cp = &cbuf[cpos];
1083				while (isdigit(cbuf[++cpos]))
1084					;
1085				c = cbuf[cpos];
1086				cbuf[cpos] = '\0';
1087				yylval = atoi(cp);
1088				cbuf[cpos] = c;
1089				return (NUMBER);
1090			}
1091			switch (cbuf[cpos++]) {
1092
1093			case '\n':
1094				state = CMD;
1095				return (CRLF);
1096
1097			case ' ':
1098				return (SP);
1099
1100			case ',':
1101				return (COMMA);
1102
1103			case 'A':
1104			case 'a':
1105				return (A);
1106
1107			case 'B':
1108			case 'b':
1109				return (B);
1110
1111			case 'C':
1112			case 'c':
1113				return (C);
1114
1115			case 'E':
1116			case 'e':
1117				return (E);
1118
1119			case 'F':
1120			case 'f':
1121				return (F);
1122
1123			case 'I':
1124			case 'i':
1125				return (I);
1126
1127			case 'L':
1128			case 'l':
1129				return (L);
1130
1131			case 'N':
1132			case 'n':
1133				return (N);
1134
1135			case 'P':
1136			case 'p':
1137				return (P);
1138
1139			case 'R':
1140			case 'r':
1141				return (R);
1142
1143			case 'S':
1144			case 's':
1145				return (S);
1146
1147			case 'T':
1148			case 't':
1149				return (T);
1150
1151			}
1152			break;
1153
1154		default:
1155			opiefatal("Unknown state in scanner.");
1156		}
1157		yyerror((char *) 0);
1158		state = CMD;
1159		longjmp(errcatch,0);
1160	}
1161}
1162
1163VOIDRET upper FUNCTION((s), char *s)
1164{
1165	while (*s != '\0') {
1166		if (islower(*s))
1167			*s = toupper(*s);
1168		s++;
1169	}
1170}
1171
1172char *copy FUNCTION((s), char *s)
1173{
1174	char *p;
1175
1176	p = malloc((unsigned) strlen(s) + 1);
1177	if (p == NULL)
1178		opiefatal("Ran out of memory.");
1179	(void) strcpy(p, s);
1180	return (p);
1181}
1182
1183VOIDRET help FUNCTION((ctab, s), struct tab *ctab AND char *s)
1184{
1185	register struct tab *c;
1186	register int width, NCMDS;
1187	char *type;
1188
1189	if (ctab == sitetab)
1190		type = "SITE ";
1191	else
1192		type = "";
1193	width = 0, NCMDS = 0;
1194	for (c = ctab; c->name != NULL; c++) {
1195		int len = strlen(c->name);
1196
1197		if (len > width)
1198			width = len;
1199		NCMDS++;
1200	}
1201	width = (width + 8) &~ 7;
1202	if (s == 0) {
1203		register int i, j, w;
1204		int columns, lines;
1205
1206		lreply(214, "The following %scommands are recognized %s.",
1207		    type, "(* =>'s unimplemented)");
1208		columns = 76 / width;
1209		if (columns == 0)
1210			columns = 1;
1211		lines = (NCMDS + columns - 1) / columns;
1212		for (i = 0; i < lines; i++) {
1213			printf("   ");
1214			for (j = 0; j < columns; j++) {
1215				c = ctab + j * lines + i;
1216				printf("%s%c", c->name,
1217					c->implemented ? ' ' : '*');
1218				if (c + lines >= &ctab[NCMDS])
1219					break;
1220				w = strlen(c->name) + 1;
1221				while (w < width) {
1222					putchar(' ');
1223					w++;
1224				}
1225			}
1226			printf("\r\n");
1227		}
1228		(void) fflush(stdout);
1229		reply(214, " ");
1230		return;
1231	}
1232	upper(s);
1233	c = lookup(ctab, s);
1234	if (c == (struct tab *)0) {
1235		reply(502, "Unknown command %s.", s);
1236		return;
1237	}
1238	if (c->implemented)
1239		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1240	else
1241		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1242		    c->name, c->help);
1243}
1244
1245VOIDRET sizecmd FUNCTION((filename), char *filename)
1246{
1247	switch (type) {
1248	case TYPE_L:
1249	case TYPE_I: {
1250		struct stat stbuf;
1251		if (stat(filename, &stbuf) < 0 ||
1252		    (stbuf.st_mode&S_IFMT) != S_IFREG)
1253			reply(550, "%s: not a plain file.", filename);
1254		else
1255			reply(213, "%lu", stbuf.st_size);
1256		break;}
1257	case TYPE_A: {
1258		FILE *fin;
1259		register int c;
1260		register long count;
1261		struct stat stbuf;
1262		fin = fopen(filename, "r");
1263		if (fin == NULL) {
1264			perror_reply(550, filename);
1265			return;
1266		}
1267		if (fstat(fileno(fin), &stbuf) < 0 ||
1268		    (stbuf.st_mode&S_IFMT) != S_IFREG) {
1269			reply(550, "%s: not a plain file.", filename);
1270			(void) fclose(fin);
1271			return;
1272		}
1273
1274		count = 0;
1275		while((c=getc(fin)) != EOF) {
1276			if (c == '\n')	/* will get expanded to \r\n */
1277				count++;
1278			count++;
1279		}
1280		(void) fclose(fin);
1281
1282		reply(213, "%ld", count);
1283		break;}
1284	default:
1285		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1286	}
1287}
1288