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