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