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