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