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