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