ftpcmd.y revision 1.50 1 /* $NetBSD: ftpcmd.y,v 1.50 2000/07/15 03:45:19 lukem Exp $ */
2
3 /*-
4 * Copyright (c) 1997-2000 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.50 2000/07/15 03:45: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 MLST MLSD
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 reply(-221, "%s", "");
229 reply(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 reply(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
956 feat();
957 }
958
959 | OPTS SP STRING CRLF
960 {
961
962 opts($3);
963 free($3);
964 }
965
966
967 /* extensions from draft-ietf-ftpext-mlst-11 */
968
969 /*
970 * Return size of file in a format suitable for
971 * using with RESTART (we just count bytes).
972 */
973 | SIZE check_login SP pathname CRLF
974 {
975 if ($2 && $4 != NULL)
976 sizecmd($4);
977 if ($4 != NULL)
978 free($4);
979 }
980
981 /*
982 * Return modification time of file as an ISO 3307
983 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
984 * where xxx is the fractional second (of any precision,
985 * not necessarily 3 digits)
986 */
987 | MDTM check_login SP pathname CRLF
988 {
989 if ($2 && $4 != NULL) {
990 struct stat stbuf;
991 if (stat($4, &stbuf) < 0)
992 perror_reply(550, $4);
993 else if (!S_ISREG(stbuf.st_mode)) {
994 reply(550, "%s: not a plain file.", $4);
995 } else {
996 struct tm *t;
997
998 t = gmtime(&stbuf.st_mtime);
999 reply(213,
1000 "%04d%02d%02d%02d%02d%02d",
1001 TM_YEAR_BASE + t->tm_year,
1002 t->tm_mon+1, t->tm_mday,
1003 t->tm_hour, t->tm_min, t->tm_sec);
1004 }
1005 }
1006 if ($4 != NULL)
1007 free($4);
1008 }
1009
1010 | MLST check_login SP pathname CRLF
1011 {
1012 if ($2 && $4 != NULL)
1013 mlst($4);
1014 if ($4 != NULL)
1015 free($4);
1016 }
1017
1018 | MLST check_login CRLF
1019 {
1020 mlst(NULL);
1021 }
1022
1023 | MLSD check_login SP pathname CRLF
1024 {
1025 if ($2 && $4 != NULL)
1026 mlsd($4);
1027 if ($4 != NULL)
1028 free($4);
1029 }
1030
1031 | MLSD check_login CRLF
1032 {
1033 mlsd(NULL);
1034 }
1035
1036 | error CRLF
1037 {
1038 yyerrok;
1039 }
1040 ;
1041
1042 rcmd
1043 : REST check_login SP byte_size CRLF
1044 {
1045 if ($2) {
1046 fromname = NULL;
1047 restart_point = $4; /* XXX $3 is only "int" */
1048 reply(350, "Restarting at %qd. %s",
1049 (qdfmt_t)restart_point,
1050 "Send STORE or RETRIEVE to initiate transfer.");
1051 }
1052 }
1053
1054 | RNFR check_modify SP pathname CRLF
1055 {
1056 restart_point = (off_t) 0;
1057 if ($2 && $4) {
1058 fromname = renamefrom($4);
1059 }
1060 if ($4)
1061 free($4);
1062 }
1063 ;
1064
1065 username
1066 : STRING
1067 ;
1068
1069 password
1070 : /* empty */
1071 {
1072 $$ = (char *)calloc(1, sizeof(char));
1073 }
1074
1075 | STRING
1076 ;
1077
1078 byte_size
1079 : NUMBER
1080 ;
1081
1082 host_port
1083 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
1084 NUMBER COMMA NUMBER
1085 {
1086 char *a, *p;
1087
1088 data_dest.su_len = sizeof(struct sockaddr_in);
1089 data_dest.su_family = AF_INET;
1090 p = (char *)&data_dest.su_sin.sin_port;
1091 p[0] = $9; p[1] = $11;
1092 a = (char *)&data_dest.su_sin.sin_addr;
1093 a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
1094 }
1095 ;
1096
1097 host_long_port4
1098 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
1099 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
1100 NUMBER
1101 {
1102 char *a, *p;
1103
1104 data_dest.su_sin.sin_len =
1105 sizeof(struct sockaddr_in);
1106 data_dest.su_family = AF_INET;
1107 p = (char *)&data_dest.su_port;
1108 p[0] = $15; p[1] = $17;
1109 a = (char *)&data_dest.su_sin.sin_addr;
1110 a[0] = $5; a[1] = $7; a[2] = $9; a[3] = $11;
1111
1112 /* reject invalid LPRT command */
1113 if ($1 != 4 || $3 != 4 || $13 != 2)
1114 memset(&data_dest, 0, sizeof(data_dest));
1115 }
1116 ;
1117
1118 host_long_port6
1119 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
1120 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
1121 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
1122 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
1123 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
1124 NUMBER
1125 {
1126 char *a, *p;
1127
1128 data_dest.su_sin6.sin6_len =
1129 sizeof(struct sockaddr_in6);
1130 data_dest.su_family = AF_INET6;
1131 p = (char *)&data_dest.su_port;
1132 p[0] = $39; p[1] = $41;
1133 a = (char *)&data_dest.su_sin6.sin6_addr;
1134 a[0] = $5; a[1] = $7; a[2] = $9; a[3] = $11;
1135 a[4] = $13; a[5] = $15; a[6] = $17; a[7] = $19;
1136 a[8] = $21; a[9] = $23; a[10] = $25; a[11] = $27;
1137 a[12] = $29; a[13] = $31; a[14] = $33; a[15] = $35;
1138 if (his_addr.su_family == AF_INET6) {
1139 /* XXX more sanity checks! */
1140 data_dest.su_sin6.sin6_scope_id =
1141 his_addr.su_sin6.sin6_scope_id;
1142 }
1143
1144 /* reject invalid LPRT command */
1145 if ($1 != 6 || $3 != 16 || $37 != 2)
1146 memset(&data_dest, 0, sizeof(data_dest));
1147 }
1148 ;
1149
1150 form_code
1151 : N
1152 {
1153 $$ = FORM_N;
1154 }
1155
1156 | T
1157 {
1158 $$ = FORM_T;
1159 }
1160
1161 | C
1162 {
1163 $$ = FORM_C;
1164 }
1165 ;
1166
1167 type_code
1168 : A
1169 {
1170 cmd_type = TYPE_A;
1171 cmd_form = FORM_N;
1172 }
1173
1174 | A SP form_code
1175 {
1176 cmd_type = TYPE_A;
1177 cmd_form = $3;
1178 }
1179
1180 | E
1181 {
1182 cmd_type = TYPE_E;
1183 cmd_form = FORM_N;
1184 }
1185
1186 | E SP form_code
1187 {
1188 cmd_type = TYPE_E;
1189 cmd_form = $3;
1190 }
1191
1192 | I
1193 {
1194 cmd_type = TYPE_I;
1195 }
1196
1197 | L
1198 {
1199 cmd_type = TYPE_L;
1200 cmd_bytesz = NBBY;
1201 }
1202
1203 | L SP byte_size
1204 {
1205 cmd_type = TYPE_L;
1206 cmd_bytesz = $3;
1207 }
1208
1209 /* this is for a bug in the BBN ftp */
1210 | L byte_size
1211 {
1212 cmd_type = TYPE_L;
1213 cmd_bytesz = $2;
1214 }
1215 ;
1216
1217 struct_code
1218 : F
1219 {
1220 $$ = STRU_F;
1221 }
1222
1223 | R
1224 {
1225 $$ = STRU_R;
1226 }
1227
1228 | P
1229 {
1230 $$ = STRU_P;
1231 }
1232 ;
1233
1234 mode_code
1235 : S
1236 {
1237 $$ = MODE_S;
1238 }
1239
1240 | B
1241 {
1242 $$ = MODE_B;
1243 }
1244
1245 | C
1246 {
1247 $$ = MODE_C;
1248 }
1249 ;
1250
1251 pathname
1252 : pathstring
1253 {
1254 /*
1255 * Problem: this production is used for all pathname
1256 * processing, but only gives a 550 error reply.
1257 * This is a valid reply in some cases but not in
1258 * others.
1259 */
1260 if (logged_in && $1 && *$1 == '~') {
1261 glob_t gl;
1262 int flags =
1263 GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
1264
1265 if ($1[1] == '\0')
1266 $$ = xstrdup(pw->pw_dir);
1267 else {
1268 memset(&gl, 0, sizeof(gl));
1269 if (glob($1, flags, NULL, &gl) ||
1270 gl.gl_pathc == 0) {
1271 reply(550, "not found");
1272 $$ = NULL;
1273 } else
1274 $$ = xstrdup(gl.gl_pathv[0]);
1275 globfree(&gl);
1276 }
1277 free($1);
1278 } else
1279 $$ = $1;
1280 }
1281 ;
1282
1283 pathstring
1284 : STRING
1285 ;
1286
1287 octal_number
1288 : NUMBER
1289 {
1290 int ret, dec, multby, digit;
1291
1292 /*
1293 * Convert a number that was read as decimal number
1294 * to what it would be if it had been read as octal.
1295 */
1296 dec = $1;
1297 multby = 1;
1298 ret = 0;
1299 while (dec) {
1300 digit = dec%10;
1301 if (digit > 7) {
1302 ret = -1;
1303 break;
1304 }
1305 ret += digit * multby;
1306 multby *= 8;
1307 dec /= 10;
1308 }
1309 $$ = ret;
1310 }
1311 ;
1312
1313 mechanism_name
1314 : STRING
1315 ;
1316
1317 base64data
1318 : STRING
1319 ;
1320
1321 prot_code
1322 : STRING
1323 ;
1324
1325 decimal_integer
1326 : NUMBER
1327 ;
1328
1329 check_login
1330 : /* empty */
1331 {
1332 if (logged_in)
1333 $$ = 1;
1334 else {
1335 reply(530, "Please login with USER and PASS.");
1336 $$ = 0;
1337 hasyyerrored = 1;
1338 }
1339 }
1340 ;
1341
1342 check_modify
1343 : /* empty */
1344 {
1345 if (logged_in) {
1346 if (curclass.modify)
1347 $$ = 1;
1348 else {
1349 reply(502,
1350 "No permission to use this command.");
1351 $$ = 0;
1352 hasyyerrored = 1;
1353 }
1354 } else {
1355 reply(530, "Please login with USER and PASS.");
1356 $$ = 0;
1357 hasyyerrored = 1;
1358 }
1359 }
1360
1361 check_upload
1362 : /* empty */
1363 {
1364 if (logged_in) {
1365 if (curclass.upload)
1366 $$ = 1;
1367 else {
1368 reply(502,
1369 "No permission to use this command.");
1370 $$ = 0;
1371 hasyyerrored = 1;
1372 }
1373 } else {
1374 reply(530, "Please login with USER and PASS.");
1375 $$ = 0;
1376 hasyyerrored = 1;
1377 }
1378 }
1379
1380
1381 %%
1382
1383 #define CMD 0 /* beginning of command */
1384 #define ARGS 1 /* expect miscellaneous arguments */
1385 #define STR1 2 /* expect SP followed by STRING */
1386 #define STR2 3 /* expect STRING */
1387 #define OSTR 4 /* optional SP then STRING */
1388 #define ZSTR1 5 /* SP then optional STRING */
1389 #define ZSTR2 6 /* optional STRING after SP */
1390 #define SITECMD 7 /* SITE command */
1391 #define NSTR 8 /* Number followed by a string */
1392 #define NOARGS 9 /* No arguments allowed */
1393
1394 struct tab cmdtab[] = {
1395 /* From RFC 959, in order defined (5.3.1) */
1396 { "USER", USER, STR1, 1, "<sp> username" },
1397 { "PASS", PASS, ZSTR1, 1, "<sp> password" },
1398 { "ACCT", ACCT, STR1, 0, "(specify account)" },
1399 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
1400 { "CDUP", CDUP, NOARGS, 1, "(change to parent directory)" },
1401 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
1402 { "QUIT", QUIT, NOARGS, 1, "(terminate service)" },
1403 { "REIN", REIN, NOARGS, 0, "(reinitialize server state)" },
1404 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" },
1405 { "LPRT", LPRT, ARGS, 1, "<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1406 { "EPRT", EPRT, STR1, 1, "<sp> |af|addr|port|" },
1407 { "PASV", PASV, NOARGS, 1, "(set server in passive mode)" },
1408 { "LPSV", LPSV, ARGS, 1, "(set server in passive mode)" },
1409 { "EPSV", EPSV, ARGS, 1, "[<sp> af|ALL]" },
1410 { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" },
1411 { "STRU", STRU, ARGS, 1, "(specify file structure)" },
1412 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
1413 { "RETR", RETR, STR1, 1, "<sp> file-name" },
1414 { "STOR", STOR, STR1, 1, "<sp> file-name" },
1415 { "STOU", STOU, STR1, 1, "<sp> file-name" },
1416 { "APPE", APPE, STR1, 1, "<sp> file-name" },
1417 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
1418 { "REST", REST, ARGS, 1, "<sp> offset (restart command)" },
1419 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
1420 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
1421 { "ABOR", ABOR, NOARGS, 1, "(abort operation)" },
1422 { "DELE", DELE, STR1, 1, "<sp> file-name" },
1423 { "RMD", RMD, STR1, 1, "<sp> path-name" },
1424 { "MKD", MKD, STR1, 1, "<sp> path-name" },
1425 { "PWD", PWD, NOARGS, 1, "(return current directory)" },
1426 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
1427 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
1428 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
1429 { "SYST", SYST, NOARGS, 1, "(get type of operating system)" },
1430 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
1431 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
1432 { "NOOP", NOOP, NOARGS, 2, "" },
1433
1434 /* From RFC 2228, in order defined */
1435 { "AUTH", AUTH, STR1, 1, "<sp> mechanism-name" },
1436 { "ADAT", ADAT, STR1, 1, "<sp> base-64-data" },
1437 { "PROT", PROT, STR1, 1, "<sp> prot-code" },
1438 { "PBSZ", PBSZ, ARGS, 1, "<sp> decimal-integer" },
1439 { "CCC", CCC, NOARGS, 1, "(Disable data protection)" },
1440 { "MIC", MIC, STR1, 1, "<sp> base64data" },
1441 { "CONF", CONF, STR1, 1, "<sp> base64data" },
1442 { "ENC", ENC, STR1, 1, "<sp> base64data" },
1443
1444 /* From RFC 2389, in order defined */
1445 { "FEAT", FEAT, NOARGS, 1, "(display extended features)" },
1446 { "OPTS", OPTS, STR1, 1, "<sp> command [ <sp> options ]" },
1447
1448 /* from draft-ietf-ftpext-mlst-11 */
1449 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
1450 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
1451 { "MLST", MLST, OSTR, 2, "[ <sp> path-name ]" },
1452 { "MLSD", MLSD, OSTR, 1, "[ <sp> directory-name ]" },
1453
1454 /* obsolete commands */
1455 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
1456 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
1457 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
1458 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
1459 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
1460 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
1461 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
1462 { "XCUP", CDUP, NOARGS, 1, "(change to parent directory)" },
1463 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
1464 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
1465 { "XPWD", PWD, NOARGS, 1, "(return current directory)" },
1466 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
1467
1468 { NULL, 0, 0, 0, 0 }
1469 };
1470
1471 struct tab sitetab[] = {
1472 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
1473 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
1474 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
1475 { "RATEGET", RATEGET,OSTR, 1, "[ <sp> get-throttle-rate ]" },
1476 { "RATEPUT", RATEPUT,OSTR, 1, "[ <sp> put-throttle-rate ]" },
1477 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
1478 { NULL, 0, 0, 0, NULL }
1479 };
1480
1481 static void help(struct tab *, const char *);
1482 static void toolong(int);
1483 static int yylex(void);
1484
1485 extern int epsvall;
1486
1487 struct tab *
1488 lookup(struct tab *p, const char *cmd)
1489 {
1490
1491 for (; p->name != NULL; p++)
1492 if (strcasecmp(cmd, p->name) == 0)
1493 return (p);
1494 return (0);
1495 }
1496
1497 #include <arpa/telnet.h>
1498
1499 /*
1500 * getline - a hacked up version of fgets to ignore TELNET escape codes.
1501 */
1502 char *
1503 getline(char *s, int n, FILE *iop)
1504 {
1505 int c;
1506 char *cs;
1507
1508 cs = s;
1509 /* tmpline may contain saved command from urgent mode interruption */
1510 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1511 *cs++ = tmpline[c];
1512 if (tmpline[c] == '\n') {
1513 *cs++ = '\0';
1514 if (debug)
1515 syslog(LOG_DEBUG, "command: %s", s);
1516 tmpline[0] = '\0';
1517 return(s);
1518 }
1519 if (c == 0)
1520 tmpline[0] = '\0';
1521 }
1522 while ((c = getc(iop)) != EOF) {
1523 total_bytes++;
1524 total_bytes_in++;
1525 c &= 0377;
1526 if (c == IAC) {
1527 if ((c = getc(iop)) != EOF) {
1528 total_bytes++;
1529 total_bytes_in++;
1530 c &= 0377;
1531 switch (c) {
1532 case WILL:
1533 case WONT:
1534 c = getc(iop);
1535 total_bytes++;
1536 total_bytes_in++;
1537 cprintf(stdout, "%c%c%c", IAC, DONT, 0377&c);
1538 (void) fflush(stdout);
1539 continue;
1540 case DO:
1541 case DONT:
1542 c = getc(iop);
1543 total_bytes++;
1544 total_bytes_in++;
1545 cprintf(stdout, "%c%c%c", IAC, WONT, 0377&c);
1546 (void) fflush(stdout);
1547 continue;
1548 case IAC:
1549 break;
1550 default:
1551 continue; /* ignore command */
1552 }
1553 }
1554 }
1555 *cs++ = c;
1556 if (--n <= 0 || c == '\n')
1557 break;
1558 }
1559 if (c == EOF && cs == s)
1560 return (NULL);
1561 *cs++ = '\0';
1562 if (debug) {
1563 if (curclass.type != CLASS_GUEST &&
1564 strncasecmp("pass ", s, 5) == 0) {
1565 /* Don't syslog passwords */
1566 syslog(LOG_DEBUG, "command: %.5s ???", s);
1567 } else {
1568 char *cp;
1569 int len;
1570
1571 /* Don't syslog trailing CR-LF */
1572 len = strlen(s);
1573 cp = s + len - 1;
1574 while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1575 --cp;
1576 --len;
1577 }
1578 syslog(LOG_DEBUG, "command: %.*s", len, s);
1579 }
1580 }
1581 return (s);
1582 }
1583
1584 static void
1585 toolong(int signo)
1586 {
1587
1588 reply(421,
1589 "Timeout (%d seconds): closing control connection.",
1590 curclass.timeout);
1591 if (logging)
1592 syslog(LOG_INFO, "User %s timed out after %d seconds",
1593 (pw ? pw->pw_name : "unknown"), curclass.timeout);
1594 dologout(1);
1595 }
1596
1597 static int
1598 yylex(void)
1599 {
1600 static int cpos, state;
1601 char *cp, *cp2;
1602 struct tab *p;
1603 int n;
1604 char c;
1605
1606 switch (state) {
1607
1608 case CMD:
1609 hasyyerrored = 0;
1610 (void) signal(SIGALRM, toolong);
1611 (void) alarm(curclass.timeout);
1612 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
1613 reply(221, "You could at least say goodbye.");
1614 dologout(0);
1615 }
1616 (void) alarm(0);
1617 if ((cp = strchr(cbuf, '\r'))) {
1618 *cp = '\0';
1619 #ifdef HASSETPROCTITLE
1620 if (strncasecmp(cbuf, "PASS", 4) != 0)
1621 setproctitle("%s: %s", proctitle, cbuf);
1622 #endif /* HASSETPROCTITLE */
1623 *cp++ = '\n';
1624 *cp = '\0';
1625 }
1626 if ((cp = strpbrk(cbuf, " \n")))
1627 cpos = cp - cbuf;
1628 if (cpos == 0)
1629 cpos = 4;
1630 c = cbuf[cpos];
1631 cbuf[cpos] = '\0';
1632 p = lookup(cmdtab, cbuf);
1633 cbuf[cpos] = c;
1634 if (p != NULL) {
1635 if (! CMD_IMPLEMENTED(p)) {
1636 reply(502, "%s command not implemented.",
1637 p->name);
1638 hasyyerrored = 1;
1639 break;
1640 }
1641 state = p->state;
1642 yylval.s = p->name;
1643 return (p->token);
1644 }
1645 break;
1646
1647 case SITECMD:
1648 if (cbuf[cpos] == ' ') {
1649 cpos++;
1650 return (SP);
1651 }
1652 cp = &cbuf[cpos];
1653 if ((cp2 = strpbrk(cp, " \n")))
1654 cpos = cp2 - cbuf;
1655 c = cbuf[cpos];
1656 cbuf[cpos] = '\0';
1657 p = lookup(sitetab, cp);
1658 cbuf[cpos] = c;
1659 if (p != NULL) {
1660 if (!CMD_IMPLEMENTED(p)) {
1661 reply(502, "SITE %s command not implemented.",
1662 p->name);
1663 hasyyerrored = 1;
1664 break;
1665 }
1666 state = p->state;
1667 yylval.s = p->name;
1668 return (p->token);
1669 }
1670 break;
1671
1672 case OSTR:
1673 if (cbuf[cpos] == '\n') {
1674 state = CMD;
1675 return (CRLF);
1676 }
1677 /* FALLTHROUGH */
1678
1679 case STR1:
1680 case ZSTR1:
1681 dostr1:
1682 if (cbuf[cpos] == ' ') {
1683 cpos++;
1684 state = state == OSTR ? STR2 : state+1;
1685 return (SP);
1686 }
1687 break;
1688
1689 case ZSTR2:
1690 if (cbuf[cpos] == '\n') {
1691 state = CMD;
1692 return (CRLF);
1693 }
1694 /* FALLTHROUGH */
1695
1696 case STR2:
1697 cp = &cbuf[cpos];
1698 n = strlen(cp);
1699 cpos += n - 1;
1700 /*
1701 * Make sure the string is nonempty and \n terminated.
1702 */
1703 if (n > 1 && cbuf[cpos] == '\n') {
1704 cbuf[cpos] = '\0';
1705 yylval.s = xstrdup(cp);
1706 cbuf[cpos] = '\n';
1707 state = ARGS;
1708 return (STRING);
1709 }
1710 break;
1711
1712 case NSTR:
1713 if (cbuf[cpos] == ' ') {
1714 cpos++;
1715 return (SP);
1716 }
1717 if (isdigit(cbuf[cpos])) {
1718 cp = &cbuf[cpos];
1719 while (isdigit(cbuf[++cpos]))
1720 ;
1721 c = cbuf[cpos];
1722 cbuf[cpos] = '\0';
1723 yylval.i = atoi(cp);
1724 cbuf[cpos] = c;
1725 state = STR1;
1726 return (NUMBER);
1727 }
1728 state = STR1;
1729 goto dostr1;
1730
1731 case ARGS:
1732 if (isdigit(cbuf[cpos])) {
1733 cp = &cbuf[cpos];
1734 while (isdigit(cbuf[++cpos]))
1735 ;
1736 c = cbuf[cpos];
1737 cbuf[cpos] = '\0';
1738 yylval.i = atoi(cp);
1739 cbuf[cpos] = c;
1740 return (NUMBER);
1741 }
1742 if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1743 && !isalnum(cbuf[cpos + 3])) {
1744 yylval.s = xstrdup("ALL");
1745 cpos += 3;
1746 return ALL;
1747 }
1748 switch (cbuf[cpos++]) {
1749
1750 case '\n':
1751 state = CMD;
1752 return (CRLF);
1753
1754 case ' ':
1755 return (SP);
1756
1757 case ',':
1758 return (COMMA);
1759
1760 case 'A':
1761 case 'a':
1762 return (A);
1763
1764 case 'B':
1765 case 'b':
1766 return (B);
1767
1768 case 'C':
1769 case 'c':
1770 return (C);
1771
1772 case 'E':
1773 case 'e':
1774 return (E);
1775
1776 case 'F':
1777 case 'f':
1778 return (F);
1779
1780 case 'I':
1781 case 'i':
1782 return (I);
1783
1784 case 'L':
1785 case 'l':
1786 return (L);
1787
1788 case 'N':
1789 case 'n':
1790 return (N);
1791
1792 case 'P':
1793 case 'p':
1794 return (P);
1795
1796 case 'R':
1797 case 'r':
1798 return (R);
1799
1800 case 'S':
1801 case 's':
1802 return (S);
1803
1804 case 'T':
1805 case 't':
1806 return (T);
1807
1808 }
1809 break;
1810
1811 case NOARGS:
1812 if (cbuf[cpos] == '\n') {
1813 state = CMD;
1814 return (CRLF);
1815 }
1816 c = cbuf[cpos];
1817 cbuf[cpos] = '\0';
1818 reply(501, "'%s' command does not take any arguments.", cbuf);
1819 hasyyerrored = 1;
1820 cbuf[cpos] = c;
1821 break;
1822
1823 default:
1824 fatal("Unknown state in scanner.");
1825 }
1826 yyerror(NULL);
1827 state = CMD;
1828 longjmp(errcatch, 0);
1829 /* NOTREACHED */
1830 }
1831
1832 /* ARGSUSED */
1833 void
1834 yyerror(char *s)
1835 {
1836 char *cp;
1837
1838 if (hasyyerrored)
1839 return;
1840 if ((cp = strchr(cbuf,'\n')) != NULL)
1841 *cp = '\0';
1842 reply(500, "'%s': command not understood.", cbuf);
1843 hasyyerrored = 1;
1844 }
1845
1846 static void
1847 help(struct tab *ctab, const char *s)
1848 {
1849 struct tab *c;
1850 int width, NCMDS;
1851 char *type;
1852
1853 if (ctab == sitetab)
1854 type = "SITE ";
1855 else
1856 type = "";
1857 width = 0, NCMDS = 0;
1858 for (c = ctab; c->name != NULL; c++) {
1859 int len = strlen(c->name);
1860
1861 if (len > width)
1862 width = len;
1863 NCMDS++;
1864 }
1865 width = (width + 8) &~ 7;
1866 if (s == 0) {
1867 int i, j, w;
1868 int columns, lines;
1869
1870 reply(-214, "%s", "");
1871 reply(0, "The following %scommands are recognized.", type);
1872 reply(0, "(`-' = not implemented, `+' = supports options)");
1873 columns = 76 / width;
1874 if (columns == 0)
1875 columns = 1;
1876 lines = (NCMDS + columns - 1) / columns;
1877 for (i = 0; i < lines; i++) {
1878 cprintf(stdout, " ");
1879 for (j = 0; j < columns; j++) {
1880 c = ctab + j * lines + i;
1881 cprintf(stdout, "%s", c->name);
1882 w = strlen(c->name);
1883 if (! CMD_IMPLEMENTED(c)) {
1884 CPUTC('-', stdout);
1885 w++;
1886 }
1887 if (CMD_HAS_OPTIONS(c)) {
1888 CPUTC('+', stdout);
1889 w++;
1890 }
1891 if (c + lines >= &ctab[NCMDS])
1892 break;
1893 while (w < width) {
1894 CPUTC(' ', stdout);
1895 w++;
1896 }
1897 }
1898 cprintf(stdout, "\r\n");
1899 }
1900 (void) fflush(stdout);
1901 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1902 return;
1903 }
1904 c = lookup(ctab, s);
1905 if (c == (struct tab *)0) {
1906 reply(502, "Unknown command %s.", s);
1907 return;
1908 }
1909 if (CMD_IMPLEMENTED(c))
1910 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1911 else
1912 reply(214, "%s%-*s\t%s; not implemented.", type, width,
1913 c->name, c->help);
1914 }
1915