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