68a598be718e68515222af7fef93970d8fb31f9d
[dragonfly.git] / contrib / lukemftpd / src / ftpcmd.y
1 /*      $NetBSD: ftpcmd.y,v 1.66 2001/12/01 10:25:30 lukem Exp $        */
2
3 /*-
4  * Copyright (c) 1997-2001 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Luke Mewburn.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *        This product includes software developed by the NetBSD
21  *        Foundation, Inc. and its contributors.
22  * 4. Neither the name of The NetBSD Foundation nor the names of its
23  *    contributors may be used to endorse or promote products derived
24  *    from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38
39 /*
40  * Copyright (c) 1985, 1988, 1993, 1994
41  *      The Regents of the University of California.  All rights reserved.
42  *
43  * Redistribution and use in source and binary forms, with or without
44  * modification, are permitted provided that the following conditions
45  * are met:
46  * 1. Redistributions of source code must retain the above copyright
47  *    notice, this list of conditions and the following disclaimer.
48  * 2. Redistributions in binary form must reproduce the above copyright
49  *    notice, this list of conditions and the following disclaimer in the
50  *    documentation and/or other materials provided with the distribution.
51  * 3. All advertising materials mentioning features or use of this software
52  *    must display the following acknowledgement:
53  *      This product includes software developed by the University of
54  *      California, Berkeley and its contributors.
55  * 4. Neither the name of the University nor the names of its contributors
56  *    may be used to endorse or promote products derived from this software
57  *    without specific prior written permission.
58  *
59  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
60  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
61  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
62  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
63  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
64  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
65  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
66  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
67  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
68  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
69  * SUCH DAMAGE.
70  *
71  *      @(#)ftpcmd.y    8.3 (Berkeley) 4/6/94
72  */
73
74 /*
75  * Grammar for FTP commands.
76  * See RFC 959.
77  */
78
79 %{
80 #include "lukemftpd.h"
81
82 #include "extern.h"
83 #include "version.h"
84
85 static  int cmd_type;
86 static  int cmd_form;
87 static  int cmd_bytesz;
88
89 char    cbuf[FTP_BUFLEN];
90 char    *cmdp;
91 char    *fromname;
92
93 %}
94
95 %union {
96         int     i;
97         char   *s;
98 }
99
100 %token
101         A       B       C       E       F       I
102         L       N       P       R       S       T
103
104         SP      CRLF    COMMA
105
106         USER    PASS    ACCT    CWD     CDUP    SMNT
107         QUIT    REIN    PORT    PASV    TYPE    STRU
108         MODE    RETR    STOR    STOU    APPE    ALLO
109         REST    RNFR    RNTO    ABOR    DELE    RMD
110         MKD     PWD     LIST    NLST    SITE    SYST
111         STAT    HELP    NOOP
112
113         AUTH    ADAT    PROT    PBSZ    CCC     MIC
114         CONF    ENC
115
116         FEAT    OPTS
117
118         SIZE    MDTM    MLST    MLSD
119
120         LPRT    LPSV    EPRT    EPSV
121
122         MAIL    MLFL    MRCP    MRSQ    MSAM    MSND
123         MSOM
124
125         CHMOD   IDLE    RATEGET RATEPUT UMASK
126
127         LEXERR
128
129 %token  <s> STRING
130 %token  <s> ALL
131 %token  <i> NUMBER
132
133 %type   <i> check_login octal_number byte_size
134 %type   <i> struct_code mode_code type_code form_code decimal_integer
135 %type   <s> pathstring pathname password username
136 %type   <s> mechanism_name base64data prot_code
137
138 %start  cmd_sel
139
140 %%
141
142 cmd_sel
143         : cmd
144                 {
145                         fromname = NULL;
146                         restart_point = (off_t) 0;
147                 }
148
149         | rcmd
150
151         ;
152
153 cmd
154                                                 /* RFC 959 */
155         : USER SP username CRLF
156                 {
157                         user($3);
158                         free($3);
159                 }
160
161         | PASS SP password CRLF
162                 {
163                         pass($3);
164                         memset($3, 0, strlen($3));
165                         free($3);
166                 }
167
168         | CWD check_login CRLF
169                 {
170                         if ($2)
171                                 cwd(homedir);
172                 }
173
174         | CWD check_login SP pathname CRLF
175                 {
176                         if ($2 && $4 != NULL)
177                                 cwd($4);
178                         if ($4 != NULL)
179                                 free($4);
180                 }
181
182         | CDUP check_login CRLF
183                 {
184                         if ($2)
185                                 cwd("..");
186                 }
187
188         | QUIT CRLF
189                 {
190                         if (logged_in) {
191                                 reply(-221, "%s", "");
192                                 reply(0,
193  "Data traffic for this session was " LLF " byte%s in " LLF " file%s.",
194                                     (LLT)total_data, PLURAL(total_data),
195                                     (LLT)total_files, PLURAL(total_files));
196                                 reply(0,
197  "Total traffic for this session was " LLF " byte%s in " LLF " transfer%s.",
198                                     (LLT)total_bytes, PLURAL(total_bytes),
199                                     (LLT)total_xfers, PLURAL(total_xfers));
200                         }
201                         reply(221,
202                             "Thank you for using the FTP service on %s.",
203                             hostname);
204                         if (logged_in && logging) {
205                                 syslog(LOG_INFO,
206                 "Data traffic: " LLF " byte%s in " LLF " file%s",
207                                     (LLT)total_data, PLURAL(total_data),
208                                     (LLT)total_files, PLURAL(total_files));
209                                 syslog(LOG_INFO,
210                 "Total traffic: " LLF " byte%s in " LLF " transfer%s",
211                                     (LLT)total_bytes, PLURAL(total_bytes),
212                                     (LLT)total_xfers, PLURAL(total_xfers));
213                         }
214
215                         dologout(0);
216                 }
217
218         | PORT check_login SP host_port CRLF
219                 {
220                         if ($2)
221                                 port_check("PORT", AF_INET);
222                 }
223
224         | LPRT check_login SP host_long_port4 CRLF
225                 {
226                         if ($2)
227                                 port_check("LPRT", AF_INET);
228                 }
229
230         | LPRT check_login SP host_long_port6 CRLF
231                 {
232 #ifdef INET6
233                         if ($2)
234                                 port_check("LPRT", AF_INET6);
235 #else
236                         reply(500, "IPv6 support not available.");
237 #endif
238                 }
239
240         | EPRT check_login SP STRING CRLF
241                 {
242                         if ($2) {
243                                 if (extended_port($4) == 0)
244                                         port_check("EPRT", -1);
245                         }
246                         free($4);
247                 }
248
249         | PASV check_login CRLF
250                 {
251                         if ($2) {
252                                 if (CURCLASS_FLAGS_ISSET(passive))
253                                         passive();
254                                 else
255                                         reply(500, "PASV mode not available.");
256                         }
257                 }
258
259         | LPSV check_login CRLF
260                 {
261                         if ($2) {
262                                 if (epsvall)
263                                         reply(501,
264                                             "LPSV disallowed after EPSV ALL");
265                                 else
266                                         long_passive("LPSV", PF_UNSPEC);
267                         }
268                 }
269
270         | EPSV check_login SP NUMBER CRLF
271                 {
272                         if ($2)
273                                 long_passive("EPSV", epsvproto2af($4));
274                 }
275
276         | EPSV check_login SP ALL CRLF
277                 {
278                         if ($2) {
279                                 reply(200, "EPSV ALL command successful.");
280                                 epsvall++;
281                         }
282                 }
283
284         | EPSV check_login CRLF
285                 {
286                         if ($2)
287                                 long_passive("EPSV", PF_UNSPEC);
288                 }
289
290         | TYPE check_login SP type_code CRLF
291                 {
292                         if ($2) {
293
294                         switch (cmd_type) {
295
296                         case TYPE_A:
297                                 if (cmd_form == FORM_N) {
298                                         reply(200, "Type set to A.");
299                                         type = cmd_type;
300                                         form = cmd_form;
301                                 } else
302                                         reply(504, "Form must be N.");
303                                 break;
304
305                         case TYPE_E:
306                                 reply(504, "Type E not implemented.");
307                                 break;
308
309                         case TYPE_I:
310                                 reply(200, "Type set to I.");
311                                 type = cmd_type;
312                                 break;
313
314                         case TYPE_L:
315 #if NBBY == 8
316                                 if (cmd_bytesz == 8) {
317                                         reply(200,
318                                             "Type set to L (byte size 8).");
319                                         type = cmd_type;
320                                 } else
321                                         reply(504, "Byte size must be 8.");
322 #else /* NBBY == 8 */
323                                 UNIMPLEMENTED for NBBY != 8
324 #endif /* NBBY == 8 */
325                         }
326                         
327                         }
328                 }
329
330         | STRU check_login SP struct_code CRLF
331                 {
332                         if ($2) {
333                                 switch ($4) {
334
335                                 case STRU_F:
336                                         reply(200, "STRU F ok.");
337                                         break;
338
339                                 default:
340                                         reply(504, "Unimplemented STRU type.");
341                                 }
342                         }
343                 }
344
345         | MODE check_login SP mode_code CRLF
346                 {
347                         if ($2) {
348                                 switch ($4) {
349
350                                 case MODE_S:
351                                         reply(200, "MODE S ok.");
352                                         break;
353
354                                 default:
355                                         reply(502, "Unimplemented MODE type.");
356                                 }
357                         }
358                 }
359
360         | RETR check_login SP pathname CRLF
361                 {
362                         if ($2 && $4 != NULL)
363                                 retrieve(NULL, $4);
364                         if ($4 != NULL)
365                                 free($4);
366                 }
367
368         | STOR SP pathname CRLF
369                 {
370                         if (check_write($3, 1))
371                                 store($3, "w", 0);
372                         if ($3 != NULL)
373                                 free($3);
374                 }
375
376         | STOU SP pathname CRLF
377                 {
378                         if (check_write($3, 1))
379                                 store($3, "w", 1);
380                         if ($3 != NULL)
381                                 free($3);
382                 }
383                 
384         | APPE SP pathname CRLF
385                 {
386                         if (check_write($3, 1))
387                                 store($3, "a", 0);
388                         if ($3 != NULL)
389                                 free($3);
390                 }
391
392         | ALLO check_login SP NUMBER CRLF
393                 {
394                         if ($2)
395                                 reply(202, "ALLO command ignored.");
396                 }
397
398         | ALLO check_login SP NUMBER SP R SP NUMBER CRLF
399                 {
400                         if ($2)
401                                 reply(202, "ALLO command ignored.");
402                 }
403
404         | RNTO SP pathname CRLF
405                 {
406                         if (check_write($3, 0)) {
407                                 if (fromname) {
408                                         renamecmd(fromname, $3);
409                                         free(fromname);
410                                         fromname = NULL;
411                                 } else {
412                                         reply(503, "Bad sequence of commands.");
413                                 }
414                         }
415                         if ($3 != NULL)
416                                 free($3);
417                 }
418
419         | ABOR check_login CRLF
420                 {
421                         if (is_oob)
422                                 abor();
423                         else if ($2)
424                                 reply(225, "ABOR command successful.");
425                 }
426
427         | DELE SP pathname CRLF
428                 {
429                         if (check_write($3, 0))
430                                 delete($3);
431                         if ($3 != NULL)
432                                 free($3);
433                 }
434
435         | RMD SP pathname CRLF
436                 {
437                         if (check_write($3, 0))
438                                 removedir($3);
439                         if ($3 != NULL)
440                                 free($3);
441                 }
442
443         | MKD SP pathname CRLF
444                 {
445                         if (check_write($3, 0))
446                                 makedir($3);
447                         if ($3 != NULL)
448                                 free($3);
449                 }
450
451         | PWD check_login CRLF
452                 {
453                         if ($2)
454                                 pwd();
455                 }
456
457         | LIST check_login CRLF
458                 {
459                         char *argv[] = { INTERNAL_LS, "-lgA", NULL };
460                         
461                         if ($2)
462                                 retrieve(argv, "");
463                 }
464
465         | LIST check_login SP pathname CRLF
466                 {
467                         char *argv[] = { INTERNAL_LS, "-lgA", NULL, NULL };
468
469                         if ($2 && $4 != NULL) {
470                                 argv[2] = $4;
471                                 retrieve(argv, $4);
472                         }
473                         if ($4 != NULL)
474                                 free($4);
475                 }
476
477         | NLST check_login CRLF
478                 {
479                         if ($2)
480                                 send_file_list(".");
481                 }
482
483         | NLST check_login SP pathname CRLF
484                 {
485                         if ($2)
486                                 send_file_list($4);
487                         free($4);
488                 }
489
490         | SITE SP HELP CRLF
491                 {
492                         help(sitetab, NULL);
493                 }
494
495         | SITE SP CHMOD SP octal_number SP pathname CRLF
496                 {
497                         if (check_write($7, 0)) {
498                                 if ($5 > 0777)
499                                         reply(501,
500                                 "CHMOD: Mode value must be between 0 and 0777");
501                                 else if (chmod($7, $5) < 0)
502                                         perror_reply(550, $7);
503                                 else
504                                         reply(200, "CHMOD command successful.");
505                         }
506                         if ($7 != NULL)
507                                 free($7);
508                 }
509
510         | SITE SP HELP SP STRING CRLF
511                 {
512                         help(sitetab, $5);
513                         free($5);
514                 }
515
516         | SITE SP IDLE check_login CRLF
517                 {
518                         if ($4) {
519                                 reply(200,
520                             "Current IDLE time limit is %d seconds; max %d",
521                                     curclass.timeout, curclass.maxtimeout);
522                         }
523                 }
524
525         | SITE SP IDLE check_login SP NUMBER CRLF
526                 {
527                         if ($4) {
528                                 if ($6 < 30 || $6 > curclass.maxtimeout) {
529                                         reply(501,
530                             "IDLE time limit must be between 30 and %d seconds",
531                                             curclass.maxtimeout);
532                                 } else {
533                                         curclass.timeout = $6;
534                                         (void) alarm(curclass.timeout);
535                                         reply(200,
536                                             "IDLE time limit set to %d seconds",
537                                             curclass.timeout);
538                                 }
539                         }
540                 }
541
542         | SITE SP RATEGET check_login CRLF
543                 {
544                         if ($4) {
545                                 reply(200,
546                                     "Current RATEGET is " LLF " bytes/sec",
547                                     (LLT)curclass.rateget);
548                         }
549                 }
550
551         | SITE SP RATEGET check_login SP STRING CRLF
552                 {
553                         char *p = $6;
554                         LLT rate;
555
556                         if ($4) {
557                                 rate = strsuftoll(p);
558                                 if (rate == -1)
559                                         reply(501, "Invalid RATEGET %s", p);
560                                 else if (curclass.maxrateget &&
561                                     rate > curclass.maxrateget)
562                                         reply(501,
563                         "RATEGET " LLF " is larger than maximum RATEGET " LLF,
564                                             (LLT)rate,
565                                             (LLT)curclass.maxrateget);
566                                 else {
567                                         curclass.rateget = rate;
568                                         reply(200,
569                                             "RATEGET set to " LLF " bytes/sec",
570                                             (LLT)curclass.rateget);
571                                 }
572                         }
573                         free($6);
574                 }
575
576         | SITE SP RATEPUT check_login CRLF
577                 {
578                         if ($4) {
579                                 reply(200,
580                                     "Current RATEPUT is " LLF " bytes/sec",
581                                     (LLT)curclass.rateput);
582                         }
583                 }
584
585         | SITE SP RATEPUT check_login SP STRING CRLF
586                 {
587                         char *p = $6;
588                         LLT rate;
589
590                         if ($4) {
591                                 rate = strsuftoll(p);
592                                 if (rate == -1)
593                                         reply(501, "Invalid RATEPUT %s", p);
594                                 else if (curclass.maxrateput &&
595                                     rate > curclass.maxrateput)
596                                         reply(501,
597                         "RATEPUT " LLF " is larger than maximum RATEPUT " LLF,
598                                             (LLT)rate,
599                                             (LLT)curclass.maxrateput);
600                                 else {
601                                         curclass.rateput = rate;
602                                         reply(200,
603                                             "RATEPUT set to " LLF " bytes/sec",
604                                             (LLT)curclass.rateput);
605                                 }
606                         }
607                         free($6);
608                 }
609
610         | SITE SP UMASK check_login CRLF
611                 {
612                         int oldmask;
613
614                         if ($4) {
615                                 oldmask = umask(0);
616                                 (void) umask(oldmask);
617                                 reply(200, "Current UMASK is %03o", oldmask);
618                         }
619                 }
620
621         | SITE SP UMASK check_login SP octal_number CRLF
622                 {
623                         int oldmask;
624
625                         if ($4 && CURCLASS_FLAGS_ISSET(modify)) {
626                                 if (($6 == -1) || ($6 > 0777)) {
627                                         reply(501, "Bad UMASK value");
628                                 } else {
629                                         oldmask = umask($6);
630                                         reply(200,
631                                             "UMASK set to %03o (was %03o)",
632                                             $6, oldmask);
633                                 }
634                         }
635                 }
636
637         | SYST CRLF
638                 {
639                         if (EMPTYSTR(version))
640                                 reply(215, "UNIX Type: L%d", NBBY);
641                         else
642                                 reply(215, "UNIX Type: L%d Version: %s", NBBY,
643                                     version);
644                 }
645
646         | STAT check_login SP pathname CRLF
647                 {
648                         if ($2 && $4 != NULL)
649                                 statfilecmd($4);
650                         if ($4 != NULL)
651                                 free($4);
652                 }
653                 
654         | STAT CRLF
655                 {
656                         if (is_oob)
657                                 statxfer();
658                         else
659                                 statcmd();
660                 }
661
662         | HELP CRLF
663                 {
664                         help(cmdtab, NULL);
665                 }
666
667         | HELP SP STRING CRLF
668                 {
669                         char *cp = $3;
670
671                         if (strncasecmp(cp, "SITE", 4) == 0) {
672                                 cp = $3 + 4;
673                                 if (*cp == ' ')
674                                         cp++;
675                                 if (*cp)
676                                         help(sitetab, cp);
677                                 else
678                                         help(sitetab, NULL);
679                         } else
680                                 help(cmdtab, $3);
681                         free($3);
682                 }
683
684         | NOOP CRLF
685                 {
686                         reply(200, "NOOP command successful.");
687                 }
688
689                                                 /* RFC 2228 */
690         | AUTH SP mechanism_name CRLF
691                 {
692                         reply(502, "RFC 2228 authentication not implemented.");
693                         free($3);
694                 }
695
696         | ADAT SP base64data CRLF
697                 {
698                         reply(503,
699                             "Please set authentication state with AUTH.");
700                         free($3);
701                 }
702
703         | PROT SP prot_code CRLF
704                 {
705                         reply(503,
706                             "Please set protection buffer size with PBSZ.");
707                         free($3);
708                 }
709
710         | PBSZ SP decimal_integer CRLF
711                 {
712                         reply(503,
713                             "Please set authentication state with AUTH.");
714                 }
715
716         | CCC CRLF
717                 {
718                         reply(533, "No protection enabled.");
719                 }
720
721         | MIC SP base64data CRLF
722                 {
723                         reply(502, "RFC 2228 authentication not implemented.");
724                         free($3);
725                 }
726
727         | CONF SP base64data CRLF
728                 {
729                         reply(502, "RFC 2228 authentication not implemented.");
730                         free($3);
731                 }
732
733         | ENC SP base64data CRLF
734                 {
735                         reply(502, "RFC 2228 authentication not implemented.");
736                         free($3);
737                 }
738
739                                                 /* RFC 2389 */
740         | FEAT CRLF
741                 {
742
743                         feat();
744                 }
745
746         | OPTS SP STRING CRLF
747                 {
748                         
749                         opts($3);
750                         free($3);
751                 }
752
753
754                                 /* extensions from draft-ietf-ftpext-mlst-11 */
755
756                 /*
757                  * Return size of file in a format suitable for
758                  * using with RESTART (we just count bytes).
759                  */
760         | SIZE check_login SP pathname CRLF
761                 {
762                         if ($2 && $4 != NULL)
763                                 sizecmd($4);
764                         if ($4 != NULL)
765                                 free($4);
766                 }
767
768                 /*
769                  * Return modification time of file as an ISO 3307
770                  * style time. E.g. YYYYMMDDHHMMSS or YYYYMMDDHHMMSS.xxx
771                  * where xxx is the fractional second (of any precision,
772                  * not necessarily 3 digits)
773                  */
774         | MDTM check_login SP pathname CRLF
775                 {
776                         if ($2 && $4 != NULL) {
777                                 struct stat stbuf;
778                                 if (stat($4, &stbuf) < 0)
779                                         perror_reply(550, $4);
780                                 else if (!S_ISREG(stbuf.st_mode)) {
781                                         reply(550, "%s: not a plain file.", $4);
782                                 } else {
783                                         struct tm *t;
784
785                                         t = gmtime(&stbuf.st_mtime);
786                                         reply(213,
787                                             "%04d%02d%02d%02d%02d%02d",
788                                             TM_YEAR_BASE + t->tm_year,
789                                             t->tm_mon+1, t->tm_mday,
790                                             t->tm_hour, t->tm_min, t->tm_sec);
791                                 }
792                         }
793                         if ($4 != NULL)
794                                 free($4);
795                 }
796
797         | MLST check_login SP pathname CRLF
798                 {
799                         if ($2 && $4 != NULL)
800                                 mlst($4);
801                         if ($4 != NULL)
802                                 free($4);
803                 }
804                 
805         | MLST check_login CRLF
806                 {
807                         mlst(NULL);
808                 }
809
810         | MLSD check_login SP pathname CRLF
811                 {
812                         if ($2 && $4 != NULL)
813                                 mlsd($4);
814                         if ($4 != NULL)
815                                 free($4);
816                 }
817                 
818         | MLSD check_login CRLF
819                 {
820                         mlsd(NULL);
821                 }
822
823         | error CRLF
824                 {
825                         yyerrok;
826                 }
827         ;
828
829 rcmd
830         : REST check_login SP byte_size CRLF
831                 {
832                         if ($2) {
833                                 fromname = NULL;
834                                 restart_point = $4; /* XXX: $4 is only "int" */
835                                 reply(350,
836     "Restarting at " LLF ". Send STORE or RETRIEVE to initiate transfer.",
837                                     (LLT)restart_point);
838                         }
839                 }
840
841         | RNFR SP pathname CRLF
842                 {
843                         restart_point = (off_t) 0;
844                         if (check_write($3, 0))
845                                 fromname = renamefrom($3);
846                         if ($3 != NULL)
847                                 free($3);
848                 }
849         ;
850
851 username
852         : STRING
853         ;
854
855 password
856         : /* empty */
857                 {
858                         $$ = (char *)calloc(1, sizeof(char));
859                 }
860
861         | STRING
862         ;
863
864 byte_size
865         : NUMBER
866         ;
867
868 host_port
869         : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
870                 NUMBER COMMA NUMBER
871                 {
872                         char *a, *p;
873
874                         memset(&data_dest, 0, sizeof(data_dest));
875                         data_dest.su_len = sizeof(struct sockaddr_in);
876                         data_dest.su_family = AF_INET;
877                         p = (char *)&data_dest.su_port;
878                         p[0] = $9; p[1] = $11;
879                         a = (char *)&data_dest.su_addr;
880                         a[0] = $1; a[1] = $3; a[2] = $5; a[3] = $7;
881                 }
882         ;
883
884 host_long_port4
885         : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
886                 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
887                 NUMBER
888                 {
889                         char *a, *p;
890
891                         memset(&data_dest, 0, sizeof(data_dest));
892                         data_dest.su_len = sizeof(struct sockaddr_in);
893                         data_dest.su_family = AF_INET;
894                         p = (char *)&data_dest.su_port;
895                         p[0] = $15; p[1] = $17;
896                         a = (char *)&data_dest.su_addr;
897                         a[0] =  $5;  a[1] =  $7;  a[2] =  $9;  a[3] = $11;
898
899                         /* reject invalid LPRT command */
900                         if ($1 != 4 || $3 != 4 || $13 != 2)
901                                 memset(&data_dest, 0, sizeof(data_dest));
902                 }
903         ;
904
905 host_long_port6
906         : NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
907                 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
908                 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
909                 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
910                 NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER COMMA
911                 NUMBER
912                 {
913 #ifdef INET6
914                         char *a, *p;
915
916                         memset(&data_dest, 0, sizeof(data_dest));
917                         data_dest.su_len = sizeof(struct sockaddr_in6);
918                         data_dest.su_family = AF_INET6;
919                         p = (char *)&data_dest.su_port;
920                         p[0] = $39; p[1] = $41;
921                         a = (char *)&data_dest.si_su.su_sin6.sin6_addr;
922                          a[0] =  $5;  a[1] =  $7;  a[2] =  $9;  a[3] = $11;
923                          a[4] = $13;  a[5] = $15;  a[6] = $17;  a[7] = $19;
924                          a[8] = $21;  a[9] = $23; a[10] = $25; a[11] = $27;
925                         a[12] = $29; a[13] = $31; a[14] = $33; a[15] = $35;
926                         if (his_addr.su_family == AF_INET6) {
927                                 /* XXX: more sanity checks! */
928                                 data_dest.su_scope_id = his_addr.su_scope_id;
929                         }
930 #else
931                         memset(&data_dest, 0, sizeof(data_dest));
932 #endif /* INET6 */
933                         /* reject invalid LPRT command */
934                         if ($1 != 6 || $3 != 16 || $37 != 2)
935                                 memset(&data_dest, 0, sizeof(data_dest));
936                 }
937         ;
938
939 form_code
940         : N
941                 {
942                         $$ = FORM_N;
943                 }
944
945         | T
946                 {
947                         $$ = FORM_T;
948                 }
949
950         | C
951                 {
952                         $$ = FORM_C;
953                 }
954         ;
955
956 type_code
957         : A
958                 {
959                         cmd_type = TYPE_A;
960                         cmd_form = FORM_N;
961                 }
962
963         | A SP form_code
964                 {
965                         cmd_type = TYPE_A;
966                         cmd_form = $3;
967                 }
968
969         | E
970                 {
971                         cmd_type = TYPE_E;
972                         cmd_form = FORM_N;
973                 }
974
975         | E SP form_code
976                 {
977                         cmd_type = TYPE_E;
978                         cmd_form = $3;
979                 }
980
981         | I
982                 {
983                         cmd_type = TYPE_I;
984                 }
985
986         | L
987                 {
988                         cmd_type = TYPE_L;
989                         cmd_bytesz = NBBY;
990                 }
991
992         | L SP byte_size
993                 {
994                         cmd_type = TYPE_L;
995                         cmd_bytesz = $3;
996                 }
997
998                 /* this is for a bug in the BBN ftp */
999         | L byte_size
1000                 {
1001                         cmd_type = TYPE_L;
1002                         cmd_bytesz = $2;
1003                 }
1004         ;
1005
1006 struct_code
1007         : F
1008                 {
1009                         $$ = STRU_F;
1010                 }
1011
1012         | R
1013                 {
1014                         $$ = STRU_R;
1015                 }
1016
1017         | P
1018                 {
1019                         $$ = STRU_P;
1020                 }
1021         ;
1022
1023 mode_code
1024         : S
1025                 {
1026                         $$ = MODE_S;
1027                 }
1028
1029         | B
1030                 {
1031                         $$ = MODE_B;
1032                 }
1033
1034         | C
1035                 {
1036                         $$ = MODE_C;
1037                 }
1038         ;
1039
1040 pathname
1041         : pathstring
1042                 {
1043                         /*
1044                          * Problem: this production is used for all pathname
1045                          * processing, but only gives a 550 error reply.
1046                          * This is a valid reply in some cases but not in
1047                          * others.
1048                          */
1049                         if (logged_in && $1 && *$1 == '~') {
1050                                 char    *path, *home, *result;
1051                                 size_t  len;
1052
1053                                 path = strchr($1 + 1, '/');
1054                                 if (path != NULL)
1055                                         *path++ = '\0';
1056                                 if ($1[1] == '\0')
1057                                         home = homedir;
1058                                 else {
1059                                         struct passwd   *hpw;
1060
1061                                         if ((hpw = getpwnam($1 + 1)) != NULL)
1062                                                 home = hpw->pw_dir;
1063                                         else
1064                                                 home = $1;
1065                                 }
1066                                 len = strlen(home) + 1;
1067                                 if (path != NULL)
1068                                         len += strlen(path) + 1;
1069                                 if ((result = malloc(len)) == NULL)
1070                                         fatal("Local resource failure: malloc");
1071                                 strlcpy(result, home, len);
1072                                 if (path != NULL) {
1073                                         strlcat(result, "/", len);
1074                                         strlcat(result, path, len);
1075                                 }
1076                                 $$ = result;
1077                                 free($1);
1078                         } else
1079                                 $$ = $1;
1080                 }
1081         ;
1082
1083 pathstring
1084         : STRING
1085         ;
1086
1087 octal_number
1088         : NUMBER
1089                 {
1090                         int ret, dec, multby, digit;
1091
1092                         /*
1093                          * Convert a number that was read as decimal number
1094                          * to what it would be if it had been read as octal.
1095                          */
1096                         dec = $1;
1097                         multby = 1;
1098                         ret = 0;
1099                         while (dec) {
1100                                 digit = dec%10;
1101                                 if (digit > 7) {
1102                                         ret = -1;
1103                                         break;
1104                                 }
1105                                 ret += digit * multby;
1106                                 multby *= 8;
1107                                 dec /= 10;
1108                         }
1109                         $$ = ret;
1110                 }
1111         ;
1112
1113 mechanism_name
1114         : STRING
1115         ;
1116
1117 base64data
1118         : STRING
1119         ;
1120
1121 prot_code
1122         : STRING
1123         ;
1124
1125 decimal_integer
1126         : NUMBER
1127         ;
1128
1129 check_login
1130         : /* empty */
1131                 {
1132                         if (logged_in)
1133                                 $$ = 1;
1134                         else {
1135                                 reply(530, "Please login with USER and PASS.");
1136                                 $$ = 0;
1137                                 hasyyerrored = 1;
1138                         }
1139                 }
1140         ;
1141
1142 %%
1143
1144 #define CMD     0       /* beginning of command */
1145 #define ARGS    1       /* expect miscellaneous arguments */
1146 #define STR1    2       /* expect SP followed by STRING */
1147 #define STR2    3       /* expect STRING */
1148 #define OSTR    4       /* optional SP then STRING */
1149 #define ZSTR1   5       /* SP then optional STRING */
1150 #define ZSTR2   6       /* optional STRING after SP */
1151 #define SITECMD 7       /* SITE command */
1152 #define NSTR    8       /* Number followed by a string */
1153 #define NOARGS  9       /* No arguments allowed */
1154 #define EOLN    10      /* End of line */
1155
1156 struct tab cmdtab[] = {
1157                                 /* From RFC 959, in order defined (5.3.1) */
1158         { "USER", USER, STR1,   1,      "<sp> username" },
1159         { "PASS", PASS, ZSTR1,  1,      "<sp> password" },
1160         { "ACCT", ACCT, STR1,   0,      "(specify account)" },
1161         { "CWD",  CWD,  OSTR,   1,      "[ <sp> directory-name ]" },
1162         { "CDUP", CDUP, NOARGS, 1,      "(change to parent directory)" },
1163         { "SMNT", SMNT, ARGS,   0,      "(structure mount)" },
1164         { "QUIT", QUIT, NOARGS, 1,      "(terminate service)" },
1165         { "REIN", REIN, NOARGS, 0,      "(reinitialize server state)" },
1166         { "PORT", PORT, ARGS,   1,      "<sp> b0, b1, b2, b3, b4" },
1167         { "LPRT", LPRT, ARGS,   1,      "<sp> af, hal, h1, h2, h3,..., pal, p1, p2..." },
1168         { "EPRT", EPRT, STR1,   1,      "<sp> |af|addr|port|" },
1169         { "PASV", PASV, NOARGS, 1,      "(set server in passive mode)" },
1170         { "LPSV", LPSV, ARGS,   1,      "(set server in passive mode)" },
1171         { "EPSV", EPSV, ARGS,   1,      "[<sp> af|ALL]" },
1172         { "TYPE", TYPE, ARGS,   1,      "<sp> [ A | E | I | L ]" },
1173         { "STRU", STRU, ARGS,   1,      "(specify file structure)" },
1174         { "MODE", MODE, ARGS,   1,      "(specify transfer mode)" },
1175         { "RETR", RETR, STR1,   1,      "<sp> file-name" },
1176         { "STOR", STOR, STR1,   1,      "<sp> file-name" },
1177         { "STOU", STOU, STR1,   1,      "<sp> file-name" },
1178         { "APPE", APPE, STR1,   1,      "<sp> file-name" },
1179         { "ALLO", ALLO, ARGS,   1,      "allocate storage (vacuously)" },
1180         { "REST", REST, ARGS,   1,      "<sp> offset (restart command)" },
1181         { "RNFR", RNFR, STR1,   1,      "<sp> file-name" },
1182         { "RNTO", RNTO, STR1,   1,      "<sp> file-name" },
1183         { "ABOR", ABOR, NOARGS, 4,      "(abort operation)" },
1184         { "DELE", DELE, STR1,   1,      "<sp> file-name" },
1185         { "RMD",  RMD,  STR1,   1,      "<sp> path-name" },
1186         { "MKD",  MKD,  STR1,   1,      "<sp> path-name" },
1187         { "PWD",  PWD,  NOARGS, 1,      "(return current directory)" },
1188         { "LIST", LIST, OSTR,   1,      "[ <sp> path-name ]" },
1189         { "NLST", NLST, OSTR,   1,      "[ <sp> path-name ]" },
1190         { "SITE", SITE, SITECMD, 1,     "site-cmd [ <sp> arguments ]" },
1191         { "SYST", SYST, NOARGS, 1,      "(get type of operating system)" },
1192         { "STAT", STAT, OSTR,   4,      "[ <sp> path-name ]" },
1193         { "HELP", HELP, OSTR,   1,      "[ <sp> <string> ]" },
1194         { "NOOP", NOOP, NOARGS, 2,      "" },
1195
1196                                 /* From RFC 2228, in order defined */
1197         { "AUTH", AUTH, STR1,   1,      "<sp> mechanism-name" },
1198         { "ADAT", ADAT, STR1,   1,      "<sp> base-64-data" },
1199         { "PROT", PROT, STR1,   1,      "<sp> prot-code" },
1200         { "PBSZ", PBSZ, ARGS,   1,      "<sp> decimal-integer" },
1201         { "CCC",  CCC,  NOARGS, 1,      "(Disable data protection)" },
1202         { "MIC",  MIC,  STR1,   4,      "<sp> base64data" },
1203         { "CONF", CONF, STR1,   4,      "<sp> base64data" },
1204         { "ENC",  ENC,  STR1,   4,      "<sp> base64data" },
1205
1206                                 /* From RFC 2389, in order defined */
1207         { "FEAT", FEAT, NOARGS, 1,      "(display extended features)" },
1208         { "OPTS", OPTS, STR1,   1,      "<sp> command [ <sp> options ]" },
1209
1210                                 /* from draft-ietf-ftpext-mlst-11 */
1211         { "MDTM", MDTM, OSTR,   1,      "<sp> path-name" },
1212         { "SIZE", SIZE, OSTR,   1,      "<sp> path-name" },
1213         { "MLST", MLST, OSTR,   2,      "[ <sp> path-name ]" },
1214         { "MLSD", MLSD, OSTR,   1,      "[ <sp> directory-name ]" },
1215
1216                                 /* obsolete commands */
1217         { "MAIL", MAIL, OSTR,   0,      "(mail to user)" },
1218         { "MLFL", MLFL, OSTR,   0,      "(mail file)" },
1219         { "MRCP", MRCP, STR1,   0,      "(mail recipient)" },
1220         { "MRSQ", MRSQ, OSTR,   0,      "(mail recipient scheme question)" },
1221         { "MSAM", MSAM, OSTR,   0,      "(mail send to terminal and mailbox)" },
1222         { "MSND", MSND, OSTR,   0,      "(mail send to terminal)" },
1223         { "MSOM", MSOM, OSTR,   0,      "(mail send to terminal or mailbox)" },
1224         { "XCUP", CDUP, NOARGS, 1,      "(change to parent directory)" },
1225         { "XCWD", CWD,  OSTR,   1,      "[ <sp> directory-name ]" },
1226         { "XMKD", MKD,  STR1,   1,      "<sp> path-name" },
1227         { "XPWD", PWD,  NOARGS, 1,      "(return current directory)" },
1228         { "XRMD", RMD,  STR1,   1,      "<sp> path-name" },
1229
1230         {  NULL,  0,    0,      0,      0 }
1231 };
1232
1233 struct tab sitetab[] = {
1234         { "CHMOD",      CHMOD,  NSTR, 1,        "<sp> mode <sp> file-name" },
1235         { "HELP",       HELP,   OSTR, 1,        "[ <sp> <string> ]" },
1236         { "IDLE",       IDLE,   ARGS, 1,        "[ <sp> maximum-idle-time ]" },
1237         { "RATEGET",    RATEGET,OSTR, 1,        "[ <sp> get-throttle-rate ]" },
1238         { "RATEPUT",    RATEPUT,OSTR, 1,        "[ <sp> put-throttle-rate ]" },
1239         { "UMASK",      UMASK,  ARGS, 1,        "[ <sp> umask ]" },
1240         { NULL,         0,     0,     0,        NULL }
1241 };
1242
1243 static  int     check_write(const char *, int);
1244 static  void    help(struct tab *, const char *);
1245 static  void    port_check(const char *, int);
1246 static  void    toolong(int);
1247 static  int     yylex(void);
1248
1249 extern int epsvall;
1250
1251 /*
1252  * Check if a filename is allowed to be modified (isupload == 0) or
1253  * uploaded (isupload == 1), and if necessary, check the filename is `sane'.
1254  */
1255 static int
1256 check_write(const char *file, int isupload)
1257 {
1258         if (file == NULL)
1259                 return (0);
1260         if (! logged_in) {
1261                 reply(530, "Please login with USER and PASS.");
1262                 return (0);
1263         }
1264                 /* checking modify */
1265         if (! isupload && ! CURCLASS_FLAGS_ISSET(modify)) {
1266                 reply(502, "No permission to use this command.");
1267                 return (0);
1268         }
1269                 /* checking upload */
1270         if (isupload && ! CURCLASS_FLAGS_ISSET(upload)) {
1271                 reply(502, "No permission to use this command.");
1272                 return (0);
1273         }
1274                 /* checking sanenames */
1275         if (CURCLASS_FLAGS_ISSET(sanenames)) {
1276                 const char *p;
1277
1278                 if (file[0] == '.')
1279                         goto insane_name;
1280                 for (p = file; *p; p++) {
1281                         if (isalnum(*p) || *p == '-' || *p == '+' ||
1282                             *p == ',' || *p == '.' || *p == '_')
1283                                 continue;
1284  insane_name:
1285                         reply(553, "File name `%s' not allowed.", file);
1286                         return (0);
1287                 }
1288         }
1289         return (1);
1290 }
1291
1292 struct tab *
1293 lookup(struct tab *p, const char *cmd)
1294 {
1295
1296         for (; p->name != NULL; p++)
1297                 if (strcasecmp(cmd, p->name) == 0)
1298                         return (p);
1299         return (0);
1300 }
1301
1302 #include <arpa/telnet.h>
1303
1304 /*
1305  * getline - a hacked up version of fgets to ignore TELNET escape codes.
1306  */
1307 char *
1308 getline(char *s, int n, FILE *iop)
1309 {
1310         int c;
1311         char *cs;
1312
1313         cs = s;
1314 /* tmpline may contain saved command from urgent mode interruption */
1315         for (c = 0; tmpline[c] != '\0' && --n > 0; ++c) {
1316                 *cs++ = tmpline[c];
1317                 if (tmpline[c] == '\n') {
1318                         *cs++ = '\0';
1319                         if (debug)
1320                                 syslog(LOG_DEBUG, "command: %s", s);
1321                         tmpline[0] = '\0';
1322                         return(s);
1323                 }
1324                 if (c == 0)
1325                         tmpline[0] = '\0';
1326         }
1327         while ((c = getc(iop)) != EOF) {
1328                 total_bytes++;
1329                 total_bytes_in++;
1330                 c &= 0377;
1331                 if (c == IAC) {
1332                     if ((c = getc(iop)) != EOF) {
1333                         total_bytes++;
1334                         total_bytes_in++;
1335                         c &= 0377;
1336                         switch (c) {
1337                         case WILL:
1338                         case WONT:
1339                                 c = getc(iop);
1340                                 total_bytes++;
1341                                 total_bytes_in++;
1342                                 cprintf(stdout, "%c%c%c", IAC, DONT, 0377&c);
1343                                 (void) fflush(stdout);
1344                                 continue;
1345                         case DO:
1346                         case DONT:
1347                                 c = getc(iop);
1348                                 total_bytes++;
1349                                 total_bytes_in++;
1350                                 cprintf(stdout, "%c%c%c", IAC, WONT, 0377&c);
1351                                 (void) fflush(stdout);
1352                                 continue;
1353                         case IAC:
1354                                 break;
1355                         default:
1356                                 continue;       /* ignore command */
1357                         }
1358                     }
1359                 }
1360                 *cs++ = c;
1361                 if (--n <= 0 || c == '\n')
1362                         break;
1363         }
1364         if (c == EOF && cs == s)
1365                 return (NULL);
1366         *cs++ = '\0';
1367         if (debug) {
1368                 if ((curclass.type != CLASS_GUEST &&
1369                     strncasecmp(s, "PASS ", 5) == 0) ||
1370                     strncasecmp(s, "ACCT ", 5) == 0) {
1371                         /* Don't syslog passwords */
1372                         syslog(LOG_DEBUG, "command: %.4s ???", s);
1373                 } else {
1374                         char *cp;
1375                         int len;
1376
1377                         /* Don't syslog trailing CR-LF */
1378                         len = strlen(s);
1379                         cp = s + len - 1;
1380                         while (cp >= s && (*cp == '\n' || *cp == '\r')) {
1381                                 --cp;
1382                                 --len;
1383                         }
1384                         syslog(LOG_DEBUG, "command: %.*s", len, s);
1385                 }
1386         }
1387         return (s);
1388 }
1389
1390 static void
1391 toolong(int signo)
1392 {
1393
1394         reply(421,
1395             "Timeout (%d seconds): closing control connection.",
1396             curclass.timeout);
1397         if (logging)
1398                 syslog(LOG_INFO, "User %s timed out after %d seconds",
1399                     (pw ? pw->pw_name : "unknown"), curclass.timeout);
1400         dologout(1);
1401 }
1402
1403 void
1404 ftp_handle_line(char *cp)
1405 {
1406
1407         cmdp = cp;
1408         yyparse();
1409 }
1410
1411 void
1412 ftp_loop(void)
1413 {
1414
1415         while (1) {
1416                 (void) signal(SIGALRM, toolong);
1417                 (void) alarm(curclass.timeout);
1418                 if (getline(cbuf, sizeof(cbuf)-1, stdin) == NULL) {
1419                         reply(221, "You could at least say goodbye.");
1420                         dologout(0);
1421                 }
1422                 (void) alarm(0);
1423                 ftp_handle_line(cbuf);
1424         }
1425         /*NOTREACHED*/
1426 }
1427
1428 static int
1429 yylex(void)
1430 {
1431         static int cpos, state;
1432         char *cp, *cp2;
1433         struct tab *p;
1434         int n;
1435         char c;
1436
1437         switch (state) {
1438
1439         case CMD:
1440                 hasyyerrored = 0;
1441                 if ((cp = strchr(cmdp, '\r'))) {
1442                         *cp = '\0';
1443 #if HAVE_SETPROCTITLE
1444                         if (strncasecmp(cmdp, "PASS", 4) != 0 &&
1445                             strncasecmp(cmdp, "ACCT", 4) != 0)
1446                                 setproctitle("%s: %s", proctitle, cmdp);
1447 #endif /* HAVE_SETPROCTITLE */
1448                         *cp++ = '\n';
1449                         *cp = '\0';
1450                 }
1451                 if ((cp = strpbrk(cmdp, " \n")))
1452                         cpos = cp - cmdp;
1453                 if (cpos == 0)
1454                         cpos = 4;
1455                 c = cmdp[cpos];
1456                 cmdp[cpos] = '\0';
1457                 p = lookup(cmdtab, cmdp);
1458                 cmdp[cpos] = c;
1459                 if (p != NULL) {
1460                         if (is_oob && ! CMD_OOB(p)) {
1461                                 /* command will be handled in-band */
1462                                 return (0);
1463                         } else if (! CMD_IMPLEMENTED(p)) {
1464                                 reply(502, "%s command not implemented.",
1465                                     p->name);
1466                                 hasyyerrored = 1;
1467                                 break;
1468                         }
1469                         state = p->state;
1470                         yylval.s = p->name;
1471                         return (p->token);
1472                 }
1473                 break;
1474
1475         case SITECMD:
1476                 if (cmdp[cpos] == ' ') {
1477                         cpos++;
1478                         return (SP);
1479                 }
1480                 cp = &cmdp[cpos];
1481                 if ((cp2 = strpbrk(cp, " \n")))
1482                         cpos = cp2 - cmdp;
1483                 c = cmdp[cpos];
1484                 cmdp[cpos] = '\0';
1485                 p = lookup(sitetab, cp);
1486                 cmdp[cpos] = c;
1487                 if (p != NULL) {
1488                         if (!CMD_IMPLEMENTED(p)) {
1489                                 reply(502, "SITE %s command not implemented.",
1490                                     p->name);
1491                                 hasyyerrored = 1;
1492                                 break;
1493                         }
1494                         state = p->state;
1495                         yylval.s = p->name;
1496                         return (p->token);
1497                 }
1498                 break;
1499
1500         case OSTR:
1501                 if (cmdp[cpos] == '\n') {
1502                         state = EOLN;
1503                         return (CRLF);
1504                 }
1505                 /* FALLTHROUGH */
1506
1507         case STR1:
1508         case ZSTR1:
1509         dostr1:
1510                 if (cmdp[cpos] == ' ') {
1511                         cpos++;
1512                         state = state == OSTR ? STR2 : state+1;
1513                         return (SP);
1514                 }
1515                 break;
1516
1517         case ZSTR2:
1518                 if (cmdp[cpos] == '\n') {
1519                         state = EOLN;
1520                         return (CRLF);
1521                 }
1522                 /* FALLTHROUGH */
1523
1524         case STR2:
1525                 cp = &cmdp[cpos];
1526                 n = strlen(cp);
1527                 cpos += n - 1;
1528                 /*
1529                  * Make sure the string is nonempty and \n terminated.
1530                  */
1531                 if (n > 1 && cmdp[cpos] == '\n') {
1532                         cmdp[cpos] = '\0';
1533                         yylval.s = xstrdup(cp);
1534                         cmdp[cpos] = '\n';
1535                         state = ARGS;
1536                         return (STRING);
1537                 }
1538                 break;
1539
1540         case NSTR:
1541                 if (cmdp[cpos] == ' ') {
1542                         cpos++;
1543                         return (SP);
1544                 }
1545                 if (isdigit(cmdp[cpos])) {
1546                         cp = &cmdp[cpos];
1547                         while (isdigit(cmdp[++cpos]))
1548                                 ;
1549                         c = cmdp[cpos];
1550                         cmdp[cpos] = '\0';
1551                         yylval.i = atoi(cp);
1552                         cmdp[cpos] = c;
1553                         state = STR1;
1554                         return (NUMBER);
1555                 }
1556                 state = STR1;
1557                 goto dostr1;
1558
1559         case ARGS:
1560                 if (isdigit(cmdp[cpos])) {
1561                         cp = &cmdp[cpos];
1562                         while (isdigit(cmdp[++cpos]))
1563                                 ;
1564                         c = cmdp[cpos];
1565                         cmdp[cpos] = '\0';
1566                         yylval.i = atoi(cp);
1567                         cmdp[cpos] = c;
1568                         return (NUMBER);
1569                 }
1570                 if (strncasecmp(&cmdp[cpos], "ALL", 3) == 0
1571                  && !isalnum(cmdp[cpos + 3])) {
1572                         yylval.s = xstrdup("ALL");
1573                         cpos += 3;
1574                         return ALL;
1575                 }
1576                 switch (cmdp[cpos++]) {
1577
1578                 case '\n':
1579                         state = EOLN;
1580                         return (CRLF);
1581
1582                 case ' ':
1583                         return (SP);
1584
1585                 case ',':
1586                         return (COMMA);
1587
1588                 case 'A':
1589                 case 'a':
1590                         return (A);
1591
1592                 case 'B':
1593                 case 'b':
1594                         return (B);
1595
1596                 case 'C':
1597                 case 'c':
1598                         return (C);
1599
1600                 case 'E':
1601                 case 'e':
1602                         return (E);
1603
1604                 case 'F':
1605                 case 'f':
1606                         return (F);
1607
1608                 case 'I':
1609                 case 'i':
1610                         return (I);
1611
1612                 case 'L':
1613                 case 'l':
1614                         return (L);
1615
1616                 case 'N':
1617                 case 'n':
1618                         return (N);
1619
1620                 case 'P':
1621                 case 'p':
1622                         return (P);
1623
1624                 case 'R':
1625                 case 'r':
1626                         return (R);
1627
1628                 case 'S':
1629                 case 's':
1630                         return (S);
1631
1632                 case 'T':
1633                 case 't':
1634                         return (T);
1635
1636                 }
1637                 break;
1638
1639         case NOARGS:
1640                 if (cmdp[cpos] == '\n') {
1641                         state = EOLN;
1642                         return (CRLF);
1643                 }
1644                 c = cmdp[cpos];
1645                 cmdp[cpos] = '\0';
1646                 reply(501, "'%s' command does not take any arguments.", cmdp);
1647                 hasyyerrored = 1;
1648                 cmdp[cpos] = c;
1649                 break;
1650
1651         case EOLN:
1652                 state = CMD;
1653                 return (0);
1654
1655         default:
1656                 fatal("Unknown state in scanner.");
1657         }
1658         yyerror(NULL);
1659         state = CMD;
1660         is_oob = 0;
1661         longjmp(errcatch, 0);
1662         /* NOTREACHED */
1663 }
1664
1665 /* ARGSUSED */
1666 void
1667 yyerror(char *s)
1668 {
1669         char *cp;
1670
1671         if (hasyyerrored || is_oob)
1672                 return;
1673         if ((cp = strchr(cmdp,'\n')) != NULL)
1674                 *cp = '\0';
1675         reply(500, "'%s': command not understood.", cmdp);
1676         hasyyerrored = 1;
1677 }
1678
1679 static void
1680 help(struct tab *ctab, const char *s)
1681 {
1682         struct tab *c;
1683         int width, NCMDS;
1684         char *htype;
1685
1686         if (ctab == sitetab)
1687                 htype = "SITE ";
1688         else
1689                 htype = "";
1690         width = 0, NCMDS = 0;
1691         for (c = ctab; c->name != NULL; c++) {
1692                 int len = strlen(c->name);
1693
1694                 if (len > width)
1695                         width = len;
1696                 NCMDS++;
1697         }
1698         width = (width + 8) &~ 7;
1699         if (s == 0) {
1700                 int i, j, w;
1701                 int columns, lines;
1702
1703                 reply(-214, "%s", "");
1704                 reply(0, "The following %scommands are recognized.", htype);
1705                 reply(0, "(`-' = not implemented, `+' = supports options)");
1706                 columns = 76 / width;
1707                 if (columns == 0)
1708                         columns = 1;
1709                 lines = (NCMDS + columns - 1) / columns;
1710                 for (i = 0; i < lines; i++) {
1711                         cprintf(stdout, "    ");
1712                         for (j = 0; j < columns; j++) {
1713                                 c = ctab + j * lines + i;
1714                                 cprintf(stdout, "%s", c->name);
1715                                 w = strlen(c->name);
1716                                 if (! CMD_IMPLEMENTED(c)) {
1717                                         CPUTC('-', stdout);
1718                                         w++;
1719                                 }
1720                                 if (CMD_HAS_OPTIONS(c)) {
1721                                         CPUTC('+', stdout);
1722                                         w++;
1723                                 }
1724                                 if (c + lines >= &ctab[NCMDS])
1725                                         break;
1726                                 while (w < width) {
1727                                         CPUTC(' ', stdout);
1728                                         w++;
1729                                 }
1730                         }
1731                         cprintf(stdout, "\r\n");
1732                 }
1733                 (void) fflush(stdout);
1734                 reply(214, "Direct comments to ftp-bugs@%s.", hostname);
1735                 return;
1736         }
1737         c = lookup(ctab, s);
1738         if (c == (struct tab *)0) {
1739                 reply(502, "Unknown command %s.", s);
1740                 return;
1741         }
1742         if (CMD_IMPLEMENTED(c))
1743                 reply(214, "Syntax: %s%s %s", htype, c->name, c->help);
1744         else
1745                 reply(214, "%s%-*s\t%s; not implemented.", htype, width,
1746                     c->name, c->help);
1747 }
1748
1749 /*
1750  * Check that the structures used for a PORT, LPRT or EPRT command are
1751  * valid (data_dest, his_addr), and if necessary, detect ftp bounce attacks.
1752  * If family != -1 check that his_addr.su_family == family.
1753  */
1754 static void
1755 port_check(const char *cmd, int family)
1756 {
1757         char h1[NI_MAXHOST], h2[NI_MAXHOST];
1758         char s1[NI_MAXHOST], s2[NI_MAXHOST];
1759 #ifdef NI_WITHSCOPEID
1760         const int niflags = NI_NUMERICHOST | NI_NUMERICSERV | NI_WITHSCOPEID;
1761 #else
1762         const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
1763 #endif
1764
1765         if (epsvall) {
1766                 reply(501, "%s disallowed after EPSV ALL", cmd);
1767                 return;
1768         }
1769
1770         if (family != -1 && his_addr.su_family != family) {
1771  port_check_fail:
1772                 reply(500, "Illegal %s command rejected", cmd);
1773                 return;
1774         }
1775
1776         if (data_dest.su_family != his_addr.su_family)
1777                 goto port_check_fail;
1778
1779                         /* be paranoid, if told so */
1780         if (CURCLASS_FLAGS_ISSET(checkportcmd)) {
1781 #ifdef INET6
1782                 /*
1783                  * be paranoid, there are getnameinfo implementation that does
1784                  * not present scopeid portion
1785                  */
1786                 if (data_dest.su_family == AF_INET6 &&
1787                     data_dest.su_scope_id != his_addr.su_scope_id)
1788                         goto port_check_fail;
1789 #endif
1790
1791                 if (getnameinfo((struct sockaddr *)&data_dest, data_dest.su_len,
1792                     h1, sizeof(h1), s1, sizeof(s1), niflags))
1793                         goto port_check_fail;
1794                 if (getnameinfo((struct sockaddr *)&his_addr, his_addr.su_len,
1795                     h2, sizeof(h2), s2, sizeof(s2), niflags))
1796                         goto port_check_fail;
1797
1798                 if (atoi(s1) < IPPORT_RESERVED || strcmp(h1, h2) != 0)
1799                         goto port_check_fail;
1800         }
1801
1802         usedefault = 0;
1803         if (pdata >= 0) {
1804                 (void) close(pdata);
1805                 pdata = -1;
1806         }
1807         reply(200, "%s command successful.", cmd);
1808 }