Files
heimdal/appl/ftp/ftpd/ftpd.c
Love Hörnquist Åstrand bf0ab85d2e Remove all traces of setjmp/longjmp.
Handle those command that is needed in oobhandler,
those are ABOR, STAT, ENC, CONF, MIC.
add options to turn off insecure OOB handling and document the option

Changes inspired by openbsd and netbsd changes but quite diffrent is
most places since the code no longer look and is structured the same
way.


git-svn-id: svn://svn.h5l.se/heimdal/trunk/heimdal@14136 ec53bebd-3082-4978-b11e-865c3cabbd6b
2004-08-20 13:31:20 +00:00

2375 lines
52 KiB
C

/*
* 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. 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.
*/
#define FTP_NAMES
#include "ftpd_locl.h"
#ifdef KRB5
#include <krb5.h>
#endif
#include "getarg.h"
RCSID("$Id$");
static char version[] = "Version 6.00";
extern off_t restart_point;
extern char cbuf[];
struct sockaddr_storage ctrl_addr_ss;
struct sockaddr *ctrl_addr = (struct sockaddr *)&ctrl_addr_ss;
struct sockaddr_storage data_source_ss;
struct sockaddr *data_source = (struct sockaddr *)&data_source_ss;
struct sockaddr_storage data_dest_ss;
struct sockaddr *data_dest = (struct sockaddr *)&data_dest_ss;
struct sockaddr_storage his_addr_ss;
struct sockaddr *his_addr = (struct sockaddr *)&his_addr_ss;
struct sockaddr_storage pasv_addr_ss;
struct sockaddr *pasv_addr = (struct sockaddr *)&pasv_addr_ss;
int data;
int logged_in;
struct passwd *pw;
int debug = 0;
int ftpd_timeout = 900; /* timeout after 15 minutes of inactivity */
int maxtimeout = 7200;/* don't allow idle time to be set beyond 2 hours */
int restricted_data_ports = 1;
int logging;
int guest;
int dochroot;
int type;
int form;
int stru; /* avoid C keyword */
int mode;
int usedefault = 1; /* for data transfers */
int pdata = -1; /* for passive mode */
int allow_insecure_oob = 1;
static int transflag;
static int urgflag;
off_t file_size;
off_t byte_count;
#if !defined(CMASK) || CMASK == 0
#undef CMASK
#define CMASK 027
#endif
int defumask = CMASK; /* default umask value */
int guest_umask = 0777; /* Paranoia for anonymous users */
char tmpline[10240];
char hostname[MaxHostNameLen];
char remotehost[MaxHostNameLen];
static char ttyline[20];
#define AUTH_PLAIN (1 << 0) /* allow sending passwords */
#define AUTH_OTP (1 << 1) /* passwords are one-time */
#define AUTH_FTP (1 << 2) /* allow anonymous login */
static int auth_level = 0; /* Only allow kerberos login by default */
/*
* Timeout intervals for retrying connections
* to hosts that don't accept PORT cmds. This
* is a kludge, but given the problems with TCP...
*/
#define SWAITMAX 90 /* wait at most 90 seconds */
#define SWAITINT 5 /* interval between retries */
int swaitmax = SWAITMAX;
int swaitint = SWAITINT;
#ifdef HAVE_SETPROCTITLE
char proctitle[BUFSIZ]; /* initial part of title */
#endif /* HAVE_SETPROCTITLE */
#define LOGCMD(cmd, file) \
if (logging > 1) \
syslog(LOG_INFO,"%s %s%s", cmd, \
*(file) == '/' ? "" : curdir(), file);
#define LOGCMD2(cmd, file1, file2) \
if (logging > 1) \
syslog(LOG_INFO,"%s %s%s %s%s", cmd, \
*(file1) == '/' ? "" : curdir(), file1, \
*(file2) == '/' ? "" : curdir(), file2);
#define LOGBYTES(cmd, file, cnt) \
if (logging > 1) { \
if (cnt == (off_t)-1) \
syslog(LOG_INFO,"%s %s%s", cmd, \
*(file) == '/' ? "" : curdir(), file); \
else \
syslog(LOG_INFO, "%s %s%s = %ld bytes", \
cmd, (*(file) == '/') ? "" : curdir(), file, (long)cnt); \
}
static void ack (char *);
static void myoob (int);
static int handleoobcmd(void);
static int checkuser (char *, char *);
static int checkaccess (char *);
static FILE *dataconn (const char *, off_t, const char *);
static void dolog (struct sockaddr *sa, int len);
static void end_login (void);
static FILE *getdatasock (const char *);
static char *gunique (char *);
static RETSIGTYPE lostconn (int);
static int receive_data (FILE *, FILE *);
static void send_data (FILE *, FILE *);
static struct passwd * sgetpwnam (char *);
static char *
curdir(void)
{
static char path[MaxPathLen+1]; /* path + '/' + '\0' */
if (getcwd(path, sizeof(path)-1) == NULL)
return ("");
if (path[1] != '\0') /* special case for root dir. */
strlcat(path, "/", sizeof(path));
/* For guest account, skip / since it's chrooted */
return (guest ? path+1 : path);
}
#ifndef LINE_MAX
#define LINE_MAX 1024
#endif
static int
parse_auth_level(char *str)
{
char *p;
int ret = 0;
char *foo = NULL;
for(p = strtok_r(str, ",", &foo);
p;
p = strtok_r(NULL, ",", &foo)) {
if(strcmp(p, "user") == 0)
;
#ifdef OTP
else if(strcmp(p, "otp") == 0)
ret |= AUTH_PLAIN|AUTH_OTP;
#endif
else if(strcmp(p, "ftp") == 0 ||
strcmp(p, "safe") == 0)
ret |= AUTH_FTP;
else if(strcmp(p, "plain") == 0)
ret |= AUTH_PLAIN;
else if(strcmp(p, "none") == 0)
ret |= AUTH_PLAIN|AUTH_FTP;
else
warnx("bad value for -a: `%s'", p);
}
return ret;
}
/*
* Print usage and die.
*/
static int interactive_flag;
static char *guest_umask_string;
static char *port_string;
static char *umask_string;
static char *auth_string;
int use_builtin_ls = -1;
static int help_flag;
static int version_flag;
static const char *good_chars = "+-=_,.";
struct getargs args[] = {
{ NULL, 'a', arg_string, &auth_string, "required authentication" },
{ NULL, 'i', arg_flag, &interactive_flag, "don't assume stdin is a socket" },
{ NULL, 'p', arg_string, &port_string, "what port to listen to" },
{ NULL, 'g', arg_string, &guest_umask_string, "umask for guest logins" },
{ NULL, 'l', arg_counter, &logging, "log more stuff", "" },
{ NULL, 't', arg_integer, &ftpd_timeout, "initial timeout" },
{ NULL, 'T', arg_integer, &maxtimeout, "max timeout" },
{ NULL, 'u', arg_string, &umask_string, "umask for user logins" },
{ NULL, 'U', arg_negative_flag, &restricted_data_ports, "don't use high data ports" },
{ NULL, 'd', arg_flag, &debug, "enable debugging" },
{ NULL, 'v', arg_flag, &debug, "enable debugging" },
{ "builtin-ls", 'B', arg_flag, &use_builtin_ls, "use built-in ls to list files" },
{ "good-chars", 0, arg_string, &good_chars, "allowed anonymous upload filename chars" },
{ "insecure-oob", 'I', arg_negative_flag, &allow_insecure_oob, "don't allow insecure OOB ABOR/STAT" },
#ifdef KRB5
{ "gss-bindings", 0, arg_flag, &ftp_do_gss_bindings, "Require GSS-API bindings", NULL},
#endif
{ "version", 0, arg_flag, &version_flag },
{ "help", 'h', arg_flag, &help_flag }
};
static int num_args = sizeof(args) / sizeof(args[0]);
static void
usage (int code)
{
arg_printusage(args, num_args, NULL, "");
exit (code);
}
/* output contents of a file */
static int
show_file(const char *file, int code)
{
FILE *f;
char buf[128];
f = fopen(file, "r");
if(f == NULL)
return -1;
while(fgets(buf, sizeof(buf), f)){
buf[strcspn(buf, "\r\n")] = '\0';
lreply(code, "%s", buf);
}
fclose(f);
return 0;
}
int
main(int argc, char **argv)
{
socklen_t his_addr_len, ctrl_addr_len;
int on = 1;
int port;
struct servent *sp;
int optind = 0;
setprogname (argv[0]);
/* detach from any tickets and tokens */
{
#ifdef KRB4
char tkfile[1024];
snprintf(tkfile, sizeof(tkfile),
"/tmp/ftp_%u", (unsigned)getpid());
krb_set_tkt_string(tkfile);
#endif
}
#if defined(KRB4) || defined(KRB5)
if(k_hasafs())
k_setpag();
#endif
if(getarg(args, num_args, argc, argv, &optind))
usage(1);
if(help_flag)
usage(0);
if(version_flag) {
print_version(NULL);
exit(0);
}
if(auth_string)
auth_level = parse_auth_level(auth_string);
{
char *p;
long val = 0;
if(guest_umask_string) {
val = strtol(guest_umask_string, &p, 8);
if (*p != '\0' || val < 0)
warnx("bad value for -g");
else
guest_umask = val;
}
if(umask_string) {
val = strtol(umask_string, &p, 8);
if (*p != '\0' || val < 0)
warnx("bad value for -u");
else
defumask = val;
}
}
sp = getservbyname("ftp", "tcp");
if(sp)
port = sp->s_port;
else
port = htons(21);
if(port_string) {
sp = getservbyname(port_string, "tcp");
if(sp)
port = sp->s_port;
else
if(isdigit((unsigned char)port_string[0]))
port = htons(atoi(port_string));
else
warnx("bad value for -p");
}
if (maxtimeout < ftpd_timeout)
maxtimeout = ftpd_timeout;
#if 0
if (ftpd_timeout > maxtimeout)
ftpd_timeout = maxtimeout;
#endif
if(interactive_flag)
mini_inetd (port);
/*
* LOG_NDELAY sets up the logging connection immediately,
* necessary for anonymous ftp's that chroot and can't do it later.
*/
openlog("ftpd", LOG_PID | LOG_NDELAY, LOG_FTP);
his_addr_len = sizeof(his_addr_ss);
if (getpeername(STDIN_FILENO, his_addr, &his_addr_len) < 0) {
syslog(LOG_ERR, "getpeername (%s): %m",argv[0]);
exit(1);
}
ctrl_addr_len = sizeof(ctrl_addr_ss);
if (getsockname(STDIN_FILENO, ctrl_addr, &ctrl_addr_len) < 0) {
syslog(LOG_ERR, "getsockname (%s): %m",argv[0]);
exit(1);
}
#if defined(IP_TOS) && defined(HAVE_SETSOCKOPT)
{
int tos = IPTOS_LOWDELAY;
if (setsockopt(STDIN_FILENO, IPPROTO_IP, IP_TOS,
(void *)&tos, sizeof(int)) < 0)
syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
}
#endif
data_source->sa_family = ctrl_addr->sa_family;
socket_set_port (data_source,
htons(ntohs(socket_get_port(ctrl_addr)) - 1));
/* set this here so it can be put in wtmp */
snprintf(ttyline, sizeof(ttyline), "ftp%u", (unsigned)getpid());
/* freopen(_PATH_DEVNULL, "w", stderr); */
signal(SIGPIPE, lostconn);
signal(SIGCHLD, SIG_IGN);
#ifdef SIGURG
if (signal(SIGURG, myoob) == SIG_ERR)
syslog(LOG_ERR, "signal: %m");
#endif
/* Try to handle urgent data inline */
#if defined(SO_OOBINLINE) && defined(HAVE_SETSOCKOPT)
if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (void *)&on,
sizeof(on)) < 0)
syslog(LOG_ERR, "setsockopt: %m");
#endif
#ifdef F_SETOWN
if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
syslog(LOG_ERR, "fcntl F_SETOWN: %m");
#endif
dolog(his_addr, his_addr_len);
/*
* Set up default state
*/
data = -1;
type = TYPE_A;
form = FORM_N;
stru = STRU_F;
mode = MODE_S;
tmpline[0] = '\0';
/* If logins are disabled, print out the message. */
if(show_file(_PATH_NOLOGIN, 530) == 0) {
reply(530, "System not available.");
exit(0);
}
show_file(_PATH_FTPWELCOME, 220);
/* reply(220,) must follow */
gethostname(hostname, sizeof(hostname));
reply(220, "%s FTP server (%s"
#ifdef KRB5
"+%s"
#endif
#ifdef KRB4
"+%s"
#endif
") ready.", hostname, version
#ifdef KRB5
,heimdal_version
#endif
#ifdef KRB4
,krb4_version
#endif
);
for (;;)
yyparse();
/* NOTREACHED */
}
static RETSIGTYPE
lostconn(int signo)
{
if (debug)
syslog(LOG_DEBUG, "lost connection");
dologout(-1);
}
/*
* Helper function for sgetpwnam().
*/
static char *
sgetsave(char *s)
{
char *new = strdup(s);
if (new == NULL) {
perror_reply(421, "Local resource failure: malloc");
dologout(1);
/* NOTREACHED */
}
return new;
}
/*
* Save the result of a getpwnam. Used for USER command, since
* the data returned must not be clobbered by any other command
* (e.g., globbing).
*/
static struct passwd *
sgetpwnam(char *name)
{
static struct passwd save;
struct passwd *p;
if ((p = k_getpwnam(name)) == NULL)
return (p);
if (save.pw_name) {
free(save.pw_name);
free(save.pw_passwd);
free(save.pw_gecos);
free(save.pw_dir);
free(save.pw_shell);
}
save = *p;
save.pw_name = sgetsave(p->pw_name);
save.pw_passwd = sgetsave(p->pw_passwd);
save.pw_gecos = sgetsave(p->pw_gecos);
save.pw_dir = sgetsave(p->pw_dir);
save.pw_shell = sgetsave(p->pw_shell);
return (&save);
}
static int login_attempts; /* number of failed login attempts */
static int askpasswd; /* had user command, ask for passwd */
static char curname[10]; /* current USER name */
#ifdef OTP
OtpContext otp_ctx;
#endif
/*
* USER command.
* Sets global passwd pointer pw if named account exists and is acceptable;
* sets askpasswd if a PASS command is expected. If logged in previously,
* need to reset state. If name is "ftp" or "anonymous", the name is not in
* _PATH_FTPUSERS, and ftp account exists, set guest and pw, then just return.
* If account doesn't exist, ask for passwd anyway. Otherwise, check user
* requesting login privileges. Disallow anyone who does not have a standard
* shell as returned by getusershell(). Disallow anyone mentioned in the file
* _PATH_FTPUSERS to allow people such as root and uucp to be avoided.
*/
void
user(char *name)
{
char *cp, *shell;
if(auth_level == 0 && !sec_complete){
reply(530, "No login allowed without authorization.");
return;
}
if (logged_in) {
if (guest) {
reply(530, "Can't change user from guest login.");
return;
} else if (dochroot) {
reply(530, "Can't change user from chroot user.");
return;
}
end_login();
}
guest = 0;
if (strcmp(name, "ftp") == 0 || strcmp(name, "anonymous") == 0) {
if ((auth_level & AUTH_FTP) == 0 ||
checkaccess("ftp") ||
checkaccess("anonymous"))
reply(530, "User %s access denied.", name);
else if ((pw = sgetpwnam("ftp")) != NULL) {
guest = 1;
defumask = guest_umask; /* paranoia for incoming */
askpasswd = 1;
reply(331, "Guest login ok, type your name as password.");
} else
reply(530, "User %s unknown.", name);
if (!askpasswd && logging) {
char data_addr[256];
if (inet_ntop (his_addr->sa_family,
socket_get_address(his_addr),
data_addr, sizeof(data_addr)) == NULL)
strlcpy (data_addr, "unknown address",
sizeof(data_addr));
syslog(LOG_NOTICE,
"ANONYMOUS FTP LOGIN REFUSED FROM %s(%s)",
remotehost, data_addr);
}
return;
}
if((auth_level & AUTH_PLAIN) == 0 && !sec_complete){
reply(530, "Only authorized and anonymous login allowed.");
return;
}
if ((pw = sgetpwnam(name))) {
if ((shell = pw->pw_shell) == NULL || *shell == 0)
shell = _PATH_BSHELL;
while ((cp = getusershell()) != NULL)
if (strcmp(cp, shell) == 0)
break;
endusershell();
if (cp == NULL || checkaccess(name)) {
reply(530, "User %s access denied.", name);
if (logging) {
char data_addr[256];
if (inet_ntop (his_addr->sa_family,
socket_get_address(his_addr),
data_addr,
sizeof(data_addr)) == NULL)
strlcpy (data_addr,
"unknown address",
sizeof(data_addr));
syslog(LOG_NOTICE,
"FTP LOGIN REFUSED FROM %s(%s), %s",
remotehost,
data_addr,
name);
}
pw = (struct passwd *) NULL;
return;
}
}
if (logging)
strlcpy(curname, name, sizeof(curname));
if(sec_complete) {
if(sec_userok(name) == 0)
do_login(232, name);
else
reply(530, "User %s access denied.", name);
} else {
char ss[256];
#ifdef OTP
if (otp_challenge(&otp_ctx, name, ss, sizeof(ss)) == 0) {
reply(331, "Password %s for %s required.",
ss, name);
askpasswd = 1;
} else
#endif
if ((auth_level & AUTH_OTP) == 0) {
reply(331, "Password required for %s.", name);
askpasswd = 1;
} else {
char *s;
#ifdef OTP
if ((s = otp_error (&otp_ctx)) != NULL)
lreply(530, "OTP: %s", s);
#endif
reply(530,
"Only authorized, anonymous"
#ifdef OTP
" and OTP "
#endif
"login allowed.");
}
}
/*
* Delay before reading passwd after first failed
* attempt to slow down passwd-guessing programs.
*/
if (login_attempts)
sleep(login_attempts);
}
/*
* Check if a user is in the file "fname"
*/
static int
checkuser(char *fname, char *name)
{
FILE *fd;
int found = 0;
char *p, line[BUFSIZ];
if ((fd = fopen(fname, "r")) != NULL) {
while (fgets(line, sizeof(line), fd) != NULL)
if ((p = strchr(line, '\n')) != NULL) {
*p = '\0';
if (line[0] == '#')
continue;
if (strcmp(line, name) == 0) {
found = 1;
break;
}
}
fclose(fd);
}
return (found);
}
/*
* Determine whether a user has access, based on information in
* _PATH_FTPUSERS. The users are listed one per line, with `allow'
* or `deny' after the username. If anything other than `allow', or
* just nothing, is given after the username, `deny' is assumed.
*
* If the user is not found in the file, but the pseudo-user `*' is,
* the permission is taken from that line.
*
* This preserves the old semantics where if a user was listed in the
* file he was denied, otherwise he was allowed.
*
* Return 1 if the user is denied, or 0 if he is allowed. */
static int
match(const char *pattern, const char *string)
{
return fnmatch(pattern, string, FNM_NOESCAPE);
}
static int
checkaccess(char *name)
{
#define ALLOWED 0
#define NOT_ALLOWED 1
FILE *fd;
int allowed = ALLOWED;
char *user, *perm, line[BUFSIZ];
char *foo;
fd = fopen(_PATH_FTPUSERS, "r");
if(fd == NULL)
return allowed;
while (fgets(line, sizeof(line), fd) != NULL) {
foo = NULL;
user = strtok_r(line, " \t\n", &foo);
if (user == NULL || user[0] == '#')
continue;
perm = strtok_r(NULL, " \t\n", &foo);
if (match(user, name) == 0){
if(perm && strcmp(perm, "allow") == 0)
allowed = ALLOWED;
else
allowed = NOT_ALLOWED;
break;
}
}
fclose(fd);
return allowed;
}
#undef ALLOWED
#undef NOT_ALLOWED
int do_login(int code, char *passwd)
{
login_attempts = 0; /* this time successful */
if (setegid((gid_t)pw->pw_gid) < 0) {
reply(550, "Can't set gid.");
return -1;
}
initgroups(pw->pw_name, pw->pw_gid);
/* open wtmp before chroot */
ftpd_logwtmp(ttyline, pw->pw_name, remotehost);
logged_in = 1;
dochroot = checkuser(_PATH_FTPCHROOT, pw->pw_name);
if (guest) {
/*
* We MUST do a chdir() after the chroot. Otherwise
* the old current directory will be accessible as "."
* outside the new root!
*/
if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
reply(550, "Can't set guest privileges.");
return -1;
}
} else if (dochroot) {
if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
reply(550, "Can't change root.");
return -1;
}
} else if (chdir(pw->pw_dir) < 0) {
if (chdir("/") < 0) {
reply(530, "User %s: can't change directory to %s.",
pw->pw_name, pw->pw_dir);
return -1;
} else
lreply(code, "No directory! Logging in with home=/");
}
if (seteuid((uid_t)pw->pw_uid) < 0) {
reply(550, "Can't set uid.");
return -1;
}
if(use_builtin_ls == -1) {
struct stat st;
/* if /bin/ls exist and is a regular file, use it, otherwise
use built-in ls */
if(stat("/bin/ls", &st) == 0 &&
S_ISREG(st.st_mode))
use_builtin_ls = 0;
else
use_builtin_ls = 1;
}
/*
* Display a login message, if it exists.
* N.B. reply(code,) must follow the message.
*/
show_file(_PATH_FTPLOGINMESG, code);
if(show_file(_PATH_ISSUE_NET, code) != 0)
show_file(_PATH_ISSUE, code);
if (guest) {
reply(code, "Guest login ok, access restrictions apply.");
#ifdef HAVE_SETPROCTITLE
snprintf (proctitle, sizeof(proctitle),
"%s: anonymous/%s",
remotehost,
passwd);
setproctitle("%s", proctitle);
#endif /* HAVE_SETPROCTITLE */
if (logging) {
char data_addr[256];
if (inet_ntop (his_addr->sa_family,
socket_get_address(his_addr),
data_addr, sizeof(data_addr)) == NULL)
strlcpy (data_addr, "unknown address",
sizeof(data_addr));
syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s(%s), %s",
remotehost,
data_addr,
passwd);
}
} else {
reply(code, "User %s logged in.", pw->pw_name);
#ifdef HAVE_SETPROCTITLE
snprintf(proctitle, sizeof(proctitle), "%s: %s", remotehost, pw->pw_name);
setproctitle("%s", proctitle);
#endif /* HAVE_SETPROCTITLE */
if (logging) {
char data_addr[256];
if (inet_ntop (his_addr->sa_family,
socket_get_address(his_addr),
data_addr, sizeof(data_addr)) == NULL)
strlcpy (data_addr, "unknown address",
sizeof(data_addr));
syslog(LOG_INFO, "FTP LOGIN FROM %s(%s) as %s",
remotehost,
data_addr,
pw->pw_name);
}
}
umask(defumask);
return 0;
}
/*
* Terminate login as previous user, if any, resetting state;
* used when USER command is given or login fails.
*/
static void
end_login(void)
{
seteuid((uid_t)0);
if (logged_in)
ftpd_logwtmp(ttyline, "", "");
pw = NULL;
logged_in = 0;
guest = 0;
dochroot = 0;
}
#ifdef KRB5
static int
krb5_verify(struct passwd *pwd, char *passwd)
{
krb5_context context;
krb5_ccache id;
krb5_principal princ;
krb5_error_code ret;
ret = krb5_init_context(&context);
if(ret)
return ret;
ret = krb5_parse_name(context, pwd->pw_name, &princ);
if(ret){
krb5_free_context(context);
return ret;
}
ret = krb5_cc_gen_new(context, &krb5_mcc_ops, &id);
if(ret){
krb5_free_principal(context, princ);
krb5_free_context(context);
return ret;
}
ret = krb5_verify_user(context,
princ,
id,
passwd,
1,
NULL);
krb5_free_principal(context, princ);
if (k_hasafs()) {
krb5_afslog_uid_home(context, id,NULL, NULL,pwd->pw_uid, pwd->pw_dir);
}
krb5_cc_destroy(context, id);
krb5_free_context (context);
if(ret)
return ret;
return 0;
}
#endif /* KRB5 */
void
pass(char *passwd)
{
int rval;
/* some clients insists on sending a password */
if (logged_in && askpasswd == 0){
reply(230, "Password not necessary");
return;
}
if (logged_in || askpasswd == 0) {
reply(503, "Login with USER first.");
return;
}
askpasswd = 0;
rval = 1;
if (!guest) { /* "ftp" is only account allowed no password */
if (pw == NULL)
rval = 1; /* failure below */
#ifdef OTP
else if (otp_verify_user (&otp_ctx, passwd) == 0) {
rval = 0;
}
#endif
else if((auth_level & AUTH_OTP) == 0) {
#ifdef KRB5
rval = krb5_verify(pw, passwd);
#endif
#ifdef KRB4
if (rval) {
char realm[REALM_SZ];
if((rval = krb_get_lrealm(realm, 1)) == KSUCCESS)
rval = krb_verify_user(pw->pw_name,
"", realm,
passwd,
KRB_VERIFY_SECURE, NULL);
if (rval == KSUCCESS ) {
chown (tkt_string(), pw->pw_uid, pw->pw_gid);
if(k_hasafs())
krb_afslog(0, 0);
}
}
#endif
if (rval)
rval = unix_verify_user(pw->pw_name, passwd);
} else {
char *s;
#ifdef OTP
if ((s = otp_error(&otp_ctx)) != NULL)
lreply(530, "OTP: %s", s);
#endif
}
memset (passwd, 0, strlen(passwd));
/*
* If rval == 1, the user failed the authentication
* check above. If rval == 0, either Kerberos or
* local authentication succeeded.
*/
if (rval) {
char data_addr[256];
if (inet_ntop (his_addr->sa_family,
socket_get_address(his_addr),
data_addr, sizeof(data_addr)) == NULL)
strlcpy (data_addr, "unknown address",
sizeof(data_addr));
reply(530, "Login incorrect.");
if (logging)
syslog(LOG_NOTICE,
"FTP LOGIN FAILED FROM %s(%s), %s",
remotehost,
data_addr,
curname);
pw = NULL;
if (login_attempts++ >= 5) {
syslog(LOG_NOTICE,
"repeated login failures from %s(%s)",
remotehost,
data_addr);
exit(0);
}
return;
}
}
if(!do_login(230, passwd))
return;
/* Forget all about it... */
end_login();
}
void
retrieve(const char *cmd, char *name)
{
FILE *fin = NULL, *dout;
struct stat st;
int (*closefunc) (FILE *);
char line[BUFSIZ];
if (cmd == 0) {
fin = fopen(name, "r");
closefunc = fclose;
st.st_size = 0;
if(fin == NULL){
int save_errno = errno;
struct cmds {
const char *ext;
const char *cmd;
const char *rev_cmd;
} cmds[] = {
{".tar", "/bin/gtar cPf - %s", NULL},
{".tar.gz", "/bin/gtar zcPf - %s", NULL},
{".tar.Z", "/bin/gtar ZcPf - %s", NULL},
{".gz", "/bin/gzip -c -- %s", "/bin/gzip -c -d -- %s"},
{".Z", "/bin/compress -c -- %s", "/bin/uncompress -c -- %s"},
{NULL, NULL}
};
struct cmds *p;
for(p = cmds; p->ext; p++){
char *tail = name + strlen(name) - strlen(p->ext);
char c = *tail;
if(strcmp(tail, p->ext) == 0 &&
(*tail = 0) == 0 &&
access(name, R_OK) == 0){
snprintf (line, sizeof(line), p->cmd, name);
*tail = c;
break;
}
*tail = c;
if (p->rev_cmd != NULL) {
char *ext;
asprintf(&ext, "%s%s", name, p->ext);
if (ext != NULL) {
if (access(ext, R_OK) == 0) {
snprintf (line, sizeof(line),
p->rev_cmd, ext);
free(ext);
break;
}
free(ext);
}
}
}
if(p->ext){
fin = ftpd_popen(line, "r", 0, 0);
closefunc = ftpd_pclose;
st.st_size = -1;
cmd = line;
} else
errno = save_errno;
}
} else {
snprintf(line, sizeof(line), cmd, name);
name = line;
fin = ftpd_popen(line, "r", 1, 0);
closefunc = ftpd_pclose;
st.st_size = -1;
}
if (fin == NULL) {
if (errno != 0) {
perror_reply(550, name);
if (cmd == 0) {
LOGCMD("get", name);
}
}
return;
}
byte_count = -1;
if (cmd == 0){
if(fstat(fileno(fin), &st) < 0 || !S_ISREG(st.st_mode)) {
reply(550, "%s: not a plain file.", name);
goto done;
}
}
if (restart_point) {
if (type == TYPE_A) {
off_t i, n;
int c;
n = restart_point;
i = 0;
while (i++ < n) {
if ((c=getc(fin)) == EOF) {
perror_reply(550, name);
goto done;
}
if (c == '\n')
i++;
}
} else if (lseek(fileno(fin), restart_point, SEEK_SET) < 0) {
perror_reply(550, name);
goto done;
}
}
dout = dataconn(name, st.st_size, "w");
if (dout == NULL)
goto done;
set_buffer_size(fileno(dout), 0);
send_data(fin, dout);
fclose(dout);
data = -1;
pdata = -1;
done:
if (cmd == 0)
LOGBYTES("get", name, byte_count);
(*closefunc)(fin);
}
/* filename sanity check */
int
filename_check(char *filename)
{
unsigned char *p;
p = (unsigned char *)strrchr(filename, '/');
if(p)
filename = p + 1;
p = filename;
if(isalnum(*p)){
p++;
while(*p && (isalnum(*p) || strchr(good_chars, *p)))
p++;
if(*p == '\0')
return 0;
}
lreply(553, "\"%s\" is not an acceptable filename.", filename);
lreply(553, "The filename must start with an alphanumeric "
"character and must only");
reply(553, "consist of alphanumeric characters or any of the following: %s",
good_chars);
return 1;
}
void
do_store(char *name, char *mode, int unique)
{
FILE *fout, *din;
struct stat st;
int (*closefunc) (FILE *);
if(guest && filename_check(name))
return;
if (unique && stat(name, &st) == 0 &&
(name = gunique(name)) == NULL) {
LOGCMD(*mode == 'w' ? "put" : "append", name);
return;
}
if (restart_point)
mode = "r+";
fout = fopen(name, mode);
closefunc = fclose;
if (fout == NULL) {
perror_reply(553, name);
LOGCMD(*mode == 'w' ? "put" : "append", name);
return;
}
byte_count = -1;
if (restart_point) {
if (type == TYPE_A) {
off_t i, n;
int c;
n = restart_point;
i = 0;
while (i++ < n) {
if ((c=getc(fout)) == EOF) {
perror_reply(550, name);
goto done;
}
if (c == '\n')
i++;
}
/*
* We must do this seek to "current" position
* because we are changing from reading to
* writing.
*/
if (fseek(fout, 0L, SEEK_CUR) < 0) {
perror_reply(550, name);
goto done;
}
} else if (lseek(fileno(fout), restart_point, SEEK_SET) < 0) {
perror_reply(550, name);
goto done;
}
}
din = dataconn(name, (off_t)-1, "r");
if (din == NULL)
goto done;
set_buffer_size(fileno(din), 1);
if (receive_data(din, fout) == 0) {
if((*closefunc)(fout) < 0)
perror_reply(552, name);
else {
if (unique)
reply(226, "Transfer complete (unique file name:%s).",
name);
else
reply(226, "Transfer complete.");
}
} else
(*closefunc)(fout);
fclose(din);
data = -1;
pdata = -1;
done:
LOGBYTES(*mode == 'w' ? "put" : "append", name, byte_count);
}
static FILE *
getdatasock(const char *mode)
{
int s, t, tries;
if (data >= 0)
return (fdopen(data, mode));
seteuid(0);
s = socket(ctrl_addr->sa_family, SOCK_STREAM, 0);
if (s < 0)
goto bad;
socket_set_reuseaddr (s, 1);
/* anchor socket to avoid multi-homing problems */
socket_set_address_and_port (data_source,
socket_get_address (ctrl_addr),
socket_get_port (data_source));
for (tries = 1; ; tries++) {
if (bind(s, data_source,
socket_sockaddr_size (data_source)) >= 0)
break;
if (errno != EADDRINUSE || tries > 10)
goto bad;
sleep(tries);
}
seteuid(pw->pw_uid);
#ifdef IPTOS_THROUGHPUT
socket_set_tos (s, IPTOS_THROUGHPUT);
#endif
return (fdopen(s, mode));
bad:
/* Return the real value of errno (close may change it) */
t = errno;
seteuid((uid_t)pw->pw_uid);
close(s);
errno = t;
return (NULL);
}
static int
accept_with_timeout(int socket,
struct sockaddr *address,
socklen_t *address_len,
struct timeval *timeout)
{
int ret;
fd_set rfd;
FD_ZERO(&rfd);
FD_SET(socket, &rfd);
ret = select(socket + 1, &rfd, NULL, NULL, timeout);
if(ret < 0)
return ret;
if(ret == 0) {
errno = ETIMEDOUT;
return -1;
}
return accept(socket, address, address_len);
}
static FILE *
dataconn(const char *name, off_t size, const char *mode)
{
char sizebuf[32];
FILE *file;
int retry = 0;
file_size = size;
byte_count = 0;
if (size >= 0)
snprintf(sizebuf, sizeof(sizebuf), " (%ld bytes)", (long)size);
else
*sizebuf = '\0';
if (pdata >= 0) {
struct sockaddr_storage from_ss;
struct sockaddr *from = (struct sockaddr *)&from_ss;
struct timeval timeout;
int s;
socklen_t fromlen = sizeof(from_ss);
timeout.tv_sec = 15;
timeout.tv_usec = 0;
s = accept_with_timeout(pdata, from, &fromlen, &timeout);
if (s < 0) {
reply(425, "Can't open data connection.");
close(pdata);
pdata = -1;
return (NULL);
}
close(pdata);
pdata = s;
#if defined(IP_TOS) && defined(HAVE_SETSOCKOPT)
{
int tos = IPTOS_THROUGHPUT;
setsockopt(s, IPPROTO_IP, IP_TOS, (void *)&tos,
sizeof(tos));
}
#endif
reply(150, "Opening %s mode data connection for '%s'%s.",
type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
return (fdopen(pdata, mode));
}
if (data >= 0) {
reply(125, "Using existing data connection for '%s'%s.",
name, sizebuf);
usedefault = 1;
return (fdopen(data, mode));
}
if (usedefault)
data_dest = his_addr;
usedefault = 1;
file = getdatasock(mode);
if (file == NULL) {
char data_addr[256];
if (inet_ntop (data_source->sa_family,
socket_get_address(data_source),
data_addr, sizeof(data_addr)) == NULL)
strlcpy (data_addr, "unknown address",
sizeof(data_addr));
reply(425, "Can't create data socket (%s,%d): %s.",
data_addr,
socket_get_port (data_source),
strerror(errno));
return (NULL);
}
data = fileno(file);
while (connect(data, data_dest,
socket_sockaddr_size(data_dest)) < 0) {
if (errno == EADDRINUSE && retry < swaitmax) {
sleep(swaitint);
retry += swaitint;
continue;
}
perror_reply(425, "Can't build data connection");
fclose(file);
data = -1;
return (NULL);
}
reply(150, "Opening %s mode data connection for '%s'%s.",
type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
return (file);
}
/*
* Tranfer the contents of "instr" to "outstr" peer using the appropriate
* encapsulation of the data subject * to Mode, Structure, and Type.
*
* NB: Form isn't handled.
*/
static void
send_data(FILE *instr, FILE *outstr)
{
int c, cnt, filefd, netfd;
static char *buf;
static size_t bufsize;
transflag = 1;
switch (type) {
case TYPE_A:
while ((c = getc(instr)) != EOF) {
if (urgflag && handleoobcmd())
return;
byte_count++;
if(c == '\n')
sec_putc('\r', outstr);
sec_putc(c, outstr);
}
sec_fflush(outstr);
transflag = 0;
urgflag = 0;
if (ferror(instr))
goto file_err;
if (ferror(outstr))
goto data_err;
reply(226, "Transfer complete.");
return;
case TYPE_I:
case TYPE_L:
#if 0 /* XXX handle urg flag */
#if defined(HAVE_MMAP) && !defined(NO_MMAP)
#ifndef MAP_FAILED
#define MAP_FAILED (-1)
#endif
{
struct stat st;
char *chunk;
int in = fileno(instr);
if(fstat(in, &st) == 0 && S_ISREG(st.st_mode)
&& st.st_size > 0) {
/*
* mmap zero bytes has potential of loosing, don't do it.
*/
chunk = mmap(0, st.st_size, PROT_READ,
MAP_SHARED, in, 0);
if((void *)chunk != (void *)MAP_FAILED) {
cnt = st.st_size - restart_point;
sec_write(fileno(outstr), chunk + restart_point, cnt);
if (munmap(chunk, st.st_size) < 0)
warn ("munmap");
sec_fflush(outstr);
byte_count = cnt;
transflag = 0;
urgflag = 0;
}
}
}
#endif
#endif
if(transflag) {
struct stat st;
netfd = fileno(outstr);
filefd = fileno(instr);
buf = alloc_buffer (buf, &bufsize,
fstat(filefd, &st) >= 0 ? &st : NULL);
if (buf == NULL) {
transflag = 0;
urgflag = 0;
perror_reply(451, "Local resource failure: malloc");
return;
}
while ((cnt = read(filefd, buf, bufsize)) > 0 &&
sec_write(netfd, buf, cnt) == cnt) {
byte_count += cnt;
if (urgflag && handleoobcmd())
return;
}
sec_fflush(outstr); /* to end an encrypted stream */
transflag = 0;
urgflag = 0;
if (cnt != 0) {
if (cnt < 0)
goto file_err;
goto data_err;
}
}
reply(226, "Transfer complete.");
return;
default:
transflag = 0;
urgflag = 0;
reply(550, "Unimplemented TYPE %d in send_data", type);
return;
}
data_err:
transflag = 0;
urgflag = 0;
perror_reply(426, "Data connection");
return;
file_err:
transflag = 0;
urgflag = 0;
perror_reply(551, "Error on input file");
}
/*
* Transfer data from peer to "outstr" using the appropriate encapulation of
* the data subject to Mode, Structure, and Type.
*
* N.B.: Form isn't handled.
*/
static int
receive_data(FILE *instr, FILE *outstr)
{
int cnt, bare_lfs = 0;
static char *buf;
static size_t bufsize;
struct stat st;
transflag = 1;
buf = alloc_buffer (buf, &bufsize,
fstat(fileno(outstr), &st) >= 0 ? &st : NULL);
if (buf == NULL) {
transflag = 0;
urgflag = 0;
perror_reply(451, "Local resource failure: malloc");
return -1;
}
switch (type) {
case TYPE_I:
case TYPE_L:
while ((cnt = sec_read(fileno(instr), buf, bufsize)) > 0) {
if (write(fileno(outstr), buf, cnt) != cnt)
goto file_err;
byte_count += cnt;
if (urgflag && handleoobcmd())
return (-1);
}
if (cnt < 0)
goto data_err;
transflag = 0;
urgflag = 0;
return (0);
case TYPE_E:
reply(553, "TYPE E not implemented.");
transflag = 0;
urgflag = 0;
return (-1);
case TYPE_A:
{
char *p, *q;
int cr_flag = 0;
while ((cnt = sec_read(fileno(instr),
buf + cr_flag,
bufsize - cr_flag)) > 0){
if (urgflag && handleoobcmd())
return (-1);
byte_count += cnt;
cnt += cr_flag;
cr_flag = 0;
for(p = buf, q = buf; p < buf + cnt;) {
if(*p == '\n')
bare_lfs++;
if(*p == '\r') {
if(p == buf + cnt - 1){
cr_flag = 1;
p++;
continue;
}else if(p[1] == '\n'){
*q++ = '\n';
p += 2;
continue;
}
}
*q++ = *p++;
}
fwrite(buf, q - buf, 1, outstr);
if(cr_flag)
buf[0] = '\r';
}
if(cr_flag)
putc('\r', outstr);
fflush(outstr);
if (ferror(instr))
goto data_err;
if (ferror(outstr))
goto file_err;
transflag = 0;
urgflag = 0;
if (bare_lfs) {
lreply(226, "WARNING! %d bare linefeeds received in ASCII mode\r\n"
" File may not have transferred correctly.\r\n",
bare_lfs);
}
return (0);
}
default:
reply(550, "Unimplemented TYPE %d in receive_data", type);
transflag = 0;
urgflag = 0;
return (-1);
}
data_err:
transflag = 0;
urgflag = 0;
perror_reply(426, "Data Connection");
return (-1);
file_err:
transflag = 0;
urgflag = 0;
perror_reply(452, "Error writing file");
return (-1);
}
void
statfilecmd(char *filename)
{
FILE *fin;
int c;
char line[LINE_MAX];
snprintf(line, sizeof(line), "/bin/ls -la -- %s", filename);
fin = ftpd_popen(line, "r", 1, 0);
lreply(211, "status of %s:", filename);
while ((c = getc(fin)) != EOF) {
if (c == '\n') {
if (ferror(stdout)){
perror_reply(421, "control connection");
ftpd_pclose(fin);
dologout(1);
/* NOTREACHED */
}
if (ferror(fin)) {
perror_reply(551, filename);
ftpd_pclose(fin);
return;
}
putc('\r', stdout);
}
putc(c, stdout);
}
ftpd_pclose(fin);
reply(211, "End of Status");
}
void
statcmd(void)
{
#if 0
struct sockaddr_in *sin;
u_char *a, *p;
lreply(211, "%s FTP server (%s) status:", hostname, version);
printf(" %s\r\n", version);
printf(" Connected to %s", remotehost);
if (!isdigit(remotehost[0]))
printf(" (%s)", inet_ntoa(his_addr.sin_addr));
printf("\r\n");
if (logged_in) {
if (guest)
printf(" Logged in anonymously\r\n");
else
printf(" Logged in as %s\r\n", pw->pw_name);
} else if (askpasswd)
printf(" Waiting for password\r\n");
else
printf(" Waiting for user name\r\n");
printf(" TYPE: %s", typenames[type]);
if (type == TYPE_A || type == TYPE_E)
printf(", FORM: %s", formnames[form]);
if (type == TYPE_L)
#if NBBY == 8
printf(" %d", NBBY);
#else
printf(" %d", bytesize); /* need definition! */
#endif
printf("; STRUcture: %s; transfer MODE: %s\r\n",
strunames[stru], modenames[mode]);
if (data != -1)
printf(" Data connection open\r\n");
else if (pdata != -1) {
printf(" in Passive mode");
sin = &pasv_addr;
goto printaddr;
} else if (usedefault == 0) {
printf(" PORT");
sin = &data_dest;
printaddr:
a = (u_char *) &sin->sin_addr;
p = (u_char *) &sin->sin_port;
#define UC(b) (((int) b) & 0xff)
printf(" (%d,%d,%d,%d,%d,%d)\r\n", UC(a[0]),
UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
#undef UC
} else
printf(" No data connection\r\n");
#endif
reply(211, "End of status");
}
void
fatal(char *s)
{
reply(451, "Error in server: %s\n", s);
reply(221, "Closing connection due to server error.");
dologout(0);
/* NOTREACHED */
}
static void
int_reply(int, char *, const char *, va_list)
#ifdef __GNUC__
__attribute__ ((format (printf, 3, 0)))
#endif
;
static void
int_reply(int n, char *c, const char *fmt, va_list ap)
{
char buf[10240];
char *p;
p=buf;
if(n){
snprintf(p, sizeof(buf), "%d%s", n, c);
p+=strlen(p);
}
vsnprintf(p, sizeof(buf) - strlen(p), fmt, ap);
p+=strlen(p);
snprintf(p, sizeof(buf) - strlen(p), "\r\n");
p+=strlen(p);
sec_fprintf(stdout, "%s", buf);
fflush(stdout);
if (debug)
syslog(LOG_DEBUG, "<--- %s- ", buf);
}
void
reply(int n, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
int_reply(n, " ", fmt, ap);
delete_ftp_command();
va_end(ap);
}
void
lreply(int n, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
int_reply(n, "-", fmt, ap);
va_end(ap);
}
void
nreply(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
int_reply(0, NULL, fmt, ap);
va_end(ap);
}
static void
ack(char *s)
{
reply(250, "%s command successful.", s);
}
void
nack(char *s)
{
reply(502, "%s command not implemented.", s);
}
void
do_delete(char *name)
{
struct stat st;
LOGCMD("delete", name);
if (stat(name, &st) < 0) {
perror_reply(550, name);
return;
}
if ((st.st_mode&S_IFMT) == S_IFDIR) {
if (rmdir(name) < 0) {
perror_reply(550, name);
return;
}
goto done;
}
if (unlink(name) < 0) {
perror_reply(550, name);
return;
}
done:
ack("DELE");
}
void
cwd(char *path)
{
if (chdir(path) < 0)
perror_reply(550, path);
else
ack("CWD");
}
void
makedir(char *name)
{
LOGCMD("mkdir", name);
if(guest && filename_check(name))
return;
if (mkdir(name, 0777) < 0)
perror_reply(550, name);
else{
if(guest)
chmod(name, 0700); /* guest has umask 777 */
reply(257, "MKD command successful.");
}
}
void
removedir(char *name)
{
LOGCMD("rmdir", name);
if (rmdir(name) < 0)
perror_reply(550, name);
else
ack("RMD");
}
void
pwd(void)
{
char path[MaxPathLen];
char *ret;
/* SunOS has a broken getcwd that does popen(pwd) (!!!), this
* failes miserably when running chroot
*/
ret = getcwd(path, sizeof(path));
if (ret == NULL)
reply(550, "%s.", strerror(errno));
else
reply(257, "\"%s\" is current directory.", path);
}
char *
renamefrom(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 (name);
}
void
renamecmd(char *from, char *to)
{
LOGCMD2("rename", from, to);
if(guest && filename_check(to))
return;
if (rename(from, to) < 0)
perror_reply(550, "rename");
else
ack("RNTO");
}
static void
dolog(struct sockaddr *sa, int len)
{
getnameinfo_verified (sa, len, remotehost, sizeof(remotehost),
NULL, 0, 0);
#ifdef HAVE_SETPROCTITLE
snprintf(proctitle, sizeof(proctitle), "%s: connected", remotehost);
setproctitle("%s", proctitle);
#endif /* HAVE_SETPROCTITLE */
if (logging) {
char data_addr[256];
if (inet_ntop (his_addr->sa_family,
socket_get_address(his_addr),
data_addr, sizeof(data_addr)) == NULL)
strlcpy (data_addr, "unknown address",
sizeof(data_addr));
syslog(LOG_INFO, "connection from %s(%s)",
remotehost,
data_addr);
}
}
/*
* Record logout in wtmp file
* and exit with supplied status.
*/
void
dologout(int status)
{
transflag = 0;
urgflag = 0;
if (logged_in) {
seteuid((uid_t)0);
ftpd_logwtmp(ttyline, "", "");
#ifdef KRB4
cond_kdestroy();
#endif
}
/* beware of flushing buffers after a SIGPIPE */
#ifdef XXX
exit(status);
#else
_exit(status);
#endif
}
void abor(void)
{
if (!transflag)
return;
reply(426, "Transfer aborted. Data connection closed.");
reply(226, "Abort successful");
transflag = 0;
}
static void
myoob(int signo)
{
urgflag = 1;
}
static char *
mec_space(char *p)
{
while(isspace(*(unsigned char *)p))
p++;
return p;
}
static int
handleoobcmd(void)
{
char *cp;
/* only process if transfer occurring */
if (!transflag)
return 0;
urgflag = 0;
cp = tmpline;
if (ftpd_getline(cp, sizeof(tmpline)) == NULL) {
reply(221, "You could at least say goodbye.");
dologout(0);
}
if (strncasecmp("MIC", cp, 3) == 0) {
mec(mec_space(cp + 3), prot_safe);
} else if (strncasecmp("CONF", cp, 4) == 0) {
mec(mec_space(cp + 4), prot_confidential);
} else if (strncasecmp("ENC", cp, 3) == 0) {
mec(mec_space(cp + 3), prot_private);
} else if (!allow_insecure_oob) {
reply(533, "Command protection level denied "
"for paranoid reasons.");
goto out;
}
if (secure_command())
cp = ftp_command;
if (strcasecmp(cp, "ABOR\r\n") == 0) {
abor();
} else if (strcasecmp(cp, "STAT\r\n") == 0) {
if (file_size != (off_t) -1)
reply(213, "Status: %ld of %ld bytes transferred",
(long)byte_count,
(long)file_size);
else
reply(213, "Status: %ld bytes transferred",
(long)byte_count);
}
out:
return (transflag == 0);
}
/*
* Note: a response of 425 is not mentioned as a possible response to
* the PASV command in RFC959. However, it has been blessed as
* a legitimate response by Jon Postel in a telephone conversation
* with Rick Adams on 25 Jan 89.
*/
void
pasv(void)
{
socklen_t len;
char *p, *a;
struct sockaddr_in *sin;
if (ctrl_addr->sa_family != AF_INET) {
reply(425,
"You cannot do PASV with something that's not IPv4");
return;
}
if(pdata != -1)
close(pdata);
pdata = socket(ctrl_addr->sa_family, SOCK_STREAM, 0);
if (pdata < 0) {
perror_reply(425, "Can't open passive connection");
return;
}
pasv_addr->sa_family = ctrl_addr->sa_family;
socket_set_address_and_port (pasv_addr,
socket_get_address (ctrl_addr),
0);
socket_set_portrange(pdata, restricted_data_ports,
pasv_addr->sa_family);
seteuid(0);
if (bind(pdata, pasv_addr, socket_sockaddr_size (pasv_addr)) < 0) {
seteuid(pw->pw_uid);
goto pasv_error;
}
seteuid(pw->pw_uid);
len = sizeof(pasv_addr_ss);
if (getsockname(pdata, pasv_addr, &len) < 0)
goto pasv_error;
if (listen(pdata, 1) < 0)
goto pasv_error;
sin = (struct sockaddr_in *)pasv_addr;
a = (char *) &sin->sin_addr;
p = (char *) &sin->sin_port;
#define UC(b) (((int) b) & 0xff)
reply(227, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)", UC(a[0]),
UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
return;
pasv_error:
close(pdata);
pdata = -1;
perror_reply(425, "Can't open passive connection");
return;
}
void
epsv(char *proto)
{
socklen_t len;
pdata = socket(ctrl_addr->sa_family, SOCK_STREAM, 0);
if (pdata < 0) {
perror_reply(425, "Can't open passive connection");
return;
}
pasv_addr->sa_family = ctrl_addr->sa_family;
socket_set_address_and_port (pasv_addr,
socket_get_address (ctrl_addr),
0);
socket_set_portrange(pdata, restricted_data_ports,
pasv_addr->sa_family);
seteuid(0);
if (bind(pdata, pasv_addr, socket_sockaddr_size (pasv_addr)) < 0) {
seteuid(pw->pw_uid);
goto pasv_error;
}
seteuid(pw->pw_uid);
len = sizeof(pasv_addr_ss);
if (getsockname(pdata, pasv_addr, &len) < 0)
goto pasv_error;
if (listen(pdata, 1) < 0)
goto pasv_error;
reply(229, "Entering Extended Passive Mode (|||%d|)",
ntohs(socket_get_port (pasv_addr)));
return;
pasv_error:
close(pdata);
pdata = -1;
perror_reply(425, "Can't open passive connection");
return;
}
void
eprt(char *str)
{
char *end;
char sep;
int af;
int ret;
int port;
usedefault = 0;
if (pdata >= 0) {
close(pdata);
pdata = -1;
}
sep = *str++;
if (sep == '\0') {
reply(500, "Bad syntax in EPRT");
return;
}
af = strtol (str, &end, 0);
if (af == 0 || *end != sep) {
reply(500, "Bad syntax in EPRT");
return;
}
str = end + 1;
switch (af) {
#ifdef HAVE_IPV6
case 2 :
data_dest->sa_family = AF_INET6;
break;
#endif
case 1 :
data_dest->sa_family = AF_INET;
break;
default :
reply(522, "Network protocol %d not supported, use (1"
#ifdef HAVE_IPV6
",2"
#endif
")", af);
return;
}
end = strchr (str, sep);
if (end == NULL) {
reply(500, "Bad syntax in EPRT");
return;
}
*end = '\0';
ret = inet_pton (data_dest->sa_family, str,
socket_get_address (data_dest));
if (ret != 1) {
reply(500, "Bad address syntax in EPRT");
return;
}
str = end + 1;
port = strtol (str, &end, 0);
if (port == 0 || *end != sep) {
reply(500, "Bad port syntax in EPRT");
return;
}
socket_set_port (data_dest, htons(port));
reply(200, "EPRT command successful.");
}
/*
* Generate unique name for file with basename "local".
* The file named "local" is already known to exist.
* Generates failure reply on error.
*/
static char *
gunique(char *local)
{
static char new[MaxPathLen];
struct stat st;
int count;
char *cp;
cp = strrchr(local, '/');
if (cp)
*cp = '\0';
if (stat(cp ? local : ".", &st) < 0) {
perror_reply(553, cp ? local : ".");
return NULL;
}
if (cp)
*cp = '/';
for (count = 1; count < 100; count++) {
snprintf (new, sizeof(new), "%s.%d", local, count);
if (stat(new, &st) < 0)
return (new);
}
reply(452, "Unique file name cannot be created.");
return (NULL);
}
/*
* Format and send reply containing system error number.
*/
void
perror_reply(int code, const char *string)
{
reply(code, "%s: %s.", string, strerror(errno));
}
static char *onefile[] = {
"",
0
};
void
list_file(char *file)
{
if(use_builtin_ls) {
FILE *dout;
dout = dataconn(file, -1, "w");
if (dout == NULL)
return;
set_buffer_size(fileno(dout), 0);
if(builtin_ls(dout, file) == 0)
reply(226, "Transfer complete.");
else
reply(451, "Requested action aborted. Local error in processing.");
fclose(dout);
data = -1;
pdata = -1;
} else {
#ifdef HAVE_LS_A
const char *cmd = "/bin/ls -lA %s";
#else
const char *cmd = "/bin/ls -la %s";
#endif
retrieve(cmd, file);
}
}
void
send_file_list(char *whichf)
{
struct stat st;
DIR *dirp = NULL;
struct dirent *dir;
FILE *dout = NULL;
char **dirlist, *dirname;
int simple = 0;
int freeglob = 0;
glob_t gl;
char buf[MaxPathLen];
if (strpbrk(whichf, "~{[*?") != NULL) {
int flags = GLOB_BRACE|GLOB_NOCHECK|GLOB_QUOTE|GLOB_TILDE|
#ifdef GLOB_MAXPATH
GLOB_MAXPATH
#else
GLOB_LIMIT
#endif
;
memset(&gl, 0, sizeof(gl));
freeglob = 1;
if (glob(whichf, flags, 0, &gl)) {
reply(550, "not found");
goto out;
} else if (gl.gl_pathc == 0) {
errno = ENOENT;
perror_reply(550, whichf);
goto out;
}
dirlist = gl.gl_pathv;
} else {
onefile[0] = whichf;
dirlist = onefile;
simple = 1;
}
while ((dirname = *dirlist++)) {
if (urgflag && handleoobcmd())
goto out;
if (stat(dirname, &st) < 0) {
/*
* If user typed "ls -l", etc, and the client
* used NLST, do what the user meant.
*/
if (dirname[0] == '-' && *dirlist == NULL &&
transflag == 0) {
list_file(dirname);
goto out;
}
perror_reply(550, whichf);
goto out;
}
if (S_ISREG(st.st_mode)) {
if (dout == NULL) {
dout = dataconn("file list", (off_t)-1, "w");
if (dout == NULL)
goto out;
transflag = 1;
}
snprintf(buf, sizeof(buf), "%s%s\n", dirname,
type == TYPE_A ? "\r" : "");
sec_write(fileno(dout), buf, strlen(buf));
byte_count += strlen(dirname) + 1;
continue;
} else if (!S_ISDIR(st.st_mode))
continue;
if ((dirp = opendir(dirname)) == NULL)
continue;
while ((dir = readdir(dirp)) != NULL) {
char nbuf[MaxPathLen];
if (urgflag && handleoobcmd())
goto out;
if (!strcmp(dir->d_name, "."))
continue;
if (!strcmp(dir->d_name, ".."))
continue;
snprintf(nbuf, sizeof(nbuf), "%s/%s", dirname, dir->d_name);
/*
* We have to do a stat to insure it's
* not a directory or special file.
*/
if (simple || (stat(nbuf, &st) == 0 &&
S_ISREG(st.st_mode))) {
if (dout == NULL) {
dout = dataconn("file list", (off_t)-1, "w");
if (dout == NULL)
goto out;
transflag = 1;
}
if(strncmp(nbuf, "./", 2) == 0)
snprintf(buf, sizeof(buf), "%s%s\n", nbuf +2,
type == TYPE_A ? "\r" : "");
else
snprintf(buf, sizeof(buf), "%s%s\n", nbuf,
type == TYPE_A ? "\r" : "");
sec_write(fileno(dout), buf, strlen(buf));
byte_count += strlen(nbuf) + 1;
}
}
closedir(dirp);
}
if (dout == NULL)
reply(550, "No files found.");
else if (ferror(dout) != 0)
perror_reply(550, "Data connection");
else
reply(226, "Transfer complete.");
out:
transflag = 0;
if (dout != NULL){
sec_write(fileno(dout), buf, 0); /* XXX flush */
fclose(dout);
}
data = -1;
pdata = -1;
if (freeglob) {
freeglob = 0;
globfree(&gl);
}
}
int
find(char *pattern)
{
char line[1024];
FILE *f;
snprintf(line, sizeof(line),
"/bin/locate -d %s -- %s",
ftp_rooted("/etc/locatedb"),
pattern);
f = ftpd_popen(line, "r", 1, 1);
if(f == NULL){
perror_reply(550, "/bin/locate");
return 1;
}
lreply(200, "Output from find.");
while(fgets(line, sizeof(line), f)){
if(line[strlen(line)-1] == '\n')
line[strlen(line)-1] = 0;
nreply("%s", line);
}
reply(200, "Done");
ftpd_pclose(f);
return 0;
}