ftpcmd.y revision 1.4 1 /*
2 * Copyright (c) 1985, 1988, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 *
33 * @(#)ftpcmd.y 8.3 (Berkeley) 4/6/94
34 */
35
36 /*
37 * Grammar for FTP commands.
38 * See RFC 959.
39 */
40
41 %{
42
43 #ifndef lint
44 static char sccsid[] = "@(#)ftpcmd.y 8.3 (Berkeley) 4/6/94";
45 static char rcsid[] = "$Id: ftpcmd.y,v 1.4 1994/06/29 01:49:41 deraadt Exp $";
46 #endif /* not lint */
47
48 #include <sys/param.h>
49 #include <sys/socket.h>
50 #include <sys/stat.h>
51
52 #include <netinet/in.h>
53 #include <arpa/ftp.h>
54
55 #include <ctype.h>
56 #include <errno.h>
57 #include <glob.h>
58 #include <pwd.h>
59 #include <setjmp.h>
60 #include <signal.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <syslog.h>
65 #include <time.h>
66 #include <unistd.h>
67
68 #include "extern.h"
69
70 extern struct sockaddr_in data_dest;
71 extern int logged_in;
72 extern struct passwd *pw;
73 extern int guest;
74 extern int logging;
75 extern int type;
76 extern int form;
77 extern int debug;
78 extern int timeout;
79 extern int maxtimeout;
80 extern int pdata;
81 extern char hostname[], remotehost[];
82 extern char proctitle[];
83 extern int usedefault;
84 extern int transflag;
85 extern char tmpline[];
86
87 off_t restart_point;
88
89 static int cmd_type;
90 static int cmd_form;
91 static int cmd_bytesz;
92 char cbuf[512];
93 char *fromname;
94
95 %}
96
97 %union {
98 int i;
99 char *s;
100 }
101
102 %token
103 A B C E F I
104 L N P R S T
105
106 SP CRLF COMMA
107
108 USER PASS ACCT REIN QUIT PORT
109 PASV TYPE STRU MODE RETR STOR
110 APPE MLFL MAIL MSND MSOM MSAM
111 MRSQ MRCP ALLO REST RNFR RNTO
112 ABOR DELE CWD LIST NLST SITE
113 STAT HELP NOOP MKD RMD PWD
114 CDUP STOU SMNT SYST SIZE MDTM
115
116 UMASK IDLE CHMOD
117
118 LEXERR
119
120 %token <s> STRING
121 %token <i> NUMBER
122
123 %type <i> check_login octal_number byte_size
124 %type <i> struct_code mode_code type_code form_code
125 %type <s> pathstring pathname password username
126
127 %start cmd_list
128
129 %%
130
131 cmd_list
132 : /* empty */
133 | cmd_list cmd
134 {
135 fromname = (char *) 0;
136 restart_point = (off_t) 0;
137 }
138 | cmd_list rcmd
139 ;
140
141 cmd
142 : USER SP username CRLF
143 {
144 user($3);
145 free($3);
146 }
147 | PASS SP password CRLF
148 {
149 pass($3);
150 free($3);
151 }
152 | PORT SP host_port CRLF
153 {
154 usedefault = 0;
155 if (pdata >= 0) {
156 (void) close(pdata);
157 pdata = -1;
158 }
159 reply(200, "PORT command successful.");
160 }
161 | PASV CRLF
162 {
163 passive();
164 }
165 | TYPE SP type_code CRLF
166 {
167 switch (cmd_type) {
168
169 case TYPE_A:
170 if (cmd_form == FORM_N) {
171 reply(200, "Type set to A.");
172 type = cmd_type;
173 form = cmd_form;
174 } else
175 reply(504, "Form must be N.");
176 break;
177
178 case TYPE_E:
179 reply(504, "Type E not implemented.");
180 break;
181
182 case TYPE_I:
183 reply(200, "Type set to I.");
184 type = cmd_type;
185 break;
186
187 case TYPE_L:
188 #if NBBY == 8
189 if (cmd_bytesz == 8) {
190 reply(200,
191 "Type set to L (byte size 8).");
192 type = cmd_type;
193 } else
194 reply(504, "Byte size must be 8.");
195 #else /* NBBY == 8 */
196 UNIMPLEMENTED for NBBY != 8
197 #endif /* NBBY == 8 */
198 }
199 }
200 | STRU SP struct_code CRLF
201 {
202 switch ($3) {
203
204 case STRU_F:
205 reply(200, "STRU F ok.");
206 break;
207
208 default:
209 reply(504, "Unimplemented STRU type.");
210 }
211 }
212 | MODE SP mode_code CRLF
213 {
214 switch ($3) {
215
216 case MODE_S:
217 reply(200, "MODE S ok.");
218 break;
219
220 default:
221 reply(502, "Unimplemented MODE type.");
222 }
223 }
224 | ALLO SP NUMBER CRLF
225 {
226 reply(202, "ALLO command ignored.");
227 }
228 | ALLO SP NUMBER SP R SP NUMBER CRLF
229 {
230 reply(202, "ALLO command ignored.");
231 }
232 | RETR check_login SP pathname CRLF
233 {
234 if ($2 && $4 != NULL)
235 retrieve((char *) 0, $4);
236 if ($4 != NULL)
237 free($4);
238 }
239 | STOR check_login SP pathname CRLF
240 {
241 if ($2 && $4 != NULL)
242 store($4, "w", 0);
243 if ($4 != NULL)
244 free($4);
245 }
246 | APPE check_login SP pathname CRLF
247 {
248 if ($2 && $4 != NULL)
249 store($4, "a", 0);
250 if ($4 != NULL)
251 free($4);
252 }
253 | NLST check_login CRLF
254 {
255 if ($2)
256 send_file_list(".");
257 }
258 | NLST check_login SP STRING CRLF
259 {
260 if ($2 && $4 != NULL)
261 send_file_list($4);
262 if ($4 != NULL)
263 free($4);
264 }
265 | LIST check_login CRLF
266 {
267 if ($2)
268 retrieve("/bin/ls -lgA", "");
269 }
270 | LIST check_login SP pathname CRLF
271 {
272 if ($2 && $4 != NULL)
273 retrieve("/bin/ls -lgA %s", $4);
274 if ($4 != NULL)
275 free($4);
276 }
277 | STAT check_login SP pathname CRLF
278 {
279 if ($2 && $4 != NULL)
280 statfilecmd($4);
281 if ($4 != NULL)
282 free($4);
283 }
284 | STAT CRLF
285 {
286 statcmd();
287 }
288 | DELE check_login SP pathname CRLF
289 {
290 if ($2 && $4 != NULL)
291 delete($4);
292 if ($4 != NULL)
293 free($4);
294 }
295 | RNTO SP pathname CRLF
296 {
297 if (fromname) {
298 renamecmd(fromname, $3);
299 free(fromname);
300 fromname = (char *) 0;
301 } else {
302 reply(503, "Bad sequence of commands.");
303 }
304 free($3);
305 }
306 | ABOR CRLF
307 {
308 reply(225, "ABOR command successful.");
309 }
310 | CWD check_login CRLF
311 {
312 if ($2)
313 cwd(pw->pw_dir);
314 }
315 | CWD check_login SP pathname CRLF
316 {
317 if ($2 && $4 != NULL)
318 cwd($4);
319 if ($4 != NULL)
320 free($4);
321 }
322 | HELP CRLF
323 {
324 help(cmdtab, (char *) 0);
325 }
326 | HELP SP STRING CRLF
327 {
328 char *cp = $3;
329
330 if (strncasecmp(cp, "SITE", 4) == 0) {
331 cp = $3 + 4;
332 if (*cp == ' ')
333 cp++;
334 if (*cp)
335 help(sitetab, cp);
336 else
337 help(sitetab, (char *) 0);
338 } else
339 help(cmdtab, $3);
340 }
341 | NOOP CRLF
342 {
343 reply(200, "NOOP command successful.");
344 }
345 | MKD check_login SP pathname CRLF
346 {
347 if ($2 && $4 != NULL)
348 makedir($4);
349 if ($4 != NULL)
350 free($4);
351 }
352 | RMD check_login SP pathname CRLF
353 {
354 if ($2 && $4 != NULL)
355 removedir($4);
356 if ($4 != NULL)
357 free($4);
358 }
359 | PWD check_login CRLF
360 {
361 if ($2)
362 pwd();
363 }
364 | CDUP check_login CRLF
365 {
366 if ($2)
367 cwd("..");
368 }
369 | SITE SP HELP CRLF
370 {
371 help(sitetab, (char *) 0);
372 }
373 | SITE SP HELP SP STRING CRLF
374 {
375 help(sitetab, $5);
376 }
377 | SITE SP UMASK check_login CRLF
378 {
379 int oldmask;
380
381 if ($4) {
382 oldmask = umask(0);
383 (void) umask(oldmask);
384 reply(200, "Current UMASK is %03o", oldmask);
385 }
386 }
387 | SITE SP UMASK check_login SP octal_number CRLF
388 {
389 int oldmask;
390
391 if ($4) {
392 if (($6 == -1) || ($6 > 0777)) {
393 reply(501, "Bad UMASK value");
394 } else {
395 oldmask = umask($6);
396 reply(200,
397 "UMASK set to %03o (was %03o)",
398 $6, oldmask);
399 }
400 }
401 }
402 | SITE SP CHMOD check_login SP octal_number SP pathname CRLF
403 {
404 if ($4 && ($8 != NULL)) {
405 if ($6 > 0777)
406 reply(501,
407 "CHMOD: Mode value must be between 0 and 0777");
408 else if (chmod($8, $6) < 0)
409 perror_reply(550, $8);
410 else
411 reply(200, "CHMOD command successful.");
412 }
413 if ($8 != NULL)
414 free($8);
415 }
416 | SITE SP IDLE CRLF
417 {
418 reply(200,
419 "Current IDLE time limit is %d seconds; max %d",
420 timeout, maxtimeout);
421 }
422 | SITE SP IDLE SP NUMBER CRLF
423 {
424 if ($5 < 30 || $5 > maxtimeout) {
425 reply(501,
426 "Maximum IDLE time must be between 30 and %d seconds",
427 maxtimeout);
428 } else {
429 timeout = $5;
430 (void) alarm((unsigned) timeout);
431 reply(200,
432 "Maximum IDLE time set to %d seconds",
433 timeout);
434 }
435 }
436 | STOU check_login SP pathname CRLF
437 {
438 if ($2 && $4 != NULL)
439 store($4, "w", 1);
440 if ($4 != NULL)
441 free($4);
442 }
443 | SYST CRLF
444 {
445 #ifdef unix
446 #ifdef BSD
447 reply(215, "UNIX Type: L%d Version: BSD-%d",
448 NBBY, BSD);
449 #else /* BSD */
450 reply(215, "UNIX Type: L%d", NBBY);
451 #endif /* BSD */
452 #else /* unix */
453 reply(215, "UNKNOWN Type: L%d", NBBY);
454 #endif /* unix */
455 }
456
457 /*
458 * SIZE is not in RFC959, 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 RFC959, 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 reply(550, "%s: %s",
487 $4, strerror(errno));
488 else if (!S_ISREG(stbuf.st_mode)) {
489 reply(550, "%s: not a plain file.", $4);
490 } else {
491 struct tm *t;
492 t = gmtime(&stbuf.st_mtime);
493 reply(213,
494 "19%02d%02d%02d%02d%02d%02d",
495 t->tm_year, 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 rcmd
513 : RNFR check_login SP pathname CRLF
514 {
515 char *renamefrom();
516
517 restart_point = (off_t) 0;
518 if ($2 && $4) {
519 fromname = renamefrom($4);
520 if (fromname == (char *) 0 && $4) {
521 free($4);
522 }
523 }
524 }
525 | REST SP byte_size CRLF
526 {
527 fromname = (char *) 0;
528 restart_point = $3; /* XXX $3 is only "int" */
529 reply(350, "Restarting at %qd. %s", restart_point,
530 "Send STORE or RETRIEVE to initiate transfer.");
531 }
532 ;
533
534 username
535 : STRING
536 ;
537
538 password
539 : /* empty */
540 {
541 $$ = (char *)calloc(1, sizeof(char));
542 }
543 | STRING
544 ;
545
546 byte_size
547 : NUMBER
548 ;
549
550 host_port
551 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
552 NUMBER COMMA NUMBER
553 {
554 char *a, *p;
555
556 a = (char *)&data_dest.sin_addr;
557 a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
558 p = (char *)&data_dest.sin_port;
559 p[0] = $9; p[1] = $11;
560 data_dest.sin_family = AF_INET;
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 others.
659 */
660 if (logged_in && $1 && *$1 == '~') {
661 glob_t gl;
662 int flags =
663 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
664
665 memset(&gl, 0, sizeof(gl));
666 if (glob($1, flags, NULL, &gl) ||
667 gl.gl_pathc == 0) {
668 reply(550, "not found");
669 $$ = NULL;
670 } else {
671 $$ = strdup(gl.gl_pathv[0]);
672 }
673 globfree(&gl);
674 free($1);
675 } else
676 $$ = $1;
677 }
678 ;
679
680 pathstring
681 : STRING
682 ;
683
684 octal_number
685 : NUMBER
686 {
687 int ret, dec, multby, digit;
688
689 /*
690 * Convert a number that was read as decimal number
691 * to what it would be if it had been read as octal.
692 */
693 dec = $1;
694 multby = 1;
695 ret = 0;
696 while (dec) {
697 digit = dec%10;
698 if (digit > 7) {
699 ret = -1;
700 break;
701 }
702 ret += digit * multby;
703 multby *= 8;
704 dec /= 10;
705 }
706 $$ = ret;
707 }
708 ;
709
710
711 check_login
712 : /* empty */
713 {
714 if (logged_in)
715 $$ = 1;
716 else {
717 reply(530, "Please login with USER and PASS.");
718 $$ = 0;
719 }
720 }
721 ;
722
723 %%
724
725 extern jmp_buf errcatch;
726
727 #define CMD 0 /* beginning of command */
728 #define ARGS 1 /* expect miscellaneous arguments */
729 #define STR1 2 /* expect SP followed by STRING */
730 #define STR2 3 /* expect STRING */
731 #define OSTR 4 /* optional SP then STRING */
732 #define ZSTR1 5 /* SP then optional STRING */
733 #define ZSTR2 6 /* optional STRING after SP */
734 #define SITECMD 7 /* SITE command */
735 #define NSTR 8 /* Number followed by a string */
736
737 struct tab {
738 char *name;
739 short token;
740 short state;
741 short implemented; /* 1 if command is implemented */
742 char *help;
743 };
744
745 struct tab cmdtab[] = { /* In order defined in RFC 765 */
746 { "USER", USER, STR1, 1, "<sp> username" },
747 { "PASS", PASS, ZSTR1, 1, "<sp> password" },
748 { "ACCT", ACCT, STR1, 0, "(specify account)" },
749 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
750 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" },
751 { "QUIT", QUIT, ARGS, 1, "(terminate service)", },
752 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" },
753 { "PASV", PASV, ARGS, 1, "(set server in passive mode)" },
754 { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" },
755 { "STRU", STRU, ARGS, 1, "(specify file structure)" },
756 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
757 { "RETR", RETR, STR1, 1, "<sp> file-name" },
758 { "STOR", STOR, STR1, 1, "<sp> file-name" },
759 { "APPE", APPE, STR1, 1, "<sp> file-name" },
760 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
761 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
762 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
763 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
764 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
765 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
766 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
767 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
768 { "REST", REST, ARGS, 1, "<sp> offset (restart command)" },
769 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
770 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
771 { "ABOR", ABOR, ARGS, 1, "(abort operation)" },
772 { "DELE", DELE, STR1, 1, "<sp> file-name" },
773 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
774 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
775 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
776 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
777 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
778 { "SYST", SYST, ARGS, 1, "(get type of operating system)" },
779 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
780 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
781 { "NOOP", NOOP, ARGS, 1, "" },
782 { "MKD", MKD, STR1, 1, "<sp> path-name" },
783 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
784 { "RMD", RMD, STR1, 1, "<sp> path-name" },
785 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
786 { "PWD", PWD, ARGS, 1, "(return current directory)" },
787 { "XPWD", PWD, ARGS, 1, "(return current directory)" },
788 { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" },
789 { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" },
790 { "STOU", STOU, STR1, 1, "<sp> file-name" },
791 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
792 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
793 { NULL, 0, 0, 0, 0 }
794 };
795
796 struct tab sitetab[] = {
797 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
798 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
799 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
800 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
801 { NULL, 0, 0, 0, 0 }
802 };
803
804 static char *copy __P((char *));
805 static void help __P((struct tab *, char *));
806 static struct tab *
807 lookup __P((struct tab *, char *));
808 static void sizecmd __P((char *));
809 static void toolong __P((int));
810 static int yylex __P((void));
811
812 static struct tab *
813 lookup(p, cmd)
814 struct tab *p;
815 char *cmd;
816 {
817
818 for (; p->name != NULL; p++)
819 if (strcmp(cmd, p->name) == 0)
820 return (p);
821 return (0);
822 }
823
824 #include <arpa/telnet.h>
825
826 /*
827 * getline - a hacked up version of fgets to ignore TELNET escape codes.
828 */
829 char *
830 getline(s, n, iop)
831 char *s;
832 int n;
833 FILE *iop;
834 {
835 int c;
836 register char *cs;
837
838 cs = s;
839 /* tmpline may contain saved command from urgent mode interruption */
840 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
841 *cs++ = tmpline[c];
842 if (tmpline[c] == '\n') {
843 *cs++ = '\0';
844 if (debug)
845 syslog(LOG_DEBUG, "command: %s", s);
846 tmpline[0] = '\0';
847 return(s);
848 }
849 if (c == 0)
850 tmpline[0] = '\0';
851 }
852 while ((c = getc(iop)) != EOF) {
853 c &= 0377;
854 if (c == IAC) {
855 if ((c = getc(iop)) != EOF) {
856 c &= 0377;
857 switch (c) {
858 case WILL:
859 case WONT:
860 c = getc(iop);
861 printf("%c%c%c", IAC, DONT, 0377&c);
862 (void) fflush(stdout);
863 continue;
864 case DO:
865 case DONT:
866 c = getc(iop);
867 printf("%c%c%c", IAC, WONT, 0377&c);
868 (void) fflush(stdout);
869 continue;
870 case IAC:
871 break;
872 default:
873 continue; /* ignore command */
874 }
875 }
876 }
877 *cs++ = c;
878 if (--n <= 0 || c == '\n')
879 break;
880 }
881 if (c == EOF && cs == s)
882 return (NULL);
883 *cs++ = '\0';
884 if (debug) {
885 if (!guest && strncasecmp("pass ", s, 5) == 0) {
886 /* Don't syslog passwords */
887 syslog(LOG_DEBUG, "command: %.5s ???", s);
888 } else {
889 register char *cp;
890 register int len;
891
892 /* Don't syslog trailing CR-LF */
893 len = strlen(s);
894 cp = s + len - 1;
895 while (cp >= s && (*cp == '\n' || *cp == '\r')) {
896 --cp;
897 --len;
898 }
899 syslog(LOG_DEBUG, "command: %.*s", len, s);
900 }
901 }
902 return (s);
903 }
904
905 static void
906 toolong(signo)
907 int signo;
908 {
909
910 reply(421,
911 "Timeout (%d seconds): closing control connection.", timeout);
912 if (logging)
913 syslog(LOG_INFO, "User %s timed out after %d seconds",
914 (pw ? pw -> pw_name : "unknown"), timeout);
915 dologout(1);
916 }
917
918 static int
919 yylex()
920 {
921 static int cpos, state;
922 char *cp, *cp2;
923 struct tab *p;
924 int n;
925 char c;
926
927 for (;;) {
928 switch (state) {
929
930 case CMD:
931 (void) signal(SIGALRM, toolong);
932 (void) alarm((unsigned) timeout);
933 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
934 reply(221, "You could at least say goodbye.");
935 dologout(0);
936 }
937 (void) alarm(0);
938 #ifdef HASSETPROCTITLE
939 if (strncasecmp(cbuf, "PASS", 4) != NULL)
940 setproctitle("%s: %s", proctitle, cbuf);
941 #endif /* HASSETPROCTITLE */
942 if ((cp = strchr(cbuf, '\r'))) {
943 *cp++ = '\n';
944 *cp = '\0';
945 }
946 if ((cp = strpbrk(cbuf, " \n")))
947 cpos = cp - cbuf;
948 if (cpos == 0)
949 cpos = 4;
950 c = cbuf[cpos];
951 cbuf[cpos] = '\0';
952 upper(cbuf);
953 p = lookup(cmdtab, cbuf);
954 cbuf[cpos] = c;
955 if (p != 0) {
956 if (p->implemented == 0) {
957 nack(p->name);
958 longjmp(errcatch,0);
959 /* NOTREACHED */
960 }
961 state = p->state;
962 yylval.s = p->name;
963 return (p->token);
964 }
965 break;
966
967 case SITECMD:
968 if (cbuf[cpos] == ' ') {
969 cpos++;
970 return (SP);
971 }
972 cp = &cbuf[cpos];
973 if ((cp2 = strpbrk(cp, " \n")))
974 cpos = cp2 - cbuf;
975 c = cbuf[cpos];
976 cbuf[cpos] = '\0';
977 upper(cp);
978 p = lookup(sitetab, cp);
979 cbuf[cpos] = c;
980 if (p != 0) {
981 if (p->implemented == 0) {
982 state = CMD;
983 nack(p->name);
984 longjmp(errcatch,0);
985 /* NOTREACHED */
986 }
987 state = p->state;
988 yylval.s = p->name;
989 return (p->token);
990 }
991 state = CMD;
992 break;
993
994 case OSTR:
995 if (cbuf[cpos] == '\n') {
996 state = CMD;
997 return (CRLF);
998 }
999 /* FALLTHROUGH */
1000
1001 case STR1:
1002 case ZSTR1:
1003 dostr1:
1004 if (cbuf[cpos] == ' ') {
1005 cpos++;
1006 state = state == OSTR ? STR2 : ++state;
1007 return (SP);
1008 }
1009 break;
1010
1011 case ZSTR2:
1012 if (cbuf[cpos] == '\n') {
1013 state = CMD;
1014 return (CRLF);
1015 }
1016 /* FALLTHROUGH */
1017
1018 case STR2:
1019 cp = &cbuf[cpos];
1020 n = strlen(cp);
1021 cpos += n - 1;
1022 /*
1023 * Make sure the string is nonempty and \n terminated.
1024 */
1025 if (n > 1 && cbuf[cpos] == '\n') {
1026 cbuf[cpos] = '\0';
1027 yylval.s = copy(cp);
1028 cbuf[cpos] = '\n';
1029 state = ARGS;
1030 return (STRING);
1031 }
1032 break;
1033
1034 case NSTR:
1035 if (cbuf[cpos] == ' ') {
1036 cpos++;
1037 return (SP);
1038 }
1039 if (isdigit(cbuf[cpos])) {
1040 cp = &cbuf[cpos];
1041 while (isdigit(cbuf[++cpos]))
1042 ;
1043 c = cbuf[cpos];
1044 cbuf[cpos] = '\0';
1045 yylval.i = atoi(cp);
1046 cbuf[cpos] = c;
1047 state = STR1;
1048 return (NUMBER);
1049 }
1050 state = STR1;
1051 goto dostr1;
1052
1053 case ARGS:
1054 if (isdigit(cbuf[cpos])) {
1055 cp = &cbuf[cpos];
1056 while (isdigit(cbuf[++cpos]))
1057 ;
1058 c = cbuf[cpos];
1059 cbuf[cpos] = '\0';
1060 yylval.i = atoi(cp);
1061 cbuf[cpos] = c;
1062 return (NUMBER);
1063 }
1064 switch (cbuf[cpos++]) {
1065
1066 case '\n':
1067 state = CMD;
1068 return (CRLF);
1069
1070 case ' ':
1071 return (SP);
1072
1073 case ',':
1074 return (COMMA);
1075
1076 case 'A':
1077 case 'a':
1078 return (A);
1079
1080 case 'B':
1081 case 'b':
1082 return (B);
1083
1084 case 'C':
1085 case 'c':
1086 return (C);
1087
1088 case 'E':
1089 case 'e':
1090 return (E);
1091
1092 case 'F':
1093 case 'f':
1094 return (F);
1095
1096 case 'I':
1097 case 'i':
1098 return (I);
1099
1100 case 'L':
1101 case 'l':
1102 return (L);
1103
1104 case 'N':
1105 case 'n':
1106 return (N);
1107
1108 case 'P':
1109 case 'p':
1110 return (P);
1111
1112 case 'R':
1113 case 'r':
1114 return (R);
1115
1116 case 'S':
1117 case 's':
1118 return (S);
1119
1120 case 'T':
1121 case 't':
1122 return (T);
1123
1124 }
1125 break;
1126
1127 default:
1128 fatal("Unknown state in scanner.");
1129 }
1130 yyerror((char *) 0);
1131 state = CMD;
1132 longjmp(errcatch,0);
1133 }
1134 }
1135
1136 void
1137 upper(s)
1138 char *s;
1139 {
1140 while (*s != '\0') {
1141 if (islower(*s))
1142 *s = toupper(*s);
1143 s++;
1144 }
1145 }
1146
1147 static char *
1148 copy(s)
1149 char *s;
1150 {
1151 char *p;
1152
1153 p = malloc((unsigned) strlen(s) + 1);
1154 if (p == NULL)
1155 fatal("Ran out of memory.");
1156 (void) strcpy(p, s);
1157 return (p);
1158 }
1159
1160 static void
1161 help(ctab, s)
1162 struct tab *ctab;
1163 char *s;
1164 {
1165 struct tab *c;
1166 int width, NCMDS;
1167 char *type;
1168
1169 if (ctab == sitetab)
1170 type = "SITE ";
1171 else
1172 type = "";
1173 width = 0, NCMDS = 0;
1174 for (c = ctab; c->name != NULL; c++) {
1175 int len = strlen(c->name);
1176
1177 if (len > width)
1178 width = len;
1179 NCMDS++;
1180 }
1181 width = (width + 8) &~ 7;
1182 if (s == 0) {
1183 int i, j, w;
1184 int columns, lines;
1185
1186 lreply(214, "The following %scommands are recognized %s.",
1187 type, "(* =>'s unimplemented)");
1188 columns = 76 / width;
1189 if (columns == 0)
1190 columns = 1;
1191 lines = (NCMDS + columns - 1) / columns;
1192 for (i = 0; i < lines; i++) {
1193 printf(" ");
1194 for (j = 0; j < columns; j++) {
1195 c = ctab + j * lines + i;
1196 printf("%s%c", c->name,
1197 c->implemented ? ' ' : '*');
1198 if (c + lines >= &ctab[NCMDS])
1199 break;
1200 w = strlen(c->name) + 1;
1201 while (w < width) {
1202 putchar(' ');
1203 w++;
1204 }
1205 }
1206 printf("\r\n");
1207 }
1208 (void) fflush(stdout);
1209 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1210 return;
1211 }
1212 upper(s);
1213 c = lookup(ctab, s);
1214 if (c == (struct tab *)0) {
1215 reply(502, "Unknown command %s.", s);
1216 return;
1217 }
1218 if (c->implemented)
1219 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1220 else
1221 reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1222 c->name, c->help);
1223 }
1224
1225 static void
1226 sizecmd(filename)
1227 char *filename;
1228 {
1229 switch (type) {
1230 case TYPE_L:
1231 case TYPE_I: {
1232 struct stat stbuf;
1233 if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1234 reply(550, "%s: not a plain file.", filename);
1235 else
1236 reply(213, "%qu", stbuf.st_size);
1237 break; }
1238 case TYPE_A: {
1239 FILE *fin;
1240 int c;
1241 off_t count;
1242 struct stat stbuf;
1243 fin = fopen(filename, "r");
1244 if (fin == NULL) {
1245 perror_reply(550, filename);
1246 return;
1247 }
1248 if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1249 reply(550, "%s: not a plain file.", filename);
1250 (void) fclose(fin);
1251 return;
1252 }
1253
1254 count = 0;
1255 while((c=getc(fin)) != EOF) {
1256 if (c == '\n') /* will get expanded to \r\n */
1257 count++;
1258 count++;
1259 }
1260 (void) fclose(fin);
1261
1262 reply(213, "%qd", count);
1263 break; }
1264 default:
1265 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1266 }
1267 }
1268