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