NetBSD/libexec/ftpd/cmds.c
lukem 635a375704 Fixes from (or inspired by) OpenBSD:
* 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.
2004-08-09 12:56:47 +00:00

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;
}