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