1 /*        $NetBSD: cmds.c,v 1.35 2016/01/17 14:46:07 christos Exp $   */
2 
3 /*
4  * Copyright (c) 1999-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, 1990, 1992, 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 
61 /*
62  * Copyright (C) 1997 and 1998 WIDE Project.
63  * All rights reserved.
64  *
65  * Redistribution and use in source and binary forms, with or without
66  * modification, are permitted provided that the following conditions
67  * are met:
68  * 1. Redistributions of source code must retain the above copyright
69  *    notice, this list of conditions and the following disclaimer.
70  * 2. Redistributions in binary form must reproduce the above copyright
71  *    notice, this list of conditions and the following disclaimer in the
72  *    documentation and/or other materials provided with the distribution.
73  * 3. Neither the name of the project nor the names of its contributors
74  *    may be used to endorse or promote products derived from this software
75  *    without specific prior written permission.
76  *
77  * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
78  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
79  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
80  * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
81  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
82  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
83  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
84  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
85  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
86  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
87  * SUCH DAMAGE.
88  */
89 
90 
91 #include <sys/cdefs.h>
92 #ifndef lint
93 __RCSID("$NetBSD: cmds.c,v 1.35 2016/01/17 14:46:07 christos Exp $");
94 #endif /* not lint */
95 
96 #include <sys/param.h>
97 #include <sys/stat.h>
98 
99 #include <arpa/ftp.h>
100 
101 #include <dirent.h>
102 #include <errno.h>
103 #include <stdio.h>
104 #include <stdlib.h>
105 #include <string.h>
106 #include <tzfile.h>
107 #include <unistd.h>
108 #include <ctype.h>
109 
110 #ifdef KERBEROS5
111 #include <krb5/krb5.h>
112 #endif
113 
114 #include "extern.h"
115 
116 typedef enum {
117           FE_MLSD             = 1<<0,             /* if op is MLSD (MLST otherwise ) */
118           FE_ISCURDIR         = 1<<1,             /* if name is the current directory */
119 } factflag_t;
120 
121 typedef struct {
122           const char          *path;              /* full pathname */
123           const char          *display; /* name to display */
124           struct stat         *stat;              /* stat of path */
125           struct stat         *pdirstat;          /* stat of path's parent dir */
126           factflag_t           flags;             /* flags */
127 } factelem;
128 
129 static void         ack(const char *);
130 static void         base64_encode(const char *, size_t, char *, int);
131 static void         fact_type(const char *, FILE *, factelem *);
132 static void         fact_size(const char *, FILE *, factelem *);
133 static void         fact_modify(const char *, FILE *, factelem *);
134 static void         fact_perm(const char *, FILE *, factelem *);
135 static void         fact_unique(const char *, FILE *, factelem *);
136 static int          matchgroup(gid_t);
137 static void         mlsname(FILE *, factelem *);
138 static void         replydirname(const char *, const char *);
139 
140 struct ftpfact {
141           const char           *name;             /* name of fact */
142           int                   enabled;          /* if fact is enabled */
143           void                (*display)(const char *, FILE *, factelem *);
144                                                   /* function to display fact */
145 };
146 
147 struct ftpfact facttab[] = {
148           { "Type", 1, fact_type },
149 #define   FACT_TYPE 0
150           { "Size", 1, fact_size },
151           { "Modify",         1, fact_modify },
152           { "Perm", 1, fact_perm },
153           { "Unique",         1, fact_unique },
154           /* "Create" */
155           /* "Lang" */
156           /* "Media-Type" */
157           /* "CharSet" */
158 };
159 
160 #define FACTTABSIZE (sizeof(facttab) / sizeof(struct ftpfact))
161 
162 static char cached_path[MAXPATHLEN + 1] = "/";
163 static void discover_path(char *, const char *);
164 
165 void
cwd(const char * path)166 cwd(const char *path)
167 {
168 
169           if (chdir(path) < 0)
170                     perror_reply(550, path);
171           else {
172                     show_chdir_messages(250);
173                     ack("CWD");
174                     if (getcwd(cached_path, MAXPATHLEN) == NULL) {
175                               discover_path(cached_path, path);
176                     }
177           }
178 }
179 
180 void
delete(const char * name)181 delete(const char *name)
182 {
183           char *p = NULL;
184 
185           if (remove(name) < 0) {
186                     p = strerror(errno);
187                     perror_reply(550, name);
188           } else
189                     ack("DELE");
190           logxfer("delete", -1, name, NULL, NULL, p);
191 }
192 
193 void
feat(void)194 feat(void)
195 {
196           size_t i;
197 
198           reply(-211, "Features supported");
199           cprintf(stdout, " MDTM\r\n");
200           cprintf(stdout, " MLST ");
201           for (i = 0; i < FACTTABSIZE; i++)
202                     cprintf(stdout, "%s%s;", facttab[i].name,
203                         facttab[i].enabled ? "*" : "");
204           cprintf(stdout, "\r\n");
205           cprintf(stdout, " REST STREAM\r\n");
206           cprintf(stdout, " SIZE\r\n");
207           cprintf(stdout, " TVFS\r\n");
208           reply(211,  "End");
209 }
210 
211 void
makedir(const char * name)212 makedir(const char *name)
213 {
214           char *p = NULL;
215 
216           if (mkdir(name, 0777) < 0) {
217                     p = strerror(errno);
218                     perror_reply(550, name);
219           } else
220                     replydirname(name, "directory created.");
221           logxfer("mkdir", -1, name, NULL, NULL, p);
222 }
223 
224 void
mlsd(const char * path)225 mlsd(const char *path)
226 {
227           struct dirent       *dp;
228           struct stat          sb, pdirstat;
229           factelem f;
230           FILE      *dout;
231           DIR       *dirp;
232           char      name[MAXPATHLEN];
233           int       hastypefact;
234 
235           hastypefact = facttab[FACT_TYPE].enabled;
236           if (path == NULL)
237                     path = ".";
238           if (stat(path, &pdirstat) == -1) {
239  mlsdperror:
240                     perror_reply(550, path);
241                     return;
242           }
243           if (! S_ISDIR(pdirstat.st_mode)) {
244                     errno = ENOTDIR;
245                     perror_reply(501, path);
246                     return;
247           }
248           if ((dirp = opendir(path)) == NULL)
249                     goto mlsdperror;
250 
251           dout = dataconn("MLSD", (off_t)-1, "w");
252           if (dout == NULL) {
253                     (void) closedir(dirp);
254                     return;
255           }
256 
257           memset(&f, 0, sizeof(f));
258           f.stat = &sb;
259           f.flags |= FE_MLSD;
260           while ((dp = readdir(dirp)) != NULL) {
261                     snprintf(name, sizeof(name), "%s/%s", path, dp->d_name);
262                     if (ISDOTDIR(dp->d_name)) {   /* special case curdir: */
263                               if (! hastypefact)
264                                         continue;
265                               f.pdirstat = NULL;  /*   require stat of parent */
266                               f.display = path;   /*   set name to real name */
267                               f.flags |= FE_ISCURDIR; /*   flag name is curdir */
268                     } else {
269                               if (ISDOTDOTDIR(dp->d_name)) {
270                                         if (! hastypefact)
271                                                   continue;
272                                         f.pdirstat = NULL;
273                               } else
274                                         f.pdirstat = &pdirstat;       /* cache parent stat */
275                               f.display = dp->d_name;
276                               f.flags &= ~FE_ISCURDIR;
277                     }
278                     if (stat(name, &sb) == -1)
279                               continue;
280                     f.path = name;
281                     mlsname(dout, &f);
282           }
283           (void)closedir(dirp);
284 
285           if (ferror(dout) != 0)
286                     perror_reply(550, "Data connection");
287           else
288                     reply(226, "MLSD complete.");
289           closedataconn(dout);
290           total_xfers_out++;
291           total_xfers++;
292 }
293 
294 void
mlst(const char * path)295 mlst(const char *path)
296 {
297           struct stat sb;
298           factelem f;
299 
300           if (path == NULL)
301                     path = ".";
302           if (stat(path, &sb) == -1) {
303                     perror_reply(550, path);
304                     return;
305           }
306           reply(-250, "MLST %s", path);
307           memset(&f, 0, sizeof(f));
308           f.path = path;
309           f.display = path;
310           f.stat = &sb;
311           f.pdirstat = NULL;
312           CPUTC(' ', stdout);
313           mlsname(stdout, &f);
314           reply(250, "End");
315 }
316 
317 
318 void
opts(const char * command)319 opts(const char *command)
320 {
321           struct tab *c;
322           char *ep;
323 
324           if ((ep = strchr(command, ' ')) != NULL)
325                     *ep++ = '\0';
326           c = lookup(cmdtab, command);
327           if (c == NULL) {
328                     reply(502, "Unknown command '%s'.", command);
329                     return;
330           }
331           if (! CMD_IMPLEMENTED(c)) {
332                     reply(502, "%s command not implemented.", c->name);
333                     return;
334           }
335           if (! CMD_HAS_OPTIONS(c)) {
336                     reply(501, "%s command does not support persistent options.",
337                         c->name);
338                     return;
339           }
340 
341                               /* special case: MLST */
342           if (strcasecmp(command, "MLST") == 0) {
343                     int        enabled[FACTTABSIZE];
344                     size_t     i, onedone;
345                     size_t     len;
346                     char      *p;
347 
348                     for (i = 0; i < sizeof(enabled) / sizeof(int); i++)
349                               enabled[i] = 0;
350                     if (ep == NULL || *ep == '\0')
351                               goto displaymlstopts;
352 
353                                         /* don't like spaces, and need trailing ; */
354                     len = strlen(ep);
355                     if (strchr(ep, ' ') != NULL || ep[len - 1] != ';') {
356  badmlstopt:
357                               reply(501, "Invalid MLST options");
358                               return;
359                     }
360                     ep[len - 1] = '\0';
361                     while ((p = strsep(&ep, ";")) != NULL) {
362                               if (*p == '\0')
363                                         goto badmlstopt;
364                               for (i = 0; i < FACTTABSIZE; i++)
365                                         if (strcasecmp(p, facttab[i].name) == 0) {
366                                                   enabled[i] = 1;
367                                                   break;
368                                         }
369                     }
370 
371  displaymlstopts:
372                     for (i = 0; i < FACTTABSIZE; i++)
373                               facttab[i].enabled = enabled[i];
374                     cprintf(stdout, "200 MLST OPTS");
375                     for (i = onedone = 0; i < FACTTABSIZE; i++) {
376                               if (facttab[i].enabled) {
377                                         cprintf(stdout, "%s%s;", onedone ? "" : " ",
378                                             facttab[i].name);
379                                         onedone++;
380                               }
381                     }
382                     cprintf(stdout, "\r\n");
383                     fflush(stdout);
384                     return;
385           }
386 
387                               /* default cases */
388           if (ep != NULL && *ep != '\0')
389                     REASSIGN(c->options, ftpd_strdup(ep));
390           if (c->options != NULL)
391                     reply(200, "Options for %s are '%s'.", c->name,
392                         c->options);
393           else
394                     reply(200, "No options defined for %s.", c->name);
395 }
396 
397 void
pwd(void)398 pwd(void)
399 {
400           char path[MAXPATHLEN];
401 
402           if (getcwd(path, sizeof(path) - 1) == NULL) {
403                     if (chdir(cached_path) < 0) {
404                               reply(550, "Can't get the current directory: %s.",
405                                   strerror(errno));
406                               return;
407                     }
408                     (void)strlcpy(path, cached_path, MAXPATHLEN);
409           }
410           replydirname(path, "is the current directory.");
411 }
412 
413 void
removedir(const char * name)414 removedir(const char *name)
415 {
416           char *p = NULL;
417 
418           if (rmdir(name) < 0) {
419                     p = strerror(errno);
420                     perror_reply(550, name);
421           } else
422                     ack("RMD");
423           logxfer("rmdir", -1, name, NULL, NULL, p);
424 }
425 
426 char *
renamefrom(const char * name)427 renamefrom(const char *name)
428 {
429           struct stat st;
430 
431           if (stat(name, &st) < 0) {
432                     perror_reply(550, name);
433                     return (NULL);
434           }
435           reply(350, "File exists, ready for destination name");
436           return (ftpd_strdup(name));
437 }
438 
439 void
renamecmd(const char * from,const char * to)440 renamecmd(const char *from, const char *to)
441 {
442           char *p = NULL;
443 
444           if (rename(from, to) < 0) {
445                     p = strerror(errno);
446                     perror_reply(550, "rename");
447           } else
448                     ack("RNTO");
449           logxfer("rename", -1, from, to, NULL, p);
450 }
451 
452 void
sizecmd(const char * filename)453 sizecmd(const char *filename)
454 {
455           switch (type) {
456           case TYPE_L:
457           case TYPE_I:
458               {
459                     struct stat stbuf;
460                     if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
461                               reply(550, "%s: not a plain file.", filename);
462                     else
463                               reply(213, ULLF, (ULLT)stbuf.st_size);
464                     break;
465               }
466           case TYPE_A:
467               {
468                     FILE *fin;
469                     int c;
470                     off_t count;
471                     struct stat stbuf;
472                     fin = fopen(filename, "r");
473                     if (fin == NULL) {
474                               perror_reply(550, filename);
475                               return;
476                     }
477                     if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
478                               reply(550, "%s: not a plain file.", filename);
479                               (void) fclose(fin);
480                               return;
481                     }
482                     if (stbuf.st_size > 10240) {
483                               reply(550, "%s: file too large for SIZE.", filename);
484                               (void) fclose(fin);
485                               return;
486                     }
487 
488                     count = 0;
489                     while((c = getc(fin)) != EOF) {
490                               if (c == '\n')      /* will get expanded to \r\n */
491                                         count++;
492                               count++;
493                     }
494                     (void) fclose(fin);
495 
496                     reply(213, LLF, (LLT)count);
497                     break;
498               }
499           default:
500                     reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
501           }
502 }
503 
504 void
statfilecmd(const char * filename)505 statfilecmd(const char *filename)
506 {
507           FILE *fin;
508           int c;
509           int atstart;
510           const char *argv[] = { INTERNAL_LS, "-lgA", "", NULL };
511 
512           argv[2] = filename;
513           fin = ftpd_popen(argv, "r", STDOUT_FILENO);
514           reply(-211, "status of %s:", filename);
515 /* XXX: use fgetln() or fparseln() here? */
516           atstart = 1;
517           while ((c = getc(fin)) != EOF) {
518                     if (c == '\n') {
519                               if (ferror(stdout)){
520                                         perror_reply(421, "control connection");
521                                         (void) ftpd_pclose(fin);
522                                         dologout(1);
523                                         /* NOTREACHED */
524                               }
525                               if (ferror(fin)) {
526                                         perror_reply(551, filename);
527                                         (void) ftpd_pclose(fin);
528                                         return;
529                               }
530                               CPUTC('\r', stdout);
531                     }
532                     if (atstart && isdigit(c))
533                               CPUTC(' ', stdout);
534                     CPUTC(c, stdout);
535                     atstart = (c == '\n');
536           }
537           (void) ftpd_pclose(fin);
538           reply(211, "End of Status");
539 }
540 
541 /* -- */
542 
543 static void
ack(const char * s)544 ack(const char *s)
545 {
546 
547           reply(250, "%s command successful.", s);
548 }
549 
550 /*
551  * Encode len bytes starting at clear using base64 encoding into encoded,
552  * which should be at least ((len + 2) * 4 / 3 + 1) in size.
553  * If nulterm is non-zero, terminate with \0 otherwise pad to 3 byte boundary
554  * with `='.
555  */
556 static void
base64_encode(const char * clear,size_t len,char * encoded,int nulterm)557 base64_encode(const char *clear, size_t len, char *encoded, int nulterm)
558 {
559           static const char base64[] =
560               "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
561           const char *c;
562           char      *e, termchar;
563           int        i;
564 
565                               /* determine whether to pad with '=' or NUL terminate */
566           termchar = nulterm ? '\0' : '=';
567           c = clear;
568           e = encoded;
569                               /* convert all but last 2 bytes */
570           for (i = len; i > 2; i -= 3, c += 3) {
571                     *e++ = base64[(c[0] >> 2) & 0x3f];
572                     *e++ = base64[((c[0] << 4) & 0x30) | ((c[1] >> 4) & 0x0f)];
573                     *e++ = base64[((c[1] << 2) & 0x3c) | ((c[2] >> 6) & 0x03)];
574                     *e++ = base64[(c[2]) & 0x3f];
575           }
576                               /* handle slop at end */
577           if (i > 0) {
578                     *e++ = base64[(c[0] >> 2) & 0x3f];
579                     *e++ = base64[((c[0] << 4) & 0x30) |
580                          (i > 1 ? ((c[1] >> 4) & 0x0f) : 0)];
581                     *e++ = (i > 1) ? base64[(c[1] << 2) & 0x3c] : termchar;
582                     *e++ = termchar;
583           }
584           *e = '\0';
585 }
586 
587 static void
fact_modify(const char * fact,FILE * fd,factelem * fe)588 fact_modify(const char *fact, FILE *fd, factelem *fe)
589 {
590           struct tm *t;
591 
592           t = gmtime(&(fe->stat->st_mtime));
593           cprintf(fd, "%s=%04d%02d%02d%02d%02d%02d;", fact,
594               TM_YEAR_BASE + t->tm_year,
595               t->tm_mon+1, t->tm_mday,
596               t->tm_hour, t->tm_min, t->tm_sec);
597 }
598 
599 static void
fact_perm(const char * fact,FILE * fd,factelem * fe)600 fact_perm(const char *fact, FILE *fd, factelem *fe)
601 {
602           int                 rok, wok, xok, pdirwok;
603           struct stat         *pdir, dir;
604 
605           if (fe->stat->st_uid == geteuid()) {
606                     rok = ((fe->stat->st_mode & S_IRUSR) != 0);
607                     wok = ((fe->stat->st_mode & S_IWUSR) != 0);
608                     xok = ((fe->stat->st_mode & S_IXUSR) != 0);
609           } else if (matchgroup(fe->stat->st_gid)) {
610                     rok = ((fe->stat->st_mode & S_IRGRP) != 0);
611                     wok = ((fe->stat->st_mode & S_IWGRP) != 0);
612                     xok = ((fe->stat->st_mode & S_IXGRP) != 0);
613           } else {
614                     rok = ((fe->stat->st_mode & S_IROTH) != 0);
615                     wok = ((fe->stat->st_mode & S_IWOTH) != 0);
616                     xok = ((fe->stat->st_mode & S_IXOTH) != 0);
617           }
618 
619           cprintf(fd, "%s=", fact);
620 
621                               /*
622                                * if parent info not provided, look it up, but
623                                * only if the current class has modify rights,
624                                * since we only need this info in such a case.
625                                */
626           pdir = fe->pdirstat;
627           if (pdir == NULL && CURCLASS_FLAGS_ISSET(modify)) {
628                     size_t              len;
629                     char                realdir[MAXPATHLEN], *p;
630 
631                     len = strlcpy(realdir, fe->path, sizeof(realdir));
632                     if (len < sizeof(realdir) - 4) {
633                               if (S_ISDIR(fe->stat->st_mode))
634                                         strlcat(realdir, "/..", sizeof(realdir));
635                               else {
636                                                   /* if has a /, move back to it */
637                                                   /* otherwise use '..' */
638                                         if ((p = strrchr(realdir, '/')) != NULL) {
639                                                   if (p == realdir)
640                                                             p++;
641                                                   *p = '\0';
642                                         } else
643                                                   strlcpy(realdir, "..", sizeof(realdir));
644                               }
645                               if (stat(realdir, &dir) == 0)
646                                         pdir = &dir;
647                     }
648           }
649           pdirwok = 0;
650           if (pdir != NULL) {
651                     if (pdir->st_uid == geteuid())
652                               pdirwok = ((pdir->st_mode & S_IWUSR) != 0);
653                     else if (matchgroup(pdir->st_gid))
654                               pdirwok = ((pdir->st_mode & S_IWGRP) != 0);
655                     else
656                               pdirwok = ((pdir->st_mode & S_IWOTH) != 0);
657           }
658 
659                               /* 'a': can APPE to file */
660           if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
661                     CPUTC('a', fd);
662 
663                               /* 'c': can create or append to files in directory */
664           if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
665                     CPUTC('c', fd);
666 
667                               /* 'd': can delete file or directory */
668           if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) {
669                     int candel;
670 
671                     candel = 1;
672                     if (S_ISDIR(fe->stat->st_mode)) {
673                               DIR *dirp;
674                               struct dirent *dp;
675 
676                               if ((dirp = opendir(fe->display)) == NULL)
677                                         candel = 0;
678                               else {
679                                         while ((dp = readdir(dirp)) != NULL) {
680                                                   if (ISDOTDIR(dp->d_name) ||
681                                                       ISDOTDOTDIR(dp->d_name))
682                                                             continue;
683                                                   candel = 0;
684                                                   break;
685                                         }
686                                         closedir(dirp);
687                               }
688                     }
689                     if (candel)
690                               CPUTC('d', fd);
691           }
692 
693                               /* 'e': can enter directory */
694           if (xok && S_ISDIR(fe->stat->st_mode))
695                     CPUTC('e', fd);
696 
697                               /* 'f': can rename file or directory */
698           if (pdirwok && CURCLASS_FLAGS_ISSET(modify))
699                     CPUTC('f', fd);
700 
701                               /* 'l': can list directory */
702           if (rok && xok && S_ISDIR(fe->stat->st_mode))
703                     CPUTC('l', fd);
704 
705                               /* 'm': can create directory */
706           if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
707                     CPUTC('m', fd);
708 
709                               /* 'p': can remove files in directory */
710           if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
711                     CPUTC('p', fd);
712 
713                               /* 'r': can RETR file */
714           if (rok && S_ISREG(fe->stat->st_mode))
715                     CPUTC('r', fd);
716 
717                               /* 'w': can STOR file */
718           if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
719                     CPUTC('w', fd);
720 
721           CPUTC(';', fd);
722 }
723 
724 static void
fact_size(const char * fact,FILE * fd,factelem * fe)725 fact_size(const char *fact, FILE *fd, factelem *fe)
726 {
727 
728           if (S_ISREG(fe->stat->st_mode))
729                     cprintf(fd, "%s=" LLF ";", fact, (LLT)fe->stat->st_size);
730 }
731 
732 static void
fact_type(const char * fact,FILE * fd,factelem * fe)733 fact_type(const char *fact, FILE *fd, factelem *fe)
734 {
735 
736           cprintf(fd, "%s=", fact);
737           switch (fe->stat->st_mode & S_IFMT) {
738           case S_IFDIR:
739                     if (fe->flags & FE_MLSD) {
740                               if ((fe->flags & FE_ISCURDIR) || ISDOTDIR(fe->display))
741                                         cprintf(fd, "cdir");
742                               else if (ISDOTDOTDIR(fe->display))
743                                         cprintf(fd, "pdir");
744                               else
745                                         cprintf(fd, "dir");
746                     } else {
747                               cprintf(fd, "dir");
748                     }
749                     break;
750           case S_IFREG:
751                     cprintf(fd, "file");
752                     break;
753           case S_IFIFO:
754                     cprintf(fd, "OS.unix=fifo");
755                     break;
756           case S_IFLNK:                 /* XXX: probably a NO-OP with stat() */
757                     cprintf(fd, "OS.unix=slink");
758                     break;
759           case S_IFSOCK:
760                     cprintf(fd, "OS.unix=socket");
761                     break;
762           case S_IFBLK:
763           case S_IFCHR:
764                     cprintf(fd, "OS.unix=%s-" ULLF "/" ULLF,
765                         S_ISBLK(fe->stat->st_mode) ? "blk" : "chr",
766                         (ULLT)major(fe->stat->st_rdev),
767                         (ULLT)minor(fe->stat->st_rdev));
768                     break;
769           default:
770                     cprintf(fd, "OS.unix=UNKNOWN(0%o)", fe->stat->st_mode & S_IFMT);
771                     break;
772           }
773           CPUTC(';', fd);
774 }
775 
776 static void
fact_unique(const char * fact,FILE * fd,factelem * fe)777 fact_unique(const char *fact, FILE *fd, factelem *fe)
778 {
779           char obuf[(sizeof(dev_t) + sizeof(ino_t) + 2) * 4 / 3 + 2];
780           char tbuf[sizeof(dev_t) + sizeof(ino_t)];
781 
782           memcpy(tbuf,
783               (char *)&(fe->stat->st_dev), sizeof(dev_t));
784           memcpy(tbuf + sizeof(dev_t),
785               (char *)&(fe->stat->st_ino), sizeof(ino_t));
786           base64_encode(tbuf, sizeof(dev_t) + sizeof(ino_t), obuf, 1);
787           cprintf(fd, "%s=%s;", fact, obuf);
788 }
789 
790 static int
matchgroup(gid_t gid)791 matchgroup(gid_t gid)
792 {
793           int       i;
794 
795           for (i = 0; i < gidcount; i++)
796                     if (gid == gidlist[i])
797                               return(1);
798           return (0);
799 }
800 
801 static void
mlsname(FILE * fp,factelem * fe)802 mlsname(FILE *fp, factelem *fe)
803 {
804           char realfile[MAXPATHLEN];
805           int userf = 0;
806           size_t i;
807 
808           for (i = 0; i < FACTTABSIZE; i++) {
809                     if (facttab[i].enabled)
810                               (facttab[i].display)(facttab[i].name, fp, fe);
811           }
812           if ((fe->flags & FE_MLSD) &&
813               !(fe->flags & FE_ISCURDIR) && !ISDOTDIR(fe->display)) {
814                               /* if MLSD and not "." entry, display as-is */
815                     userf = 0;
816           } else {
817                               /* if MLST, or MLSD and "." entry, realpath(3) it */
818                     if (realpath(fe->display, realfile) != NULL)
819                               userf = 1;
820           }
821           cprintf(fp, " %s\r\n", userf ? realfile : fe->display);
822 }
823 
824 static void
replydirname(const char * name,const char * message)825 replydirname(const char *name, const char *message)
826 {
827           char *p, *ep;
828           char npath[MAXPATHLEN * 2];
829 
830           p = npath;
831           ep = &npath[sizeof(npath) - 1];
832           while (*name) {
833                     if (*name == '"') {
834                               if (ep - p < 2)
835                                         break;
836                               *p++ = *name++;
837                               *p++ = '"';
838                     } else {
839                               if (ep - p < 1)
840                                         break;
841                               *p++ = *name++;
842                     }
843           }
844           *p = '\0';
845           reply(257, "\"%s\" %s", npath, message);
846 }
847 
848 static void
discover_path(char * last_path,const char * new_path)849 discover_path(char *last_path, const char *new_path)
850 {
851           char tp[MAXPATHLEN + 1] = "";
852           char tq[MAXPATHLEN + 1] = "";
853           char *cp;
854           char *cq;
855           int sz1, sz2;
856           int nomorelink;
857           struct stat st1, st2;
858 
859           if (new_path[0] != '/') {
860                     (void)strlcpy(tp, last_path, MAXPATHLEN);
861                     (void)strlcat(tp, "/", MAXPATHLEN);
862           }
863           (void)strlcat(tp, new_path, MAXPATHLEN);
864           (void)strlcat(tp, "/", MAXPATHLEN);
865 
866           /*
867            * resolve symlinks. A symlink may introduce another symlink, so we
868            * loop trying to resolve symlinks until we don't find any of them.
869            */
870           do {
871                     /* Collapse any // into / */
872                     while ((cp = strstr(tp, "//")) != NULL)
873                               (void)memmove(cp, cp + 1, strlen(cp) - 1 + 1);
874 
875                     /* Collapse any /./ into / */
876                     while ((cp = strstr(tp, "/./")) != NULL)
877                               (void)memmove(cp, cp + 2, strlen(cp) - 2 + 1);
878 
879                     cp = tp;
880                     nomorelink = 1;
881 
882                     while ((cp = strstr(cp + 1, "/")) != NULL) {
883                               sz1 = (unsigned long)cp - (unsigned long)tp;
884                               if (sz1 > MAXPATHLEN)
885                                         goto bad;
886                               *cp = 0;
887                               sz2 = readlink(tp, tq, MAXPATHLEN);
888                               *cp = '/';
889 
890                               /* If this is not a symlink, move to next / */
891                               if (sz2 <= 0)
892                                         continue;
893 
894                               /*
895                                * We found a symlink, so we will have to
896                                * do one more pass to check there is no
897                                * more symlink in the path
898                                */
899                               nomorelink = 0;
900 
901                               /*
902                                * Null terminate the string and remove trailing /
903                                */
904                               tq[sz2] = 0;
905                               sz2 = strlen(tq);
906                               if (tq[sz2 - 1] == '/')
907                                         tq[--sz2] = 0;
908 
909                               /*
910                                * Is this an absolute link or a relative link?
911                                */
912                               if (tq[0] == '/') {
913                                         /* absolute link */
914                                         if (strlen(cp) + sz2 > MAXPATHLEN)
915                                                   goto bad;
916                                         memmove(tp + sz2, cp, strlen(cp) + 1);
917                                         memcpy(tp, tq, sz2);
918                               } else {
919                                         /* relative link */
920                                         for (cq = cp - 1; *cq != '/'; cq--);
921                                         if (strlen(tp) -
922                                             ((unsigned long)cq - (unsigned long)cp)
923                                             + 1 + sz2 > MAXPATHLEN)
924                                                   goto bad;
925                                         (void)memmove(cq + 1 + sz2,
926                                             cp, strlen(cp) + 1);
927                                         (void)memcpy(cq + 1, tq, sz2);
928                               }
929 
930                               /*
931                                * start over, looking for new symlinks
932                                */
933                               break;
934                     }
935           } while (nomorelink == 0);
936 
937           /* Collapse any /foo/../ into /foo/ */
938           while ((cp = strstr(tp, "/../")) != NULL) {
939                     /* ^/../foo/ becomes ^/foo/ */
940                     if (cp == tp) {
941                               (void)memmove(cp, cp + 3,
942                                   strlen(cp) - 3 + 1);
943                     } else {
944                               for (cq = cp - 1; *cq != '/'; cq--);
945                               (void)memmove(cq, cp + 3,
946                                   strlen(cp) - 3 + 1);
947                     }
948           }
949 
950           /* strip strailing / */
951           if (strlen(tp) != 1)
952                     tp[strlen(tp) - 1] = '\0';
953 
954           /* check that the path is correct */
955           if (stat(tp, &st1) == -1 || stat(".", &st2) == -1)
956                     goto bad;
957           if ((st1.st_dev != st2.st_dev) || (st1.st_ino != st2.st_ino))
958                     goto bad;
959 
960           (void)strlcpy(last_path, tp, MAXPATHLEN);
961           return;
962 
963 bad:
964           (void)strlcat(last_path, "/", MAXPATHLEN);
965           (void)strlcat(last_path, new_path, MAXPATHLEN);
966           return;
967 }
968 
969