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