ftpcmd.y revision 1.32 1 /* $NetBSD: ftpcmd.y,v 1.32 1999/07/02 05:52:14 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.32 1999/07/02 05:52:14 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
374 reply(200, "EPRT command successful.");
375 eprt_done:;
376 }
377
378 | PASV check_login CRLF
379 {
380 if (curclass.passive) {
381 passive();
382 } else {
383 reply(500, "PASV mode not available.");
384 }
385 }
386
387 | LPSV CRLF
388 {
389 if (epsvall)
390 reply(501, "LPSV disallowed after EPSV ALL");
391 else
392 long_passive("LPSV", AF_INET6);
393 }
394
395 | EPSV SP NUMBER CRLF
396 {
397 int pf;
398 switch ($3) {
399 case 1:
400 pf = PF_INET;
401 break;
402 case 2:
403 pf = PF_INET6;
404 break;
405 default:
406 pf = -1; /*junk*/
407 break;
408 }
409 long_passive("EPSV", pf);
410 }
411
412 | EPSV SP ALL CRLF
413 {
414 if (!logged_in) {
415 syslog(LOG_NOTICE, "long passive but not logged in");
416 reply(503, "Login with USER first.");
417 } else {
418 reply(200, "EPSV ALL command successful.");
419 epsvall++;
420 }
421 }
422
423 | EPSV CRLF
424 {
425 long_passive("EPSV", PF_UNSPEC);
426 }
427
428 | TYPE SP type_code CRLF
429 {
430 switch (cmd_type) {
431
432 case TYPE_A:
433 if (cmd_form == FORM_N) {
434 reply(200, "Type set to A.");
435 type = cmd_type;
436 form = cmd_form;
437 } else
438 reply(504, "Form must be N.");
439 break;
440
441 case TYPE_E:
442 reply(504, "Type E not implemented.");
443 break;
444
445 case TYPE_I:
446 reply(200, "Type set to I.");
447 type = cmd_type;
448 break;
449
450 case TYPE_L:
451 #if NBBY == 8
452 if (cmd_bytesz == 8) {
453 reply(200,
454 "Type set to L (byte size 8).");
455 type = cmd_type;
456 } else
457 reply(504, "Byte size must be 8.");
458 #else /* NBBY == 8 */
459 UNIMPLEMENTED for NBBY != 8
460 #endif /* NBBY == 8 */
461 }
462 }
463
464 | STRU SP struct_code CRLF
465 {
466 switch ($3) {
467
468 case STRU_F:
469 reply(200, "STRU F ok.");
470 break;
471
472 default:
473 reply(504, "Unimplemented STRU type.");
474 }
475 }
476
477 | MODE SP mode_code CRLF
478 {
479 switch ($3) {
480
481 case MODE_S:
482 reply(200, "MODE S ok.");
483 break;
484
485 default:
486 reply(502, "Unimplemented MODE type.");
487 }
488 }
489
490 | RETR check_login SP pathname CRLF
491 {
492 if ($2 && $4 != NULL)
493 retrieve(NULL, $4);
494 if ($4 != NULL)
495 free($4);
496 }
497
498 | STOR check_login SP pathname CRLF
499 {
500 if ($2 && $4 != NULL)
501 store($4, "w", 0);
502 if ($4 != NULL)
503 free($4);
504 }
505
506 | STOU check_login SP pathname CRLF
507 {
508 if ($2 && $4 != NULL)
509 store($4, "w", 1);
510 if ($4 != NULL)
511 free($4);
512 }
513
514 | APPE check_login SP pathname CRLF
515 {
516 if ($2 && $4 != NULL)
517 store($4, "a", 0);
518 if ($4 != NULL)
519 free($4);
520 }
521
522 | ALLO SP NUMBER CRLF
523 {
524 reply(202, "ALLO command ignored.");
525 }
526
527 | ALLO SP NUMBER SP R SP NUMBER CRLF
528 {
529 reply(202, "ALLO command ignored.");
530 }
531
532 | RNTO SP pathname CRLF
533 {
534 if (fromname) {
535 renamecmd(fromname, $3);
536 free(fromname);
537 fromname = NULL;
538 } else {
539 reply(503, "Bad sequence of commands.");
540 }
541 free($3);
542 }
543
544 | ABOR CRLF
545 {
546 reply(225, "ABOR command successful.");
547 }
548
549 | DELE check_modify SP pathname CRLF
550 {
551 if ($2 && $4 != NULL)
552 delete($4);
553 if ($4 != NULL)
554 free($4);
555 }
556
557 | RMD check_modify SP pathname CRLF
558 {
559 if ($2 && $4 != NULL)
560 removedir($4);
561 if ($4 != NULL)
562 free($4);
563 }
564
565 | MKD check_modify SP pathname CRLF
566 {
567 if ($2 && $4 != NULL)
568 makedir($4);
569 if ($4 != NULL)
570 free($4);
571 }
572
573 | PWD check_login CRLF
574 {
575 if ($2)
576 pwd();
577 }
578
579 | LIST check_login CRLF
580 {
581 if ($2)
582 retrieve("/bin/ls -lgA", "");
583 }
584
585 | LIST check_login SP pathname CRLF
586 {
587 if ($2 && $4 != NULL)
588 retrieve("/bin/ls -lgA %s", $4);
589 if ($4 != NULL)
590 free($4);
591 }
592
593 | NLST check_login CRLF
594 {
595 if ($2)
596 send_file_list(".");
597 }
598
599 | NLST check_login SP STRING CRLF
600 {
601 if ($2 && $4 != NULL)
602 send_file_list($4);
603 if ($4 != NULL)
604 free($4);
605 }
606
607 | SITE SP HELP CRLF
608 {
609 help(sitetab, NULL);
610 }
611
612 | SITE SP HELP SP STRING CRLF
613 {
614 help(sitetab, $5);
615 }
616
617 | SITE SP UMASK check_login CRLF
618 {
619 int oldmask;
620
621 if ($4) {
622 oldmask = umask(0);
623 (void) umask(oldmask);
624 reply(200, "Current UMASK is %03o", oldmask);
625 }
626 }
627
628 | SITE SP UMASK check_modify SP octal_number CRLF
629 {
630 int oldmask;
631
632 if ($4) {
633 if (($6 == -1) || ($6 > 0777)) {
634 reply(501, "Bad UMASK value");
635 } else {
636 oldmask = umask($6);
637 reply(200,
638 "UMASK set to %03o (was %03o)",
639 $6, oldmask);
640 }
641 }
642 }
643
644 | SITE SP CHMOD check_modify SP octal_number SP pathname CRLF
645 {
646 if ($4 && ($8 != NULL)) {
647 if ($6 > 0777)
648 reply(501,
649 "CHMOD: Mode value must be between 0 and 0777");
650 else if (chmod($8, $6) < 0)
651 perror_reply(550, $8);
652 else
653 reply(200, "CHMOD command successful.");
654 }
655 if ($8 != NULL)
656 free($8);
657 }
658
659 | SITE SP IDLE CRLF
660 {
661 reply(200,
662 "Current IDLE time limit is %d seconds; max %d",
663 curclass.timeout, curclass.maxtimeout);
664 }
665
666 | SITE SP IDLE SP NUMBER CRLF
667 {
668 if ($5 < 30 || $5 > curclass.maxtimeout) {
669 reply(501,
670 "IDLE time limit must be between 30 and %d seconds",
671 curclass.maxtimeout);
672 } else {
673 curclass.timeout = $5;
674 (void) alarm(curclass.timeout);
675 reply(200,
676 "IDLE time limit set to %d seconds",
677 curclass.timeout);
678 }
679 }
680
681 | SYST CRLF
682 {
683 reply(215, "UNIX Type: L%d %s", NBBY, version);
684 }
685
686 | STAT check_login SP pathname CRLF
687 {
688 if ($2 && $4 != NULL)
689 statfilecmd($4);
690 if ($4 != NULL)
691 free($4);
692 }
693
694 | STAT CRLF
695 {
696 statcmd();
697 }
698
699 | HELP CRLF
700 {
701 help(cmdtab, NULL);
702 }
703
704 | HELP SP STRING CRLF
705 {
706 char *cp = $3;
707
708 if (strncasecmp(cp, "SITE", 4) == 0) {
709 cp = $3 + 4;
710 if (*cp == ' ')
711 cp++;
712 if (*cp)
713 help(sitetab, cp);
714 else
715 help(sitetab, NULL);
716 } else
717 help(cmdtab, $3);
718 }
719
720 | NOOP CRLF
721 {
722 reply(200, "NOOP command successful.");
723 }
724
725 /* RFC 2228 */
726 | AUTH SP mechanism_name CRLF
727 {
728 reply(502, "RFC 2228 authentication not implemented.");
729 }
730
731 | ADAT SP base64data CRLF
732 {
733 reply(503,
734 "Please set authentication state with AUTH.");
735 }
736
737 | PROT SP prot_code CRLF
738 {
739 reply(503,
740 "Please set protection buffer size with PBSZ.");
741 }
742
743 | PBSZ SP decimal_integer CRLF
744 {
745 reply(503,
746 "Please set authentication state with AUTH.");
747 }
748
749 | CCC CRLF
750 {
751 reply(533, "No protection enabled.");
752 }
753
754 | MIC SP base64data CRLF
755 {
756 reply(502, "RFC 2228 authentication not implemented.");
757 }
758
759 | CONF SP base64data CRLF
760 {
761 reply(502, "RFC 2228 authentication not implemented.");
762 }
763
764 | ENC SP base64data CRLF
765 {
766 reply(502, "RFC 2228 authentication not implemented.");
767 }
768
769 /* RFC 2389 */
770 | FEAT CRLF
771 {
772 lreply(211, "Features supported");
773 lreply(-1, " MDTM");
774 lreply(-1, " REST STREAM");
775 lreply(-1, " SIZE");
776 reply(211, "End");
777 }
778
779 | OPTS SP STRING CRLF
780 {
781
782 opts($3);
783 }
784
785
786 /* BSD extensions */
787
788 /*
789 * SIZE is not in RFC 959, but Postel has blessed it and
790 * it will be in the updated RFC.
791 *
792 * Return size of file in a format suitable for
793 * using with RESTART (we just count bytes).
794 */
795 | SIZE check_login SP pathname CRLF
796 {
797 if ($2 && $4 != NULL)
798 sizecmd($4);
799 if ($4 != NULL)
800 free($4);
801 }
802
803 /*
804 * MDTM is not in RFC 959, but Postel has blessed it and
805 * it will be in the updated RFC.
806 *
807 * Return modification time of file as an ISO 3307
808 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
809 * where xxx is the fractional second (of any precision,
810 * not necessarily 3 digits)
811 */
812 | MDTM check_login SP pathname CRLF
813 {
814 if ($2 && $4 != NULL) {
815 struct stat stbuf;
816 if (stat($4, &stbuf) < 0)
817 perror_reply(550, $4);
818 else if (!S_ISREG(stbuf.st_mode)) {
819 reply(550, "%s: not a plain file.", $4);
820 } else {
821 struct tm *t;
822 t = gmtime(&stbuf.st_mtime);
823 reply(213,
824 "%04d%02d%02d%02d%02d%02d",
825 TM_YEAR_BASE + t->tm_year,
826 t->tm_mon+1, t->tm_mday,
827 t->tm_hour, t->tm_min, t->tm_sec);
828 }
829 }
830 if ($4 != NULL)
831 free($4);
832 }
833
834 | error CRLF
835 {
836 yyerrok;
837 }
838 ;
839
840 rcmd
841 : REST SP byte_size CRLF
842 {
843 fromname = NULL;
844 restart_point = $3; /* XXX $3 is only "int" */
845 reply(350, "Restarting at %qd. %s",
846 (qdfmt_t)restart_point,
847 "Send STORE or RETRIEVE to initiate transfer.");
848 }
849 | RNFR check_modify SP pathname CRLF
850 {
851 restart_point = (off_t) 0;
852 if ($2 && $4) {
853 fromname = renamefrom($4);
854 if (fromname == NULL && $4) {
855 free($4);
856 }
857 }
858 }
859 ;
860
861 username
862 : STRING
863 ;
864
865 password
866 : /* empty */
867 {
868 $$ = (char *)calloc(1, sizeof(char));
869 }
870
871 | STRING
872 ;
873
874 byte_size
875 : NUMBER
876 ;
877
878 host_port
879 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
880 NUMBER COMMA NUMBER
881 {
882 char *a, *p;
883
884 data_dest.su_len = sizeof(struct sockaddr_in);
885 data_dest.su_family = AF_INET;
886 p = (char *)&data_dest.su_sin.sin_port;
887 p[0] = $9; p[1] = $11;
888 a = (char *)&data_dest.su_sin.sin_addr;
889 a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
890 }
891 ;
892
893 host_long_port
894 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
895 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
896 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
897 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
898 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
899 NUMBER
900 {
901 char *a, *p;
902
903 data_dest.su_sin6.sin6_len =
904 sizeof(struct sockaddr_in6);
905 data_dest.su_family = AF_INET6;
906 p = (char *)&data_dest.su_port;
907 p[0] = $39; p[1] = $41;
908 a = (char *)&data_dest.su_sin6.sin6_addr;
909 a[0] = $5; a[1] = $7; a[2] = $9; a[3] = $11;
910 a[4] = $13; a[5] = $15; a[6] = $17; a[7] = $19;
911 a[8] = $21; a[9] = $23; a[10] = $25; a[11] = $27;
912 a[12] = $29; a[13] = $31; a[14] = $33; a[15] = $35;
913 }
914 ;
915
916 form_code
917 : N
918 {
919 $$ = FORM_N;
920 }
921
922 | T
923 {
924 $$ = FORM_T;
925 }
926
927 | C
928 {
929 $$ = FORM_C;
930 }
931 ;
932
933 type_code
934 : A
935 {
936 cmd_type = TYPE_A;
937 cmd_form = FORM_N;
938 }
939
940 | A SP form_code
941 {
942 cmd_type = TYPE_A;
943 cmd_form = $3;
944 }
945
946 | E
947 {
948 cmd_type = TYPE_E;
949 cmd_form = FORM_N;
950 }
951
952 | E SP form_code
953 {
954 cmd_type = TYPE_E;
955 cmd_form = $3;
956 }
957
958 | I
959 {
960 cmd_type = TYPE_I;
961 }
962
963 | L
964 {
965 cmd_type = TYPE_L;
966 cmd_bytesz = NBBY;
967 }
968
969 | L SP byte_size
970 {
971 cmd_type = TYPE_L;
972 cmd_bytesz = $3;
973 }
974
975 /* this is for a bug in the BBN ftp */
976 | L byte_size
977 {
978 cmd_type = TYPE_L;
979 cmd_bytesz = $2;
980 }
981 ;
982
983 struct_code
984 : F
985 {
986 $$ = STRU_F;
987 }
988
989 | R
990 {
991 $$ = STRU_R;
992 }
993
994 | P
995 {
996 $$ = STRU_P;
997 }
998 ;
999
1000 mode_code
1001 : S
1002 {
1003 $$ = MODE_S;
1004 }
1005
1006 | B
1007 {
1008 $$ = MODE_B;
1009 }
1010
1011 | C
1012 {
1013 $$ = MODE_C;
1014 }
1015 ;
1016
1017 pathname
1018 : pathstring
1019 {
1020 /*
1021 * Problem: this production is used for all pathname
1022 * processing, but only gives a 550 error reply.
1023 * This is a valid reply in some cases but not in
1024 * others.
1025 */
1026 if (logged_in && $1 && *$1 == '~') {
1027 glob_t gl;
1028 int flags =
1029 GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
1030
1031 if ($1[1] == '\0')
1032 $$ = xstrdup(pw->pw_dir);
1033 else {
1034 memset(&gl, 0, sizeof(gl));
1035 if (glob($1, flags, NULL, &gl) ||
1036 gl.gl_pathc == 0) {
1037 reply(550, "not found");
1038 $$ = NULL;
1039 } else
1040 $$ = xstrdup(gl.gl_pathv[0]);
1041 globfree(&gl);
1042 }
1043 free($1);
1044 } else
1045 $$ = $1;
1046 }
1047 ;
1048
1049 pathstring
1050 : STRING
1051 ;
1052
1053 octal_number
1054 : NUMBER
1055 {
1056 int ret, dec, multby, digit;
1057
1058 /*
1059 * Convert a number that was read as decimal number
1060 * to what it would be if it had been read as octal.
1061 */
1062 dec = $1;
1063 multby = 1;
1064 ret = 0;
1065 while (dec) {
1066 digit = dec%10;
1067 if (digit > 7) {
1068 ret = -1;
1069 break;
1070 }
1071 ret += digit * multby;
1072 multby *= 8;
1073 dec /= 10;
1074 }
1075 $$ = ret;
1076 }
1077 ;
1078
1079 mechanism_name
1080 : STRING
1081 ;
1082
1083 base64data
1084 : STRING
1085 ;
1086
1087 prot_code
1088 : STRING
1089 ;
1090
1091 decimal_integer
1092 : NUMBER
1093 ;
1094
1095 check_login
1096 : /* empty */
1097 {
1098 if (logged_in)
1099 $$ = 1;
1100 else {
1101 reply(530, "Please login with USER and PASS.");
1102 $$ = 0;
1103 hasyyerrored = 1;
1104 }
1105 }
1106 ;
1107
1108 check_modify
1109 : /* empty */
1110 {
1111 if (logged_in) {
1112 if (curclass.modify)
1113 $$ = 1;
1114 else {
1115 reply(502,
1116 "No permission to use this command.");
1117 $$ = 0;
1118 hasyyerrored = 1;
1119 }
1120 } else {
1121 reply(530, "Please login with USER and PASS.");
1122 $$ = 0;
1123 hasyyerrored = 1;
1124 }
1125 }
1126
1127 %%
1128
1129 #define CMD 0 /* beginning of command */
1130 #define ARGS 1 /* expect miscellaneous arguments */
1131 #define STR1 2 /* expect SP followed by STRING */
1132 #define STR2 3 /* expect STRING */
1133 #define OSTR 4 /* optional SP then STRING */
1134 #define ZSTR1 5 /* SP then optional STRING */
1135 #define ZSTR2 6 /* optional STRING after SP */
1136 #define SITECMD 7 /* SITE command */
1137 #define NSTR 8 /* Number followed by a string */
1138 #define NOARGS 9 /* No arguments allowed */
1139
1140 struct tab {
1141 char *name;
1142 short token;
1143 short state;
1144 short implemented; /* 1 if command is implemented */
1145 short hasopts; /* 1 if command takes options */
1146 char *help;
1147 char *options;
1148 };
1149
1150 struct tab cmdtab[] = {
1151 /* From RFC 959, in order defined (5.3.1) */
1152 { "USER", USER, STR1, 1, 0, "<sp> username" },
1153 { "PASS", PASS, ZSTR1, 1, 0, "<sp> password" },
1154 { "ACCT", ACCT, STR1, 0, 0, "(specify account)" },
1155 { "CWD", CWD, OSTR, 1, 0, "[ <sp> directory-name ]" },
1156 { "CDUP", CDUP, NOARGS, 1, 0, "(change to parent directory)" },
1157 { "SMNT", SMNT, ARGS, 0, 0, "(structure mount)" },
1158 { "QUIT", QUIT, NOARGS, 1, 0, "(terminate service)", },
1159 { "REIN", REIN, NOARGS, 0, 0, "(reinitialize server state)" },
1160 { "PORT", PORT, ARGS, 1, 0, "<sp> b0, b1, b2, b3, b4" },
1161 { "LPRT", LPRT, ARGS, 1, 0, "<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1162 { "EPRT", EPRT, STR1, 1, 0, "<sp> |af|addr|port|" },
1163 { "PASV", PASV, NOARGS, 1, 0, "(set server in passive mode)" },
1164 { "LPSV", LPSV, ARGS, 1, 0, "(set server in passive mode)" },
1165 { "EPSV", EPSV, ARGS, 1, 0, "[<sp> af|ALL]" },
1166 { "TYPE", TYPE, ARGS, 1, 0, "<sp> [ A | E | I | L ]" },
1167 { "STRU", STRU, ARGS, 1, 0, "(specify file structure)" },
1168 { "MODE", MODE, ARGS, 1, 0, "(specify transfer mode)" },
1169 { "RETR", RETR, STR1, 1, 0, "<sp> file-name" },
1170 { "STOR", STOR, STR1, 1, 0, "<sp> file-name" },
1171 { "STOU", STOU, STR1, 1, 0, "<sp> file-name" },
1172 { "APPE", APPE, STR1, 1, 0, "<sp> file-name" },
1173 { "ALLO", ALLO, ARGS, 1, 0, "allocate storage (vacuously)" },
1174 { "REST", REST, ARGS, 1, 0, "<sp> offset (restart command)" },
1175 { "RNFR", RNFR, STR1, 1, 0, "<sp> file-name" },
1176 { "RNTO", RNTO, STR1, 1, 0, "<sp> file-name" },
1177 { "ABOR", ABOR, NOARGS, 1, 0, "(abort operation)" },
1178 { "DELE", DELE, STR1, 1, 0, "<sp> file-name" },
1179 { "RMD", RMD, STR1, 1, 0, "<sp> path-name" },
1180 { "MKD", MKD, STR1, 1, 0, "<sp> path-name" },
1181 { "PWD", PWD, NOARGS, 1, 0, "(return current directory)" },
1182 { "LIST", LIST, OSTR, 1, 0, "[ <sp> path-name ]" },
1183 { "NLST", NLST, OSTR, 1, 0, "[ <sp> path-name ]" },
1184 { "SITE", SITE, SITECMD, 1, 0, "site-cmd [ <sp> arguments ]" },
1185 { "SYST", SYST, NOARGS, 1, 0, "(get type of operating system)" },
1186 { "STAT", STAT, OSTR, 1, 0, "[ <sp> path-name ]" },
1187 { "HELP", HELP, OSTR, 1, 0, "[ <sp> <string> ]" },
1188 { "NOOP", NOOP, NOARGS, 1, 1, "" },
1189
1190 /* From RFC 2228, in order defined */
1191 { "AUTH", AUTH, STR1, 1, 0, "<sp> mechanism-name" },
1192 { "ADAT", ADAT, STR1, 1, 0, "<sp> base-64-data" },
1193 { "PROT", PROT, STR1, 1, 0, "<sp> prot-code" },
1194 { "PBSZ", PBSZ, ARGS, 1, 0, "<sp> decimal-integer" },
1195 { "CCC", CCC, NOARGS, 1, 0, "(Disable data protection)" },
1196 { "MIC", MIC, STR1, 1, 0, "<sp> base64data" },
1197 { "CONF", CONF, STR1, 1, 0, "<sp> base64data" },
1198 { "ENC", ENC, STR1, 1, 0, "<sp> base64data" },
1199
1200 /* From RFC 2389, in order defined */
1201 { "FEAT", FEAT, NOARGS, 1, 0, "(display extended features)" },
1202 { "OPTS", OPTS, STR1, 1, 0, "<sp> command [ <sp> options ]" },
1203
1204 /* Non standardized extensions */
1205 { "SIZE", SIZE, OSTR, 1, 0, "<sp> path-name" },
1206 { "MDTM", MDTM, OSTR, 1, 0, "<sp> path-name" },
1207
1208 /* obsolete commands */
1209 { "MAIL", MAIL, OSTR, 0, 0, "(mail to user)" },
1210 { "MLFL", MLFL, OSTR, 0, 0, "(mail file)" },
1211 { "MRCP", MRCP, STR1, 0, 0, "(mail recipient)" },
1212 { "MRSQ", MRSQ, OSTR, 0, 0, "(mail recipient scheme question)" },
1213 { "MSAM", MSAM, OSTR, 0, 0, "(mail send to terminal and mailbox)" },
1214 { "MSND", MSND, OSTR, 0, 0, "(mail send to terminal)" },
1215 { "MSOM", MSOM, OSTR, 0, 0, "(mail send to terminal or mailbox)" },
1216 { "XCUP", CDUP, NOARGS, 1, 0, "(change to parent directory)" },
1217 { "XCWD", CWD, OSTR, 1, 0, "[ <sp> directory-name ]" },
1218 { "XMKD", MKD, STR1, 1, 0, "<sp> path-name" },
1219 { "XPWD", PWD, NOARGS, 1, 0, "(return current directory)" },
1220 { "XRMD", RMD, STR1, 1, 0, "<sp> path-name" },
1221
1222 { NULL, 0, 0, 0, 0, 0 }
1223 };
1224
1225 struct tab sitetab[] = {
1226 { "UMASK", UMASK, ARGS, 1, 0, "[ <sp> umask ]" },
1227 { "IDLE", IDLE, ARGS, 1, 0, "[ <sp> maximum-idle-time ]" },
1228 { "CHMOD", CHMOD, NSTR, 1, 0, "<sp> mode <sp> file-name" },
1229 { "HELP", HELP, OSTR, 1, 0, "[ <sp> <string> ]" },
1230 { NULL, 0, 0, 0, 0, 0 }
1231 };
1232
1233 static void help __P((struct tab *, char *));
1234 static struct tab *lookup __P((struct tab *, const char *));
1235 static void opts __P((const char *));
1236 static void sizecmd __P((char *));
1237 static void toolong __P((int));
1238 static int yylex __P((void));
1239
1240 extern int epsvall;
1241
1242 static struct tab *
1243 lookup(p, cmd)
1244 struct tab *p;
1245 const char *cmd;
1246 {
1247
1248 for (; p->name != NULL; p++)
1249 if (strcasecmp(cmd, p->name) == 0)
1250 return (p);
1251 return (0);
1252 }
1253
1254 #include <arpa/telnet.h>
1255
1256 /*
1257 * getline - a hacked up version of fgets to ignore TELNET escape codes.
1258 */
1259 char *
1260 getline(s, n, iop)
1261 char *s;
1262 int n;
1263 FILE *iop;
1264 {
1265 off_t b;
1266 int c;
1267 char *cs;
1268
1269 cs = s;
1270 /* tmpline may contain saved command from urgent mode interruption */
1271 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1272 *cs++ = tmpline[c];
1273 if (tmpline[c] == '\n') {
1274 *cs++ = '\0';
1275 if (debug)
1276 syslog(LOG_DEBUG, "command: %s", s);
1277 tmpline[0] = '\0';
1278 return(s);
1279 }
1280 if (c == 0)
1281 tmpline[0] = '\0';
1282 }
1283 while ((c = getc(iop)) != EOF) {
1284 total_bytes++;
1285 total_bytes_in++;
1286 c &= 0377;
1287 if (c == IAC) {
1288 if ((c = getc(iop)) != EOF) {
1289 total_bytes++;
1290 total_bytes_in++;
1291 c &= 0377;
1292 switch (c) {
1293 case WILL:
1294 case WONT:
1295 c = getc(iop);
1296 total_bytes++;
1297 total_bytes_in++;
1298 b = printf("%c%c%c", IAC, DONT, 0377&c);
1299 total_bytes += b;
1300 total_bytes_out += b;
1301 (void) fflush(stdout);
1302 continue;
1303 case DO:
1304 case DONT:
1305 c = getc(iop);
1306 total_bytes++;
1307 total_bytes_in++;
1308 b = printf("%c%c%c", IAC, WONT, 0377&c);
1309 total_bytes += b;
1310 total_bytes_out += b;
1311 (void) fflush(stdout);
1312 continue;
1313 case IAC:
1314 break;
1315 default:
1316 continue; /* ignore command */
1317 }
1318 }
1319 }
1320 *cs++ = c;
1321 if (--n <= 0 || c == '\n')
1322 break;
1323 }
1324 if (c == EOF && cs == s)
1325 return (NULL);
1326 *cs++ = '\0';
1327 if (debug) {
1328 if (!guest && strncasecmp("pass ", s, 5) == 0) {
1329 /* Don't syslog passwords */
1330 syslog(LOG_DEBUG, "command: %.5s ???", s);
1331 } else {
1332 char *cp;
1333 int len;
1334
1335 /* Don't syslog trailing CR-LF */
1336 len = strlen(s);
1337 cp = s + len - 1;
1338 while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1339 --cp;
1340 --len;
1341 }
1342 syslog(LOG_DEBUG, "command: %.*s", len, s);
1343 }
1344 }
1345 return (s);
1346 }
1347
1348 static void
1349 toolong(signo)
1350 int signo;
1351 {
1352
1353 reply(421,
1354 "Timeout (%d seconds): closing control connection.",
1355 curclass.timeout);
1356 if (logging)
1357 syslog(LOG_INFO, "User %s timed out after %d seconds",
1358 (pw ? pw -> pw_name : "unknown"), curclass.timeout);
1359 dologout(1);
1360 }
1361
1362 static int
1363 yylex()
1364 {
1365 static int cpos, state;
1366 char *cp, *cp2;
1367 struct tab *p;
1368 int n;
1369 char c;
1370
1371 switch (state) {
1372
1373 case CMD:
1374 hasyyerrored = 0;
1375 (void) signal(SIGALRM, toolong);
1376 (void) alarm(curclass.timeout);
1377 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
1378 reply(221, "You could at least say goodbye.");
1379 dologout(0);
1380 }
1381 (void) alarm(0);
1382 #ifdef HASSETPROCTITLE
1383 if (strncasecmp(cbuf, "PASS", 4) != 0)
1384 setproctitle("%s: %s", proctitle, cbuf);
1385 #endif /* HASSETPROCTITLE */
1386 if ((cp = strchr(cbuf, '\r'))) {
1387 *cp++ = '\n';
1388 *cp = '\0';
1389 }
1390 if ((cp = strpbrk(cbuf, " \n")))
1391 cpos = cp - cbuf;
1392 if (cpos == 0)
1393 cpos = 4;
1394 c = cbuf[cpos];
1395 cbuf[cpos] = '\0';
1396 p = lookup(cmdtab, cbuf);
1397 cbuf[cpos] = c;
1398 if (p != NULL) {
1399 if (p->implemented == 0) {
1400 reply(502, "%s command not implemented.",
1401 p->name);
1402 hasyyerrored = 1;
1403 break;
1404 }
1405 state = p->state;
1406 yylval.s = p->name;
1407 return (p->token);
1408 }
1409 break;
1410
1411 case SITECMD:
1412 if (cbuf[cpos] == ' ') {
1413 cpos++;
1414 return (SP);
1415 }
1416 cp = &cbuf[cpos];
1417 if ((cp2 = strpbrk(cp, " \n")))
1418 cpos = cp2 - cbuf;
1419 c = cbuf[cpos];
1420 cbuf[cpos] = '\0';
1421 p = lookup(sitetab, cp);
1422 cbuf[cpos] = c;
1423 if (p != NULL) {
1424 if (p->implemented == 0) {
1425 reply(502, "SITE %s command not implemented.",
1426 p->name);
1427 hasyyerrored = 1;
1428 break;
1429 }
1430 state = p->state;
1431 yylval.s = p->name;
1432 return (p->token);
1433 }
1434 break;
1435
1436 case OSTR:
1437 if (cbuf[cpos] == '\n') {
1438 state = CMD;
1439 return (CRLF);
1440 }
1441 /* FALLTHROUGH */
1442
1443 case STR1:
1444 case ZSTR1:
1445 dostr1:
1446 if (cbuf[cpos] == ' ') {
1447 cpos++;
1448 state = state == OSTR ? STR2 : ++state;
1449 return (SP);
1450 }
1451 break;
1452
1453 case ZSTR2:
1454 if (cbuf[cpos] == '\n') {
1455 state = CMD;
1456 return (CRLF);
1457 }
1458 /* FALLTHROUGH */
1459
1460 case STR2:
1461 cp = &cbuf[cpos];
1462 n = strlen(cp);
1463 cpos += n - 1;
1464 /*
1465 * Make sure the string is nonempty and \n terminated.
1466 */
1467 if (n > 1 && cbuf[cpos] == '\n') {
1468 cbuf[cpos] = '\0';
1469 yylval.s = xstrdup(cp);
1470 cbuf[cpos] = '\n';
1471 state = ARGS;
1472 return (STRING);
1473 }
1474 break;
1475
1476 case NSTR:
1477 if (cbuf[cpos] == ' ') {
1478 cpos++;
1479 return (SP);
1480 }
1481 if (isdigit(cbuf[cpos])) {
1482 cp = &cbuf[cpos];
1483 while (isdigit(cbuf[++cpos]))
1484 ;
1485 c = cbuf[cpos];
1486 cbuf[cpos] = '\0';
1487 yylval.i = atoi(cp);
1488 cbuf[cpos] = c;
1489 state = STR1;
1490 return (NUMBER);
1491 }
1492 state = STR1;
1493 goto dostr1;
1494
1495 case ARGS:
1496 if (isdigit(cbuf[cpos])) {
1497 cp = &cbuf[cpos];
1498 while (isdigit(cbuf[++cpos]))
1499 ;
1500 c = cbuf[cpos];
1501 cbuf[cpos] = '\0';
1502 yylval.i = atoi(cp);
1503 cbuf[cpos] = c;
1504 return (NUMBER);
1505 }
1506 if (strncasecmp(&cbuf[cpos], "ALL", 3) == 0
1507 && !isalnum(cbuf[cpos + 3])) {
1508 yylval.s = xstrdup("ALL");
1509 cpos += 3;
1510 return ALL;
1511 }
1512 switch (cbuf[cpos++]) {
1513
1514 case '\n':
1515 state = CMD;
1516 return (CRLF);
1517
1518 case ' ':
1519 return (SP);
1520
1521 case ',':
1522 return (COMMA);
1523
1524 case 'A':
1525 case 'a':
1526 return (A);
1527
1528 case 'B':
1529 case 'b':
1530 return (B);
1531
1532 case 'C':
1533 case 'c':
1534 return (C);
1535
1536 case 'E':
1537 case 'e':
1538 return (E);
1539
1540 case 'F':
1541 case 'f':
1542 return (F);
1543
1544 case 'I':
1545 case 'i':
1546 return (I);
1547
1548 case 'L':
1549 case 'l':
1550 return (L);
1551
1552 case 'N':
1553 case 'n':
1554 return (N);
1555
1556 case 'P':
1557 case 'p':
1558 return (P);
1559
1560 case 'R':
1561 case 'r':
1562 return (R);
1563
1564 case 'S':
1565 case 's':
1566 return (S);
1567
1568 case 'T':
1569 case 't':
1570 return (T);
1571
1572 }
1573 break;
1574
1575 case NOARGS:
1576 if (cbuf[cpos] == '\n') {
1577 state = CMD;
1578 return (CRLF);
1579 }
1580 c = cbuf[cpos];
1581 cbuf[cpos] = '\0';
1582 reply(501, "'%s' command does not take any arguments.", cbuf);
1583 hasyyerrored = 1;
1584 cbuf[cpos] = c;
1585 break;
1586
1587 default:
1588 fatal("Unknown state in scanner.");
1589 }
1590 yyerror(NULL);
1591 state = CMD;
1592 longjmp(errcatch, 0);
1593 /* NOTREACHED */
1594 }
1595
1596 /* ARGSUSED */
1597 void
1598 yyerror(s)
1599 char *s;
1600 {
1601 char *cp;
1602
1603 if (hasyyerrored)
1604 return;
1605 if ((cp = strchr(cbuf,'\n')) != NULL)
1606 *cp = '\0';
1607 reply(500, "'%s': command not understood.", cbuf);
1608 hasyyerrored = 1;
1609 }
1610
1611 static void
1612 help(ctab, s)
1613 struct tab *ctab;
1614 char *s;
1615 {
1616 struct tab *c;
1617 int width, NCMDS;
1618 off_t b;
1619 char *type;
1620
1621 if (ctab == sitetab)
1622 type = "SITE ";
1623 else
1624 type = "";
1625 width = 0, NCMDS = 0;
1626 for (c = ctab; c->name != NULL; c++) {
1627 int len = strlen(c->name);
1628
1629 if (len > width)
1630 width = len;
1631 NCMDS++;
1632 }
1633 width = (width + 8) &~ 7;
1634 if (s == 0) {
1635 int i, j, w;
1636 int columns, lines;
1637
1638 lreply(214, "");
1639 lreply(0, "The following %scommands are recognized.", type);
1640 lreply(0, "(`-' = not implemented, `+' = supports options)");
1641 columns = 76 / width;
1642 if (columns == 0)
1643 columns = 1;
1644 lines = (NCMDS + columns - 1) / columns;
1645 for (i = 0; i < lines; i++) {
1646 b = printf(" ");
1647 total_bytes += b;
1648 total_bytes_out += b;
1649 for (j = 0; j < columns; j++) {
1650 c = ctab + j * lines + i;
1651 b = printf("%s", c->name);
1652 total_bytes += b;
1653 total_bytes_out += b;
1654 w = strlen(c->name);
1655 if (! c->implemented) {
1656 putchar('-');
1657 total_bytes++;
1658 total_bytes_out++;
1659 w++;
1660 }
1661 if (c->hasopts) {
1662 putchar('+');
1663 total_bytes++;
1664 total_bytes_out++;
1665 w++;
1666 }
1667 if (c + lines >= &ctab[NCMDS])
1668 break;
1669 while (w < width) {
1670 putchar(' ');
1671 total_bytes++;
1672 total_bytes_out++;
1673 w++;
1674 }
1675 }
1676 b = printf("\r\n");
1677 total_bytes += b;
1678 total_bytes_out += b;
1679 }
1680 (void) fflush(stdout);
1681 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1682 return;
1683 }
1684 c = lookup(ctab, s);
1685 if (c == (struct tab *)0) {
1686 reply(502, "Unknown command %s.", s);
1687 return;
1688 }
1689 if (c->implemented)
1690 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1691 else
1692 reply(214, "%s%-*s\t%s; not implemented.", type, width,
1693 c->name, c->help);
1694 }
1695
1696 static void
1697 sizecmd(filename)
1698 char *filename;
1699 {
1700 switch (type) {
1701 case TYPE_L:
1702 case TYPE_I: {
1703 struct stat stbuf;
1704 if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1705 reply(550, "%s: not a plain file.", filename);
1706 else
1707 reply(213, "%qu", (qufmt_t)stbuf.st_size);
1708 break; }
1709 case TYPE_A: {
1710 FILE *fin;
1711 int c;
1712 off_t count;
1713 struct stat stbuf;
1714 fin = fopen(filename, "r");
1715 if (fin == NULL) {
1716 perror_reply(550, filename);
1717 return;
1718 }
1719 if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1720 reply(550, "%s: not a plain file.", filename);
1721 (void) fclose(fin);
1722 return;
1723 }
1724
1725 count = 0;
1726 while((c=getc(fin)) != EOF) {
1727 if (c == '\n') /* will get expanded to \r\n */
1728 count++;
1729 count++;
1730 }
1731 (void) fclose(fin);
1732
1733 reply(213, "%qd", (qdfmt_t)count);
1734 break; }
1735 default:
1736 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1737 }
1738 }
1739
1740 static void
1741 opts(command)
1742 const char *command;
1743 {
1744 struct tab *c;
1745 char *ep;
1746
1747 if ((ep = strchr(command, ' ')) != NULL)
1748 *ep++ = '\0';
1749 c = lookup(cmdtab, command);
1750 if (c == NULL) {
1751 reply(502, "Unknown command %s.", command);
1752 return;
1753 }
1754 if (c->implemented == 0) {
1755 reply(502, "%s command not implemented.", c->name);
1756 return;
1757 }
1758 if (c->hasopts == 0) {
1759 reply(501, "%s command does not support persistent options.",
1760 c->name);
1761 return;
1762 }
1763
1764 if (ep != NULL && *ep != '\0') {
1765 if (c->options != NULL)
1766 free(c->options);
1767 c->options = xstrdup(ep);
1768 }
1769 if (c->options != NULL)
1770 reply(200, "Options for %s are '%s'.", c->name, c->options);
1771 else
1772 reply(200, "No options defined for %s.", c->name);
1773 }
1774