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