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