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