ftpcmd.y revision 1.22 1 /* $NetBSD: ftpcmd.y,v 1.22 1998/09/06 10:39:40 lukem 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.22 1998/09/06 10:39:40 lukem 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
76 #include "extern.h"
77
78 off_t restart_point;
79
80 static int cmd_type;
81 static int cmd_form;
82 static int cmd_bytesz;
83 char cbuf[512];
84 char *fromname;
85 int hasyyerrored;
86
87 %}
88
89 %union {
90 int i;
91 char *s;
92 }
93
94 %token
95 A B C E F I
96 L N P R S T
97
98 SP CRLF COMMA
99
100 USER PASS ACCT REIN QUIT PORT
101 PASV TYPE STRU MODE RETR STOR
102 APPE MLFL MAIL MSND MSOM MSAM
103 MRSQ MRCP ALLO REST RNFR RNTO
104 ABOR DELE CWD LIST NLST SITE
105 STAT HELP NOOP MKD RMD PWD
106 CDUP STOU SMNT SYST
107
108 SIZE MDTM
109
110 UMASK IDLE CHMOD
111
112 LEXERR
113
114 %token <s> STRING
115 %token <i> NUMBER
116
117 %type <i> check_login check_modify octal_number byte_size
118 %type <i> struct_code mode_code type_code form_code
119 %type <s> pathstring pathname password username
120
121 %start cmd_list
122
123 %%
124
125 cmd_list
126 : /* empty */
127 | cmd_list cmd
128 {
129 fromname = NULL;
130 restart_point = (off_t) 0;
131 }
132 | cmd_list rcmd
133 ;
134
135 cmd
136 : USER SP username CRLF
137 {
138 user($3);
139 free($3);
140 }
141 | PASS SP password CRLF
142 {
143 pass($3);
144 free($3);
145 }
146 | PORT check_login SP host_port CRLF
147 {
148 if ($2) {
149 /* be paranoid, if told so */
150 if (curclass.checkportcmd &&
151 ((ntohs(data_dest.sin_port) < IPPORT_RESERVED) ||
152 memcmp(&data_dest.sin_addr, &his_addr.sin_addr,
153 sizeof(data_dest.sin_addr)) != 0)) {
154 reply(500,
155 "Illegal PORT command rejected");
156 } else {
157 usedefault = 0;
158 if (pdata >= 0) {
159 (void) close(pdata);
160 pdata = -1;
161 }
162 reply(200, "PORT command successful.");
163 }
164 }
165 }
166 | PASV check_login CRLF
167 {
168 if (curclass.passive) {
169 passive();
170 } else {
171 reply(500, "PASV mode not available.");
172 }
173 }
174 | TYPE SP type_code CRLF
175 {
176 switch (cmd_type) {
177
178 case TYPE_A:
179 if (cmd_form == FORM_N) {
180 reply(200, "Type set to A.");
181 type = cmd_type;
182 form = cmd_form;
183 } else
184 reply(504, "Form must be N.");
185 break;
186
187 case TYPE_E:
188 reply(504, "Type E not implemented.");
189 break;
190
191 case TYPE_I:
192 reply(200, "Type set to I.");
193 type = cmd_type;
194 break;
195
196 case TYPE_L:
197 #if NBBY == 8
198 if (cmd_bytesz == 8) {
199 reply(200,
200 "Type set to L (byte size 8).");
201 type = cmd_type;
202 } else
203 reply(504, "Byte size must be 8.");
204 #else /* NBBY == 8 */
205 UNIMPLEMENTED for NBBY != 8
206 #endif /* NBBY == 8 */
207 }
208 }
209 | STRU SP struct_code CRLF
210 {
211 switch ($3) {
212
213 case STRU_F:
214 reply(200, "STRU F ok.");
215 break;
216
217 default:
218 reply(504, "Unimplemented STRU type.");
219 }
220 }
221 | MODE SP mode_code CRLF
222 {
223 switch ($3) {
224
225 case MODE_S:
226 reply(200, "MODE S ok.");
227 break;
228
229 default:
230 reply(502, "Unimplemented MODE type.");
231 }
232 }
233 | ALLO SP NUMBER CRLF
234 {
235 reply(202, "ALLO command ignored.");
236 }
237 | ALLO SP NUMBER SP R SP NUMBER CRLF
238 {
239 reply(202, "ALLO command ignored.");
240 }
241 | RETR check_login SP pathname CRLF
242 {
243 if ($2 && $4 != NULL)
244 retrieve(NULL, $4);
245 if ($4 != NULL)
246 free($4);
247 }
248 | STOR check_login SP pathname CRLF
249 {
250 if ($2 && $4 != NULL)
251 store($4, "w", 0);
252 if ($4 != NULL)
253 free($4);
254 }
255 | APPE check_login SP pathname CRLF
256 {
257 if ($2 && $4 != NULL)
258 store($4, "a", 0);
259 if ($4 != NULL)
260 free($4);
261 }
262 | NLST check_login CRLF
263 {
264 if ($2)
265 send_file_list(".");
266 }
267 | NLST check_login SP STRING CRLF
268 {
269 if ($2 && $4 != NULL)
270 send_file_list($4);
271 if ($4 != NULL)
272 free($4);
273 }
274 | LIST check_login CRLF
275 {
276 if ($2)
277 retrieve("/bin/ls -lgA", "");
278 }
279 | LIST check_login SP pathname CRLF
280 {
281 if ($2 && $4 != NULL)
282 retrieve("/bin/ls -lgA %s", $4);
283 if ($4 != NULL)
284 free($4);
285 }
286 | STAT check_login SP pathname CRLF
287 {
288 if ($2 && $4 != NULL)
289 statfilecmd($4);
290 if ($4 != NULL)
291 free($4);
292 }
293 | STAT CRLF
294 {
295 statcmd();
296 }
297 | DELE check_modify SP pathname CRLF
298 {
299 if ($2 && $4 != NULL)
300 delete($4);
301 if ($4 != NULL)
302 free($4);
303 }
304 | RNTO SP pathname CRLF
305 {
306 if (fromname) {
307 renamecmd(fromname, $3);
308 free(fromname);
309 fromname = NULL;
310 } else {
311 reply(503, "Bad sequence of commands.");
312 }
313 free($3);
314 }
315 | ABOR CRLF
316 {
317 reply(225, "ABOR command successful.");
318 }
319 | CWD check_login CRLF
320 {
321 if ($2)
322 cwd(pw->pw_dir);
323 }
324 | CWD check_login SP pathname CRLF
325 {
326 if ($2 && $4 != NULL)
327 cwd($4);
328 if ($4 != NULL)
329 free($4);
330 }
331 | HELP CRLF
332 {
333 help(cmdtab, NULL);
334 }
335 | HELP SP STRING CRLF
336 {
337 char *cp = $3;
338
339 if (strncasecmp(cp, "SITE", 4) == 0) {
340 cp = $3 + 4;
341 if (*cp == ' ')
342 cp++;
343 if (*cp)
344 help(sitetab, cp);
345 else
346 help(sitetab, NULL);
347 } else
348 help(cmdtab, $3);
349 }
350 | NOOP CRLF
351 {
352 reply(200, "NOOP command successful.");
353 }
354 | MKD check_modify SP pathname CRLF
355 {
356 if ($2 && $4 != NULL)
357 makedir($4);
358 if ($4 != NULL)
359 free($4);
360 }
361 | RMD check_modify SP pathname CRLF
362 {
363 if ($2 && $4 != NULL)
364 removedir($4);
365 if ($4 != NULL)
366 free($4);
367 }
368 | PWD check_login CRLF
369 {
370 if ($2)
371 pwd();
372 }
373 | CDUP check_login CRLF
374 {
375 if ($2)
376 cwd("..");
377 }
378 | SITE SP HELP CRLF
379 {
380 help(sitetab, NULL);
381 }
382 | SITE SP HELP SP STRING CRLF
383 {
384 help(sitetab, $5);
385 }
386 | SITE SP UMASK check_login CRLF
387 {
388 int oldmask;
389
390 if ($4) {
391 oldmask = umask(0);
392 (void) umask(oldmask);
393 reply(200, "Current UMASK is %03o", oldmask);
394 }
395 }
396 | SITE SP UMASK check_modify SP octal_number CRLF
397 {
398 int oldmask;
399
400 if ($4) {
401 if (($6 == -1) || ($6 > 0777)) {
402 reply(501, "Bad UMASK value");
403 } else {
404 oldmask = umask($6);
405 reply(200,
406 "UMASK set to %03o (was %03o)",
407 $6, oldmask);
408 }
409 }
410 }
411 | SITE SP CHMOD check_modify SP octal_number SP pathname CRLF
412 {
413 if ($4 && ($8 != NULL)) {
414 if ($6 > 0777)
415 reply(501,
416 "CHMOD: Mode value must be between 0 and 0777");
417 else if (chmod($8, $6) < 0)
418 perror_reply(550, $8);
419 else
420 reply(200, "CHMOD command successful.");
421 }
422 if ($8 != NULL)
423 free($8);
424 }
425 | SITE SP IDLE CRLF
426 {
427 reply(200,
428 "Current IDLE time limit is %d seconds; max %d",
429 curclass.timeout, curclass.maxtimeout);
430 }
431 | SITE SP IDLE SP NUMBER CRLF
432 {
433 if ($5 < 30 || $5 > curclass.maxtimeout) {
434 reply(501,
435 "IDLE time limit must be between 30 and %d seconds",
436 curclass.maxtimeout);
437 } else {
438 curclass.timeout = $5;
439 (void) alarm(curclass.timeout);
440 reply(200,
441 "IDLE time limit set to %d seconds",
442 curclass.timeout);
443 }
444 }
445 | STOU check_login SP pathname CRLF
446 {
447 if ($2 && $4 != NULL)
448 store($4, "w", 1);
449 if ($4 != NULL)
450 free($4);
451 }
452 | SYST CRLF
453 {
454 reply(215, "UNIX Type: L%d %s", NBBY, version);
455 }
456
457 /*
458 * SIZE is not in RFC 959, but Postel has blessed it and
459 * it will be in the updated RFC.
460 *
461 * Return size of file in a format suitable for
462 * using with RESTART (we just count bytes).
463 */
464 | SIZE check_login SP pathname CRLF
465 {
466 if ($2 && $4 != NULL)
467 sizecmd($4);
468 if ($4 != NULL)
469 free($4);
470 }
471
472 /*
473 * MDTM is not in RFC 959, but Postel has blessed it and
474 * it will be in the updated RFC.
475 *
476 * Return modification time of file as an ISO 3307
477 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
478 * where xxx is the fractional second (of any precision,
479 * not necessarily 3 digits)
480 */
481 | MDTM check_login SP pathname CRLF
482 {
483 if ($2 && $4 != NULL) {
484 struct stat stbuf;
485 if (stat($4, &stbuf) < 0)
486 perror_reply(550, $4);
487 else if (!S_ISREG(stbuf.st_mode)) {
488 reply(550, "%s: not a plain file.", $4);
489 } else {
490 struct tm *t;
491 t = gmtime(&stbuf.st_mtime);
492 reply(213,
493 "%04d%02d%02d%02d%02d%02d",
494 TM_YEAR_BASE + t->tm_year,
495 t->tm_mon+1, t->tm_mday,
496 t->tm_hour, t->tm_min, t->tm_sec);
497 }
498 }
499 if ($4 != NULL)
500 free($4);
501 }
502 | QUIT CRLF
503 {
504 reply(221, "Goodbye.");
505 dologout(0);
506 }
507 | error CRLF
508 {
509 yyerrok;
510 }
511 ;
512
513 rcmd
514 : RNFR check_modify SP pathname CRLF
515 {
516 restart_point = (off_t) 0;
517 if ($2 && $4) {
518 fromname = renamefrom($4);
519 if (fromname == NULL && $4) {
520 free($4);
521 }
522 }
523 }
524 | REST SP byte_size CRLF
525 {
526 fromname = NULL;
527 restart_point = $3; /* XXX $3 is only "int" */
528 reply(350, "Restarting at %qd. %s", restart_point,
529 "Send STORE or RETRIEVE to initiate transfer.");
530 }
531 ;
532
533 username
534 : STRING
535 ;
536
537 password
538 : /* empty */
539 {
540 $$ = (char *)calloc(1, sizeof(char));
541 }
542 | STRING
543 ;
544
545 byte_size
546 : NUMBER
547 ;
548
549 host_port
550 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
551 NUMBER COMMA NUMBER
552 {
553 char *a, *p;
554
555 data_dest.sin_len = sizeof(struct sockaddr_in);
556 data_dest.sin_family = AF_INET;
557 p = (char *)&data_dest.sin_port;
558 p[0] = $9; p[1] = $11;
559 a = (char *)&data_dest.sin_addr;
560 a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
561 }
562 ;
563
564 form_code
565 : N
566 {
567 $$ = FORM_N;
568 }
569 | T
570 {
571 $$ = FORM_T;
572 }
573 | C
574 {
575 $$ = FORM_C;
576 }
577 ;
578
579 type_code
580 : A
581 {
582 cmd_type = TYPE_A;
583 cmd_form = FORM_N;
584 }
585 | A SP form_code
586 {
587 cmd_type = TYPE_A;
588 cmd_form = $3;
589 }
590 | E
591 {
592 cmd_type = TYPE_E;
593 cmd_form = FORM_N;
594 }
595 | E SP form_code
596 {
597 cmd_type = TYPE_E;
598 cmd_form = $3;
599 }
600 | I
601 {
602 cmd_type = TYPE_I;
603 }
604 | L
605 {
606 cmd_type = TYPE_L;
607 cmd_bytesz = NBBY;
608 }
609 | L SP byte_size
610 {
611 cmd_type = TYPE_L;
612 cmd_bytesz = $3;
613 }
614 /* this is for a bug in the BBN ftp */
615 | L byte_size
616 {
617 cmd_type = TYPE_L;
618 cmd_bytesz = $2;
619 }
620 ;
621
622 struct_code
623 : F
624 {
625 $$ = STRU_F;
626 }
627 | R
628 {
629 $$ = STRU_R;
630 }
631 | P
632 {
633 $$ = STRU_P;
634 }
635 ;
636
637 mode_code
638 : S
639 {
640 $$ = MODE_S;
641 }
642 | B
643 {
644 $$ = MODE_B;
645 }
646 | C
647 {
648 $$ = MODE_C;
649 }
650 ;
651
652 pathname
653 : pathstring
654 {
655 /*
656 * Problem: this production is used for all pathname
657 * processing, but only gives a 550 error reply.
658 * This is a valid reply in some cases but not in
659 * others.
660 */
661 if (logged_in && $1 && *$1 == '~') {
662 glob_t gl;
663 int flags =
664 GLOB_BRACE|GLOB_NOCHECK|GLOB_TILDE;
665
666 if ($1[1] == '\0')
667 $$ = strdup(pw->pw_dir);
668 else {
669 memset(&gl, 0, sizeof(gl));
670 if (glob($1, flags, NULL, &gl) ||
671 gl.gl_pathc == 0) {
672 reply(550, "not found");
673 $$ = NULL;
674 } else
675 $$ = strdup(gl.gl_pathv[0]);
676 globfree(&gl);
677 }
678 free($1);
679 } else
680 $$ = $1;
681 }
682 ;
683
684 pathstring
685 : STRING
686 ;
687
688 octal_number
689 : NUMBER
690 {
691 int ret, dec, multby, digit;
692
693 /*
694 * Convert a number that was read as decimal number
695 * to what it would be if it had been read as octal.
696 */
697 dec = $1;
698 multby = 1;
699 ret = 0;
700 while (dec) {
701 digit = dec%10;
702 if (digit > 7) {
703 ret = -1;
704 break;
705 }
706 ret += digit * multby;
707 multby *= 8;
708 dec /= 10;
709 }
710 $$ = ret;
711 }
712 ;
713
714 check_login
715 : /* empty */
716 {
717 if (logged_in)
718 $$ = 1;
719 else {
720 reply(530, "Please login with USER and PASS.");
721 $$ = 0;
722 hasyyerrored = 1;
723 }
724 }
725 ;
726
727 check_modify
728 : /* empty */
729 {
730 if (logged_in) {
731 if (curclass.modify)
732 $$ = 1;
733 else {
734 reply(502,
735 "No permission to use this command.");
736 $$ = 0;
737 hasyyerrored = 1;
738 }
739 } else {
740 reply(530, "Please login with USER and PASS.");
741 $$ = 0;
742 hasyyerrored = 1;
743 }
744 }
745
746 %%
747
748 #define CMD 0 /* beginning of command */
749 #define ARGS 1 /* expect miscellaneous arguments */
750 #define STR1 2 /* expect SP followed by STRING */
751 #define STR2 3 /* expect STRING */
752 #define OSTR 4 /* optional SP then STRING */
753 #define ZSTR1 5 /* SP then optional STRING */
754 #define ZSTR2 6 /* optional STRING after SP */
755 #define SITECMD 7 /* SITE command */
756 #define NSTR 8 /* Number followed by a string */
757 #define NOARGS 9 /* No arguments allowed */
758
759 struct tab {
760 char *name;
761 short token;
762 short state;
763 short implemented; /* 1 if command is implemented */
764 char *help;
765 };
766
767 struct tab cmdtab[] = {
768 /* From RFC 959, in order defined (5.3.1) */
769 { "USER", USER, STR1, 1, "<sp> username" },
770 { "PASS", PASS, ZSTR1, 1, "<sp> password" },
771 { "ACCT", ACCT, STR1, 0, "(specify account)" },
772 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
773 { "CDUP", CDUP, NOARGS, 1, "(change to parent directory)" },
774 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
775 { "QUIT", QUIT, NOARGS, 1, "(terminate service)", },
776 { "REIN", REIN, NOARGS, 0, "(reinitialize server state)" },
777 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" },
778 { "PASV", PASV, NOARGS, 1, "(set server in passive mode)" },
779 { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" },
780 { "STRU", STRU, ARGS, 1, "(specify file structure)" },
781 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
782 { "RETR", RETR, STR1, 1, "<sp> file-name" },
783 { "STOR", STOR, STR1, 1, "<sp> file-name" },
784 { "STOU", STOU, STR1, 1, "<sp> file-name" },
785 { "APPE", APPE, STR1, 1, "<sp> file-name" },
786 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
787 { "REST", REST, ARGS, 1, "<sp> offset (restart command)" },
788 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
789 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
790 { "ABOR", ABOR, NOARGS, 1, "(abort operation)" },
791 { "DELE", DELE, STR1, 1, "<sp> file-name" },
792 { "RMD", RMD, STR1, 1, "<sp> path-name" },
793 { "MKD", MKD, STR1, 1, "<sp> path-name" },
794 { "PWD", PWD, NOARGS, 1, "(return current directory)" },
795 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
796 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
797 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
798 { "SYST", SYST, NOARGS, 1, "(get type of operating system)" },
799 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
800 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
801 { "NOOP", NOOP, NOARGS, 1, "" },
802
803 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
804 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
805
806 /* obsolete commands */
807 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
808 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
809 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
810 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
811 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
812 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
813 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
814 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
815 { "XCUP", CDUP, NOARGS, 1, "(change to parent directory)" },
816 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
817 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
818 { "XPWD", PWD, NOARGS, 1, "(return current directory)" },
819
820 { NULL, 0, 0, 0, 0 }
821 };
822
823 struct tab sitetab[] = {
824 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
825 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
826 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
827 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
828 { NULL, 0, 0, 0, 0 }
829 };
830
831 static char *copy __P((char *));
832 static void help __P((struct tab *, char *));
833 static struct tab *
834 lookup __P((struct tab *, char *));
835 static void sizecmd __P((char *));
836 static void toolong __P((int));
837 static int yylex __P((void));
838
839 static struct tab *
840 lookup(p, cmd)
841 struct tab *p;
842 char *cmd;
843 {
844
845 for (; p->name != NULL; p++)
846 if (strcmp(cmd, p->name) == 0)
847 return (p);
848 return (0);
849 }
850
851 #include <arpa/telnet.h>
852
853 /*
854 * getline - a hacked up version of fgets to ignore TELNET escape codes.
855 */
856 char *
857 getline(s, n, iop)
858 char *s;
859 int n;
860 FILE *iop;
861 {
862 int c;
863 char *cs;
864
865 cs = s;
866 /* tmpline may contain saved command from urgent mode interruption */
867 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
868 *cs++ = tmpline[c];
869 if (tmpline[c] == '\n') {
870 *cs++ = '\0';
871 if (debug)
872 syslog(LOG_DEBUG, "command: %s", s);
873 tmpline[0] = '\0';
874 return(s);
875 }
876 if (c == 0)
877 tmpline[0] = '\0';
878 }
879 while ((c = getc(iop)) != EOF) {
880 c &= 0377;
881 if (c == IAC) {
882 if ((c = getc(iop)) != EOF) {
883 c &= 0377;
884 switch (c) {
885 case WILL:
886 case WONT:
887 c = getc(iop);
888 printf("%c%c%c", IAC, DONT, 0377&c);
889 (void) fflush(stdout);
890 continue;
891 case DO:
892 case DONT:
893 c = getc(iop);
894 printf("%c%c%c", IAC, WONT, 0377&c);
895 (void) fflush(stdout);
896 continue;
897 case IAC:
898 break;
899 default:
900 continue; /* ignore command */
901 }
902 }
903 }
904 *cs++ = c;
905 if (--n <= 0 || c == '\n')
906 break;
907 }
908 if (c == EOF && cs == s)
909 return (NULL);
910 *cs++ = '\0';
911 if (debug) {
912 if (!guest && strncasecmp("pass ", s, 5) == 0) {
913 /* Don't syslog passwords */
914 syslog(LOG_DEBUG, "command: %.5s ???", s);
915 } else {
916 char *cp;
917 int len;
918
919 /* Don't syslog trailing CR-LF */
920 len = strlen(s);
921 cp = s + len - 1;
922 while (cp >= s && (*cp == '\n' || *cp == '\r')) {
923 --cp;
924 --len;
925 }
926 syslog(LOG_DEBUG, "command: %.*s", len, s);
927 }
928 }
929 return (s);
930 }
931
932 static void
933 toolong(signo)
934 int signo;
935 {
936
937 reply(421,
938 "Timeout (%d seconds): closing control connection.",
939 curclass.timeout);
940 if (logging)
941 syslog(LOG_INFO, "User %s timed out after %d seconds",
942 (pw ? pw -> pw_name : "unknown"), curclass.timeout);
943 dologout(1);
944 }
945
946 static int
947 yylex()
948 {
949 static int cpos, state;
950 char *cp, *cp2;
951 struct tab *p;
952 int n;
953 char c;
954
955 for (;;) {
956 switch (state) {
957
958 case CMD:
959 hasyyerrored = 0;
960 (void) signal(SIGALRM, toolong);
961 (void) alarm(curclass.timeout);
962 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
963 reply(221, "You could at least say goodbye.");
964 dologout(0);
965 }
966 (void) alarm(0);
967 #ifdef HASSETPROCTITLE
968 if (strncasecmp(cbuf, "PASS", 4) != 0)
969 setproctitle("%s: %s", proctitle, cbuf);
970 #endif /* HASSETPROCTITLE */
971 if ((cp = strchr(cbuf, '\r'))) {
972 *cp++ = '\n';
973 *cp = '\0';
974 }
975 if ((cp = strpbrk(cbuf, " \n")))
976 cpos = cp - cbuf;
977 if (cpos == 0)
978 cpos = 4;
979 c = cbuf[cpos];
980 cbuf[cpos] = '\0';
981 upper(cbuf);
982 p = lookup(cmdtab, cbuf);
983 cbuf[cpos] = c;
984 if (p != 0) {
985 if (p->implemented == 0) {
986 nack(p->name);
987 break;
988 }
989 state = p->state;
990 yylval.s = p->name;
991 return (p->token);
992 }
993 break;
994
995 case SITECMD:
996 if (cbuf[cpos] == ' ') {
997 cpos++;
998 return (SP);
999 }
1000 cp = &cbuf[cpos];
1001 if ((cp2 = strpbrk(cp, " \n")))
1002 cpos = cp2 - cbuf;
1003 c = cbuf[cpos];
1004 cbuf[cpos] = '\0';
1005 upper(cp);
1006 p = lookup(sitetab, cp);
1007 cbuf[cpos] = c;
1008 if (p != 0) {
1009 if (p->implemented == 0) {
1010 nack(p->name);
1011 break;
1012 }
1013 state = p->state;
1014 yylval.s = p->name;
1015 return (p->token);
1016 }
1017 state = CMD;
1018 break;
1019
1020 case OSTR:
1021 if (cbuf[cpos] == '\n') {
1022 state = CMD;
1023 return (CRLF);
1024 }
1025 /* FALLTHROUGH */
1026
1027 case STR1:
1028 case ZSTR1:
1029 dostr1:
1030 if (cbuf[cpos] == ' ') {
1031 cpos++;
1032 state = state == OSTR ? STR2 : ++state;
1033 return (SP);
1034 }
1035 break;
1036
1037 case ZSTR2:
1038 if (cbuf[cpos] == '\n') {
1039 state = CMD;
1040 return (CRLF);
1041 }
1042 /* FALLTHROUGH */
1043
1044 case STR2:
1045 cp = &cbuf[cpos];
1046 n = strlen(cp);
1047 cpos += n - 1;
1048 /*
1049 * Make sure the string is nonempty and \n terminated.
1050 */
1051 if (n > 1 && cbuf[cpos] == '\n') {
1052 cbuf[cpos] = '\0';
1053 yylval.s = copy(cp);
1054 cbuf[cpos] = '\n';
1055 state = ARGS;
1056 return (STRING);
1057 }
1058 break;
1059
1060 case NSTR:
1061 if (cbuf[cpos] == ' ') {
1062 cpos++;
1063 return (SP);
1064 }
1065 if (isdigit(cbuf[cpos])) {
1066 cp = &cbuf[cpos];
1067 while (isdigit(cbuf[++cpos]))
1068 ;
1069 c = cbuf[cpos];
1070 cbuf[cpos] = '\0';
1071 yylval.i = atoi(cp);
1072 cbuf[cpos] = c;
1073 state = STR1;
1074 return (NUMBER);
1075 }
1076 state = STR1;
1077 goto dostr1;
1078
1079 case ARGS:
1080 if (isdigit(cbuf[cpos])) {
1081 cp = &cbuf[cpos];
1082 while (isdigit(cbuf[++cpos]))
1083 ;
1084 c = cbuf[cpos];
1085 cbuf[cpos] = '\0';
1086 yylval.i = atoi(cp);
1087 cbuf[cpos] = c;
1088 return (NUMBER);
1089 }
1090 switch (cbuf[cpos++]) {
1091
1092 case '\n':
1093 state = CMD;
1094 return (CRLF);
1095
1096 case ' ':
1097 return (SP);
1098
1099 case ',':
1100 return (COMMA);
1101
1102 case 'A':
1103 case 'a':
1104 return (A);
1105
1106 case 'B':
1107 case 'b':
1108 return (B);
1109
1110 case 'C':
1111 case 'c':
1112 return (C);
1113
1114 case 'E':
1115 case 'e':
1116 return (E);
1117
1118 case 'F':
1119 case 'f':
1120 return (F);
1121
1122 case 'I':
1123 case 'i':
1124 return (I);
1125
1126 case 'L':
1127 case 'l':
1128 return (L);
1129
1130 case 'N':
1131 case 'n':
1132 return (N);
1133
1134 case 'P':
1135 case 'p':
1136 return (P);
1137
1138 case 'R':
1139 case 'r':
1140 return (R);
1141
1142 case 'S':
1143 case 's':
1144 return (S);
1145
1146 case 'T':
1147 case 't':
1148 return (T);
1149
1150 }
1151 break;
1152
1153 case NOARGS:
1154 if (cbuf[cpos] == '\n') {
1155 state = CMD;
1156 return (CRLF);
1157 }
1158 c = cbuf[cpos];
1159 cbuf[cpos] = '\0';
1160 reply(501, "'%s' command does not take any arguments.",
1161 cbuf);
1162 cbuf[cpos] = c;
1163 break;
1164
1165 default:
1166 fatal("Unknown state in scanner.");
1167 }
1168 yyerror(NULL);
1169 state = CMD;
1170 longjmp(errcatch, 0);
1171 /* NOTREACHED */
1172 }
1173 }
1174
1175 /* ARGSUSED */
1176 void
1177 yyerror(s)
1178 char *s;
1179 {
1180 char *cp;
1181
1182 if (hasyyerrored)
1183 return;
1184 if ((cp = strchr(cbuf,'\n')) != NULL)
1185 *cp = '\0';
1186 reply(500, "'%s': command not understood.", cbuf);
1187 hasyyerrored = 1;
1188 }
1189
1190 void
1191 upper(s)
1192 char *s;
1193 {
1194 while (*s != '\0') {
1195 *s = toupper(*s);
1196 s++;
1197 }
1198 }
1199
1200 static char *
1201 copy(s)
1202 char *s;
1203 {
1204 char *p;
1205
1206 p = strdup(s);
1207 if (p == NULL)
1208 fatal("Ran out of memory.");
1209 return (p);
1210 }
1211
1212 static void
1213 help(ctab, s)
1214 struct tab *ctab;
1215 char *s;
1216 {
1217 struct tab *c;
1218 int width, NCMDS;
1219 char *type;
1220
1221 if (ctab == sitetab)
1222 type = "SITE ";
1223 else
1224 type = "";
1225 width = 0, NCMDS = 0;
1226 for (c = ctab; c->name != NULL; c++) {
1227 int len = strlen(c->name);
1228
1229 if (len > width)
1230 width = len;
1231 NCMDS++;
1232 }
1233 width = (width + 8) &~ 7;
1234 if (s == 0) {
1235 int i, j, w;
1236 int columns, lines;
1237
1238 lreply(214, "The following %scommands are recognized %s.",
1239 type, "(* =>'s unimplemented)");
1240 columns = 76 / width;
1241 if (columns == 0)
1242 columns = 1;
1243 lines = (NCMDS + columns - 1) / columns;
1244 for (i = 0; i < lines; i++) {
1245 printf(" ");
1246 for (j = 0; j < columns; j++) {
1247 c = ctab + j * lines + i;
1248 printf("%s%c", c->name,
1249 c->implemented ? ' ' : '*');
1250 if (c + lines >= &ctab[NCMDS])
1251 break;
1252 w = strlen(c->name) + 1;
1253 while (w < width) {
1254 putchar(' ');
1255 w++;
1256 }
1257 }
1258 printf("\r\n");
1259 }
1260 (void) fflush(stdout);
1261 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1262 return;
1263 }
1264 upper(s);
1265 c = lookup(ctab, s);
1266 if (c == (struct tab *)0) {
1267 reply(502, "Unknown command %s.", s);
1268 return;
1269 }
1270 if (c->implemented)
1271 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1272 else
1273 reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1274 c->name, c->help);
1275 }
1276
1277 static void
1278 sizecmd(filename)
1279 char *filename;
1280 {
1281 switch (type) {
1282 case TYPE_L:
1283 case TYPE_I: {
1284 struct stat stbuf;
1285 if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1286 reply(550, "%s: not a plain file.", filename);
1287 else
1288 reply(213, "%qu", stbuf.st_size);
1289 break; }
1290 case TYPE_A: {
1291 FILE *fin;
1292 int c;
1293 off_t count;
1294 struct stat stbuf;
1295 fin = fopen(filename, "r");
1296 if (fin == NULL) {
1297 perror_reply(550, filename);
1298 return;
1299 }
1300 if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1301 reply(550, "%s: not a plain file.", filename);
1302 (void) fclose(fin);
1303 return;
1304 }
1305
1306 count = 0;
1307 while((c=getc(fin)) != EOF) {
1308 if (c == '\n') /* will get expanded to \r\n */
1309 count++;
1310 count++;
1311 }
1312 (void) fclose(fin);
1313
1314 reply(213, "%qd", count);
1315 break; }
1316 default:
1317 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1318 }
1319 }
1320
1321