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