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