ftpcmd.y revision 1.45 1 /* $NetBSD: ftpcmd.y,v 1.45 2000/03/05 06:12:19 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.45 2000/03/05 06:12:19 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 __P((struct tab *, char *));
1472 static struct tab *lookup __P((struct tab *, const char *));
1473 static void opts __P((const char *));
1474 static void sizecmd __P((char *));
1475 static void toolong __P((int));
1476 static int yylex __P((void));
1477
1478 extern int epsvall;
1479
1480 static struct tab *
1481 lookup(p, cmd)
1482 struct tab *p;
1483 const char *cmd;
1484 {
1485
1486 for (; p->name != NULL; p++)
1487 if (strcasecmp(cmd, p->name) == 0)
1488 return (p);
1489 return (0);
1490 }
1491
1492 #include <arpa/telnet.h>
1493
1494 /*
1495 * getline - a hacked up version of fgets to ignore TELNET escape codes.
1496 */
1497 char *
1498 getline(s, n, iop)
1499 char *s;
1500 int n;
1501 FILE *iop;
1502 {
1503 off_t b;
1504 int c;
1505 char *cs;
1506
1507 cs = s;
1508 /* tmpline may contain saved command from urgent mode interruption */
1509 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1510 *cs++ = tmpline[c];
1511 if (tmpline[c] == '\n') {
1512 *cs++ = '\0';
1513 if (debug)
1514 syslog(LOG_DEBUG, "command: %s", s);
1515 tmpline[0] = '\0';
1516 return(s);
1517 }
1518 if (c == 0)
1519 tmpline[0] = '\0';
1520 }
1521 while ((c = getc(iop)) != EOF) {
1522 total_bytes++;
1523 total_bytes_in++;
1524 c &= 0377;
1525 if (c == IAC) {
1526 if ((c = getc(iop)) != EOF) {
1527 total_bytes++;
1528 total_bytes_in++;
1529 c &= 0377;
1530 switch (c) {
1531 case WILL:
1532 case WONT:
1533 c = getc(iop);
1534 total_bytes++;
1535 total_bytes_in++;
1536 b = printf("%c%c%c", IAC, DONT, 0377&c);
1537 total_bytes += b;
1538 total_bytes_out += b;
1539 (void) fflush(stdout);
1540 continue;
1541 case DO:
1542 case DONT:
1543 c = getc(iop);
1544 total_bytes++;
1545 total_bytes_in++;
1546 b = printf("%c%c%c", IAC, WONT, 0377&c);
1547 total_bytes += b;
1548 total_bytes_out += b;
1549 (void) fflush(stdout);
1550 continue;
1551 case IAC:
1552 break;
1553 default:
1554 continue; /* ignore command */
1555 }
1556 }
1557 }
1558 *cs++ = c;
1559 if (--n <= 0 || c == '\n')
1560 break;
1561 }
1562 if (c == EOF && cs == s)
1563 return (NULL);
1564 *cs++ = '\0';
1565 if (debug) {
1566 if (curclass.type != CLASS_GUEST &&
1567 strncasecmp("pass ", s, 5) == 0) {
1568 /* Don't syslog passwords */
1569 syslog(LOG_DEBUG, "command: %.5s ???", s);
1570 } else {
1571 char *cp;
1572 int len;
1573
1574 /* Don't syslog trailing CR-LF */
1575 len = strlen(s);
1576 cp = s + len - 1;
1577 while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1578 --cp;
1579 --len;
1580 }
1581 syslog(LOG_DEBUG, "command: %.*s", len, s);
1582 }
1583 }
1584 return (s);
1585 }
1586
1587 static void
1588 toolong(signo)
1589 int signo;
1590 {
1591
1592 reply(421,
1593 "Timeout (%d seconds): closing control connection.",
1594 curclass.timeout);
1595 if (logging)
1596 syslog(LOG_INFO, "User %s timed out after %d seconds",
1597 (pw ? pw->pw_name : "unknown"), curclass.timeout);
1598 dologout(1);
1599 }
1600
1601 static int
1602 yylex()
1603 {
1604 static int cpos, state;
1605 char *cp, *cp2;
1606 struct tab *p;
1607 int n;
1608 char c;
1609
1610 switch (state) {
1611
1612 case CMD:
1613 hasyyerrored = 0;
1614 (void) signal(SIGALRM, toolong);
1615 (void) alarm(curclass.timeout);
1616 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
1617 reply(221, "You could at least say goodbye.");
1618 dologout(0);
1619 }
1620 (void) alarm(0);
1621 if ((cp = strchr(cbuf, '\r'))) {
1622 *cp = '\0';
1623 #ifdef HASSETPROCTITLE
1624 if (strncasecmp(cbuf, "PASS", 4) != 0)
1625 setproctitle("%s: %s", proctitle, cbuf);
1626 #endif /* HASSETPROCTITLE */
1627 *cp++ = '\n';
1628 *cp = '\0';
1629 }
1630 if ((cp = strpbrk(cbuf, " \n")))
1631 cpos = cp - cbuf;
1632 if (cpos == 0)
1633 cpos = 4;
1634 c = cbuf[cpos];
1635 cbuf[cpos] = '\0';
1636 p = lookup(cmdtab, cbuf);
1637 cbuf[cpos] = c;
1638 if (p != NULL) {
1639 if (p->implemented == 0) {
1640 reply(502, "%s command not implemented.",
1641 p->name);
1642 hasyyerrored = 1;
1643 break;
1644 }
1645 state = p->state;
1646 yylval.s = p->name;
1647 return (p->token);
1648 }
1649 break;
1650
1651 case SITECMD:
1652 if (cbuf[cpos] == ' ') {
1653 cpos++;
1654 return (SP);
1655 }
1656 cp = &cbuf[cpos];
1657 if ((cp2 = strpbrk(cp, " \n")))
1658 cpos = cp2 - cbuf;
1659 c = cbuf[cpos];
1660 cbuf[cpos] = '\0';
1661 p = lookup(sitetab, cp);
1662 cbuf[cpos] = c;
1663 if (p != NULL) {
1664 if (p->implemented == 0) {
1665 reply(502, "SITE %s command not implemented.",
1666 p->name);
1667 hasyyerrored = 1;
1668 break;
1669 }
1670 state = p->state;
1671 yylval.s = p->name;
1672 return (p->token);
1673 }
1674 break;
1675
1676 case OSTR:
1677 if (cbuf[cpos] == '\n') {
1678 state = CMD;
1679 return (CRLF);
1680 }
1681 /* FALLTHROUGH */
1682
1683 case STR1:
1684 case ZSTR1:
1685 dostr1:
1686 if (cbuf[cpos] == ' ') {
1687 cpos++;
1688 state = state == OSTR ? STR2 : state+1;
1689 return (SP);
1690 }
1691 break;
1692
1693 case ZSTR2:
1694 if (cbuf[cpos] == '\n') {
1695 state = CMD;
1696 return (CRLF);
1697 }
1698 /* FALLTHROUGH */
1699
1700 case STR2:
1701 cp = &cbuf[cpos];
1702 n = strlen(cp);
1703 cpos += n - 1;
1704 /*
1705 * Make sure the string is nonempty and \n terminated.
1706 */
1707 if (n > 1 && cbuf[cpos] == '\n') {
1708 cbuf[cpos] = '\0';
1709 yylval.s = xstrdup(cp);
1710 cbuf[cpos] = '\n';
1711 state = ARGS;
1712 return (STRING);
1713 }
1714 break;
1715
1716 case NSTR:
1717 if (cbuf[cpos] == ' ') {
1718 cpos++;
1719 return (SP);
1720 }
1721 if (isdigit(cbuf[cpos])) {
1722 cp = &cbuf[cpos];
1723 while (isdigit(cbuf[++cpos]))
1724 ;
1725 c = cbuf[cpos];
1726 cbuf[cpos] = '\0';
1727 yylval.i = atoi(cp);
1728 cbuf[cpos] = c;
1729 state = STR1;
1730 return (NUMBER);
1731 }
1732 state = STR1;
1733 goto dostr1;
1734
1735 case ARGS:
1736 if (isdigit(cbuf[cpos])) {
1737 cp = &cbuf[cpos];
1738 while (isdigit(cbuf[++cpos]))
1739 ;
1740 c = cbuf[cpos];
1741 cbuf[cpos] = '\0';
1742 yylval.i = atoi(cp);
1743 cbuf[cpos] = c;
1744 return (NUMBER);
1745 }
1746 if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1747 && !isalnum(cbuf[cpos + 3])) {
1748 yylval.s = xstrdup("ALL");
1749 cpos += 3;
1750 return ALL;
1751 }
1752 switch (cbuf[cpos++]) {
1753
1754 case '\n':
1755 state = CMD;
1756 return (CRLF);
1757
1758 case ' ':
1759 return (SP);
1760
1761 case ',':
1762 return (COMMA);
1763
1764 case 'A':
1765 case 'a':
1766 return (A);
1767
1768 case 'B':
1769 case 'b':
1770 return (B);
1771
1772 case 'C':
1773 case 'c':
1774 return (C);
1775
1776 case 'E':
1777 case 'e':
1778 return (E);
1779
1780 case 'F':
1781 case 'f':
1782 return (F);
1783
1784 case 'I':
1785 case 'i':
1786 return (I);
1787
1788 case 'L':
1789 case 'l':
1790 return (L);
1791
1792 case 'N':
1793 case 'n':
1794 return (N);
1795
1796 case 'P':
1797 case 'p':
1798 return (P);
1799
1800 case 'R':
1801 case 'r':
1802 return (R);
1803
1804 case 'S':
1805 case 's':
1806 return (S);
1807
1808 case 'T':
1809 case 't':
1810 return (T);
1811
1812 }
1813 break;
1814
1815 case NOARGS:
1816 if (cbuf[cpos] == '\n') {
1817 state = CMD;
1818 return (CRLF);
1819 }
1820 c = cbuf[cpos];
1821 cbuf[cpos] = '\0';
1822 reply(501, "'%s' command does not take any arguments.", cbuf);
1823 hasyyerrored = 1;
1824 cbuf[cpos] = c;
1825 break;
1826
1827 default:
1828 fatal("Unknown state in scanner.");
1829 }
1830 yyerror(NULL);
1831 state = CMD;
1832 longjmp(errcatch, 0);
1833 /* NOTREACHED */
1834 }
1835
1836 /* ARGSUSED */
1837 void
1838 yyerror(s)
1839 char *s;
1840 {
1841 char *cp;
1842
1843 if (hasyyerrored)
1844 return;
1845 if ((cp = strchr(cbuf,'\n')) != NULL)
1846 *cp = '\0';
1847 reply(500, "'%s': command not understood.", cbuf);
1848 hasyyerrored = 1;
1849 }
1850
1851 static void
1852 help(ctab, s)
1853 struct tab *ctab;
1854 char *s;
1855 {
1856 struct tab *c;
1857 int width, NCMDS;
1858 off_t b;
1859 char *type;
1860
1861 if (ctab == sitetab)
1862 type = "SITE ";
1863 else
1864 type = "";
1865 width = 0, NCMDS = 0;
1866 for (c = ctab; c->name != NULL; c++) {
1867 int len = strlen(c->name);
1868
1869 if (len > width)
1870 width = len;
1871 NCMDS++;
1872 }
1873 width = (width + 8) &~ 7;
1874 if (s == 0) {
1875 int i, j, w;
1876 int columns, lines;
1877
1878 lreply(214, "");
1879 lreply(0, "The following %scommands are recognized.", type);
1880 lreply(0, "(`-' = not implemented, `+' = supports options)");
1881 columns = 76 / width;
1882 if (columns == 0)
1883 columns = 1;
1884 lines = (NCMDS + columns - 1) / columns;
1885 for (i = 0; i < lines; i++) {
1886 b = printf(" ");
1887 total_bytes += b;
1888 total_bytes_out += b;
1889 for (j = 0; j < columns; j++) {
1890 c = ctab + j * lines + i;
1891 b = printf("%s", c->name);
1892 total_bytes += b;
1893 total_bytes_out += b;
1894 w = strlen(c->name);
1895 if (! c->implemented) {
1896 putchar('-');
1897 total_bytes++;
1898 total_bytes_out++;
1899 w++;
1900 }
1901 if (c->hasopts) {
1902 putchar('+');
1903 total_bytes++;
1904 total_bytes_out++;
1905 w++;
1906 }
1907 if (c + lines >= &ctab[NCMDS])
1908 break;
1909 while (w < width) {
1910 putchar(' ');
1911 total_bytes++;
1912 total_bytes_out++;
1913 w++;
1914 }
1915 }
1916 b = printf("\r\n");
1917 total_bytes += b;
1918 total_bytes_out += b;
1919 }
1920 (void) fflush(stdout);
1921 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1922 return;
1923 }
1924 c = lookup(ctab, s);
1925 if (c == (struct tab *)0) {
1926 reply(502, "Unknown command %s.", s);
1927 return;
1928 }
1929 if (c->implemented)
1930 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1931 else
1932 reply(214, "%s%-*s\t%s; not implemented.", type, width,
1933 c->name, c->help);
1934 }
1935
1936 static void
1937 sizecmd(filename)
1938 char *filename;
1939 {
1940 switch (type) {
1941 case TYPE_L:
1942 case TYPE_I: {
1943 struct stat stbuf;
1944 if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1945 reply(550, "%s: not a plain file.", filename);
1946 else
1947 reply(213, "%qu", (qufmt_t)stbuf.st_size);
1948 break; }
1949 case TYPE_A: {
1950 FILE *fin;
1951 int c;
1952 off_t count;
1953 struct stat stbuf;
1954 fin = fopen(filename, "r");
1955 if (fin == NULL) {
1956 perror_reply(550, filename);
1957 return;
1958 }
1959 if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1960 reply(550, "%s: not a plain file.", filename);
1961 (void) fclose(fin);
1962 return;
1963 }
1964
1965 count = 0;
1966 while((c=getc(fin)) != EOF) {
1967 if (c == '\n') /* will get expanded to \r\n */
1968 count++;
1969 count++;
1970 }
1971 (void) fclose(fin);
1972
1973 reply(213, "%qd", (qdfmt_t)count);
1974 break; }
1975 default:
1976 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1977 }
1978 }
1979
1980 static void
1981 opts(command)
1982 const char *command;
1983 {
1984 struct tab *c;
1985 char *ep;
1986
1987 if ((ep = strchr(command, ' ')) != NULL)
1988 *ep++ = '\0';
1989 c = lookup(cmdtab, command);
1990 if (c == NULL) {
1991 reply(502, "Unknown command %s.", command);
1992 return;
1993 }
1994 if (c->implemented == 0) {
1995 reply(502, "%s command not implemented.", c->name);
1996 return;
1997 }
1998 if (c->hasopts == 0) {
1999 reply(501, "%s command does not support persistent options.",
2000 c->name);
2001 return;
2002 }
2003
2004 if (ep != NULL && *ep != '\0')
2005 REASSIGN(c->options, xstrdup(ep));
2006 if (c->options != NULL)
2007 reply(200, "Options for %s are '%s'.", c->name, c->options);
2008 else
2009 reply(200, "No options defined for %s.", c->name);
2010 }
2011