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