ftpcmd.y revision 31940
1/* ftpcmd.y: yacc parser for the FTP daemon.
2
3%%% portions-copyright-cmetz-96
4Portions of this software are Copyright 1996-1997 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/*
26 * Copyright (c) 1985, 1988 Regents of the University of California.
27 * All rights reserved.
28 *
29 * Redistribution and use in source and binary forms, with or without
30 * modification, are permitted provided that the following conditions
31 * are met:
32 * 1. Redistributions of source code must retain the above copyright
33 *    notice, this list of conditions and the following disclaimer.
34 * 2. Redistributions in binary form must reproduce the above copyright
35 *    notice, this list of conditions and the following disclaimer in the
36 *    documentation and/or other materials provided with the distribution.
37 * 3. All advertising materials mentioning features or use of this software
38 *    must display the following acknowledgement:
39 *	This product includes software developed by the University of
40 *	California, Berkeley and its contributors.
41 * 4. Neither the name of the University nor the names of its contributors
42 *    may be used to endorse or promote products derived from this software
43 *    without specific prior written permission.
44 *
45 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
46 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
47 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
48 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
49 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
50 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
51 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
52 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
53 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
54 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
55 * SUCH DAMAGE.
56 *
57 *	@(#)ftpcmd.y	5.24 (Berkeley) 2/25/91
58 */
59
60/*
61 * Grammar for FTP commands.
62 * See RFC 959.
63 */
64
65%{
66#include "opie_cfg.h"
67
68#include <sys/param.h>
69#include <sys/types.h>
70#include <sys/socket.h>
71#include <sys/stat.h>
72#include <netinet/in.h>
73#include <arpa/ftp.h>
74#include <signal.h>
75#include <setjmp.h>
76#include <syslog.h>
77#if TM_IN_SYS_TIME
78#include <sys/time.h>
79#else /* TM_IN_SYS_TIME */
80#include <time.h>
81#endif /* TM_IN_SYS_TIME */
82#include <pwd.h>
83#include <unistd.h>
84#include <stdio.h>
85#include <ctype.h>
86#include <stdlib.h>
87#include <string.h>
88
89#include "opie.h"
90
91#if HAVE_LS_G_FLAG
92#define LS_COMMAND "/bin/ls -lgA"
93#else /* HAVE_LS_G_FLAG */
94#define LS_COMMAND "/bin/ls -lA"
95#endif /* HAVE_LS_G_FLAG */
96
97extern	struct sockaddr_in data_dest;
98extern  struct sockaddr_in his_addr;
99extern	int logged_in;
100extern	struct passwd *pw;
101extern	int guest;
102extern	int type;
103extern	int form;
104extern	int debug;
105extern	int timeout;
106extern	int maxtimeout;
107extern  int pdata;
108extern	char *remotehost;
109extern	char *proctitle;
110extern	char *globerr;
111extern	int usedefault;
112extern  int transflag;
113extern  char tmpline[];
114char	**ftpglob();
115
116VOIDRET dologout __P((int));
117VOIDRET upper __P((char *));
118VOIDRET nack __P((char *));
119VOIDRET opiefatal __P((char *));
120
121VOIDRET pass __P((char *));
122int user __P((char *));
123VOIDRET passive __P((void));
124VOIDRET retrieve __P((char *, char *));
125VOIDRET store __P((char *, char *, int));
126VOIDRET send_file_list __P((char *));
127VOIDRET statfilecmd __P((char *));
128VOIDRET statcmd __P((void));
129VOIDRET delete __P((char *));
130VOIDRET renamecmd __P((char *, char *));
131VOIDRET cwd __P((char *));
132VOIDRET makedir __P((char *));
133VOIDRET removedir __P((char *));
134VOIDRET pwd __P((void));
135
136VOIDRET sizecmd __P((char *));
137
138off_t	restart_point;
139
140static	int cmd_type;
141static	int cmd_form;
142static	int cmd_bytesz;
143static  unsigned short cliport = 0;
144char	cbuf[512];
145char	*fromname;
146
147struct tab {
148	char	*name;
149	short	token;
150	short	state;
151	short	implemented;	/* 1 if command is implemented */
152	char	*help;
153};
154
155VOIDRET help __P((struct tab *, char *));
156
157struct tab cmdtab[], sitetab[];
158
159%}
160
161%token
162	A	B	C	E	F	I
163	L	N	P	R	S	T
164
165	SP	CRLF	COMMA	STRING	NUMBER
166
167	USER	PASS	ACCT	REIN	QUIT	PORT
168	PASV	TYPE	STRU	MODE	RETR	STOR
169	APPE	MLFL	MAIL	MSND	MSOM	MSAM
170	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
171	ABOR	DELE	CWD	LIST	NLST	SITE
172	STAT	HELP	NOOP	MKD	RMD	PWD
173	CDUP	STOU	SMNT	SYST	SIZE	MDTM
174
175	UMASK	IDLE	CHMOD
176
177	LEXERR
178
179%start	cmd_list
180
181%%
182
183cmd_list:	/* empty */
184	|	cmd_list cmd
185		= {
186			fromname = (char *) 0;
187			restart_point = (off_t) 0;
188		}
189	|	cmd_list rcmd
190	;
191
192cmd:		USER SP username CRLF
193		= {
194			user((char *) $3);
195			free((char *) $3);
196		}
197	|	PASS SP password CRLF
198		= {
199			pass((char *) $3);
200			free((char *) $3);
201		}
202        |   PORT check_login SP host_port CRLF
203                = {
204             usedefault = 0;
205             if (pdata >= 0) {
206                 (void) close(pdata);
207                 pdata = -1;
208             }
209/* H* port fix, part B: admonish the twit.
210   Also require login before PORT works */
211            if ($2) {
212              if ((cliport > 1023) && (data_dest.sin_addr.s_addr > 0)) {
213                reply(200, "PORT command successful.");
214              } else {
215                syslog (LOG_WARNING, "refused %s from %s",
216                       cbuf, remotehost);
217                reply(500, "You've GOT to be joking.");
218              }
219            }
220                }
221/*	|	PASV CRLF
222		= {
223			passive();
224		} */
225    |   PASV check_login CRLF
226        = {
227/* Require login for PASV, too.  This actually fixes a bug -- telnet to an
228   unfixed wu-ftpd and type PASV first off, and it crashes! */
229            if ($2) {
230                passive();
231            }
232        }
233	|	TYPE SP type_code CRLF
234		= {
235			switch (cmd_type) {
236
237			case TYPE_A:
238				if (cmd_form == FORM_N) {
239					reply(200, "Type set to A.");
240					type = cmd_type;
241					form = cmd_form;
242				} else
243					reply(504, "Form must be N.");
244				break;
245
246			case TYPE_E:
247				reply(504, "Type E not implemented.");
248				break;
249
250			case TYPE_I:
251				reply(200, "Type set to I.");
252				type = cmd_type;
253				break;
254
255			case TYPE_L:
256#if NBBY == 8
257				if (cmd_bytesz == 8) {
258					reply(200,
259					    "Type set to L (byte size 8).");
260					type = cmd_type;
261				} else
262					reply(504, "Byte size must be 8.");
263#else /* NBBY == 8 */
264				UNIMPLEMENTED for NBBY != 8
265#endif /* NBBY == 8 */
266			}
267		}
268	|	STRU SP struct_code CRLF
269		= {
270			switch ($3) {
271
272			case STRU_F:
273				reply(200, "STRU F ok.");
274				break;
275
276			default:
277				reply(504, "Unimplemented STRU type.");
278			}
279		}
280	|	MODE SP mode_code CRLF
281		= {
282			switch ($3) {
283
284			case MODE_S:
285				reply(200, "MODE S ok.");
286				break;
287
288			default:
289				reply(502, "Unimplemented MODE type.");
290			}
291		}
292	|	ALLO SP NUMBER CRLF
293		= {
294			reply(202, "ALLO command ignored.");
295		}
296	|	ALLO SP NUMBER SP R SP NUMBER CRLF
297		= {
298			reply(202, "ALLO command ignored.");
299		}
300	|	RETR check_login SP pathname CRLF
301		= {
302			if ($2 && $4)
303				retrieve((char *) 0, (char *) $4);
304			if ($4)
305				free((char *) $4);
306		}
307	|	STOR check_login SP pathname CRLF
308		= {
309			if ($2 && $4)
310				store((char *) $4, "w", 0);
311			if ($4)
312				free((char *) $4);
313		}
314	|	APPE check_login SP pathname CRLF
315		= {
316			if ($2 && $4)
317				store((char *) $4, "a", 0);
318			if ($4)
319				free((char *) $4);
320		}
321	|	NLST check_login CRLF
322		= {
323			if ($2)
324				send_file_list(".");
325		}
326	|	NLST check_login SP STRING CRLF
327		= {
328			if ($2 && $4)
329				send_file_list((char *) $4);
330			if ($4)
331				free((char *) $4);
332		}
333	|	LIST check_login CRLF
334		= {
335			if ($2)
336				retrieve(LS_COMMAND, "");
337		}
338	|	LIST check_login SP pathname CRLF
339		= {
340			if ($2 && $4)
341                                {
342                                char buffer[sizeof(LS_COMMAND)+3];
343                                strcpy(buffer, LS_COMMAND);
344                                strcat(buffer, " %s");
345				retrieve(buffer, (char *) $4);
346                                }
347			if ($4)
348				free((char *) $4);
349		}
350	|	STAT check_login SP pathname CRLF
351		= {
352			if ($2 && $4)
353				statfilecmd((char *) $4);
354			if ($4)
355				free((char *) $4);
356		}
357	|	STAT CRLF
358		= {
359			statcmd();
360		}
361	|	DELE check_login SP pathname CRLF
362		= {
363			if ($2 && $4)
364				delete((char *) $4);
365			if ($4)
366				free((char *) $4);
367		}
368	|	RNTO SP pathname CRLF
369		= {
370			if (fromname) {
371				renamecmd(fromname, (char *) $3);
372				free(fromname);
373				fromname = (char *) 0;
374			} else {
375				reply(503, "Bad sequence of commands.");
376			}
377			free((char *) $3);
378		}
379	|	ABOR CRLF
380		= {
381			reply(225, "ABOR command successful.");
382		}
383	|	CWD check_login CRLF
384		= {
385			if ($2)
386				cwd(pw->pw_dir);
387		}
388	|	CWD check_login SP pathname CRLF
389		= {
390			if ($2 && $4)
391				cwd((char *) $4);
392			if ($4)
393				free((char *) $4);
394		}
395	|	HELP CRLF
396		= {
397			help(cmdtab, (char *) 0);
398		}
399	|	HELP SP STRING CRLF
400		= {
401			register char *cp = (char *)$3;
402
403			if (strncasecmp(cp, "SITE", 4) == 0) {
404				cp = (char *)$3 + 4;
405				if (*cp == ' ')
406					cp++;
407				if (*cp)
408					help(sitetab, cp);
409				else
410					help(sitetab, (char *) 0);
411			} else
412				help(cmdtab, (char *) $3);
413		}
414	|	NOOP CRLF
415		= {
416			reply(200, "NOOP command successful.");
417		}
418	|	MKD check_login SP pathname CRLF
419		= {
420			if ($2 && $4)
421				makedir((char *) $4);
422			if ($4)
423				free((char *) $4);
424		}
425	|	RMD check_login SP pathname CRLF
426		= {
427			if ($2 && $4)
428				removedir((char *) $4);
429			if ($4)
430				free((char *) $4);
431		}
432	|	PWD check_login CRLF
433		= {
434			if ($2)
435				pwd();
436		}
437	|	CDUP check_login CRLF
438		= {
439			if ($2)
440				cwd("..");
441		}
442	|	SITE SP HELP CRLF
443		= {
444			help(sitetab, (char *) 0);
445		}
446	|	SITE SP HELP SP STRING CRLF
447		= {
448			help(sitetab, (char *) $5);
449		}
450	|	SITE SP UMASK check_login CRLF
451		= {
452			int oldmask;
453
454			if ($4) {
455				oldmask = umask(0);
456				(void) umask(oldmask);
457				reply(200, "Current UMASK is %03o", oldmask);
458			}
459		}
460	|	SITE SP UMASK check_login SP octal_number CRLF
461		= {
462			int oldmask;
463
464			if ($4) {
465				if (($6 == -1) || ($6 > 0777)) {
466					reply(501, "Bad UMASK value");
467				} else {
468					oldmask = umask($6);
469					reply(200,
470					    "UMASK set to %03o (was %03o)",
471					    $6, oldmask);
472				}
473			}
474		}
475	|	SITE SP CHMOD check_login SP octal_number SP pathname CRLF
476		= {
477			if ($4 && $8) {
478				if ($6 > 0777)
479					reply(501,
480				"CHMOD: Mode value must be between 0 and 0777");
481				else if (chmod((char *) $8, $6) < 0)
482					perror_reply(550, (char *) $8);
483				else
484					reply(200, "CHMOD command successful.");
485			}
486			if ($8)
487				free((char *) $8);
488		}
489	|	SITE SP IDLE CRLF
490		= {
491			reply(200,
492			    "Current IDLE time limit is %d seconds; max %d",
493				timeout, maxtimeout);
494		}
495	|	SITE SP IDLE SP NUMBER CRLF
496		= {
497			if ($5 < 30 || $5 > maxtimeout) {
498				reply(501,
499			"Maximum IDLE time must be between 30 and %d seconds",
500				    maxtimeout);
501			} else {
502				timeout = $5;
503				(void) alarm((unsigned) timeout);
504				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					    "%d%02d%02d%02d%02d%02d",
569					    t->tm_year+1900, 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