1 /* $NetBSD: cmds.c,v 1.16 2002/02/13 15:15:23 lukem Exp $ */
4 * Copyright (c) 1999-2001 The NetBSD Foundation, Inc.
7 * This code is derived from software contributed to The NetBSD Foundation
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
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.
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.
40 * Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994
41 * The Regents of the University of California. All rights reserved.
43 * Redistribution and use in source and binary forms, with or without
44 * modification, are permitted provided that the following conditions
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.
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
73 * Copyright (C) 1997 and 1998 WIDE Project.
74 * All rights reserved.
76 * Redistribution and use in source and binary forms, with or without
77 * modification, are permitted provided that the following conditions
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.
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
101 #include "lukemftpd.h"
106 FE_MLSD = 1<<0, /* if op is MLSD (MLST otherwise ) */
107 FE_ISCURDIR = 1<<1, /* if name is the current directory */
111 const char *path; /* full pathname */
112 const char *display; /* name to display */
113 struct stat *stat; /* stat of path */
114 struct stat *pdirstat; /* stat of path's parent dir */
115 factflag_t flags; /* flags */
118 static void ack(const char *);
119 static void base64_encode(const char *, size_t, char *, int);
120 static void fact_type(const char *, FILE *, factelem *);
121 static void fact_size(const char *, FILE *, factelem *);
122 static void fact_modify(const char *, FILE *, factelem *);
123 static void fact_perm(const char *, FILE *, factelem *);
124 static void fact_unique(const char *, FILE *, factelem *);
125 static int matchgroup(gid_t);
126 static void mlsname(FILE *, factelem *);
127 static void replydirname(const char *, const char *);
130 const char *name; /* name of fact */
131 int enabled; /* if fact is enabled */
132 void (*display)(const char *, FILE *, factelem *);
133 /* function to display fact */
136 struct ftpfact facttab[] = {
137 { "Type", 1, fact_type },
139 { "Size", 1, fact_size },
140 { "Modify", 1, fact_modify },
141 { "Perm", 1, fact_perm },
142 { "Unique", 1, fact_unique },
149 #define FACTTABSIZE (sizeof(facttab) / sizeof(struct ftpfact))
153 cwd(const char *path)
157 perror_reply(550, path);
159 show_chdir_messages(250);
165 delete(const char *name)
169 if (remove(name) < 0) {
171 perror_reply(550, name);
174 logxfer("delete", -1, name, NULL, NULL, p);
182 reply(-211, "Features supported");
183 cprintf(stdout, " MDTM\r\n");
184 cprintf(stdout, " MLST ");
185 for (i = 0; i < FACTTABSIZE; i++)
186 cprintf(stdout, "%s%s;", facttab[i].name,
187 facttab[i].enabled ? "*" : "");
188 cprintf(stdout, "\r\n");
189 cprintf(stdout, " REST STREAM\r\n");
190 cprintf(stdout, " SIZE\r\n");
191 cprintf(stdout, " TVFS\r\n");
196 makedir(const char *name)
200 if (mkdir(name, 0777) < 0) {
202 perror_reply(550, name);
204 replydirname(name, "directory created.");
205 logxfer("mkdir", -1, name, NULL, NULL, p);
209 mlsd(const char *path)
212 struct stat sb, pdirstat;
216 char name[MAXPATHLEN];
219 hastypefact = facttab[FACT_TYPE].enabled;
222 if (stat(path, &pdirstat) == -1) {
224 perror_reply(550, path);
227 if (! S_ISDIR(pdirstat.st_mode)) {
229 perror_reply(501, path);
232 if ((dirp = opendir(path)) == NULL)
235 dout = dataconn("MLSD", (off_t)-1, "w");
239 memset(&f, 0, sizeof(f));
242 while ((dp = readdir(dirp)) != NULL) {
243 snprintf(name, sizeof(name), "%s/%s", path, dp->d_name);
244 if (ISDOTDIR(dp->d_name)) { /* special case curdir: */
247 f.pdirstat = NULL; /* require stat of parent */
248 f.display = path; /* set name to real name */
249 f.flags |= FE_ISCURDIR; /* flag name is curdir */
251 if (ISDOTDOTDIR(dp->d_name)) {
256 f.pdirstat = &pdirstat; /* cache parent stat */
257 f.display = dp->d_name;
258 f.flags &= ~FE_ISCURDIR;
260 if (stat(name, &sb) == -1)
265 (void)closedir(dirp);
267 if (ferror(dout) != 0)
268 perror_reply(550, "Data connection");
270 reply(226, "MLSD complete.");
277 mlst(const char *path)
284 if (stat(path, &sb) == -1) {
285 perror_reply(550, path);
288 reply(-250, "MLST %s", path);
289 memset(&f, 0, sizeof(f));
301 opts(const char *command)
306 if ((ep = strchr(command, ' ')) != NULL)
308 c = lookup(cmdtab, command);
310 reply(502, "Unknown command %s.", command);
313 if (! CMD_IMPLEMENTED(c)) {
314 reply(501, "%s command not implemented.", c->name);
317 if (! CMD_HAS_OPTIONS(c)) {
318 reply(501, "%s command does not support persistent options.",
323 /* special case: MLST */
324 if (strcasecmp(command, "MLST") == 0) {
325 int enabled[FACTTABSIZE];
330 for (i = 0; i < sizeof(enabled) / sizeof(int); i++)
332 if (ep == NULL || *ep == '\0')
333 goto displaymlstopts;
335 /* don't like spaces, and need trailing ; */
337 if (strchr(ep, ' ') != NULL || ep[len - 1] != ';') {
339 reply(501, "Invalid MLST options");
343 while ((p = strsep(&ep, ";")) != NULL) {
346 for (i = 0; i < FACTTABSIZE; i++)
347 if (strcasecmp(p, facttab[i].name) == 0) {
354 for (i = 0; i < FACTTABSIZE; i++)
355 facttab[i].enabled = enabled[i];
356 cprintf(stdout, "200 MLST OPTS");
357 for (i = onedone = 0; i < FACTTABSIZE; i++) {
358 if (facttab[i].enabled) {
359 cprintf(stdout, "%s%s;", onedone ? "" : " ",
364 cprintf(stdout, "\r\n");
370 if (ep != NULL && *ep != '\0')
371 REASSIGN(c->options, xstrdup(ep));
372 if (c->options != NULL)
373 reply(200, "Options for %s are '%s'.", c->name,
376 reply(200, "No options defined for %s.", c->name);
382 char path[MAXPATHLEN];
384 if (getcwd(path, sizeof(path) - 1) == NULL)
385 reply(550, "Can't get the current directory: %s.",
388 replydirname(path, "is the current directory.");
392 removedir(const char *name)
396 if (rmdir(name) < 0) {
398 perror_reply(550, name);
401 logxfer("rmdir", -1, name, NULL, NULL, p);
405 renamefrom(const char *name)
409 if (stat(name, &st) < 0) {
410 perror_reply(550, name);
413 reply(350, "File exists, ready for destination name");
414 return (xstrdup(name));
418 renamecmd(const char *from, const char *to)
422 if (rename(from, to) < 0) {
424 perror_reply(550, "rename");
427 logxfer("rename", -1, from, to, NULL, p);
431 sizecmd(const char *filename)
438 if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
439 reply(550, "%s: not a plain file.", filename);
441 reply(213, ULLF, (ULLT)stbuf.st_size);
450 fin = fopen(filename, "r");
452 perror_reply(550, filename);
455 if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
456 reply(550, "%s: not a plain file.", filename);
460 if (stbuf.st_size > 10240) {
461 reply(550, "%s: file too large for SIZE.", filename);
467 while((c = getc(fin)) != EOF) {
468 if (c == '\n') /* will get expanded to \r\n */
474 reply(213, LLF, (LLT)count);
478 reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
483 statfilecmd(const char *filename)
487 char *argv[] = { INTERNAL_LS, "-lgA", "", NULL };
489 argv[2] = (char *)filename;
490 fin = ftpd_popen(argv, "r", STDOUT_FILENO);
491 reply(-211, "status of %s:", filename);
492 /* XXX: use fgetln() or fparseln() here? */
493 while ((c = getc(fin)) != EOF) {
496 perror_reply(421, "control connection");
497 (void) ftpd_pclose(fin);
502 perror_reply(551, filename);
503 (void) ftpd_pclose(fin);
510 (void) ftpd_pclose(fin);
511 reply(211, "End of Status");
520 reply(250, "%s command successful.", s);
524 * Encode len bytes starting at clear using base64 encoding into encoded,
525 * which should be at least ((len + 2) * 4 / 3 + 1) in size.
526 * If nulterm is non-zero, terminate with \0 otherwise pad to 3 byte boundary
530 base64_encode(const char *clear, size_t len, char *encoded, int nulterm)
532 static const char base64[] =
533 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
538 /* determine whether to pad with '=' or NUL terminate */
539 termchar = nulterm ? '\0' : '=';
542 /* convert all but last 2 bytes */
543 for (i = len; i > 2; i -= 3, c += 3) {
544 *e++ = base64[(c[0] >> 2) & 0x3f];
545 *e++ = base64[((c[0] << 4) & 0x30) | ((c[1] >> 4) & 0x0f)];
546 *e++ = base64[((c[1] << 2) & 0x3c) | ((c[2] >> 6) & 0x03)];
547 *e++ = base64[(c[2]) & 0x3f];
549 /* handle slop at end */
551 *e++ = base64[(c[0] >> 2) & 0x3f];
552 *e++ = base64[((c[0] << 4) & 0x30) |
553 (i > 1 ? ((c[1] >> 4) & 0x0f) : 0)];
554 *e++ = (i > 1) ? base64[(c[1] << 2) & 0x3c] : termchar;
561 fact_modify(const char *fact, FILE *fd, factelem *fe)
565 t = gmtime(&(fe->stat->st_mtime));
566 cprintf(fd, "%s=%04d%02d%02d%02d%02d%02d;", fact,
567 TM_YEAR_BASE + t->tm_year,
568 t->tm_mon+1, t->tm_mday,
569 t->tm_hour, t->tm_min, t->tm_sec);
573 fact_perm(const char *fact, FILE *fd, factelem *fe)
575 int rok, wok, xok, pdirwok;
578 if (fe->stat->st_uid == geteuid()) {
579 rok = ((fe->stat->st_mode & S_IRUSR) != 0);
580 wok = ((fe->stat->st_mode & S_IWUSR) != 0);
581 xok = ((fe->stat->st_mode & S_IXUSR) != 0);
582 } else if (matchgroup(fe->stat->st_gid)) {
583 rok = ((fe->stat->st_mode & S_IRGRP) != 0);
584 wok = ((fe->stat->st_mode & S_IWGRP) != 0);
585 xok = ((fe->stat->st_mode & S_IXGRP) != 0);
587 rok = ((fe->stat->st_mode & S_IROTH) != 0);
588 wok = ((fe->stat->st_mode & S_IWOTH) != 0);
589 xok = ((fe->stat->st_mode & S_IXOTH) != 0);
592 cprintf(fd, "%s=", fact);
595 * if parent info not provided, look it up, but
596 * only if the current class has modify rights,
597 * since we only need this info in such a case.
600 if (pdir == NULL && CURCLASS_FLAGS_ISSET(modify)) {
602 char realdir[MAXPATHLEN], *p;
605 len = strlcpy(realdir, fe->path, sizeof(realdir));
606 if (len < sizeof(realdir) - 4) {
607 if (S_ISDIR(fe->stat->st_mode))
608 strlcat(realdir, "/..", sizeof(realdir));
610 /* if has a /, move back to it */
611 /* otherwise use '..' */
612 if ((p = strrchr(realdir, '/')) != NULL) {
617 strlcpy(realdir, "..", sizeof(realdir));
619 if (stat(realdir, &dir) == 0)
625 if (pdir->st_uid == geteuid())
626 pdirwok = ((pdir->st_mode & S_IWUSR) != 0);
627 else if (matchgroup(pdir->st_gid))
628 pdirwok = ((pdir->st_mode & S_IWGRP) != 0);
630 pdirwok = ((pdir->st_mode & S_IWOTH) != 0);
633 /* 'a': can APPE to file */
634 if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
637 /* 'c': can create or append to files in directory */
638 if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
641 /* 'd': can delete file or directory */
642 if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) {
646 if (S_ISDIR(fe->stat->st_mode)) {
650 if ((dirp = opendir(fe->display)) == NULL)
653 while ((dp = readdir(dirp)) != NULL) {
654 if (ISDOTDIR(dp->d_name) ||
655 ISDOTDOTDIR(dp->d_name))
667 /* 'e': can enter directory */
668 if (xok && S_ISDIR(fe->stat->st_mode))
671 /* 'f': can rename file or directory */
672 if (pdirwok && CURCLASS_FLAGS_ISSET(modify))
675 /* 'l': can list directory */
676 if (rok && xok && S_ISDIR(fe->stat->st_mode))
679 /* 'm': can create directory */
680 if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
683 /* 'p': can remove files in directory */
684 if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
687 /* 'r': can RETR file */
688 if (rok && S_ISREG(fe->stat->st_mode))
691 /* 'w': can STOR file */
692 if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
699 fact_size(const char *fact, FILE *fd, factelem *fe)
702 if (S_ISREG(fe->stat->st_mode))
703 cprintf(fd, "%s=" LLF ";", fact, (LLT)fe->stat->st_size);
707 fact_type(const char *fact, FILE *fd, factelem *fe)
710 cprintf(fd, "%s=", fact);
711 switch (fe->stat->st_mode & S_IFMT) {
713 if (fe->flags & FE_MLSD) {
714 if ((fe->flags & FE_ISCURDIR) || ISDOTDIR(fe->display))
716 else if (ISDOTDOTDIR(fe->display))
728 cprintf(fd, "OS.unix=fifo");
730 case S_IFLNK: /* XXX: probably a NO-OP with stat() */
731 cprintf(fd, "OS.unix=slink");
734 cprintf(fd, "OS.unix=socket");
738 cprintf(fd, "OS.unix=%s-%d/%d",
739 S_ISBLK(fe->stat->st_mode) ? "blk" : "chr",
740 major(fe->stat->st_rdev), minor(fe->stat->st_rdev));
743 cprintf(fd, "OS.unix=UNKNOWN(0%o)", fe->stat->st_mode & S_IFMT);
750 fact_unique(const char *fact, FILE *fd, factelem *fe)
752 char obuf[(sizeof(dev_t) + sizeof(ino_t) + 2) * 4 / 3 + 2];
753 char tbuf[sizeof(dev_t) + sizeof(ino_t)];
756 (char *)&(fe->stat->st_dev), sizeof(dev_t));
757 memcpy(tbuf + sizeof(dev_t),
758 (char *)&(fe->stat->st_ino), sizeof(ino_t));
759 base64_encode(tbuf, sizeof(dev_t) + sizeof(ino_t), obuf, 1);
760 cprintf(fd, "%s=%s;", fact, obuf);
764 matchgroup(gid_t gid)
768 for (i = 0; i < gidcount; i++)
769 if (gid == gidlist[i])
775 mlsname(FILE *fp, factelem *fe)
777 char realfile[MAXPATHLEN];
780 for (i = 0; i < FACTTABSIZE; i++) {
781 if (facttab[i].enabled)
782 (facttab[i].display)(facttab[i].name, fp, fe);
784 if ((fe->flags & FE_MLSD) &&
785 !(fe->flags & FE_ISCURDIR) && !ISDOTDIR(fe->display)) {
786 /* if MLSD and not "." entry, display as-is */
789 /* if MLST, or MLSD and "." entry, realpath(3) it */
790 if (realpath(fe->display, realfile) != NULL)
793 cprintf(fp, " %s\r\n", userf ? realfile : fe->display);
797 replydirname(const char *name, const char *message)
800 char npath[MAXPATHLEN * 2];
803 ep = &npath[sizeof(npath) - 1];
817 reply(257, "\"%s\" %s", npath, message);