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