ftpcmd.y revision 22347
1187770Sluigi/* ftpcmd.y: yacc parser for the FTP daemon.
2187770Sluigi
3187770Sluigi%%% portions-copyright-cmetz
4187770SluigiPortions of this software are Copyright 1996 by Craig Metz, All Rights
5187770SluigiReserved. The Inner Net License Version 2 applies to these portions of
6187770Sluigithe software.
7187770SluigiYou should have received a copy of the license with this software. If
8187770Sluigiyou didn't get a copy, you may request one from <license@inner.net>.
9187770Sluigi
10187770Sluigi	History:
11187770Sluigi
12187770Sluigi	Modified by cmetz for OPIE 2.3. Moved LS_COMMAND here.
13187770Sluigi        Modified by cmetz for OPIE 2.2. Fixed a *lot* of warnings.
14187770Sluigi                Use FUNCTION declaration et al. Removed useless strings.
15187770Sluigi                Changed some char []s to char *s. Deleted comment address.
16187770Sluigi                Changed tmpline references to be more pure-pointer
17187770Sluigi                references. Changed tmpline declaration back to char [].
18187770Sluigi	Modified at NRL for OPIE 2.1. Minor changes for autoconf.
19187770Sluigi        Modified at NRL for OPIE 2.01. Added forward declaration for sitetab[]
20187770Sluigi                -- fixes problems experienced by bison users. Merged in new
21187770Sluigi                PORT attack fixes from Hobbit.
22187770Sluigi	Modified at NRL for OPIE 2.0.
23187770Sluigi	Originally from BSD.
24187770Sluigi*/
25187770Sluigi/*
26187770Sluigi * Copyright (c) 1985, 1988 Regents of the University of California.
27187770Sluigi * All rights reserved.
28187770Sluigi *
29187770Sluigi * Redistribution and use in source and binary forms, with or without
30187770Sluigi * modification, are permitted provided that the following conditions
31187770Sluigi * are met:
32187770Sluigi * 1. Redistributions of source code must retain the above copyright
33187770Sluigi *    notice, this list of conditions and the following disclaimer.
34187770Sluigi * 2. Redistributions in binary form must reproduce the above copyright
35187770Sluigi *    notice, this list of conditions and the following disclaimer in the
36187770Sluigi *    documentation and/or other materials provided with the distribution.
37187770Sluigi * 3. All advertising materials mentioning features or use of this software
38187770Sluigi *    must display the following acknowledgement:
39187770Sluigi *	This product includes software developed by the University of
40187770Sluigi *	California, Berkeley and its contributors.
41187770Sluigi * 4. Neither the name of the University nor the names of its contributors
42187770Sluigi *    may be used to endorse or promote products derived from this software
43187770Sluigi *    without specific prior written permission.
44187770Sluigi *
45187770Sluigi * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
46187770Sluigi * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
47187770Sluigi * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
48187770Sluigi * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
49187770Sluigi * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
50220802Sglebius * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
51220802Sglebius * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
52220802Sglebius * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
53220802Sglebius * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
54220802Sglebius * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
55220802Sglebius * SUCH DAMAGE.
56223080Sae *
57220802Sglebius *	@(#)ftpcmd.y	5.24 (Berkeley) 2/25/91
58220804Sglebius */
59220802Sglebius
60187770Sluigi/*
61187770Sluigi * Grammar for FTP commands.
62187770Sluigi * See RFC 959.
63187770Sluigi */
64187770Sluigi
65187770Sluigi%{
66187770Sluigi#include "opie_cfg.h"
67220802Sglebius
68187770Sluigi#include <sys/param.h>
69187770Sluigi#include <sys/types.h>
70220802Sglebius#include <sys/socket.h>
71187770Sluigi#include <sys/stat.h>
72187770Sluigi#include <netinet/in.h>
73187770Sluigi#include <arpa/ftp.h>
74187770Sluigi#include <signal.h>
75187770Sluigi#include <setjmp.h>
76187770Sluigi#include <syslog.h>
77187770Sluigi#if TM_IN_SYS_TIME
78187770Sluigi#include <sys/time.h>
79187770Sluigi#else /* TM_IN_SYS_TIME */
80187770Sluigi#include <time.h>
81187770Sluigi#endif /* TM_IN_SYS_TIME */
82187770Sluigi#include <pwd.h>
83187770Sluigi#include <unistd.h>
84187770Sluigi#include <stdio.h>
85187770Sluigi#include <ctype.h>
86187770Sluigi#include <stdlib.h>
87187770Sluigi#include <string.h>
88220804Sglebius
89187770Sluigi#include "opie.h"
90220804Sglebius
91187770Sluigi#if HAVE_LS_G_FLAG
92187770Sluigi#define LS_COMMAND "/bin/ls -lgA"
93187770Sluigi#else /* HAVE_LS_G_FLAG */
94187770Sluigi#define LS_COMMAND "/bin/ls -lA"
95187770Sluigi#endif /* HAVE_LS_G_FLAG */
96187770Sluigi
97187770Sluigiextern	struct sockaddr_in data_dest;
98187770Sluigiextern  struct sockaddr_in his_addr;
99187770Sluigiextern	int logged_in;
100187770Sluigiextern	struct passwd *pw;
101187770Sluigiextern	int guest;
102187770Sluigiextern	int type;
103187770Sluigiextern	int form;
104187770Sluigiextern	int debug;
105187770Sluigiextern	int timeout;
106187770Sluigiextern	int maxtimeout;
107187770Sluigiextern  int pdata;
108187770Sluigiextern	char *remotehost;
109187770Sluigiextern	char *proctitle;
110187770Sluigiextern	char *globerr;
111187770Sluigiextern	int usedefault;
112187770Sluigiextern  int transflag;
113187770Sluigiextern  char tmpline[];
114187770Sluigichar	**ftpglob();
115187770Sluigi
116187770SluigiVOIDRET dologout __P((int));
117187770SluigiVOIDRET upper __P((char *));
118187770SluigiVOIDRET nack __P((char *));
119187770SluigiVOIDRET opiefatal __P((char *));
120187770Sluigi
121187770SluigiVOIDRET pass __P((char *));
122187770Sluigiint user __P((char *));
123187770SluigiVOIDRET passive __P((void));
124187770SluigiVOIDRET retrieve __P((char *, char *));
125187770SluigiVOIDRET store __P((char *, char *, int));
126187770SluigiVOIDRET send_file_list __P((char *));
127187770SluigiVOIDRET statfilecmd __P((char *));
128187770SluigiVOIDRET statcmd __P((void));
129187770SluigiVOIDRET delete __P((char *));
130187770SluigiVOIDRET renamecmd __P((char *, char *));
131187770SluigiVOIDRET cwd __P((char *));
132187770SluigiVOIDRET makedir __P((char *));
133187770SluigiVOIDRET removedir __P((char *));
134187770SluigiVOIDRET pwd __P((void));
135187770Sluigi
136187770SluigiVOIDRET sizecmd __P((char *));
137187770Sluigi
138187770Sluigioff_t	restart_point;
139187770Sluigi
140187770Sluigistatic	int cmd_type;
141187770Sluigistatic	int cmd_form;
142187770Sluigistatic	int cmd_bytesz;
143187770Sluigistatic  unsigned short cliport = 0;
144187770Sluigichar	cbuf[512];
145187770Sluigichar	*fromname;
146187770Sluigi
147187770Sluigistruct tab {
148187770Sluigi	char	*name;
149187770Sluigi	short	token;
150187770Sluigi	short	state;
151187770Sluigi	short	implemented;	/* 1 if command is implemented */
152187770Sluigi	char	*help;
153187770Sluigi};
154187770Sluigi
155187770SluigiVOIDRET help __P((struct tab *, char *));
156187770Sluigi
157187770Sluigistruct tab cmdtab[], sitetab[];
158187770Sluigi
159187770Sluigi%}
160187770Sluigi
161187770Sluigi%token
162187770Sluigi	A	B	C	E	F	I
163187770Sluigi	L	N	P	R	S	T
164187770Sluigi
165187770Sluigi	SP	CRLF	COMMA	STRING	NUMBER
166187770Sluigi
167220802Sglebius	USER	PASS	ACCT	REIN	QUIT	PORT
168187770Sluigi	PASV	TYPE	STRU	MODE	RETR	STOR
169220802Sglebius	APPE	MLFL	MAIL	MSND	MSOM	MSAM
170220802Sglebius	MRSQ	MRCP	ALLO	REST	RNFR	RNTO
171187770Sluigi	ABOR	DELE	CWD	LIST	NLST	SITE
172187770Sluigi	STAT	HELP	NOOP	MKD	RMD	PWD
173187770Sluigi	CDUP	STOU	SMNT	SYST	SIZE	MDTM
174187770Sluigi
175187770Sluigi	UMASK	IDLE	CHMOD
176187770Sluigi
177220802Sglebius	LEXERR
178187770Sluigi
179187770Sluigi%start	cmd_list
180187770Sluigi
181187770Sluigi%%
182187770Sluigi
183220802Sglebiuscmd_list:	/* empty */
184220802Sglebius	|	cmd_list cmd
185220802Sglebius		= {
186187770Sluigi			fromname = (char *) 0;
187187770Sluigi			restart_point = (off_t) 0;
188187770Sluigi		}
189187770Sluigi	|	cmd_list rcmd
190187770Sluigi	;
191187770Sluigi
192187770Sluigicmd:		USER SP username CRLF
193220802Sglebius		= {
194187770Sluigi			user((char *) $3);
195187770Sluigi			free((char *) $3);
196187770Sluigi		}
197187770Sluigi	|	PASS SP password CRLF
198187770Sluigi		= {
199187770Sluigi			pass((char *) $3);
200187770Sluigi			free((char *) $3);
201187770Sluigi		}
202187770Sluigi        |   PORT check_login SP host_port CRLF
203187770Sluigi                = {
204187770Sluigi             usedefault = 0;
205187770Sluigi             if (pdata >= 0) {
206187770Sluigi                 (void) close(pdata);
207187770Sluigi                 pdata = -1;
208220802Sglebius             }
209187770Sluigi/* H* port fix, part B: admonish the twit.
210187770Sluigi   Also require login before PORT works */
211220802Sglebius            if ($2) {
212187770Sluigi              if ((cliport > 1023) && (data_dest.sin_addr.s_addr > 0)) {
213187770Sluigi                reply(200, "PORT command successful.");
214220802Sglebius              } else {
215220802Sglebius                syslog (LOG_WARNING, "refused %s from %s",
216220804Sglebius                       cbuf, remotehost);
217187770Sluigi                reply(500, "You've GOT to be joking.");
218187770Sluigi              }
219187770Sluigi            }
220220802Sglebius                }
221187770Sluigi/*	|	PASV CRLF
222187770Sluigi		= {
223187770Sluigi			passive();
224220802Sglebius		} */
225187770Sluigi    |   PASV check_login CRLF
226187770Sluigi        = {
227187770Sluigi/* Require login for PASV, too.  This actually fixes a bug -- telnet to an
228220802Sglebius   unfixed wu-ftpd and type PASV first off, and it crashes! */
229187770Sluigi            if ($2) {
230220802Sglebius                passive();
231220802Sglebius            }
232187770Sluigi        }
233187770Sluigi	|	TYPE SP type_code CRLF
234187770Sluigi		= {
235187770Sluigi			switch (cmd_type) {
236187770Sluigi
237187770Sluigi			case TYPE_A:
238187770Sluigi				if (cmd_form == FORM_N) {
239187770Sluigi					reply(200, "Type set to A.");
240187770Sluigi					type = cmd_type;
241187770Sluigi					form = cmd_form;
242187770Sluigi				} else
243187770Sluigi					reply(504, "Form must be N.");
244220802Sglebius				break;
245187770Sluigi
246187770Sluigi			case TYPE_E:
247220802Sglebius				reply(504, "Type E not implemented.");
248187770Sluigi				break;
249187770Sluigi
250187770Sluigi			case TYPE_I:
251187770Sluigi				reply(200, "Type set to I.");
252220802Sglebius				type = cmd_type;
253187770Sluigi				break;
254187770Sluigi
255187770Sluigi			case TYPE_L:
256187770Sluigi#if NBBY == 8
257187770Sluigi				if (cmd_bytesz == 8) {
258187770Sluigi					reply(200,
259187770Sluigi					    "Type set to L (byte size 8).");
260187770Sluigi					type = cmd_type;
261188294Spiso				} else
262188294Spiso					reply(504, "Byte size must be 8.");
263188294Spiso#else /* NBBY == 8 */
264187770Sluigi				UNIMPLEMENTED for NBBY != 8
265187770Sluigi#endif /* NBBY == 8 */
266220802Sglebius			}
267220802Sglebius		}
268220802Sglebius	|	STRU SP struct_code CRLF
269187770Sluigi		= {
270187770Sluigi			switch ($3) {
271187770Sluigi
272187770Sluigi			case STRU_F:
273187770Sluigi				reply(200, "STRU F ok.");
274187770Sluigi				break;
275187770Sluigi
276187770Sluigi			default:
277187770Sluigi				reply(504, "Unimplemented STRU type.");
278187770Sluigi			}
279187770Sluigi		}
280187770Sluigi	|	MODE SP mode_code CRLF
281187770Sluigi		= {
282187770Sluigi			switch ($3) {
283187770Sluigi
284187770Sluigi			case MODE_S:
285220802Sglebius				reply(200, "MODE S ok.");
286220802Sglebius				break;
287220802Sglebius
288187770Sluigi			default:
289187770Sluigi				reply(502, "Unimplemented MODE type.");
290220802Sglebius			}
291220802Sglebius		}
292187770Sluigi	|	ALLO SP NUMBER CRLF
293220802Sglebius		= {
294187770Sluigi			reply(202, "ALLO command ignored.");
295187770Sluigi		}
296220802Sglebius	|	ALLO SP NUMBER SP R SP NUMBER CRLF
297187770Sluigi		= {
298187770Sluigi			reply(202, "ALLO command ignored.");
299187770Sluigi		}
300187770Sluigi	|	RETR check_login SP pathname CRLF
301220802Sglebius		= {
302187770Sluigi			if ($2 && $4)
303187770Sluigi				retrieve((char *) 0, (char *) $4);
304187770Sluigi			if ($4)
305187770Sluigi				free((char *) $4);
306187770Sluigi		}
307220802Sglebius	|	STOR check_login SP pathname CRLF
308187770Sluigi		= {
309220802Sglebius			if ($2 && $4)
310220802Sglebius				store((char *) $4, "w", 0);
311187770Sluigi			if ($4)
312187770Sluigi				free((char *) $4);
313187770Sluigi		}
314187770Sluigi	|	APPE check_login SP pathname CRLF
315220835Sglebius		= {
316187770Sluigi			if ($2 && $4)
317220835Sglebius				store((char *) $4, "a", 0);
318223185Sglebius			if ($4)
319223185Sglebius				free((char *) $4);
320220835Sglebius		}
321223185Sglebius	|	NLST check_login CRLF
322223185Sglebius		= {
323223185Sglebius			if ($2)
324220835Sglebius				send_file_list(".");
325220835Sglebius		}
326223185Sglebius	|	NLST check_login SP STRING CRLF
327223185Sglebius		= {
328223185Sglebius			if ($2 && $4)
329223185Sglebius				send_file_list((char *) $4);
330223185Sglebius			if ($4)
331220835Sglebius				free((char *) $4);
332220835Sglebius		}
333220835Sglebius	|	LIST check_login CRLF
334220835Sglebius		= {
335220835Sglebius			if ($2)
336220835Sglebius				retrieve(LS_COMMAND, "");
337220804Sglebius		}
338220835Sglebius	|	LIST check_login SP pathname CRLF
339220835Sglebius		= {
340187770Sluigi			if ($2 && $4)
341220835Sglebius                                {
342187770Sluigi                                char buffer[sizeof(LS_COMMAND)+3];
343220835Sglebius                                strcpy(buffer, LS_COMMAND);
344220835Sglebius                                strcat(buffer, " %s");
345220835Sglebius				retrieve(buffer, (char *) $4);
346220835Sglebius                                }
347187770Sluigi			if ($4)
348228051Sglebius				free((char *) $4);
349220835Sglebius		}
350220835Sglebius	|	STAT check_login SP pathname CRLF
351220835Sglebius		= {
352187770Sluigi			if ($2 && $4)
353228051Sglebius				statfilecmd((char *) $4);
354187770Sluigi			if ($4)
355220835Sglebius				free((char *) $4);
356220835Sglebius		}
357220835Sglebius	|	STAT CRLF
358220835Sglebius		= {
359187770Sluigi			statcmd();
360187770Sluigi		}
361220835Sglebius	|	DELE check_login SP pathname CRLF
362187770Sluigi		= {
363187770Sluigi			if ($2 && $4)
364220835Sglebius				delete((char *) $4);
365220835Sglebius			if ($4)
366220835Sglebius				free((char *) $4);
367220835Sglebius		}
368220835Sglebius	|	RNTO SP pathname CRLF
369220835Sglebius		= {
370220835Sglebius			if (fromname) {
371220835Sglebius				renamecmd(fromname, (char *) $3);
372220835Sglebius				free(fromname);
373220835Sglebius				fromname = (char *) 0;
374220835Sglebius			} else {
375220835Sglebius				reply(503, "Bad sequence of commands.");
376220835Sglebius			}
377220835Sglebius			free((char *) $3);
378220835Sglebius		}
379223185Sglebius	|	ABOR CRLF
380223185Sglebius		= {
381220835Sglebius			reply(225, "ABOR command successful.");
382223185Sglebius		}
383223185Sglebius	|	CWD check_login CRLF
384223185Sglebius		= {
385187770Sluigi			if ($2)
386220835Sglebius				cwd(pw->pw_dir);
387223185Sglebius		}
388223185Sglebius	|	CWD check_login SP pathname CRLF
389223185Sglebius		= {
390223185Sglebius			if ($2 && $4)
391223185Sglebius				cwd((char *) $4);
392220835Sglebius			if ($4)
393187770Sluigi				free((char *) $4);
394187770Sluigi		}
395187770Sluigi	|	HELP CRLF
396220835Sglebius		= {
397187770Sluigi			help(cmdtab, (char *) 0);
398187770Sluigi		}
399220835Sglebius	|	HELP SP STRING CRLF
400220835Sglebius		= {
401187770Sluigi			register char *cp = (char *)$3;
402220804Sglebius
403187770Sluigi			if (strncasecmp(cp, "SITE", 4) == 0) {
404220804Sglebius				cp = (char *)$3 + 4;
405187770Sluigi				if (*cp == ' ')
406220835Sglebius					cp++;
407187770Sluigi				if (*cp)
408220835Sglebius					help(sitetab, cp);
409220835Sglebius				else
410220835Sglebius					help(sitetab, (char *) 0);
411220835Sglebius			} else
412187770Sluigi				help(cmdtab, (char *) $3);
413187770Sluigi		}
414187770Sluigi	|	NOOP CRLF
415220835Sglebius		= {
416220835Sglebius			reply(200, "NOOP command successful.");
417220835Sglebius		}
418187770Sluigi	|	MKD check_login SP pathname CRLF
419187770Sluigi		= {
420187770Sluigi			if ($2 && $4)
421187770Sluigi				makedir((char *) $4);
422220835Sglebius			if ($4)
423187770Sluigi				free((char *) $4);
424187770Sluigi		}
425187770Sluigi	|	RMD check_login SP pathname CRLF
426220835Sglebius		= {
427187770Sluigi			if ($2 && $4)
428188294Spiso				removedir((char *) $4);
429220802Sglebius			if ($4)
430220802Sglebius				free((char *) $4);
431188294Spiso		}
432188294Spiso	|	PWD check_login CRLF
433188294Spiso		= {
434220835Sglebius			if ($2)
435188294Spiso				pwd();
436220835Sglebius		}
437220835Sglebius	|	CDUP check_login CRLF
438188294Spiso		= {
439220835Sglebius			if ($2)
440188294Spiso				cwd("..");
441220835Sglebius		}
442220835Sglebius	|	SITE SP HELP CRLF
443220835Sglebius		= {
444188294Spiso			help(sitetab, (char *) 0);
445187770Sluigi		}
446188294Spiso	|	SITE SP HELP SP STRING CRLF
447188294Spiso		= {
448188294Spiso			help(sitetab, (char *) $5);
449187770Sluigi		}
450220835Sglebius	|	SITE SP UMASK check_login CRLF
451187770Sluigi		= {
452187770Sluigi			int oldmask;
453187770Sluigi
454187770Sluigi			if ($4) {
455220835Sglebius				oldmask = umask(0);
456220835Sglebius				(void) umask(oldmask);
457187770Sluigi				reply(200, "Current UMASK is %03o", oldmask);
458220835Sglebius			}
459187770Sluigi		}
460187770Sluigi	|	SITE SP UMASK check_login SP octal_number CRLF
461187770Sluigi		= {
462220835Sglebius			int oldmask;
463220835Sglebius
464187770Sluigi			if ($4) {
465187770Sluigi				if (($6 == -1) || ($6 > 0777)) {
466187770Sluigi					reply(501, "Bad UMASK value");
467187770Sluigi				} else {
468188294Spiso					oldmask = umask($6);
469188294Spiso					reply(200,
470188294Spiso					    "UMASK set to %03o (was %03o)",
471188294Spiso					    $6, oldmask);
472187770Sluigi				}
473220835Sglebius			}
474187770Sluigi		}
475187770Sluigi	|	SITE SP CHMOD check_login SP octal_number SP pathname CRLF
476187770Sluigi		= {
477220804Sglebius			if ($4 && $8) {
478220802Sglebius				if ($6 > 0777)
479223185Sglebius					reply(501,
480187770Sluigi				"CHMOD: Mode value must be between 0 and 0777");
481187770Sluigi				else if (chmod((char *) $8, $6) < 0)
482223185Sglebius					perror_reply(550, (char *) $8);
483220835Sglebius				else
484220835Sglebius					reply(200, "CHMOD command successful.");
485187770Sluigi			}
486220835Sglebius			if ($8)
487187770Sluigi				free((char *) $8);
488187770Sluigi		}
489220802Sglebius	|	SITE SP IDLE CRLF
490187770Sluigi		= {
491220835Sglebius			reply(200,
492187770Sluigi			    "Current IDLE time limit is %d seconds; max %d",
493220835Sglebius				timeout, maxtimeout);
494187770Sluigi		}
495187770Sluigi	|	SITE SP IDLE SP NUMBER CRLF
496187770Sluigi		= {
497187770Sluigi			if ($5 < 30 || $5 > maxtimeout) {
498187770Sluigi				reply(501,
499187770Sluigi			"Maximum IDLE time must be between 30 and %d seconds",
500187770Sluigi				    maxtimeout);
501187770Sluigi			} else {
502220802Sglebius				timeout = $5;
503187770Sluigi				(void) alarm((unsigned) timeout);
504187770Sluigi				reply(200,
505187770Sluigi				    "Maximum IDLE time set to %d seconds",
506220835Sglebius				    timeout);
507187770Sluigi			}
508187770Sluigi		}
509187770Sluigi	|	STOU check_login SP pathname CRLF
510220802Sglebius		= {
511187770Sluigi			if ($2 && $4)
512220802Sglebius				store((char *) $4, "w", 1);
513187770Sluigi			if ($4)
514187770Sluigi				free((char *) $4);
515220835Sglebius		}
516220835Sglebius	|	SYST CRLF
517220835Sglebius		= {
518220835Sglebius#ifdef unix
519220835Sglebius#ifdef BSD
520187770Sluigi			reply(215, "UNIX Type: L%d Version: BSD-%d",
521220835Sglebius				NBBY, BSD);
522220835Sglebius#else /* BSD */
523188294Spiso			reply(215, "UNIX Type: L%d", NBBY);
524220835Sglebius#endif /* BSD */
525220835Sglebius#else /* unix */
526220835Sglebius			reply(215, "UNKNOWN Type: L%d", NBBY);
527188294Spiso#endif /* unix */
528188294Spiso		}
529188294Spiso
530188294Spiso		/*
531188294Spiso		 * SIZE is not in RFC959, but Postel has blessed it and
532188294Spiso		 * it will be in the updated RFC.
533188294Spiso		 *
534188294Spiso		 * Return size of file in a format suitable for
535220835Sglebius		 * using with RESTART (we just count bytes).
536220835Sglebius		 */
537188294Spiso	|	SIZE check_login SP pathname CRLF
538188294Spiso		= {
539220835Sglebius			if ($2 && $4)
540188294Spiso				sizecmd((char *) $4);
541188294Spiso			if ($4)
542188294Spiso				free((char *) $4);
543188294Spiso		}
544188294Spiso
545188294Spiso		/*
546188294Spiso		 * MDTM is not in RFC959, but Postel has blessed it and
547220835Sglebius		 * it will be in the updated RFC.
548188294Spiso		 *
549220804Sglebius		 * Return modification time of file as an ISO 3307
550187770Sluigi		 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
551220835Sglebius		 * where xxx is the fractional second (of any precision,
552187770Sluigi		 * not necessarily 3 digits)
553187770Sluigi		 */
554187770Sluigi	|	MDTM check_login SP pathname CRLF
555220835Sglebius		= {
556187770Sluigi			if ($2 && $4) {
557187770Sluigi				struct stat stbuf;
558187770Sluigi				if (stat((char *) $4, &stbuf) < 0)
559187770Sluigi					perror_reply(550, (char *) $4);
560220835Sglebius				else if ((stbuf.st_mode&S_IFMT) != S_IFREG) {
561187770Sluigi					reply(550, "%s: not a plain file.",
562220835Sglebius						(char *) $4);
563187770Sluigi				} else {
564220835Sglebius					register struct tm *t;
565220804Sglebius					struct tm *gmtime();
566220835Sglebius					t = gmtime(&stbuf.st_mtime);
567187770Sluigi					reply(213,
568220835Sglebius					    "19%02d%02d%02d%02d%02d%02d",
569220835Sglebius					    t->tm_year, t->tm_mon+1, t->tm_mday,
570220835Sglebius					    t->tm_hour, t->tm_min, t->tm_sec);
571220835Sglebius				}
572187770Sluigi			}
573187770Sluigi			if ($4)
574220804Sglebius				free((char *) $4);
575220835Sglebius		}
576187770Sluigi	|	QUIT CRLF
577220835Sglebius		= {
578187770Sluigi			reply(221, "Goodbye.");
579187770Sluigi			dologout(0);
580187770Sluigi		}
581220835Sglebius	|	error CRLF
582220804Sglebius		= {
583187770Sluigi			yyerrok;
584187770Sluigi		}
585187770Sluigi	;
586220835Sglebiusrcmd:		RNFR check_login SP pathname CRLF
587187770Sluigi		= {
588220835Sglebius			char *renamefrom();
589220804Sglebius
590187770Sluigi			restart_point = (off_t) 0;
591187770Sluigi			if ($2 && $4) {
592187770Sluigi				fromname = renamefrom((char *) $4);
593220835Sglebius				if (fromname == (char *) 0 && $4) {
594220804Sglebius					free((char *) $4);
595220804Sglebius				}
596187770Sluigi			}
597187770Sluigi		}
598223185Sglebius	|	REST SP byte_size CRLF
599220835Sglebius		= {
600220835Sglebius			long atol();
601220804Sglebius
602187770Sluigi			fromname = (char *) 0;
603187770Sluigi			restart_point = $3;
604220804Sglebius			reply(350, "Restarting at %ld. %s", restart_point,
605187770Sluigi			    "Send STORE or RETRIEVE to initiate transfer.");
606223185Sglebius		}
607220835Sglebius	;
608220835Sglebius
609187770Sluigiusername:	STRING
610220804Sglebius	;
611187770Sluigi
612220835Sglebiuspassword:	/* empty */
613187770Sluigi		= {
614187770Sluigi			*(char **)&($$) = (char *)calloc(1, sizeof(char));
615187770Sluigi		}
616187770Sluigi	|	STRING
617187770Sluigi	;
618187770Sluigi
619187770Sluigibyte_size:	NUMBER
620187770Sluigi	;
621187770Sluigi
622187770Sluigihost_port:	NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
623187770Sluigi		NUMBER COMMA NUMBER
624187770Sluigi		= {
625187770Sluigi			register char *a, *p;
626187770Sluigi
627187770Sluigi			a = (char *)&data_dest.sin_addr;
628187770Sluigi			a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
629187770Sluigi
630187770Sluigi/* H* port fix, part A-1: Check the args against the client addr */
631187770Sluigi            p = (char *)&his_addr.sin_addr;
632187770Sluigi             if (memcmp (a, p, sizeof (data_dest.sin_addr)))
633187770Sluigi                 memset (a, 0, sizeof (data_dest.sin_addr));     /* XXX */
634187770Sluigi
635187770Sluigi			p = (char *)&data_dest.sin_port;
636187770Sluigi
637187770Sluigi/* H* port fix, part A-2: only allow client ports in "user space" */
638187770Sluigi            p[0] = 0; p[1] = 0;
639187770Sluigi            cliport = ($9 << 8) + $11;
640187770Sluigi            if (cliport > 1023) {
641187770Sluigi                 p[0] = $9; p[1] = $11;
642187770Sluigi            }
643223080Sae
644223080Sae			p[0] = $9; p[1] = $11;
645223080Sae			data_dest.sin_family = AF_INET;
646187770Sluigi		}
647187770Sluigi	;
648187770Sluigi
649187770Sluigiform_code:	N
650187770Sluigi	= {
651187770Sluigi		$$ = FORM_N;
652187770Sluigi	}
653187770Sluigi	|	T
654187770Sluigi	= {
655187770Sluigi		$$ = FORM_T;
656187770Sluigi	}
657187770Sluigi	|	C
658187770Sluigi	= {
659187770Sluigi		$$ = FORM_C;
660187770Sluigi	}
661187770Sluigi	;
662187770Sluigi
663187770Sluigitype_code:	A
664187770Sluigi	= {
665187770Sluigi		cmd_type = TYPE_A;
666187770Sluigi		cmd_form = FORM_N;
667187770Sluigi	}
668187770Sluigi	|	A SP form_code
669187770Sluigi	= {
670187770Sluigi		cmd_type = TYPE_A;
671187770Sluigi		cmd_form = $3;
672187770Sluigi	}
673187770Sluigi	|	E
674220802Sglebius	= {
675187770Sluigi		cmd_type = TYPE_E;
676187770Sluigi		cmd_form = FORM_N;
677187770Sluigi	}
678187770Sluigi	|	E SP form_code
679187770Sluigi	= {
680187770Sluigi		cmd_type = TYPE_E;
681187770Sluigi		cmd_form = $3;
682187770Sluigi	}
683187770Sluigi	|	I
684187770Sluigi	= {
685187770Sluigi		cmd_type = TYPE_I;
686187770Sluigi	}
687220802Sglebius	|	L
688187770Sluigi	= {
689187770Sluigi		cmd_type = TYPE_L;
690187770Sluigi		cmd_bytesz = NBBY;
691187770Sluigi	}
692187770Sluigi	|	L SP byte_size
693187770Sluigi	= {
694220802Sglebius		cmd_type = TYPE_L;
695187770Sluigi		cmd_bytesz = $3;
696187770Sluigi	}
697187770Sluigi	/* this is for a bug in the BBN ftp */
698187770Sluigi	|	L byte_size
699187770Sluigi	= {
700187770Sluigi		cmd_type = TYPE_L;
701220802Sglebius		cmd_bytesz = $2;
702187770Sluigi	}
703187770Sluigi	;
704187770Sluigi
705187770Sluigistruct_code:	F
706187770Sluigi	= {
707187770Sluigi		$$ = STRU_F;
708187770Sluigi	}
709187770Sluigi	|	R
710187770Sluigi	= {
711220802Sglebius		$$ = STRU_R;
712187770Sluigi	}
713187770Sluigi	|	P
714187770Sluigi	= {
715187770Sluigi		$$ = STRU_P;
716187770Sluigi	}
717187770Sluigi	;
718220802Sglebius
719187770Sluigimode_code:	S
720187770Sluigi	= {
721187770Sluigi		$$ = MODE_S;
722187770Sluigi	}
723187770Sluigi	|	B
724187770Sluigi	= {
725187770Sluigi		$$ = MODE_B;
726187770Sluigi	}
727187770Sluigi	|	C
728187770Sluigi	= {
729187770Sluigi		$$ = MODE_C;
730187770Sluigi	}
731187770Sluigi	;
732187770Sluigi
733187770Sluigipathname:	pathstring
734187770Sluigi	= {
735187770Sluigi		/*
736187770Sluigi		 * Problem: this production is used for all pathname
737220802Sglebius		 * processing, but only gives a 550 error reply.
738220835Sglebius		 * This is a valid reply in some cases but not in others.
739223079Sae		 */
740220835Sglebius		if (logged_in && $1 && strncmp((char *) $1, "~", 1) == 0) {
741220804Sglebius			*(char **)&($$) = *ftpglob((char *) $1);
742223499Sglebius			if (globerr != NULL) {
743223499Sglebius				reply(550, globerr);
744187770Sluigi/*				$$ = NULL; */
745223079Sae				$$ = 0;
746187770Sluigi			}
747223079Sae			free((char *) $1);
748223079Sae		} else
749223079Sae			$$ = $1;
750223079Sae	}
751223499Sglebius	;
752223499Sglebius
753220802Sglebiuspathstring:	STRING
754187770Sluigi	;
755187770Sluigi
756220835Sglebiusoctal_number:	NUMBER
757220835Sglebius	= {
758220835Sglebius		register int ret, dec, multby, digit;
759220835Sglebius
760220835Sglebius		/*
761223499Sglebius		 * Convert a number that was read as decimal number
762223499Sglebius		 * to what it would be if it had been read as octal.
763220835Sglebius		 */
764220835Sglebius		dec = $1;
765220835Sglebius		multby = 1;
766223499Sglebius		ret = 0;
767223499Sglebius		while (dec) {
768223080Sae			digit = dec%10;
769220835Sglebius			if (digit > 7) {
770220835Sglebius				ret = -1;
771220835Sglebius				break;
772223080Sae			}
773220835Sglebius			ret += digit * multby;
774220835Sglebius			multby *= 8;
775220835Sglebius			dec /= 10;
776220835Sglebius		}
777220835Sglebius		$$ = ret;
778220835Sglebius	}
779220835Sglebius	;
780220835Sglebius
781220835Sglebiuscheck_login:	/* empty */
782220835Sglebius	= {
783223499Sglebius		if (logged_in)
784223499Sglebius			$$ = 1;
785220835Sglebius		else {
786220835Sglebius			reply(530, "Please login with USER and PASS.");
787220835Sglebius			$$ = 0;
788220835Sglebius		}
789220835Sglebius	}
790223499Sglebius	;
791223499Sglebius
792220835Sglebius%%
793223499Sglebius
794223499Sglebiusextern jmp_buf errcatch;
795223185Sglebius
796223416Sglebius#define	CMD	0	/* beginning of command */
797223499Sglebius#define	ARGS	1	/* expect miscellaneous arguments */
798223499Sglebius#define	STR1	2	/* expect SP followed by STRING */
799223416Sglebius#define	STR2	3	/* expect STRING */
800220835Sglebius#define	OSTR	4	/* optional SP then STRING */
801220835Sglebius#define	ZSTR1	5	/* SP then optional STRING */
802220835Sglebius#define	ZSTR2	6	/* optional STRING after SP */
803220835Sglebius#define	SITECMD	7	/* SITE command */
804220835Sglebius#define	NSTR	8	/* Number followed by a string */
805220835Sglebius
806223499Sglebiusstruct tab cmdtab[] = {		/* In order defined in RFC 765 */
807223499Sglebius	{ "USER", USER, STR1, 1,	"<sp> username" },
808223185Sglebius	{ "PASS", PASS, ZSTR1, 1,	"<sp> password" },
809223416Sglebius	{ "ACCT", ACCT, STR1, 0,	"(specify account)" },
810223499Sglebius	{ "SMNT", SMNT, ARGS, 0,	"(structure mount)" },
811223499Sglebius	{ "REIN", REIN, ARGS, 0,	"(reinitialize server state)" },
812223416Sglebius	{ "QUIT", QUIT, ARGS, 1,	"(terminate service)", },
813223416Sglebius	{ "PORT", PORT, ARGS, 1,	"<sp> b0, b1, b2, b3, b4" },
814223499Sglebius	{ "PASV", PASV, ARGS, 1,	"(set server in passive mode)" },
815223499Sglebius	{ "TYPE", TYPE, ARGS, 1,	"<sp> [ A | E | I | L ]" },
816223416Sglebius	{ "STRU", STRU, ARGS, 1,	"(specify file structure)" },
817220835Sglebius	{ "MODE", MODE, ARGS, 1,	"(specify transfer mode)" },
818220835Sglebius	{ "RETR", RETR, STR1, 1,	"<sp> file-name" },
819220835Sglebius	{ "STOR", STOR, STR1, 1,	"<sp> file-name" },
820220835Sglebius	{ "APPE", APPE, STR1, 1,	"<sp> file-name" },
821220835Sglebius	{ "MLFL", MLFL, OSTR, 0,	"(mail file)" },
822220835Sglebius	{ "MAIL", MAIL, OSTR, 0,	"(mail to user)" },
823220835Sglebius	{ "MSND", MSND, OSTR, 0,	"(mail send to terminal)" },
824220835Sglebius	{ "MSOM", MSOM, OSTR, 0,	"(mail send to terminal or mailbox)" },
825220835Sglebius	{ "MSAM", MSAM, OSTR, 0,	"(mail send to terminal and mailbox)" },
826220835Sglebius	{ "MRSQ", MRSQ, OSTR, 0,	"(mail recipient scheme question)" },
827220835Sglebius	{ "MRCP", MRCP, STR1, 0,	"(mail recipient)" },
828220835Sglebius	{ "ALLO", ALLO, ARGS, 1,	"allocate storage (vacuously)" },
829220835Sglebius	{ "REST", REST, ARGS, 1,	"(restart command)" },
830220835Sglebius	{ "RNFR", RNFR, STR1, 1,	"<sp> file-name" },
831220835Sglebius	{ "RNTO", RNTO, STR1, 1,	"<sp> file-name" },
832187770Sluigi	{ "ABOR", ABOR, ARGS, 1,	"(abort operation)" },
833187770Sluigi	{ "DELE", DELE, STR1, 1,	"<sp> file-name" },
834223499Sglebius	{ "CWD",  CWD,  OSTR, 1,	"[ <sp> directory-name ]" },
835223499Sglebius	{ "XCWD", CWD,	OSTR, 1,	"[ <sp> directory-name ]" },
836187770Sluigi	{ "LIST", LIST, OSTR, 1,	"[ <sp> path-name ]" },
837187770Sluigi	{ "NLST", NLST, OSTR, 1,	"[ <sp> path-name ]" },
838220802Sglebius	{ "SITE", SITE, SITECMD, 1,	"site-cmd [ <sp> arguments ]" },
839187770Sluigi	{ "SYST", SYST, ARGS, 1,	"(get type of operating system)" },
840187770Sluigi	{ "STAT", STAT, OSTR, 1,	"[ <sp> path-name ]" },
841220802Sglebius	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
842187770Sluigi	{ "NOOP", NOOP, ARGS, 1,	"" },
843223499Sglebius	{ "MKD",  MKD,  STR1, 1,	"<sp> path-name" },
844223499Sglebius	{ "XMKD", MKD,  STR1, 1,	"<sp> path-name" },
845220802Sglebius	{ "RMD",  RMD,  STR1, 1,	"<sp> path-name" },
846187770Sluigi	{ "XRMD", RMD,  STR1, 1,	"<sp> path-name" },
847220802Sglebius	{ "PWD",  PWD,  ARGS, 1,	"(return current directory)" },
848187770Sluigi	{ "XPWD", PWD,  ARGS, 1,	"(return current directory)" },
849187770Sluigi	{ "CDUP", CDUP, ARGS, 1,	"(change to parent directory)" },
850223499Sglebius	{ "XCUP", CDUP, ARGS, 1,	"(change to parent directory)" },
851223499Sglebius	{ "STOU", STOU, STR1, 1,	"<sp> file-name" },
852187770Sluigi	{ "SIZE", SIZE, OSTR, 1,	"<sp> path-name" },
853187770Sluigi	{ "MDTM", MDTM, OSTR, 1,	"<sp> path-name" },
854187770Sluigi	{ NULL,   0,    0,    0,	0 }
855187770Sluigi};
856187770Sluigi
857187770Sluigistruct tab sitetab[] = {
858187770Sluigi	{ "UMASK", UMASK, ARGS, 1,	"[ <sp> umask ]" },
859187770Sluigi	{ "IDLE", IDLE, ARGS, 1,	"[ <sp> maximum-idle-time ]" },
860187770Sluigi	{ "CHMOD", CHMOD, NSTR, 1,	"<sp> mode <sp> file-name" },
861187770Sluigi	{ "HELP", HELP, OSTR, 1,	"[ <sp> <string> ]" },
862187770Sluigi	{ NULL,   0,    0,    0,	0 }
863187770Sluigi};
864187770Sluigi
865223080Saestruct tab *lookup FUNCTION((p, cmd), register struct tab *p AND char *cmd)
866223080Sae{
867223080Sae
868187770Sluigi	for (; p->name != NULL; p++)
869187770Sluigi		if (strcmp(cmd, p->name) == 0)
870187770Sluigi			return (p);
871187770Sluigi	return (0);
872187770Sluigi}
873187770Sluigi
874187770Sluigi#include <arpa/telnet.h>
875187770Sluigi
876187770Sluigi/*
877220802Sglebius * getline - a hacked up version of fgets to ignore TELNET escape codes.
878220802Sglebius */
879220802Sglebiuschar *getline FUNCTION((s, n, iop), char *s AND int n AND FILE *iop)
880187770Sluigi{
881187770Sluigi	register c;
882187770Sluigi	register char *cs;
883187770Sluigi
884187770Sluigi	cs = s;
885187770Sluigi/* tmpline may contain saved command from urgent mode interruption */
886220835Sglebius	for (c = 0; *(tmpline + c) && --n > 0; ++c) {
887220802Sglebius		*cs++ = *(tmpline + c);
888187770Sluigi		if (*(tmpline + c) == '\n') {
889220835Sglebius			*cs++ = '\0';
890220802Sglebius			if (debug)
891187770Sluigi				syslog(LOG_DEBUG, "command: %s", s);
892220835Sglebius			*tmpline = '\0';
893187770Sluigi			return(s);
894187770Sluigi		}
895187770Sluigi		if (c == 0)
896187770Sluigi			*tmpline = '\0';
897187770Sluigi	}
898187770Sluigi	while ((c = getc(iop)) != EOF) {
899187770Sluigi		c &= 0377;
900187770Sluigi		if (c == IAC) {
901187770Sluigi		    if ((c = getc(iop)) != EOF) {
902187770Sluigi			c &= 0377;
903187770Sluigi			switch (c) {
904187770Sluigi			case WILL:
905187770Sluigi			case WONT:
906187770Sluigi				c = getc(iop);
907187770Sluigi				printf("%c%c%c", IAC, DONT, 0377&c);
908189395Sluigi				(void) fflush(stdout);
909189395Sluigi				continue;
910187770Sluigi			case DO:
911187770Sluigi			case DONT:
912187770Sluigi				c = getc(iop);
913187770Sluigi				printf("%c%c%c", IAC, WONT, 0377&c);
914187770Sluigi				(void) fflush(stdout);
915187770Sluigi				continue;
916187770Sluigi			case IAC:
917187770Sluigi				break;
918187770Sluigi			default:
919187770Sluigi				continue;	/* ignore command */
920187770Sluigi			}
921187770Sluigi		    }
922187770Sluigi		}
923187770Sluigi		*cs++ = c;
924187770Sluigi		if (--n <= 0 || c == '\n')
925187770Sluigi			break;
926187770Sluigi	}
927187770Sluigi	if (c == EOF && cs == s)
928187770Sluigi		return (NULL);
929187770Sluigi	*cs++ = '\0';
930223499Sglebius	if (debug)
931223499Sglebius		syslog(LOG_DEBUG, "command: %s", s);
932187770Sluigi	return (s);
933187770Sluigi}
934187770Sluigi
935187770Sluigistatic VOIDRET toolong FUNCTION((input), int input)
936187770Sluigi{
937187770Sluigi	time_t now;
938187770Sluigi
939220802Sglebius	reply(421, "Timeout (%d seconds): closing control connection.", timeout);
940187770Sluigi	(void) time(&now);
941187770Sluigi        syslog(LOG_INFO, "User %s timed out after %d seconds at %s",
942187770Sluigi          (pw ? pw -> pw_name : "unknown"), timeout, ctime(&now));
943187770Sluigi	dologout(1);
944187770Sluigi}
945187770Sluigi
946220804Sglebiusint yylex FUNCTION_NOARGS
947187770Sluigi{
948187770Sluigi	static int cpos, state;
949187770Sluigi	register char *cp, *cp2;
950187770Sluigi	register struct tab *p;
951187770Sluigi	int n;
952187770Sluigi	char c, *copy();
953187770Sluigi
954187770Sluigi	for (;;) {
955187770Sluigi		switch (state) {
956187770Sluigi
957187770Sluigi		case CMD:
958187770Sluigi			(void) signal(SIGALRM, toolong);
959187770Sluigi			(void) alarm((unsigned) timeout);
960187770Sluigi			if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
961187770Sluigi				reply(221, "You could at least say goodbye.");
962187770Sluigi				dologout(0);
963187770Sluigi			}
964187770Sluigi			(void) alarm(0);
965187770Sluigi#ifdef SETPROCTITLE
966187770Sluigi			if (strncasecmp(cbuf, "PASS", 4) != NULL)
967187770Sluigi				setproctitle("%s: %s", proctitle, cbuf);
968187770Sluigi#endif /* SETPROCTITLE */
969187770Sluigi			if ((cp = strchr(cbuf, '\r'))) {
970187770Sluigi				*cp++ = '\n';
971220802Sglebius				*cp = '\0';
972187770Sluigi			}
973187770Sluigi			if ((cp = strpbrk(cbuf, " \n")))
974187770Sluigi				cpos = cp - cbuf;
975187770Sluigi			if (cpos == 0)
976187770Sluigi				cpos = 4;
977187770Sluigi			c = cbuf[cpos];
978187770Sluigi			cbuf[cpos] = '\0';
979187770Sluigi			upper(cbuf);
980187770Sluigi			p = lookup(cmdtab, cbuf);
981187770Sluigi			cbuf[cpos] = c;
982187770Sluigi			if (p != 0) {
983187770Sluigi				if (p->implemented == 0) {
984187770Sluigi					nack(p->name);
985187770Sluigi					longjmp(errcatch,0);
986187770Sluigi					/* NOTREACHED */
987187770Sluigi				}
988187770Sluigi				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		return;
1230	}
1231	upper(s);
1232	c = lookup(ctab, s);
1233	if (c == (struct tab *)0) {
1234		reply(502, "Unknown command %s.", s);
1235		return;
1236	}
1237	if (c->implemented)
1238		reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1239	else
1240		reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1241		    c->name, c->help);
1242}
1243
1244VOIDRET sizecmd FUNCTION((filename), char *filename)
1245{
1246	switch (type) {
1247	case TYPE_L:
1248	case TYPE_I: {
1249		struct stat stbuf;
1250		if (stat(filename, &stbuf) < 0 ||
1251		    (stbuf.st_mode&S_IFMT) != S_IFREG)
1252			reply(550, "%s: not a plain file.", filename);
1253		else
1254			reply(213, "%lu", stbuf.st_size);
1255		break;}
1256	case TYPE_A: {
1257		FILE *fin;
1258		register int c;
1259		register long count;
1260		struct stat stbuf;
1261		fin = fopen(filename, "r");
1262		if (fin == NULL) {
1263			perror_reply(550, filename);
1264			return;
1265		}
1266		if (fstat(fileno(fin), &stbuf) < 0 ||
1267		    (stbuf.st_mode&S_IFMT) != S_IFREG) {
1268			reply(550, "%s: not a plain file.", filename);
1269			(void) fclose(fin);
1270			return;
1271		}
1272
1273		count = 0;
1274		while((c=getc(fin)) != EOF) {
1275			if (c == '\n')	/* will get expanded to \r\n */
1276				count++;
1277			count++;
1278		}
1279		(void) fclose(fin);
1280
1281		reply(213, "%ld", count);
1282		break;}
1283	default:
1284		reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1285	}
1286}
1287