ftpcmd.y revision 1.12 1 /* $NetBSD: ftpcmd.y,v 1.12 1997/06/14 08:43:29 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
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.12 1997/06/14 08:43:29 lukem 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 pdata;
84 extern char hostname[], remotehost[];
85 extern char proctitle[];
86 extern int usedefault;
87 extern int transflag;
88 extern char tmpline[];
89 extern struct ftpclass curclass;
90
91 off_t restart_point;
92
93 static int cmd_type;
94 static int cmd_form;
95 static int cmd_bytesz;
96 char cbuf[512];
97 char *fromname;
98
99 %}
100
101 %union {
102 int i;
103 char *s;
104 }
105
106 %token
107 A B C E F I
108 L N P R S T
109
110 SP CRLF COMMA
111
112 USER PASS ACCT REIN QUIT PORT
113 PASV TYPE STRU MODE RETR STOR
114 APPE MLFL MAIL MSND MSOM MSAM
115 MRSQ MRCP ALLO REST RNFR RNTO
116 ABOR DELE CWD LIST NLST SITE
117 STAT HELP NOOP MKD RMD PWD
118 CDUP STOU SMNT SYST SIZE MDTM
119
120 UMASK IDLE CHMOD
121
122 LEXERR
123
124 %token <s> STRING
125 %token <i> NUMBER
126
127 %type <i> check_login check_modify octal_number byte_size
128 %type <i> struct_code mode_code type_code form_code
129 %type <s> pathstring pathname password username
130
131 %start cmd_list
132
133 %%
134
135 cmd_list
136 : /* empty */
137 | cmd_list cmd
138 {
139 fromname = (char *) 0;
140 restart_point = (off_t) 0;
141 }
142 | cmd_list rcmd
143 ;
144
145 cmd
146 : USER SP username CRLF
147 {
148 user($3);
149 free($3);
150 }
151 | PASS SP password CRLF
152 {
153 pass($3);
154 free($3);
155 }
156 | PORT SP host_port CRLF
157 {
158 usedefault = 0;
159 if (pdata >= 0) {
160 (void) close(pdata);
161 pdata = -1;
162 }
163 reply(200, "PORT command successful.");
164 }
165 | PASV CRLF
166 {
167 passive();
168 }
169 | TYPE SP type_code CRLF
170 {
171 switch (cmd_type) {
172
173 case TYPE_A:
174 if (cmd_form == FORM_N) {
175 reply(200, "Type set to A.");
176 type = cmd_type;
177 form = cmd_form;
178 } else
179 reply(504, "Form must be N.");
180 break;
181
182 case TYPE_E:
183 reply(504, "Type E not implemented.");
184 break;
185
186 case TYPE_I:
187 reply(200, "Type set to I.");
188 type = cmd_type;
189 break;
190
191 case TYPE_L:
192 #if NBBY == 8
193 if (cmd_bytesz == 8) {
194 reply(200,
195 "Type set to L (byte size 8).");
196 type = cmd_type;
197 } else
198 reply(504, "Byte size must be 8.");
199 #else /* NBBY == 8 */
200 UNIMPLEMENTED for NBBY != 8
201 #endif /* NBBY == 8 */
202 }
203 }
204 | STRU SP struct_code CRLF
205 {
206 switch ($3) {
207
208 case STRU_F:
209 reply(200, "STRU F ok.");
210 break;
211
212 default:
213 reply(504, "Unimplemented STRU type.");
214 }
215 }
216 | MODE SP mode_code CRLF
217 {
218 switch ($3) {
219
220 case MODE_S:
221 reply(200, "MODE S ok.");
222 break;
223
224 default:
225 reply(502, "Unimplemented MODE type.");
226 }
227 }
228 | ALLO SP NUMBER CRLF
229 {
230 reply(202, "ALLO command ignored.");
231 }
232 | ALLO SP NUMBER SP R SP NUMBER CRLF
233 {
234 reply(202, "ALLO command ignored.");
235 }
236 | RETR check_login SP pathname CRLF
237 {
238 if ($2 && $4 != NULL)
239 retrieve((char *) 0, $4);
240 if ($4 != NULL)
241 free($4);
242 }
243 | STOR check_login SP pathname CRLF
244 {
245 if ($2 && $4 != NULL)
246 store($4, "w", 0);
247 if ($4 != NULL)
248 free($4);
249 }
250 | APPE check_login SP pathname CRLF
251 {
252 if ($2 && $4 != NULL)
253 store($4, "a", 0);
254 if ($4 != NULL)
255 free($4);
256 }
257 | NLST check_login CRLF
258 {
259 if ($2)
260 send_file_list(".");
261 }
262 | NLST check_login SP STRING CRLF
263 {
264 if ($2 && $4 != NULL)
265 send_file_list($4);
266 if ($4 != NULL)
267 free($4);
268 }
269 | LIST check_login CRLF
270 {
271 if ($2)
272 retrieve("/bin/ls -lgA", "");
273 }
274 | LIST check_login SP pathname CRLF
275 {
276 if ($2 && $4 != NULL)
277 retrieve("/bin/ls -lgA %s", $4);
278 if ($4 != NULL)
279 free($4);
280 }
281 | STAT check_login SP pathname CRLF
282 {
283 if ($2 && $4 != NULL)
284 statfilecmd($4);
285 if ($4 != NULL)
286 free($4);
287 }
288 | STAT CRLF
289 {
290 statcmd();
291 }
292 | DELE check_modify SP pathname CRLF
293 {
294 if ($2 && $4 != NULL)
295 delete($4);
296 if ($4 != NULL)
297 free($4);
298 }
299 | RNTO SP pathname CRLF
300 {
301 if (fromname) {
302 renamecmd(fromname, $3);
303 free(fromname);
304 fromname = (char *) 0;
305 } else {
306 reply(503, "Bad sequence of commands.");
307 }
308 free($3);
309 }
310 | ABOR CRLF
311 {
312 reply(225, "ABOR command successful.");
313 }
314 | CWD check_login CRLF
315 {
316 if ($2)
317 cwd(pw->pw_dir);
318 }
319 | CWD check_login SP pathname CRLF
320 {
321 if ($2 && $4 != NULL)
322 cwd($4);
323 if ($4 != NULL)
324 free($4);
325 }
326 | HELP CRLF
327 {
328 help(cmdtab, (char *) 0);
329 }
330 | HELP SP STRING CRLF
331 {
332 char *cp = $3;
333
334 if (strncasecmp(cp, "SITE", 4) == 0) {
335 cp = $3 + 4;
336 if (*cp == ' ')
337 cp++;
338 if (*cp)
339 help(sitetab, cp);
340 else
341 help(sitetab, (char *) 0);
342 } else
343 help(cmdtab, $3);
344 }
345 | NOOP CRLF
346 {
347 reply(200, "NOOP command successful.");
348 }
349 | MKD check_modify SP pathname CRLF
350 {
351 if ($2 && $4 != NULL)
352 makedir($4);
353 if ($4 != NULL)
354 free($4);
355 }
356 | RMD check_modify SP pathname CRLF
357 {
358 if ($2 && $4 != NULL)
359 removedir($4);
360 if ($4 != NULL)
361 free($4);
362 }
363 | PWD check_login CRLF
364 {
365 if ($2)
366 pwd();
367 }
368 | CDUP check_login CRLF
369 {
370 if ($2)
371 cwd("..");
372 }
373 | SITE SP HELP CRLF
374 {
375 help(sitetab, (char *) 0);
376 }
377 | SITE SP HELP SP STRING CRLF
378 {
379 help(sitetab, $5);
380 }
381 | SITE SP UMASK check_login CRLF
382 {
383 int oldmask;
384
385 if ($4) {
386 oldmask = umask(0);
387 (void) umask(oldmask);
388 reply(200, "Current UMASK is %03o", oldmask);
389 }
390 }
391 | SITE SP UMASK check_modify SP octal_number CRLF
392 {
393 int oldmask;
394
395 if ($4) {
396 if (($6 == -1) || ($6 > 0777)) {
397 reply(501, "Bad UMASK value");
398 } else {
399 oldmask = umask($6);
400 reply(200,
401 "UMASK set to %03o (was %03o)",
402 $6, oldmask);
403 }
404 }
405 }
406 | SITE SP CHMOD check_modify SP octal_number SP pathname CRLF
407 {
408 if ($4 && ($8 != NULL)) {
409 if ($6 > 0777)
410 reply(501,
411 "CHMOD: Mode value must be between 0 and 0777");
412 else if (chmod($8, $6) < 0)
413 perror_reply(550, $8);
414 else
415 reply(200, "CHMOD command successful.");
416 }
417 if ($8 != NULL)
418 free($8);
419 }
420 | SITE SP IDLE CRLF
421 {
422 reply(200,
423 "Current IDLE time limit is %d seconds; max %d",
424 curclass.timeout, curclass.maxtimeout);
425 }
426 | SITE SP IDLE SP NUMBER CRLF
427 {
428 if ($5 < 30 || $5 > curclass.maxtimeout) {
429 reply(501,
430 "IDLE time limit must be between 30 and %d seconds",
431 curclass.maxtimeout);
432 } else {
433 curclass.timeout = $5;
434 (void) alarm(curclass.timeout);
435 reply(200,
436 "IDLE time limit set to %d seconds",
437 curclass.timeout);
438 }
439 }
440 | STOU check_login SP pathname CRLF
441 {
442 if ($2 && $4 != NULL)
443 store($4, "w", 1);
444 if ($4 != NULL)
445 free($4);
446 }
447 | SYST CRLF
448 {
449 #ifdef unix
450 #ifdef BSD
451 reply(215, "UNIX Type: L%d Version: BSD-%d",
452 NBBY, BSD);
453 #else /* BSD */
454 reply(215, "UNIX Type: L%d", NBBY);
455 #endif /* BSD */
456 #else /* unix */
457 reply(215, "UNKNOWN Type: L%d", NBBY);
458 #endif /* unix */
459 }
460
461 /*
462 * SIZE is not in RFC959, but Postel has blessed it and
463 * it will be in the updated RFC.
464 *
465 * Return size of file in a format suitable for
466 * using with RESTART (we just count bytes).
467 */
468 | SIZE check_login SP pathname CRLF
469 {
470 if ($2 && $4 != NULL)
471 sizecmd($4);
472 if ($4 != NULL)
473 free($4);
474 }
475
476 /*
477 * MDTM is not in RFC959, but Postel has blessed it and
478 * it will be in the updated RFC.
479 *
480 * Return modification time of file as an ISO 3307
481 * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
482 * where xxx is the fractional second (of any precision,
483 * not necessarily 3 digits)
484 */
485 | MDTM check_login SP pathname CRLF
486 {
487 if ($2 && $4 != NULL) {
488 struct stat stbuf;
489 if (stat($4, &stbuf) < 0)
490 reply(550, "%s: %s",
491 $4, strerror(errno));
492 else if (!S_ISREG(stbuf.st_mode)) {
493 reply(550, "%s: not a plain file.", $4);
494 } else {
495 struct tm *t;
496 t = gmtime(&stbuf.st_mtime);
497 reply(213,
498 "%04d%02d%02d%02d%02d%02d",
499 1900 + t->tm_year,
500 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
665 * others.
666 */
667 if (logged_in && $1 && *$1 == '~') {
668 glob_t gl;
669 int flags =
670 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
671
672 if ($1[1] == '\0')
673 $$ = strdup(pw->pw_dir);
674 else {
675 memset(&gl, 0, sizeof(gl));
676 if (glob($1, flags, NULL, &gl) ||
677 gl.gl_pathc == 0) {
678 reply(550, "not found");
679 $$ = NULL;
680 } else
681 $$ = strdup(gl.gl_pathv[0]);
682 globfree(&gl);
683 }
684 free($1);
685 } else
686 $$ = $1;
687 }
688 ;
689
690 pathstring
691 : STRING
692 ;
693
694 octal_number
695 : NUMBER
696 {
697 int ret, dec, multby, digit;
698
699 /*
700 * Convert a number that was read as decimal number
701 * to what it would be if it had been read as octal.
702 */
703 dec = $1;
704 multby = 1;
705 ret = 0;
706 while (dec) {
707 digit = dec%10;
708 if (digit > 7) {
709 ret = -1;
710 break;
711 }
712 ret += digit * multby;
713 multby *= 8;
714 dec /= 10;
715 }
716 $$ = ret;
717 }
718 ;
719
720
721 check_login
722 : /* empty */
723 {
724 if (logged_in)
725 $$ = 1;
726 else {
727 reply(530, "Please login with USER and PASS.");
728 $$ = 0;
729 }
730 }
731 ;
732 check_modify
733 : /* empty */
734 {
735 if (logged_in) {
736 if (curclass.modify) {
737 $$ = 1;
738 } else
739 reply(502,
740 "No permission to use this command.");
741 $$ = 0;
742 } else {
743 reply(530, "Please login with USER and PASS.");
744 $$ = 0;
745 }
746 }
747
748 %%
749
750 extern jmp_buf errcatch;
751
752 #define CMD 0 /* beginning of command */
753 #define ARGS 1 /* expect miscellaneous arguments */
754 #define STR1 2 /* expect SP followed by STRING */
755 #define STR2 3 /* expect STRING */
756 #define OSTR 4 /* optional SP then STRING */
757 #define ZSTR1 5 /* SP then optional STRING */
758 #define ZSTR2 6 /* optional STRING after SP */
759 #define SITECMD 7 /* SITE command */
760 #define NSTR 8 /* Number followed by a string */
761
762 struct tab {
763 char *name;
764 short token;
765 short state;
766 short implemented; /* 1 if command is implemented */
767 char *help;
768 };
769
770 struct tab cmdtab[] = { /* In order defined in RFC 765 */
771 { "USER", USER, STR1, 1, "<sp> username" },
772 { "PASS", PASS, ZSTR1, 1, "<sp> password" },
773 { "ACCT", ACCT, STR1, 0, "(specify account)" },
774 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
775 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" },
776 { "QUIT", QUIT, ARGS, 1, "(terminate service)", },
777 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" },
778 { "PASV", PASV, ARGS, 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 { "APPE", APPE, STR1, 1, "<sp> file-name" },
785 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
786 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
787 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
788 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
789 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
790 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
791 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
792 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
793 { "REST", REST, ARGS, 1, "<sp> offset (restart command)" },
794 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
795 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
796 { "ABOR", ABOR, ARGS, 1, "(abort operation)" },
797 { "DELE", DELE, STR1, 1, "<sp> file-name" },
798 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
799 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
800 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
801 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
802 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
803 { "SYST", SYST, ARGS, 1, "(get type of operating system)" },
804 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
805 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
806 { "NOOP", NOOP, ARGS, 1, "" },
807 { "MKD", MKD, STR1, 1, "<sp> path-name" },
808 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
809 { "RMD", RMD, STR1, 1, "<sp> path-name" },
810 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
811 { "PWD", PWD, ARGS, 1, "(return current directory)" },
812 { "XPWD", PWD, ARGS, 1, "(return current directory)" },
813 { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" },
814 { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" },
815 { "STOU", STOU, STR1, 1, "<sp> file-name" },
816 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
817 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
818 { NULL, 0, 0, 0, 0 }
819 };
820
821 struct tab sitetab[] = {
822 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
823 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
824 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
825 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
826 { NULL, 0, 0, 0, 0 }
827 };
828
829 static char *copy __P((char *));
830 static void help __P((struct tab *, char *));
831 static struct tab *
832 lookup __P((struct tab *, char *));
833 static void sizecmd __P((char *));
834 static void toolong __P((int));
835 static int yylex __P((void));
836
837 static struct tab *
838 lookup(p, cmd)
839 struct tab *p;
840 char *cmd;
841 {
842
843 for (; p->name != NULL; p++)
844 if (strcmp(cmd, p->name) == 0)
845 return (p);
846 return (0);
847 }
848
849 #include <arpa/telnet.h>
850
851 /*
852 * getline - a hacked up version of fgets to ignore TELNET escape codes.
853 */
854 char *
855 getline(s, n, iop)
856 char *s;
857 int n;
858 FILE *iop;
859 {
860 int c;
861 register char *cs;
862
863 cs = s;
864 /* tmpline may contain saved command from urgent mode interruption */
865 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
866 *cs++ = tmpline[c];
867 if (tmpline[c] == '\n') {
868 *cs++ = '\0';
869 if (debug)
870 syslog(LOG_DEBUG, "command: %s", s);
871 tmpline[0] = '\0';
872 return(s);
873 }
874 if (c == 0)
875 tmpline[0] = '\0';
876 }
877 while ((c = getc(iop)) != EOF) {
878 c &= 0377;
879 if (c == IAC) {
880 if ((c = getc(iop)) != EOF) {
881 c &= 0377;
882 switch (c) {
883 case WILL:
884 case WONT:
885 c = getc(iop);
886 printf("%c%c%c", IAC, DONT, 0377&c);
887 (void) fflush(stdout);
888 continue;
889 case DO:
890 case DONT:
891 c = getc(iop);
892 printf("%c%c%c", IAC, WONT, 0377&c);
893 (void) fflush(stdout);
894 continue;
895 case IAC:
896 break;
897 default:
898 continue; /* ignore command */
899 }
900 }
901 }
902 *cs++ = c;
903 if (--n <= 0 || c == '\n')
904 break;
905 }
906 if (c == EOF && cs == s)
907 return (NULL);
908 *cs++ = '\0';
909 if (debug) {
910 if (!guest && strncasecmp("pass ", s, 5) == 0) {
911 /* Don't syslog passwords */
912 syslog(LOG_DEBUG, "command: %.5s ???", s);
913 } else {
914 register char *cp;
915 register int len;
916
917 /* Don't syslog trailing CR-LF */
918 len = strlen(s);
919 cp = s + len - 1;
920 while (cp >= s && (*cp == '\n' || *cp == '\r')) {
921 --cp;
922 --len;
923 }
924 syslog(LOG_DEBUG, "command: %.*s", len, s);
925 }
926 }
927 return (s);
928 }
929
930 static void
931 toolong(signo)
932 int signo;
933 {
934
935 reply(421,
936 "Timeout (%d seconds): closing control connection.",
937 curclass.timeout);
938 if (logging)
939 syslog(LOG_INFO, "User %s timed out after %d seconds",
940 (pw ? pw -> pw_name : "unknown"), curclass.timeout);
941 dologout(1);
942 }
943
944 static int
945 yylex()
946 {
947 static int cpos, state;
948 char *cp, *cp2;
949 struct tab *p;
950 int n;
951 char c;
952
953 for (;;) {
954 switch (state) {
955
956 case CMD:
957 (void) signal(SIGALRM, toolong);
958 (void) alarm(curclass.timeout);
959 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
960 reply(221, "You could at least say goodbye.");
961 dologout(0);
962 }
963 (void) alarm(0);
964 #ifdef HASSETPROCTITLE
965 if (strncasecmp(cbuf, "PASS", 4) != 0)
966 setproctitle("%s: %s", proctitle, cbuf);
967 #endif /* HASSETPROCTITLE */
968 if ((cp = strchr(cbuf, '\r'))) {
969 *cp++ = '\n';
970 *cp = '\0';
971 }
972 if ((cp = strpbrk(cbuf, " \n")))
973 cpos = cp - cbuf;
974 if (cpos == 0)
975 cpos = 4;
976 c = cbuf[cpos];
977 cbuf[cpos] = '\0';
978 upper(cbuf);
979 p = lookup(cmdtab, cbuf);
980 cbuf[cpos] = c;
981 if (p != 0) {
982 if (p->implemented == 0) {
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 break;
992
993 case SITECMD:
994 if (cbuf[cpos] == ' ') {
995 cpos++;
996 return (SP);
997 }
998 cp = &cbuf[cpos];
999 if ((cp2 = strpbrk(cp, " \n")))
1000 cpos = cp2 - cbuf;
1001 c = cbuf[cpos];
1002 cbuf[cpos] = '\0';
1003 upper(cp);
1004 p = lookup(sitetab, cp);
1005 cbuf[cpos] = c;
1006 if (p != 0) {
1007 if (p->implemented == 0) {
1008 state = CMD;
1009 nack(p->name);
1010 longjmp(errcatch,0);
1011 /* NOTREACHED */
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 default:
1154 fatal("Unknown state in scanner.");
1155 }
1156 yyerror((char *) 0);
1157 state = CMD;
1158 longjmp(errcatch,0);
1159 }
1160 }
1161
1162 void
1163 upper(s)
1164 char *s;
1165 {
1166 while (*s != '\0') {
1167 *s = toupper(*s);
1168 s++;
1169 }
1170 }
1171
1172 static char *
1173 copy(s)
1174 char *s;
1175 {
1176 char *p;
1177
1178 p = malloc((unsigned) strlen(s) + 1);
1179 if (p == NULL)
1180 fatal("Ran out of memory.");
1181 (void) strcpy(p, s);
1182 return (p);
1183 }
1184
1185 static void
1186 help(ctab, s)
1187 struct tab *ctab;
1188 char *s;
1189 {
1190 struct tab *c;
1191 int width, NCMDS;
1192 char *type;
1193
1194 if (ctab == sitetab)
1195 type = "SITE ";
1196 else
1197 type = "";
1198 width = 0, NCMDS = 0;
1199 for (c = ctab; c->name != NULL; c++) {
1200 int len = strlen(c->name);
1201
1202 if (len > width)
1203 width = len;
1204 NCMDS++;
1205 }
1206 width = (width + 8) &~ 7;
1207 if (s == 0) {
1208 int i, j, w;
1209 int columns, lines;
1210
1211 lreply(214, "The following %scommands are recognized %s.",
1212 type, "(* =>'s unimplemented)");
1213 columns = 76 / width;
1214 if (columns == 0)
1215 columns = 1;
1216 lines = (NCMDS + columns - 1) / columns;
1217 for (i = 0; i < lines; i++) {
1218 printf(" ");
1219 for (j = 0; j < columns; j++) {
1220 c = ctab + j * lines + i;
1221 printf("%s%c", c->name,
1222 c->implemented ? ' ' : '*');
1223 if (c + lines >= &ctab[NCMDS])
1224 break;
1225 w = strlen(c->name) + 1;
1226 while (w < width) {
1227 putchar(' ');
1228 w++;
1229 }
1230 }
1231 printf("\r\n");
1232 }
1233 (void) fflush(stdout);
1234 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1235 return;
1236 }
1237 upper(s);
1238 c = lookup(ctab, s);
1239 if (c == (struct tab *)0) {
1240 reply(502, "Unknown command %s.", s);
1241 return;
1242 }
1243 if (c->implemented)
1244 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1245 else
1246 reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1247 c->name, c->help);
1248 }
1249
1250 static void
1251 sizecmd(filename)
1252 char *filename;
1253 {
1254 switch (type) {
1255 case TYPE_L:
1256 case TYPE_I: {
1257 struct stat stbuf;
1258 if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1259 reply(550, "%s: not a plain file.", filename);
1260 else
1261 reply(213, "%qu", stbuf.st_size);
1262 break; }
1263 case TYPE_A: {
1264 FILE *fin;
1265 int c;
1266 off_t count;
1267 struct stat stbuf;
1268 fin = fopen(filename, "r");
1269 if (fin == NULL) {
1270 perror_reply(550, filename);
1271 return;
1272 }
1273 if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1274 reply(550, "%s: not a plain file.", filename);
1275 (void) fclose(fin);
1276 return;
1277 }
1278
1279 count = 0;
1280 while((c=getc(fin)) != EOF) {
1281 if (c == '\n') /* will get expanded to \r\n */
1282 count++;
1283 count++;
1284 }
1285 (void) fclose(fin);
1286
1287 reply(213, "%qd", count);
1288 break; }
1289 default:
1290 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1291 }
1292 }
1293