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