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