ftpcmd.y revision 1.27 1 /* $NetBSD: ftpcmd.y,v 1.27 1999/05/17 15:14:54 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.27 1999/05/17 15:14:54 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 #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 "Data traffic for this session was %qd byte%s in %qd file%s.",
191 total_data, PLURAL(total_data),
192 total_files, PLURAL(total_files));
193 lreply(221,
194 "Total traffic for this session was %qd byte%s in %qd transfer%s.",
195 total_bytes, PLURAL(total_bytes),
196 total_xfers, PLURAL(total_xfers));
197 syslog(LOG_INFO,
198 "Data traffic: %qd byte%s in %qd file%s",
199 total_data, PLURAL(total_data),
200 total_files, PLURAL(total_files));
201 syslog(LOG_INFO,
202 "Total traffic: %qd byte%s in %qd transfer%s",
203 total_bytes, PLURAL(total_bytes),
204 total_xfers, PLURAL(total_xfers));
205 }
206 lreply(211,
207 "Thank you for using the FTP service on %s.",
208 hostname);
209 reply(221, "Goodbye.");
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", restart_point,
661 "Send STORE or RETRIEVE to initiate transfer.");
662 }
663 | RNFR check_modify SP pathname CRLF
664 {
665 restart_point = (off_t) 0;
666 if ($2 && $4) {
667 fromname = renamefrom($4);
668 if (fromname == NULL && $4) {
669 free($4);
670 }
671 }
672 }
673 ;
674
675 username
676 : STRING
677 ;
678
679 password
680 : /* empty */
681 {
682 $$ = (char *)calloc(1, sizeof(char));
683 }
684
685 | STRING
686 ;
687
688 byte_size
689 : NUMBER
690 ;
691
692 host_port
693 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
694 NUMBER COMMA NUMBER
695 {
696 char *a, *p;
697
698 data_dest.sin_len = sizeof(struct sockaddr_in);
699 data_dest.sin_family = AF_INET;
700 p = (char *)&data_dest.sin_port;
701 p[0] = $9; p[1] = $11;
702 a = (char *)&data_dest.sin_addr;
703 a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
704 }
705 ;
706
707 form_code
708 : N
709 {
710 $$ = FORM_N;
711 }
712
713 | T
714 {
715 $$ = FORM_T;
716 }
717
718 | C
719 {
720 $$ = FORM_C;
721 }
722 ;
723
724 type_code
725 : A
726 {
727 cmd_type = TYPE_A;
728 cmd_form = FORM_N;
729 }
730
731 | A SP form_code
732 {
733 cmd_type = TYPE_A;
734 cmd_form = $3;
735 }
736
737 | E
738 {
739 cmd_type = TYPE_E;
740 cmd_form = FORM_N;
741 }
742
743 | E SP form_code
744 {
745 cmd_type = TYPE_E;
746 cmd_form = $3;
747 }
748
749 | I
750 {
751 cmd_type = TYPE_I;
752 }
753
754 | L
755 {
756 cmd_type = TYPE_L;
757 cmd_bytesz = NBBY;
758 }
759
760 | L SP byte_size
761 {
762 cmd_type = TYPE_L;
763 cmd_bytesz = $3;
764 }
765
766 /* this is for a bug in the BBN ftp */
767 | L byte_size
768 {
769 cmd_type = TYPE_L;
770 cmd_bytesz = $2;
771 }
772 ;
773
774 struct_code
775 : F
776 {
777 $$ = STRU_F;
778 }
779
780 | R
781 {
782 $$ = STRU_R;
783 }
784
785 | P
786 {
787 $$ = STRU_P;
788 }
789 ;
790
791 mode_code
792 : S
793 {
794 $$ = MODE_S;
795 }
796
797 | B
798 {
799 $$ = MODE_B;
800 }
801
802 | C
803 {
804 $$ = MODE_C;
805 }
806 ;
807
808 pathname
809 : pathstring
810 {
811 /*
812 * Problem: this production is used for all pathname
813 * processing, but only gives a 550 error reply.
814 * This is a valid reply in some cases but not in
815 * others.
816 */
817 if (logged_in && $1 && *$1 == '~') {
818 glob_t gl;
819 int flags =
820 GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
821
822 if ($1[1] == '\0')
823 $$ = xstrdup(pw->pw_dir);
824 else {
825 memset(&gl, 0, sizeof(gl));
826 if (glob($1, flags, NULL, &gl) ||
827 gl.gl_pathc == 0) {
828 reply(550, "not found");
829 $$ = NULL;
830 } else
831 $$ = xstrdup(gl.gl_pathv[0]);
832 globfree(&gl);
833 }
834 free($1);
835 } else
836 $$ = $1;
837 }
838 ;
839
840 pathstring
841 : STRING
842 ;
843
844 octal_number
845 : NUMBER
846 {
847 int ret, dec, multby, digit;
848
849 /*
850 * Convert a number that was read as decimal number
851 * to what it would be if it had been read as octal.
852 */
853 dec = $1;
854 multby = 1;
855 ret = 0;
856 while (dec) {
857 digit = dec%10;
858 if (digit > 7) {
859 ret = -1;
860 break;
861 }
862 ret += digit * multby;
863 multby *= 8;
864 dec /= 10;
865 }
866 $$ = ret;
867 }
868 ;
869
870 mechanism_name
871 : STRING
872 ;
873
874 base64data
875 : STRING
876 ;
877
878 prot_code
879 : STRING
880 ;
881
882 decimal_integer
883 : NUMBER
884 ;
885
886 check_login
887 : /* empty */
888 {
889 if (logged_in)
890 $$ = 1;
891 else {
892 reply(530, "Please login with USER and PASS.");
893 $$ = 0;
894 hasyyerrored = 1;
895 }
896 }
897 ;
898
899 check_modify
900 : /* empty */
901 {
902 if (logged_in) {
903 if (curclass.modify)
904 $$ = 1;
905 else {
906 reply(502,
907 "No permission to use this command.");
908 $$ = 0;
909 hasyyerrored = 1;
910 }
911 } else {
912 reply(530, "Please login with USER and PASS.");
913 $$ = 0;
914 hasyyerrored = 1;
915 }
916 }
917
918 %%
919
920 #define CMD 0 /* beginning of command */
921 #define ARGS 1 /* expect miscellaneous arguments */
922 #define STR1 2 /* expect SP followed by STRING */
923 #define STR2 3 /* expect STRING */
924 #define OSTR 4 /* optional SP then STRING */
925 #define ZSTR1 5 /* SP then optional STRING */
926 #define ZSTR2 6 /* optional STRING after SP */
927 #define SITECMD 7 /* SITE command */
928 #define NSTR 8 /* Number followed by a string */
929 #define NOARGS 9 /* No arguments allowed */
930
931 struct tab {
932 char *name;
933 short token;
934 short state;
935 short implemented; /* 1 if command is implemented */
936 short hasopts; /* 1 if command takes options */
937 char *help;
938 char *options;
939 };
940
941 struct tab cmdtab[] = {
942 /* From RFC 959, in order defined (5.3.1) */
943 { "USER", USER, STR1, 1, 0, "<sp> username" },
944 { "PASS", PASS, ZSTR1, 1, 0, "<sp> password" },
945 { "ACCT", ACCT, STR1, 0, 0, "(specify account)" },
946 { "CWD", CWD, OSTR, 1, 0, "[ <sp> directory-name ]" },
947 { "CDUP", CDUP, NOARGS, 1, 0, "(change to parent directory)" },
948 { "SMNT", SMNT, ARGS, 0, 0, "(structure mount)" },
949 { "QUIT", QUIT, NOARGS, 1, 0, "(terminate service)", },
950 { "REIN", REIN, NOARGS, 0, 0, "(reinitialize server state)" },
951 { "PORT", PORT, ARGS, 1, 0, "<sp> b0, b1, b2, b3, b4" },
952 { "PASV", PASV, NOARGS, 1, 0, "(set server in passive mode)" },
953 { "TYPE", TYPE, ARGS, 1, 0, "<sp> [ A | E | I | L ]" },
954 { "STRU", STRU, ARGS, 1, 0, "(specify file structure)" },
955 { "MODE", MODE, ARGS, 1, 0, "(specify transfer mode)" },
956 { "RETR", RETR, STR1, 1, 0, "<sp> file-name" },
957 { "STOR", STOR, STR1, 1, 0, "<sp> file-name" },
958 { "STOU", STOU, STR1, 1, 0, "<sp> file-name" },
959 { "APPE", APPE, STR1, 1, 0, "<sp> file-name" },
960 { "ALLO", ALLO, ARGS, 1, 0, "allocate storage (vacuously)" },
961 { "REST", REST, ARGS, 1, 0, "<sp> offset (restart command)" },
962 { "RNFR", RNFR, STR1, 1, 0, "<sp> file-name" },
963 { "RNTO", RNTO, STR1, 1, 0, "<sp> file-name" },
964 { "ABOR", ABOR, NOARGS, 1, 0, "(abort operation)" },
965 { "DELE", DELE, STR1, 1, 0, "<sp> file-name" },
966 { "RMD", RMD, STR1, 1, 0, "<sp> path-name" },
967 { "MKD", MKD, STR1, 1, 0, "<sp> path-name" },
968 { "PWD", PWD, NOARGS, 1, 0, "(return current directory)" },
969 { "LIST", LIST, OSTR, 1, 0, "[ <sp> path-name ]" },
970 { "NLST", NLST, OSTR, 1, 0, "[ <sp> path-name ]" },
971 { "SITE", SITE, SITECMD, 1, 0, "site-cmd [ <sp> arguments ]" },
972 { "SYST", SYST, NOARGS, 1, 0, "(get type of operating system)" },
973 { "STAT", STAT, OSTR, 1, 0, "[ <sp> path-name ]" },
974 { "HELP", HELP, OSTR, 1, 0, "[ <sp> <string> ]" },
975 { "NOOP", NOOP, NOARGS, 1, 1, "" },
976
977 /* From RFC 2228, in order defined */
978 { "AUTH", AUTH, STR1, 1, 0, "<sp> mechanism-name" },
979 { "ADAT", ADAT, STR1, 1, 0, "<sp> base-64-data" },
980 { "PROT", PROT, STR1, 1, 0, "<sp> prot-code" },
981 { "PBSZ", PBSZ, ARGS, 1, 0, "<sp> decimal-integer" },
982 { "CCC", CCC, NOARGS, 1, 0, "(Disable data protection)" },
983 { "MIC", MIC, STR1, 1, 0, "<sp> base64data" },
984 { "CONF", CONF, STR1, 1, 0, "<sp> base64data" },
985 { "ENC", ENC, STR1, 1, 0, "<sp> base64data" },
986
987 /* From RFC 2389, in order defined */
988 { "FEAT", FEAT, NOARGS, 1, 0, "(display extended features)" },
989 { "OPTS", OPTS, STR1, 1, 0, "<sp> command [ <sp> options ]" },
990
991 /* Non standardized extensions */
992 { "SIZE", SIZE, OSTR, 1, 0, "<sp> path-name" },
993 { "MDTM", MDTM, OSTR, 1, 0, "<sp> path-name" },
994
995 /* obsolete commands */
996 { "MAIL", MAIL, OSTR, 0, 0, "(mail to user)" },
997 { "MLFL", MLFL, OSTR, 0, 0, "(mail file)" },
998 { "MRCP", MRCP, STR1, 0, 0, "(mail recipient)" },
999 { "MRSQ", MRSQ, OSTR, 0, 0, "(mail recipient scheme question)" },
1000 { "MSAM", MSAM, OSTR, 0, 0, "(mail send to terminal and mailbox)" },
1001 { "MSND", MSND, OSTR, 0, 0, "(mail send to terminal)" },
1002 { "MSOM", MSOM, OSTR, 0, 0, "(mail send to terminal or mailbox)" },
1003 { "XCUP", CDUP, NOARGS, 1, 0, "(change to parent directory)" },
1004 { "XCWD", CWD, OSTR, 1, 0, "[ <sp> directory-name ]" },
1005 { "XMKD", MKD, STR1, 1, 0, "<sp> path-name" },
1006 { "XPWD", PWD, NOARGS, 1, 0, "(return current directory)" },
1007 { "XRMD", RMD, STR1, 1, 0, "<sp> path-name" },
1008
1009 { NULL, 0, 0, 0, 0, 0 }
1010 };
1011
1012 struct tab sitetab[] = {
1013 { "UMASK", UMASK, ARGS, 1, 0, "[ <sp> umask ]" },
1014 { "IDLE", IDLE, ARGS, 1, 0, "[ <sp> maximum-idle-time ]" },
1015 { "CHMOD", CHMOD, NSTR, 1, 0, "<sp> mode <sp> file-name" },
1016 { "HELP", HELP, OSTR, 1, 0, "[ <sp> <string> ]" },
1017 { NULL, 0, 0, 0, 0, 0 }
1018 };
1019
1020 static void help __P((struct tab *, char *));
1021 static struct tab *lookup __P((struct tab *, const char *));
1022 static void opts __P((const char *));
1023 static void sizecmd __P((char *));
1024 static void toolong __P((int));
1025 static int yylex __P((void));
1026
1027 static struct tab *
1028 lookup(p, cmd)
1029 struct tab *p;
1030 const char *cmd;
1031 {
1032
1033 for (; p->name != NULL; p++)
1034 if (strcasecmp(cmd, p->name) == 0)
1035 return (p);
1036 return (0);
1037 }
1038
1039 #include <arpa/telnet.h>
1040
1041 /*
1042 * getline - a hacked up version of fgets to ignore TELNET escape codes.
1043 */
1044 char *
1045 getline(s, n, iop)
1046 char *s;
1047 int n;
1048 FILE *iop;
1049 {
1050 off_t b;
1051 int c;
1052 char *cs;
1053
1054 cs = s;
1055 /* tmpline may contain saved command from urgent mode interruption */
1056 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1057 *cs++ = tmpline[c];
1058 if (tmpline[c] == '\n') {
1059 *cs++ = '\0';
1060 if (debug)
1061 syslog(LOG_DEBUG, "command: %s", s);
1062 tmpline[0] = '\0';
1063 return(s);
1064 }
1065 if (c == 0)
1066 tmpline[0] = '\0';
1067 }
1068 while ((c = getc(iop)) != EOF) {
1069 total_bytes++;
1070 total_bytes_in++;
1071 c &= 0377;
1072 if (c == IAC) {
1073 if ((c = getc(iop)) != EOF) {
1074 total_bytes++;
1075 total_bytes_in++;
1076 c &= 0377;
1077 switch (c) {
1078 case WILL:
1079 case WONT:
1080 c = getc(iop);
1081 total_bytes++;
1082 total_bytes_in++;
1083 b = printf("%c%c%c", IAC, DONT, 0377&c);
1084 total_bytes += b;
1085 total_bytes_out += b;
1086 (void) fflush(stdout);
1087 continue;
1088 case DO:
1089 case DONT:
1090 c = getc(iop);
1091 total_bytes++;
1092 total_bytes_in++;
1093 b = printf("%c%c%c", IAC, WONT, 0377&c);
1094 total_bytes += b;
1095 total_bytes_out += b;
1096 (void) fflush(stdout);
1097 continue;
1098 case IAC:
1099 break;
1100 default:
1101 continue; /* ignore command */
1102 }
1103 }
1104 }
1105 *cs++ = c;
1106 if (--n <= 0 || c == '\n')
1107 break;
1108 }
1109 if (c == EOF && cs == s)
1110 return (NULL);
1111 *cs++ = '\0';
1112 if (debug) {
1113 if (!guest && strncasecmp("pass ", s, 5) == 0) {
1114 /* Don't syslog passwords */
1115 syslog(LOG_DEBUG, "command: %.5s ???", s);
1116 } else {
1117 char *cp;
1118 int len;
1119
1120 /* Don't syslog trailing CR-LF */
1121 len = strlen(s);
1122 cp = s + len - 1;
1123 while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1124 --cp;
1125 --len;
1126 }
1127 syslog(LOG_DEBUG, "command: %.*s", len, s);
1128 }
1129 }
1130 return (s);
1131 }
1132
1133 static void
1134 toolong(signo)
1135 int signo;
1136 {
1137
1138 reply(421,
1139 "Timeout (%d seconds): closing control connection.",
1140 curclass.timeout);
1141 if (logging)
1142 syslog(LOG_INFO, "User %s timed out after %d seconds",
1143 (pw ? pw -> pw_name : "unknown"), curclass.timeout);
1144 dologout(1);
1145 }
1146
1147 static int
1148 yylex()
1149 {
1150 static int cpos, state;
1151 char *cp, *cp2;
1152 struct tab *p;
1153 int n;
1154 char c;
1155
1156 switch (state) {
1157
1158 case CMD:
1159 hasyyerrored = 0;
1160 (void) signal(SIGALRM, toolong);
1161 (void) alarm(curclass.timeout);
1162 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
1163 reply(221, "You could at least say goodbye.");
1164 dologout(0);
1165 }
1166 (void) alarm(0);
1167 #ifdef HASSETPROCTITLE
1168 if (strncasecmp(cbuf, "PASS", 4) != 0)
1169 setproctitle("%s: %s", proctitle, cbuf);
1170 #endif /* HASSETPROCTITLE */
1171 if ((cp = strchr(cbuf, '\r'))) {
1172 *cp++ = '\n';
1173 *cp = '\0';
1174 }
1175 if ((cp = strpbrk(cbuf, " \n")))
1176 cpos = cp - cbuf;
1177 if (cpos == 0)
1178 cpos = 4;
1179 c = cbuf[cpos];
1180 cbuf[cpos] = '\0';
1181 p = lookup(cmdtab, cbuf);
1182 cbuf[cpos] = c;
1183 if (p != NULL) {
1184 if (p->implemented == 0) {
1185 reply(502, "%s command not implemented.",
1186 p->name);
1187 hasyyerrored = 1;
1188 break;
1189 }
1190 state = p->state;
1191 yylval.s = p->name;
1192 return (p->token);
1193 }
1194 break;
1195
1196 case SITECMD:
1197 if (cbuf[cpos] == ' ') {
1198 cpos++;
1199 return (SP);
1200 }
1201 cp = &cbuf[cpos];
1202 if ((cp2 = strpbrk(cp, " \n")))
1203 cpos = cp2 - cbuf;
1204 c = cbuf[cpos];
1205 cbuf[cpos] = '\0';
1206 p = lookup(sitetab, cp);
1207 cbuf[cpos] = c;
1208 if (p != NULL) {
1209 if (p->implemented == 0) {
1210 reply(502, "SITE %s command not implemented.",
1211 p->name);
1212 hasyyerrored = 1;
1213 break;
1214 }
1215 state = p->state;
1216 yylval.s = p->name;
1217 return (p->token);
1218 }
1219 break;
1220
1221 case OSTR:
1222 if (cbuf[cpos] == '\n') {
1223 state = CMD;
1224 return (CRLF);
1225 }
1226 /* FALLTHROUGH */
1227
1228 case STR1:
1229 case ZSTR1:
1230 dostr1:
1231 if (cbuf[cpos] == ' ') {
1232 cpos++;
1233 state = state == OSTR ? STR2 : ++state;
1234 return (SP);
1235 }
1236 break;
1237
1238 case ZSTR2:
1239 if (cbuf[cpos] == '\n') {
1240 state = CMD;
1241 return (CRLF);
1242 }
1243 /* FALLTHROUGH */
1244
1245 case STR2:
1246 cp = &cbuf[cpos];
1247 n = strlen(cp);
1248 cpos += n - 1;
1249 /*
1250 * Make sure the string is nonempty and \n terminated.
1251 */
1252 if (n > 1 && cbuf[cpos] == '\n') {
1253 cbuf[cpos] = '\0';
1254 yylval.s = xstrdup(cp);
1255 cbuf[cpos] = '\n';
1256 state = ARGS;
1257 return (STRING);
1258 }
1259 break;
1260
1261 case NSTR:
1262 if (cbuf[cpos] == ' ') {
1263 cpos++;
1264 return (SP);
1265 }
1266 if (isdigit(cbuf[cpos])) {
1267 cp = &cbuf[cpos];
1268 while (isdigit(cbuf[++cpos]))
1269 ;
1270 c = cbuf[cpos];
1271 cbuf[cpos] = '\0';
1272 yylval.i = atoi(cp);
1273 cbuf[cpos] = c;
1274 state = STR1;
1275 return (NUMBER);
1276 }
1277 state = STR1;
1278 goto dostr1;
1279
1280 case ARGS:
1281 if (isdigit(cbuf[cpos])) {
1282 cp = &cbuf[cpos];
1283 while (isdigit(cbuf[++cpos]))
1284 ;
1285 c = cbuf[cpos];
1286 cbuf[cpos] = '\0';
1287 yylval.i = atoi(cp);
1288 cbuf[cpos] = c;
1289 return (NUMBER);
1290 }
1291 switch (cbuf[cpos++]) {
1292
1293 case '\n':
1294 state = CMD;
1295 return (CRLF);
1296
1297 case ' ':
1298 return (SP);
1299
1300 case ',':
1301 return (COMMA);
1302
1303 case 'A':
1304 case 'a':
1305 return (A);
1306
1307 case 'B':
1308 case 'b':
1309 return (B);
1310
1311 case 'C':
1312 case 'c':
1313 return (C);
1314
1315 case 'E':
1316 case 'e':
1317 return (E);
1318
1319 case 'F':
1320 case 'f':
1321 return (F);
1322
1323 case 'I':
1324 case 'i':
1325 return (I);
1326
1327 case 'L':
1328 case 'l':
1329 return (L);
1330
1331 case 'N':
1332 case 'n':
1333 return (N);
1334
1335 case 'P':
1336 case 'p':
1337 return (P);
1338
1339 case 'R':
1340 case 'r':
1341 return (R);
1342
1343 case 'S':
1344 case 's':
1345 return (S);
1346
1347 case 'T':
1348 case 't':
1349 return (T);
1350
1351 }
1352 break;
1353
1354 case NOARGS:
1355 if (cbuf[cpos] == '\n') {
1356 state = CMD;
1357 return (CRLF);
1358 }
1359 c = cbuf[cpos];
1360 cbuf[cpos] = '\0';
1361 reply(501, "'%s' command does not take any arguments.", cbuf);
1362 hasyyerrored = 1;
1363 cbuf[cpos] = c;
1364 break;
1365
1366 default:
1367 fatal("Unknown state in scanner.");
1368 }
1369 yyerror(NULL);
1370 state = CMD;
1371 longjmp(errcatch, 0);
1372 /* NOTREACHED */
1373 }
1374
1375 /* ARGSUSED */
1376 void
1377 yyerror(s)
1378 char *s;
1379 {
1380 char *cp;
1381
1382 if (hasyyerrored)
1383 return;
1384 if ((cp = strchr(cbuf,'\n')) != NULL)
1385 *cp = '\0';
1386 reply(500, "'%s': command not understood.", cbuf);
1387 hasyyerrored = 1;
1388 }
1389
1390 static void
1391 help(ctab, s)
1392 struct tab *ctab;
1393 char *s;
1394 {
1395 struct tab *c;
1396 int width, NCMDS;
1397 off_t b;
1398 char *type;
1399
1400 if (ctab == sitetab)
1401 type = "SITE ";
1402 else
1403 type = "";
1404 width = 0, NCMDS = 0;
1405 for (c = ctab; c->name != NULL; c++) {
1406 int len = strlen(c->name);
1407
1408 if (len > width)
1409 width = len;
1410 NCMDS++;
1411 }
1412 width = (width + 8) &~ 7;
1413 if (s == 0) {
1414 int i, j, w;
1415 int columns, lines;
1416
1417 lreply(214, "The following %scommands are recognized.", type);
1418 lreply(0, "(`-' = not implemented, `+' = supports options)");
1419 columns = 76 / width;
1420 if (columns == 0)
1421 columns = 1;
1422 lines = (NCMDS + columns - 1) / columns;
1423 for (i = 0; i < lines; i++) {
1424 b = printf(" ");
1425 total_bytes += b;
1426 total_bytes_out += b;
1427 for (j = 0; j < columns; j++) {
1428 c = ctab + j * lines + i;
1429 b = printf("%s", c->name);
1430 total_bytes += b;
1431 total_bytes_out += b;
1432 w = strlen(c->name);
1433 if (! c->implemented) {
1434 putchar('-');
1435 w++;
1436 }
1437 if (c->hasopts) {
1438 putchar('+');
1439 w++;
1440 }
1441 if (c + lines >= &ctab[NCMDS])
1442 break;
1443 while (w < width) {
1444 putchar(' ');
1445 w++;
1446 }
1447 }
1448 b = printf("\r\n");
1449 total_bytes += b;
1450 total_bytes_out += b;
1451 }
1452 (void) fflush(stdout);
1453 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1454 return;
1455 }
1456 c = lookup(ctab, s);
1457 if (c == (struct tab *)0) {
1458 reply(502, "Unknown command %s.", s);
1459 return;
1460 }
1461 if (c->implemented)
1462 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1463 else
1464 reply(214, "%s%-*s\t%s; not implemented.", type, width,
1465 c->name, c->help);
1466 }
1467
1468 static void
1469 sizecmd(filename)
1470 char *filename;
1471 {
1472 switch (type) {
1473 case TYPE_L:
1474 case TYPE_I: {
1475 struct stat stbuf;
1476 if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1477 reply(550, "%s: not a plain file.", filename);
1478 else
1479 reply(213, "%qu", stbuf.st_size);
1480 break; }
1481 case TYPE_A: {
1482 FILE *fin;
1483 int c;
1484 off_t count;
1485 struct stat stbuf;
1486 fin = fopen(filename, "r");
1487 if (fin == NULL) {
1488 perror_reply(550, filename);
1489 return;
1490 }
1491 if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1492 reply(550, "%s: not a plain file.", filename);
1493 (void) fclose(fin);
1494 return;
1495 }
1496
1497 count = 0;
1498 while((c=getc(fin)) != EOF) {
1499 if (c == '\n') /* will get expanded to \r\n */
1500 count++;
1501 count++;
1502 }
1503 (void) fclose(fin);
1504
1505 reply(213, "%qd", count);
1506 break; }
1507 default:
1508 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1509 }
1510 }
1511
1512 static void
1513 opts(command)
1514 const char *command;
1515 {
1516 struct tab *c;
1517 char *ep;
1518
1519 if ((ep = strchr(command, ' ')) != NULL)
1520 *ep++ = '\0';
1521 c = lookup(cmdtab, command);
1522 if (c == NULL) {
1523 reply(502, "Unknown command %s.", command);
1524 return;
1525 }
1526 if (c->implemented == 0) {
1527 reply(502, "%s command not implemented.", c->name);
1528 return;
1529 }
1530 if (c->hasopts == 0) {
1531 reply(501, "%s command does not support persistent options.",
1532 c->name);
1533 return;
1534 }
1535
1536 if (ep != NULL && *ep != '\0') {
1537 if (c->options != NULL)
1538 free(c->options);
1539 c->options = xstrdup(ep);
1540 }
1541 if (c->options != NULL)
1542 reply(200, "Options for %s are '%s'.", c->name, c->options);
1543 else
1544 reply(200, "No options defined for %s.", c->name);
1545 }
1546