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