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