ftpcmd.y revision 1.51 1 /* $NetBSD: ftpcmd.y,v 1.51 2000/07/17 02:30:53 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.51 2000/07/17 02:30:53 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(homedir);
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 pathname 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 = GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
1263
1264 if ($1[1] == '\0')
1265 $$ = xstrdup(homedir);
1266 else {
1267 memset(&gl, 0, sizeof(gl));
1268 if (glob($1, flags, NULL, &gl) ||
1269 gl.gl_pathc == 0) {
1270 reply(550, "not found");
1271 $$ = NULL;
1272 } else
1273 $$ = xstrdup(gl.gl_pathv[0]);
1274 globfree(&gl);
1275 }
1276 free($1);
1277 } else
1278 $$ = $1;
1279 }
1280 ;
1281
1282 pathstring
1283 : STRING
1284 ;
1285
1286 octal_number
1287 : NUMBER
1288 {
1289 int ret, dec, multby, digit;
1290
1291 /*
1292 * Convert a number that was read as decimal number
1293 * to what it would be if it had been read as octal.
1294 */
1295 dec = $1;
1296 multby = 1;
1297 ret = 0;
1298 while (dec) {
1299 digit = dec%10;
1300 if (digit > 7) {
1301 ret = -1;
1302 break;
1303 }
1304 ret += digit * multby;
1305 multby *= 8;
1306 dec /= 10;
1307 }
1308 $$ = ret;
1309 }
1310 ;
1311
1312 mechanism_name
1313 : STRING
1314 ;
1315
1316 base64data
1317 : STRING
1318 ;
1319
1320 prot_code
1321 : STRING
1322 ;
1323
1324 decimal_integer
1325 : NUMBER
1326 ;
1327
1328 check_login
1329 : /* empty */
1330 {
1331 if (logged_in)
1332 $$ = 1;
1333 else {
1334 reply(530, "Please login with USER and PASS.");
1335 $$ = 0;
1336 hasyyerrored = 1;
1337 }
1338 }
1339 ;
1340
1341 check_modify
1342 : /* empty */
1343 {
1344 if (logged_in) {
1345 if (curclass.modify)
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 check_upload
1361 : /* empty */
1362 {
1363 if (logged_in) {
1364 if (curclass.upload)
1365 $$ = 1;
1366 else {
1367 reply(502,
1368 "No permission to use this command.");
1369 $$ = 0;
1370 hasyyerrored = 1;
1371 }
1372 } else {
1373 reply(530, "Please login with USER and PASS.");
1374 $$ = 0;
1375 hasyyerrored = 1;
1376 }
1377 }
1378
1379
1380 %%
1381
1382 #define CMD 0 /* beginning of command */
1383 #define ARGS 1 /* expect miscellaneous arguments */
1384 #define STR1 2 /* expect SP followed by STRING */
1385 #define STR2 3 /* expect STRING */
1386 #define OSTR 4 /* optional SP then STRING */
1387 #define ZSTR1 5 /* SP then optional STRING */
1388 #define ZSTR2 6 /* optional STRING after SP */
1389 #define SITECMD 7 /* SITE command */
1390 #define NSTR 8 /* Number followed by a string */
1391 #define NOARGS 9 /* No arguments allowed */
1392
1393 struct tab cmdtab[] = {
1394 /* From RFC 959, in order defined (5.3.1) */
1395 { "USER", USER, STR1, 1, "<sp> username" },
1396 { "PASS", PASS, ZSTR1, 1, "<sp> password" },
1397 { "ACCT", ACCT, STR1, 0, "(specify account)" },
1398 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
1399 { "CDUP", CDUP, NOARGS, 1, "(change to parent directory)" },
1400 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
1401 { "QUIT", QUIT, NOARGS, 1, "(terminate service)" },
1402 { "REIN", REIN, NOARGS, 0, "(reinitialize server state)" },
1403 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" },
1404 { "LPRT", LPRT, ARGS, 1, "<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1405 { "EPRT", EPRT, STR1, 1, "<sp> |af|addr|port|" },
1406 { "PASV", PASV, NOARGS, 1, "(set server in passive mode)" },
1407 { "LPSV", LPSV, ARGS, 1, "(set server in passive mode)" },
1408 { "EPSV", EPSV, ARGS, 1, "[<sp> af|ALL]" },
1409 { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" },
1410 { "STRU", STRU, ARGS, 1, "(specify file structure)" },
1411 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
1412 { "RETR", RETR, STR1, 1, "<sp> file-name" },
1413 { "STOR", STOR, STR1, 1, "<sp> file-name" },
1414 { "STOU", STOU, STR1, 1, "<sp> file-name" },
1415 { "APPE", APPE, STR1, 1, "<sp> file-name" },
1416 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
1417 { "REST", REST, ARGS, 1, "<sp> offset (restart command)" },
1418 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
1419 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
1420 { "ABOR", ABOR, NOARGS, 1, "(abort operation)" },
1421 { "DELE", DELE, STR1, 1, "<sp> file-name" },
1422 { "RMD", RMD, STR1, 1, "<sp> path-name" },
1423 { "MKD", MKD, STR1, 1, "<sp> path-name" },
1424 { "PWD", PWD, NOARGS, 1, "(return current directory)" },
1425 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
1426 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
1427 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
1428 { "SYST", SYST, NOARGS, 1, "(get type of operating system)" },
1429 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
1430 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
1431 { "NOOP", NOOP, NOARGS, 2, "" },
1432
1433 /* From RFC 2228, in order defined */
1434 { "AUTH", AUTH, STR1, 1, "<sp> mechanism-name" },
1435 { "ADAT", ADAT, STR1, 1, "<sp> base-64-data" },
1436 { "PROT", PROT, STR1, 1, "<sp> prot-code" },
1437 { "PBSZ", PBSZ, ARGS, 1, "<sp> decimal-integer" },
1438 { "CCC", CCC, NOARGS, 1, "(Disable data protection)" },
1439 { "MIC", MIC, STR1, 1, "<sp> base64data" },
1440 { "CONF", CONF, STR1, 1, "<sp> base64data" },
1441 { "ENC", ENC, STR1, 1, "<sp> base64data" },
1442
1443 /* From RFC 2389, in order defined */
1444 { "FEAT", FEAT, NOARGS, 1, "(display extended features)" },
1445 { "OPTS", OPTS, STR1, 1, "<sp> command [ <sp> options ]" },
1446
1447 /* from draft-ietf-ftpext-mlst-11 */
1448 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
1449 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
1450 { "MLST", MLST, OSTR, 2, "[ <sp> path-name ]" },
1451 { "MLSD", MLSD, OSTR, 1, "[ <sp> directory-name ]" },
1452
1453 /* obsolete commands */
1454 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
1455 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
1456 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
1457 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
1458 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
1459 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
1460 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
1461 { "XCUP", CDUP, NOARGS, 1, "(change to parent directory)" },
1462 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
1463 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
1464 { "XPWD", PWD, NOARGS, 1, "(return current directory)" },
1465 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
1466
1467 { NULL, 0, 0, 0, 0 }
1468 };
1469
1470 struct tab sitetab[] = {
1471 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
1472 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
1473 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
1474 { "RATEGET", RATEGET,OSTR, 1, "[ <sp> get-throttle-rate ]" },
1475 { "RATEPUT", RATEPUT,OSTR, 1, "[ <sp> put-throttle-rate ]" },
1476 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
1477 { NULL, 0, 0, 0, NULL }
1478 };
1479
1480 static void help(struct tab *, const char *);
1481 static void toolong(int);
1482 static int yylex(void);
1483
1484 extern int epsvall;
1485
1486 struct tab *
1487 lookup(struct tab *p, const char *cmd)
1488 {
1489
1490 for (; p->name != NULL; p++)
1491 if (strcasecmp(cmd, p->name) == 0)
1492 return (p);
1493 return (0);
1494 }
1495
1496 #include <arpa/telnet.h>
1497
1498 /*
1499 * getline - a hacked up version of fgets to ignore TELNET escape codes.
1500 */
1501 char *
1502 getline(char *s, int n, FILE *iop)
1503 {
1504 int c;
1505 char *cs;
1506
1507 cs = s;
1508 /* tmpline may contain saved command from urgent mode interruption */
1509 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1510 *cs++ = tmpline[c];
1511 if (tmpline[c] == '\n') {
1512 *cs++ = '\0';
1513 if (debug)
1514 syslog(LOG_DEBUG, "command: %s", s);
1515 tmpline[0] = '\0';
1516 return(s);
1517 }
1518 if (c == 0)
1519 tmpline[0] = '\0';
1520 }
1521 while ((c = getc(iop)) != EOF) {
1522 total_bytes++;
1523 total_bytes_in++;
1524 c &= 0377;
1525 if (c == IAC) {
1526 if ((c = getc(iop)) != EOF) {
1527 total_bytes++;
1528 total_bytes_in++;
1529 c &= 0377;
1530 switch (c) {
1531 case WILL:
1532 case WONT:
1533 c = getc(iop);
1534 total_bytes++;
1535 total_bytes_in++;
1536 cprintf(stdout, "%c%c%c", IAC, DONT, 0377&c);
1537 (void) fflush(stdout);
1538 continue;
1539 case DO:
1540 case DONT:
1541 c = getc(iop);
1542 total_bytes++;
1543 total_bytes_in++;
1544 cprintf(stdout, "%c%c%c", IAC, WONT, 0377&c);
1545 (void) fflush(stdout);
1546 continue;
1547 case IAC:
1548 break;
1549 default:
1550 continue; /* ignore command */
1551 }
1552 }
1553 }
1554 *cs++ = c;
1555 if (--n <= 0 || c == '\n')
1556 break;
1557 }
1558 if (c == EOF && cs == s)
1559 return (NULL);
1560 *cs++ = '\0';
1561 if (debug) {
1562 if (curclass.type != CLASS_GUEST &&
1563 strncasecmp("pass ", s, 5) == 0) {
1564 /* Don't syslog passwords */
1565 syslog(LOG_DEBUG, "command: %.5s ???", s);
1566 } else {
1567 char *cp;
1568 int len;
1569
1570 /* Don't syslog trailing CR-LF */
1571 len = strlen(s);
1572 cp = s + len - 1;
1573 while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1574 --cp;
1575 --len;
1576 }
1577 syslog(LOG_DEBUG, "command: %.*s", len, s);
1578 }
1579 }
1580 return (s);
1581 }
1582
1583 static void
1584 toolong(int signo)
1585 {
1586
1587 reply(421,
1588 "Timeout (%d seconds): closing control connection.",
1589 curclass.timeout);
1590 if (logging)
1591 syslog(LOG_INFO, "User %s timed out after %d seconds",
1592 (pw ? pw->pw_name : "unknown"), curclass.timeout);
1593 dologout(1);
1594 }
1595
1596 static int
1597 yylex(void)
1598 {
1599 static int cpos, state;
1600 char *cp, *cp2;
1601 struct tab *p;
1602 int n;
1603 char c;
1604
1605 switch (state) {
1606
1607 case CMD:
1608 hasyyerrored = 0;
1609 (void) signal(SIGALRM, toolong);
1610 (void) alarm(curclass.timeout);
1611 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
1612 reply(221, "You could at least say goodbye.");
1613 dologout(0);
1614 }
1615 (void) alarm(0);
1616 if ((cp = strchr(cbuf, '\r'))) {
1617 *cp = '\0';
1618 #ifdef HASSETPROCTITLE
1619 if (strncasecmp(cbuf, "PASS", 4) != 0)
1620 setproctitle("%s: %s", proctitle, cbuf);
1621 #endif /* HASSETPROCTITLE */
1622 *cp++ = '\n';
1623 *cp = '\0';
1624 }
1625 if ((cp = strpbrk(cbuf, " \n")))
1626 cpos = cp - cbuf;
1627 if (cpos == 0)
1628 cpos = 4;
1629 c = cbuf[cpos];
1630 cbuf[cpos] = '\0';
1631 p = lookup(cmdtab, cbuf);
1632 cbuf[cpos] = c;
1633 if (p != NULL) {
1634 if (! CMD_IMPLEMENTED(p)) {
1635 reply(502, "%s command not implemented.",
1636 p->name);
1637 hasyyerrored = 1;
1638 break;
1639 }
1640 state = p->state;
1641 yylval.s = p->name;
1642 return (p->token);
1643 }
1644 break;
1645
1646 case SITECMD:
1647 if (cbuf[cpos] == ' ') {
1648 cpos++;
1649 return (SP);
1650 }
1651 cp = &cbuf[cpos];
1652 if ((cp2 = strpbrk(cp, " \n")))
1653 cpos = cp2 - cbuf;
1654 c = cbuf[cpos];
1655 cbuf[cpos] = '\0';
1656 p = lookup(sitetab, cp);
1657 cbuf[cpos] = c;
1658 if (p != NULL) {
1659 if (!CMD_IMPLEMENTED(p)) {
1660 reply(502, "SITE %s command not implemented.",
1661 p->name);
1662 hasyyerrored = 1;
1663 break;
1664 }
1665 state = p->state;
1666 yylval.s = p->name;
1667 return (p->token);
1668 }
1669 break;
1670
1671 case OSTR:
1672 if (cbuf[cpos] == '\n') {
1673 state = CMD;
1674 return (CRLF);
1675 }
1676 /* FALLTHROUGH */
1677
1678 case STR1:
1679 case ZSTR1:
1680 dostr1:
1681 if (cbuf[cpos] == ' ') {
1682 cpos++;
1683 state = state == OSTR ? STR2 : state+1;
1684 return (SP);
1685 }
1686 break;
1687
1688 case ZSTR2:
1689 if (cbuf[cpos] == '\n') {
1690 state = CMD;
1691 return (CRLF);
1692 }
1693 /* FALLTHROUGH */
1694
1695 case STR2:
1696 cp = &cbuf[cpos];
1697 n = strlen(cp);
1698 cpos += n - 1;
1699 /*
1700 * Make sure the string is nonempty and \n terminated.
1701 */
1702 if (n > 1 && cbuf[cpos] == '\n') {
1703 cbuf[cpos] = '\0';
1704 yylval.s = xstrdup(cp);
1705 cbuf[cpos] = '\n';
1706 state = ARGS;
1707 return (STRING);
1708 }
1709 break;
1710
1711 case NSTR:
1712 if (cbuf[cpos] == ' ') {
1713 cpos++;
1714 return (SP);
1715 }
1716 if (isdigit(cbuf[cpos])) {
1717 cp = &cbuf[cpos];
1718 while (isdigit(cbuf[++cpos]))
1719 ;
1720 c = cbuf[cpos];
1721 cbuf[cpos] = '\0';
1722 yylval.i = atoi(cp);
1723 cbuf[cpos] = c;
1724 state = STR1;
1725 return (NUMBER);
1726 }
1727 state = STR1;
1728 goto dostr1;
1729
1730 case ARGS:
1731 if (isdigit(cbuf[cpos])) {
1732 cp = &cbuf[cpos];
1733 while (isdigit(cbuf[++cpos]))
1734 ;
1735 c = cbuf[cpos];
1736 cbuf[cpos] = '\0';
1737 yylval.i = atoi(cp);
1738 cbuf[cpos] = c;
1739 return (NUMBER);
1740 }
1741 if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1742 && !isalnum(cbuf[cpos + 3])) {
1743 yylval.s = xstrdup("ALL");
1744 cpos += 3;
1745 return ALL;
1746 }
1747 switch (cbuf[cpos++]) {
1748
1749 case '\n':
1750 state = CMD;
1751 return (CRLF);
1752
1753 case ' ':
1754 return (SP);
1755
1756 case ',':
1757 return (COMMA);
1758
1759 case 'A':
1760 case 'a':
1761 return (A);
1762
1763 case 'B':
1764 case 'b':
1765 return (B);
1766
1767 case 'C':
1768 case 'c':
1769 return (C);
1770
1771 case 'E':
1772 case 'e':
1773 return (E);
1774
1775 case 'F':
1776 case 'f':
1777 return (F);
1778
1779 case 'I':
1780 case 'i':
1781 return (I);
1782
1783 case 'L':
1784 case 'l':
1785 return (L);
1786
1787 case 'N':
1788 case 'n':
1789 return (N);
1790
1791 case 'P':
1792 case 'p':
1793 return (P);
1794
1795 case 'R':
1796 case 'r':
1797 return (R);
1798
1799 case 'S':
1800 case 's':
1801 return (S);
1802
1803 case 'T':
1804 case 't':
1805 return (T);
1806
1807 }
1808 break;
1809
1810 case NOARGS:
1811 if (cbuf[cpos] == '\n') {
1812 state = CMD;
1813 return (CRLF);
1814 }
1815 c = cbuf[cpos];
1816 cbuf[cpos] = '\0';
1817 reply(501, "'%s' command does not take any arguments.", cbuf);
1818 hasyyerrored = 1;
1819 cbuf[cpos] = c;
1820 break;
1821
1822 default:
1823 fatal("Unknown state in scanner.");
1824 }
1825 yyerror(NULL);
1826 state = CMD;
1827 longjmp(errcatch, 0);
1828 /* NOTREACHED */
1829 }
1830
1831 /* ARGSUSED */
1832 void
1833 yyerror(char *s)
1834 {
1835 char *cp;
1836
1837 if (hasyyerrored)
1838 return;
1839 if ((cp = strchr(cbuf,'\n')) != NULL)
1840 *cp = '\0';
1841 reply(500, "'%s': command not understood.", cbuf);
1842 hasyyerrored = 1;
1843 }
1844
1845 static void
1846 help(struct tab *ctab, const char *s)
1847 {
1848 struct tab *c;
1849 int width, NCMDS;
1850 char *type;
1851
1852 if (ctab == sitetab)
1853 type = "SITE ";
1854 else
1855 type = "";
1856 width = 0, NCMDS = 0;
1857 for (c = ctab; c->name != NULL; c++) {
1858 int len = strlen(c->name);
1859
1860 if (len > width)
1861 width = len;
1862 NCMDS++;
1863 }
1864 width = (width + 8) &~ 7;
1865 if (s == 0) {
1866 int i, j, w;
1867 int columns, lines;
1868
1869 reply(-214, "%s", "");
1870 reply(0, "The following %scommands are recognized.", type);
1871 reply(0, "(`-' = not implemented, `+' = supports options)");
1872 columns = 76 / width;
1873 if (columns == 0)
1874 columns = 1;
1875 lines = (NCMDS + columns - 1) / columns;
1876 for (i = 0; i < lines; i++) {
1877 cprintf(stdout, " ");
1878 for (j = 0; j < columns; j++) {
1879 c = ctab + j * lines + i;
1880 cprintf(stdout, "%s", c->name);
1881 w = strlen(c->name);
1882 if (! CMD_IMPLEMENTED(c)) {
1883 CPUTC('-', stdout);
1884 w++;
1885 }
1886 if (CMD_HAS_OPTIONS(c)) {
1887 CPUTC('+', stdout);
1888 w++;
1889 }
1890 if (c + lines >= &ctab[NCMDS])
1891 break;
1892 while (w < width) {
1893 CPUTC(' ', stdout);
1894 w++;
1895 }
1896 }
1897 cprintf(stdout, "\r\n");
1898 }
1899 (void) fflush(stdout);
1900 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1901 return;
1902 }
1903 c = lookup(ctab, s);
1904 if (c == (struct tab *)0) {
1905 reply(502, "Unknown command %s.", s);
1906 return;
1907 }
1908 if (CMD_IMPLEMENTED(c))
1909 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1910 else
1911 reply(214, "%s%-*s\t%s; not implemented.", type, width,
1912 c->name, c->help);
1913 }
1914