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