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