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