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