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