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