ftpcmd.y revision 1.5 1 /* $NetBSD: ftpcmd.y,v 1.5 1995/04/11 02:44:51 cgd 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.5 1995/04/11 02:44:51 cgd 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 a = (char *)&data_dest.sin_addr;
562 a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
563 p = (char *)&data_dest.sin_port;
564 p[0] = $9; p[1] = $11;
565 data_dest.sin_family = AF_INET;
566 }
567 ;
568
569 form_code
570 : N
571 {
572 $$ = FORM_N;
573 }
574 | T
575 {
576 $$ = FORM_T;
577 }
578 | C
579 {
580 $$ = FORM_C;
581 }
582 ;
583
584 type_code
585 : A
586 {
587 cmd_type = TYPE_A;
588 cmd_form = FORM_N;
589 }
590 | A SP form_code
591 {
592 cmd_type = TYPE_A;
593 cmd_form = $3;
594 }
595 | E
596 {
597 cmd_type = TYPE_E;
598 cmd_form = FORM_N;
599 }
600 | E SP form_code
601 {
602 cmd_type = TYPE_E;
603 cmd_form = $3;
604 }
605 | I
606 {
607 cmd_type = TYPE_I;
608 }
609 | L
610 {
611 cmd_type = TYPE_L;
612 cmd_bytesz = NBBY;
613 }
614 | L SP byte_size
615 {
616 cmd_type = TYPE_L;
617 cmd_bytesz = $3;
618 }
619 /* this is for a bug in the BBN ftp */
620 | L byte_size
621 {
622 cmd_type = TYPE_L;
623 cmd_bytesz = $2;
624 }
625 ;
626
627 struct_code
628 : F
629 {
630 $$ = STRU_F;
631 }
632 | R
633 {
634 $$ = STRU_R;
635 }
636 | P
637 {
638 $$ = STRU_P;
639 }
640 ;
641
642 mode_code
643 : S
644 {
645 $$ = MODE_S;
646 }
647 | B
648 {
649 $$ = MODE_B;
650 }
651 | C
652 {
653 $$ = MODE_C;
654 }
655 ;
656
657 pathname
658 : pathstring
659 {
660 /*
661 * Problem: this production is used for all pathname
662 * processing, but only gives a 550 error reply.
663 * This is a valid reply in some cases but not in others.
664 */
665 if (logged_in && $1 && *$1 == '~') {
666 glob_t gl;
667 int flags =
668 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
669
670 memset(&gl, 0, sizeof(gl));
671 if (glob($1, flags, NULL, &gl) ||
672 gl.gl_pathc == 0) {
673 reply(550, "not found");
674 $$ = NULL;
675 } else {
676 $$ = strdup(gl.gl_pathv[0]);
677 }
678 globfree(&gl);
679 free($1);
680 } else
681 $$ = $1;
682 }
683 ;
684
685 pathstring
686 : STRING
687 ;
688
689 octal_number
690 : NUMBER
691 {
692 int ret, dec, multby, digit;
693
694 /*
695 * Convert a number that was read as decimal number
696 * to what it would be if it had been read as octal.
697 */
698 dec = $1;
699 multby = 1;
700 ret = 0;
701 while (dec) {
702 digit = dec%10;
703 if (digit > 7) {
704 ret = -1;
705 break;
706 }
707 ret += digit * multby;
708 multby *= 8;
709 dec /= 10;
710 }
711 $$ = ret;
712 }
713 ;
714
715
716 check_login
717 : /* empty */
718 {
719 if (logged_in)
720 $$ = 1;
721 else {
722 reply(530, "Please login with USER and PASS.");
723 $$ = 0;
724 }
725 }
726 ;
727
728 %%
729
730 extern jmp_buf errcatch;
731
732 #define CMD 0 /* beginning of command */
733 #define ARGS 1 /* expect miscellaneous arguments */
734 #define STR1 2 /* expect SP followed by STRING */
735 #define STR2 3 /* expect STRING */
736 #define OSTR 4 /* optional SP then STRING */
737 #define ZSTR1 5 /* SP then optional STRING */
738 #define ZSTR2 6 /* optional STRING after SP */
739 #define SITECMD 7 /* SITE command */
740 #define NSTR 8 /* Number followed by a string */
741
742 struct tab {
743 char *name;
744 short token;
745 short state;
746 short implemented; /* 1 if command is implemented */
747 char *help;
748 };
749
750 struct tab cmdtab[] = { /* In order defined in RFC 765 */
751 { "USER", USER, STR1, 1, "<sp> username" },
752 { "PASS", PASS, ZSTR1, 1, "<sp> password" },
753 { "ACCT", ACCT, STR1, 0, "(specify account)" },
754 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
755 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" },
756 { "QUIT", QUIT, ARGS, 1, "(terminate service)", },
757 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" },
758 { "PASV", PASV, ARGS, 1, "(set server in passive mode)" },
759 { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" },
760 { "STRU", STRU, ARGS, 1, "(specify file structure)" },
761 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
762 { "RETR", RETR, STR1, 1, "<sp> file-name" },
763 { "STOR", STOR, STR1, 1, "<sp> file-name" },
764 { "APPE", APPE, STR1, 1, "<sp> file-name" },
765 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
766 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
767 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
768 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
769 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
770 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
771 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
772 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
773 { "REST", REST, ARGS, 1, "<sp> offset (restart command)" },
774 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
775 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
776 { "ABOR", ABOR, ARGS, 1, "(abort operation)" },
777 { "DELE", DELE, STR1, 1, "<sp> file-name" },
778 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
779 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
780 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
781 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
782 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
783 { "SYST", SYST, ARGS, 1, "(get type of operating system)" },
784 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
785 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
786 { "NOOP", NOOP, ARGS, 1, "" },
787 { "MKD", MKD, STR1, 1, "<sp> path-name" },
788 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
789 { "RMD", RMD, STR1, 1, "<sp> path-name" },
790 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
791 { "PWD", PWD, ARGS, 1, "(return current directory)" },
792 { "XPWD", PWD, ARGS, 1, "(return current directory)" },
793 { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" },
794 { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" },
795 { "STOU", STOU, STR1, 1, "<sp> file-name" },
796 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
797 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
798 { NULL, 0, 0, 0, 0 }
799 };
800
801 struct tab sitetab[] = {
802 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
803 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
804 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
805 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
806 { NULL, 0, 0, 0, 0 }
807 };
808
809 static char *copy __P((char *));
810 static void help __P((struct tab *, char *));
811 static struct tab *
812 lookup __P((struct tab *, char *));
813 static void sizecmd __P((char *));
814 static void toolong __P((int));
815 static int yylex __P((void));
816
817 static struct tab *
818 lookup(p, cmd)
819 struct tab *p;
820 char *cmd;
821 {
822
823 for (; p->name != NULL; p++)
824 if (strcmp(cmd, p->name) == 0)
825 return (p);
826 return (0);
827 }
828
829 #include <arpa/telnet.h>
830
831 /*
832 * getline - a hacked up version of fgets to ignore TELNET escape codes.
833 */
834 char *
835 getline(s, n, iop)
836 char *s;
837 int n;
838 FILE *iop;
839 {
840 int c;
841 register char *cs;
842
843 cs = s;
844 /* tmpline may contain saved command from urgent mode interruption */
845 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
846 *cs++ = tmpline[c];
847 if (tmpline[c] == '\n') {
848 *cs++ = '\0';
849 if (debug)
850 syslog(LOG_DEBUG, "command: %s", s);
851 tmpline[0] = '\0';
852 return(s);
853 }
854 if (c == 0)
855 tmpline[0] = '\0';
856 }
857 while ((c = getc(iop)) != EOF) {
858 c &= 0377;
859 if (c == IAC) {
860 if ((c = getc(iop)) != EOF) {
861 c &= 0377;
862 switch (c) {
863 case WILL:
864 case WONT:
865 c = getc(iop);
866 printf("%c%c%c", IAC, DONT, 0377&c);
867 (void) fflush(stdout);
868 continue;
869 case DO:
870 case DONT:
871 c = getc(iop);
872 printf("%c%c%c", IAC, WONT, 0377&c);
873 (void) fflush(stdout);
874 continue;
875 case IAC:
876 break;
877 default:
878 continue; /* ignore command */
879 }
880 }
881 }
882 *cs++ = c;
883 if (--n <= 0 || c == '\n')
884 break;
885 }
886 if (c == EOF && cs == s)
887 return (NULL);
888 *cs++ = '\0';
889 if (debug) {
890 if (!guest && strncasecmp("pass ", s, 5) == 0) {
891 /* Don't syslog passwords */
892 syslog(LOG_DEBUG, "command: %.5s ???", s);
893 } else {
894 register char *cp;
895 register int len;
896
897 /* Don't syslog trailing CR-LF */
898 len = strlen(s);
899 cp = s + len - 1;
900 while (cp >= s && (*cp == '\n' || *cp == '\r')) {
901 --cp;
902 --len;
903 }
904 syslog(LOG_DEBUG, "command: %.*s", len, s);
905 }
906 }
907 return (s);
908 }
909
910 static void
911 toolong(signo)
912 int signo;
913 {
914
915 reply(421,
916 "Timeout (%d seconds): closing control connection.", timeout);
917 if (logging)
918 syslog(LOG_INFO, "User %s timed out after %d seconds",
919 (pw ? pw -> pw_name : "unknown"), timeout);
920 dologout(1);
921 }
922
923 static int
924 yylex()
925 {
926 static int cpos, state;
927 char *cp, *cp2;
928 struct tab *p;
929 int n;
930 char c;
931
932 for (;;) {
933 switch (state) {
934
935 case CMD:
936 (void) signal(SIGALRM, toolong);
937 (void) alarm((unsigned) timeout);
938 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
939 reply(221, "You could at least say goodbye.");
940 dologout(0);
941 }
942 (void) alarm(0);
943 #ifdef HASSETPROCTITLE
944 if (strncasecmp(cbuf, "PASS", 4) != NULL)
945 setproctitle("%s: %s", proctitle, cbuf);
946 #endif /* HASSETPROCTITLE */
947 if ((cp = strchr(cbuf, '\r'))) {
948 *cp++ = '\n';
949 *cp = '\0';
950 }
951 if ((cp = strpbrk(cbuf, " \n")))
952 cpos = cp - cbuf;
953 if (cpos == 0)
954 cpos = 4;
955 c = cbuf[cpos];
956 cbuf[cpos] = '\0';
957 upper(cbuf);
958 p = lookup(cmdtab, cbuf);
959 cbuf[cpos] = c;
960 if (p != 0) {
961 if (p->implemented == 0) {
962 nack(p->name);
963 longjmp(errcatch,0);
964 /* NOTREACHED */
965 }
966 state = p->state;
967 yylval.s = p->name;
968 return (p->token);
969 }
970 break;
971
972 case SITECMD:
973 if (cbuf[cpos] == ' ') {
974 cpos++;
975 return (SP);
976 }
977 cp = &cbuf[cpos];
978 if ((cp2 = strpbrk(cp, " \n")))
979 cpos = cp2 - cbuf;
980 c = cbuf[cpos];
981 cbuf[cpos] = '\0';
982 upper(cp);
983 p = lookup(sitetab, cp);
984 cbuf[cpos] = c;
985 if (p != 0) {
986 if (p->implemented == 0) {
987 state = CMD;
988 nack(p->name);
989 longjmp(errcatch,0);
990 /* NOTREACHED */
991 }
992 state = p->state;
993 yylval.s = p->name;
994 return (p->token);
995 }
996 state = CMD;
997 break;
998
999 case OSTR:
1000 if (cbuf[cpos] == '\n') {
1001 state = CMD;
1002 return (CRLF);
1003 }
1004 /* FALLTHROUGH */
1005
1006 case STR1:
1007 case ZSTR1:
1008 dostr1:
1009 if (cbuf[cpos] == ' ') {
1010 cpos++;
1011 state = state == OSTR ? STR2 : ++state;
1012 return (SP);
1013 }
1014 break;
1015
1016 case ZSTR2:
1017 if (cbuf[cpos] == '\n') {
1018 state = CMD;
1019 return (CRLF);
1020 }
1021 /* FALLTHROUGH */
1022
1023 case STR2:
1024 cp = &cbuf[cpos];
1025 n = strlen(cp);
1026 cpos += n - 1;
1027 /*
1028 * Make sure the string is nonempty and \n terminated.
1029 */
1030 if (n > 1 && cbuf[cpos] == '\n') {
1031 cbuf[cpos] = '\0';
1032 yylval.s = copy(cp);
1033 cbuf[cpos] = '\n';
1034 state = ARGS;
1035 return (STRING);
1036 }
1037 break;
1038
1039 case NSTR:
1040 if (cbuf[cpos] == ' ') {
1041 cpos++;
1042 return (SP);
1043 }
1044 if (isdigit(cbuf[cpos])) {
1045 cp = &cbuf[cpos];
1046 while (isdigit(cbuf[++cpos]))
1047 ;
1048 c = cbuf[cpos];
1049 cbuf[cpos] = '\0';
1050 yylval.i = atoi(cp);
1051 cbuf[cpos] = c;
1052 state = STR1;
1053 return (NUMBER);
1054 }
1055 state = STR1;
1056 goto dostr1;
1057
1058 case ARGS:
1059 if (isdigit(cbuf[cpos])) {
1060 cp = &cbuf[cpos];
1061 while (isdigit(cbuf[++cpos]))
1062 ;
1063 c = cbuf[cpos];
1064 cbuf[cpos] = '\0';
1065 yylval.i = atoi(cp);
1066 cbuf[cpos] = c;
1067 return (NUMBER);
1068 }
1069 switch (cbuf[cpos++]) {
1070
1071 case '\n':
1072 state = CMD;
1073 return (CRLF);
1074
1075 case ' ':
1076 return (SP);
1077
1078 case ',':
1079 return (COMMA);
1080
1081 case 'A':
1082 case 'a':
1083 return (A);
1084
1085 case 'B':
1086 case 'b':
1087 return (B);
1088
1089 case 'C':
1090 case 'c':
1091 return (C);
1092
1093 case 'E':
1094 case 'e':
1095 return (E);
1096
1097 case 'F':
1098 case 'f':
1099 return (F);
1100
1101 case 'I':
1102 case 'i':
1103 return (I);
1104
1105 case 'L':
1106 case 'l':
1107 return (L);
1108
1109 case 'N':
1110 case 'n':
1111 return (N);
1112
1113 case 'P':
1114 case 'p':
1115 return (P);
1116
1117 case 'R':
1118 case 'r':
1119 return (R);
1120
1121 case 'S':
1122 case 's':
1123 return (S);
1124
1125 case 'T':
1126 case 't':
1127 return (T);
1128
1129 }
1130 break;
1131
1132 default:
1133 fatal("Unknown state in scanner.");
1134 }
1135 yyerror((char *) 0);
1136 state = CMD;
1137 longjmp(errcatch,0);
1138 }
1139 }
1140
1141 void
1142 upper(s)
1143 char *s;
1144 {
1145 while (*s != '\0') {
1146 if (islower(*s))
1147 *s = toupper(*s);
1148 s++;
1149 }
1150 }
1151
1152 static char *
1153 copy(s)
1154 char *s;
1155 {
1156 char *p;
1157
1158 p = malloc((unsigned) strlen(s) + 1);
1159 if (p == NULL)
1160 fatal("Ran out of memory.");
1161 (void) strcpy(p, s);
1162 return (p);
1163 }
1164
1165 static void
1166 help(ctab, s)
1167 struct tab *ctab;
1168 char *s;
1169 {
1170 struct tab *c;
1171 int width, NCMDS;
1172 char *type;
1173
1174 if (ctab == sitetab)
1175 type = "SITE ";
1176 else
1177 type = "";
1178 width = 0, NCMDS = 0;
1179 for (c = ctab; c->name != NULL; c++) {
1180 int len = strlen(c->name);
1181
1182 if (len > width)
1183 width = len;
1184 NCMDS++;
1185 }
1186 width = (width + 8) &~ 7;
1187 if (s == 0) {
1188 int i, j, w;
1189 int columns, lines;
1190
1191 lreply(214, "The following %scommands are recognized %s.",
1192 type, "(* =>'s unimplemented)");
1193 columns = 76 / width;
1194 if (columns == 0)
1195 columns = 1;
1196 lines = (NCMDS + columns - 1) / columns;
1197 for (i = 0; i < lines; i++) {
1198 printf(" ");
1199 for (j = 0; j < columns; j++) {
1200 c = ctab + j * lines + i;
1201 printf("%s%c", c->name,
1202 c->implemented ? ' ' : '*');
1203 if (c + lines >= &ctab[NCMDS])
1204 break;
1205 w = strlen(c->name) + 1;
1206 while (w < width) {
1207 putchar(' ');
1208 w++;
1209 }
1210 }
1211 printf("\r\n");
1212 }
1213 (void) fflush(stdout);
1214 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1215 return;
1216 }
1217 upper(s);
1218 c = lookup(ctab, s);
1219 if (c == (struct tab *)0) {
1220 reply(502, "Unknown command %s.", s);
1221 return;
1222 }
1223 if (c->implemented)
1224 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1225 else
1226 reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1227 c->name, c->help);
1228 }
1229
1230 static void
1231 sizecmd(filename)
1232 char *filename;
1233 {
1234 switch (type) {
1235 case TYPE_L:
1236 case TYPE_I: {
1237 struct stat stbuf;
1238 if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1239 reply(550, "%s: not a plain file.", filename);
1240 else
1241 reply(213, "%qu", stbuf.st_size);
1242 break; }
1243 case TYPE_A: {
1244 FILE *fin;
1245 int c;
1246 off_t count;
1247 struct stat stbuf;
1248 fin = fopen(filename, "r");
1249 if (fin == NULL) {
1250 perror_reply(550, filename);
1251 return;
1252 }
1253 if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1254 reply(550, "%s: not a plain file.", filename);
1255 (void) fclose(fin);
1256 return;
1257 }
1258
1259 count = 0;
1260 while((c=getc(fin)) != EOF) {
1261 if (c == '\n') /* will get expanded to \r\n */
1262 count++;
1263 count++;
1264 }
1265 (void) fclose(fin);
1266
1267 reply(213, "%qd", count);
1268 break; }
1269 default:
1270 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1271 }
1272 }
1273