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