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