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