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