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