635a375704
* Fix yacc parser error recovery so that setjmp(3)/longjmp(3) is unnecessary. * Fix SIGURG handler to set an urgflag that's later tested, rather than abusing setjmp(3)/longjmp(3). * Use "volatile sig_atomic_t" as the type of variables modified by sig handlers. * Use sigaction(3) instead of signal(3) to set the signal handlers. * Only set the main SIGALRM handler once. If we need to change it, cache the old handler and restore appropriately... * Remove a bunch of signal races by improving the signal handlers. * Fix memory leak with 'ESPV ALL'. My stuff: * Clean up the debug message in reply(); use vsnprintf(3) instead of vsyslog(3). * Rework parsing of OOB commands to _not_ use the yacc parser, since the latter isn't reentrant and the hacks to work around that are ugly. We now examine urgflag at appropriate locations and call handleoobcmd() if it's set. Since the only OOB commands we currently implement are ABOR and STAT, this isn't an issue. (I also can't find the reference in RFC2228 where MIC, CONF & ENC are OOB-only commands. Go figure.) I could clean up the is_oob stuff some more, but the remaining stuff in ftpcmd.y is harmless and it's unnecessary churn right this moment.
974 lines
24 KiB
C
974 lines
24 KiB
C
/* $NetBSD: cmds.c,v 1.23 2004/08/09 12:56:47 lukem Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 1999-2004 The NetBSD Foundation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
* by Luke Mewburn.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by the NetBSD
|
|
* Foundation, Inc. and its contributors.
|
|
* 4. Neither the name of The NetBSD Foundation nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 1985, 1988, 1990, 1992, 1993, 1994
|
|
* The Regents of the University of California. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the University nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 1997 and 1998 WIDE Project.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the project nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
|
|
#include <sys/cdefs.h>
|
|
#ifndef lint
|
|
__RCSID("$NetBSD: cmds.c,v 1.23 2004/08/09 12:56:47 lukem Exp $");
|
|
#endif /* not lint */
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <arpa/ftp.h>
|
|
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <tzfile.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
|
|
#ifdef KERBEROS5
|
|
#include <krb5/krb5.h>
|
|
#endif
|
|
|
|
#include "extern.h"
|
|
|
|
typedef enum {
|
|
FE_MLSD = 1<<0, /* if op is MLSD (MLST otherwise ) */
|
|
FE_ISCURDIR = 1<<1, /* if name is the current directory */
|
|
} factflag_t;
|
|
|
|
typedef struct {
|
|
const char *path; /* full pathname */
|
|
const char *display; /* name to display */
|
|
struct stat *stat; /* stat of path */
|
|
struct stat *pdirstat; /* stat of path's parent dir */
|
|
factflag_t flags; /* flags */
|
|
} factelem;
|
|
|
|
static void ack(const char *);
|
|
static void base64_encode(const char *, size_t, char *, int);
|
|
static void fact_type(const char *, FILE *, factelem *);
|
|
static void fact_size(const char *, FILE *, factelem *);
|
|
static void fact_modify(const char *, FILE *, factelem *);
|
|
static void fact_perm(const char *, FILE *, factelem *);
|
|
static void fact_unique(const char *, FILE *, factelem *);
|
|
static int matchgroup(gid_t);
|
|
static void mlsname(FILE *, factelem *);
|
|
static void replydirname(const char *, const char *);
|
|
|
|
struct ftpfact {
|
|
const char *name; /* name of fact */
|
|
int enabled; /* if fact is enabled */
|
|
void (*display)(const char *, FILE *, factelem *);
|
|
/* function to display fact */
|
|
};
|
|
|
|
struct ftpfact facttab[] = {
|
|
{ "Type", 1, fact_type },
|
|
#define FACT_TYPE 0
|
|
{ "Size", 1, fact_size },
|
|
{ "Modify", 1, fact_modify },
|
|
{ "Perm", 1, fact_perm },
|
|
{ "Unique", 1, fact_unique },
|
|
/* "Create" */
|
|
/* "Lang" */
|
|
/* "Media-Type" */
|
|
/* "CharSet" */
|
|
};
|
|
|
|
#define FACTTABSIZE (sizeof(facttab) / sizeof(struct ftpfact))
|
|
|
|
static char cached_path[MAXPATHLEN + 1] = "/";
|
|
static void discover_path(char *, const char *);
|
|
|
|
void
|
|
cwd(const char *path)
|
|
{
|
|
|
|
if (chdir(path) < 0)
|
|
perror_reply(550, path);
|
|
else {
|
|
show_chdir_messages(250);
|
|
ack("CWD");
|
|
if (getcwd(cached_path, MAXPATHLEN) == NULL) {
|
|
discover_path(cached_path, path);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
delete(const char *name)
|
|
{
|
|
char *p = NULL;
|
|
|
|
if (remove(name) < 0) {
|
|
p = strerror(errno);
|
|
perror_reply(550, name);
|
|
} else
|
|
ack("DELE");
|
|
logxfer("delete", -1, name, NULL, NULL, p);
|
|
}
|
|
|
|
void
|
|
feat(void)
|
|
{
|
|
int i;
|
|
|
|
reply(-211, "Features supported");
|
|
cprintf(stdout, " MDTM\r\n");
|
|
cprintf(stdout, " MLST ");
|
|
for (i = 0; i < FACTTABSIZE; i++)
|
|
cprintf(stdout, "%s%s;", facttab[i].name,
|
|
facttab[i].enabled ? "*" : "");
|
|
cprintf(stdout, "\r\n");
|
|
cprintf(stdout, " REST STREAM\r\n");
|
|
cprintf(stdout, " SIZE\r\n");
|
|
cprintf(stdout, " TVFS\r\n");
|
|
reply(211, "End");
|
|
}
|
|
|
|
void
|
|
makedir(const char *name)
|
|
{
|
|
char *p = NULL;
|
|
|
|
if (mkdir(name, 0777) < 0) {
|
|
p = strerror(errno);
|
|
perror_reply(550, name);
|
|
} else
|
|
replydirname(name, "directory created.");
|
|
logxfer("mkdir", -1, name, NULL, NULL, p);
|
|
}
|
|
|
|
void
|
|
mlsd(const char *path)
|
|
{
|
|
struct dirent *dp;
|
|
struct stat sb, pdirstat;
|
|
factelem f;
|
|
FILE *dout;
|
|
DIR *dirp;
|
|
char name[MAXPATHLEN];
|
|
int hastypefact;
|
|
|
|
hastypefact = facttab[FACT_TYPE].enabled;
|
|
if (path == NULL)
|
|
path = ".";
|
|
if (stat(path, &pdirstat) == -1) {
|
|
mlsdperror:
|
|
perror_reply(550, path);
|
|
return;
|
|
}
|
|
if (! S_ISDIR(pdirstat.st_mode)) {
|
|
errno = ENOTDIR;
|
|
perror_reply(501, path);
|
|
return;
|
|
}
|
|
if ((dirp = opendir(path)) == NULL)
|
|
goto mlsdperror;
|
|
|
|
dout = dataconn("MLSD", (off_t)-1, "w");
|
|
if (dout == NULL)
|
|
return;
|
|
|
|
memset(&f, 0, sizeof(f));
|
|
f.stat = &sb;
|
|
f.flags |= FE_MLSD;
|
|
while ((dp = readdir(dirp)) != NULL) {
|
|
snprintf(name, sizeof(name), "%s/%s", path, dp->d_name);
|
|
if (ISDOTDIR(dp->d_name)) { /* special case curdir: */
|
|
if (! hastypefact)
|
|
continue;
|
|
f.pdirstat = NULL; /* require stat of parent */
|
|
f.display = path; /* set name to real name */
|
|
f.flags |= FE_ISCURDIR; /* flag name is curdir */
|
|
} else {
|
|
if (ISDOTDOTDIR(dp->d_name)) {
|
|
if (! hastypefact)
|
|
continue;
|
|
f.pdirstat = NULL;
|
|
} else
|
|
f.pdirstat = &pdirstat; /* cache parent stat */
|
|
f.display = dp->d_name;
|
|
f.flags &= ~FE_ISCURDIR;
|
|
}
|
|
if (stat(name, &sb) == -1)
|
|
continue;
|
|
f.path = name;
|
|
mlsname(dout, &f);
|
|
}
|
|
(void)closedir(dirp);
|
|
|
|
if (ferror(dout) != 0)
|
|
perror_reply(550, "Data connection");
|
|
else
|
|
reply(226, "MLSD complete.");
|
|
closedataconn(dout);
|
|
total_xfers_out++;
|
|
total_xfers++;
|
|
}
|
|
|
|
void
|
|
mlst(const char *path)
|
|
{
|
|
struct stat sb;
|
|
factelem f;
|
|
|
|
if (path == NULL)
|
|
path = ".";
|
|
if (stat(path, &sb) == -1) {
|
|
perror_reply(550, path);
|
|
return;
|
|
}
|
|
reply(-250, "MLST %s", path);
|
|
memset(&f, 0, sizeof(f));
|
|
f.path = path;
|
|
f.display = path;
|
|
f.stat = &sb;
|
|
f.pdirstat = NULL;
|
|
CPUTC(' ', stdout);
|
|
mlsname(stdout, &f);
|
|
reply(250, "End");
|
|
}
|
|
|
|
|
|
void
|
|
opts(const char *command)
|
|
{
|
|
struct tab *c;
|
|
char *ep;
|
|
|
|
if ((ep = strchr(command, ' ')) != NULL)
|
|
*ep++ = '\0';
|
|
c = lookup(cmdtab, command);
|
|
if (c == NULL) {
|
|
reply(502, "Unknown command '%s'.", command);
|
|
return;
|
|
}
|
|
if (! CMD_IMPLEMENTED(c)) {
|
|
reply(502, "%s command not implemented.", c->name);
|
|
return;
|
|
}
|
|
if (! CMD_HAS_OPTIONS(c)) {
|
|
reply(501, "%s command does not support persistent options.",
|
|
c->name);
|
|
return;
|
|
}
|
|
|
|
/* special case: MLST */
|
|
if (strcasecmp(command, "MLST") == 0) {
|
|
int enabled[FACTTABSIZE];
|
|
int i, onedone;
|
|
size_t len;
|
|
char *p;
|
|
|
|
for (i = 0; i < sizeof(enabled) / sizeof(int); i++)
|
|
enabled[i] = 0;
|
|
if (ep == NULL || *ep == '\0')
|
|
goto displaymlstopts;
|
|
|
|
/* don't like spaces, and need trailing ; */
|
|
len = strlen(ep);
|
|
if (strchr(ep, ' ') != NULL || ep[len - 1] != ';') {
|
|
badmlstopt:
|
|
reply(501, "Invalid MLST options");
|
|
return;
|
|
}
|
|
ep[len - 1] = '\0';
|
|
while ((p = strsep(&ep, ";")) != NULL) {
|
|
if (*p == '\0')
|
|
goto badmlstopt;
|
|
for (i = 0; i < FACTTABSIZE; i++)
|
|
if (strcasecmp(p, facttab[i].name) == 0) {
|
|
enabled[i] = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
displaymlstopts:
|
|
for (i = 0; i < FACTTABSIZE; i++)
|
|
facttab[i].enabled = enabled[i];
|
|
cprintf(stdout, "200 MLST OPTS");
|
|
for (i = onedone = 0; i < FACTTABSIZE; i++) {
|
|
if (facttab[i].enabled) {
|
|
cprintf(stdout, "%s%s;", onedone ? "" : " ",
|
|
facttab[i].name);
|
|
onedone++;
|
|
}
|
|
}
|
|
cprintf(stdout, "\r\n");
|
|
fflush(stdout);
|
|
return;
|
|
}
|
|
|
|
/* default cases */
|
|
if (ep != NULL && *ep != '\0')
|
|
REASSIGN(c->options, xstrdup(ep));
|
|
if (c->options != NULL)
|
|
reply(200, "Options for %s are '%s'.", c->name,
|
|
c->options);
|
|
else
|
|
reply(200, "No options defined for %s.", c->name);
|
|
}
|
|
|
|
void
|
|
pwd(void)
|
|
{
|
|
char path[MAXPATHLEN];
|
|
|
|
if (getcwd(path, sizeof(path) - 1) == NULL) {
|
|
if (chdir(cached_path) < 0) {
|
|
reply(550, "Can't get the current directory: %s.",
|
|
strerror(errno));
|
|
return;
|
|
}
|
|
(void)strlcpy(path, cached_path, MAXPATHLEN);
|
|
}
|
|
replydirname(path, "is the current directory.");
|
|
}
|
|
|
|
void
|
|
removedir(const char *name)
|
|
{
|
|
char *p = NULL;
|
|
|
|
if (rmdir(name) < 0) {
|
|
p = strerror(errno);
|
|
perror_reply(550, name);
|
|
} else
|
|
ack("RMD");
|
|
logxfer("rmdir", -1, name, NULL, NULL, p);
|
|
}
|
|
|
|
char *
|
|
renamefrom(const char *name)
|
|
{
|
|
struct stat st;
|
|
|
|
if (stat(name, &st) < 0) {
|
|
perror_reply(550, name);
|
|
return (NULL);
|
|
}
|
|
reply(350, "File exists, ready for destination name");
|
|
return (xstrdup(name));
|
|
}
|
|
|
|
void
|
|
renamecmd(const char *from, const char *to)
|
|
{
|
|
char *p = NULL;
|
|
|
|
if (rename(from, to) < 0) {
|
|
p = strerror(errno);
|
|
perror_reply(550, "rename");
|
|
} else
|
|
ack("RNTO");
|
|
logxfer("rename", -1, from, to, NULL, p);
|
|
}
|
|
|
|
void
|
|
sizecmd(const char *filename)
|
|
{
|
|
switch (type) {
|
|
case TYPE_L:
|
|
case TYPE_I:
|
|
{
|
|
struct stat stbuf;
|
|
if (stat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode))
|
|
reply(550, "%s: not a plain file.", filename);
|
|
else
|
|
reply(213, ULLF, (ULLT)stbuf.st_size);
|
|
break;
|
|
}
|
|
case TYPE_A:
|
|
{
|
|
FILE *fin;
|
|
int c;
|
|
off_t count;
|
|
struct stat stbuf;
|
|
fin = fopen(filename, "r");
|
|
if (fin == NULL) {
|
|
perror_reply(550, filename);
|
|
return;
|
|
}
|
|
if (fstat(fileno(fin), &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
|
|
reply(550, "%s: not a plain file.", filename);
|
|
(void) fclose(fin);
|
|
return;
|
|
}
|
|
if (stbuf.st_size > 10240) {
|
|
reply(550, "%s: file too large for SIZE.", filename);
|
|
(void) fclose(fin);
|
|
return;
|
|
}
|
|
|
|
count = 0;
|
|
while((c = getc(fin)) != EOF) {
|
|
if (c == '\n') /* will get expanded to \r\n */
|
|
count++;
|
|
count++;
|
|
}
|
|
(void) fclose(fin);
|
|
|
|
reply(213, LLF, (LLT)count);
|
|
break;
|
|
}
|
|
default:
|
|
reply(504, "SIZE not implemented for Type %c.", "?AEIL"[type]);
|
|
}
|
|
}
|
|
|
|
void
|
|
statfilecmd(const char *filename)
|
|
{
|
|
FILE *fin;
|
|
int c;
|
|
int atstart;
|
|
char *argv[] = { INTERNAL_LS, "-lgA", "", NULL };
|
|
|
|
argv[2] = (char *)filename;
|
|
fin = ftpd_popen(argv, "r", STDOUT_FILENO);
|
|
reply(-211, "status of %s:", filename);
|
|
/* XXX: use fgetln() or fparseln() here? */
|
|
atstart = 1;
|
|
while ((c = getc(fin)) != EOF) {
|
|
if (c == '\n') {
|
|
if (ferror(stdout)){
|
|
perror_reply(421, "control connection");
|
|
(void) ftpd_pclose(fin);
|
|
dologout(1);
|
|
/* NOTREACHED */
|
|
}
|
|
if (ferror(fin)) {
|
|
perror_reply(551, filename);
|
|
(void) ftpd_pclose(fin);
|
|
return;
|
|
}
|
|
CPUTC('\r', stdout);
|
|
}
|
|
if (atstart && isdigit(c))
|
|
CPUTC(' ', stdout);
|
|
CPUTC(c, stdout);
|
|
atstart = (c == '\n');
|
|
}
|
|
(void) ftpd_pclose(fin);
|
|
reply(211, "End of Status");
|
|
}
|
|
|
|
/* -- */
|
|
|
|
static void
|
|
ack(const char *s)
|
|
{
|
|
|
|
reply(250, "%s command successful.", s);
|
|
}
|
|
|
|
/*
|
|
* Encode len bytes starting at clear using base64 encoding into encoded,
|
|
* which should be at least ((len + 2) * 4 / 3 + 1) in size.
|
|
* If nulterm is non-zero, terminate with \0 otherwise pad to 3 byte boundary
|
|
* with `='.
|
|
*/
|
|
static void
|
|
base64_encode(const char *clear, size_t len, char *encoded, int nulterm)
|
|
{
|
|
static const char base64[] =
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
const char *c;
|
|
char *e, termchar;
|
|
int i;
|
|
|
|
/* determine whether to pad with '=' or NUL terminate */
|
|
termchar = nulterm ? '\0' : '=';
|
|
c = clear;
|
|
e = encoded;
|
|
/* convert all but last 2 bytes */
|
|
for (i = len; i > 2; i -= 3, c += 3) {
|
|
*e++ = base64[(c[0] >> 2) & 0x3f];
|
|
*e++ = base64[((c[0] << 4) & 0x30) | ((c[1] >> 4) & 0x0f)];
|
|
*e++ = base64[((c[1] << 2) & 0x3c) | ((c[2] >> 6) & 0x03)];
|
|
*e++ = base64[(c[2]) & 0x3f];
|
|
}
|
|
/* handle slop at end */
|
|
if (i > 0) {
|
|
*e++ = base64[(c[0] >> 2) & 0x3f];
|
|
*e++ = base64[((c[0] << 4) & 0x30) |
|
|
(i > 1 ? ((c[1] >> 4) & 0x0f) : 0)];
|
|
*e++ = (i > 1) ? base64[(c[1] << 2) & 0x3c] : termchar;
|
|
*e++ = termchar;
|
|
}
|
|
*e = '\0';
|
|
}
|
|
|
|
static void
|
|
fact_modify(const char *fact, FILE *fd, factelem *fe)
|
|
{
|
|
struct tm *t;
|
|
|
|
t = gmtime(&(fe->stat->st_mtime));
|
|
cprintf(fd, "%s=%04d%02d%02d%02d%02d%02d;", fact,
|
|
TM_YEAR_BASE + t->tm_year,
|
|
t->tm_mon+1, t->tm_mday,
|
|
t->tm_hour, t->tm_min, t->tm_sec);
|
|
}
|
|
|
|
static void
|
|
fact_perm(const char *fact, FILE *fd, factelem *fe)
|
|
{
|
|
int rok, wok, xok, pdirwok;
|
|
struct stat *pdir;
|
|
|
|
if (fe->stat->st_uid == geteuid()) {
|
|
rok = ((fe->stat->st_mode & S_IRUSR) != 0);
|
|
wok = ((fe->stat->st_mode & S_IWUSR) != 0);
|
|
xok = ((fe->stat->st_mode & S_IXUSR) != 0);
|
|
} else if (matchgroup(fe->stat->st_gid)) {
|
|
rok = ((fe->stat->st_mode & S_IRGRP) != 0);
|
|
wok = ((fe->stat->st_mode & S_IWGRP) != 0);
|
|
xok = ((fe->stat->st_mode & S_IXGRP) != 0);
|
|
} else {
|
|
rok = ((fe->stat->st_mode & S_IROTH) != 0);
|
|
wok = ((fe->stat->st_mode & S_IWOTH) != 0);
|
|
xok = ((fe->stat->st_mode & S_IXOTH) != 0);
|
|
}
|
|
|
|
cprintf(fd, "%s=", fact);
|
|
|
|
/*
|
|
* if parent info not provided, look it up, but
|
|
* only if the current class has modify rights,
|
|
* since we only need this info in such a case.
|
|
*/
|
|
pdir = fe->pdirstat;
|
|
if (pdir == NULL && CURCLASS_FLAGS_ISSET(modify)) {
|
|
size_t len;
|
|
char realdir[MAXPATHLEN], *p;
|
|
struct stat dir;
|
|
|
|
len = strlcpy(realdir, fe->path, sizeof(realdir));
|
|
if (len < sizeof(realdir) - 4) {
|
|
if (S_ISDIR(fe->stat->st_mode))
|
|
strlcat(realdir, "/..", sizeof(realdir));
|
|
else {
|
|
/* if has a /, move back to it */
|
|
/* otherwise use '..' */
|
|
if ((p = strrchr(realdir, '/')) != NULL) {
|
|
if (p == realdir)
|
|
p++;
|
|
*p = '\0';
|
|
} else
|
|
strlcpy(realdir, "..", sizeof(realdir));
|
|
}
|
|
if (stat(realdir, &dir) == 0)
|
|
pdir = &dir;
|
|
}
|
|
}
|
|
pdirwok = 0;
|
|
if (pdir != NULL) {
|
|
if (pdir->st_uid == geteuid())
|
|
pdirwok = ((pdir->st_mode & S_IWUSR) != 0);
|
|
else if (matchgroup(pdir->st_gid))
|
|
pdirwok = ((pdir->st_mode & S_IWGRP) != 0);
|
|
else
|
|
pdirwok = ((pdir->st_mode & S_IWOTH) != 0);
|
|
}
|
|
|
|
/* 'a': can APPE to file */
|
|
if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
|
|
CPUTC('a', fd);
|
|
|
|
/* 'c': can create or append to files in directory */
|
|
if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
|
|
CPUTC('c', fd);
|
|
|
|
/* 'd': can delete file or directory */
|
|
if (pdirwok && CURCLASS_FLAGS_ISSET(modify)) {
|
|
int candel;
|
|
|
|
candel = 1;
|
|
if (S_ISDIR(fe->stat->st_mode)) {
|
|
DIR *dirp;
|
|
struct dirent *dp;
|
|
|
|
if ((dirp = opendir(fe->display)) == NULL)
|
|
candel = 0;
|
|
else {
|
|
while ((dp = readdir(dirp)) != NULL) {
|
|
if (ISDOTDIR(dp->d_name) ||
|
|
ISDOTDOTDIR(dp->d_name))
|
|
continue;
|
|
candel = 0;
|
|
break;
|
|
}
|
|
closedir(dirp);
|
|
}
|
|
}
|
|
if (candel)
|
|
CPUTC('d', fd);
|
|
}
|
|
|
|
/* 'e': can enter directory */
|
|
if (xok && S_ISDIR(fe->stat->st_mode))
|
|
CPUTC('e', fd);
|
|
|
|
/* 'f': can rename file or directory */
|
|
if (pdirwok && CURCLASS_FLAGS_ISSET(modify))
|
|
CPUTC('f', fd);
|
|
|
|
/* 'l': can list directory */
|
|
if (rok && xok && S_ISDIR(fe->stat->st_mode))
|
|
CPUTC('l', fd);
|
|
|
|
/* 'm': can create directory */
|
|
if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
|
|
CPUTC('m', fd);
|
|
|
|
/* 'p': can remove files in directory */
|
|
if (wok && CURCLASS_FLAGS_ISSET(modify) && S_ISDIR(fe->stat->st_mode))
|
|
CPUTC('p', fd);
|
|
|
|
/* 'r': can RETR file */
|
|
if (rok && S_ISREG(fe->stat->st_mode))
|
|
CPUTC('r', fd);
|
|
|
|
/* 'w': can STOR file */
|
|
if (wok && CURCLASS_FLAGS_ISSET(upload) && S_ISREG(fe->stat->st_mode))
|
|
CPUTC('w', fd);
|
|
|
|
CPUTC(';', fd);
|
|
}
|
|
|
|
static void
|
|
fact_size(const char *fact, FILE *fd, factelem *fe)
|
|
{
|
|
|
|
if (S_ISREG(fe->stat->st_mode))
|
|
cprintf(fd, "%s=" LLF ";", fact, (LLT)fe->stat->st_size);
|
|
}
|
|
|
|
static void
|
|
fact_type(const char *fact, FILE *fd, factelem *fe)
|
|
{
|
|
|
|
cprintf(fd, "%s=", fact);
|
|
switch (fe->stat->st_mode & S_IFMT) {
|
|
case S_IFDIR:
|
|
if (fe->flags & FE_MLSD) {
|
|
if ((fe->flags & FE_ISCURDIR) || ISDOTDIR(fe->display))
|
|
cprintf(fd, "cdir");
|
|
else if (ISDOTDOTDIR(fe->display))
|
|
cprintf(fd, "pdir");
|
|
else
|
|
cprintf(fd, "dir");
|
|
} else {
|
|
cprintf(fd, "dir");
|
|
}
|
|
break;
|
|
case S_IFREG:
|
|
cprintf(fd, "file");
|
|
break;
|
|
case S_IFIFO:
|
|
cprintf(fd, "OS.unix=fifo");
|
|
break;
|
|
case S_IFLNK: /* XXX: probably a NO-OP with stat() */
|
|
cprintf(fd, "OS.unix=slink");
|
|
break;
|
|
case S_IFSOCK:
|
|
cprintf(fd, "OS.unix=socket");
|
|
break;
|
|
case S_IFBLK:
|
|
case S_IFCHR:
|
|
cprintf(fd, "OS.unix=%s-%d/%d",
|
|
S_ISBLK(fe->stat->st_mode) ? "blk" : "chr",
|
|
major(fe->stat->st_rdev), minor(fe->stat->st_rdev));
|
|
break;
|
|
default:
|
|
cprintf(fd, "OS.unix=UNKNOWN(0%o)", fe->stat->st_mode & S_IFMT);
|
|
break;
|
|
}
|
|
CPUTC(';', fd);
|
|
}
|
|
|
|
static void
|
|
fact_unique(const char *fact, FILE *fd, factelem *fe)
|
|
{
|
|
char obuf[(sizeof(dev_t) + sizeof(ino_t) + 2) * 4 / 3 + 2];
|
|
char tbuf[sizeof(dev_t) + sizeof(ino_t)];
|
|
|
|
memcpy(tbuf,
|
|
(char *)&(fe->stat->st_dev), sizeof(dev_t));
|
|
memcpy(tbuf + sizeof(dev_t),
|
|
(char *)&(fe->stat->st_ino), sizeof(ino_t));
|
|
base64_encode(tbuf, sizeof(dev_t) + sizeof(ino_t), obuf, 1);
|
|
cprintf(fd, "%s=%s;", fact, obuf);
|
|
}
|
|
|
|
static int
|
|
matchgroup(gid_t gid)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < gidcount; i++)
|
|
if (gid == gidlist[i])
|
|
return(1);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
mlsname(FILE *fp, factelem *fe)
|
|
{
|
|
char realfile[MAXPATHLEN];
|
|
int i, userf = 0;
|
|
|
|
for (i = 0; i < FACTTABSIZE; i++) {
|
|
if (facttab[i].enabled)
|
|
(facttab[i].display)(facttab[i].name, fp, fe);
|
|
}
|
|
if ((fe->flags & FE_MLSD) &&
|
|
!(fe->flags & FE_ISCURDIR) && !ISDOTDIR(fe->display)) {
|
|
/* if MLSD and not "." entry, display as-is */
|
|
userf = 0;
|
|
} else {
|
|
/* if MLST, or MLSD and "." entry, realpath(3) it */
|
|
if (realpath(fe->display, realfile) != NULL)
|
|
userf = 1;
|
|
}
|
|
cprintf(fp, " %s\r\n", userf ? realfile : fe->display);
|
|
}
|
|
|
|
static void
|
|
replydirname(const char *name, const char *message)
|
|
{
|
|
char *p, *ep;
|
|
char npath[MAXPATHLEN * 2];
|
|
|
|
p = npath;
|
|
ep = &npath[sizeof(npath) - 1];
|
|
while (*name) {
|
|
if (*name == '"') {
|
|
if (ep - p < 2)
|
|
break;
|
|
*p++ = *name++;
|
|
*p++ = '"';
|
|
} else {
|
|
if (ep - p < 1)
|
|
break;
|
|
*p++ = *name++;
|
|
}
|
|
}
|
|
*p = '\0';
|
|
reply(257, "\"%s\" %s", npath, message);
|
|
}
|
|
|
|
static void
|
|
discover_path(last_path, new_path)
|
|
char *last_path;
|
|
const char *new_path;
|
|
{
|
|
char tp[MAXPATHLEN + 1] = "";
|
|
char tq[MAXPATHLEN + 1] = "";
|
|
char *cp;
|
|
char *cq;
|
|
int sz1, sz2;
|
|
int nomorelink;
|
|
struct stat st1, st2;
|
|
|
|
if (new_path[0] != '/') {
|
|
(void)strlcpy(tp, last_path, MAXPATHLEN);
|
|
(void)strlcat(tp, "/", MAXPATHLEN);
|
|
}
|
|
(void)strlcat(tp, new_path, MAXPATHLEN);
|
|
(void)strlcat(tp, "/", MAXPATHLEN);
|
|
|
|
/*
|
|
* resolve symlinks. A symlink may introduce another symlink, so we
|
|
* loop trying to resolve symlinks until we don't find any of them.
|
|
*/
|
|
do {
|
|
/* Collapse any // into / */
|
|
while ((cp = strstr(tp, "//")) != NULL)
|
|
(void)memmove(cp, cp + 1, strlen(cp) - 1 + 1);
|
|
|
|
/* Collapse any /./ into / */
|
|
while ((cp = strstr(tp, "/./")) != NULL)
|
|
(void)memmove(cp, cp + 2, strlen(cp) - 2 + 1);
|
|
|
|
cp = tp;
|
|
nomorelink = 1;
|
|
|
|
while ((cp = strstr(++cp, "/")) != NULL) {
|
|
sz1 = (u_long)cp - (u_long)tp;
|
|
if (sz1 > MAXPATHLEN)
|
|
goto bad;
|
|
*cp = 0;
|
|
sz2 = readlink(tp, tq, MAXPATHLEN);
|
|
*cp = '/';
|
|
|
|
/* If this is not a symlink, move to next / */
|
|
if (sz2 <= 0)
|
|
continue;
|
|
|
|
/*
|
|
* We found a symlink, so we will have to
|
|
* do one more pass to check there is no
|
|
* more symlink in the path
|
|
*/
|
|
nomorelink = 0;
|
|
|
|
/*
|
|
* Null terminate the string and remove trailing /
|
|
*/
|
|
tq[sz2] = 0;
|
|
sz2 = strlen(tq);
|
|
if (tq[sz2 - 1] == '/')
|
|
tq[--sz2] = 0;
|
|
|
|
/*
|
|
* Is this an absolute link or a relative link?
|
|
*/
|
|
if (tq[0] == '/') {
|
|
/* absolute link */
|
|
if (strlen(cp) + sz2 > MAXPATHLEN)
|
|
goto bad;
|
|
memmove(tp + sz2, cp, strlen(cp) + 1);
|
|
memcpy(tp, tq, sz2);
|
|
} else {
|
|
/* relative link */
|
|
for (cq = cp - 1; *cq != '/'; cq--);
|
|
if (strlen(tp) - ((u_long)cq - (u_long)cp)
|
|
+ 1 + sz2 > MAXPATHLEN)
|
|
goto bad;
|
|
(void)memmove(cq + 1 + sz2,
|
|
cp, strlen(cp) + 1);
|
|
(void)memcpy(cq + 1, tq, sz2);
|
|
}
|
|
|
|
/*
|
|
* start over, looking for new symlinks
|
|
*/
|
|
break;
|
|
}
|
|
} while (nomorelink == 0);
|
|
|
|
/* Collapse any /foo/../ into /foo/ */
|
|
while ((cp = strstr(tp, "/../")) != NULL) {
|
|
/* ^/../foo/ becomes ^/foo/ */
|
|
if (cp == tp) {
|
|
(void)memmove(cp, cp + 3,
|
|
strlen(cp) - 3 + 1);
|
|
} else {
|
|
for (cq = cp - 1; *cq != '/'; cq--);
|
|
(void)memmove(cq, cp + 3,
|
|
strlen(cp) - 3 + 1);
|
|
}
|
|
}
|
|
|
|
/* strip strailing / */
|
|
if (strlen(tp) != 1)
|
|
tp[strlen(tp) - 1] = '\0';
|
|
|
|
/* check that the path is correct */
|
|
stat(tp, &st1);
|
|
stat(".", &st2);
|
|
if ((st1.st_dev != st2.st_dev) || (st1.st_ino != st2.st_ino))
|
|
goto bad;
|
|
|
|
(void)strlcpy(last_path, tp, MAXPATHLEN);
|
|
return;
|
|
|
|
bad:
|
|
(void)strlcat(last_path, "/", MAXPATHLEN);
|
|
(void)strlcat(last_path, new_path, MAXPATHLEN);
|
|
return;
|
|
}
|
|
|