ftpcmd.y revision 1.14 1 /* $NetBSD: ftpcmd.y,v 1.14 1997/06/24 08:49:27 hannken 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.14 1997/06/24 08:49:27 hannken 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
61 #include <ctype.h>
62 #include <errno.h>
63 #include <glob.h>
64 #include <pwd.h>
65 #include <setjmp.h>
66 #include <signal.h>
67 #include <stdio.h>
68 #include <stdlib.h>
69 #include <string.h>
70 #include <syslog.h>
71 #include <time.h>
72 #include <unistd.h>
73
74 #include "extern.h"
75
76 extern struct sockaddr_in data_dest;
77 extern int logged_in;
78 extern struct passwd *pw;
79 extern int guest;
80 extern int logging;
81 extern int type;
82 extern int form;
83 extern int debug;
84 extern int pdata;
85 extern char hostname[], remotehost[];
86 extern char proctitle[];
87 extern int usedefault;
88 extern int transflag;
89 extern char tmpline[];
90 extern struct ftpclass curclass;
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_modify 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_modify 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_modify SP pathname CRLF
351 {
352 if ($2 && $4 != NULL)
353 makedir($4);
354 if ($4 != NULL)
355 free($4);
356 }
357 | RMD check_modify 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_modify 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_modify 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 curclass.timeout, curclass.maxtimeout);
426 }
427 | SITE SP IDLE SP NUMBER CRLF
428 {
429 if ($5 < 30 || $5 > curclass.maxtimeout) {
430 reply(501,
431 "IDLE time limit must be between 30 and %d seconds",
432 curclass.maxtimeout);
433 } else {
434 curclass.timeout = $5;
435 (void) alarm(curclass.timeout);
436 reply(200,
437 "IDLE time limit set to %d seconds",
438 curclass.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 restart_point = (off_t) 0;
522 if ($2 && $4) {
523 fromname = renamefrom($4);
524 if (fromname == (char *) 0 && $4) {
525 free($4);
526 }
527 }
528 }
529 | REST SP byte_size CRLF
530 {
531 fromname = (char *) 0;
532 restart_point = $3; /* XXX $3 is only "int" */
533 reply(350, "Restarting at %qd. %s", restart_point,
534 "Send STORE or RETRIEVE to initiate transfer.");
535 }
536 ;
537
538 username
539 : STRING
540 ;
541
542 password
543 : /* empty */
544 {
545 $$ = (char *)calloc(1, sizeof(char));
546 }
547 | STRING
548 ;
549
550 byte_size
551 : NUMBER
552 ;
553
554 host_port
555 : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
556 NUMBER COMMA NUMBER
557 {
558 char *a, *p;
559
560 data_dest.sin_len = sizeof(struct sockaddr_in);
561 data_dest.sin_family = AF_INET;
562 p = (char *)&data_dest.sin_port;
563 p[0] = $9; p[1] = $11;
564 a = (char *)&data_dest.sin_addr;
565 a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
566 }
567 ;
568
569 form_code
570 : N
571 {
572 $$ = FORM_N;
573 }
574 | T
575 {
576 $$ = FORM_T;
577 }
578 | C
579 {
580 $$ = FORM_C;
581 }
582 ;
583
584 type_code
585 : A
586 {
587 cmd_type = TYPE_A;
588 cmd_form = FORM_N;
589 }
590 | A SP form_code
591 {
592 cmd_type = TYPE_A;
593 cmd_form = $3;
594 }
595 | E
596 {
597 cmd_type = TYPE_E;
598 cmd_form = FORM_N;
599 }
600 | E SP form_code
601 {
602 cmd_type = TYPE_E;
603 cmd_form = $3;
604 }
605 | I
606 {
607 cmd_type = TYPE_I;
608 }
609 | L
610 {
611 cmd_type = TYPE_L;
612 cmd_bytesz = NBBY;
613 }
614 | L SP byte_size
615 {
616 cmd_type = TYPE_L;
617 cmd_bytesz = $3;
618 }
619 /* this is for a bug in the BBN ftp */
620 | L byte_size
621 {
622 cmd_type = TYPE_L;
623 cmd_bytesz = $2;
624 }
625 ;
626
627 struct_code
628 : F
629 {
630 $$ = STRU_F;
631 }
632 | R
633 {
634 $$ = STRU_R;
635 }
636 | P
637 {
638 $$ = STRU_P;
639 }
640 ;
641
642 mode_code
643 : S
644 {
645 $$ = MODE_S;
646 }
647 | B
648 {
649 $$ = MODE_B;
650 }
651 | C
652 {
653 $$ = MODE_C;
654 }
655 ;
656
657 pathname
658 : pathstring
659 {
660 /*
661 * Problem: this production is used for all pathname
662 * processing, but only gives a 550 error reply.
663 * This is a valid reply in some cases but not in
664 * others.
665 */
666 if (logged_in && $1 && *$1 == '~') {
667 glob_t gl;
668 int flags =
669 GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE;
670
671 if ($1[1] == '\0')
672 $$ = strdup(pw->pw_dir);
673 else {
674 memset(&gl, 0, sizeof(gl));
675 if (glob($1, flags, NULL, &gl) ||
676 gl.gl_pathc == 0) {
677 reply(550, "not found");
678 $$ = NULL;
679 } else
680 $$ = strdup(gl.gl_pathv[0]);
681 globfree(&gl);
682 }
683 free($1);
684 } else
685 $$ = $1;
686 }
687 ;
688
689 pathstring
690 : STRING
691 ;
692
693 octal_number
694 : NUMBER
695 {
696 int ret, dec, multby, digit;
697
698 /*
699 * Convert a number that was read as decimal number
700 * to what it would be if it had been read as octal.
701 */
702 dec = $1;
703 multby = 1;
704 ret = 0;
705 while (dec) {
706 digit = dec%10;
707 if (digit > 7) {
708 ret = -1;
709 break;
710 }
711 ret += digit * multby;
712 multby *= 8;
713 dec /= 10;
714 }
715 $$ = ret;
716 }
717 ;
718
719
720 check_login
721 : /* empty */
722 {
723 if (logged_in)
724 $$ = 1;
725 else {
726 reply(530, "Please login with USER and PASS.");
727 $$ = 0;
728 }
729 }
730 ;
731 check_modify
732 : /* empty */
733 {
734 if (logged_in) {
735 if (curclass.modify) {
736 $$ = 1;
737 } else {
738 reply(502,
739 "No permission to use this command.");
740 $$ = 0;
741 }
742 } else {
743 reply(530, "Please login with USER and PASS.");
744 $$ = 0;
745 }
746 }
747
748 %%
749
750 extern jmp_buf errcatch;
751
752 #define CMD 0 /* beginning of command */
753 #define ARGS 1 /* expect miscellaneous arguments */
754 #define STR1 2 /* expect SP followed by STRING */
755 #define STR2 3 /* expect STRING */
756 #define OSTR 4 /* optional SP then STRING */
757 #define ZSTR1 5 /* SP then optional STRING */
758 #define ZSTR2 6 /* optional STRING after SP */
759 #define SITECMD 7 /* SITE command */
760 #define NSTR 8 /* Number followed by a string */
761
762 struct tab {
763 char *name;
764 short token;
765 short state;
766 short implemented; /* 1 if command is implemented */
767 char *help;
768 };
769
770 struct tab cmdtab[] = { /* In order defined in RFC 765 */
771 { "USER", USER, STR1, 1, "<sp> username" },
772 { "PASS", PASS, ZSTR1, 1, "<sp> password" },
773 { "ACCT", ACCT, STR1, 0, "(specify account)" },
774 { "SMNT", SMNT, ARGS, 0, "(structure mount)" },
775 { "REIN", REIN, ARGS, 0, "(reinitialize server state)" },
776 { "QUIT", QUIT, ARGS, 1, "(terminate service)", },
777 { "PORT", PORT, ARGS, 1, "<sp> b0, b1, b2, b3, b4" },
778 { "PASV", PASV, ARGS, 1, "(set server in passive mode)" },
779 { "TYPE", TYPE, ARGS, 1, "<sp> [ A | E | I | L ]" },
780 { "STRU", STRU, ARGS, 1, "(specify file structure)" },
781 { "MODE", MODE, ARGS, 1, "(specify transfer mode)" },
782 { "RETR", RETR, STR1, 1, "<sp> file-name" },
783 { "STOR", STOR, STR1, 1, "<sp> file-name" },
784 { "APPE", APPE, STR1, 1, "<sp> file-name" },
785 { "MLFL", MLFL, OSTR, 0, "(mail file)" },
786 { "MAIL", MAIL, OSTR, 0, "(mail to user)" },
787 { "MSND", MSND, OSTR, 0, "(mail send to terminal)" },
788 { "MSOM", MSOM, OSTR, 0, "(mail send to terminal or mailbox)" },
789 { "MSAM", MSAM, OSTR, 0, "(mail send to terminal and mailbox)" },
790 { "MRSQ", MRSQ, OSTR, 0, "(mail recipient scheme question)" },
791 { "MRCP", MRCP, STR1, 0, "(mail recipient)" },
792 { "ALLO", ALLO, ARGS, 1, "allocate storage (vacuously)" },
793 { "REST", REST, ARGS, 1, "<sp> offset (restart command)" },
794 { "RNFR", RNFR, STR1, 1, "<sp> file-name" },
795 { "RNTO", RNTO, STR1, 1, "<sp> file-name" },
796 { "ABOR", ABOR, ARGS, 1, "(abort operation)" },
797 { "DELE", DELE, STR1, 1, "<sp> file-name" },
798 { "CWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
799 { "XCWD", CWD, OSTR, 1, "[ <sp> directory-name ]" },
800 { "LIST", LIST, OSTR, 1, "[ <sp> path-name ]" },
801 { "NLST", NLST, OSTR, 1, "[ <sp> path-name ]" },
802 { "SITE", SITE, SITECMD, 1, "site-cmd [ <sp> arguments ]" },
803 { "SYST", SYST, ARGS, 1, "(get type of operating system)" },
804 { "STAT", STAT, OSTR, 1, "[ <sp> path-name ]" },
805 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
806 { "NOOP", NOOP, ARGS, 1, "" },
807 { "MKD", MKD, STR1, 1, "<sp> path-name" },
808 { "XMKD", MKD, STR1, 1, "<sp> path-name" },
809 { "RMD", RMD, STR1, 1, "<sp> path-name" },
810 { "XRMD", RMD, STR1, 1, "<sp> path-name" },
811 { "PWD", PWD, ARGS, 1, "(return current directory)" },
812 { "XPWD", PWD, ARGS, 1, "(return current directory)" },
813 { "CDUP", CDUP, ARGS, 1, "(change to parent directory)" },
814 { "XCUP", CDUP, ARGS, 1, "(change to parent directory)" },
815 { "STOU", STOU, STR1, 1, "<sp> file-name" },
816 { "SIZE", SIZE, OSTR, 1, "<sp> path-name" },
817 { "MDTM", MDTM, OSTR, 1, "<sp> path-name" },
818 { NULL, 0, 0, 0, 0 }
819 };
820
821 struct tab sitetab[] = {
822 { "UMASK", UMASK, ARGS, 1, "[ <sp> umask ]" },
823 { "IDLE", IDLE, ARGS, 1, "[ <sp> maximum-idle-time ]" },
824 { "CHMOD", CHMOD, NSTR, 1, "<sp> mode <sp> file-name" },
825 { "HELP", HELP, OSTR, 1, "[ <sp> <string> ]" },
826 { NULL, 0, 0, 0, 0 }
827 };
828
829 static char *copy __P((char *));
830 static void help __P((struct tab *, char *));
831 static struct tab *
832 lookup __P((struct tab *, char *));
833 static void sizecmd __P((char *));
834 static void toolong __P((int));
835 static int yylex __P((void));
836
837 static struct tab *
838 lookup(p, cmd)
839 struct tab *p;
840 char *cmd;
841 {
842
843 for (; p->name != NULL; p++)
844 if (strcmp(cmd, p->name) == 0)
845 return (p);
846 return (0);
847 }
848
849 #include <arpa/telnet.h>
850
851 /*
852 * getline - a hacked up version of fgets to ignore TELNET escape codes.
853 */
854 char *
855 getline(s, n, iop)
856 char *s;
857 int n;
858 FILE *iop;
859 {
860 int c;
861 register char *cs;
862
863 cs = s;
864 /* tmpline may contain saved command from urgent mode interruption */
865 for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
866 *cs++ = tmpline[c];
867 if (tmpline[c] == '\n') {
868 *cs++ = '\0';
869 if (debug)
870 syslog(LOG_DEBUG, "command: %s", s);
871 tmpline[0] = '\0';
872 return(s);
873 }
874 if (c == 0)
875 tmpline[0] = '\0';
876 }
877 while ((c = getc(iop)) != EOF) {
878 c &= 0377;
879 if (c == IAC) {
880 if ((c = getc(iop)) != EOF) {
881 c &= 0377;
882 switch (c) {
883 case WILL:
884 case WONT:
885 c = getc(iop);
886 printf("%c%c%c", IAC, DONT, 0377&c);
887 (void) fflush(stdout);
888 continue;
889 case DO:
890 case DONT:
891 c = getc(iop);
892 printf("%c%c%c", IAC, WONT, 0377&c);
893 (void) fflush(stdout);
894 continue;
895 case IAC:
896 break;
897 default:
898 continue; /* ignore command */
899 }
900 }
901 }
902 *cs++ = c;
903 if (--n <= 0 || c == '\n')
904 break;
905 }
906 if (c == EOF && cs == s)
907 return (NULL);
908 *cs++ = '\0';
909 if (debug) {
910 if (!guest && strncasecmp("pass ", s, 5) == 0) {
911 /* Don't syslog passwords */
912 syslog(LOG_DEBUG, "command: %.5s ???", s);
913 } else {
914 register char *cp;
915 register int len;
916
917 /* Don't syslog trailing CR-LF */
918 len = strlen(s);
919 cp = s + len - 1;
920 while (cp >= s && (*cp == '\n' || *cp == '\r')) {
921 --cp;
922 --len;
923 }
924 syslog(LOG_DEBUG, "command: %.*s", len, s);
925 }
926 }
927 return (s);
928 }
929
930 static void
931 toolong(signo)
932 int signo;
933 {
934
935 reply(421,
936 "Timeout (%d seconds): closing control connection.",
937 curclass.timeout);
938 if (logging)
939 syslog(LOG_INFO, "User %s timed out after %d seconds",
940 (pw ? pw -> pw_name : "unknown"), curclass.timeout);
941 dologout(1);
942 }
943
944 static int
945 yylex()
946 {
947 static int cpos, state;
948 char *cp, *cp2;
949 struct tab *p;
950 int n;
951 char c;
952
953 for (;;) {
954 switch (state) {
955
956 case CMD:
957 (void) signal(SIGALRM, toolong);
958 (void) alarm(curclass.timeout);
959 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
960 reply(221, "You could at least say goodbye.");
961 dologout(0);
962 }
963 (void) alarm(0);
964 #ifdef HASSETPROCTITLE
965 if (strncasecmp(cbuf, "PASS", 4) != 0)
966 setproctitle("%s: %s", proctitle, cbuf);
967 #endif /* HASSETPROCTITLE */
968 if ((cp = strchr(cbuf, '\r'))) {
969 *cp++ = '\n';
970 *cp = '\0';
971 }
972 if ((cp = strpbrk(cbuf, " \n")))
973 cpos = cp - cbuf;
974 if (cpos == 0)
975 cpos = 4;
976 c = cbuf[cpos];
977 cbuf[cpos] = '\0';
978 upper(cbuf);
979 p = lookup(cmdtab, cbuf);
980 cbuf[cpos] = c;
981 if (p != 0) {
982 if (p->implemented == 0) {
983 nack(p->name);
984 longjmp(errcatch,0);
985 /* NOTREACHED */
986 }
987 state = p->state;
988 yylval.s = p->name;
989 return (p->token);
990 }
991 break;
992
993 case SITECMD:
994 if (cbuf[cpos] == ' ') {
995 cpos++;
996 return (SP);
997 }
998 cp = &cbuf[cpos];
999 if ((cp2 = strpbrk(cp, " \n")))
1000 cpos = cp2 - cbuf;
1001 c = cbuf[cpos];
1002 cbuf[cpos] = '\0';
1003 upper(cp);
1004 p = lookup(sitetab, cp);
1005 cbuf[cpos] = c;
1006 if (p != 0) {
1007 if (p->implemented == 0) {
1008 state = CMD;
1009 nack(p->name);
1010 longjmp(errcatch,0);
1011 /* NOTREACHED */
1012 }
1013 state = p->state;
1014 yylval.s = p->name;
1015 return (p->token);
1016 }
1017 state = CMD;
1018 break;
1019
1020 case OSTR:
1021 if (cbuf[cpos] == '\n') {
1022 state = CMD;
1023 return (CRLF);
1024 }
1025 /* FALLTHROUGH */
1026
1027 case STR1:
1028 case ZSTR1:
1029 dostr1:
1030 if (cbuf[cpos] == ' ') {
1031 cpos++;
1032 state = state == OSTR ? STR2 : ++state;
1033 return (SP);
1034 }
1035 break;
1036
1037 case ZSTR2:
1038 if (cbuf[cpos] == '\n') {
1039 state = CMD;
1040 return (CRLF);
1041 }
1042 /* FALLTHROUGH */
1043
1044 case STR2:
1045 cp = &cbuf[cpos];
1046 n = strlen(cp);
1047 cpos += n - 1;
1048 /*
1049 * Make sure the string is nonempty and \n terminated.
1050 */
1051 if (n > 1 && cbuf[cpos] == '\n') {
1052 cbuf[cpos] = '\0';
1053 yylval.s = copy(cp);
1054 cbuf[cpos] = '\n';
1055 state = ARGS;
1056 return (STRING);
1057 }
1058 break;
1059
1060 case NSTR:
1061 if (cbuf[cpos] == ' ') {
1062 cpos++;
1063 return (SP);
1064 }
1065 if (isdigit(cbuf[cpos])) {
1066 cp = &cbuf[cpos];
1067 while (isdigit(cbuf[++cpos]))
1068 ;
1069 c = cbuf[cpos];
1070 cbuf[cpos] = '\0';
1071 yylval.i = atoi(cp);
1072 cbuf[cpos] = c;
1073 state = STR1;
1074 return (NUMBER);
1075 }
1076 state = STR1;
1077 goto dostr1;
1078
1079 case ARGS:
1080 if (isdigit(cbuf[cpos])) {
1081 cp = &cbuf[cpos];
1082 while (isdigit(cbuf[++cpos]))
1083 ;
1084 c = cbuf[cpos];
1085 cbuf[cpos] = '\0';
1086 yylval.i = atoi(cp);
1087 cbuf[cpos] = c;
1088 return (NUMBER);
1089 }
1090 switch (cbuf[cpos++]) {
1091
1092 case '\n':
1093 state = CMD;
1094 return (CRLF);
1095
1096 case ' ':
1097 return (SP);
1098
1099 case ',':
1100 return (COMMA);
1101
1102 case 'A':
1103 case 'a':
1104 return (A);
1105
1106 case 'B':
1107 case 'b':
1108 return (B);
1109
1110 case 'C':
1111 case 'c':
1112 return (C);
1113
1114 case 'E':
1115 case 'e':
1116 return (E);
1117
1118 case 'F':
1119 case 'f':
1120 return (F);
1121
1122 case 'I':
1123 case 'i':
1124 return (I);
1125
1126 case 'L':
1127 case 'l':
1128 return (L);
1129
1130 case 'N':
1131 case 'n':
1132 return (N);
1133
1134 case 'P':
1135 case 'p':
1136 return (P);
1137
1138 case 'R':
1139 case 'r':
1140 return (R);
1141
1142 case 'S':
1143 case 's':
1144 return (S);
1145
1146 case 'T':
1147 case 't':
1148 return (T);
1149
1150 }
1151 break;
1152
1153 default:
1154 fatal("Unknown state in scanner.");
1155 }
1156 yyerror((char *) 0);
1157 state = CMD;
1158 longjmp(errcatch,0);
1159 }
1160 }
1161
1162 void
1163 upper(s)
1164 char *s;
1165 {
1166 while (*s != '\0') {
1167 *s = toupper(*s);
1168 s++;
1169 }
1170 }
1171
1172 static char *
1173 copy(s)
1174 char *s;
1175 {
1176 char *p;
1177
1178 p = malloc((unsigned) strlen(s) + 1);
1179 if (p == NULL)
1180 fatal("Ran out of memory.");
1181 (void) strcpy(p, s);
1182 return (p);
1183 }
1184
1185 static void
1186 help(ctab, s)
1187 struct tab *ctab;
1188 char *s;
1189 {
1190 struct tab *c;
1191 int width, NCMDS;
1192 char *type;
1193
1194 if (ctab == sitetab)
1195 type = "SITE ";
1196 else
1197 type = "";
1198 width = 0, NCMDS = 0;
1199 for (c = ctab; c->name != NULL; c++) {
1200 int len = strlen(c->name);
1201
1202 if (len > width)
1203 width = len;
1204 NCMDS++;
1205 }
1206 width = (width + 8) &~ 7;
1207 if (s == 0) {
1208 int i, j, w;
1209 int columns, lines;
1210
1211 lreply(214, "The following %scommands are recognized %s.",
1212 type, "(* =>'s unimplemented)");
1213 columns = 76 / width;
1214 if (columns == 0)
1215 columns = 1;
1216 lines = (NCMDS + columns - 1) / columns;
1217 for (i = 0; i < lines; i++) {
1218 printf(" ");
1219 for (j = 0; j < columns; j++) {
1220 c = ctab + j * lines + i;
1221 printf("%s%c", c->name,
1222 c->implemented ? ' ' : '*');
1223 if (c + lines >= &ctab[NCMDS])
1224 break;
1225 w = strlen(c->name) + 1;
1226 while (w < width) {
1227 putchar(' ');
1228 w++;
1229 }
1230 }
1231 printf("\r\n");
1232 }
1233 (void) fflush(stdout);
1234 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1235 return;
1236 }
1237 upper(s);
1238 c = lookup(ctab, s);
1239 if (c == (struct tab *)0) {
1240 reply(502, "Unknown command %s.", s);
1241 return;
1242 }
1243 if (c->implemented)
1244 reply(214, "Syntax: %s%s %s", type, c->name, c->help);
1245 else
1246 reply(214, "%s%-*s\t%s; unimplemented.", type, width,
1247 c->name, c->help);
1248 }
1249
1250 static void
1251 sizecmd(filename)
1252 char *filename;
1253 {
1254 switch (type) {
1255 case TYPE_L:
1256 case TYPE_I: {
1257 struct stat stbuf;
1258 if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
1259 reply(550, "%s: not a plain file.", filename);
1260 else
1261 reply(213, "%qu", stbuf.st_size);
1262 break; }
1263 case TYPE_A: {
1264 FILE *fin;
1265 int c;
1266 off_t count;
1267 struct stat stbuf;
1268 fin = fopen(filename, "r");
1269 if (fin == NULL) {
1270 perror_reply(550, filename);
1271 return;
1272 }
1273 if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
1274 reply(550, "%s: not a plain file.", filename);
1275 (void) fclose(fin);
1276 return;
1277 }
1278
1279 count = 0;
1280 while((c=getc(fin)) != EOF) {
1281 if (c == '\n') /* will get expanded to \r\n */
1282 count++;
1283 count++;
1284 }
1285 (void) fclose(fin);
1286
1287 reply(213, "%qd", count);
1288 break; }
1289 default:
1290 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
1291 }
1292 }
1293