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