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