ftpcmd.y revision 59121
1/* ftpcmd.y: yacc parser for the FTP daemon.
2
3%%% portions-copyright-cmetz-96
4Portions of this software are Copyright 1996-1998 by Craig Metz, All Rights
5Reserved. The Inner Net License Version 2 applies to these portions of
6the software.
7You should have received a copy of the license with this software. If
8you didn't get a copy, you may request one from <license@inner.net>.
9
10	History:
11
12	Modified by cmetz for OPIE 2.3. Moved LS_COMMAND here.
13        Modified by cmetz for OPIE 2.2. Fixed a *lot* of warnings.
14                Use FUNCTION declaration et al. Removed useless strings.
15                Changed some char []s to char *s. Deleted comment address.
16                Changed tmpline references to be more pure-pointer
17                references. Changed tmpline declaration back to char [].
18	Modified at NRL for OPIE 2.1. Minor changes for autoconf.
19        Modified at NRL for OPIE 2.01. Added forward declaration for sitetab[]
20                -- fixes problems experienced by bison users. Merged in new
21                PORT attack fixes from Hobbit.
22	Modified at NRL for OPIE 2.0.
23	Originally from BSD.
24
25$FreeBSD: head/contrib/opie/ftpcmd.y 59121 2000-04-10 11:18:54Z kris $
26*/
27/*
28 * Copyright (c) 1985, 1988 Regents of the University of California.
29 * All rights reserved.
30 *
31 * Redistribution and use in source and binary forms, with or without
32 * modification, are permitted provided that the following conditions
33 * are met:
34 * 1. Redistributions of source code must retain the above copyright
35 *    notice, this list of conditions and the following disclaimer.
36 * 2. Redistributions in binary form must reproduce the above copyright
37 *    notice, this list of conditions and the following disclaimer in the
38 *    documentation and/or other materials provided with the distribution.
39 * 3. All advertising materials mentioning features or use of this software
40 *    must display the following acknowledgement:
41 *	This product includes software developed by the University of
42 *	California, Berkeley and its contributors.
43 * 4. Neither the name of the University nor the names of its contributors
44 *    may be used to endorse or promote products derived from this software
45 *    without specific prior written permission.
46 *
47 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
48 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
49 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
50 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
51 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
52 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
53 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
54 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
55 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
56 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
57 * SUCH DAMAGE.
58 *
59 *	@(#)ftpcmd.y	5.24 (Berkeley) 2/25/91
60 */
61
62/*
63 * Grammar for FTP commands.
64 * See RFC 959.
65 */
66
67%{
68#include "opie_cfg.h"
69
70#include <sys/param.h>
71#include <sys/types.h>
72#include <sys/socket.h>
73#include <sys/stat.h>
74#include <netinet/in.h>
75#include <arpa/ftp.h>
76#include <signal.h>
77#include <setjmp.h>
78#include <syslog.h>
79#if TM_IN_SYS_TIME
80#include <sys/time.h>
81#else /* TM_IN_SYS_TIME */
82#include <time.h>
83#endif /* TM_IN_SYS_TIME */
84#include <pwd.h>
85#include <unistd.h>
86#include <stdio.h>
87#include <ctype.h>
88#include <stdlib.h>
89#include <string.h>
90
91#include "opie.h"
92
93#if HAVE_LS_G_FLAG
94#define LS_COMMAND "/bin/ls -lgA"
95#else /* HAVE_LS_G_FLAG */
96#define LS_COMMAND "/bin/ls -lA"
97#endif /* HAVE_LS_G_FLAG */
98
99extern	struct sockaddr_in data_dest;
100extern  struct sockaddr_in his_addr;
101extern	int logged_in;
102extern	struct passwd *pw;
103extern	int guest;
104extern	int type;
105extern	int form;
106extern	int debug;
107extern	int timeout;
108extern	int maxtimeout;
109extern  int pdata;
110extern	char *remotehost;
111extern	char *proctitle;
112extern	char *globerr;
113extern	int usedefault;
114extern  int transflag;
115extern  char tmpline[];
116char	**ftpglob();
117
118VOIDRET dologout __P((int));
119VOIDRET upper __P((char *));
120VOIDRET nack __P((char *));
121VOIDRET opiefatal __P((char *));
122
123VOIDRET pass __P((char *));
124int user __P((char *));
125VOIDRET passive __P((void));
126VOIDRET retrieve __P((char *, char *));
127VOIDRET store __P((char *, char *, int));
128VOIDRET send_file_list __P((char *));
129VOIDRET statfilecmd __P((char *));
130VOIDRET statcmd __P((void));
131VOIDRET delete __P((char *));
132VOIDRET renamecmd __P((char *, char *));
133VOIDRET cwd __P((char *));
134VOIDRET makedir __P((char *));
135VOIDRET removedir __P((char *));
136VOIDRET pwd __P((void));
137
138VOIDRET sizecmd __P((char *));
139
140off_t	restart_point;
141
142static	int cmd_type;
143static	int cmd_form;
144static	int cmd_bytesz;
145static  unsigned short cliport = 0;
146char	cbuf[512];
147char	*fromname;
148
149struct tab {
150	char	*name;
151	short	token;
152	short	state;
153	short	implemented;	/* 1 if command is implemented */
154	char	*help;
155};
156
157VOIDRET help __P((struct tab *, char *));
158
159struct tab cmdtab[], sitetab[];
160
161%}
162
163%token
164	A	B	C	E	F	I
165	L	N	P	R	S	T
166
167	SP	CRLF	COMMA	STRING	NUMBER
168
169	USER	PASS	ACCT	REIN	QUIT	PORT
170	PASV	TYPE	STRU	MODE	RETR	STOR
171	APPE	MLFL	MAIL	MSND	MSOM	MSAM
172	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
173	ABOR	DELE	CWD	LIST	NLST	SITE
174	STAT	HELP	NOOP	MKD	RMD	PWD
175	CDUP	STOU	SMNT	SYST	SIZE	MDTM
176
177	UMASK	IDLE	CHMOD
178
179	LEXERR
180
181%start	cmd_list
182
183%%
184
185cmd_list:	/* empty */
186	|	cmd_list cmd
187		= {
188			fromname = (char *) 0;
189			restart_point = (off_t) 0;
190		}
191	|	cmd_list rcmd
192	;
193
194cmd:		USER SP username CRLF
195		= {
196			user((char *) $3);
197			free((char *) $3);
198		}
199	|	PASS SP password CRLF
200		= {
201			pass((char *) $3);
202			free((char *) $3);
203		}
204        |   PORT check_login SP host_port CRLF
205                = {
206             usedefault = 0;
207             if (pdata >= 0) {
208                 (void) close(pdata);
209                 pdata = -1;
210             }
211/* H* port fix, part B: admonish the twit.
212   Also require login before PORT works */
213            if ($2) {
214              if ((cliport > 1023) && (data_dest.sin_addr.s_addr > 0)) {
215                reply(200, "PORT command successful.");
216              } else {
217                syslog (LOG_WARNING, "refused %s from %s",
218                       cbuf, remotehost);
219                reply(500, "You've GOT to be joking.");
220              }
221            }
222                }
223/*	|	PASV CRLF
224		= {
225			passive();
226		} */
227    |   PASV check_login CRLF
228        = {
229/* Require login for PASV, too.  This actually fixes a bug -- telnet to an
230   unfixed wu-ftpd and type PASV first off, and it crashes! */
231            if ($2) {
232                passive();
233            }
234        }
235	|	TYPE SP type_code CRLF
236		= {
237			switch (cmd_type) {
238
239			case TYPE_A:
240				if (cmd_form == FORM_N) {
241					reply(200, "Type set to A.");
242					type = cmd_type;
243					form = cmd_form;
244				} else
245					reply(504, "Form must be N.");
246				break;
247
248			case TYPE_E:
249				reply(504, "Type E not implemented.");
250				break;
251
252			case TYPE_I:
253				reply(200, "Type set to I.");
254				type = cmd_type;
255				break;
256
257			case TYPE_L:
258#if NBBY == 8
259				if (cmd_bytesz == 8) {
260					reply(200,
261					    "Type set to L (byte size 8).");
262					type = cmd_type;
263				} else
264					reply(504, "Byte size must be 8.");
265#else /* NBBY == 8 */
266				UNIMPLEMENTED for NBBY != 8
267#endif /* NBBY == 8 */
268			}
269		}
270	|	STRU SP struct_code CRLF
271		= {
272			switch ($3) {
273
274			case STRU_F:
275				reply(200, "STRU F ok.");
276				break;
277
278			default:
279				reply(504, "Unimplemented STRU type.");
280			}
281		}
282	|	MODE SP mode_code CRLF
283		= {
284			switch ($3) {
285
286			case MODE_S:
287				reply(200, "MODE S ok.");
288				break;
289
290			default:
291				reply(502, "Unimplemented MODE type.");
292			}
293		}
294	|	ALLO SP NUMBER CRLF
295		= {
296			reply(202, "ALLO command ignored.");
297		}
298	|	ALLO SP NUMBER SP R SP NUMBER CRLF
299		= {
300			reply(202, "ALLO command ignored.");
301		}
302	|	RETR check_login SP pathname CRLF
303		= {
304			if ($2 && $4)
305				retrieve((char *) 0, (char *) $4);
306			if ($4)
307				free((char *) $4);
308		}
309	|	STOR check_login SP pathname CRLF
310		= {
311			if ($2 && $4)
312				store((char *) $4, "w", 0);
313			if ($4)
314				free((char *) $4);
315		}
316	|	APPE check_login SP pathname CRLF
317		= {
318			if ($2 && $4)
319				store((char *) $4, "a", 0);
320			if ($4)
321				free((char *) $4);
322		}
323	|	NLST check_login CRLF
324		= {
325			if ($2)
326				send_file_list(".");
327		}
328	|	NLST check_login SP STRING CRLF
329		= {
330			if ($2 && $4)
331				send_file_list((char *) $4);
332			if ($4)
333				free((char *) $4);
334		}
335	|	LIST check_login CRLF
336		= {
337			if ($2)
338				retrieve(LS_COMMAND, "");
339		}
340	|	LIST check_login SP pathname CRLF
341		= {
342			if ($2 && $4)
343                                {
344                                char buffer[sizeof(LS_COMMAND)+3];
345                                strcpy(buffer, LS_COMMAND);
346                                strcat(buffer, " %s");
347				retrieve(buffer, (char *) $4);
348                                }
349			if ($4)
350				free((char *) $4);
351		}
352	|	STAT check_login SP pathname CRLF
353		= {
354			if ($2 && $4)
355				statfilecmd((char *) $4);
356			if ($4)
357				free((char *) $4);
358		}
359	|	STAT CRLF
360		= {
361			statcmd();
362		}
363	|	DELE check_login SP pathname CRLF
364		= {
365			if ($2 && $4)
366				delete((char *) $4);
367			if ($4)
368				free((char *) $4);
369		}
370	|	RNTO SP pathname CRLF
371		= {
372			if (fromname) {
373				renamecmd(fromname, (char *) $3);
374				free(fromname);
375				fromname = (char *) 0;
376			} else {
377				reply(503, "Bad sequence of commands.");
378			}
379			free((char *) $3);
380		}
381	|	ABOR CRLF
382		= {
383			reply(225, "ABOR command successful.");
384		}
385	|	CWD check_login CRLF
386		= {
387			if ($2)
388				cwd(pw->pw_dir);
389		}
390	|	CWD check_login SP pathname CRLF
391		= {
392			if ($2 && $4)
393				cwd((char *) $4);
394			if ($4)
395				free((char *) $4);
396		}
397	|	HELP CRLF
398		= {
399			help(cmdtab, (char *) 0);
400		}
401	|	HELP SP STRING CRLF
402		= {
403			register char *cp = (char *)$3;
404
405			if (strncasecmp(cp, "SITE", 4) == 0) {
406				cp = (char *)$3 + 4;
407				if (*cp == ' ')
408					cp++;
409				if (*cp)
410					help(sitetab, cp);
411				else
412					help(sitetab, (char *) 0);
413			} else
414				help(cmdtab, (char *) $3);
415		}
416	|	NOOP CRLF
417		= {
418			reply(200, "NOOP command successful.");
419		}
420	|	MKD check_login SP pathname CRLF
421		= {
422			if ($2 && $4)
423				makedir((char *) $4);
424			if ($4)
425				free((char *) $4);
426		}
427	|	RMD check_login SP pathname CRLF
428		= {
429			if ($2 && $4)
430				removedir((char *) $4);
431			if ($4)
432				free((char *) $4);
433		}
434	|	PWD check_login CRLF
435		= {
436			if ($2)
437				pwd();
438		}
439	|	CDUP check_login CRLF
440		= {
441			if ($2)
442				cwd("..");
443		}
444	|	SITE SP HELP CRLF
445		= {
446			help(sitetab, (char *) 0);
447		}
448	|	SITE SP HELP SP STRING CRLF
449		= {
450			help(sitetab, (char *) $5);
451		}
452	|	SITE SP UMASK check_login CRLF
453		= {
454			int oldmask;
455
456			if ($4) {
457				oldmask = umask(0);
458				(void) umask(oldmask);
459				reply(200, "Current UMASK is %03o", oldmask);
460			}
461		}
462	|	SITE SP UMASK check_login SP octal_number CRLF
463		= {
464			int oldmask;
465
466			if ($4) {
467				if (($6 == -1) || ($6 > 0777)) {
468					reply(501, "Bad UMASK value");
469				} else {
470					oldmask = umask($6);
471					reply(200,
472					    "UMASK set to %03o (was %03o)",
473					    $6, oldmask);
474				}
475			}
476		}
477	|	SITE SP CHMOD check_login SP octal_number SP pathname CRLF
478		= {
479			if ($4 && $8) {
480				if ($6 > 0777)
481					reply(501,
482				"CHMOD: Mode value must be between 0 and 0777");
483				else if (chmod((char *) $8, $6) < 0)
484					perror_reply(550, (char *) $8);
485				else
486					reply(200, "CHMOD command successful.");
487			}
488			if ($8)
489				free((char *) $8);
490		}
491	|	SITE SP IDLE CRLF
492		= {
493			reply(200,
494			    "Current IDLE time limit is %d seconds; max %d",
495				timeout, maxtimeout);
496		}
497	|	SITE SP IDLE SP NUMBER CRLF
498		= {
499			if ($5 < 30 || $5 > maxtimeout) {
500				reply(501,
501			"Maximum IDLE time must be between 30 and %d seconds",
502				    maxtimeout);
503			} else {
504				timeout = $5;
505				(void) alarm((unsigned) timeout);
506				reply(200,
507				    "Maximum IDLE time set to %d seconds",
508				    timeout);
509			}
510		}
511	|	STOU check_login SP pathname CRLF
512		= {
513			if ($2 && $4)
514				store((char *) $4, "w", 1);
515			if ($4)
516				free((char *) $4);
517		}
518	|	SYST CRLF
519		= {
520#ifdef unix
521#ifdef BSD
522			reply(215, "UNIX Type: L%d Version: BSD-%d",
523				NBBY, BSD);
524#else /* BSD */
525			reply(215, "UNIX Type: L%d", NBBY);
526#endif /* BSD */
527#else /* unix */
528			reply(215, "UNKNOWN Type: L%d", NBBY);
529#endif /* unix */
530		}
531
532		/*
533		 * SIZE is not in RFC959, but Postel has blessed it and
534		 * it will be in the updated RFC.
535		 *
536		 * Return size of file in a format suitable for
537		 * using with RESTART (we just count bytes).
538		 */
539	|	SIZE check_login SP pathname CRLF
540		= {
541			if ($2 && $4)
542				sizecmd((char *) $4);
543			if ($4)
544				free((char *) $4);
545		}
546
547		/*
548		 * MDTM is not in RFC959, but Postel has blessed it and
549		 * it will be in the updated RFC.
550		 *
551		 * Return modification time of file as an ISO 3307
552		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
553		 * where xxx is the fractional second (of any precision,
554		 * not necessarily 3 digits)
555		 */
556	|	MDTM check_login SP pathname CRLF
557		= {
558			if ($2 && $4) {
559				struct stat stbuf;
560				if (stat((char *) $4, &stbuf) < 0)
561					perror_reply(550, (char *) $4);
562				else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
563					reply(550, "%s: not a plain file.",
564						(char *) $4);
565				} else {
566					register struct tm *t;
567					struct tm *gmtime();
568					t = gmtime(&stbuf.st_mtime);
569					reply(213,
570					    "%d%02d%02d%02d%02d%02d",
571					    t->tm_year+1900, t->tm_mon+1, t->tm_mday,
572					    t->tm_hour, t->tm_min, t->tm_sec);
573				}
574			}
575			if ($4)
576				free((char *) $4);
577		}
578	|	QUIT CRLF
579		= {
580			reply(221, "Goodbye.");
581			dologout(0);
582		}
583	|	error CRLF
584		= {
585			yyerrok;
586		}
587	;
588rcmd:		RNFR check_login SP pathname CRLF
589		= {
590			char *renamefrom();
591
592			restart_point = (off_t) 0;
593			if ($2 && $4) {
594				fromname = renamefrom((char *) $4);
595				if (fromname == (char *) 0 && $4) {
596					free((char *) $4);
597				}
598			}
599		}
600	|	REST SP byte_size CRLF
601		= {
602			long atol();
603
604			fromname = (char *) 0;
605			restart_point = $3;
606			reply(350, "Restarting at %ld. %s", restart_point,
607			    "Send STORE or RETRIEVE to initiate transfer.");
608		}
609	;
610
611username:	STRING
612	;
613
614password:	/* empty */
615		= {
616			*(char **)&($$) = (char *)calloc(1, sizeof(char));
617		}
618	|	STRING
619	;
620
621byte_size:	NUMBER
622	;
623
624host_port:	NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
625		NUMBER COMMA NUMBER
626		= {
627			register char *a, *p;
628
629			a = (char *)&data_dest.sin_addr;
630			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
631
632/* H* port fix, part A-1: Check the args against the client addr */
633            p = (char *)&his_addr.sin_addr;
634             if (memcmp (a, p, sizeof (data_dest.sin_addr)))
635                 memset (a, 0, sizeof (data_dest.sin_addr));     /* XXX */
636
637			p = (char *)&data_dest.sin_port;
638
639/* H* port fix, part A-2: only allow client ports in "user space" */
640            p[0] = 0; p[1] = 0;
641            cliport = ($9 << 8) + $11;
642            if (cliport > 1023) {
643                 p[0] = $9; p[1] = $11;
644            }
645
646			p[0] = $9; p[1] = $11;
647			data_dest.sin_family = AF_INET;
648		}
649	;
650
651form_code:	N
652	= {
653		$$ = FORM_N;
654	}
655	|	T
656	= {
657		$$ = FORM_T;
658	}
659	|	C
660	= {
661		$$ = FORM_C;
662	}
663	;
664
665type_code:	A
666	= {
667		cmd_type = TYPE_A;
668		cmd_form = FORM_N;
669	}
670	|	A SP form_code
671	= {
672		cmd_type = TYPE_A;
673		cmd_form = $3;
674	}
675	|	E
676	= {
677		cmd_type = TYPE_E;
678		cmd_form = FORM_N;
679	}
680	|	E SP form_code
681	= {
682		cmd_type = TYPE_E;
683		cmd_form = $3;
684	}
685	|	I
686	= {
687		cmd_type = TYPE_I;
688	}
689	|	L
690	= {
691		cmd_type = TYPE_L;
692		cmd_bytesz = NBBY;
693	}
694	|	L SP byte_size
695	= {
696		cmd_type = TYPE_L;
697		cmd_bytesz = $3;
698	}
699	/* this is for a bug in the BBN ftp */
700	|	L byte_size
701	= {
702		cmd_type = TYPE_L;
703		cmd_bytesz = $2;
704	}
705	;
706
707struct_code:	F
708	= {
709		$$ = STRU_F;
710	}
711	|	R
712	= {
713		$$ = STRU_R;
714	}
715	|	P
716	= {
717		$$ = STRU_P;
718	}
719	;
720
721mode_code:	S
722	= {
723		$$ = MODE_S;
724	}
725	|	B
726	= {
727		$$ = MODE_B;
728	}
729	|	C
730	= {
731		$$ = MODE_C;
732	}
733	;
734
735pathname:	pathstring
736	= {
737		/*
738		 * Problem: this production is used for all pathname
739		 * processing, but only gives a 550 error reply.
740		 * This is a valid reply in some cases but not in others.
741		 */
742		if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) {
743			*(char **)&($$) = *ftpglob((char *) $1);
744			if (globerr != NULL) {
745				reply(550, globerr);
746/*				$$ = NULL; */
747				$$ = 0;
748			}
749			free((char *) $1);
750		} else
751			$$ = $1;
752	}
753	;
754
755pathstring:	STRING
756	;
757
758octal_number:	NUMBER
759	= {
760		register int ret, dec, multby, digit;
761
762		/*
763		 * Convert a number that was read as decimal number
764		 * to what it would be if it had been read as octal.
765		 */
766		dec = $1;
767		multby = 1;
768		ret = 0;
769		while (dec) {
770			digit = dec%10;
771			if (digit > 7) {
772				ret = -1;
773				break;
774			}
775			ret += digit * multby;
776			multby *= 8;
777			dec /= 10;
778		}
779		$$ = ret;
780	}
781	;
782
783check_login:	/* empty */
784	= {
785		if (logged_in)
786			$$ = 1;
787		else {
788			reply(530, "Please login with USER and PASS.");
789			$$ = 0;
790		}
791	}
792	;
793
794%%
795
796extern jmp_buf errcatch;
797
798#define	CMD	0	/* beginning of command */
799#define	ARGS	1	/* expect miscellaneous arguments */
800#define	STR1	2	/* expect SP followed by STRING */
801#define	STR2	3	/* expect STRING */
802#define	OSTR	4	/* optional SP then STRING */
803#define	ZSTR1	5	/* SP then optional STRING */
804#define	ZSTR2	6	/* optional STRING after SP */
805#define	SITECMD	7	/* SITE command */
806#define	NSTR	8	/* Number followed by a string */
807
808struct tab cmdtab[] = {		/* In order defined in RFC 765 */
809	{ "USER", USER, STR1, 1,	"<sp> username" },
810	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
811	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
812	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
813	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
814	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
815	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
816	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
817	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
818	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
819	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
820	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
821	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
822	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
823	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
824	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
825	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
826	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
827	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
828	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
829	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
830	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
831	{ "REST", REST, ARGS, 1,	"(restart command)" },
832	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
833	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
834	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
835	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
836	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
837	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
838	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
839	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
840	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
841	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
842	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
843	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
844	{ "NOOP", NOOP, ARGS, 1,	"" },
845	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
846	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
847	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
848	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
849	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
850	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
851	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
852	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
853	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
854	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
855	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
856	{ NULL,   0,    0,    0,	0 }
857};
858
859struct tab sitetab[] = {
860	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
861	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
862	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
863	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
864	{ NULL,   0,    0,    0,	0 }
865};
866
867struct tab *lookup FUNCTION((p, cmd), register struct tab *p AND char *cmd)
868{
869
870	for (; p->name != NULL; p++)
871		if (strcmp(cmd, p->name) == 0)
872			return (p);
873	return (0);
874}
875
876#include <arpa/telnet.h>
877
878/*
879 * getline - a hacked up version of fgets to ignore TELNET escape codes.
880 */
881char *getline FUNCTION((s, n, iop), char *s AND int n AND FILE *iop)
882{
883	register c;
884	register char *cs;
885
886	cs = s;
887/* tmpline may contain saved command from urgent mode interruption */
888	for (c = 0; *(tmpline + c) && --n > 0; ++c) {
889		*cs++ = *(tmpline + c);
890		if (*(tmpline + c) == '\n') {
891			*cs++ = '\0';
892			if (debug)
893				syslog(LOG_DEBUG, "command: %s", s);
894			*tmpline = '\0';
895			return(s);
896		}
897		if (c == 0)
898			*tmpline = '\0';
899	}
900	while ((c = getc(iop)) != EOF) {
901		c &= 0377;
902		if (c == IAC) {
903		    if ((c = getc(iop)) != EOF) {
904			c &= 0377;
905			switch (c) {
906			case WILL:
907			case WONT:
908				c = getc(iop);
909				printf("%c%c%c", IAC, DONT, 0377&c);
910				(void) fflush(stdout);
911				continue;
912			case DO:
913			case DONT:
914				c = getc(iop);
915				printf("%c%c%c", IAC, WONT, 0377&c);
916				(void) fflush(stdout);
917				continue;
918			case IAC:
919				break;
920			default:
921				continue;	/* ignore command */
922			}
923		    }
924		}
925		*cs++ = c;
926		if (--n <= 0 || c == '\n')
927			break;
928	}
929	if (c == EOF && cs == s)
930		return (NULL);
931	*cs++ = '\0';
932	if (debug)
933		syslog(LOG_DEBUG, "command: %s", s);
934	return (s);
935}
936
937static VOIDRET toolong FUNCTION((input), int input)
938{
939	time_t now;
940
941	reply(421, "Timeout (%d seconds): closing control connection.", timeout);
942	(void) time(&now);
943        syslog(LOG_INFO, "User %s timed out after %d seconds at %s",
944          (pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
945	dologout(1);
946}
947
948int yylex FUNCTION_NOARGS
949{
950	static int cpos, state;
951	register char *cp, *cp2;
952	register struct tab *p;
953	int n;
954	char c, *copy();
955
956	for (;;) {
957		switch (state) {
958
959		case CMD:
960			(void) signal(SIGALRM, toolong);
961			(void) alarm((unsigned) timeout);
962			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
963				reply(221, "You could at least say goodbye.");
964				dologout(0);
965			}
966			(void) alarm(0);
967#ifdef SETPROCTITLE
968			if (strncasecmp(cbuf, "PASS", 4) != NULL)
969				setproctitle("%s: %s", proctitle, cbuf);
970#endif /* SETPROCTITLE */
971			if ((cp = strchr(cbuf, '\r'))) {
972				*cp++ = '\n';
973				*cp = '\0';
974			}
975			if ((cp = strpbrk(cbuf, " \n")))
976				cpos = cp - cbuf;
977			if (cpos == 0)
978				cpos = 4;
979			c = cbuf[cpos];
980			cbuf[cpos] = '\0';
981			upper(cbuf);
982			p = lookup(cmdtab, cbuf);
983			cbuf[cpos] = c;
984			if (p != 0) {
985				if (p->implemented == 0) {
986					nack(p->name);
987					longjmp(errcatch,0);
988					/* NOTREACHED */
989				}
990				state = p->state;
991				*(char **)&yylval = p->name;
992				return (p->token);
993			}
994			break;
995
996		case SITECMD:
997			if (cbuf[cpos] == ' ') {
998				cpos++;
999				return (SP);
1000			}
1001			cp = &cbuf[cpos];
1002			if ((cp2 = strpbrk(cp, " \n")))
1003				cpos = cp2 - cbuf;
1004			c = cbuf[cpos];
1005			cbuf[cpos] = '\0';
1006			upper(cp);
1007			p = lookup(sitetab, cp);
1008			cbuf[cpos] = c;
1009			if (p != 0) {
1010				if (p->implemented == 0) {
1011					state = CMD;
1012					nack(p->name);
1013					longjmp(errcatch,0);
1014					/* NOTREACHED */
1015				}
1016				state = p->state;
1017				*(char **)&yylval = p->name;
1018				return (p->token);
1019			}
1020			state = CMD;
1021			break;
1022
1023		case OSTR:
1024			if (cbuf[cpos] == '\n') {
1025				state = CMD;
1026				return (CRLF);
1027			}
1028			/* FALLTHROUGH */
1029
1030		case STR1:
1031		case ZSTR1:
1032		dostr1:
1033			if (cbuf[cpos] == ' ') {
1034				cpos++;
1035				state = state == OSTR ? STR2 : ++state;
1036				return (SP);
1037			}
1038			break;
1039
1040		case ZSTR2:
1041			if (cbuf[cpos] == '\n') {
1042				state = CMD;
1043				return (CRLF);
1044			}
1045			/* FALLTHROUGH */
1046
1047		case STR2:
1048			cp = &cbuf[cpos];
1049			n = strlen(cp);
1050			cpos += n - 1;
1051			/*
1052			 * Make sure the string is nonempty and \n terminated.
1053			 */
1054			if (n > 1 && cbuf[cpos] == '\n') {
1055				cbuf[cpos] = '\0';
1056				*(char **)&yylval = copy(cp);
1057				cbuf[cpos] = '\n';
1058				state = ARGS;
1059				return (STRING);
1060			}
1061			break;
1062
1063		case NSTR:
1064			if (cbuf[cpos] == ' ') {
1065				cpos++;
1066				return (SP);
1067			}
1068			if (isdigit(cbuf[cpos])) {
1069				cp = &cbuf[cpos];
1070				while (isdigit(cbuf[++cpos]))
1071					;
1072				c = cbuf[cpos];
1073				cbuf[cpos] = '\0';
1074				yylval = atoi(cp);
1075				cbuf[cpos] = c;
1076				state = STR1;
1077				return (NUMBER);
1078			}
1079			state = STR1;
1080			goto dostr1;
1081
1082		case ARGS:
1083			if (isdigit(cbuf[cpos])) {
1084				cp = &cbuf[cpos];
1085				while (isdigit(cbuf[++cpos]))
1086					;
1087				c = cbuf[cpos];
1088				cbuf[cpos] = '\0';
1089				yylval = atoi(cp);
1090				cbuf[cpos] = c;
1091				return (NUMBER);
1092			}
1093			switch (cbuf[cpos++]) {
1094
1095			case '\n':
1096				state = CMD;
1097				return (CRLF);
1098
1099			case ' ':
1100				return (SP);
1101
1102			case ',':
1103				return (COMMA);
1104
1105			case 'A':
1106			case 'a':
1107				return (A);
1108
1109			case 'B':
1110			case 'b':
1111				return (B);
1112
1113			case 'C':
1114			case 'c':
1115				return (C);
1116
1117			case 'E':
1118			case 'e':
1119				return (E);
1120
1121			case 'F':
1122			case 'f':
1123				return (F);
1124
1125			case 'I':
1126			case 'i':
1127				return (I);
1128
1129			case 'L':
1130			case 'l':
1131				return (L);
1132
1133			case 'N':
1134			case 'n':
1135				return (N);
1136
1137			case 'P':
1138			case 'p':
1139				return (P);
1140
1141			case 'R':
1142			case 'r':
1143				return (R);
1144
1145			case 'S':
1146			case 's':
1147				return (S);
1148
1149			case 'T':
1150			case 't':
1151				return (T);
1152
1153			}
1154			break;
1155
1156		default:
1157			opiefatal("Unknown state in scanner.");
1158		}
1159		yyerror((char *) 0);
1160		state = CMD;
1161		longjmp(errcatch,0);
1162	}
1163}
1164
1165VOIDRET upper FUNCTION((s), char *s)
1166{
1167	while (*s != '\0') {
1168		if (islower(*s))
1169			*s = toupper(*s);
1170		s++;
1171	}
1172}
1173
1174char *copy FUNCTION((s), char *s)
1175{
1176	char *p;
1177
1178	p = malloc((unsigned) strlen(s) + 1);
1179	if (p == NULL)
1180		opiefatal("Ran out of memory.");
1181	(void) strcpy(p, s);
1182	return (p);
1183}
1184
1185VOIDRET help FUNCTION((ctab, s), struct tab *ctab AND char *s)
1186{
1187	register struct tab *c;
1188	register int width, NCMDS;
1189	char *type;
1190
1191	if (ctab == sitetab)
1192		type = "SITE ";
1193	else
1194		type = "";
1195	width = 0, NCMDS = 0;
1196	for (c = ctab; c->name != NULL; c++) {
1197		int len = strlen(c->name);
1198
1199		if (len > width)
1200			width = len;
1201		NCMDS++;
1202	}
1203	width = (width + 8) &~ 7;
1204	if (s == 0) {
1205		register int i, j, w;
1206		int columns, lines;
1207
1208		lreply(214, "The following %scommands are recognized %s.",
1209		    type, "(* =>'s unimplemented)");
1210		columns = 76 / width;
1211		if (columns == 0)
1212			columns = 1;
1213		lines = (NCMDS + columns - 1) / columns;
1214		for (i = 0; i < lines; i++) {
1215			printf("   ");
1216			for (j = 0; j < columns; j++) {
1217				c = ctab + j * lines + i;
1218				printf("%s%c", c->name,
1219					c->implemented ? ' ' : '*');
1220				if (c + lines >= &ctab[NCMDS])
1221					break;
1222				w = strlen(c->name) + 1;
1223				while (w < width) {
1224					putchar(' ');
1225					w++;
1226				}
1227			}
1228			printf("\r\n");
1229		}
1230		(void) fflush(stdout);
1231		reply(214, " ");
1232		return;
1233	}
1234	upper(s);
1235	c = lookup(ctab, s);
1236	if (c == (struct tab *)0) {
1237		reply(502, "Unknown command %s.", s);
1238		return;
1239	}
1240	if (c->implemented)
1241		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1242	else
1243		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1244		    c->name, c->help);
1245}
1246
1247VOIDRET sizecmd FUNCTION((filename), char *filename)
1248{
1249	switch (type) {
1250	case TYPE_L:
1251	case TYPE_I: {
1252		struct stat stbuf;
1253		if (stat(filename, &stbuf) < 0 ||
1254		    (stbuf.st_mode&S_IFMT) != S_IFREG)
1255			reply(550, "%s: not a plain file.", filename);
1256		else
1257			reply(213, "%lu", stbuf.st_size);
1258		break;}
1259	case TYPE_A: {
1260		FILE *fin;
1261		register int c;
1262		register long count;
1263		struct stat stbuf;
1264		fin = fopen(filename, "r");
1265		if (fin == NULL) {
1266			perror_reply(550, filename);
1267			return;
1268		}
1269		if (fstat(fileno(fin), &stbuf) < 0 ||
1270		    (stbuf.st_mode&S_IFMT) != S_IFREG) {
1271			reply(550, "%s: not a plain file.", filename);
1272			(void) fclose(fin);
1273			return;
1274		}
1275
1276		count = 0;
1277		while((c=getc(fin)) != EOF) {
1278			if (c == '\n')	/* will get expanded to \r\n */
1279				count++;
1280			count++;
1281		}
1282		(void) fclose(fin);
1283
1284		reply(213, "%ld", count);
1285		break;}
1286	default:
1287		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1288	}
1289}
1290