view usr/src/cmd/cmd-inet/usr.sbin/in.ftpd/ftpd.c @ 4:1a15d5aaf794

synchronized with onnv_86 (6202) in onnv-gate
author Koji Uno <koji.uno@sun.com>
date Mon, 31 Aug 2009 14:38:03 +0900
parents c9caec207d52
children
line wrap: on
line source

/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/****************************************************************************  
 
  Copyright (c) 1999,2000,2001 WU-FTPD Development Group.  
  All rights reserved.
  
  Portions Copyright (c) 1980, 1985, 1988, 1989, 1990, 1991, 1993, 1994
    The Regents of the University of California.
  Portions Copyright (c) 1993, 1994 Washington University in Saint Louis.
  Portions Copyright (c) 1996, 1998 Berkeley Software Design, Inc.
  Portions Copyright (c) 1989 Massachusetts Institute of Technology.
  Portions Copyright (c) 1998 Sendmail, Inc.
  Portions Copyright (c) 1983, 1995, 1996, 1997 Eric P.  Allman.
  Portions Copyright (c) 1997 by Stan Barber.
  Portions Copyright (c) 1997 by Kent Landfield.
  Portions Copyright (c) 1991, 1992, 1993, 1994, 1995, 1996, 1997
    Free Software Foundation, Inc.  
 
  Use and distribution of this software and its source code are governed 
  by the terms and conditions of the WU-FTPD Software License ("LICENSE").
 
  If you did not receive a copy of the license, it may be obtained online
  at http://www.wu-ftpd.org/license.html.
 
  $Id: ftpd.c,v 1.111 2000/07/01 18:17:39 wuftpd Exp $
 
****************************************************************************/
/* FTP server. */
#include "config.h"

#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/wait.h>

#ifdef AIX
#include <sys/id.h>
#include <sys/priv.h>
#include <netinet/if_ether.h>
#include <net/if_dl.h>
#endif

#ifdef AUX
#include <compat.h>
#endif

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>

#define FTP_NAMES
#include <arpa/ftp.h>
#include <arpa/inet.h>

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <pwd.h>
#include <grp.h>
#include <setjmp.h>
#include <errno.h>
#include <string.h>
#ifdef INTERNAL_LS
#ifdef HAVE_GLOB_H
#include <glob.h>
#else
#include <wuftpd_glob.h>
#endif
#endif
#ifdef HAVE_GRP_H
#include <grp.h>
#endif
#include <sys/stat.h>

#define VA_LOCAL_DECL	va_list ap;
#define VA_START(f)	va_start(ap, f)
#define VA_END		va_end(ap)

#include "proto.h"

#ifdef HAVE_UFS_QUOTA_H
#include <ufs/quota.h>
#endif
#ifdef HAVE_SYS_FS_UFS_QUOTA_H
#include <sys/fs/ufs_quota.h>
#endif

#ifdef HAVE_SYS_SYSLOG_H
#include <sys/syslog.h>
#endif
#if defined(HAVE_SYSLOG_H) || (!defined(AUTOCONF) && !defined(HAVE_SYS_SYSLOG_H))
#include <syslog.h>
#endif
#ifdef TIME_WITH_SYS_TIME
#include <time.h>
#include <sys/time.h>
#else
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
#endif

#ifdef HAVE_SYS_SENDFILE_H
#include <sys/sendfile.h>
#endif

#include "conversions.h"
#include "extensions.h"

#ifdef SHADOW_PASSWORD
#include <shadow.h>
#endif

#include "pathnames.h"

#ifdef M_UNIX
#include <arpa/nameser.h>
#include <resolv.h>
#endif

#if defined(HAVE_FCNTL_H)
#include <fcntl.h>
#endif

#ifdef HAVE_SYSINFO
#include <sys/systeminfo.h>
#endif

#ifdef KERBEROS
#include <sys/types.h>
#include <auth.h>
#include <krb.h>
#endif

#ifdef ULTRIX_AUTH
#include <auth.h>
#include <sys/svcinfo.h>
#endif

#ifndef HAVE_LSTAT
#define lstat stat
#endif

#ifdef AFS_AUTH
#include <afs/stds.h>
#include <afs/kautils.h>
#endif

#ifdef DCE_AUTH
#include <dce/rpc.h>
#include <dce/sec_login.h>
#include <dce/dce_error.h>
#endif


#ifdef HAVE_DIRENT_H
#include <dirent.h>
#else
#include <sys/dir.h>
#endif

#if defined(USE_LONGJMP)
#define wu_longjmp(x, y)	longjmp((x), (y))
#define wu_setjmp(x)		setjmp(x)
#ifndef JMP_BUF
#define JMP_BUF			jmp_buf
#endif
#else
#define wu_longjmp(x, y)	siglongjmp((x), (y))
#define wu_setjmp(x)		sigsetjmp((x), 1)
#ifndef JMP_BUF
#define JMP_BUF			sigjmp_buf
#endif
#endif

#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 64	/* may be too big */
#endif

#ifndef TRUE
#define  TRUE   1
#endif

#ifndef FALSE
#define  FALSE  !TRUE
#endif

#ifdef MAIL_ADMIN
#define MAILSERVERS 10
#define INCMAILS 10
int mailservers = 0;
char *mailserver[MAILSERVERS];
int incmails = 0;
char *incmail[INCMAILS];
char *mailfrom;
char *email(char *full_address);
FILE *SockOpen(char *host, int clientPort);
char *SockGets(FILE *sockfp, char *buf, int len);
int SockWrite(char *buf, int size, int nels, FILE *sockfp);
int SockPrintf(FILE *sockfp, char *format,...);
int SockPuts(FILE *sockfp, char *buf);
int Reply(FILE *sockfp);
int Send(FILE *sockfp, char *format,...);
#endif /* MAIL_ADMIN */

#if defined(_SCO_DS) && !defined(SIGURG)
#define SIGURG	SIGUSR1
#endif

/* File containing login names NOT to be used on this machine. Commonly used
 * to disallow uucp. */
extern int errno;

extern char *ctime(const time_t *);
#ifndef NO_CRYPT_PROTO
extern char *crypt(const char *, const char *);
#endif

extern char version[];
extern char *home;		/* pointer to home directory for glob */
extern char cbuf[];
extern off_t restart_point;
extern int yyerrorcalled;

struct SOCKSTORAGE ctrl_addr;
struct SOCKSTORAGE data_source;
struct SOCKSTORAGE data_dest;
struct SOCKSTORAGE his_addr;
struct SOCKSTORAGE pasv_addr;
struct SOCKSTORAGE vect_addr;
int route_vectored = 0;
int passive_port_min = 1024;
int passive_port_max = 65535;
int restricted_user = 0;
unsigned short data_port = 0;

#ifdef INET6
int ctrl_v4mapped = 0;
int epsv_all = 0;
int listen_v4 = 0;	/* when set, listen on IPv4 socket in standalone mode */
#endif

#ifdef VIRTUAL
char virtual_root[MAXPATHLEN];
char virtual_banner[MAXPATHLEN];
char virtual_email[MAXPATHLEN];

char virtual_hostname[MAXHOSTNAMELEN];
char virtual_address[MAXHOSTNAMELEN];

extern int virtual_mode;
extern int virtual_ftpaccess;
#endif

#ifdef QUOTA
extern struct dqblk quota;
#endif

#if defined(USE_GSS)
#include "gssutil.h"

extern gss_info_t gss_info;

int allow_ccc = 0;
int ccc_ok = 0;
extern char *cur_auth_type;
#endif /* USE_GSS */

int data;
jmp_buf errcatch;
JMP_BUF urgcatch;
int logged_in = 0;
struct passwd *pw;
char chroot_path[MAXPATHLEN];
int debug = 0;
int disable_rfc931 = 0;
extern unsigned int timeout_idle;
extern unsigned int timeout_maxidle;
extern unsigned int timeout_data;
extern unsigned int timeout_accept;
extern unsigned int timeout_connect;

/* previously defaulted to 1, and -l or -L set them to 1, so that there was
   no way to turn them *off*!  Changed so that the manpage reflects common
   sense.  -L is way noisy; -l we'll change to be "just right".  _H */
int logging = 0;
int log_commands = 0;
int log_security = 0;
int syslogmsg = 0;
static int wtmp_logging = 1;

#ifdef SECUREOSF
#define SecureWare		/* Does this mean it works for all SecureWare? */
#endif

#ifdef HPUX_10_TRUSTED
#include <hpsecurity.h>
#endif

#if defined(SecureWare) || defined(HPUX_10_TRUSTED)
#include <prot.h>
#endif

int anonymous = 1;
int guest;
int type;
int form;
int stru;			/* avoid C keyword */
int mode;
int usedefault = 1;		/* for data transfers */
int pdata = -1;			/* for passive mode */
int transflag;
int ftwflag;
off_t file_size;
off_t byte_count;
int TCPwindowsize = 0;		/* 0 = use system default */
size_t sendbufsz;		/* buffer size to use when sending data */
size_t recvbufsz;		/* buffer size to use when receiving data */

#ifdef TRANSFER_COUNT
off_t data_count_total = 0;	/* total number of data bytes */
off_t data_count_in = 0;
off_t data_count_out = 0;
off_t byte_count_total = 0;	/* total number of general traffic */
off_t byte_count_in = 0;
off_t byte_count_out = 0;
int file_count_total = 0;	/* total number of data files */
int file_count_in = 0;
int file_count_out = 0;
int xfer_count_total = 0;	/* total number of transfers */
int xfer_count_in = 0;
int xfer_count_out = 0;
#ifdef TRANSFER_LIMIT
int file_limit_raw_in = 0;
int file_limit_raw_out = 0;
int file_limit_raw_total = 0;
int file_limit_data_in = 0;
int file_limit_data_out = 0;
int file_limit_data_total = 0;
off_t data_limit_raw_in = 0;
off_t data_limit_raw_out = 0;
off_t data_limit_raw_total = 0;
off_t data_limit_data_in = 0;
off_t data_limit_data_out = 0;
off_t data_limit_data_total = 0;
#ifdef RATIO /* 1998/08/04 K.Wakui */
#define TRUNC_KB(n)   ((n)/1024+(((n)%1024)?1:0))
off_t   total_free_dl = 0;
int     upload_download_rate = 0;
int     freefile;
int     is_downloadfree( char * );
#endif /* RATIO */
#endif
#endif

int retrieve_is_data = 1;	/* !0=data, 0=general traffic -- for 'ls' */
char LastFileTransferred[MAXPATHLEN] = "";

static char *RootDirectory = NULL;

#if !defined(CMASK) || CMASK == 0
#undef CMASK
#define CMASK 022
#endif
mode_t defumask = CMASK;	/* default umask value */
#ifdef ALTERNATE_CD
char defhome[] = "/";
#endif
char tmpline[7];
char hostname[MAXHOSTNAMELEN];
char remotehost[MAXHOSTNAMELEN];
char remoteaddr[MAXHOSTNAMELEN];
char *remoteident = "[nowhere yet]";
int rhlookup = TRUE;		/* when TRUE lookup the remote hosts name */

#if defined(SOLARIS_2) && !defined(NAME_SERVICE_DOOR)
#define NAME_SERVICE_DOOR "/var/run/name_service_door"
#endif

#if defined(SOLARIS_2)
int close_nsdoor(void *cb_data, int fd);
void cleanup_nscd();
#endif

/* log failures         27-apr-93 ehk/bm */
#define MAXUSERNAMELEN	256
char the_user[MAXUSERNAMELEN];

/* Access control and logging passwords */
/* OFF by default.  _H */
int use_accessfile = 0;
char guestpw[MAXHOSTNAMELEN];
char privatepw[MAXHOSTNAMELEN];
int nameserved = 0;
extern char authuser[];
extern int authenticated;
extern int keepalive;

/* File transfer logging (xferlog) */
int xferlog = 0;
int log_outbound_xfers = 0;
int log_incoming_xfers = 0;
char logfile[MAXPATHLEN];

/* Allow use of lreply(); this is here since some older FTP clients don't
 * support continuation messages.  In violation of the RFCs... */
int dolreplies = 1;

/* Spontaneous reply text.  To be sent along with next reply to user */
char *autospout = NULL;
int autospout_free = 0;

/* allowed on-the-fly file manipulations (compress, tar) */
int mangleopts = 0;

/* number of login failures before attempts are logged and FTP *EXITS* */
int lgi_failure_threshold = 5;

/* 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;

SIGNAL_TYPE lostconn(int sig);
SIGNAL_TYPE randomsig(int sig);
SIGNAL_TYPE myoob(int sig);
FILE *getdatasock(char *mode);
FILE *dataconn(char *name, off_t size, char *mode);
void setproctitle(const char *fmt,...);
void initsetproctitle(int, char **, char **);
void reply(int, char *fmt,...);
void lreply(int, char *fmt,...);

#ifndef HAVE_VSNPRINTF
extern int vsnprintf(char *, size_t, const char *, va_list);
#endif

#ifndef HAVE_SNPRINTF
extern int snprintf(char *, size_t, const char *,...);
#endif

#ifdef NEED_SIGFIX
extern sigset_t block_sigmask;	/* defined in sigfix.c */
#endif

char proctitle[BUFSIZ];		/* initial part of title */

#if defined(SKEY) && defined(OPIE)
#error YOU SHOULD NOT HAVE BOTH SKEY AND OPIE DEFINED!!!!!
#endif

#ifdef SKEY
#include <skey.h>
int pwok = 0;
#endif

#ifdef OPIE
#include <opie.h>
int pwok = 0;
int af_pwok = 0;
struct opie opiestate;
#endif

#ifdef KERBEROS
void init_krb();
void end_krb();
char krb_ticket_name[100];
#endif /* KERBEROS */

#ifdef ULTRIX_AUTH
int ultrix_check_pass(char *passwd, char *xpasswd);
#endif

#ifdef USE_PAM
#if defined(ULTRIX_AUTH) || defined(SECUREOSF) || defined(KERBEROS) || defined(SKEY) || defined (OPIE) || defined (BSD_AUTH)
#error No other auth methods are allowed with PAM.
#endif
#include <security/pam_appl.h>
static int pam_check_pass(char *user, char *passwd);
pam_handle_t *pamh;
#endif

#ifndef INTERNAL_LS
/* ls program commands and options for lreplies on and off */
char ls_long[BUFSIZ * 2];
char ls_short[BUFSIZ * 2];
char ls_plain[BUFSIZ * 2];
#endif

#define FTPD_OPTS	":4aAdiIlLoP:qQr:t:T:u:vVwWxX"
#if defined(DAEMON)
#  define DAEMON_OPTS	"p:sS"
#else /* !(defined(DAEMON)) */
#  define DAEMON_OPTS
#endif /* !(defined(DAEMON)) */
#if defined(USE_GSS)
#  define GSS_OPTS	"CK"
#else /* !(defined(USE_GSS)) */
#  define GSS_OPTS
#endif /* !(defined(USE_GSS)) */

/* Some systems use one format, some another.  This takes care of the garbage */
#ifndef L_FORMAT		/* Autoconf detects this... */
#if (defined(BSD) && (BSD >= 199103)) && !defined(LONGOFF_T)
#define L_FORMAT "qd"
#else
#ifdef _AIX42
#define L_FORMAT "lld"
#else
#ifdef SOLARIS_2
#define L_FORMAT "ld"
#else
#define L_FORMAT "d"
#endif
#endif
#endif
#endif

#ifdef DAEMON
int be_daemon = 0;		/* Run standalone? */
int daemon_port = 0;
static void do_daemon(void);
#endif
int Bypass_PID_Files = 0;

#ifdef OTHER_PASSWD
#include "getpwnam.h"
char _path_passwd[MAXPATHLEN];
#ifdef SHADOW_PASSWORD
char _path_shadow[MAXPATHLEN];
#endif
#endif
#if defined(USE_PAM) && defined(OTHER_PASSWD)
int use_pam = 1;
#else
int use_pam = 0;
#endif

void print_copyright(void);
char *mapping_getcwd(char *path, size_t size);

void dolog(struct SOCKSTORAGE *);

#ifdef THROUGHPUT
extern void throughput_calc(char *, int *, double *);
extern void throughput_adjust(char *);
#endif

time_t login_time;
time_t limit_time = 0;

int regexmatch(char *name, char *rgexp);

int pasv_allowed(char *remoteaddr);
int port_allowed(char *remoteaddr);

#if sparc && !__svr4__
int fclose(FILE *);
#endif

static SIGNAL_TYPE alarm_signal(int sig)
{
}

static FILE *draconian_FILE = NULL;

static SIGNAL_TYPE draconian_alarm_signal(int sig)
{
    if (draconian_FILE != NULL) {
	fclose(draconian_FILE);
	draconian_FILE = NULL;
    }
    (void) signal(SIGALRM, draconian_alarm_signal);
}

static void socket_flush_wait(FILE *file)
{
    static int flushwait = TRUE;
    static int first_time = TRUE;
    char c;
    int set;
    int fd = fileno(file);
    int serrno = errno;
    struct aclmember *entry;

    if (first_time) {
	entry = NULL;
	/* flush-wait yes|no [typelist] */
	while (getaclentry("flush-wait", &entry)) {
	    if (!ARG0)
		continue;
	    if (strcasecmp(ARG0, "yes") == 0)
		set = TRUE;
	    else if (strcasecmp(ARG0, "no") == 0)
		set = FALSE;
	    else
		continue;

	    if (!ARG1)
		flushwait = set;
	    else if (type_match(ARG1)) {
		flushwait = set;
		break;
	    }
	}
	first_time = FALSE;
    }
    if (flushwait) {
	if (draconian_FILE != NULL)
	    shutdown(fd, 1);
	if (draconian_FILE != NULL)
	    read(fd, &c, 1);
    }
    errno = serrno;
/*
 * GAL - the read() here should be checked to ensure it returned 0 (indicating
 * EOF) or -1 (an error occurred).  Anything else (real data) is a protocol
 * error.
 */
}

static int IPClassOfService(const char *type)
{
    int ipcos = -1, value;
    char *endp;
    struct aclmember *entry = NULL;

    /* ipcos control|data <value> [<typelist>] */
    while (getaclentry("ipcos", &entry)) {
	if (ARG0 && ARG1) {
	    if (strcasecmp(type, ARG0) == 0) {
		if (!ARG2) {
		    errno = 0;
		    value = (int) strtol(ARG1, &endp, 0);
		    if ((errno == 0) && (value >= 0) && (*endp == '\0'))
			ipcos = value;
		}
		else if (type_match(ARG2)) {
		    errno = 0;
		    value = (int) strtol(ARG1, &endp, 0);
		    if ((errno == 0) && (value >= 0) && (*endp == '\0')) {
			ipcos = value;
			break;
		    }
		}
	    }
	}
    }
    return ipcos;
}

int main(int argc, char **argv, char **envp)
{
#if defined(UNIXWARE) || defined(AIX)
    size_t addrlen;
#else
    int addrlen;
#endif
    int on = 1;
    int cos;
    int c;
#ifndef INTERNAL_LS
    int which;
#endif
    extern int optopt;
    extern char *optarg;
    char *hp;
    struct aclmember *entry;
#ifdef VIRTUAL
#if defined(UNIXWARE) || defined(AIX)
    size_t virtual_len;
#else
    int virtual_len;
#endif
    struct SOCKSTORAGE virtual_addr;
#endif
    struct servent *serv;

#ifdef AUX
    setcompat(COMPAT_POSIX | COMPAT_BSDSETUGID);
#endif

    closelog();
#ifdef FACILITY
    openlog("ftpd", LOG_PID | LOG_NDELAY, FACILITY);
#else
    openlog("ftpd", LOG_PID);
#endif

#ifdef SecureWare
    setluid(1);			/* make sure there is a valid luid */
    set_auth_parameters(argc, argv);
    setreuid(0, 0);
#endif
#if defined(M_UNIX) && !defined(_M_UNIX)
    res_init();			/* bug in old (1.1.1) resolver     */
    _res.retrans = 20;		/* because of fake syslog in 3.2.2 */
    setlogmask(LOG_UPTO(LOG_INFO));
#endif

    while ((c = getopt(argc, argv, FTPD_OPTS DAEMON_OPTS GSS_OPTS)) != -1) {
	switch (c) {

	case '4':
#ifdef INET6
	    listen_v4 = 1;
#endif
	    break;

	case 'a':
	    use_accessfile = 1;
	    break;

	case 'A':
	    use_accessfile = 0;
	    break;

	case 'v':
	    debug = 1;
	    break;

	case 'd':
	    debug = 1;
	    break;

#if defined(USE_GSS)
	case 'C':
	    gss_info.want_creds = 1;
	    break;

	case 'K':
	    gss_info.must_gss_auth = 1;
	    break;
#endif /* USE_GSS */

	case 'l':
	    logging = 1;
	    break;

	case 'L':
	    log_commands = 3;
	    break;

	case 'i':
	    log_incoming_xfers = 3;
	    break;

	case 'I':
	    disable_rfc931 = 1;
	    break;

	case 'o':
	    log_outbound_xfers = 3;
	    break;

	case 'q':
	    Bypass_PID_Files = 0;
	    break;

	case 'Q':
	    Bypass_PID_Files = 1;
	    break;

	case 'r':
	    if ((optarg != NULL) && (optarg[0] != '\0')) {
		RootDirectory = malloc(strlen(optarg) + 1);
		if (RootDirectory != NULL)
		    strcpy(RootDirectory, optarg);
	    }
	    break;

	case 'P':
	    data_port = htons(atoi(optarg));
	    break;

#ifdef DAEMON
	case 'p':
	    daemon_port = atoi(optarg);
	    break;

	case 's':
	    be_daemon = 1;
	    break;

	case 'S':
	    be_daemon = 2;
	    break;
#endif /* DAEMON */

	case 't':
	    timeout_idle = atoi(optarg);
	    if (timeout_maxidle < timeout_idle)
		timeout_maxidle = timeout_idle;
	    break;

	case 'T':
	    timeout_maxidle = atoi(optarg);
	    if (timeout_idle > timeout_maxidle)
		timeout_idle = timeout_maxidle;
	    break;

	case 'u':
	    {
		unsigned int val = 0;

		while (*optarg && *optarg >= '0' && *optarg <= '7')
		    val = val * 8 + *optarg++ - '0';
		if (*optarg || val > 0777)
		    syslog(LOG_ERR, "bad value for -u");
		else
		    defumask = val;
		break;
	    }

	case 'V':
	    print_copyright();
	    exit(0);
	    /* NOTREACHED */
	case 'w':
	    wtmp_logging = 1;
	    break;

	case 'W':
	    wtmp_logging = 0;
	    break;

	case 'x':
	    syslogmsg = 2;
	    break;

	case 'X':
	    syslogmsg = 1;
	    break;

	case ':':
	    syslog(LOG_ERR, "option -%c requires an argument", optopt);
	    break;

	default:
	    syslog(LOG_ERR, "unknown option -%c ignored", optopt);
	    break;
	}
    }
    initsetproctitle(argc, argv, envp);
    (void) freopen(_PATH_DEVNULL, "w", stderr);

    /* Checking for random signals ... */
#ifdef NEED_SIGFIX
    sigemptyset(&block_sigmask);
#endif
#ifndef SIG_DEBUG
#ifdef SIGHUP
    (void) signal(SIGHUP, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGHUP);
#endif
#endif
#ifdef SIGINT
    (void) signal(SIGINT, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGINT);
#endif
#endif
#ifdef SIGQUIT
    (void) signal(SIGQUIT, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGQUIT);
#endif
#endif
#ifdef SIGILL
    (void) signal(SIGILL, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGILL);
#endif
#endif
#ifdef SIGTRAP
    (void) signal(SIGTRAP, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGTRAP);
#endif
#endif
#ifdef SIGIOT
    (void) signal(SIGIOT, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGIOT);
#endif
#endif
#ifdef SIGEMT
    (void) signal(SIGEMT, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGEMT);
#endif
#endif
#ifdef SIGFPE
    (void) signal(SIGFPE, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGFPE);
#endif
#endif
#ifdef SIGKILL
    (void) signal(SIGKILL, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGKILL);
#endif
#endif
#ifdef SIGBUS
    (void) signal(SIGBUS, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGBUS);
#endif
#endif
#ifdef SIGSEGV
    (void) signal(SIGSEGV, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGSEGV);
#endif
#endif
#ifdef SIGSYS
    (void) signal(SIGSYS, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGSYS);
#endif
#endif
#ifdef SIGALRM
    (void) signal(SIGALRM, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGALRM);
#endif
#endif
#ifdef SIGSTOP
    (void) signal(SIGSTOP, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGSTOP);
#endif
#endif
#ifdef SIGTSTP
    (void) signal(SIGTSTP, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGTSTP);
#endif
#endif
#ifdef SIGTTIN
    (void) signal(SIGTTIN, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGTTIN);
#endif
#endif
#ifdef SIGTTOU
    (void) signal(SIGTTOU, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGTTOU);
#endif
#endif
#ifdef SIGIO
    (void) signal(SIGIO, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGIO);
#endif
#endif
#ifdef SIGXCPU
    (void) signal(SIGXCPU, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGXCPU);
#endif
#endif
#ifdef SIGXFSZ
    (void) signal(SIGXFSZ, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGXFSZ);
#endif
#endif
#ifdef SIGWINCH
    (void) signal(SIGWINCH, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGWINCH);
#endif
#endif
#ifdef SIGVTALRM
    (void) signal(SIGVTALRM, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGVTALRM);
#endif
#endif
#ifdef SIGPROF
    (void) signal(SIGPROF, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGPROF);
#endif
#endif
#ifdef SIGUSR1
    (void) signal(SIGUSR1, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGUSR1);
#endif
#endif
#ifdef SIGUSR2
    (void) signal(SIGUSR2, randomsig);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGUSR2);
#endif
#endif

#ifdef SIGPIPE
    (void) signal(SIGPIPE, lostconn);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGPIPE);
#endif
#endif
#ifdef SIGCHLD
    (void) signal(SIGCHLD, SIG_IGN);
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGCHLD);
#endif
#endif

#ifdef SIGURG
    if (signal(SIGURG, myoob) == SIG_ERR)
	syslog(LOG_ERR, "signal: %m");
#ifdef NEED_SIGFIX
    sigaddset(&block_sigmask, SIGURG);
#endif
#endif
#endif /* SIG_DEBUG */

#ifdef VIRTUAL
    virtual_root[0] = '\0';
    virtual_banner[0] = '\0';
#endif

    setup_paths();

#ifdef OTHER_PASSWD
    strcpy(_path_passwd, "/etc/passwd");
#ifdef SHADOW_PASSWORD
    strcpy(_path_shadow, "/etc/shadow");
#endif
#endif

    access_init();

#ifdef DAEMON
    if (be_daemon != 0)
	do_daemon();
    else {
#endif
	addrlen = sizeof(his_addr);
	if (getpeername(0, (struct sockaddr *) &his_addr, &addrlen) < 0) {
	    syslog(LOG_ERR, "getpeername: %m");
#ifndef DEBUG
	    exit(1);
#endif
	}
#ifdef DAEMON
    }
#endif
    addrlen = sizeof(ctrl_addr);
    if (getsockname(0, (struct sockaddr *) &ctrl_addr, &addrlen) < 0) {
	syslog(LOG_ERR, "getsockname: %m");
#ifndef DEBUG
	exit(1);
#endif
    }
    /* Sanity check */
    if ((SOCK_FAMILY(ctrl_addr) != AF_INET)
#ifdef INET6
        && (SOCK_FAMILY(ctrl_addr) != AF_INET6)
#endif
	) {
	syslog(LOG_ERR, "control connection address family (%d) not supported.",
	       SOCK_FAMILY(ctrl_addr));
#ifndef DEBUG
	exit(1);
#endif
    }
#ifdef SOLARIS_BSM_AUDIT
    /* Set audit characteristics */
    if (audit_settid(0)) {
	syslog(LOG_ERR, "audit failure");
	exit(1);
    }
#endif
#ifdef INET6
    /* IP_TOS is an IPv4 socket option */
    if (SOCK_FAMILY(ctrl_addr) == AF_INET)
#endif
    if ((cos = IPClassOfService("control")) >= 0) {
	if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *) &cos, sizeof(int)) < 0)
	    syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
    }

#ifdef TCP_NODELAY
    /*
     * Disable Nagle on the control channel so that we don't have to wait
     * for peer's ACK before issuing our next reply.
     */
    if (setsockopt(0, IPPROTO_TCP, TCP_NODELAY, &on, sizeof (on)) < 0)
	syslog(LOG_WARNING, "control setsockopt TCP_NODELAY: %m");
#endif

    if (keepalive)
	if (setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on)) < 0)
	    syslog(LOG_ERR, "setsockopt SO_KEEPALIVE %m");

    /* Try to handle urgent data inline */
#ifdef SO_OOBINLINE
    if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, (char *) &on, sizeof(int)) < 0)
	    syslog(LOG_ERR, "setsockopt (SO_OOBINLINE): %m");
#endif

#ifdef  F_SETOWN
    if (fcntl(fileno(stdin), F_SETOWN, getpid()) == -1)
	syslog(LOG_ERR, "fcntl F_SETOWN: %m");
#elif defined(SIOCSPGRP)
    {
	int pid;
	pid = getpid();
	if (ioctl(fileno(stdin), SIOCSPGRP, &pid) == -1)
	    syslog(LOG_ERR, "ioctl SIOCSPGRP: %m");
    }
#endif

#ifdef INET6
    if ((SOCK_FAMILY(ctrl_addr) == AF_INET6) &&
	IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)&(ctrl_addr))->sin6_addr))
	ctrl_v4mapped = 1;
#endif

    if (data_port == 0) {
	serv = getservbyname("ftp-data", "tcp");
	if (serv != NULL)
	    data_port = serv->s_port;
	else
	    data_port = htons(ntohs(SOCK_PORT(ctrl_addr)) - 1);
    }

    if (RootDirectory != NULL) {
	if ((chroot(RootDirectory) < 0)
	    || (chdir("/") < 0)) {
	    syslog(LOG_ERR, "Cannot chroot to initial directory, aborting.");
	    exit(1);
	}
    }

    load_timeouts();

    /* set resolver options */
    set_res_options();

    dolog(&his_addr);
    /* Set up default state */
    data = -1;
    type = TYPE_A;
    form = FORM_N;
    stru = STRU_F;
    mode = MODE_S;
    tmpline[0] = '\0';
    yyerrorcalled = 0;

    entry = (struct aclmember *) NULL;
    if ((getaclentry("hostname", &entry)) && ARG0) {
	(void) strncpy(hostname, ARG0, sizeof(hostname));
	hostname[sizeof(hostname) - 1] = '\0';
    }
    else {
#ifdef HAVE_SYSINFO
	sysinfo(SI_HOSTNAME, hostname, sizeof(hostname));
#else
	(void) gethostname(hostname, sizeof(hostname));
#endif
/* set the FQDN here */
	hp = wu_gethostbyname(hostname);
	if (hp) {
	    (void) strncpy(hostname, hp, sizeof(hostname));
	    hostname[sizeof(hostname) - 1] = '\0';
	}
    }
    route_vectored = routevector();
    conv_init();

#ifdef MAIL_ADMIN
    incmails = 0;
    mailfrom = NULL;
#endif /* MAIL_ADMIN */
#ifdef VIRTUAL
    /*
       ** If virtual_mode is set at this point then an alternate ftpaccess
       ** is in use.  Otherwise we need to check the Master ftpaccess file
       ** to see if the site is only using the "virtual" directives to
       ** specify virtual site directives.
       **
       ** In this manner an admin can put a virtual site in the ftpservers
       ** file if they need expanded configuration support or can use the
       ** minimal root/banner/logfile if they do not need any more than that.
     */

    if (virtual_mode) {
	/* Get the root of the virtual server directory */
	entry = (struct aclmember *) NULL;
	if (getaclentry("root", &entry)) {
	    if (ARG0)
		strcpy(virtual_root, ARG0);
	}

	/* Get the logfile to use */
	entry = (struct aclmember *) NULL;
	if (getaclentry("logfile", &entry)) {
	    if (ARG0)
		strcpy(logfile, ARG0);
	}
    }
    else {
	virtual_hostname[0] = '\0';
	virtual_address[0] = '\0';
	virtual_len = sizeof(virtual_addr);
	if (getsockname(0, (struct sockaddr *) &virtual_addr, &virtual_len) == 0) {
	    strcpy(virtual_address, inet_stop(&virtual_addr));
	    wu_gethostbyaddr(&virtual_addr, virtual_hostname, sizeof(virtual_hostname));
	    entry = (struct aclmember *) NULL;
	    while (getaclentry("virtual", &entry)) {
		if (!ARG0 || !ARG1 || !ARG2)
		    continue;
		if (hostmatch(ARG0, virtual_address, virtual_hostname)) {
		    if (!strcasecmp(ARG1, "root")) {
			if (debug)
			    syslog(LOG_DEBUG, "VirtualFTP Connect to: %s [%s]",
				   virtual_hostname, virtual_address);
			virtual_mode = 1;
			strncpy(virtual_root, ARG2, sizeof(virtual_root));
			virtual_root[sizeof(virtual_root) - 1] = '\0';
			/* reset hostname to this virtual name */
			(void) strcpy(hostname, virtual_hostname);
			virtual_email[0] = '\0';
		    }
		    if (!strcasecmp(ARG1, "banner")) {
			strncpy(virtual_banner, ARG2, sizeof(virtual_banner));
			virtual_banner[sizeof(virtual_banner) - 1] = '\0';
		    }
		    if (!strcasecmp(ARG1, "logfile")) {
			strncpy(logfile, ARG2, sizeof(logfile));
			logfile[sizeof(logfile) - 1] = '\0';
		    }
		    if (!strcasecmp(ARG1, "hostname")) {
			strncpy(hostname, ARG2, sizeof(hostname));
			hostname[sizeof(hostname) - 1] = '\0';
		    }
		    if (!strcasecmp(ARG1, "email")) {
			strncpy(virtual_email, ARG2, sizeof(virtual_email));
			virtual_email[sizeof(virtual_email) - 1] = '\0';
		    }
#ifdef OTHER_PASSWD
		    if (!strcasecmp(ARG1, "passwd")) {
			strncpy(_path_passwd, ARG2, sizeof(_path_passwd));
			_path_passwd[sizeof(_path_passwd) - 1] = '\0';
#ifdef USE_PAM
			use_pam = 0;
#endif
		    }
#ifdef SHADOW_PASSWORD
		    if (!strcasecmp(ARG1, "shadow")) {
			strncpy(_path_shadow, ARG2, sizeof(_path_shadow));
			_path_shadow[sizeof(_path_shadow) - 1] = '\0';
#ifdef USE_PAM
			use_pam = 0;
#endif
		    }
#endif
#endif
#ifdef MAIL_ADMIN
		    if (mailfrom == NULL)
			if (!strcasecmp(ARG1, "mailfrom")) {
			    mailfrom = strdup(ARG2);
			}
		    if (!strcasecmp(ARG1, "incmail")) {
			if (incmails < INCMAILS)
			    incmail[incmails++] = strdup(ARG2);
		    }
#endif
		}
	    }
	    if (!virtual_mode) {
		entry = (struct aclmember *) NULL;
		while (getaclentry("defaultserver", &entry)) {
		    if (!ARG0 || !ARG1)
			continue;
#ifdef MAIL_ADMIN
		    if (mailfrom == NULL)
			if (!strcasecmp(ARG0, "mailfrom")) {
			    mailfrom = strdup(ARG1);
			}
		    if (!strcasecmp(ARG0, "incmail")) {
			if (incmails < INCMAILS)
			    incmail[incmails++] = strdup(ARG1);
		    }
#endif
		}
	    }
	}
    }

#ifdef VIRTUAL_DEBUG
    lreply(220, "_path_ftpaccess == %s", _path_ftpaccess);
    lreply(220, "_path_ftpusers == %s", _path_ftpusers);
    lreply(220, "_path_ftphosts == %s", _path_ftphosts);
    lreply(220, "_path_private == %s", _path_private);
    lreply(220, "_path_cvt == %s", _path_cvt);
    if (virtual_mode) {
	if (virtual_ftpaccess)
	    lreply(220, "VIRTUAL Mode: Using %s specific %s access file",
		   hostname, _path_ftpaccess);
	else
	    lreply(220, "VIRTUAL Mode: Using Master access file %s",
		   _path_ftpaccess);

	lreply(220, "virtual_root == %s", virtual_root);
	if (!virtual_ftpaccess)
	    lreply(220, "virtual_banner == %s", virtual_banner);
    }
    lreply(220, "logfile == %s", logfile);
#endif
#endif

    if (is_shutdown(1, 1) != 0) {
	syslog(LOG_INFO, "connection refused (server shut down) from %s",
	       remoteident);
	reply(500, "%s FTP server shut down -- please try again later.",
	      hostname);
	exit(0);
    }

#ifdef OPIE
    af_pwok = opieaccessfile(remotehost);
#endif

    /* check permitted access based on name and address lookup of remote host */
    if (!check_rhost_reverse()) {
	exit(0);
    }
    if (!check_rhost_matches()) {
	exit(0);
    }

    show_banner(220);

#ifndef INTERNAL_LS
    entry = (struct aclmember *) NULL;
    if (getaclentry("lslong", &entry) && ARG0 && (int) strlen(ARG0) > 0) {
	strcpy(ls_long, ARG0);
	for (which = 1; (which < MAXARGS) && ARG[which]; which++) {
	    strcat(ls_long, " ");
	    strcat(ls_long, ARG[which]);
	}
    }
    else {
#if defined(SVR4) || defined(ISC)
#if defined(AIX) || defined(SOLARIS_2)
	strcpy(ls_long, "/bin/ls -lA");
#else
	strcpy(ls_long, "/bin/ls -la");
#endif
#else
	strcpy(ls_long, "/bin/ls -lgA");
#endif
    }
    strcat(ls_long, " %s");

    entry = (struct aclmember *) NULL;
    if (getaclentry("lsshort", &entry) && ARG0 && (int) strlen(ARG0) > 0) {
	strcpy(ls_short, ARG0);
	for (which = 1; (which < MAXARGS) && ARG[which]; which++) {
	    strcat(ls_short, " ");
	    strcat(ls_short, ARG[which]);
	}
    }
    else {
#if defined(SVR4) || defined(ISC)
#if defined(AIX) || defined(SOLARIS_2)
	strcpy(ls_short, "/bin/ls -lA");
#else
	strcpy(ls_short, "/bin/ls -la");

#endif
#else
	strcpy(ls_short, "/bin/ls -lgA");
#endif
    }
    strcat(ls_short, " %s");

    entry = (struct aclmember *) NULL;
    if (getaclentry("lsplain", &entry) && ARG0 && (int) strlen(ARG0) > 0) {
	strcpy(ls_plain, ARG0);
	for (which = 1; (which < MAXARGS) && ARG[which]; which++) {
	    strcat(ls_plain, " ");
	    strcat(ls_plain, ARG[which]);
	}
    }
    else
	strcpy(ls_plain, "/bin/ls");
    strcat(ls_plain, " %s");
#endif /* ! INTERNAL_LS */
#ifdef MAIL_ADMIN
    mailservers = 0;
    entry = (struct aclmember *) NULL;
    while (getaclentry("mailserver", &entry) && ARG0 && mailservers < MAILSERVERS)
	mailserver[mailservers++] = strdup(ARG0);
    if (mailservers == 0)
	mailserver[mailservers++] = strdup("localhost");
    if (incmails == 0) {
	entry = (struct aclmember *) NULL;
	while (getaclentry("incmail", &entry) && ARG0 && incmails < INCMAILS)
	    incmail[incmails++] = strdup(ARG0);
    }
    if (mailfrom == NULL) {
	entry = (struct aclmember *) NULL;
	if (getaclentry("mailfrom", &entry) && ARG0)
	    mailfrom = strdup(ARG0);
	else
	    mailfrom = strdup("wu-ftpd");
    }
#endif /* MAIL_ADMIN */
    {
#define OUTPUT_LEN (BUFSIZ * 2)
	int version_option = 0;
	char output_text[OUTPUT_LEN + 1];
	int which;

	entry = NULL;
	if (getaclentry("greeting", &entry) && ARG0) {
	    if (!strcasecmp(ARG0, "full"))
		version_option = 0;
	    else if (!strcasecmp(ARG0, "text") && ARG1)
		version_option = 3;
	    else if (!strcasecmp(ARG0, "terse"))
		version_option = 2;
	    else if (!strcasecmp(ARG0, "brief"))
		version_option = 1;
	}
	switch (version_option) {
	default:
	    reply(220, "%s FTP server (%s) ready.", hostname, version);
	    break;
	case 1:
	    reply(220, "%s FTP server ready.", hostname);
	    break;
	case 2:
	    reply(220, "FTP server ready.");
	    break;
	case 3:
	    output_text[0] = '\0';
	    for (which = 1; (which < MAXARGS) && ARG[which]; which++) {
		if (which > 1)
		    (void) strlcat(output_text, " ", sizeof(output_text));
		(void) strlcat(output_text, ARG[which], sizeof(output_text));
	    }
	    reply(220, "%s", output_text);
	    break;
	}
    }
    (void) setjmp(errcatch);

    for (;;)
	(void) yyparse();
    /* NOTREACHED */
}


SIGNAL_TYPE randomsig(int sig)
{
#ifdef HAVE_SIGLIST
    syslog(LOG_ERR, "exiting on signal %d: %s", sig, sys_siglist[sig]);
#else
    syslog(LOG_ERR, "exiting on signal %d", sig);
#endif
    chdir("/");
    signal(SIGIOT, SIG_DFL);
    signal(SIGILL, SIG_DFL);
    exit(1);
    /* dologout(-1); *//* NOTREACHED */
}

SIGNAL_TYPE lostconn(int sig)
{
#ifdef VERBOSE_ERROR_LOGING
    syslog(LOG_INFO, "lost connection to %s", remoteident);
#else
    if (debug)
	syslog(LOG_DEBUG, "lost connection to %s", remoteident);
#endif
    dologout(-1);
}

static char ttyline[20];

#ifdef MAPPING_CHDIR
/* Keep track of the path the user has chdir'd into and respond with
 * that to pwd commands.  This is to avoid having the absolue disk
 * path returned, which I want to avoid.
 */
char mapped_path[MAXPATHLEN] = "/";

#if !defined(HAVE_GETCWD)
char *mapping_getwd(char *path)
{
    strcpy(path, mapped_path);
    return path;
}
#endif /* !defined(HAVE_GETCWD) */ 

char *mapping_getcwd(char *path, size_t size)
{
    (void) strlcpy(path, mapped_path, size);
    return path;
}

/* Make these globals rather than local to mapping_chdir to avoid stack overflow */
char pathspace[MAXPATHLEN];
char old_mapped_path[MAXPATHLEN];

void do_elem(char *dir)
{
    /* . */
    if (dir[0] == '.' && dir[1] == '\0') {
	/* ignore it */
	return;
    }

    /* .. */
    if (dir[0] == '.' && dir[1] == '.' && dir[2] == '\0') {
	char *last;
	/* lop the last directory off the path */
	if ((last = strrchr(mapped_path, '/'))) {
	    /* If start of pathname leave the / */
	    if (last == mapped_path)
		last++;
	    *last = '\0';
	}
	return;
    }

    /* append the dir part with a leading / unless at root */
    if (!(mapped_path[0] == '/' && mapped_path[1] == '\0'))
	(void) strlcat(mapped_path, "/", sizeof(mapped_path));
    (void) strlcat(mapped_path, dir, sizeof(mapped_path));
}

int mapping_chdir(char *orig_path)
{
    int ret;
    char *sl, *path;

    (void) strlcpy(old_mapped_path, mapped_path, sizeof(old_mapped_path));
    (void) strlcpy(pathspace, orig_path, sizeof(pathspace));
    path = pathspace;

    /* / at start of path, set the start of the mapped_path to / */
    if (path[0] == '/') {
	mapped_path[0] = '/';
	mapped_path[1] = '\0';
	path++;
    }

    while ((sl = strchr(path, '/'))) {
	char *dir;
	dir = path;
	*sl = '\0';
	path = sl + 1;
	if (*dir)
	    do_elem(dir);
	if (*path == '\0')
	    break;
    }
    if (*path)
	do_elem(path);

    if ((ret = chdir(mapped_path)) < 0) {
	(void) strlcpy(mapped_path, old_mapped_path, sizeof(mapped_path));
    }

    return ret;
}
/* From now on use the mapping version */

#define chdir(d) mapping_chdir(d)
#define getwd(d) mapping_getwd(d)
#define getcwd(d,u) mapping_getcwd((d),(u))

#endif /* MAPPING_CHDIR */

/* Helper function for sgetpwnam(). */
char *sgetsave(char *s)
{
    char *new;

    new = (char *) malloc(strlen(s) + 1);

    if (new == NULL) {
	perror_reply(421, "Local resource failure: malloc");
	dologout(1);
	/* NOTREACHED */
    }
    (void) strcpy(new, s);
    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). */
struct passwd *sgetpwnam(char *name)
{
    static struct passwd save;
    register struct passwd *p;
#ifdef M_UNIX
    struct passwd *ret = (struct passwd *) NULL;
#endif
    char *sgetsave(char *s);
#ifdef KERBEROS
    register struct authorization *q;
#endif /* KERBEROS */

#if defined(SecureWare) || defined(HPUX_10_TRUSTED)
    struct pr_passwd *pr;
#endif

#ifdef KERBEROS
    init_krb();
    q = getauthuid(p->pw_uid);
    end_krb();
#endif /* KERBEROS */

#ifdef M_UNIX
#if defined(SecureWare) || defined(HPUX_10_TRUSTED)
    if ((pr = getprpwnam(name)) == NULL)
	goto DONE;
#endif /* SecureWare || HPUX_10_TRUSTED */
#ifdef OTHER_PASSWD
    if ((p = bero_getpwnam(name, _path_passwd)) == NULL)
#else
    if ((p = getpwnam(name)) == NULL)
#endif
	goto DONE;
#else /* M_UNIX */
#if defined(SecureWare) || defined(HPUX_10_TRUSTED)
    if ((pr = getprpwnam(name)) == NULL)
	return ((struct passwd *) pr);
#endif /* SecureWare || HPUX_10_TRUSTED */
#ifdef OTHER_PASSWD
    if ((p = bero_getpwnam(name, _path_passwd)) == NULL)
#else
    if ((p = getpwnam(name)) == NULL)
#endif
	return (p);
#endif /* M_UNIX */

    if (save.pw_name)
	free(save.pw_name);
    if (save.pw_gecos)
	free(save.pw_gecos);
    if (save.pw_dir)
	free(save.pw_dir);
    if (save.pw_shell)
	free(save.pw_shell);
    if (save.pw_passwd)
	free(save.pw_passwd);

    save = *p;

    save.pw_name = sgetsave(p->pw_name);

#ifdef KERBEROS
    save.pw_passwd = sgetsave(q->a_password);
#elif defined(SecureWare) || defined(HPUX_10_TRUSTED)
    if (pr->uflg.fg_encrypt && pr->ufld.fd_encrypt && *pr->ufld.fd_encrypt)
	save.pw_passwd = sgetsave(pr->ufld.fd_encrypt);
    else
	save.pw_passwd = sgetsave("");
#else
    save.pw_passwd = sgetsave(p->pw_passwd);
#endif
#ifdef SHADOW_PASSWORD
    if (p && (p->pw_passwd==NULL || strlen(p->pw_passwd)<8)) {
	struct spwd *spw;
#ifdef OTHER_PASSWD
	if ((spw = bero_getspnam(p->pw_name, _path_shadow)) != NULL) {
#else
	setspent();
	if ((spw = getspnam(p->pw_name)) != NULL) {
#endif
	    int expired = 0;
	    /*XXX Does this work on all Shadow Password Implementations? */
	    /* it is supposed to work on Solaris 2.x */
	    time_t now;
	    long today;

	    now = time((time_t *) 0);
	    today = now / (60 * 60 * 24);

	    if ((spw->sp_expire > 0) && (spw->sp_expire < today))
		expired++;
	    if ((spw->sp_max > 0) && (spw->sp_lstchg > 0) &&
		(spw->sp_lstchg + spw->sp_max < today))
		expired++;
	    free(save.pw_passwd);
	    save.pw_passwd = sgetsave(expired ? "" : spw->sp_pwdp);
	}
/* Don't overwrite the password if the shadow read fails, getpwnam() is NIS
   aware but getspnam() is not. */
/* Shadow passwords are optional on Linux.  --marekm */
#if !defined(LINUX) && !defined(UNIXWARE)
	else {
	    free(save.pw_passwd);
	    save.pw_passwd = sgetsave("");
	}
#endif
/* marekm's fix for linux proc file system shadow passwd exposure problem */
#ifndef OTHER_PASSWD
	endspent();
#endif
    }
#endif
    save.pw_gecos = sgetsave(p->pw_gecos);
    save.pw_dir = sgetsave(p->pw_dir);
    save.pw_shell = sgetsave(p->pw_shell);
#ifdef M_UNIX
    ret = &save;
  DONE:
    endpwent();
#endif
#if defined(SecureWare) || defined(HPUX_10_TRUSTED)
    endprpwent();
#endif
#ifdef M_UNIX
    return (ret);
#else
    return (&save);
#endif
}
#if defined(SKEY) && !defined(__NetBSD__)
/*
 * From Wietse Venema, Eindhoven University of Technology. 
 */
/* skey_challenge - additional password prompt stuff */

char *skey_challenge(char *name, struct passwd *pwd, int pwok)
{
    static char buf[128];
    char sbuf[40];
    struct skey skey;

    /* Display s/key challenge where appropriate. */

    if (pwd == NULL || skeychallenge(&skey, pwd->pw_name, sbuf))
	sprintf(buf, "Password required for %s.", name);
    else
	sprintf(buf, "%s %s for %s.", sbuf,
		pwok ? "allowed" : "required", name);
    return (buf);
}
#endif

int login_attempts;		/* number of failed login attempts */
int askpasswd;			/* had user command, ask for passwd */
#ifndef HELP_CRACKERS
int DenyLoginAfterPassword;
char DelayedMessageFile[MAXPATHLEN];
extern void pr_mesg(int msgcode, char *msgfile);
#endif

#if defined(VIRTUAL) && defined(CLOSED_VIRTUAL_SERVER)
static int defaultserver_allow(const char *username)
{
    struct aclmember *entry = NULL;
    int which;

    while (getaclentry("defaultserver", &entry))
	if (ARG0 && !strcasecmp(ARG0, "allow"))
	    for (which = 1; (which < MAXARGS) && ARG[which]; which++)
		if (!strcasecmp(username, ARG[which]) || !strcmp("*", ARG[which]))
		    return (1);
    return (0);
}

static int defaultserver_deny(const char *username)
{
    struct aclmember *entry = NULL;
    int which;

    while (getaclentry("defaultserver", &entry))
	if (ARG0 && !strcasecmp(ARG0, "deny"))
	    for (which = 1; (which < MAXARGS) && ARG[which]; which++)
		if (!strcasecmp(username, ARG[which]) || !strcmp("*", ARG[which]))
		    return (1);
    return (0);
}

static int defaultserver_private(void)
{
    struct aclmember *entry = NULL;

    while (getaclentry("defaultserver", &entry))
	if (ARG0 && !strcasecmp(ARG0, "private"))
	    return (1);
    return (0);
}
#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 the ftpusers file, and ftp account exists, set anonymous 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 ftpusers file to allow people such as root and
 * uucp to be avoided. */

/*
   char *getusershell();
 */
void user(char *name)
{
    char *cp;
    char *shell;
#ifdef	BSD_AUTH
    char *auth;
#endif
#if defined(USE_GSS)
    int gss_need_passwd = 1;
#endif

/* H* fix: if we're logged in at all, we can't log in again. */
    if (logged_in) {
#ifdef VERBOSE_ERROR_LOGING
	syslog(LOG_NOTICE, "FTP LOGIN REFUSED (already logged in as %s) FROM %s, %s",
	       pw->pw_name, remoteident, name);
#endif
	reply(530, "Already logged in.");
	return;
    }
#ifndef HELP_CRACKERS
    askpasswd = 1;
    DenyLoginAfterPassword = 0;
    DelayedMessageFile[0] = '\0';
#endif
#ifdef	BSD_AUTH
    if ((auth = strchr(name, ':')))
	*auth++ = 0;
#endif

#ifdef HOST_ACCESS		/* 19-Mar-93    BM              */
    if (!rhost_ok(name, remotehost, remoteaddr)) {
#ifndef HELP_CRACKERS
	DenyLoginAfterPassword = 1;
	syslog(LOG_NOTICE, "FTP LOGIN REFUSED (name in %s) FROM %s, %s",
	       _path_ftphosts, remoteident, name);
#else
	reply(530, "User %s access denied.", name);
	syslog(LOG_NOTICE,
	       "FTP LOGIN REFUSED (name in %s) FROM %s, %s",
	       _path_ftphosts, remoteident, name);
	return;
#endif
    }
#endif

    strncpy(the_user, name, MAXUSERNAMELEN - 1);

    anonymous = 0;
    guest = 0;

    if (!strcasecmp(name, "ftp") || !strcasecmp(name, "anonymous")) {
	struct aclmember *entry = NULL;
	int machineok = 1;
	char guestservername[MAXHOSTNAMELEN];
	guestservername[0] = '\0';

#ifdef NO_ANONYMOUS_ACCESS
	reply(530, "Anonymous FTP access denied.");
	syslog(LOG_NOTICE, "FTP LOGIN REFUSED (anonymous ftp not supported) FROM %s, %s",
	       remoteident, name);
	return;
#else
#if defined(VIRTUAL) && defined(CLOSED_VIRTUAL_SERVER)
	if (!virtual_mode && defaultserver_private()) {
#ifndef HELP_CRACKERS
	    DenyLoginAfterPassword = 1;
	    syslog(LOG_NOTICE, "FTP LOGIN REFUSED (anonymous ftp denied on default server) FROM %s, %s",
		   remoteident, name);
#else
	    reply(530, "User %s access denied.", name);
	    syslog(LOG_NOTICE,
		   "FTP LOGIN REFUSED (anonymous ftp denied on default server) FROM %s, %s",
		   remoteident, name);
	    return;
#endif
	}
#endif
	if (checkuser("ftp") || checkuser("anonymous")) {
#ifndef HELP_CRACKERS
	    DenyLoginAfterPassword = 1;
	    syslog(LOG_NOTICE, "FTP LOGIN REFUSED (ftp in %s) FROM %s, %s",
		   _path_ftpusers, remoteident, name);
#else
	    reply(530, "User %s access denied.", name);
	    syslog(LOG_NOTICE,
		   "FTP LOGIN REFUSED (ftp in %s) FROM %s, %s",
		   _path_ftpusers, remoteident, name);
	    return;
#endif

	    /*
	       ** Algorithm used:
	       ** - if no "guestserver" directive is present,
	       **     anonymous access is allowed, for backward compatibility.
	       ** - if a "guestserver" directive is present,
	       **     anonymous access is restricted to the machines listed,
	       **     usually the machine whose CNAME on the current domain
	       **     is "ftp"...
	       **
	       ** the format of the "guestserver" line is
	       ** guestserver [<machine1> [<machineN>]]
	       ** that is, "guestserver" will forbid anonymous access on all machines
	       ** while "guestserver ftp inf" will allow anonymous access on
	       ** the two machines whose CNAMES are "ftp.enst.fr" and "inf.enst.fr".
	       **
	       ** if anonymous access is denied on the current machine,
	       ** the user will be asked to use the first machine listed (if any)
	       ** on the "guestserver" line instead:
	       ** 530- Guest login not allowed on this machine,
	       **      connect to ftp.enst.fr instead.
	       **
	       ** -- <Nicolas.Pioch@enst.fr>
	     */
	}
	else if (getaclentry("guestserver", &entry)) {
	    char *tmphost;

	    /*
	       ** if a "guestserver" line is present,
	       ** default is not to allow guest logins
	     */
	    machineok = 0;

	    if (hostname[0]
		&& ((tmphost = wu_gethostbyname(hostname)))) {

		/*
		   ** hostname is the only first part of the FQDN
		   ** this may or may not correspond to the h_name value
		   ** (machines with more than one IP#, CNAMEs...)
		   ** -> need to fix that, calling gethostbyname on hostname
		   **
		   ** WARNING!
		   ** for SunOS 4.x, you need to have a working resolver in the libc
		   ** for CNAMES to work properly.
		   ** If you don't, add "-lresolv" to the libraries before compiling!
		 */
		char dns_localhost[MAXHOSTNAMELEN];
		int machinecount;

		strncpy(dns_localhost, tmphost, sizeof(dns_localhost));
		dns_localhost[sizeof(dns_localhost) - 1] = '\0';

		for (machinecount = 0;
		     (machinecount < MAXARGS) && entry->arg[machinecount];
		     machinecount++) {

		    if ((tmphost = wu_gethostbyname(entry->arg[machinecount]))) {
			/*
			   ** remember the name of the first machine for redirection
			 */

			if (!machinecount) {
			    strncpy(guestservername, entry->arg[machinecount],
				    sizeof(guestservername));
			    guestservername[sizeof(guestservername) - 1] = '\0';
			}

			if (!strcasecmp(tmphost, dns_localhost)) {
			    machineok++;
			    break;
			}
		    }
		}
	    }
	}
	if (!machineok) {
	    if (guestservername[0])
		reply(530,
		      "Guest login not allowed on this machine, connect to %s instead.",
		      guestservername);
	    else
		reply(530,
		      "Guest login not allowed on this machine.");
	    syslog(LOG_NOTICE,
	    "FTP LOGIN REFUSED (localhost not in guestservers) FROM %s, %s",
		   remoteident, name);
	    /* End of the big patch -- Nap */

	    dologout(0);
	}
	else if ((pw = sgetpwnam("ftp")) != NULL) {
	    anonymous = 1;	/* for the access_ok call */
	    if (access_ok(530) < 1) {
#ifndef HELP_CRACKERS
		DenyLoginAfterPassword = 1;
		syslog(LOG_NOTICE, "FTP LOGIN REFUSED (access denied) FROM %s, %s",
		       remoteident, name);
		reply(331, "Guest login ok, send your complete e-mail address as password.");
#else
		reply(530, "User %s access denied.", name);
		syslog(LOG_NOTICE,
		       "FTP LOGIN REFUSED (access denied) FROM %s, %s",
		       remoteident, name);
		dologout(0);
#endif
	    }
	    else {
		askpasswd = 1;
/* H* fix: obey use_accessfile a little better.  This way, things set on the
   command line [like xferlog stuff] don't get stupidly overridden.
   XXX: all these checks maybe should be in acl.c and access.c */
		if (use_accessfile)
		    acl_setfunctions();
		reply(331, "Guest login ok, send your complete e-mail address as password.");
	    }
	}
	else {
#ifndef HELP_CRACKERS
	    DenyLoginAfterPassword = 1;
	    reply(331, "Guest login ok, send your complete e-mail address as password.");
	    syslog(LOG_NOTICE, "FTP LOGIN REFUSED (ftp not in /etc/passwd) FROM %s, %s",
		   remoteident, name);
#else
	    reply(530, "User %s unknown.", name);
	    syslog(LOG_NOTICE,
		   "FTP LOGIN REFUSED (ftp not in /etc/passwd) FROM %s, %s",
		   remoteident, name);
#endif
#ifdef SOLARIS_BSM_AUDIT
	    audit_ftpd_no_anon();
#endif
	}
	return;
#endif
    }
#ifdef ANON_ONLY
/* H* fix: define the above to completely DISABLE logins by real users,
   despite ftpusers, shells, or any of that rot.  You can always hang your
   "real" server off some other port, and access-control it. */

    else {			/* "ftp" or "anon" -- MARK your conditionals, okay?! */
#ifndef HELP_CRACKERS
	DenyLoginAfterPassword = 1;
	syslog(LOG_NOTICE, "FTP LOGIN REFUSED (not anonymous) FROM %s, %s",
	       remoteident, name);
	reply(331, "Password required for %s.", name);
#else
	reply(530, "User %s unknown.", name);
	syslog(LOG_NOTICE,
	       "FTP LOGIN REFUSED (not anonymous) FROM %s, %s",
	       remoteident, name);
#endif
	return;
    }
/* fall here if username okay in any case */
#endif /* ANON_ONLY */

#if defined(VIRTUAL) && defined(CLOSED_VIRTUAL_SERVER)
    if (!virtual_mode && defaultserver_deny(name) && !defaultserver_allow(name)) {
#ifndef HELP_CRACKERS
	DenyLoginAfterPassword = 1;
	syslog(LOG_NOTICE, "FTP LOGIN REFUSED (ftp denied on default server) FROM %s, %s",
	       remoteident, name);
#else
	reply(530, "User %s access denied.", name);
	syslog(LOG_NOTICE,
	     "FTP LOGIN REFUSED (ftp denied on default server) FROM %s, %s",
	       remoteident, name);
	return;
#endif
    }
#endif

#if defined(USE_GSS)
    if (gss_info.must_gss_auth &&
	(!IS_GSSAUTH(cur_auth_type) ||
	!(gss_info.authstate & GSS_ADAT_DONE))) {
	reply(530, "Must perform authentication before identifying USER.");
	return;
    }
#endif /* USE_GSS */

    if ((pw = sgetpwnam(name)) != NULL) {
	if ((denieduid(pw->pw_uid) && !alloweduid(pw->pw_uid))
	    || (deniedgid(pw->pw_gid) && !allowedgid(pw->pw_gid))) {
#ifndef HELP_CRACKERS
	    DenyLoginAfterPassword = 1;
	    syslog(LOG_NOTICE, "FTP LOGIN REFUSED (username in denied-uid) FROM %s, %s",
		   remoteident, name);
	    reply(331, "Password required for %s.", name);
#else
	    reply(530, "User %s access denied.", name);
	    syslog(LOG_NOTICE,
		   "FTP LOGIN REFUSED (username in denied-uid) FROM %s, %s",
		   remoteident, name);
#endif
	    return;
	}
#if defined(USE_GSS)
	if (IS_GSSAUTH(cur_auth_type) &&
	    (gss_info.authstate & GSS_ADAT_DONE)) {
	    char buf[BUFSIZ];

	    if (gss_user(pw))
		gss_info.authstate |= GSS_USER_DONE;

	    if (gss_info.must_gss_auth &&
		!GSSUSERAUTH_OK(gss_info)) {
		reply(530, "User %s access denied", name);
		if (logging)
		    syslog(LOG_NOTICE, "FTP GSSAPI LOGIN REFUSED FROM %s, %s",
			remoteident, name);
		pw = NULL;
		return;
	    }
	    /*
	     * If GSSAPI user auth failed, or it succeeded but creds were
	     * not forwarded as required, prompt for password.
	     */
	    gss_need_passwd = !GSSUSERAUTH_OK(gss_info) ||
		(GSSUSERAUTH_OK(gss_info) &&
		(gss_info.want_creds && !gss_info.have_creds));
	    if (gss_need_passwd) {
		snprintf(buf, sizeof(buf),
		    "GSSAPI user %s is authorized as %s password required",
		    gss_info.display_name, name);
		reply(331, "%s", buf);
		askpasswd = 1;
		syslog(LOG_DEBUG, "%s", buf);
		return;
	    }
	}
#endif /* defined(USE_GSS) */

#if !defined(USE_PAM) || (defined(USE_PAM) && defined(OTHER_PASSWD)) || defined(SOLARIS_2)	/* PAM should be doing these checks, not ftpd */
#if defined(USE_PAM) && !defined(SOLARIS_2)
	if(!use_pam) {
#endif
	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 || checkuser(name)) {
#ifndef HELP_CRACKERS
	    DenyLoginAfterPassword = 1;
	    if (cp == NULL)
		syslog(LOG_NOTICE, "FTP LOGIN REFUSED (shell not in /etc/shells) FROM %s, %s", remoteident, name);
	    else
		syslog(LOG_NOTICE, "FTP LOGIN REFUSED (username in %s) FROM %s, %s", _path_ftpusers, remoteident, name);
	    reply(331, "Password required for %s.", name);
#else
	    reply(530, "User %s access denied.", name);
	    if (cp == NULL)
		syslog(LOG_NOTICE, "FTP LOGIN REFUSED (shell not in /etc/shells) FROM %s, %s", remoteident, name);
	    else
		syslog(LOG_NOTICE, "FTP LOGIN REFUSED (username in %s) FROM %s, %s", _path_ftpusers, remoteident, name);
#endif /* HELP_CRACKERS */
	    pw = (struct passwd *) NULL;
	    return;
	}
#if defined(USE_PAM) && !defined(SOLARIS_2)
	} /* if(!use_pam) */
#endif
#endif /* !USE_PAM || (USE_PAM && OTHER_PASSWD) || SOLARIS_2 */
	/* if user is a member of any of the guestgroups, cause a chroot() */
	/* after they log in successfully                                  */
	if (use_accessfile) {	/* see above.  _H */
	    guest = acl_guestgroup(pw);
	    if (guest && acl_realgroup(pw))
		guest = 0;
	}
    }
    if (access_ok(530) < 1) {
#ifndef HELP_CRACKERS
	DenyLoginAfterPassword = 1;
	syslog(LOG_NOTICE, "FTP LOGIN REFUSED (access denied) FROM %s, %s",
	       remoteident, name);
	reply(331, "Password required for %s.", name);
#else
	reply(530, "User %s access denied.", name);
	syslog(LOG_NOTICE, "FTP LOGIN REFUSED (access denied) FROM %s, %s",
	       remoteident, name);
#endif
	return;
    }
    else if (use_accessfile)	/* see above.  _H */
	acl_setfunctions();

#ifdef	BSD_AUTH
    if ((cp = start_auth(auth, name, pw)) != NULL) {
	char *s;

	for (;;) {
	    s = strsep(&cp, "\n");
	    if (cp == NULL || *cp == '\0')
		break;
	    lreply(331, "%s", s);
	}
	reply(331, "%s", s);
    }
    else {
#endif /* BSD_AUTH */
#ifdef SKEY
#ifndef __NetBSD__
#ifdef SKEY_NAME
	/* this is the old way, but freebsd uses it */
	pwok = skeyaccess(name, NULL, remotehost, remoteaddr);
#else
	/* this is the new way */
	pwok = skeyaccess(pw, NULL, remotehost, remoteaddr);
#endif /* SKEY_NAME */
	reply(331, "%s", skey_challenge(name, pw, pwok));
#else
	if (skey_haskey(name) == 0) {
	    char *myskey;

	    myskey = skey_keyinfo(name);
	    reply(331, "Password [%s] required for %s.",
		  myskey ? myskey : "error getting challenge", name);
	}
	else
	    reply(331, "Password required for %s.", name);
#endif /* __NetBSD__ */
#else
#ifdef OPIE
	{
	    char prompt[OPIE_CHALLENGE_MAX + 1];
	    opiechallenge(&opiestate, name, prompt);

	    if (askpasswd == -1) {
		syslog(LOG_WARNING, "Invalid FTP user name %s attempted from %s", name, remotehost);
		pwok = 0;
	    }
	    else
		pwok = af_pwok && opiealways(pw->pw_dir);
	    reply(331, "Response to %s %s for %s.",
		  prompt, pwok ? "requested" : "required", name);
	}
#else /* !SKEY */

#if defined(USE_GSS)
	if (GSSUSERAUTH_OK(gss_info) && !gss_need_passwd) {
	    /*
	     * We got this far, we are allowing the GSSAPI authentication
	     * to succeed without further passwd prompting.  Jump
	     * to "pass" processing.
	     */
	    askpasswd = 0;
	    logged_in = 1;
	    pass("");
	    return;
	}
#endif /* defined(USE_GSS) */
	reply(331, "Password required for %s.", name);
#endif /* OPIE */
#endif /* SKEY */
#ifdef	BSD_AUTH
    }
#endif /* BSD_AUTH */

    askpasswd = 1;
    /* Delay before reading passwd after first failed attempt to slow down
     * passwd-guessing programs. */
    if (login_attempts) {
	enable_signaling();	/* we can allow signals once again: kinch */
	sleep((unsigned) login_attempts);
    }
    return;
}

/* Check if a user is in the ftpusers file */
int checkuser(char *name)
{
    register FILE *fd;
    register char *p;
    char line[BUFSIZ];

#ifdef SOLARIS_ETC_FTPUSERS
    static int etc_ftpusers = 0;

    if (etc_ftpusers) {
	strcpy(_path_ftpusers, _PATH_FTPUSERS);
	etc_ftpusers = 0;
    }
retry:
#endif
    if ((fd = fopen(_path_ftpusers, "r")) != NULL) {
	while (fgets(line, sizeof(line), fd) != NULL)
	    if ((p = strchr(line, '\n')) != NULL) {
		*p = '\0';
		if (line[0] == '#')
		    continue;
		if (strcasecmp(line, name) == 0) {
		    (void) fclose(fd);
#ifdef SOLARIS_BSM_AUDIT
		    audit_ftpd_excluded(name);
#endif
#ifdef SOLARIS_ETC_FTPUSERS
		    if (etc_ftpusers)
			syslog(LOG_NOTICE, "%s is deprecated, use %s instead", _path_ftpusers, _PATH_FTPUSERS);
#endif
		    return (1);
		}
	    }
	(void) fclose(fd);
    }
#ifdef SOLARIS_ETC_FTPUSERS
    if (!etc_ftpusers && (strcmp(_path_ftpusers, _PATH_FTPUSERS) == 0)) {
	strcpy(_path_ftpusers, "/etc/ftpusers");
	etc_ftpusers = 1;
	goto retry;
    }
#endif
    return (0);
}

int uid_match(char *keyword, uid_t uid)
{
    struct aclmember *entry = NULL;
    int which;
    char *ptr;
    struct passwd *pw;

    /*
     * keyword <uid-range> [<uid-range> ...]
     *
     * uid-range may be a username or begin with '%' and be treated as numeric:
     *   %<uid>       A single numeric UID
     *   %<uid>+      All UIDs greater or equal to UID
     *   %<uid>-      All UIDs greater or equal to UID
     *   %-<uid>      All UIDs less or equal to UID
     *   %<uid>-<uid> All UIDs between the two (inclusive)
     *   *            All UIDs
     */
    while (getaclentry(keyword, &entry)) {
	for (which = 0; (which < MAXARGS) && ARG[which]; which++) {
	    if (!strcmp(ARG[which], "*"))
		return (1);
	    if (ARG[which][0] == '%') {
		if ((ptr = strchr(ARG[which] + 1, '-')) == NULL) {
		    if ((ptr = strchr(ARG[which] + 1, '+')) == NULL) {
			if (uid == strtoul(ARG[which] + 1, NULL, 0))
			    return (1);
		    }
		    else {
			*ptr++ = '\0';
			if ((ARG[which][1] == '\0')
			    || (uid >= strtoul(ARG[which] + 1, NULL, 0))) {
			    *--ptr = '+';
			    return (1);
			}
			*--ptr = '+';
		    }
		}
		else {
		    *ptr++ = '\0';
		    if (((ARG[which][1] == '\0')
			 || (uid >= strtoul(ARG[which] + 1, NULL, 0)))
			&& ((*ptr == '\0')
			    || (uid <= strtoul(ptr, NULL, 0)))) {
			*--ptr = '-';
			return (1);
		    }
		    *--ptr = '-';
		}
	    }
	    else {
#ifdef OTHER_PASSWD
		pw = bero_getpwnam(ARG[which], _path_passwd);
#else
		pw = getpwnam(ARG[which]);
#endif
		if (pw && (uid == pw->pw_uid))
		    return (1);
	    }
	}
    }
    return (0);
}

int gid_match(char *keyword, gid_t gid, char *username)
{
    struct aclmember *entry = NULL;
    int which;
    char *ptr;
    struct group *grp;
    char **member;

    /*
     * keyword <gid-range> [<gid-range> ...]
     *
     * gid-range may be a groupname or begin with '%' and be treated as numeric:
     *   %<gid>       A single GID
     *   %<gid>+      All GIDs greater or equal to GID
     *   %<gid>-      All GIDs greater or equal to GID
     *   %-<gid>      All GIDs less or equal to GID
     *   %<gid>-<gid> All GIDs between the two (inclusive)
     *   *            All GIDs
     */
    while (getaclentry(keyword, &entry)) {
	for (which = 0; (which < MAXARGS) && ARG[which]; which++) {
	    if (!strcmp(ARG[which], "*"))
		return (1);
	    if (ARG[which][0] == '%') {
		if ((ptr = strchr(ARG[which] + 1, '-')) == NULL) {
		    if ((ptr = strchr(ARG[which] + 1, '+')) == NULL) {
			if (gid == strtoul(ARG[which] + 1, NULL, 0))
			    return (1);
		    }
		    else {
			*ptr++ = '\0';
			if ((ARG[which][1] == '\0')
			    || (gid >= strtoul(ARG[which] + 1, NULL, 0))) {
			    *--ptr = '+';
			    return (1);
			}
			*--ptr = '+';
		    }
		}
		else {
		    *ptr++ = '\0';
		    if (((ARG[which][1] == '\0')
			 || (gid >= strtoul(ARG[which] + 1, NULL, 0)))
			&& ((*ptr == '\0')
			    || (gid <= strtoul(ptr, NULL, 0)))) {
			*--ptr = '-';
			return (1);
		    }
		    *--ptr = '-';
		}
	    }
	    else {
		if ((grp = getgrnam(ARG[which]))) {
		    if (gid == grp->gr_gid)
			return (1);
		    if (username) {
			for (member = grp->gr_mem; *member; member++)
			    if (!strcasecmp(*member, username))
				return (1);
		    }
		}
	    }
	}
    }
    return (0);
}

int denieduid(uid_t uid)
{
    return uid_match("deny-uid", uid);
}

int alloweduid(uid_t uid)
{
    return uid_match("allow-uid", uid);
}

int deniedgid(gid_t gid)
{
    return gid_match("deny-gid", gid, NULL);
}

int allowedgid(gid_t gid)
{
    return gid_match("allow-gid", gid, NULL);
}

/* Terminate login as previous user, if any, resetting state; used when USER
 * command is given or login fails. */

void end_login(void)
{
    delay_signaling();		/* we can't allow any signals while euid==0: kinch */
    (void) seteuid((uid_t) 0);
    if (logged_in) {
	if (wtmp_logging)
	    wu_logwtmp(ttyline, pw->pw_name, remotehost, 0);
#ifdef USE_PAM
	if (!anonymous && pamh) {
	    (void) pam_close_session(pamh, 0);
	    (void) pam_end(pamh, PAM_SUCCESS); 
	    pamh = (pam_handle_t *)0;
	}
#endif
    }
    pw = NULL;
#ifdef AFS_AUTH
    ktc_ForgetAllTokens();
#endif
    logged_in = 0;
    anonymous = 0;
    guest = 0;
}

int validate_eaddr(char *eaddr)
{
    int i, host, state;

    for (i = host = state = 0; eaddr[i] != '\0'; i++) {
	switch (eaddr[i]) {
	case '.':
	    if (!host)
		return 0;
	    if (state == 2)
		state = 3;
	    host = 0;
	    break;
	case '@':
	    if (!host || state > 1 || !strncasecmp("ftp", eaddr + i - host, host))
		return 0;
	    state = 2;
	    host = 0;
	    break;
	case '!':
	case '%':
	    if (!host || state > 1)
		return 0;
	    state = 1;
	    host = 0;
	    break;
	case '-':
	    break;
	default:
	    host++;
	}
    }
    if (((state == 3) && host > 1) || ((state == 1) && host > 1))
	return 1;
    else
	return 0;
}


#if defined(VIRTUAL) && defined(CLOSED_VIRTUAL_SERVER)
static int AllowVirtualUser(const char *username)
{
    struct aclmember *entry = NULL;
    int which;

    while (getaclentry("virtual", &entry))
	if (ARG0 && hostmatch(ARG0, virtual_address, virtual_hostname)
	    && ARG1 && !strcasecmp(ARG1, "allow"))
	    for (which = 2; (which < MAXARGS) && ARG[which]; which++)
		if (!strcasecmp(username, ARG[which]) || !strcmp("*", ARG[which]))
		    return (1);
    return (0);
}

static int DenyVirtualUser(const char *username)
{
    struct aclmember *entry = NULL;
    int which;

    while (getaclentry("virtual", &entry))
	if (ARG0 && hostmatch(ARG0, virtual_address, virtual_hostname)
	    && ARG1 && !strcasecmp(ARG1, "deny"))
	    for (which = 2; (which < MAXARGS) && ARG[which]; which++)
		if (!strcasecmp(username, ARG[which]) || !strcmp("*", ARG[which]))
		    return (1);
    return (0);
}

static int DenyVirtualAnonymous(void)
{
    struct aclmember *entry = NULL;

    while (getaclentry("virtual", &entry))
	if (ARG0 && hostmatch(ARG0, virtual_address, virtual_hostname)
	    && ARG1 && !strcasecmp(ARG1, "private"))
	    return (1);
    return (0);
}
#endif

void pass(char *passwd)
{

#if !defined(USE_PAM) || (defined(USE_PAM) && defined(OTHER_PASSWD))
    char *xpasswd, *salt;
#endif

    int passwarn = 0;
    int rval = 1;
    int success_code = 230;
    int cos;

#ifdef SECUREOSF
    struct pr_passwd *pr;
    int crypt_alg = 0;
#endif

#ifdef BSD_AUTH
    extern int ext_auth;
    extern char *check_auth();
#endif

#ifdef ULTRIX_AUTH
    int numfails;
#endif /* ULTRIX_AUTH */

#ifdef HAS_PW_EXPIRE
    int set_expired = FALSE;
#endif 

#ifdef AFS_AUTH
    char *reason;
#endif /* AFS_AUTH */

#ifdef DCE_AUTH
    sec_passwd_rec_t pwr;
    sec_login_handle_t lhdl;
    boolean32 rstpwd;
    sec_login_auth_src_t asrc;
    error_status_t status;
#endif /* DCE_AUTH */

#if defined(USE_GSS)
    /*
     * LOGIC:
     *   If [ the user presented GSSAPI creds and was authorized ]
     *      jump down past the password validation code.
     */
    if (GSSUSERAUTH_OK(gss_info) && logged_in) {
	/*
	 * We could reply(202, "PASS command superfluous.") here, but
	 * allow this for compat with some clients.
	 */
	success_code = 232;
	goto pwd_validation_done;
    }
#endif /* defined(USE_GSS) */

    if (logged_in || askpasswd == 0) {
#ifdef VERBOSE_ERROR_LOGING
	syslog(LOG_NOTICE, "FTP LOGIN REFUSED (PASS before USER) FROM %s",
	       remoteident);
#endif
	reply(503, "Login with USER first.");
	return;
    }
    askpasswd = 0;

    /* Disable lreply() if the first character of the password is '-' since
     * some hosts don't understand continuation messages and hang... */

    if (*passwd == '-')
	dolreplies = 0;
    else
	dolreplies = 1;
/* ******** REGULAR/GUEST USER PASSWORD PROCESSING ********** */
    if (!anonymous) {		/* "ftp" is only account allowed no password */
#ifndef HELP_CRACKERS
	if (DenyLoginAfterPassword) {
	    pr_mesg(530, DelayedMessageFile);
	    reply(530, "Login incorrect.");
#ifdef SOLARIS_BSM_AUDIT
	    audit_ftpd_failure(the_user);
#endif
	    acl_remove();
	    pw = NULL;
	    if (++login_attempts >= lgi_failure_threshold) {
		syslog(LOG_NOTICE, "repeated login failures from %s",
		       remoteident);
		exit(0);
	    }
	    return;
	}
#endif
	if (*passwd == '-')
	    passwd++;
#ifdef USE_PAM
#ifdef OTHER_PASSWD
	if (use_pam
#if defined(USE_GSS)
	    && !GSSUSERAUTH_OK(gss_info)
#endif
	) {
#endif
	/* PAM authentication
	 * If PAM authenticates a user we know nothing about on the local
	 * system, use the generic guest account credentials. We should make
	 * this somehow a configurable item somewhere; later more on that.
	 *
	 * For now assume the guest (not anonymous) identity, so the site
	 * admins can still differentiate between the truw anonymous user and
	 * a little bit more special ones. Otherwise he wouldn't go the extra
	 * mile to have a different user database, right?
	 *              --gaftonc */
	if (pam_check_pass(the_user, passwd)) {
	    rval = 0;
	    if (pw == NULL) {
		/* assume guest account identity */
		pw = sgetpwnam("ftp");
		anonymous = 0;
		guest = 1;
		/* even go as far as... */
		if (pw != NULL && pw->pw_name != NULL) {
		    free(pw->pw_name);
		    pw->pw_name = sgetsave(the_user);
		}
	    }
	}
#ifdef OTHER_PASSWD
	} else {
#endif
#endif /* USE_PAM */
#if !defined(USE_PAM) || (defined(USE_PAM) && defined(OTHER_PASSWD))
#ifdef BSD_AUTH
	if (ext_auth) {
	    if ((salt = check_auth(the_user, passwd))) {
		reply(530, "%s", salt);
#ifdef LOG_FAILED		/* 27-Apr-93      EHK/BM          */
		/*
		 * To avoid logging passwords mistakenly entered as
		 * usernames, only log the names of users which exist.
		 */
		syslog(LOG_INFO, "failed login from %s, %s", remoteident,
		       (pw == NULL) ? "[unknown]" : the_user);
#endif /* LOG_FAILED */
		acl_remove();
		pw = NULL;
		if (++login_attempts >= lgi_failure_threshold) {
		    syslog(LOG_NOTICE, "repeated login failures from %s",
			   remoteident);
		    exit(0);
		}
		return;
	    }
	}
	else {
#endif /* BSD_AUTH */
	    *guestpw = '\0';
	    if (pw == NULL)
		salt = "xx";
	    else
#ifndef OPIE
		salt = pw->pw_passwd;
#ifdef SECUREOSF
	    if ((pr = getprpwnam(pw->pw_name)) != NULL) {
		if (pr->uflg.fg_newcrypt)
		    crypt_alg = pr->ufld.fd_newcrypt;
		else if (pr->sflg.fg_newcrypt)
		    crypt_alg = pr->sfld.fd_newcrypt;
		else
		    crypt_alg = 0;
	    }
	    else
		crypt_alg = 0;

	    xpasswd = dispcrypt(passwd, salt, crypt_alg);
#elif defined(SecureWare) || defined(HPUX_10_TRUSTED)
	    xpasswd = bigcrypt(passwd, salt);
#elif defined(KERBEROS)
	    xpasswd = crypt16(passwd, salt);
#elif defined(SKEY)
#ifndef __NetBSD__
	    xpasswd = skey_crypt(passwd, salt, pw, pwok);
	    pwok = 0;
#else
	    if ((pw != NULL) && (pw->pw_name != NULL) && skey_haskey(pw->pw_name) == 0 &&
		skey_passcheck(pw->pw_name, passwd) != -1)
		xpasswd = pw->pw_passwd;
	    else
		xpasswd = crypt(passwd, salt);
#endif
#else /* !SKEY */
	    xpasswd = crypt(passwd, salt);
#endif /* SKEY */
#else /* OPIE */
	    if (!opieverify(&opiestate, passwd))
		rval = 0;
	    xpasswd = crypt(passwd, pw->pw_passwd);
#endif /* OPIE */
#ifdef ULTRIX_AUTH
	    if ((numfails = ultrix_check_pass(passwd, xpasswd)) >= 0) {
#else
	    if (pw != NULL) {
#ifdef AFS_AUTH
		if (strcmp(pw->pw_passwd, "X") == 0)
		    if (ka_UserAuthenticateGeneral(KA_USERAUTH_VERSION | KA_USERAUTH_DOSETPAG, pw->pw_name, "", 0, passwd, 0, 0, 0, &reason) == 0)
			rval = 0;
		    else
			printf("230-AFS: %s", reason);
		else
#endif /* AFS_AUTH */
		    /* The strcmp does not catch null passwords! */
#ifdef HAS_PW_EXPIRE
		    if(pw->pw_expire != NULL) {
			if(pw->pw_expire && time(NULL) >= pw->pw_expire) {
			    set_expired = TRUE;
			} 
		    }
#endif
			    
		    if (*pw->pw_passwd != '\0' &&
#ifdef HAS_PW_EXPIRE
			!set_expired &&
#endif
			strcmp(xpasswd, pw->pw_passwd) == 0) {
#endif
		    rval = 0;
		}
#ifdef DCE_AUTH
#ifndef ALWAYS_TRY_DCE
		else
#endif /* ALWAYS_TRY_DCE */
		{
		    sec_login_setup_identity((unsigned_char_p_t) pw->pw_name, sec_login_no_flags, &lhdl, &status);
		    if (status == error_status_ok) {
			printf("230-sec_login_setup_identity OK\n");
			pwr.key.tagged_union.plain = (idl_char *) passwd;
			pwr.key.key_type = sec_passwd_plain;
			pwr.pepper = 0;
			pwr.version_number = sec_passwd_c_version_none;
			/* validate password with login context */
			sec_login_valid_and_cert_ident(lhdl, &pwr, &rstpwd, &asrc, &status);
			if (!rstpwd && (asrc == sec_login_auth_src_network) && (status == error_status_ok)) {
			    printf("230-sec_login_valid_and_cert_ident OK\n");
			    sec_login_set_context(lhdl, &status);
			    printf("230-sec_login_set_context finished\n");
			    if (status != error_status_ok) {
				int pstatus;
				dce_error_string_t s;
				printf("230-Error status: %d:\n", status);
				dce_error_inq_text(status, s, &pstatus);
				printf("230-%s\n", s);
				fflush(stderr);
				sec_login_purge_context(lhdl, &status);
			    }
			    else {
				/*sec_login_get_pwent(lhdl, &pw, &status); */
				rval = 0;
			    }
			}
		    }
		}
#endif /* DCE_AUTH */
	    }
#ifdef USE_PAM
	    }
#endif
#endif /* !USE_PAM  || (USE_PAM && OTHER_PASSWD) */
	    if (rval) {
		reply(530, "Login incorrect.");

#ifdef LOG_FAILED		/* 27-Apr-93    EHK/BM             */
/* H* add-on: yell about attempts to use the trojan.  This may alarm you
   if you're "stringsing" the binary and you see "NULL" pop out in just
   about the same place as it would have in 2.2c! */
		if (!strcasecmp(passwd, "NULL"))
		    syslog(LOG_NOTICE, "REFUSED \"NULL\" from %s, %s",
			   remoteident, the_user);
		else {
		    /*
		     * To avoid logging passwords mistakenly entered as
		     * usernames, only log the names of users which exist.
		     */
		    syslog(LOG_INFO, "failed login from %s, %s", remoteident,
			   (pw == NULL) ? "[unknown]" : the_user);
		}
#endif
#ifdef SOLARIS_BSM_AUDIT
		audit_ftpd_failure(the_user);
#endif
		acl_remove();

		pw = NULL;
		if (++login_attempts >= lgi_failure_threshold) {
		    syslog(LOG_NOTICE, "repeated login failures from %s",
			   remoteident);
		    exit(0);
		}
		return;
	    }
#ifdef	BSD_AUTH
	}
#endif
/* ANONYMOUS USER PROCESSING STARTS HERE */
    }
    else {
	char *pwin, *pwout = guestpw;
	struct aclmember *entry = NULL;
	int valid;
	int enforce = 0;

	if (getaclentry("passwd-check", &entry) &&
	    ARG0 && strcasecmp(ARG0, "none")) {

	    if (!strcasecmp(ARG0, "rfc822"))
		valid = validate_eaddr(passwd);
	    else if (!strcasecmp(ARG0, "trivial"))
		valid = (strchr(passwd, '@') == NULL) ? 0 : 1;
	    else
		valid = 1;
	    if (ARG1 && !strcasecmp(ARG1, "enforce"))
		enforce = 1;
	    /* Block off "default" responses like mozilla@ and IE30User@
	     * (at the administrator's discretion).  --AC
	     */
	    entry = NULL;
	    while (getaclentry("deny-email", &entry)) {
		if (ARG0
		    && ((strcasecmp(passwd, ARG0) == 0)
			|| regexmatch(passwd, ARG0)
			|| ((*passwd == '-')
			    && ((strcasecmp(passwd + 1, ARG0) == 0)
				|| regexmatch(passwd + 1, ARG0))))) {
		    valid = 0;
		    break;
		}
	    }
	    if (!valid && enforce) {
		lreply(530, "The response '%s' is not valid", passwd);
		lreply(530, "Please use your e-mail address as your password");
		lreply(530, "   for example: %s@%s%s",
		       authenticated ? authuser : "joe", remotehost,
		       strchr(remotehost, '.') ? "" : ".network");
		reply(530, "Login incorrect.");
#ifdef VERBOSE_ERROR_LOGING
		syslog(LOG_NOTICE, "FTP ACCESS REFUSED (anonymous password not rfc822) from %s",
		       remoteident);
#endif
#ifdef SOLARIS_BSM_AUDIT
		audit_ftpd_bad_pw(the_user);
#endif
		acl_remove();
		if (++login_attempts >= lgi_failure_threshold) {
		    syslog(LOG_NOTICE, "repeated login failures from %s",
			   remoteident);
		    exit(0);
		}
		return;
	    }
	    else if (!valid)
		passwarn = 1;
	}
	if (!*passwd) {
	    strcpy(guestpw, "[none_given]");
	}
	else {
	    int cnt = sizeof(guestpw) - 2;

	    for (pwin = passwd; *pwin && cnt--; pwin++)
		if (!isgraph(*pwin))
		    *pwout++ = '_';
		else
		    *pwout++ = *pwin;
	}
#ifndef HELP_CRACKERS
	if (DenyLoginAfterPassword) {
	    pr_mesg(530, DelayedMessageFile);
	    reply(530, "Login incorrect.");
#ifdef SOLARIS_BSM_AUDIT
	    audit_ftpd_failure(the_user);
#endif
	    acl_remove();
	    pw = NULL;
	    if (++login_attempts >= lgi_failure_threshold) {
		syslog(LOG_NOTICE, "repeated login failures from %s",
		       remoteident);
		exit(0);
	    }
	    return;
	}
#endif
    }

#if defined(USE_GSS)
pwd_validation_done:
#endif /* USE_GSS */
    /* if logging is enabled, open logfile before chroot or set group ID */
    if ((log_outbound_xfers || log_incoming_xfers) && (syslogmsg != 1)) {
	mode_t oldmask;
	oldmask = umask(0);
	xferlog = open(logfile, O_WRONLY | O_APPEND | O_CREAT, 0640);
	(void) umask(oldmask);
	if (xferlog < 0) {
	    syslog(LOG_ERR, "cannot open logfile %s: %s", logfile,
		   strerror(errno));
	    xferlog = 0;
	}
    }

#ifdef DEBUG
/* I had a lot of trouble getting xferlog working, because of two factors:
   acl_setfunctions making stupid assumptions, and sprintf LOSING.  _H */
/* 
 * Actually, sprintf was not losing, but the rules changed... next release
 * this will be fixed the correct way, but right now, it works well enough
 * -- sob 
 */
    syslog(LOG_INFO, "-i %d,-o %d,xferlog %s: %d",
	   log_incoming_xfers, log_outbound_xfers, logfile, xferlog);
#endif
    enable_signaling();		/* we can allow signals once again: kinch */
    /* if autogroup command applies to user's class change pw->pw_gid */
    if (anonymous && use_accessfile) {	/* see above.  _H */
	(void) acl_autogroup(pw);
	guest = acl_guestgroup(pw);	/* the new group may be a guest */
	if (guest && acl_realgroup(pw))
	    guest = 0;
	anonymous = !guest;
    }
/* END AUTHENTICATION */

/* SET GROUP ID STARTS HERE */
#ifndef AIX
    (void) setegid((gid_t) pw->pw_gid);
#else
    (void) setgid((gid_t) pw->pw_gid);
#endif
    (void) initgroups(pw->pw_name, pw->pw_gid);
#ifdef DEBUG
    syslog(LOG_DEBUG, "initgroups has been called");
#endif
/* WTMP PROCESSING STARTS HERE */
    if (wtmp_logging) {
	/* open wtmp before chroot */
#if ((defined(BSD) && (BSD >= 199103)) || defined(sun))
	(void) sprintf(ttyline, "ftp%ld", (long) getpid());
#else
	(void) sprintf(ttyline, "ftpd%d", getpid());
#endif
#ifdef DEBUG
	syslog(LOG_DEBUG, "about to call wtmp");
#endif
	wu_logwtmp(ttyline, pw->pw_name, remotehost, 1);
    }
    logged_in = 1;

    expand_id();

#ifdef QUOTA
    memset(&quota, 0, sizeof(quota));
    get_quota(pw->pw_dir, pw->pw_uid);
#endif

    restricted_user = 0;
    if (!anonymous)
	if ((restricteduid(pw->pw_uid) && !unrestricteduid(pw->pw_uid))
	    || (restrictedgid(pw->pw_gid) && !unrestrictedgid(pw->pw_gid)))
	    restricted_user = 1;
    if (anonymous || guest) {
	char *sp;
	/* We MUST do a chdir() after the chroot. Otherwise the old current
	 * directory will be accessible as "." outside the new root! */
#ifdef ALTERNATE_CD
	home = defhome;
#endif
#ifdef VIRTUAL
	if (virtual_mode && !guest) {
#ifdef CLOSED_VIRTUAL_SERVER
	    if (DenyVirtualAnonymous()) {
#ifdef VERBOSE_ERROR_LOGING
		syslog(LOG_NOTICE, "FTP LOGIN FAILED (virtual host anonymous access denied) for %s",
		       remoteident);
#endif
		reply(530, "Login incorrect.");
		if (++login_attempts >= lgi_failure_threshold) {
		    syslog(LOG_NOTICE, "repeated login failures from %s", remoteident);
		    dologout(0);
		}
		goto bad;
	    }
#endif
	    /* Anonymous user in virtual_mode */
	    if (pw->pw_dir)
		free(pw->pw_dir);
	    pw->pw_dir = sgetsave(virtual_root);
	}
	else
#endif

	    /*
	       *  New chroot logic.
	       *
	       *  If VIRTUAL is supported, the chroot for anonymous users on the
	       *  virtual host has already been determined.  Otherwise the logic
	       *  below applies:
	       *
	       *  If this is an anonymous user, the chroot directory is determined
	       *  by the "anonymous-root" clause and the home directory is taken
	       *  from the etc/passwd file found after chroot'ing.
	       *
	       *  If this a guest user, the chroot directory is determined by the
	       *  "guest-root" clause and the home directory is taken from the
	       *  etc/passwd file found after chroot'ing.
	       *
	       *  The effect of this logic is that the entire chroot environment
	       *  is under the control of the ftpaccess file and the supporting
	       *  files in the ftp environment.  The system-wide passwd file is
	       *  used only to authenticate the user.
	     */

	{
	    struct aclmember *entry = NULL;
	    char *root_path = NULL;

	    if (anonymous) {
		char class[BUFSIZ];

		(void) acl_getclass(class);
		while (getaclentry("anonymous-root", &entry) && ARG0) {
		    if (!ARG1) {
			if (!root_path)
			    root_path = ARG0;
		    }
		    else {
			int which;

			for (which = 1; (which < MAXARGS) && ARG[which]; which++) {
			    if (!strcmp(ARG[which], "*")) {
				if (!root_path)
				    root_path = ARG0;
			    }
			    else {
				if (!strcasecmp(ARG[which], class))
				    root_path = ARG0;
			    }
			}
		    }
		}
	    }
	    else {		/* (guest) */
		while (getaclentry("guest-root", &entry) && ARG0) {
		    if (!ARG1) {
			if (!root_path)
			    root_path = ARG0;
		    }
		    else {
			int which;
			char *ptr;

			for (which = 1; (which < MAXARGS) && ARG[which]; which++) {
			    if (!strcmp(ARG[which], "*")) {
				if (!root_path)
				    root_path = ARG0;
			    }
			    else {
				if (ARG[which][0] == '%') {
				    if ((ptr = strchr(ARG[which] + 1, '-')) == NULL) {
					if ((ptr = strchr(ARG[which] + 1, '+')) == NULL) {
					    if (pw->pw_uid == strtoul(ARG[which] + 1, NULL, 0))
						root_path = ARG0;
					}
					else {
					    *ptr++ = '\0';
					    if ((ARG[which][1] == '\0')
						|| (pw->pw_uid >= strtoul(ARG[which] + 1, NULL, 0)))
						root_path = ARG0;
					    *--ptr = '+';
					}
				    }
				    else {
					*ptr++ = '\0';
					if (((ARG[which][1] == '\0')
					     || (pw->pw_uid >= strtoul(ARG[which] + 1, NULL, 0)))
					    && ((*ptr == '\0')
						|| (pw->pw_uid <= strtoul(ptr, NULL, 0))))
					    root_path = ARG0;
					*--ptr = '-';
				    }
				}
				else {
#ifdef OTHER_PASSWD
				    struct passwd *guest_pw = bero_getpwnam(ARG[which], _path_passwd);
#else
				    struct passwd *guest_pw = getpwnam(ARG[which]);
#endif
				    if (guest_pw && (pw->pw_uid == guest_pw->pw_uid))
					root_path = ARG0;
				}
			    }
			}
		    }
		}
	    }

	    if (root_path) {
		struct passwd *chroot_pw = NULL;

#if defined(VIRTUAL) && defined(CLOSED_VIRTUAL_SERVER)
		if (virtual_mode && strcmp(root_path, virtual_root) && !(AllowVirtualUser(pw->pw_name) && !DenyVirtualUser(pw->pw_name))) {
#ifdef VERBOSE_ERROR_LOGING
		    syslog(LOG_NOTICE, "FTP LOGIN FAILED (virtual host access denied) for %s, %s",
			   remoteident, pw->pw_name);
#endif
		    reply(530, "Login incorrect.");
		    if (++login_attempts >= lgi_failure_threshold) {
			syslog(LOG_NOTICE, "repeated login failures from %s", remoteident);
			dologout(0);
		    }
		    goto bad;
		}
#endif
		(void) strncpy(chroot_path, root_path, sizeof(chroot_path));
		chroot_path[sizeof(chroot_path) - 1] = '\0';
		if (pw->pw_dir)
		    free(pw->pw_dir);
		pw->pw_dir = sgetsave(chroot_path);
#if defined(SOLARIS_2)
		cleanup_nscd();
#endif
		if (chroot(root_path) < 0 || chdir("/") < 0) {
#ifdef VERBOSE_ERROR_LOGING
		    syslog(LOG_NOTICE, "FTP LOGIN FAILED (cannot set guest privileges) for %s, %s",
			   remoteident, pw->pw_name);
#endif
		    reply(530, "Can't set guest privileges.");
		    goto bad;
		}
#ifdef OTHER_PASSWD
		if ((chroot_pw = bero_getpwuid(pw->pw_uid, _path_passwd)) != NULL)
#else
		if ((chroot_pw = getpwuid(pw->pw_uid)) != NULL)
#endif
		    if (chdir(chroot_pw->pw_dir) >= 0)
			home = sgetsave(chroot_pw->pw_dir);
		goto slimy_hack;	/* onea these days I'll make this structured code, honest ... */
	    }
	}

	/* determine root and home directory */

	if ((sp = strstr(pw->pw_dir, "/./")) == NULL) {
	    (void) strncpy(chroot_path, pw->pw_dir, sizeof(chroot_path));
	    chroot_path[sizeof(chroot_path) - 1] = '\0';
#if defined(VIRTUAL) && defined(CLOSED_VIRTUAL_SERVER)
	    if (virtual_mode && strcmp(chroot_path, virtual_root) && !(AllowVirtualUser(pw->pw_name) && !DenyVirtualUser(pw->pw_name))) {
#ifdef VERBOSE_ERROR_LOGING
		syslog(LOG_NOTICE, "FTP LOGIN FAILED (virtual host access denied) for %s, %s",
		       remoteident, pw->pw_name);
#endif
		reply(530, "Login incorrect.");
		if (++login_attempts >= lgi_failure_threshold) {
		    syslog(LOG_NOTICE, "repeated login failures from %s", remoteident);
		    dologout(0);
		}
		goto bad;
	    }
#endif
#if defined(SOLARIS_2)
	    cleanup_nscd();
#endif
	    if (chroot(pw->pw_dir) < 0 || chdir("/") < 0) {
#ifdef VERBOSE_ERROR_LOGING
		syslog(LOG_NOTICE, "FTP LOGIN FAILED (cannot set guest privileges) for %s, %s",
		       remoteident, pw->pw_name);
#endif
		reply(530, "Can't set guest privileges.");
		goto bad;
	    }
	}
	else {
	    *sp++ = '\0';
	    (void) strncpy(chroot_path, pw->pw_dir, sizeof(chroot_path));
	    chroot_path[sizeof(chroot_path) - 1] = '\0';
#if defined(VIRTUAL) && defined(CLOSED_VIRTUAL_SERVER)
	    if (virtual_mode && strcmp(chroot_path, virtual_root) && !(AllowVirtualUser(pw->pw_name) && !DenyVirtualUser(pw->pw_name))) {
#ifdef VERBOSE_ERROR_LOGING
		syslog(LOG_NOTICE, "FTP LOGIN FAILED (virtual host access denied) for %s, %s",
		       remoteident, pw->pw_name);
#endif
		reply(530, "Login incorrect.");
		if (++login_attempts >= lgi_failure_threshold) {
		    syslog(LOG_NOTICE, "repeated login failures from %s", remoteident);
		    dologout(0);
		}
		goto bad;
	    }
#endif
#if defined(SOLARIS_2)
	    cleanup_nscd();
#endif
	    if (chroot(pw->pw_dir) < 0 || chdir(++sp) < 0) {
#ifdef VERBOSE_ERROR_LOGING
		syslog(LOG_NOTICE, "FTP LOGIN FAILED (cannot set guest privileges) for %s, %s",
		       remoteident, pw->pw_name);
#endif
		reply(550, "Can't set guest privileges.");
		goto bad;
	    }
#ifdef ALTERNATE_CD
	    home = sp;
#endif
	}
      slimy_hack:
	/* shut up you stupid compiler! */  {
	    int i = 0;
	    i++;
	}
    }
#if defined(VIRTUAL) && defined(CLOSED_VIRTUAL_SERVER)
    else if (virtual_mode && !(AllowVirtualUser(pw->pw_name) && !DenyVirtualUser(pw->pw_name))) {
#ifdef VERBOSE_ERROR_LOGING
	syslog(LOG_NOTICE, "FTP LOGIN FAILED (virtual host access denied) for %s, %s",
	       remoteident, pw->pw_name);
#endif
	reply(530, "Login incorrect.");
	if (++login_attempts >= lgi_failure_threshold) {
	    syslog(LOG_NOTICE, "repeated login failures from %s", remoteident);
	    dologout(0);
	}
	goto bad;
    }
#endif
#ifdef AIX
    {
	/* AIX 3 lossage.  Don't ask.  It's undocumented.  */
	priv_t priv;

	priv.pv_priv[0] = 0;
	priv.pv_priv[1] = 0;
/*       setgroups(NULL, NULL); */
	if (setpriv(PRIV_SET | PRIV_INHERITED | PRIV_EFFECTIVE | PRIV_BEQUEATH,
		    &priv, sizeof(priv_t)) < 0 ||
	    setuidx(ID_REAL | ID_EFFECTIVE, (uid_t) pw->pw_uid) < 0 ||
	    seteuid((uid_t) pw->pw_uid) < 0) {
#ifdef VERBOSE_ERROR_LOGING
	    syslog(LOG_NOTICE, "FTP LOGIN FAILED (cannot set uid) for %s, %s",
		   remoteident, pw->pw_name);
#endif
	    reply(530, "Can't set uid (AIX3).");
	    goto bad;
	}
    }
#ifdef UID_DEBUG
    lreply(success_code, "ruid=%d, euid=%d, suid=%d, luid=%d", getuidx(ID_REAL),
	   getuidx(ID_EFFECTIVE), getuidx(ID_SAVED), getuidx(ID_LOGIN));
    lreply(success_code, "rgid=%d, egid=%d, sgid=%d, lgid=%d", getgidx(ID_REAL),
	   getgidx(ID_EFFECTIVE), getgidx(ID_SAVED), getgidx(ID_LOGIN));
#endif
#else /* AIX */
#ifdef HAVE_SETREUID
    if (setreuid(-1, (uid_t) pw->pw_uid) < 0) {
#else
    if (seteuid((uid_t) pw->pw_uid) < 0) {
#endif
#ifdef VERBOSE_ERROR_LOGING
	syslog(LOG_NOTICE, "FTP LOGIN FAILED (cannot set uid) for %s, %s",
	       remoteident, pw->pw_name);
#endif
	reply(530, "Can't set uid.");
	goto bad;
    }
#endif /* AIX */
    if (!anonymous && !guest) {
	if (chdir(pw->pw_dir) < 0) {
#ifdef PARANOID
#ifdef VERBOSE_ERROR_LOGING
	    syslog(LOG_NOTICE, "FTP LOGIN FAILED (cannot chdir) for %s, %s",
		   remoteident, pw->pw_name);
#endif
	    reply(530, "User %s: can't change directory to %s.",
		  pw->pw_name, pw->pw_dir);
	    goto bad;
#else /* PARANOID */
	    if (restricted_user || chdir("/") < 0) {
#ifdef VERBOSE_ERROR_LOGING
		syslog(LOG_NOTICE, "FTP LOGIN FAILED (cannot chdir) for %s, %s",
		       remoteident, pw->pw_name);
#endif
		reply(530, "User %s: can't change directory to %s.",
		      pw->pw_name, pw->pw_dir);
		goto bad;
	    }
	    else {
		lreply(success_code, "No directory! Logging in with home=/");
#ifdef ALTERNATE_CD
		home = defhome;
#endif
	    }
#endif /* PARANOID */
	}
    }

    if (passwarn) {
	lreply(success_code, "The response '%s' is not valid", passwd);
	lreply(success_code,
	       "Next time please use your e-mail address as your password");
	lreply(success_code, "   for example: %s@%s%s",
	       authenticated ? authuser : "joe", remotehost,
	       strchr(remotehost, '.') ? "" : ".network");
    }

    login_attempts = 0;		/* this time successful */

    /* following two lines were inside the next scope... */

    show_message(success_code, LOG_IN);
    show_message(success_code, C_WD);
    show_readme(success_code, LOG_IN);
    show_readme(success_code, C_WD);

#ifdef ULTRIX_AUTH
    if (!anonymous && numfails > 0) {
	lreply(success_code,
	   "There have been %d unsuccessful login attempts on your account",
	       numfails);
    }
#endif /* ULTRIX_AUTH */

    (void) is_shutdown(0, 0);	/* display any shutdown messages now */

    if (anonymous) {

	reply(success_code, "Guest login ok, access restrictions apply.");
	sprintf(proctitle, "%s: anonymous/%.*s", remotehost,
		(int) (sizeof(proctitle) - sizeof(remotehost) -
		       sizeof(": anonymous/")), passwd);
	setproctitle("%s", proctitle);
	if (logging)
	    syslog(LOG_INFO, "ANONYMOUS FTP LOGIN FROM %s, %s",
		   remoteident, passwd);
    }
    else {
	reply(success_code, "User %s logged in.%s", pw->pw_name, guest ?
	      "  Access restrictions apply." : "");
	sprintf(proctitle, "%s: %s", remotehost, pw->pw_name);
	setproctitle("%s", proctitle);
	if (logging)
	    syslog(LOG_INFO, "FTP LOGIN FROM %s, %s", remoteident, pw->pw_name);
/* H* mod: if non-anonymous user, copy it to "authuser" so everyone can
   see it, since whoever he was @foreign-host is now largely irrelevant.
   NMM mod: no, it isn't!  Think about accounting for the transfers from or
   to a shared account. */
	/* strcpy (authuser, pw->pw_name); */
    }				/* anonymous */
#ifdef ALTERNATE_CD
    if (!home)
#endif
	home = pw->pw_dir;	/* home dir for globbing */
    (void) umask(defumask);
    time(&login_time);
    {
	struct aclmember *entry;
	entry = NULL;
	while (getaclentry("limit-time", &entry) && ARG0 && ARG1)
	    if ((anonymous && strcasecmp(ARG0, "anonymous") == 0)
		|| (guest && strcasecmp(ARG0, "guest") == 0)
		|| ((guest | anonymous) && strcmp(ARG0, "*") == 0))
		limit_time = strtoul(ARG1, NULL, 0);
    }

    /* Need to reset here as user type/class now known */
#ifdef INET6
    /* IP_TOS is an IPv4 socket option */
    if (SOCK_FAMILY(ctrl_addr) == AF_INET)
#endif
    if ((cos = IPClassOfService("control")) >= 0) {
	if (setsockopt(0, IPPROTO_IP, IP_TOS, (char *) &cos, sizeof(int)) < 0)
	    syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
    }

#ifdef SOLARIS_BSM_AUDIT
    audit_ftpd_success(the_user);
#endif
    init_privs(pw->pw_name);
    return;
  bad:
    /* Forget all about it... */
    if (xferlog)
	close(xferlog);
    xferlog = 0;
    acl_remove();
#ifdef SOLARIS_BSM_AUDIT
    audit_ftpd_failure(the_user);
#endif
    end_login();
    return;
}

int restricteduid(uid_t uid)
{
    return uid_match("restricted-uid", uid);
}

int unrestricteduid(uid_t uid)
{
    return uid_match("unrestricted-uid", uid);
}

int restrictedgid(gid_t gid)
{
    return gid_match("restricted-gid", gid, NULL);
}

int unrestrictedgid(gid_t gid)
{
    return gid_match("unrestricted-gid", gid, NULL);
}

char *opt_string(int options)
{
    static char buf[100];
    char *ptr = buf;

    if ((options & O_COMPRESS) != 0)	/* debian fixes: NULL -> 0 */
	*ptr++ = 'C';
    if ((options & O_TAR) != 0)
	*ptr++ = 'T';
    if ((options & O_UNCOMPRESS) != 0)
	*ptr++ = 'U';
    if (options == 0)
	*ptr++ = '_';
    *ptr++ = '\0';
    return (buf);
}

#ifdef INTERNAL_LS
char *rpad(char *s, unsigned int len)
{
    char *a;
    a = (char *) malloc(len + 1);
    memset(a, ' ', len);
    a[len] = 0;
    if (strlen(s) <= len)
	memcpy(a, s, strlen(s));
    else
	strncpy(a, s, len);
    return a;
}

char *ls_file(const char *file, int nameonly, char remove_path, char classify)
{
    static const char month[12][4] =
    {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

    char *permissions;
    struct stat s;
    struct tm *t;
    char *ls_entry;
    char *owner, *ownerg;
    char *rpowner, *rpownerg;
    char *link;
#ifndef LS_NUMERIC_UIDS
    struct passwd *pw;
    struct group *gr;
#endif
    link = NULL;
    owner = NULL;
    ownerg = NULL;
    if (lstat(file, &s) != 0)	/* File doesn't exist, or is not readable by user */
	return NULL;
    ls_entry = (char *) malloc(312);
    memset(ls_entry, 0, 312);
    permissions = strdup("----------");
    if (S_ISLNK(s.st_mode)) {
	permissions[0] = 'l';
	if (classify)
	    classify = '@';
    }
    else if (S_ISDIR(s.st_mode)) {
	permissions[0] = 'd';
	if (classify)
	    classify = '/';
    }
    else if (S_ISBLK(s.st_mode))
	permissions[0] = 'b';
    else if (S_ISCHR(s.st_mode))
	permissions[0] = 'c';
    else if (S_ISFIFO(s.st_mode)) {
	permissions[0] = 'p';
	if (classify == 1)
	    classify = '=';
    }
#ifdef S_ISSOCK
    else if (S_ISSOCK(s.st_mode))
	permissions[0] = 's';
#endif
    if ((s.st_mode & S_IRUSR) == S_IRUSR)
	permissions[1] = 'r';
    if ((s.st_mode & S_IWUSR) == S_IWUSR)
	permissions[2] = 'w';
    if ((s.st_mode & S_IXUSR) == S_IXUSR) {
	permissions[3] = 'x';
	if (classify == 1)
	    classify = '*';
#ifndef HIDE_SETUID
	if ((s.st_mode & S_ISUID) == S_ISUID)
	    permissions[3] = 's';
#endif
    }
#ifndef HIDE_SETUID
    else if ((s.st_mode & S_ISUID) == S_ISUID)
	permissions[3] = 'S';
#endif
    if ((s.st_mode & S_IRGRP) == S_IRGRP)
	permissions[4] = 'r';
    if ((s.st_mode & S_IWGRP) == S_IWGRP)
	permissions[5] = 'w';
    if ((s.st_mode & S_IXGRP) == S_IXGRP) {
	permissions[6] = 'x';
	if (classify == 1)
	    classify = '*';
#ifndef HIDE_SETUID
	if ((s.st_mode & S_ISGID) == S_ISGID)
	    permissions[6] = 's';
#endif
    }
#ifndef HIDE_SETUID
    else if ((s.st_mode & S_ISGID) == S_ISGID)
	permissions[6] = 'S';
#endif
    if ((s.st_mode & S_IROTH) == S_IROTH)
	permissions[7] = 'r';
    if ((s.st_mode & S_IWOTH) == S_IWOTH)
	permissions[8] = 'w';
    if ((s.st_mode & S_IXOTH) == S_IXOTH) {
	permissions[9] = 'x';
	if (classify == 1)
	    classify = '*';
#ifndef HIDE_SETUID
	if ((s.st_mode & S_ISVTX) == S_ISVTX)
	    permissions[9] = 't';
#endif
    }
#ifndef HIDE_SETUID
    else if ((s.st_mode & S_ISVTX) == S_ISVTX)
	permissions[9] = 'T';
#endif
    t = localtime(&s.st_mtime);
#ifndef LS_NUMERIC_UIDS
#ifdef OTHER_PASSWD
    pw = bero_getpwuid(s.st_uid, _path_passwd);
#else
    pw = getpwuid(s.st_uid);
#endif
    if (pw != NULL)
	owner = strdup(pw->pw_name);
    gr = getgrgid(s.st_gid);
    if (gr != NULL)
	ownerg = strdup(gr->gr_name);
#endif
    if (owner == NULL) {	/* Can't figure out username (or don't want to) */
	if (s.st_uid == 0)
	    owner = strdup("root");
	else {
	    owner = (char *) malloc(9);
	    memset(owner, 0, 9);
#ifdef SOLARIS_2
	    snprintf(owner, 8, "%lu", s.st_uid);
#else
	    snprintf(owner, 8, "%u", s.st_uid);
#endif
	}
    }
    if (ownerg == NULL) {	/* Can't figure out groupname (or don't want to) */
	if (s.st_gid == 0)
	    ownerg = strdup("root");
	else {
	    ownerg = (char *) malloc(9);
	    memset(ownerg, 0, 9);
#ifdef SOLARIS_2
	    snprintf(ownerg, 8, "%lu", s.st_gid);
#else
	    snprintf(ownerg, 8, "%u", s.st_gid);
#endif
	}
    }

#ifdef HAVE_LSTAT
    if (S_ISLNK(s.st_mode)) {
	link = (char *) malloc(MAXPATHLEN);
	memset(link, 0, MAXPATHLEN);
	if (readlink(file, link, MAXPATHLEN) == -1) {
	    free(link);
	    link = NULL;
	}
    }
#endif

    if (remove_path != 0 && strchr(file, '/'))
	file = strrchr(file, '/') + 1;

    rpowner = rpad(owner, 8);
    rpownerg = rpad(ownerg, 8);

#ifdef SOLARIS_2
#define N_FORMAT "lu"
#else
#if defined(__FreeBSD__) || defined(__bsdi__)
#define N_FORMAT "u"
#else
#define N_FORMAT "u"
#endif
#endif

    if (nameonly) {
	sprintf(ls_entry, "%s", file);
	if (link != NULL)
	    free(link);
    }
    else {
	if ((time(NULL) - s.st_mtime) > 6307200) {	/* File is older than 6 months */
	    if (link == NULL)
		snprintf(ls_entry, 311, "%s %3" N_FORMAT " %s %s %8" L_FORMAT " %s %2u %5u %s", permissions, s.st_nlink, rpowner, rpownerg, s.st_size, month[t->tm_mon], t->tm_mday, 1900 + t->tm_year, file);
	    else {
		snprintf(ls_entry, 311, "%s %3" N_FORMAT " %s %s %8" L_FORMAT " %s %2u %5u %s -> %s", permissions, s.st_nlink, rpowner, rpownerg, s.st_size, month[t->tm_mon], t->tm_mday, 1900 + t->tm_year, file, link);
		free(link);
	    }
	}
	else if (link == NULL)
	    snprintf(ls_entry, 311, "%s %3" N_FORMAT " %s %s %8" L_FORMAT " %s %2u %02u:%02u %s", permissions, s.st_nlink, rpowner, rpownerg, s.st_size, month[t->tm_mon], t->tm_mday, t->tm_hour, t->tm_min, file);
	else {
	    snprintf(ls_entry, 311, "%s %3" N_FORMAT " %s %s %8" L_FORMAT " %s %2u %02u:%02u %s -> %s", permissions, s.st_nlink, rpowner, rpownerg, s.st_size, month[t->tm_mon], t->tm_mday, t->tm_hour, t->tm_min, file, link);
	    free(link);
	}
    }
    free(rpowner);
    free(rpownerg);
    free(owner);
    free(ownerg);
    if (classify > 1)
	sprintf(ls_entry + strlen(ls_entry), "%c", classify);
    strcat(ls_entry, "\r\n");
    free(permissions);
    return ls_entry;
}

void ls_dir(char *d, char ls_a, char ls_F, char ls_l, char ls_R, char omit_total, FILE *out)
{
    int total;
    char *realdir;		/* fixed up value to pass to glob() */
    char **subdirs;		/* Subdirs to be scanned for ls -R  */
    int numSubdirs = 0;
    glob_t g;
    char isDir;			/* 0: d is a file; 1: d is some files; 2: d is dir */
    struct stat s;
    char *dirlist;
    unsigned long dl_size, dl_used;
    char *c;
    char *lsentry;
    int i;
#ifndef GLOB_PERIOD
    char *dperiod;
#endif

    isDir = 0;
    realdir = (char *) malloc(strlen(d) + 3);
    memset(realdir, 0, strlen(d) + 3);
    strcpy(realdir, d);
    if (strcmp(realdir, ".") == 0)
	realdir[0] = '*';
    if (strcmp(realdir + strlen(realdir) - 2, "/.") == 0)
	realdir[strlen(realdir) - 1] = '*';
    if (realdir[strlen(realdir) - 1] == '/')
	strcat(realdir, "*");
    if (strchr(realdir, '*') || strchr(realdir, '?'))
	isDir = 1;
    if (strcmp(realdir, "*") == 0 || strcmp(realdir + strlen(realdir) - 2, "/*") == 0)
	isDir = 2;
    else {
	if (lstat(realdir, &s) == 0) {
	    if (S_ISDIR(s.st_mode)) {
		strcat(realdir, "/*");
		isDir = 2;
	    }
	}
    }

    if (isDir == 0) {
	if (ls_l) {
	    lsentry = ls_file(realdir, 0, 0, ls_F);
	    if (lsentry != NULL) {
		if (draconian_FILE != NULL) {
		    (void) signal(SIGALRM, draconian_alarm_signal);
		    alarm(timeout_data);
#if defined(USE_GSS)
		    sec_fprintf(out, "%s", lsentry);
#else
		    fputs(lsentry, out);
#endif /* defined(USE_GSS) */
		    (void) signal(SIGALRM, SIG_DFL);
		}
		free(lsentry);
	    }
	}
	else {
	    if (draconian_FILE != NULL) {
		(void) signal(SIGALRM, draconian_alarm_signal);
		alarm(timeout_data);
#if defined(USE_GSS)
		sec_fprintf(out, "%s", realdir);
#else
		fputs(realdir, out);
#endif /* defined(USE_GSS) */
		(void) signal(SIGALRM, SIG_DFL);
	    }
	}
	free(realdir);
    }
    else {
	if (ls_R) {
	    numSubdirs = 0;
	    subdirs = (char **) malloc(200 * sizeof(char *));
	    memset(subdirs, 0, 200 * sizeof(char *));
	}

	dl_size = 65536;
	dirlist = (char *) malloc(65536);
	memset(dirlist, 0, 65536);
	dl_used = 0;

	total = 0;
	memset(&g, 0, sizeof(g));
	if (ls_a) {
#ifdef GLOB_PERIOD
	    if (glob(realdir, GLOB_ERR | GLOB_PERIOD, NULL, &g) != 0)
		g.gl_pathc = 0;
#else
	    dperiod = (char *) malloc(strlen(realdir) + 2);
	    memset(dperiod, 0, strlen(realdir) + 2);
	    strcpy(dperiod, ".");
	    strcat(dperiod, realdir);
	    if (glob(dperiod, GLOB_ERR, NULL, &g) != 0)
		g.gl_pathc = 0;
	    glob(realdir, GLOB_ERR | GLOB_APPEND, NULL, &g);
	    free(dperiod);
#endif
	}
	else if (glob(realdir, GLOB_ERR, NULL, &g) != 0)
	    g.gl_pathc = 0;
	free(realdir);
	for (i = 0; i < g.gl_pathc; i++) {
	    c = g.gl_pathv[i];
	    if (lstat(c, &s) != -1) {
		if (ls_l) {
		    total += s.st_blocks;
		    lsentry = ls_file(c, 0, 1, ls_F);
		    if (lsentry != NULL) {
			/* This can actually happen even though the lstat() worked - 
			   if someone deletes the file between the lstat() and ls_file()
			   calls. Unlikely, but better safe than sorry... */
			int flag = snprintf(dirlist + dl_used, dl_size - dl_used, "%s", lsentry);
			dl_used += (flag == -1 ? dl_size - dl_used : flag);
			free(lsentry);
		    }
		}
		else {
		    int flag;
		    lsentry = ls_file(c, 1, 1, ls_F);
		    if (lsentry != NULL) {
		        flag = snprintf(dirlist + dl_used, dl_size - dl_used, "%s", lsentry);
		        dl_used += (flag == -1 ? dl_size - dl_used : flag);
			free(lsentry);
		    }
		}
		if ((ls_R != 0) && (S_ISDIR(s.st_mode))
		    && (strcmp(c, "..") != 0) && (strcmp(c, ".") != 0)
		&& !(strlen(c) > 3 && strcmp(c + strlen(c) - 3, "/..") == 0)
		    && !(strlen(c) > 2 && strcmp(c + strlen(c) - 2, "/.") == 0)) {
		    subdirs[numSubdirs++] = strdup(c);
		    if ((numSubdirs % 200) == 0)
			subdirs = (char **) realloc(subdirs, (numSubdirs + 200) * sizeof(char *));
		}
	    }
	    if (dl_used + 512 >= dl_size) {
		dl_size += 65536;
		dirlist = (char *) realloc(dirlist, dl_size);
	    }
	}
	globfree(&g);
	if (ls_l && isDir == 2 && omit_total == 0) {
	    if (draconian_FILE != NULL) {
		(void) signal(SIGALRM, draconian_alarm_signal);
		alarm(timeout_data);
#if defined(USE_GSS)
		sec_fprintf(out, "total %u\r\n", total);
#else
		fprintf(out, "total %u\r\n", total);
#endif /* defined(USE_GSS) */
	    }
	}
	if (draconian_FILE != NULL) {
	    (void) signal(SIGALRM, draconian_alarm_signal);
	    alarm(timeout_data);
#if defined(USE_GSS)
	    sec_fprintf(out, "%s", dirlist);
#else
	    fputs(dirlist, out);
#endif /* defined(USE_GSS) */
	}
	free(dirlist);
	if (ls_R) {
	    for (i = 0; i < numSubdirs; i++) {
		if (draconian_FILE != NULL) {
		    (void) signal(SIGALRM, draconian_alarm_signal);
		    alarm(timeout_data);
#if defined(USE_GSS)
		    sec_fprintf(out, "\r\n%s:\r\n", subdirs[i]);
#else
		    fprintf(out, "\r\n%s:\r\n", subdirs[i]);
#endif /* defined(USE_GSS) */
		    ls_dir(subdirs[i], ls_a, ls_F, ls_l, ls_R, 0, out);
		}
		free(subdirs[i]);
	    }
	    free(subdirs);
	}
    }
}

void ls(char *file, char nlst)
{
    FILE *out;
    char free_file = 0;
    char ls_l = 0, ls_a = 0, ls_R = 0, ls_F = 0;

    if (nlst == 0)
	ls_l = 1;		/* LIST defaults to ls -l */
    if (file == NULL) {
	file = strdup(".");
	free_file = 1;
    }
    if (strcmp(file, "*") == 0)
	file[0] = '.';

    if (file[0] == '-') {	/* options... */
	if (strchr(file, ' ') == 0) {
	    if (strchr(file, 'l'))
		ls_l = 1;
	    if (strchr(file, 'a'))
		ls_a = 1;
	    if (strchr(file, 'R'))
		ls_R = 1;
	    if (strchr(file, 'F'))
		ls_F = 1;
	    file = strdup(".");
	    free_file = 1;
	}
	else {
	    if (strchr(file, 'l') != NULL && strchr(file, 'l') < strchr(file, ' '))
		ls_l = 1;
	    if (strchr(file, 'a') != NULL && strchr(file, 'a') < strchr(file, ' '))
		ls_a = 1;
	    if (strchr(file, 'R') != NULL && strchr(file, 'R') < strchr(file, ' '))
		ls_R = 1;
	    if (strchr(file, 'F') != NULL && strchr(file, 'F') < strchr(file, ' '))
		ls_F = 1;
	    file = strchr(file, ' ');
	}
    }
    while (file[0] == ' ')	/* ignore additional whitespaces between parameters */
	file++;
    if (strlen(file) == 0) {
	file = strdup(".");
	free_file = 1;
    }

    out = dataconn("directory listing", -1, "w");
    draconian_FILE = out;

    transflag++;

    fixpath(file);
    if (file[0] == '\0') {
	if (free_file != 0)
	    free(file);
	file = strdup(".");
	free_file = 1;
    }

    ls_dir(file, ls_a, ls_F, ls_l, ls_R, 0, out);
    data = -1;
    pdata = -1;
    if (draconian_FILE != NULL) {
	(void) signal(SIGALRM, draconian_alarm_signal);
	alarm(timeout_data);
#if defined(USE_GSS)
	if (sec_fflush(out) < 0) {
	    draconian_FILE = NULL;
	    alarm(0);
	    transflag = 0;
	    perror_reply(550, "Data connection");
	    fclose(out);
	    goto ls_done;
	}
#else
	fflush(out);
#endif /* defined(USE_GSS) */
    }
    if (draconian_FILE != NULL) {
	(void) signal(SIGALRM, draconian_alarm_signal);
	alarm(timeout_data);
	socket_flush_wait(out);
    }
    if (draconian_FILE != NULL) {
	(void) signal(SIGALRM, draconian_alarm_signal);
	alarm(timeout_data);
	fclose(out);
	draconian_FILE = NULL;
    }
    alarm(0);
    transflag = 0;
    reply(226, "Transfer complete.");
  ls_done:
    if (free_file != 0)
	free(file);
}
#endif /* INTERNAL_LS */

void retrieve(char *cmd, char *name)
{
    FILE *fin = NULL, *dout;
    struct stat st, junk;
    int (*closefunc) () = NULL;
    int options = 0;
    int ThisRetrieveIsData = retrieve_is_data;
    time_t start_time = time(NULL);
    char *logname;
    char namebuf[MAXPATHLEN];
    char fnbuf[MAXPATHLEN];
    static int TransferComplete;	/* static as retrieve can call itself */
    struct convert *cptr;
    char realname[MAXPATHLEN];
    int stat_ret = -1;
    size_t buffersize;

    TransferComplete = 0;
    wu_realpath(name, realname, chroot_path);

    if (cmd == NULL && (stat_ret = stat(name, &st)) == 0)
	/* there isn't a command and the file exists */
	if (use_accessfile && checknoretrieve(name)) {	/* see above.  _H */
	    if (log_security)
		if (anonymous)
		    syslog(LOG_NOTICE, "anonymous(%s) of %s tried to download %s (noretrieve)",
			   guestpw, remoteident, realname);
		else
		    syslog(LOG_NOTICE, "%s of %s tried to download %s (noretrieve)",
			   pw->pw_name, remoteident, realname);
	    return;
	}

#ifdef TRANSFER_COUNT
#ifdef TRANSFER_LIMIT
    if (retrieve_is_data)
	if (((file_limit_data_out > 0) && (file_count_out >= file_limit_data_out))
	    || ((file_limit_data_total > 0) && (file_count_total >= file_limit_data_total))
	    || ((data_limit_data_out > 0) && (data_count_out >= data_limit_data_out))
	    || ((data_limit_data_total > 0) && (data_count_total >= data_limit_data_total))) {
	    if (log_security)
		if (anonymous)
		    syslog(LOG_NOTICE, "anonymous(%s) of %s tried to retrieve %s (Transfer limits exceeded)",
			   guestpw, remoteident, realname);
		else
		    syslog(LOG_NOTICE, "%s of %s tried to retrieve %s (Transfer limits exceeded)",
			   pw->pw_name, remoteident, realname);
	    reply(553, "Permission denied on server. (Transfer limits exceeded)");
	    return;
	}
    if (((file_limit_raw_out > 0) && (xfer_count_out >= file_limit_raw_out))
	|| ((file_limit_raw_total > 0) && (xfer_count_total >= file_limit_raw_total))
	|| ((data_limit_raw_out > 0) && (byte_count_out >= data_limit_raw_out))
	|| ((data_limit_raw_total > 0) && (byte_count_total >= data_limit_raw_total))) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to retrieve %s (Transfer limits exceeded)",
		       guestpw, remoteident, realname);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to retrieve %s (Transfer limits exceeded)",
		       pw->pw_name, remoteident, realname);
	reply(553, "Permission denied on server. (Transfer limits exceeded)");
	return;
    }
#ifdef RATIO
    if (retrieve_is_data && (upload_download_rate > 0) )
	if( freefile = is_downloadfree(name) ) {
	    syslog(LOG_INFO, "%s is download free.", name );
	}
	else {
	    if ((cmd == NULL) && ((data_count_in * upload_download_rate) < (data_count_out - total_free_dl))) {
		reply(550, "%s: Upload/Download ratio exceeded", name);
		goto done;
	    }
	}
#endif /* RATIO */
#endif
#endif

    logname = (char *) NULL;
    if (cmd == NULL && stat_ret != 0) {		/* file does not exist */
	char *ptr;

	for (cptr = cvtptr; cptr != NULL; cptr = cptr->next) {
	    if (!(mangleopts & O_COMPRESS) && (cptr->options & O_COMPRESS))
		continue;
	    if (!(mangleopts & O_UNCOMPRESS) && (cptr->options & O_UNCOMPRESS))
		continue;
	    if (!(mangleopts & O_TAR) && (cptr->options & O_TAR))
		continue;

	    if ((cptr->stripfix) && (cptr->postfix)) {
		int pfxlen = strlen(cptr->postfix);
		int sfxlen = strlen(cptr->stripfix);
		int namelen = strlen(name);

		if (namelen <= pfxlen)
		    continue;
		if (((namelen - pfxlen + sfxlen) >= sizeof(fnbuf)) ||
		    (namelen >= sizeof(fnbuf)))
		    continue;

		(void) strcpy(fnbuf, name);
		if (strcmp(fnbuf + namelen - pfxlen, cptr->postfix))
		    continue;
		*(fnbuf + namelen - pfxlen) = '\0';
		(void) strcat(fnbuf, cptr->stripfix);
		if (stat(fnbuf, &st) != 0)
		    continue;
	    }
	    else if (cptr->postfix) {
		int pfxlen = strlen(cptr->postfix);
		int namelen = strlen(name);

		if ((namelen <= pfxlen) || (namelen >= sizeof(fnbuf)))
		    continue;
		(void) strcpy(fnbuf, name);
		if (strcmp(fnbuf + namelen - pfxlen, cptr->postfix))
		    continue;
		*(fnbuf + namelen - pfxlen) = (char) NULL;
		if (stat(fnbuf, &st) != 0)
		    continue;
	    }
	    else if (cptr->stripfix) {
		if (strlen(name) + strlen(cptr->stripfix) >= sizeof(fnbuf))
		    continue;
		(void) strcpy(fnbuf, name);
		(void) strcat(fnbuf, cptr->stripfix);
		if (stat(fnbuf, &st) != 0)
		    continue;
	    }
	    else {
		continue;
	    }

	    if (S_ISDIR(st.st_mode)) {
		if (!cptr->types || !(cptr->types & T_DIR)) {
		    reply(550, "Cannot %s directories.", cptr->name);
		    return;
		}
		if ((cptr->options & O_TAR)) {
		    strcpy(namebuf, fnbuf);
		    if (strlcat(namebuf, "/.notar", sizeof(namebuf)) >=
			sizeof(namebuf))
			continue;
		    if (stat(namebuf, &junk) == 0) {
			if (log_security)
			    if (anonymous)
				syslog(LOG_NOTICE, "anonymous(%s) of %s tried to tar %s (.notar)",
				       guestpw, remoteident, realname);
			    else
				syslog(LOG_NOTICE, "%s of %s tried to tar %s (.notar)",
				       pw->pw_name, remoteident, realname);
			reply(550, "Sorry, you may not TAR that directory.");
			return;
		    }
		}
	    }
/* XXX: checknoretrieve() test is weak in that if I can't get /etc/passwd
   but I can tar /etc or /, I still win.  Be careful out there... _H*
   but you could put .notar in / and /etc and stop that ! */
	    if (use_accessfile && checknoretrieve(fnbuf)) {
		if (log_security)
		    if (anonymous)
			syslog(LOG_NOTICE, "anonymous(%s) of %s tried to download %s (noretrieve)",
			       guestpw, remoteident, realname);
		    else
			syslog(LOG_NOTICE, "%s of %s tried to download %s (noretrieve)",
			       pw->pw_name, remoteident, realname);
		return;
	    }

	    if (S_ISREG(st.st_mode) && (!cptr->types || (cptr->types & T_REG) == 0)) {
		reply(550, "Cannot %s plain files.", cptr->name);
		return;
	    }
	    if (S_ISREG(st.st_mode) != 0 && S_ISDIR(st.st_mode) != 0) {
		reply(550, "Cannot %s special files.", cptr->name);
		return;
	    }
	    if ((!cptr->types || !(cptr->types & T_ASCII)) && deny_badasciixfer(550, ""))
		return;

	    logname = &fnbuf[0];
	    options |= cptr->options;

	    strcpy(namebuf, cptr->external_cmd);
	    if ((ptr = strchr(namebuf, ' ')) != NULL)
		*ptr = '\0';
	    if (stat(namebuf, &junk) != 0) {
		syslog(LOG_ERR, "external command %s not found", namebuf);
		reply(550,
		"Local error: conversion program not found. Cannot %s file.",
		      cptr->name);
		return;
	    }
	    (void) retrieve(cptr->external_cmd, logname);

	    goto logresults;	/* transfer of converted file completed */
	}
    }

    if (cmd == NULL) {		/* no command */
	fin = fopen(name, "r"), closefunc = fclose;
	st.st_size = 0;
    }
    else {			/* run command */
	static char line[BUFSIZ];

	(void) snprintf(line, sizeof(line), cmd, name), name = line;
	fin = ftpd_popen(line, "r", 1), closefunc = ftpd_pclose;
	st.st_size = -1;
#ifdef HAVE_ST_BLKSIZE
	st.st_blksize = BUFSIZ;
#endif
    }
    if (fin == NULL) {
	if (errno != 0)
	    perror_reply(550, name);
	if ((errno == EACCES) || (errno == EPERM))
	    if (log_security)
		if (anonymous)
		    syslog(LOG_NOTICE, "anonymous(%s) of %s tried to download %s (file permissions)",
			   guestpw, remoteident, realname);
		else
		    syslog(LOG_NOTICE, "%s of %s tried to download %s (file permissions)",
			   pw->pw_name, remoteident, realname);
	return;
    }
    if (cmd == NULL &&
	(fstat(fileno(fin), &st) < 0 || (st.st_mode & S_IFMT) != S_IFREG)) {
	reply(550, "%s: not a plain file.", name);
	goto done;
    }
    if (restart_point) {
	if (type == TYPE_A) {
	    register int c;
	    off_t i;

	    i = 0;
	    while (i++ < restart_point) {
		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;

    if (sendbufsz > 0) {
	buffersize = sendbufsz;
    }
    else {
#ifdef BUFFER_SIZE
	buffersize = BUFFER_SIZE;
#elif HAVE_ST_BLKSIZE
	buffersize = st.st_blksize * 2;
#else
	buffersize = BUFSIZ * 16;
#endif
    }

#ifdef THROUGHPUT
    TransferComplete = send_data(name, fin, dout, buffersize);
#else
    TransferComplete = send_data(fin, dout, buffersize);
#endif
#ifdef SIGPIPE
    (void) signal(SIGPIPE, SIG_IGN);
#endif
    (void) fclose(dout);
#ifdef SIGPIPE
    (void) signal(SIGPIPE, lostconn);
#endif

  logresults:
    if (ThisRetrieveIsData)
	fb_realpath((logname != NULL) ? logname : name, LastFileTransferred);

    if (log_outbound_xfers && (xferlog || syslogmsg) && (cmd == NULL)) {
	char msg[MAXXFERSTRLEN];	/* see extensions.h */
	int xfertime = time(NULL) - start_time;
	size_t msglen;

	if (!xfertime)
	    xfertime++;

	/* Gather transfer statistics */
	xfervalues.filename = (logname != NULL) ? logname : name;
	xfervalues.filesize = st.st_size;
	xfervalues.transfer_bytes = byte_count;
	xfervalues.transfer_direction = 'o';
	xfervalues.transfer_type = (type == TYPE_A) ? 'a' : 'b';
	xfervalues.transfer_time = xfertime;
	xfervalues.restart_offset = restart_point;
	strlcpy(xfervalues.special_action, opt_string(options), MAXSPACTCHARS);
	xfervalues.access_mode = anonymous ? 'a' : (guest ? 'g' : 'r');
	xfervalues.auth = authenticated;
	xfervalues.completion = TransferComplete ? 'c' : 'i';

	xferdone = 1;
	msg_massage(xferlog_format, msg, sizeof(msg));
	xferdone = 0;

	/* Ensure msg always ends with '\n' */
	msglen = strlen(msg);
	if (msglen == sizeof(msg) - 1) {
	    msg[sizeof(msg) - 2] = '\n';
	    msg[sizeof(msg) - 1] = '\0';
	}
	else {
	    msg[msglen] = '\n';
	    msg[msglen + 1] = '\0';
	}

	if (syslogmsg != 1)
	    write(xferlog, msg, strlen(msg));
	if (syslogmsg != 0) {
	    char *msgp = msg;
	    /*
	     * To preserve the behavior when the xferlog format was fixed, skip
	     * over the time string if the message starts with the local time.
	     */
	    if (strncmp(xferlog_format, "%T ", 3) == 0)
		msgp += 25;
	    syslog(LOG_INFO, "xferlog (send): %s", msgp);
	}
    }
    data = -1;
    pdata = -1;
  done:
    if (closefunc)
	(*closefunc) (fin);
}

void store(char *name, char *mode, int unique)
{
    FILE *fout, *din;
    struct stat st;
    int TransferIncomplete = 1;
    char *gunique(char *local);
    time_t start_time = time(NULL);

    struct aclmember *entry = NULL;

    int fdout;
    char realname[MAXPATHLEN];

#ifdef OVERWRITE
    int overwrite = 1;
    int exists = 0;

#endif /* OVERWRITE */

    int open_flags = 0;

#ifdef UPLOAD
    mode_t oldmask;
    uid_t uid;
    gid_t gid;
    uid_t oldid;
    int f_mode = -1, match_value = -1;
    int valid = 0;
    int ret, serrno;
    open_flags = (O_RDWR | O_CREAT |
		  ((mode != NULL && *mode == 'a') ? O_APPEND : O_TRUNC));
#endif /* UPLOAD */

    wu_realpath(name, realname, chroot_path);

#ifdef TRANSFER_COUNT
#ifdef TRANSFER_LIMIT
    if (((file_limit_data_in > 0) && (file_count_in >= file_limit_data_in))
	|| ((file_limit_data_total > 0) && (file_count_total >= file_limit_data_total))
      || ((data_limit_data_in > 0) && (data_count_in >= data_limit_data_in))
	|| ((data_limit_data_total > 0) && (data_count_total >= data_limit_data_total))) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to upload %s (Transfer limits exceeded)",
		       guestpw, remoteident, realname);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to upload %s (Transfer limits exceeded)",
		       pw->pw_name, remoteident, realname);
	reply(553, "Permission denied on server. (Transfer limits exceeded)");
	return;
    }
    if (((file_limit_raw_in > 0) && (xfer_count_in >= file_limit_raw_in))
	|| ((file_limit_raw_total > 0) && (xfer_count_total >= file_limit_raw_total))
	|| ((data_limit_raw_in > 0) && (byte_count_in >= data_limit_raw_in))
	|| ((data_limit_raw_total > 0) && (byte_count_total >= data_limit_raw_total))) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to upload %s (Transfer limits exceeded)",
		       guestpw, remoteident, realname);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to upload %s (Transfer limits exceeded)",
		       pw->pw_name, remoteident, realname);
	reply(553, "Permission denied on server. (Transfer limits exceeded)");
	return;
    }
#endif
#endif

    if (unique && stat(name, &st) == 0 &&
	(name = gunique(name)) == NULL)
	return;

    /*
     * check the filename, is it legal?
     */
    if ((fn_check(name)) <= 0) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to upload \"%s\" (path-filter)",
		       guestpw, remoteident, realname);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to upload \"%s\" (path-filter)",
		       pw->pw_name, remoteident, realname);
	return;
    }

#ifdef OVERWRITE
    /* if overwrite permission denied and file exists... then deny the user
     * permission to write the file. */
    while (getaclentry("overwrite", &entry) && ARG0 && ARG1 != NULL) {
	if (type_match(ARG1))
	    if (strcasecmp(ARG0, "yes") != 0) {
		overwrite = 0;
		open_flags |= O_EXCL;
	    }
    }

#ifdef PARANOID
    overwrite = 0;
#endif
    if (!stat(name, &st))
	exists = 1;

    if (!overwrite && exists) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to overwrite %s",
		       guestpw, remoteident, realname);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to overwrite %s",
		       pw->pw_name, remoteident, realname);
	reply(553, "%s: Permission denied on server. (Overwrite)", name);
	return;
    }
#endif /* OVERWRITE */

#ifdef UPLOAD
    if ((match_value = upl_check(name, &uid, &gid, &f_mode, &valid)) < 0) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to upload %s (upload denied)",
		       guestpw, remoteident, realname);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to upload %s (upload denied)",
		       pw->pw_name, remoteident, realname);
	return;
    }

    /* do not truncate the file if we are restarting */
    if (restart_point)
	open_flags &= ~O_TRUNC;

    /* if the user has an explicit new file mode, than open the file using
     * that mode.  We must take care to not let the umask affect the file
     * mode.
     * 
     * else open the file and let the default umask determine the file mode. */
    if (f_mode >= 0) {
	oldmask = umask(0000);
	fdout = open(name, open_flags, f_mode);
	umask(oldmask);
    }
    else
	fdout = open(name, open_flags, 0666);

    if (fdout < 0) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to upload %s (permissions)",
		       guestpw, remoteident, realname);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to upload %s (permissions)",
		       pw->pw_name, remoteident, realname);
	perror_reply(553, name);
	return;
    }
    /* if we have a uid and gid, then use them. */

#ifdef OVERWRITE
    if (!exists)
#endif
	if (valid > 0) {
	    oldid = geteuid();
	    if (uid != 0)
		(void) seteuid((uid_t) uid);
	    if ((uid == 0) || ((fchown(fdout, uid, gid)) < 0)) {
		chown_priv_on(0);
		ret = fchown(fdout, uid, gid);
		serrno = errno;
		chown_priv_off(oldid);
		if (ret < 0) {
		    errno = serrno;
		    perror_reply(550, "fchown");
		    return;
		}
	    }
	    else
		(void) seteuid(oldid);
	}
#endif /* UPLOAD */

    if (restart_point && (open_flags & O_APPEND) == 0)
	mode = "r+";

#ifdef UPLOAD
    fout = fdopen(fdout, mode);
#else
    fout = fopen(name, mode);
#endif /* UPLOAD */

    if (fout == NULL) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to upload %s (permissions)",
		       guestpw, remoteident, realname);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to upload %s (permissions)",
		       pw->pw_name, remoteident, realname);
	perror_reply(553, name);
	return;
    }
    if (restart_point && (open_flags & O_APPEND) == 0) {
	if (type == TYPE_A) {
	    register int c;
	    off_t i;

	    i = 0;
	    while (i++ < restart_point) {
		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 _FILE_OFFSET_BITS == 64
	    if (fseeko(fout, 0L, SEEK_CUR) < 0) {
#else
	    if (fseek(fout, 0L, SEEK_CUR) < 0) {
#endif
		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;
    TransferIncomplete = receive_data(din, fout);

    if (fstat(fileno(fout), &st) != 0) {
	/* shouldn't fail, but just in case */
	st.st_size = -1;
    }
    (void) fclose(din);
    if (TransferIncomplete == 0) {
	if (unique)
	    reply(226, "Transfer complete (unique file name:%s).", name);
	else
	    reply(226, "Transfer complete.");
    }

    fb_realpath(name, LastFileTransferred);

#ifdef MAIL_ADMIN
    if (anonymous && incmails > 0) {
	FILE *sck = NULL;

	unsigned char temp = 0, temp2 = 0;
	char pathname[MAXPATHLEN];
	while ((temp < mailservers) && (sck == NULL))
	    sck = SockOpen(mailserver[temp++], 25);
	if (sck == NULL) {
	    syslog(LOG_ERR, "Can't connect to a mailserver.");
	    goto mailfail;
	}
	if (Reply(sck) != 220) {
	    syslog(LOG_ERR, "Mailserver failed to initiate contact.");
	    goto mailfail;
	}
	if (Send(sck, "HELO localhost\r\n") != 250) {
	    syslog(LOG_ERR, "Mailserver doesn't understand HELO.");
	    goto mailfail;
	}
	if (Send(sck, "MAIL FROM: <%s>\r\n", email(mailfrom)) != 250) {
	    syslog(LOG_ERR, "Mailserver didn't accept MAIL FROM.");
	    goto mailfail;
	}
	for (temp = 0; temp < incmails; temp++) {
	    if (Send(sck, "RCPT TO: <%s>\r\n", email(incmail[temp])) == 250)
		temp2++;
	}
	if (temp2 == 0) {
	    syslog(LOG_ERR, "Mailserver didn't accept any RCPT TO.");
	    goto mailfail;
	}
	if (Send(sck, "DATA\r\n") != 354) {
	    syslog(LOG_ERR, "Mailserver didn't accept DATA.");
	    goto mailfail;
	}
	SockPrintf(sck, "From: wu-ftpd <%s>\r\n", mailfrom);
	SockPrintf(sck, "Subject: New file uploaded: %s\r\n\r\n", name);
	fb_realpath(name, pathname);
	SockPrintf(sck, "%s uploaded %s from %s.\r\nFile size is %" L_FORMAT ".\r\nPlease move the file where it belongs.\r\n", guestpw, pathname, remotehost, st.st_size);
	if (Send(sck, ".\r\n") != 250)
	    syslog(LOG_ERR, "Message rejected by mailserver.");
	if (Send(sck, "QUIT\r\n") != 221)
	    syslog(LOG_ERR, "Mailserver didn't accept QUIT.");
      mailfail:
	if (sck != NULL)
	    fclose(sck);
    }
#endif /* MAIL_ADMIN */

    if (log_incoming_xfers && (xferlog || syslogmsg)) {
	char msg[MAXXFERSTRLEN];	/* see extensions.h */
	int xfertime = time(NULL) - start_time;
	size_t msglen;

	if (!xfertime)
	    xfertime++;

	/* Gather transfer statistics */
	xfervalues.filename = name;
	xfervalues.filesize = st.st_size;
	xfervalues.transfer_bytes = byte_count;
	xfervalues.transfer_direction = 'i';
	xfervalues.transfer_type = (type == TYPE_A) ? 'a' : 'b';
	xfervalues.transfer_time = xfertime;
	xfervalues.restart_offset = restart_point;
	strlcpy(xfervalues.special_action, opt_string(0), MAXSPACTCHARS);
	xfervalues.access_mode = anonymous ? 'a' : (guest ? 'g' : 'r');
	xfervalues.auth = authenticated;
	xfervalues.completion = TransferIncomplete ? 'i' : 'c';

	xferdone = 1;
	msg_massage(xferlog_format, msg, sizeof(msg));
	xferdone = 0;

	/* Ensure msg always ends with '\n' */
	msglen = strlen(msg);
	if (msglen == sizeof(msg) - 1) {
	    msg[sizeof(msg) - 2] = '\n';
	    msg[sizeof(msg) - 1] = '\0';
	}
	else {
	    msg[msglen] = '\n';
	    msg[msglen + 1] = '\0';
	}

	if (syslogmsg != 1)
	    write(xferlog, msg, strlen(msg));
	if (syslogmsg != 0) {
	    char *msgp = msg;
	    /*
	     * To preserve the behavior when the xferlog format was fixed, skip
	     * over the time string if the message starts with the local time.
	     */
	    if (strncmp(xferlog_format, "%T ", 3) == 0)
		msgp += 25;
	    syslog(LOG_INFO, "xferlog (recv): %s", msgp);
	}
    }
    data = -1;
    pdata = -1;
  done:
    (void) fclose(fout);
}

FILE *getdatasock(char *mode)
{
    int s, on = 1, tries;

    if (data >= 0)
	return (fdopen(data, mode));
    port_priv_on(0);
    s = socket(SOCK_FAMILY(data_dest), SOCK_STREAM, 0);
    if (s < 0)
	goto bad;
    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
		   (char *) &on, sizeof(on)) < 0)
	goto bad;
    if (keepalive)
	(void) setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on));
    if (TCPwindowsize)
	(void) setsockopt(s, SOL_SOCKET, (*mode == 'w' ? SO_SNDBUF : SO_RCVBUF),
			  (char *) &TCPwindowsize, sizeof(TCPwindowsize));
    /* anchor socket to avoid multi-homing problems */
#ifdef INET6
    if (SOCK_FAMILY(data_dest) == SOCK_FAMILY(ctrl_addr))
	data_source = ctrl_addr;
    else if ((SOCK_FAMILY(data_dest) == AF_INET) && ctrl_v4mapped) {
	struct sockaddr_in6 *ctrl_sin6 = (struct sockaddr_in6 *)&ctrl_addr;
	struct sockaddr_in *data_sin = (struct sockaddr_in *)&data_source;

	SET_SOCK_FAMILY(data_source, AF_INET);
	memcpy(&data_sin->sin_addr, &ctrl_sin6->sin6_addr.s6_addr[12],
	       sizeof(struct in_addr));
    }
    else {
	memset(&data_source, 0, sizeof(struct sockaddr_in6));
	SET_SOCK_FAMILY(data_source, SOCK_FAMILY(data_dest));
	SET_SOCK_ADDR_ANY(data_source);
    }
#else
    data_source = ctrl_addr;
#endif
    SET_SOCK_PORT(data_source, data_port);

#if defined(VIRTUAL) && defined(CANT_BIND)	/* can't bind to virtual address */
    SET_SOCK_ADDR_ANY(data_source);
#endif
    for (tries = 1;; tries++) {
	if (bind(s, (struct sockaddr *) &data_source, SOCK_LEN(data_source)) >= 0)
	    break;
	if (errno != EADDRINUSE || tries > 10)
	    goto bad;
	sleep(tries);
    }
#if defined(M_UNIX) && !defined(_M_UNIX)	/* bug in old TCP/IP release */
    {
	struct linger li;
	li.l_onoff = 1;
	li.l_linger = 900;
	if (setsockopt(s, SOL_SOCKET, SO_LINGER,
		       (char *) &li, sizeof(struct linger)) < 0) {
	    syslog(LOG_WARNING, "setsockopt (SO_LINGER): %m");
	    goto bad;
	}
    }
#endif
    port_priv_off((uid_t) pw->pw_uid);

#ifdef INET6
    /* IP_TOS is an IPv4 socket option */
    if (SOCK_FAMILY(data_source) == AF_INET)
#endif
    if ((on = IPClassOfService("data")) >= 0) {
	if (setsockopt(s, IPPROTO_IP, IP_TOS, (char *) &on, sizeof(int)) < 0)
	    syslog(LOG_WARNING, "setsockopt (IP_TOS): %m");
    }
#ifdef TCP_NOPUSH
    /*
     * Turn off push flag to keep sender TCP from sending short packets
     * at the boundaries of each write().  Should probably do a SO_SNDBUF
     * to set the send buffer size as well, but that may not be desirable
     * in heavy-load situations.
     */
    on = 1;
    if (setsockopt(s, IPPROTO_TCP, TCP_NOPUSH, (char *) &on, sizeof on) < 0)
	syslog(LOG_WARNING, "setsockopt (TCP_NOPUSH): %m");
#endif

    return (fdopen(s, mode));
  bad:
    on = errno;			/* hold errno for return */
    port_priv_off((uid_t) pw->pw_uid);
    if (s != -1)
	(void) close(s);
    errno = on;
    return (NULL);
}

FILE *dataconn(char *name, off_t size, char *mode)
{
    char sizebuf[32];
    FILE *file;
    int retry = 0;
    int on = 1;
    int cval, serrno;
    int cos;
#ifdef THROUGHPUT
    int bps;
    double bpsmult;
#endif

    file_size = size;
    byte_count = 0;
    if (size != (off_t) - 1)
	(void) sprintf(sizebuf, " (%" L_FORMAT " bytes)", size);
    else
	(void) strcpy(sizebuf, "");
    if (pdata >= 0) {
	struct SOCKSTORAGE from;
	char dataaddr[MAXHOSTNAMELEN];
#if defined(UNIXWARE) || defined(AIX)
	size_t fromlen = sizeof(from);
#else
	int fromlen = sizeof(from);
#endif
	int s;
#ifdef FD_ZERO
	int rv;
#endif

	if (keepalive)
	    (void) setsockopt(pdata, SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on));
	if (TCPwindowsize)
	    (void) setsockopt(pdata, SOL_SOCKET, (*mode == 'w' ? SO_SNDBUF : SO_RCVBUF),
			    (char *) &TCPwindowsize, sizeof(TCPwindowsize));
#ifdef FD_ZERO
	do {
	    struct timeval timeout;
	    fd_set set;

	    FD_ZERO(&set);
	    FD_SET(pdata, &set);

	    timeout.tv_usec = 0;
	    timeout.tv_sec = timeout_accept;
#ifdef HPUX_SELECT
	    rv = select(pdata + 1, (int *) &set, NULL, NULL, &timeout);
#else
	    rv = select(pdata + 1, &set, (fd_set *) 0, (fd_set *) 0,
			(struct timeval *) &timeout);
#endif
	} while ((rv == -1) && (errno == EINTR));
	if ((rv != -1) && (rv != 0))
	    s = accept(pdata, (struct sockaddr *) &from, &fromlen);
	else
	    s = -1;
#else /* FD_ZERO */
	(void) signal(SIGALRM, alarm_signal);
	alarm(timeout_accept);
	s = accept(pdata, (struct sockaddr *) &from, &fromlen);
	alarm(0);
#endif
	if (s == -1) {
	    reply(425, "Can't open data connection.");
	    (void) close(pdata);
	    pdata = -1;
	    return (NULL);
	}
	(void) close(pdata);
	pdata = s;
#ifdef INET6
	/* IP_TOS is an IPv4 socket option */
	if (SOCK_FAMILY(from) == AF_INET)
#endif
	if ((cos = IPClassOfService("data")) >= 0)
	    (void) setsockopt(s, IPPROTO_IP, IP_TOS, (char *)&cos, sizeof(int));
	(void) strncpy(dataaddr, inet_stop(&from), sizeof(dataaddr));
	if (!pasv_allowed(dataaddr))
	    if (strcasecmp(dataaddr, remoteaddr) != 0) {
		/* 
		 * This will log when data connection comes from an address different
		 * than the control connection.
		 */
#ifdef FIGHT_PASV_PORT_RACE
		syslog(LOG_ERR, "%s of %s: data connect from %s for %s%s",
		       anonymous ? guestpw : pw->pw_name, remoteident,
		       dataaddr, name, sizebuf);
		reply(425, "Possible PASV port theft, cannot open data connection.");
		(void) close(pdata);
		pdata = -1;
		return (NULL);
#else
		syslog(LOG_NOTICE, "%s of %s: data connect from %s for %s%s",
		       anonymous ? guestpw : pw->pw_name, remoteident,
		       dataaddr, name, sizebuf);
#endif
	    }
#ifdef THROUGHPUT
	throughput_calc(name, &bps, &bpsmult);
	if (bps != -1) {
	    lreply(150, "Opening %s mode data connection for %s%s.",
		   type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
	    reply(150, "Restricting network throughput to %d bytes/s.", bps);
	}
	else
#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;
    if (SOCK_PORT(data_dest) == 0) {
	reply(500, "Can't build data connection: no PORT specified");
	return (NULL);
    }
    usedefault = 1;
    do {
	file = getdatasock(mode);
	if (file == NULL) {
	    reply(425, "Can't create data socket (%s,%d): %s.",
		  inet_stop(&data_source), ntohs(SOCK_PORT(data_source)),
			    strerror(errno));
	    return (NULL);
	}
	data = fileno(file);
	(void) signal(SIGALRM, alarm_signal);
	alarm(timeout_connect);
	cval = connect(data, (struct sockaddr *) &data_dest,
		       SOCK_LEN(data_dest));
	serrno = errno;
	alarm(0);
	if (cval == -1) {
	    /*
	     * When connect fails, the state of the socket is unspecified so
	     * it should be closed and a new socket created for each connection
	     * attempt. This also prevents denial of service problems when
	     * running on operating systems that only allow one non-connected
	     * socket bound to the same local address.
	     */
	    (void) fclose(file);
	    data = -1;
	    errno = serrno;
	    if ((errno == EADDRINUSE || errno == EINTR) && retry < swaitmax) {
		sleep((unsigned) swaitint);
		retry += swaitint;
	    }
	    else {
		perror_reply(425, "Can't build data connection");
		return (NULL);
	    }
	}
    } while (cval == -1);
    if (keepalive)
	(void) setsockopt(data, SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on));
    if (TCPwindowsize)
	(void) setsockopt(data, SOL_SOCKET, (*mode == 'w' ? SO_SNDBUF : SO_RCVBUF),
			  (char *) &TCPwindowsize, sizeof(TCPwindowsize));
#ifdef THROUGHPUT
    throughput_calc(name, &bps, &bpsmult);
    if (bps != -1) {
	lreply(150, "Opening %s mode data connection for %s%s.",
	       type == TYPE_A ? "ASCII" : "BINARY", name, sizebuf);
	reply(150, "Restricting network throughput to %d bytes/s.", bps);
    }
    else
#endif
	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. */

int
#ifdef THROUGHPUT
    send_data(char *name, FILE *instr, FILE *outstr, size_t blksize)
#else
    send_data(FILE *instr, FILE *outstr, size_t blksize)
#endif
{
    register int c, cnt = 0;
    static char *buf;
    int netfd, filefd;
#ifdef THROUGHPUT
    int bps;
    double bpsmult;
    time_t t1, t2;
#endif
#ifdef SENDFILE
    int use_sf = 0;
    size_t xferred;
    struct stat st;
    struct sendfilevec sfv;
#endif

    buf = NULL;
    if (wu_setjmp(urgcatch)) {
	draconian_FILE = NULL;
	alarm(0);
	transflag = 0;
#ifdef SIGPIPE
	(void) signal(SIGPIPE, lostconn);
#endif
	if (buf)
	    (void) free(buf);
	retrieve_is_data = 1;
	return (0);
    }
    transflag++;

#ifdef THROUGHPUT
    throughput_calc(name, &bps, &bpsmult);
#endif

    switch (type) {

    case TYPE_A:
#ifdef SIGPIPE
	/*
	 * Ignore SIGPIPE while sending data, necessary so lostconn() isn't
	 * called if we write to the data connection after the client has
	 * closed it.
	 */
	(void) signal(SIGPIPE, SIG_IGN);
#endif
	draconian_FILE = outstr;
	(void) signal(SIGALRM, draconian_alarm_signal);
	alarm(timeout_data);
#ifdef THROUGHPUT
	if (bps != -1)
	    t1 = time(NULL);
#endif
	while ((draconian_FILE != NULL) && ((c = getc(instr)) != EOF)) {
	    if (++byte_count % 4096 == 0)
		alarm(timeout_data);
	    if (c == '\n') {
		if (ferror(outstr))
		    goto data_err;
		if (++byte_count % 4096 == 0)
		    alarm(timeout_data);
#if defined(USE_GSS)
		if (sec_putc('\r', outstr) != '\r')
		    goto data_err;
#else
		(void) putc('\r', outstr);
#endif
#ifdef TRANSFER_COUNT
		if (retrieve_is_data) {
		    data_count_total++;
		    data_count_out++;
		}
		byte_count_total++;
		byte_count_out++;
#endif
	    }
#if defined(USE_GSS)
	    if (sec_putc(c, outstr) != c)
		goto data_err;
#else
	    (void) putc(c, outstr);
#endif

#ifdef TRANSFER_COUNT
	    if (retrieve_is_data) {
		data_count_total++;
		data_count_out++;
	    }
	    byte_count_total++;
	    byte_count_out++;
#endif
#ifdef THROUGHPUT
	    if (bps > 0 && (byte_count % bps) == 0) {
		t2 = time(NULL);
		if (t2 == t1)
		    sleep(1);
		t1 = time(NULL);
	    }
#endif
	}
#ifdef THROUGHPUT
	if (bps != -1)
	    throughput_adjust(name);
#endif
	if (draconian_FILE != NULL) {
	    alarm(timeout_data);
#if defined(USE_GSS)
	    if (sec_fflush(outstr) < 0)
		goto data_err;
#else
	    fflush(outstr);
#endif /* defined(USE_GSS) */
	}
	if (draconian_FILE != NULL) {
	    alarm(timeout_data);
	    socket_flush_wait(outstr);
	}
	transflag = 0;
	if (ferror(instr))
	    goto file_err;
	if ((draconian_FILE == NULL) || ferror(outstr))
	    goto data_err;
	draconian_FILE = NULL;
	alarm(0);
#ifdef SIGPIPE
	(void) signal(SIGPIPE, lostconn);
#endif
	reply(226, "Transfer complete.");
#ifdef TRANSFER_COUNT
	if (retrieve_is_data) {
	    file_count_total++;
	    file_count_out++;
	}
	xfer_count_total++;
	xfer_count_out++;
#endif
	retrieve_is_data = 1;
	return (1);

    case TYPE_I:
    case TYPE_L:
#ifdef THROUGHPUT
	if (bps != -1)
	    blksize = bps;
#endif
	netfd = fileno(outstr);
	filefd = fileno(instr);
#ifdef SENDFILE
	/* check the input file is a regular file */
	if ((fstat(filefd, &st) == 0) && ((st.st_mode & S_IFMT) == S_IFREG)) {
#if defined(USE_GSS)
	    if (gss_info.data_prot == PROT_C || !IS_GSSAUTH(cur_auth_type) ||
		!(gss_info.authstate & GSS_ADAT_DONE))
#endif /* defined(USE_GSS) */
	    {
		use_sf = 1;
		/*
		 * Use a private sfv_flag SFV_NOWAIT to tell sendfilev(),
		 * when zero-copy is enabled, not to wait for all data to be
		 * ACKed before returning. This is important for throughput
		 * performance when sendfilev() is called to send small piece
		 * at a time.
		 */
		sfv.sfv_flag = SFV_NOWAIT;
		sfv.sfv_fd = filefd;
		sfv.sfv_off = restart_point;
		sfv.sfv_len = blksize;
	    }
	}
	if (use_sf == 0)
#endif
	if ((buf = (char *) malloc(blksize)) == NULL) {
	    transflag = 0;
	    perror_reply(451, "Local resource failure: malloc");
	    retrieve_is_data = 1;
	    return (0);
	}
#ifdef SIGPIPE
	/*
	 * Ignore SIGPIPE while sending data, necessary so lostconn() isn't
	 * called if we write to the data connection after the client has
	 * closed it.
	 */
	(void) signal(SIGPIPE, SIG_IGN);
#endif
	draconian_FILE = outstr;
	(void) signal(SIGALRM, draconian_alarm_signal);
	alarm(timeout_data);
#ifdef THROUGHPUT
	if (bps != -1)
	    t1 = time(NULL);
#endif
	while ((draconian_FILE != NULL) && (
#ifdef SENDFILE
	       (use_sf && (cnt = sendfilev(netfd, &sfv, 1, &xferred)) > 0)
	       || (!use_sf &&
#endif
		   ((cnt = read(filefd, buf, blksize)) > 0 &&
#if defined(USE_GSS)
		    sec_write(netfd, buf, cnt) == cnt)
#else
		    write(netfd, buf, cnt) == cnt)
#endif /* defined(USE_GSS) */
#ifdef SENDFILE
		  )
#endif
	)) {
	    alarm(timeout_data);
#ifdef SENDFILE
	    sfv.sfv_off += cnt;
#endif
	    byte_count += cnt;
#ifdef TRANSFER_COUNT
	    if (retrieve_is_data) {
#ifdef RATIO
		if( freefile ) {
		    total_free_dl += cnt;
		}
#endif /* RATIO */
		data_count_total += cnt;
		data_count_out += cnt;
	    }
	    byte_count_total += cnt;
	    byte_count_out += cnt;
#endif
#ifdef THROUGHPUT
	    if (bps != -1) {
		t2 = time(NULL);
		if (t2 == t1)
		    sleep(1);
		t1 = time(NULL);
	    }
#endif /* THROUGHPUT */
	}
#ifdef THROUGHPUT
	if (bps != -1)
	    throughput_adjust(name);
#endif
#if defined(USE_GSS)
	if (sec_fflush(outstr) < 0)
	    goto data_err;
#endif
	transflag = 0;
	if (buf)
	    (void) free(buf);
	if (draconian_FILE != NULL) {
	    alarm(timeout_data);
	    socket_flush_wait(outstr);
	}
	if (cnt != 0) {
#ifdef SENDFILE
	    if (use_sf && cnt < 0 && errno == EPIPE)
		goto data_err;
#endif
	    if (cnt < 0)
		goto file_err;
	    goto data_err;
	}
	if (draconian_FILE == NULL)
	    goto data_err;
	draconian_FILE = NULL;
	alarm(0);
#ifdef SIGPIPE
	(void) signal(SIGPIPE, lostconn);
#endif
	reply(226, "Transfer complete.");
#ifdef TRANSFER_COUNT
	if (retrieve_is_data) {
	    file_count_total++;
	    file_count_out++;
	}
	xfer_count_total++;
	xfer_count_out++;
#endif
	retrieve_is_data = 1;
	return (1);
    default:
	transflag = 0;
	reply(550, "Unimplemented TYPE %d in send_data", type);
	retrieve_is_data = 1;
	return (0);
    }

  data_err:
    draconian_FILE = NULL;
    alarm(0);
    transflag = 0;
#ifdef SIGPIPE
    (void) signal(SIGPIPE, lostconn);
#endif
    perror_reply(426, "Data connection");
    retrieve_is_data = 1;
    return (0);

  file_err:
    draconian_FILE = NULL;
    alarm(0);
    transflag = 0;
#ifdef SIGPIPE
    (void) signal(SIGPIPE, lostconn);
#endif
    perror_reply(551, "Error on input file");
    retrieve_is_data = 1;
    return (0);
}

/* 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. */

int receive_data(FILE *instr, FILE *outstr)
{
    register int c;
    int rcnt = 0, n = 0, bare_lfs = 0;
    static char *buf;
    int netfd, filefd, wcnt;
#ifdef BUFFER_SIZE
    size_t buffer_size = BUFFER_SIZE;
#else
    size_t buffer_size = BUFSIZ * 16;
#endif

    buf = NULL;
    if (wu_setjmp(urgcatch)) {
	alarm(0);
	transflag = 0;
	if (buf)
	    (void) free(buf);
	return (-1);
    }
    transflag++;
    switch (type) {

    case TYPE_I:
    case TYPE_L:
#if defined(USE_GSS)
	if (GSSUSERAUTH_OK(gss_info))
	    buffer_size = gss_getinbufsz();
	else
#endif
	if (recvbufsz > 0)
	    buffer_size = recvbufsz;
	if ((buf = (char *) malloc(buffer_size)) == NULL) {
	    transflag = 0;
	    perror_reply(451, "Local resource failure: malloc");
	    return (-1);
	}
	netfd = fileno(instr);
	filefd = fileno(outstr);
	draconian_FILE = instr;
	(void) signal(SIGALRM, draconian_alarm_signal);
	alarm(timeout_data);

	while ((draconian_FILE != NULL) &&
#if defined(USE_GSS)
	    (rcnt = sec_read(netfd, buf, buffer_size)) > 0) {
#else
	    (rcnt = read(netfd, buf, buffer_size)) > 0) {
#endif
	    for (wcnt = 0; wcnt < rcnt; wcnt += n) {
		if ((n = write(filefd, &buf[wcnt], rcnt - wcnt)) == -1)
		    break;
	    }
	    byte_count += wcnt;
#ifdef TRANSFER_COUNT
	    data_count_total += wcnt;
	    data_count_in += wcnt;
	    byte_count_total += wcnt;
	    byte_count_in += wcnt;
#endif
	    if (n == -1)
		break;
	    alarm(timeout_data);
	}
	transflag = 0;
	(void) free(buf);
	if ((rcnt == -1) || (draconian_FILE == NULL))
	    goto data_err;
	if (n == -1)
	    goto file_err;
	draconian_FILE = NULL;
	alarm(0);
#ifdef TRANSFER_COUNT
	file_count_total++;
	file_count_in++;
	xfer_count_total++;
	xfer_count_in++;
#endif
	return (0);

    case TYPE_E:
	reply(553, "TYPE E not implemented.");
	transflag = 0;
	return (-1);

    case TYPE_A:
	draconian_FILE = instr;
	(void) signal(SIGALRM, draconian_alarm_signal);
	alarm(timeout_data);
	while ((draconian_FILE != NULL) &&
#if defined(USE_GSS)
	    ((c = sec_getc(instr)) != EOF)
#else
	    ((c = getc(instr)) != EOF)
#endif
	) {
	    if (++byte_count % 4096 == 0)
		alarm(timeout_data);
	    if (c == '\n')
		bare_lfs++;
	    while (c == '\r') {
		if (ferror(outstr))
		    goto file_err;
		alarm(timeout_data);
		if (draconian_FILE != NULL) {
#if defined(USE_GSS)
		    if ((c = sec_getc(instr)) != '\n')
#else
		    if ((c = getc(instr)) != '\n')
#endif
			(void) putc('\r', outstr);
#ifdef TRANSFER_COUNT
		    data_count_total++;
		    data_count_in++;
		    byte_count_total++;
		    byte_count_in++;
#endif
		    if (c == EOF)	/* null byte fix, noid@cyborg.larc.nasa.gov */
			goto contin2;
		    if (++byte_count % 4096 == 0)
			alarm(timeout_data);
		}
	    }
	    (void) putc(c, outstr);
#ifdef TRANSFER_COUNT
	    data_count_total++;
	    data_count_in++;
	    byte_count_total++;
	    byte_count_in++;
#endif
	  contin2:;
	}
	fflush(outstr);
	if ((draconian_FILE == NULL) || ferror(instr))
	    goto data_err;
	if (ferror(outstr))
	    goto file_err;
	transflag = 0;
	draconian_FILE = NULL;
	alarm(0);
	if (bare_lfs) {
	    lreply(226, "WARNING! %d bare linefeeds received in ASCII mode", bare_lfs);
	    lreply(0, "   File may not have transferred correctly.");
	}
#ifdef TRANSFER_COUNT
	file_count_total++;
	file_count_in++;
	xfer_count_total++;
	xfer_count_in++;
#endif
	return (0);
    default:
	reply(550, "Unimplemented TYPE %d in receive_data", type);
	transflag = 0;
	return (-1);
    }

  data_err:
    draconian_FILE = NULL;
    alarm(0);
    transflag = 0;
    perror_reply(426, "Data Connection");
    return (-1);

  file_err:
    draconian_FILE = NULL;
    alarm(0);
    transflag = 0;
    perror_reply(452, "Error writing file");
    return (-1);
}

void statfilecmd(char *filename)
{
#ifndef INTERNAL_LS
    char line[BUFSIZ * 2], *ptr;
    FILE *fin;
    int c;
#endif /* ! INTERNAL_LS */

    fixpath(filename);
    if (filename[0] == '\0')
	filename = ".";
#ifndef INTERNAL_LS
    if (anonymous && dolreplies)
	(void) snprintf(line, sizeof(line), ls_long, filename);
    else
	(void) snprintf(line, sizeof(line), ls_short, filename);
    fin = ftpd_popen(line, "r", 0);
#endif /* ! INTERNAL_LS */
    lreply(213, "status of %s:", filename);
#ifndef INTERNAL_LS
    /*
       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;
       }
       (void) putc('\r', stdout);
       }
       (void) putc(c, stdout);
       }
     */
    while (fgets(line, sizeof(line), fin) != NULL) {
	if ((ptr = strchr(line, '\n')))		/* clip out unnecessary newline */
	    *ptr = '\0';
	lreply(0, "%s", line);
    }
    (void) ftpd_pclose(fin);
#else /* INTERNAL_LS */
    ls_dir(filename, 1, 0, 1, 0, 1, stdout);
#endif /* INTERNAL_LS */
    reply(213, "End of Status");
}

void statcmd(void)
{
    struct SOCKSTORAGE *sin;
    u_char *a, *p;
    unsigned short port;
#ifdef INET6
    int isv4 = 0;
#endif

    lreply(211, "%s FTP server status:", hostname);
    lreply(0, "     %s", version);
    if (nameserved)
	lreply(0, "     Connected to %s (%s)", remotehost, remoteaddr);
    else
	lreply(0, "     Connected to %s", remotehost);

    if (logged_in) {
	if (anonymous)
	    lreply(0, "     Logged in anonymously");
	else
	    lreply(0, "     Logged in as %s", pw->pw_name);
    }
    else if (askpasswd)
	lreply(0, "     Waiting for password");
    else
	lreply(0, "     Waiting for user name");

    if (type == TYPE_L)
#ifdef NBBY
	lreply(0, "     TYPE: %s %d; STRUcture: %s; transfer MODE: %s",
	       typenames[type], NBBY, strunames[stru], modenames[mode]);
#else
	lreply(0, "     TYPE: %s %d; STRUcture: %s; transfer MODE: %s",
	       typenames[type], bytesize, strunames[stru], modenames[mode]);
#endif /* NBBY */
    else
	lreply(0, "     TYPE: %s%s%s; STRUcture: %s; transfer MODE: %s",
	       typenames[type], (type == TYPE_A || type == TYPE_E) ?
	       ", FORM: " : "", (type == TYPE_A || type == TYPE_E) ?
	       formnames[form] : "", strunames[stru], modenames[mode]);
    if (data != -1)
	lreply(0, "     Data connection open");
    else if (pdata != -1 || usedefault == 0) {
	if (usedefault == 0) {
	    sin = &data_dest;
	    port = SOCK_PORT(data_dest);
	}
	else {
	    port = SOCK_PORT(pasv_addr);
	    if (route_vectored)
		sin = &vect_addr;
	    else
		sin = &pasv_addr;
	}
	a = (u_char *) SOCK_ADDR(*sin);
	p = (u_char *) &port;
#define UC(b) (((int) b) & 0xff)
#ifdef INET6
	if (SOCK_FAMILY(*sin) == AF_INET)
	    isv4 = 1;
	else if (IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)sin)->sin6_addr))
	{
	    isv4 = 1;
	    a += 12; /* move to the IPv4 part of an IPv4-mapped IPv6 address */
	}
	if (epsv_all)
	    lreply(0, "     EPSV only mode (EPSV ALL)");
	if (isv4 && !epsv_all)
#endif
	    lreply(0, "     %s (%d,%d,%d,%d,%d,%d)",
		   usedefault == 0 ? "PORT" : "PASV",
		   UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]), UC(p[0]), UC(p[1]));
#ifdef INET6
	lreply(0, "     %s (|%d|%s|%d|)", usedefault == 0 ? "EPRT" : "EPSV",
	       isv4 ? 1 : 2, inet_stop(sin), ntohs(port));
	if (!epsv_all)
	    if (isv4)
		lreply(0, "     %s (4,4,%d,%d,%d,%d,2,%d,%d)",
		       usedefault == 0 ? "LPRT" : "LPSV",
		       UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
		       UC(p[0]), UC(p[1]));
	    else
		lreply(0, "     %s (6,16,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,"
		       "%d,%d,%d,%d,2,%d,%d)",
		       usedefault == 0 ? "LPRT" : "LPSV",
		       UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
		       UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]),
		       UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]),
		       UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]),
		       UC(p[0]), UC(p[1]));
#endif
#undef UC
    }
    else
	lreply(0, "     No data connection");
#ifdef TRANSFER_COUNT
    lreply(0, "     %" L_FORMAT " data bytes received in %d files", data_count_in, file_count_in);
    lreply(0, "     %" L_FORMAT " data bytes transmitted in %d files", data_count_out, file_count_out);
    lreply(0, "     %" L_FORMAT " data bytes total in %d files", data_count_total, file_count_total);
    lreply(0, "     %" L_FORMAT " traffic bytes received in %d transfers", byte_count_in, xfer_count_in);
    lreply(0, "     %" L_FORMAT " traffic bytes transmitted in %d transfers", byte_count_out, xfer_count_out);
    lreply(0, "     %" L_FORMAT " traffic bytes total in %d transfers", byte_count_total, xfer_count_total);
#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 */
}

#define USE_REPLY_NOTFMT	(1<<1)	/* fmt is not a printf fmt (KLUDGE) */
#define USE_REPLY_LONG		(1<<2)	/* this is a long reply; use a - */

void vreply(long flags, int n, char *fmt, va_list ap)
{
    char buf[BUFSIZ * 16];

    flags &= USE_REPLY_NOTFMT | USE_REPLY_LONG;

    if (n)	/* if numeric is 0, don't output one; use n==0 in place of printf's */
	sprintf(buf, "%03d%c", n, flags & USE_REPLY_LONG ? '-' : ' ');

    /* This is somewhat of a kludge for autospout.  I personally think that
     * autospout should be done differently, but that's not my department. -Kev
     */
    if (flags & USE_REPLY_NOTFMT)
	snprintf(buf + (n ? 4 : 0), n ? sizeof(buf) - 4 : sizeof(buf), "%s", fmt);
    else
	vsnprintf(buf + (n ? 4 : 0), n ? sizeof(buf) - 4 : sizeof(buf), fmt, ap);

#if defined(USE_GSS)
    if (IS_GSSAUTH(cur_auth_type) &&
	(gss_info.authstate & GSS_ADAT_DONE) &&
	gss_info.ctrl_prot != PROT_C) {
	if (buf[strlen(buf)-1] != '\n')
		strlcat(buf, "\r\n", sizeof(buf));
	(void) sec_reply(buf, sizeof(buf), n);
    }
#endif

    if (debug)			/* debugging output :) */
	syslog(LOG_DEBUG, "<--- %s", buf);

    /* Yes, you want the debugging output before the client output; wrapping
     * stuff goes here, you see, and you want to log the cleartext and send
     * the wrapped text to the client.
     */

    printf("%s\r\n", buf);	/* and send it to the client */
#ifdef TRANSFER_COUNT
    byte_count_total += strlen(buf);
    byte_count_out += strlen(buf);
#endif
    /*
     * We dont need to worry about "sec_fflush" here since "sec_reply"
     * already wrapped the reply if necessary.
     */
    fflush(stdout);
}

void reply(int n, char *fmt,...)
{
    VA_LOCAL_DECL

	if (autospout != NULL) {	/* deal with the autospout stuff... */
	char *p, *ptr = autospout;

	while (*ptr) {
	    if ((p = strchr(ptr, '\n')) != NULL)	/* step through line by line */
		*p = '\0';

	    /* send a line...(note that this overrides dolreplies!) */
	    vreply(USE_REPLY_LONG | USE_REPLY_NOTFMT, n, ptr, ap);

	    if (p)
		ptr = p + 1;	/* set to the next line... (\0 is handled in the while) */
	    else
		break;		/* oh, we're done; drop out of the loop */
	}

	if (autospout_free) {	/* free autospout if necessary */
	    (void) free(autospout);
	    autospout_free = 0;
	}
	autospout = 0;		/* clear the autospout */
    }

    VA_START(fmt);

    /* send the reply */
    vreply(0L, n, fmt, ap);

    VA_END;
}

void lreply(int n, char *fmt,...)
{
    VA_LOCAL_DECL

    if (!dolreplies)	/* prohibited from doing long replies? */
	return;

    VA_START(fmt);

    /* send the reply */
    vreply(USE_REPLY_LONG, n, fmt, ap);

    VA_END;
}

void ack(char *s)
{
    reply(250, "%s command successful.", s);
}

void nack(char *s)
{
    reply(502, "%s command not implemented.", s);
}

void yyerror(char *s)
{
    char *cp;
    if (s == NULL || yyerrorcalled != 0)
	return;
    if ((cp = strchr(cbuf, '\n')) != NULL)
	*cp = '\0';
    reply(500, "'%s': command not understood.", cbuf);
    yyerrorcalled = 1;
    return;
}

void delete(char *name)
{
    struct stat st;
    char realname[MAXPATHLEN];

    /*
     * delete permission?
     */

    wu_realpath(name, realname, chroot_path);

    if ((del_check(name)) == 0) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to delete %s",
		       guestpw, remoteident, realname);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to delete %s",
		       pw->pw_name, remoteident, realname);
	return;
    }

    if (lstat(name, &st) < 0) {
	perror_reply(550, name);
	return;
    }
    if ((st.st_mode & S_IFMT) == S_IFDIR) {
	uid_t uid;
	gid_t gid;
	int d_mode;
	int valid;

	/*
	 * check the directory, can we rmdir here?
	 */
	if ((dir_check(name, &uid, &gid, &d_mode, &valid)) <= 0) {
	    if (log_security)
		if (anonymous)
		    syslog(LOG_NOTICE, "anonymous(%s) of %s tried to delete directory %s",
			   guestpw, remoteident, realname);
		else
		    syslog(LOG_NOTICE, "%s of %s tried to delete directory %s",
			   pw->pw_name, remoteident, realname);
	    return;
	}

	if (rmdir(name) < 0) {
	    if (log_security)
		if (anonymous)
		    syslog(LOG_NOTICE, "anonymous(%s) of %s tried to delete directory %s (permissions)",
			   guestpw, remoteident, realname);
		else
		    syslog(LOG_NOTICE, "%s of %s tried to delete directory %s (permissions)",
			   pw->pw_name, remoteident, realname);
	    perror_reply(550, name);
	    return;
	}
	goto done;
    }
    if (unlink(name) < 0) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to delete %s (permissions)",
		       guestpw, remoteident, realname);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to delete %s (permissions)",
		       pw->pw_name, remoteident, realname);
	perror_reply(550, name);
	return;
    }
  done:
    {
	char path[MAXPATHLEN];

	wu_realpath(name, path, chroot_path);

	if (log_security)
	    if ((st.st_mode & S_IFMT) == S_IFDIR)
		if (anonymous) {
		    syslog(LOG_NOTICE, "%s of %s deleted directory %s", guestpw, remoteident, path);
		}
		else {
		    syslog(LOG_NOTICE, "%s of %s deleted directory %s", pw->pw_name,
			   remoteident, path);
		}
	    else if (anonymous) {
		syslog(LOG_NOTICE, "%s of %s deleted %s", guestpw,
		       remoteident, path);
	    }
	    else {
		syslog(LOG_NOTICE, "%s of %s deleted %s", pw->pw_name,
		       remoteident, path);
	    }
    }

    ack("DELE");
}

void cwd(char *path)
{
    struct aclmember *entry = NULL;
    char cdpath[MAXPATHLEN];

    if (chdir(path) < 0) {
	/* alias checking */
	while (getaclentry("alias", &entry) && ARG0 && ARG1 != NULL) {
	    if (!strcasecmp(ARG0, path)) {
		if (chdir(ARG1) < 0)
		    perror_reply(550, path);
		else {
		    show_message(250, C_WD);
		    show_readme(250, C_WD);
		    ack("CWD");
		}
		return;
	    }
	}
	/* check for "cdpath" directories. */
	entry = (struct aclmember *) NULL;
	while (getaclentry("cdpath", &entry) && ARG0 != NULL) {
	    snprintf(cdpath, sizeof cdpath, "%s/%s", ARG0, path);
	    if (chdir(cdpath) >= 0) {
		show_message(250, C_WD);
		show_readme(250, C_WD);
		ack("CWD");
		return;
	    }
	}
	perror_reply(550, path);
    }
    else {
	show_message(250, C_WD);
	show_readme(250, C_WD);
	ack("CWD");
    }
}

void makedir(char *name)
{
    uid_t uid;
    gid_t gid;
    int d_mode;
    mode_t oldumask;
    int valid;
    int ret, serrno;
    uid_t oldid;
    char path[MAXPATHLEN + 1];	/* for realpath() later  - cky */
    char realname[MAXPATHLEN];

    wu_realpath(name, realname, chroot_path);
    /*
     * check the directory, can we mkdir here?
     */
    if ((dir_check(name, &uid, &gid, &d_mode, &valid)) <= 0) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to create directory %s",
		       guestpw, remoteident, realname);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to create directory %s",
		       pw->pw_name, remoteident, realname);
	return;
    }

    /*
     * check the filename, is it legal?
     */
    if ((fn_check(name)) <= 0) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to create directory %s (path-filter)",
		       guestpw, remoteident, realname);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to create directory %s (path-filter)",
		       pw->pw_name, remoteident, realname);
	return;
    }

    oldumask = umask(0000);
    if (valid <= 0) {
	d_mode = 0777;
	umask(oldumask);
    }

    if (mkdir(name, d_mode) < 0) {
	if (errno == EEXIST) {
	    if (log_security)
		if (anonymous)
		    syslog(LOG_NOTICE, "anonymous(%s) of %s tried to create directory %s (exists)",
			   guestpw, remoteident, realname);
		else
		    syslog(LOG_NOTICE, "%s of %s tried to create directory %s (exists)",
			   pw->pw_name, remoteident, realname);
	    fb_realpath(name, path);
	    reply(521, "\"%s\" directory exists", path);
	}
	else {
	    if (log_security)
		if (anonymous)
		    syslog(LOG_NOTICE, "anonymous(%s) of %s tried to create directory %s (permissions)",
			   guestpw, remoteident, realname);
		else
		    syslog(LOG_NOTICE, "%s of %s tried to create directory %s (permissions)",
			   pw->pw_name, remoteident, realname);
	    perror_reply(550, name);
	}
	umask(oldumask);
	return;
    }
    umask(oldumask);
    if (valid > 0) {
	oldid = geteuid();
	if (uid != 0)
	    (void) seteuid((uid_t) uid);
	if ((uid == 0) || ((chown(name, uid, gid)) < 0)) {
	    chown_priv_on(0);
	    ret = chown(name, uid, gid);
	    serrno = errno;
	    chown_priv_off(oldid);
	    if (ret < 0) {
		errno = serrno;
		perror_reply(550, "chown");
		return;
	    }
	}
	else
	    (void) seteuid(oldid);
    }
    wu_realpath(name, path, chroot_path);
    if (log_security)
	if (anonymous) {
	    syslog(LOG_NOTICE, "%s of %s created directory %s", guestpw, remoteident, path);
	}
	else {
	    syslog(LOG_NOTICE, "%s of %s created directory %s", pw->pw_name,
		   remoteident, path);
	}
    fb_realpath(name, path);
    /* According to RFC 959:
     *   The 257 reply to the MKD command must always contain the
     *   absolute pathname of the created directory.
     * This is implemented here using similar code to the PWD command.
     * XXX - still need to do `quote-doubling'.
     */
    reply(257, "\"%s\" new directory created.", path);
}

void removedir(char *name)
{
    uid_t uid;
    gid_t gid;
    int d_mode;
    int valid;
    char realname[MAXPATHLEN];

    wu_realpath(name, realname, chroot_path);

    /*
     * delete permission?
     */

    if ((del_check(name)) == 0)
	return;
    /*
     * check the directory, can we rmdir here?
     */
    if ((dir_check(name, &uid, &gid, &d_mode, &valid)) <= 0) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to remove directory %s",
		       guestpw, remoteident, realname);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to remove directory %s",
		       pw->pw_name, remoteident, realname);
	return;
    }

    if (rmdir(name) < 0) {
	if (errno == EBUSY)
	    perror_reply(450, name);
	else {
	    if (log_security)
		if (anonymous)
		    syslog(LOG_NOTICE, "anonymous(%s) of %s tried to remove directory %s (permissions)",
			   guestpw, remoteident, realname);
		else
		    syslog(LOG_NOTICE, "%s of %s tried to remove directory %s (permissions)",
			   pw->pw_name, remoteident, realname);
	    perror_reply(550, name);
	}
    }
    else {
	char path[MAXPATHLEN];

	wu_realpath(name, path, chroot_path);

	if (log_security)
	    if (anonymous) {
		syslog(LOG_NOTICE, "%s of %s deleted directory %s", guestpw, remoteident, path);
	    }
	    else {
		syslog(LOG_NOTICE, "%s of %s deleted directory %s", pw->pw_name,
		       remoteident, path);
	    }
	ack("RMD");
    }
}

void pwd(void)
{
    char path[MAXPATHLEN + 1];
    char rhome[MAXPATHLEN + 1];
    char *rpath = path;		/* Path to return to client */
    int pathlen;
#ifndef MAPPING_CHDIR
#ifdef HAVE_GETCWD
    extern char *getcwd();
#else
    extern char *getwd(char *);
#endif
#endif /* MAPPING_CHDIR */

#ifdef HAVE_GETCWD
    if (getcwd(path, MAXPATHLEN) == (char *) NULL)
#else
    if (getwd(path) == (char *) NULL)
#endif
/* Dink!  If you couldn't get the path and the buffer is now likely to
   be undefined, why are you trying to PRINT it?!  _H*
   reply(550, "%s.", path); */
    {
	fb_realpath(".", path);	/* realpath_on_steroids can deal */
    }
    /* relative to home directory if restricted_user */
    if (restricted_user) {
	/*
	 * Re-adjust real path because previous call to getXwd() did
	 * not resolve symlink.
	 */
	fb_realpath(".", path);
	fb_realpath(home, rhome);
	pathlen = strlen(rhome);
	if (pathlen && rhome[pathlen - 1] == '/')
	    pathlen--;
	rpath = rpath + pathlen;
	if (!*rpath)
	    strcpy(rpath, "/");
    }
    reply(257, "\"%s\" is current directory.", rpath);
}

char *renamefrom(char *name)
{
    struct stat st;

    if (lstat(name, &st) < 0) {
	perror_reply(550, name);
	return ((char *) 0);
    }
    reply(350, "File exists, ready for destination name");
    return (name);
}

void renamecmd(char *from, char *to)
{
    int allowed = (anonymous ? 0 : 1);
    char realfrom[MAXPATHLEN];
    char realto[MAXPATHLEN];
    struct aclmember *entry = NULL;
#ifdef PARANOID
    struct stat st;
#endif
    wu_realpath(from, realfrom, chroot_path);
    wu_realpath(to, realto, chroot_path);
    /*
     * check the filename, is it legal?
     */
    if ((fn_check(to)) == 0) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to rename %s to \"%s\" (path-filter)",
		       guestpw, remoteident, realfrom, realto);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to rename %s to \"%s\" (path-filter)",
		       pw->pw_name, remoteident, realfrom, realto);
	return;
    }

    /* 
     * if rename permission denied and file exists... then deny the user
     * permission to rename the file. 
     */
    while (getaclentry("rename", &entry) && ARG0 && ARG1 != NULL) {
	if (type_match(ARG1))
	    if (anonymous) {
		if (*ARG0 == 'y')
		    allowed = 1;
	    }
	    else if (*ARG0 == 'n')
		allowed = 0;
    }
    if (!allowed) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to rename %s to %s",
		       guestpw, remoteident, realfrom, realto);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to rename %s to %s",
		       pw->pw_name, remoteident, realfrom, realto);
	reply(553, "%s: Permission denied on server. (rename)", from);
	return;
    }


#ifdef PARANOID
/* Almost forgot about this.  Don't allow renaming TO existing files --
   otherwise someone can rename "trivial" to "warez", and "warez" is gone!
   XXX: This part really should do the same "overwrite" check as store(). */
    if (!stat(to, &st)) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to rename %s to %s",
		       guestpw, remoteident, realfrom, realto);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to rename %s to %s",
		       pw->pw_name, remoteident, realfrom, realto);
	reply(550, "%s: Permission denied on server. (rename)", to);
	return;
    }
#endif
    if (rename(from, to) < 0) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to rename %s to %s",
		       guestpw, remoteident, realfrom, realto);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to rename %s to %s",
		       pw->pw_name, remoteident, realfrom, realto);
	perror_reply(550, "rename");
    }
    else {
	char frompath[MAXPATHLEN];
	char topath[MAXPATHLEN];

	wu_realpath(from, frompath, chroot_path);
	wu_realpath(to, topath, chroot_path);

	if (log_security)
	    if (anonymous) {
		syslog(LOG_NOTICE, "%s of %s renamed %s to %s", guestpw, remoteident, frompath, topath);
	    }
	    else {
		syslog(LOG_NOTICE, "%s of %s renamed %s to %s", pw->pw_name,
		       remoteident, frompath, topath);
	    }
	ack("RNTO");
    }
}

void dolog(struct SOCKSTORAGE *sin)
{
    char *blah;
    int rval;

    blah = inet_stop(sin);
    (void) strncpy(remoteaddr, blah, sizeof(remoteaddr));
    nameserved = 0;
    (void) strncpy(remotehost, remoteaddr, sizeof(remotehost));

    rhlookup = rhostlookup(remoteaddr);
    if (rhlookup) {
	if (!strcasecmp(remoteaddr, "0.0.0.0")) {
	    nameserved = 1;
	    strncpy(remotehost, "localhost", sizeof(remotehost));
	}
	else {
#ifdef DNS_TRYAGAIN
	    int num_dns_tries = 0;
	    /*
	     * 27-Apr-93    EHK/BM
	     * far away connections might take some time to get their IP address
	     * resolved. That's why we try again -- maybe our DNS cache has the
	     * PTR-RR now. This code is sloppy. Far better is to check what the
	     * resolver returned so that in case of error, there's no need to
	     * try again.
	     */
  dns_again:
#endif /* DNS_TRYAGAIN */

	    rval = wu_gethostbyaddr(sin, remotehost, sizeof(remotehost));

#ifdef DNS_TRYAGAIN
	    if (!rval && ++num_dns_tries <= 1) {
		sleep(3);
		goto dns_again;		/* try DNS lookup once more     */
	    }
#endif /* DNS_TRYAGAIN */

	    if (rval)
		nameserved = 1;
	}
    }

    remotehost[sizeof(remotehost) - 1] = '\0';
    sprintf(proctitle, "%s: connected", remotehost);
    setproctitle("%s", proctitle);

    wu_authenticate();
/* Create a composite source identification string, to improve the logging
 * when RFC 931 is being used. */
    {
	int n = 20 + strlen(remotehost) + strlen(remoteaddr) +
	(authenticated ? strlen(authuser + 5) : 0);
	if ((remoteident = malloc(n)) == NULL) {
	    syslog(LOG_ERR, "malloc: %m");
#ifndef DEBUG
	    exit(1);
#endif
	}
	else if (authenticated)
	    sprintf(remoteident, "%s @ %s [%s]",
		    authuser, remotehost, remoteaddr);
	else
	    sprintf(remoteident, "%s [%s]", remotehost, remoteaddr);
    }
#ifdef DAEMON
    if (be_daemon && logging)
	syslog(LOG_INFO, "connection from %s", remoteident);
#else
#if 0				/* this is redundant unless the caller doesn't do *anything*, and
				   tcpd will pick it up and deal with it better anyways. _H */
    if (logging)
	syslog(LOG_INFO, "connection from %s", remoteident);
#endif
#endif
}

/* Record logout in wtmp file and exit with supplied status. */

void dologout(int status)
{
    /*
     * Prevent reception of SIGURG from resulting in a resumption
     * back to the main program loop.
     */
    transflag = 0;

    /*
     * Cancel any pending alarm request, reception of SIGALRM would cause
     * dologout() to be called again from the SIGALRM handler toolong().
     */
    (void) alarm(0);

    if (logged_in) {
	delay_signaling();	/* we can't allow any signals while euid==0: kinch */
#if defined(SOLARIS_BSM_AUDIT) && !defined(SOLARIS_NO_AUDIT_FTPD_LOGOUT)
	audit_ftpd_logout();
#endif
	(void) seteuid((uid_t) 0);
	if (wtmp_logging)
	    wu_logwtmp(ttyline, pw->pw_name, remotehost, 0);
#ifdef USE_PAM
	if (!anonymous && pamh) {
	    (void) pam_close_session(pamh, 0);
	    (void) pam_end(pamh, PAM_SUCCESS); 
	    pamh = (pam_handle_t *)0;
	}
#endif
    }
    if (logging)
	syslog(LOG_INFO, "FTP session closed");
    if (xferlog)
	close(xferlog);
    acl_remove();
    if (data >= 0)
	close(data);
    if (pdata >= 0)
	close(pdata);
#ifdef AFS_AUTH
    ktc_ForgetAllTokens();
#endif
    /* beware of flushing buffers after a SIGPIPE */
    _exit(status);
}

SIGNAL_TYPE myoob(int sig)
{
    char *cp;
#ifdef SIGPIPE
    void (*pipe_handler)();
#endif

    /* only process if transfer occurring */
    if (!transflag) {
#ifdef SIGURG
	(void) signal(SIGURG, myoob);
#endif
	return;
    }
#ifdef SIGPIPE
    pipe_handler = signal(SIGPIPE, lostconn);
#endif
    cp = tmpline;
    if (wu_getline(cp, sizeof(tmpline) - 1, stdin) == NULL) {
	reply(221, "You could at least say goodbye.");
	dologout(0);
    }
    upper(cp);
    if (strcasecmp(cp, "ABOR\r\n") == 0) {
	tmpline[0] = '\0';
	reply(426, "Transfer aborted. Data connection closed.");
	reply(226, "Abort successful");
#ifdef SIGPIPE
	(void) signal(SIGPIPE, pipe_handler);
#endif
#ifdef SIGURG
	(void) signal(SIGURG, myoob);
#endif
	if (ftwflag > 0) {
	    ftwflag++;
	    return;
	}
	wu_longjmp(urgcatch, 1);
    }
    if (strcasecmp(cp, "STAT\r\n") == 0) {
	tmpline[0] = '\0';
	if (file_size != (off_t) - 1)
	    reply(213, "Status: %" L_FORMAT " of %" L_FORMAT " bytes transferred",
		  byte_count, file_size);
	else
	    reply(213, "Status: %" L_FORMAT " bytes transferred", byte_count);
    }
#ifdef SIGPIPE
    (void) signal(SIGPIPE, pipe_handler);
#endif
#ifdef SIGURG
    (void) signal(SIGURG, myoob);
#endif
}

/* 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 passive(int passive_mode, int proto)
{
    /* First prime number after 2^n where 4 <= n <= 16 */
    static int primes[] = {17,37,67,131,257,521,1031,2053,4099,8209,16411,32771,65537,0};
    static int prime = 0;
    static int range;
#if defined(UNIXWARE) || defined(AIX)
    size_t len;
#else
    int len;
#endif
    int bind_error, serrno;
    int on = 1;
    int i, j, inc, val;
    unsigned short port;
    register char *p, *a;
    struct SOCKSTORAGE *reply_addr;
    struct timeval tv;
#ifdef INET6
    int isv4 = 0;
#endif

/* H* fix: if we already *have* a passive socket, close it first.  Prevents
   a whole variety of entertaining clogging attacks. */
    if (pdata >= 0) {
	close(pdata);
	pdata = -1;
    }
    if (!logged_in) {
	reply(530, "Login with USER first.");
	return;
    }
#ifdef INET6
    switch (proto) {
    case 0:
	if ((passive_mode == TYPE_PASV) && (SOCK_FAMILY(ctrl_addr) == AF_INET6)
	    && !ctrl_v4mapped) {
	    reply(501, "Network protocol mismatch");
	    return;
	}
	else
	    pasv_addr = ctrl_addr;
	break;
    case 1:
	if (SOCK_FAMILY(ctrl_addr) == AF_INET)
	    pasv_addr = ctrl_addr;
	else if (ctrl_v4mapped) {
	    struct sockaddr_in6 *ctrl_sin6 = (struct sockaddr_in6 *)&ctrl_addr;
	    struct sockaddr_in *pasv_sin = (struct sockaddr_in *)&pasv_addr;

	    SET_SOCK_FAMILY(pasv_addr, AF_INET);
	    memcpy(&pasv_sin->sin_addr, &ctrl_sin6->sin6_addr.s6_addr[12],
		   sizeof(struct in_addr));
	}
	else {
	    reply(522, "Network protocol mismatch, use (2)");
	    return;
	}
	break;
    case 2:
	if ((SOCK_FAMILY(ctrl_addr) == AF_INET6) && !ctrl_v4mapped)
	    pasv_addr = ctrl_addr;
	else {
	    reply(522, "Network protocol mismatch, use (1)");
	    return;
	}
	break;
    default:
	reply(522, "Network protocol not supported, use (1,2)");
	return;
    }
#else
    pasv_addr = ctrl_addr;
#endif

    if (passive_port_min == 0 && passive_port_max == 0) {
	/* let the kernel allocate the port */
	SET_SOCK_PORT(pasv_addr, 0);
    }
    else if (prime == 0) {
	range = passive_port_max - passive_port_min + 1;

	/* find the first prime greater than the range in the primes list */
	for (i = 0; primes[i] != 0 && range >= primes[i]; i++)
	    ;
	/* shouldn't happen, but check just in case */
	if (primes[i] == 0) {
	    syslog(LOG_ERR, "passive ports range too large %d-%d", passive_port_min, passive_port_max);
	    /* let the kernel allocate the port */
	    SET_SOCK_PORT(pasv_addr, 0);
	}
	else
	    prime = primes[i];
    }
    len = SOCK_LEN(pasv_addr);

    port_priv_on(0);	/* necessary as port can be < 1024 */
    pdata = socket(SOCK_FAMILY(pasv_addr), SOCK_STREAM, 0);
    if (pdata < 0) {
	serrno = errno;
	port_priv_off((uid_t) pw->pw_uid);
	errno = serrno;
	perror_reply(425, "Can't open passive connection");
	return;
    }
    if (keepalive)
	(void) setsockopt(pdata, SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on));
    if (TCPwindowsize) {
	(void) setsockopt(pdata, SOL_SOCKET, SO_SNDBUF, (char *) &TCPwindowsize, sizeof(TCPwindowsize));
	(void) setsockopt(pdata, SOL_SOCKET, SO_RCVBUF, (char *) &TCPwindowsize, sizeof(TCPwindowsize));
    }

    bind_error = -1;
    errno = EADDRINUSE;

    /* try each port in the specified range a maximum of 3 times */
    for (i = 0; i < 3 && bind_error != 0 && \
	((errno == EADDRINUSE) || (errno == EACCES)); i++) {
	if (i > 0)
	    sleep(i);
	if (SOCK_PORT(pasv_addr) == 0)
	    bind_error = bind(pdata, (struct sockaddr *) &pasv_addr, len);
	else {
	    gettimeofday(&tv, NULL);
	    srand(tv.tv_usec + tv.tv_sec);
	    inc = 1 + (int) ((1.0 * (prime - 1) * rand()) / (RAND_MAX + 1.0));
	    val = (int) ((1.0 * range * rand()) / (RAND_MAX + 1.0));
	    /*
	     * Using the modulus operator with a prime number allows us to
	     * try each port in the range once.
	     */
	    for (j = 0; j < range && bind_error != 0 && \
		((errno == EADDRINUSE) || (errno == EACCES)); j++) {
		while ((val = ((val + inc) % prime)) >= range)
		    ;
		SET_SOCK_PORT(pasv_addr, htons(val + passive_port_min));
		bind_error = bind(pdata, (struct sockaddr *) &pasv_addr, len);
	    }
	}
    }
    serrno = errno;
    port_priv_off((uid_t) pw->pw_uid);
    if (bind_error != 0) {
	errno = serrno;
	goto pasv_error;
    }

    /* if the kernel allocated the port, find out which one */
    if ((SOCK_PORT(pasv_addr) == 0) &&
	(getsockname(pdata, (struct sockaddr *) &pasv_addr, &len) < 0))
	goto pasv_error;

    if (listen(pdata, 1) < 0)
	goto pasv_error;
    usedefault = 1;
    if (route_vectored)
	reply_addr = &vect_addr;
    else
	reply_addr = &pasv_addr;
    a = (char *) SOCK_ADDR(*reply_addr);
    port = SOCK_PORT(pasv_addr);
    p = (char *) &port;

#define UC(b) (((int) b) & 0xff)

    if (debug) {
	char *s = calloc(128 + strlen(remoteident), sizeof(char));
	if (s) {
	    int i = ntohs(port);
	    sprintf(s, "PASV port %i assigned to %s", i, remoteident);
	    syslog(LOG_DEBUG, "%s", s);
	    free(s);
	}
    }
#ifdef INET6
    if (SOCK_FAMILY(*reply_addr) == AF_INET)
	isv4 = 1;
    else if (IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)reply_addr)->sin6_addr)) {
	    isv4 = 1;
	    a += 12; /* move to the IPv4 part of an IPv4-mapped IPv6 address */
    }
    switch (passive_mode) {
    case TYPE_PASV:
	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;
    case TYPE_EPSV:
	reply(229, "Entering Extended Passive Mode (|||%d|)", ntohs(port));
	return;
    case TYPE_LPSV:
	if (isv4) {
	    reply(228, "Entering Long Passive Mode "
		  "(%d,%d,%d,%d,%d,%d,%d,%d,%d)",
		  4, 4, UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
		  2, UC(p[0]), UC(p[1]));
	}
	else {
	    reply(228, "Entering Long Passive Mode "
		  "(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,"
		  "%d,%d,%d,%d,%d)", 6, 16,
		  UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
		  UC(a[4]), UC(a[5]), UC(a[6]), UC(a[7]),
		  UC(a[8]), UC(a[9]), UC(a[10]), UC(a[11]),
		  UC(a[12]), UC(a[13]), UC(a[14]), UC(a[15]),
		  2, UC(p[0]), UC(p[1]));
	}
	return;
     }
#else
    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;
#endif /* INET6 */

  pasv_error:
    perror_reply(425, "Can't open passive connection");
    (void) close(pdata);
    pdata = -1;
    if (debug) {
	char *s = calloc(128 + strlen(remoteident), sizeof(char));
	if (s) {
	    sprintf(s, "PASV port assignment assigned for %s", remoteident);
	    syslog(LOG_DEBUG, "%s", s);
	    free(s);
	}
    }
    return;
}

/*
 * Generate unique name for file with basename "local". The file named
 * "local" is already known to exist. Generates failure reply on error. 
 */
char *gunique(char *local)
{
    static char new[MAXPATHLEN];
    struct stat st;
    char *cp = strrchr(local, '/');
    int count = 0;

    if (cp)
	*cp = '\0';
    if (stat(cp ? local : ".", &st) < 0) {
	perror_reply(553, cp ? local : ".");
	return ((char *) 0);
    }
    if (cp)
	*cp = '/';
    (void) strncpy(new, local, (sizeof new) - 3);
    new[sizeof(new) - 3] = '\0';
    cp = new + strlen(new);
    *cp++ = '.';
    for (count = 1; count < 100; count++) {
	if (count == 10) {
	    cp -= 2;
	    *cp++ = '.';
	}
	(void) sprintf(cp, "%d", count);
	if (stat(new, &st) < 0)
	    return (new);
    }
    reply(452, "Unique file name cannot be created.");
    return ((char *) 0);
}

/* Format and send reply containing system error number. */

void perror_reply(int code, char *string)
{
    /*
     * If restricted user and string starts with home dir path, strip it off
     * and return only the relative path.
     */
    if (restricted_user && (home != NULL) && (home[0] != '\0')) {
	size_t len = strlen (home);
	if (strncmp (home, string, len) == 0) {
	    if (string[len - 1] == '/')
		string += len - 1;
	    else if (string[len] == '/')
		string += len;
	    else if (string[len] == '\0')
		string = "/";
	}
    }
    reply(code, "%s: %s.", string, strerror(errno));
}

static char *onefile[] =
{"", 0};

extern char **ftpglob(register char *v);
extern char *globerr;

void send_file_list(char *whichfiles)
{
    /* static so not clobbered by longjmp(), volatile would also work */
    static FILE *dout;
    static DIR *dirp;
    static char **sdirlist;
    static char *wildcard = NULL;

    struct stat st;

    register char **dirlist, *dirname;
    int simple = 0;
    int statret;
    /* This is ANSI/ISO C .. strpbrk should be in <string.h> which we've 
       ** already included so we don't need the following line.  'sides, it 
       ** breaks the GNU EGCS C compiler
       ** extern char *strpbrk(const char *, const char *);
     */

#ifdef TRANSFER_COUNT
#ifdef TRANSFER_LIMIT
    if (((file_limit_raw_out > 0) && (xfer_count_out >= file_limit_raw_out))
	|| ((file_limit_raw_total > 0) && (xfer_count_total >= file_limit_raw_total))
     || ((data_limit_raw_out > 0) && (byte_count_out >= data_limit_raw_out))
	|| ((data_limit_raw_total > 0) && (byte_count_total >= data_limit_raw_total))) {
	if (log_security)
	    if (anonymous)
		syslog(LOG_NOTICE, "anonymous(%s) of %s tried to list files (Transfer limits exceeded)",
		       guestpw, remoteident);
	    else
		syslog(LOG_NOTICE, "%s of %s tried to list files (Transfer limits exceeded)",
		       pw->pw_name, remoteident);
	reply(553, "Permission denied on server. (Transfer limits exceeded)");
	return;
    }
#endif
#endif

    draconian_FILE = NULL;
    dout = NULL;
    dirp = NULL;
    sdirlist = NULL;
    wildcard = NULL;
    if (strpbrk(whichfiles, "~{[*?") == NULL) {
	if (whichfiles[0] == '\0') {
	    wildcard = strdup("*");
	    if (wildcard == NULL) {
		reply(550, "Memory allocation error");
		goto globfree;
	    }
	    whichfiles = wildcard;
	}
	else {
	    if (statret=stat(whichfiles, &st) < 0)
	       statret=lstat(whichfiles, &st); /* Check if it's a dangling symlink */
	    if (statret >= 0) {
	       if ((st.st_mode & S_IFMT) == S_IFDIR) {
		   wildcard = malloc(strlen(whichfiles) + 3);
		   if (wildcard == NULL) {
		       reply(550, "Memory allocation error");
		       goto globfree;
		   }
		   strcpy(wildcard, whichfiles);
		   strcat(wildcard, "/*");
		   whichfiles = wildcard;
	       }
	    }
	}
    }
    if (strpbrk(whichfiles, "~{[*?") != NULL) {
	globerr = NULL;
	dirlist = ftpglob(whichfiles);
	sdirlist = dirlist;	/* save to free later */
	if (globerr != NULL) {
	    reply(550, "%s", globerr);
	    goto globfree;
	}
	else if (dirlist == NULL) {
	    errno = ENOENT;
	    perror_reply(550, whichfiles);
	    goto globfree;
	}
    }
    else {
	onefile[0] = whichfiles;
	dirlist = onefile;
	simple = 1;
    }

    if (wu_setjmp(urgcatch)) {
	transflag = 0;
	if (dout != NULL)
	    (void) fclose(dout);
	if (dirp != NULL)
	    (void) closedir(dirp);
	data = -1;
	pdata = -1;
	goto globfree;
    }
    while ((dirname = *dirlist++) != NULL) {
	statret=stat(dirname, &st);
	if (statret < 0)
	   statret=lstat(dirname, &st); /* Could be a dangling symlink */
	if (statret < 0) {
	    /* If user typed "ls -l", etc, and the client used NLST, do what
	     * the user meant. */
	    if (dirname[0] == '-' && *dirlist == NULL && transflag == 0) {
		retrieve_is_data = 0;
#ifndef INTERNAL_LS
		retrieve(ls_plain, dirname);
#else
		ls(dirname, 1);
#endif
		retrieve_is_data = 1;
		goto globfree;
	    }
	    perror_reply(550, dirname);
	    if (dout != NULL) {
		(void) fclose(dout);
		transflag = 0;
		data = -1;
		pdata = -1;
	    }
	    goto globfree;
	}
#ifndef NLST_SHOWS_DIRS
	if ((st.st_mode & S_IFMT) != S_IFDIR)
#endif
	{
	    if (dout == NULL) {
		dout = dataconn("file list", (off_t) - 1, "w");
		if (dout == NULL)
		    goto globfree;
		transflag++;
		draconian_FILE = dout;
	    }
	    if (draconian_FILE != NULL) {
		(void) signal(SIGALRM, draconian_alarm_signal);
		alarm(timeout_data);
#if defined(USE_GSS)
		(void) sec_fprintf(dout, "%s%s\n", dirname,
				type == TYPE_A ? "\r" : "");
#else
		fprintf(dout, "%s%s\n", dirname,
			type == TYPE_A ? "\r" : "");
#endif /* USE_GSS */
	    }
	    byte_count += strlen(dirname) + 1;
#ifdef TRANSFER_COUNT
	    byte_count_total += strlen(dirname) + 1;
	    byte_count_out += strlen(dirname) + 1;
	    if (type == TYPE_A) {
		byte_count_total++;
		byte_count_out++;
	    }
#endif
	}
    }

    if (dout != NULL) {
	if (draconian_FILE != NULL) {
	    (void) signal(SIGALRM, draconian_alarm_signal);
	    alarm(timeout_data);
#if defined(USE_GSS)
	    if (sec_fflush(dout) < 0) {
		alarm(0);
		perror_reply(550, "Data connection");
		goto sfl_cleanup; /* send file list cleanup */
	    }
#else
	    fflush(dout);
#endif /* USE_GSS */
	}
	if (draconian_FILE != NULL) {
	    (void) signal(SIGALRM, draconian_alarm_signal);
	    alarm(timeout_data);
	    socket_flush_wait(dout);
	}
    }
    if (dout == NULL)
	reply(550, "No files found.");
    else if ((draconian_FILE == NULL) || ferror(dout) != 0) {
	alarm(0);
	perror_reply(550, "Data connection");
    }
    else {
#ifdef TRANSFER_COUNT
	xfer_count_total++;
	xfer_count_out++;
#endif
	alarm(0);
	reply(226, "Transfer complete.");
    }
  sfl_cleanup:
    transflag = 0;
    if ((dout != NULL) && (draconian_FILE != NULL))
	(void) fclose(dout);
    data = -1;
    pdata = -1;
  globfree:
    if (wildcard != NULL) {
	free(wildcard);
	wildcard = NULL;
    }
    if (sdirlist) {
	blkfree(sdirlist);
	free((char *) sdirlist);
    }
}

/*
   **  SETPROCTITLE -- set process title for ps
   **
   **   Parameters:
   **           fmt -- a printf style format string.
   **           a, b, c -- possible parameters to fmt.
   **
   **   Returns:
   **           none.
   **
   **   Side Effects:
   **           Clobbers argv of our main procedure so ps(1) will
   **           display the title.
 */

#define SPT_NONE	0	/* don't use it at all */
#define SPT_REUSEARGV	1	/* cover argv with title information */
#define SPT_BUILTIN	2	/* use libc builtin */
#define SPT_PSTAT	3	/* use pstat(PSTAT_SETCMD, ...) */
#define SPT_PSSTRINGS	4	/* use PS_STRINGS->... */
#define SPT_SYSMIPS	5	/* use sysmips() supported by NEWS-OS 6 */
#define SPT_SCO		6	/* write kernel u. area */
#define SPT_CHANGEARGV	7	/* write our own strings into argv[] */
#define MAXLINE      2048	/* max line length for setproctitle */
#define SPACELEFT(buf, ptr)  (sizeof buf - ((ptr) - buf))

#ifndef SPT_TYPE
#define SPT_TYPE	SPT_REUSEARGV
#endif

#if SPT_TYPE != SPT_NONE && SPT_TYPE != SPT_BUILTIN

#if SPT_TYPE == SPT_PSTAT
#include <sys/pstat.h>
#endif
#if SPT_TYPE == SPT_PSSTRINGS
#include <machine/vmparam.h>
#include <sys/exec.h>
#ifndef PS_STRINGS		/* hmmmm....  apparently not available after all */
#undef SPT_TYPE
#define SPT_TYPE	SPT_REUSEARGV
#else
#ifndef NKPDE			/* FreeBSD 2.0 */
#define NKPDE 63
typedef unsigned int *pt_entry_t;
#endif
#endif
#endif

#if SPT_TYPE == SPT_PSSTRINGS || SPT_TYPE == SPT_CHANGEARGV
#define SETPROC_STATIC	static
#else
#define SETPROC_STATIC
#endif

#if SPT_TYPE == SPT_SYSMIPS
#include <sys/sysmips.h>
#include <sys/sysnews.h>
#endif

#if SPT_TYPE == SPT_SCO
#ifdef UNIXWARE
#include <sys/exec.h>
#include <sys/ksym.h>
#include <sys/proc.h>
#include <sys/user.h>
#else /* UNIXWARE */
#include <sys/immu.h>
#include <sys/dir.h>
#include <sys/user.h>
#include <sys/fs/s5param.h>
#endif /* UNIXWARE */
#if PSARGSZ > MAXLINE
#define SPT_BUFSIZE	PSARGSZ
#endif
#ifndef _PATH_KMEM
#define _PATH_KMEM	"/dev/kmem"
#endif /* _PATH_KMEM */
#endif /* SPT_SCO */

#ifndef SPT_PADCHAR
#define SPT_PADCHAR	' '
#endif

#ifndef SPT_BUFSIZE
#define SPT_BUFSIZE	MAXLINE
#endif

#endif /* SPT_TYPE != SPT_NONE && SPT_TYPE != SPT_BUILTIN */

#if SPT_TYPE == SPT_REUSEARGV || SPT_TYPE == SPT_CHANGEARGV
char **Argv = NULL;		/* pointer to argument vector */
#endif

#if SPT_TYPE == SPT_REUSEARGV
char *LastArgv = NULL;		/* end of argv */
#endif

/*
   **  Pointers for setproctitle.
   **   This allows "ps" listings to give more useful information.
 */
void initsetproctitle(argc, argv, envp)
     int argc;
     char **argv;
     char **envp;
{
#if SPT_TYPE == SPT_REUSEARGV
    register int i, envpsize = 0;
    char **newenviron;
    extern char **environ;

    /*
       **  Save start and extent of argv for setproctitle.
     */

    LastArgv = argv[argc - 1] + strlen(argv[argc - 1]);
    if (envp != NULL) {
	/*
	   **  Move the environment so setproctitle can use the space at
	   **  the top of memory.
	 */
	for (i = 0; envp[i] != NULL; i++)
	    envpsize += strlen(envp[i]) + 1;
	newenviron = (char **) malloc(sizeof(char *) * (i + 1));
	if (newenviron) {
	    int err = 0;
	    for (i = 0; envp[i] != NULL; i++) {
		if ((newenviron[i] = strdup(envp[i])) == NULL) {
		    err = 1;
		    break;
		}
	    }
	    if (err) {
		for (i = 0; newenviron[i] != NULL; i++)
		    free(newenviron[i]);
		free(newenviron);
		i = 0;
	    }
	    else {
		newenviron[i] = NULL;
		environ = newenviron;
	    }
	}
	else {
	    i = 0;
	}

	/*
	   **  Find the last environment variable within wu-ftpd's
	   **  process memory area.
	 */
	while (i > 0 && (envp[i - 1] < argv[0] ||
		    envp[i - 1] > (argv[argc - 1] + strlen(argv[argc - 1]) +
				   1 + envpsize)))
	    i--;

	if (i > 0)
	    LastArgv = envp[i - 1] + strlen(envp[i - 1]);
    }
#endif /* SPT_TYPE == SPT_REUSEARGV */

#if SPT_TYPE == SPT_REUSEARGV || SPT_TYPE == SPT_CHANGEARGV
    Argv = argv;
#endif
}


#if SPT_TYPE != SPT_BUILTIN

/*VARARGS1 */
void setproctitle(const char *fmt,...)
{
#if SPT_TYPE != SPT_NONE
    register char *p;
    register int i;
    SETPROC_STATIC char buf[SPT_BUFSIZE];
    VA_LOCAL_DECL
#if SPT_TYPE == SPT_PSTAT
	union pstun pst;
#endif
#if SPT_TYPE == SPT_SCO
    static off_t seek_off;
    static int kmemfd = -1;
    static int kmempid = -1;
#ifdef UNIXWARE
    off_t offset;
    void *ptr;
    struct mioc_rksym rks;
#endif /* UNIXWARE */
#endif /* SPT_SCO */

    p = buf;

    /* print ftpd: heading for grep */
    (void) strcpy(p, "ftpd: ");
    p += strlen(p);

    /* print the argument string */
    VA_START(fmt);
    (void) vsnprintf(p, SPACELEFT(buf, p), fmt, ap);
    VA_END;

    i = strlen(buf);

#if SPT_TYPE == SPT_PSTAT
    pst.pst_command = buf;
    pstat(PSTAT_SETCMD, pst, i, 0, 0);
#endif
#if SPT_TYPE == SPT_PSSTRINGS
    PS_STRINGS->ps_nargvstr = 1;
    PS_STRINGS->ps_argvstr = buf;
#endif
#if SPT_TYPE == SPT_SYSMIPS
    sysmips(SONY_SYSNEWS, NEWS_SETPSARGS, buf);
#endif
#if SPT_TYPE == SPT_SCO
    if (kmemfd < 0 || kmempid != getpid()) {
	if (kmemfd >= 0)
	    close(kmemfd);
	if ((kmemfd = open(_PATH_KMEM, O_RDWR, 0)) < 0)
	    return;
	(void) fcntl(kmemfd, F_SETFD, 1);
	kmempid = getpid();
#ifdef UNIXWARE
	seek_off = 0;
	rks.mirk_symname = "upointer";
	rks.mirk_buf = &ptr;
	rks.mirk_buflen = sizeof(ptr);
	if (ioctl(kmemfd, MIOC_READKSYM, &rks) < 0)
	    return;
	offset = (off_t) ptr + (off_t) & ((struct user *) 0)->u_procp;
	if (lseek(kmemfd, offset, SEEK_SET) != offset)
	    return;
	if (read(kmemfd, &ptr, sizeof(ptr)) != sizeof(ptr))
	    return;
	offset = (off_t) ptr + (off_t) & ((struct proc *) 0)->p_execinfo;
	if (lseek(kmemfd, offset, SEEK_SET) != offset)
	    return;
	if (read(kmemfd, &ptr, sizeof(ptr)) != sizeof(ptr))
	    return;
	seek_off = (off_t) ptr + (off_t) ((struct execinfo *) 0)->ei_psargs;
#else /* UNIXWARE */
	seek_off = UVUBLK + (off_t) & ((struct user *) 0)->u_psargs;
#endif /* UNIXWARE */
    }
#ifdef UNIXWARE
    if (seek_off == 0)
	return;
#endif /* UNIXWARE */
    buf[PSARGSZ - 1] = '\0';
    if (lseek(kmemfd, (off_t) seek_off, SEEK_SET) == seek_off)
	(void) write(kmemfd, buf, PSARGSZ);
#endif /* SPT_SCO */
#if SPT_TYPE == SPT_REUSEARGV
    if (i > LastArgv - Argv[0] - 2) {
	i = LastArgv - Argv[0] - 2;
	buf[i] = '\0';
    }
    (void) strcpy(Argv[0], buf);
    p = &Argv[0][i];
    while (p < LastArgv)
	*p++ = SPT_PADCHAR;
    Argv[1] = NULL;
#endif
#if SPT_TYPE == SPT_CHANGEARGV
    Argv[0] = buf;
    Argv[1] = 0;
#endif
#endif /* SPT_TYPE != SPT_NONE */
}

#endif /* SPT_TYPE != SPT_BUILTIN */

#ifdef KERBEROS
/* thanks to gshapiro@wpi.wpi.edu for the following kerberosities */

void init_krb()
{
    char hostname[100];

#ifdef HAVE_SYSINFO
    if (sysinfo(SI_HOSTNAME, hostname, sizeof(hostname)) < 0) {
	perror("sysinfo");
#else
    if (gethostname(hostname, sizeof(hostname)) < 0) {
	perror("gethostname");
#endif
	exit(1);
    }
    if (strchr(hostname, '.'))
	*(strchr(hostname, '.')) = 0;

    sprintf(krb_ticket_name, "/var/dss/kerberos/tkt/tkt.%d", getpid());
    krb_set_tkt_string(krb_ticket_name);

    config_auth();

    if (krb_svc_init("hesiod", hostname, (char *) NULL, 0, (char *) NULL,
		     (char *) NULL) != KSUCCESS) {
	fprintf(stderr, "Couldn't initialize Kerberos\n");
	exit(1);
    }
}

void end_krb()
{
    unlink(krb_ticket_name);
}

#endif /* KERBEROS */

#ifdef ULTRIX_AUTH
static int ultrix_check_pass(char *passwd, char *xpasswd)
{
    struct svcinfo *svp;
    int auth_status;

    if ((svp = getsvc()) == (struct svcinfo *) NULL) {
	syslog(LOG_WARNING, "getsvc() failed in ultrix_check_pass");
	return -1;
    }
    if (pw == (struct passwd *) NULL) {
	return -1;
    }
    if (((svp->svcauth.seclevel == SEC_UPGRADE) &&
	 (!strcmp(pw->pw_passwd, "*")))
	|| (svp->svcauth.seclevel == SEC_ENHANCED)) {
	if ((auth_status = authenticate_user(pw, passwd, "/dev/ttypXX")) >= 0) {
	    /* Indicate successful validation */
	    return auth_status;
	}
	if (auth_status < 0 && errno == EPERM) {
	    /* Log some information about the failed login attempt. */
	    switch (abs(auth_status)) {
	    case A_EBADPASS:
		break;
	    case A_ESOFTEXP:
		syslog(LOG_NOTICE, "password will expire soon for user %s",
		       pw->pw_name);
		break;
	    case A_EHARDEXP:
		syslog(LOG_NOTICE, "password has expired for user %s",
		       pw->pw_name);
		break;
	    case A_ENOLOGIN:
		syslog(LOG_NOTICE, "user %s attempted login to disabled acct",
		       pw->pw_name);
		break;
	    }
	}
    }
    else {
	if ((*pw->pw_passwd != '\0') && (!strcmp(xpasswd, pw->pw_passwd))) {
	    /* passwd in /etc/passwd isn't empty && encrypted passwd matches */
	    return 0;
	}
    }
    return -1;
}
#endif /* ULTRIX_AUTH */

#ifdef USE_PAM
/* This is rather an abuse of PAM, but the FTP protocol doesn't allow much
 * flexibility here.  :-(
 */

/* Static variables used to communicate between the conversation function
 * and the server_login function
 */
static char *PAM_password;

/* PAM conversation function
 * Here we assume (for now, at least) that echo on means login name, and
 * echo off means password.
 */
#ifdef SOLARIS_2
/* Workaround bug 4430970/4413889 which causes a compiler warning, necessary
 * as usr/src/Makefile.master now includes "-errwarn=%all".
 */
static int PAM_conv(int num_msg, struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
#else
static int PAM_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
#endif
{
    int replies = 0;
    struct pam_response *reply = NULL;

#define COPY_STRING(s) (s) ? strdup(s) : NULL

    reply = malloc(sizeof(struct pam_response) * num_msg);
    if (!reply)
	return PAM_CONV_ERR;

    for (replies = 0; replies < num_msg; replies++) {
	switch (msg[replies]->msg_style) {
	case PAM_PROMPT_ECHO_ON:
	    return PAM_CONV_ERR;
	    break;
	case PAM_PROMPT_ECHO_OFF:
	    reply[replies].resp_retcode = PAM_SUCCESS;
	    reply[replies].resp = COPY_STRING(PAM_password);
	    /* PAM frees resp */
	    break;
	case PAM_TEXT_INFO:
	    /* ignore it... */
	    reply[replies].resp_retcode = PAM_SUCCESS;
	    reply[replies].resp = NULL;
	    break;
	case PAM_ERROR_MSG:
	    /* ignore it... */
	    reply[replies].resp_retcode = PAM_SUCCESS;
	    reply[replies].resp = NULL;
	    break;
	default:
	    /* Must be an error of some sort... */
	    return PAM_CONV_ERR;
	}
    }
    *resp = reply;
    return PAM_SUCCESS;
}
static struct pam_conv PAM_conversation =
{
    &PAM_conv,
    NULL
};

static int pam_check_pass(char *user, char *passwd)
{
    char tty[20];
    int pam_session = 0;

    /* Now use PAM to do authentication and session logging. Bail out if
     * there are any errors. Since this is a limited protocol, and an even
     * more limited function within a server speaking this protocol, we
     * can't be as verbose as would otherwise make sense.
     */
    PAM_password = passwd;
    pamh = (pam_handle_t *)0;
    if (pam_start("ftp", user, &PAM_conversation, &pamh) != PAM_SUCCESS)
	return 0;

#if ((defined(BSD) && (BSD >= 199103)) || defined(sun))
    (void) sprintf(tty, "/dev/ftp%ld", (long) getpid());
#else
    (void) sprintf(tty, "/dev/ftpd%d", getpid());
#endif

    if (pam_set_item(pamh, PAM_TTY, tty) != PAM_SUCCESS)
	goto pam_fail;
    if (pam_set_item(pamh, PAM_RHOST, remotehost) != PAM_SUCCESS)
	goto pam_fail;
    if (pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK) != PAM_SUCCESS) {
#ifdef SOLARIS_BSM_AUDIT
	audit_ftpd_bad_pw(user);
#endif
	goto pam_fail;
    }
    if (pam_acct_mgmt(pamh, 0) != PAM_SUCCESS) {
#ifdef SOLARIS_BSM_AUDIT
	audit_ftpd_bad_pw(user);
#endif
	goto pam_fail;
    }
    if (pam_open_session(pamh, 0) != PAM_SUCCESS)
	goto pam_fail;
    pam_session = 1;
#ifdef PAM_ESTABLISH_CRED
    if (pam_setcred(pamh, PAM_ESTABLISH_CRED) != PAM_SUCCESS)
	goto pam_fail;
#else
    if (pam_setcred(pamh, PAM_CRED_ESTABLISH) != PAM_SUCCESS)
	goto pam_fail;
#endif
    /* If this point is reached, the user has been authenticated. */
    return 1;

pam_fail:
    if (pam_session)
	(void) pam_close_session(pamh, 0);
    (void) pam_end(pamh, 0);
    pamh = (pam_handle_t *)0;
    return 0;
}
#endif

#ifdef DAEMON

#ifdef INET6
static struct in6_addr acl_DaemonAddress6(void)
{
    struct in6_addr rv = in6addr_any;
    struct aclmember *entry = NULL;

    if (getaclentry("daemonaddress", &entry) && ARG0) {
	if (inet_pton6(ARG0, &rv) != 1)
	    rv = in6addr_any;
    }
    return rv;
}
#endif /* INET6 */
static unsigned long int acl_DaemonAddress(void)
{
    unsigned long int rv = INADDR_ANY;
    struct aclmember *entry = NULL;

    if (getaclentry("daemonaddress", &entry) && ARG0) {
	rv = inet_addr(ARG0);
	if (rv == -1)
	    rv = INADDR_ANY;
    }
    return rv;
}

/* I am running as a standalone daemon (not under inetd) */
static void do_daemon(void)
{
    struct SOCKSTORAGE server;
    struct servent *serv;
    int pgrp;
    int lsock;
    int one = 1;
    FILE *pidfile;
    int i;
#if defined(UNIXWARE) || defined(AIX)
    size_t addrlen;
#else
    int addrlen;
#endif

    /* Some of this is "borrowed" from inn - lots of it isn't */

    if (be_daemon == 2) {
	/* Fork - so I'm not the owner of the process group any more */
	i = fork();
	if (i < 0) {
	    syslog(LOG_ERR, "cant fork %m");
	    exit(1);
	}
	/* No need for the parent any more */
	if (i > 0)
	    exit(0);

#ifdef NO_SETSID
	pgrp = setpgrp(0, getpid());
#else
	pgrp = setsid();
#endif
	if (pgrp < 0) {
	    syslog(LOG_ERR, "cannot daemonise: %m");
	    exit(1);
	}
    }

    if (!Bypass_PID_Files)
	if ((pidfile = fopen(_PATH_FTPD_PID, "w"))) {
	    fprintf(pidfile, "%ld\n", (long) getpid());
	    fclose(pidfile);
	}
	else {
	    syslog(LOG_ERR, "Cannot write pidfile: %m");
	}

    /* Close off all file descriptors and reopen syslog */
    if (be_daemon == 2) {
	closelog();
	closefds(0);
	(void) open(_PATH_DEVNULL, O_RDWR);
	(void) dup2(0, 1);
	/* junk stderr */
	(void) freopen(_PATH_DEVNULL, "w", stderr);

#ifdef FACILITY
	openlog("ftpd", LOG_PID | LOG_NDELAY, FACILITY);
#else
	openlog("ftpd", LOG_PID);
#endif
    }

    if (RootDirectory != NULL) {
	if ((chroot(RootDirectory) < 0)
	    || (chdir("/") < 0)) {
	    syslog(LOG_ERR, "Cannot chroot to initial directory, aborting.");
	    exit(1);
	}
	free(RootDirectory);
	RootDirectory = NULL;
    }

    if (!use_accessfile)
	syslog(LOG_WARNING, "FTP server started without ftpaccess file");

    syslog(LOG_INFO, "FTP server (%s) ready.", version);

    /* Create a socket to listen on */
#ifdef INET6
    if (listen_v4 == 0)
	lsock = socket(AF_INET6, SOCK_STREAM, 0);
    else
#endif
    lsock = socket(AF_INET, SOCK_STREAM, 0);
    if (lsock < 0) {
	syslog(LOG_ERR, "Cannot create socket to listen on: %m");
	exit(1);
    }
    if (setsockopt(lsock, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof(one)) < 0) {
	syslog(LOG_ERR, "Cannot set SO_REUSEADDR option: %m");
	exit(1);
    }
    if (keepalive)
	(void) setsockopt(lsock, SOL_SOCKET, SO_KEEPALIVE, (char *) &one, sizeof(one));

#ifdef INET6
    if (listen_v4 == 0) {
	struct sockaddr_in6 *server_sin6 = (struct sockaddr_in6 *)&server;

	memset(&server, 0, sizeof(struct sockaddr_in6));
	server_sin6->sin6_family = AF_INET6;
	server_sin6->sin6_addr = acl_DaemonAddress6();
    }
    else {
	struct sockaddr_in *server_sin = (struct sockaddr_in *)&server;

	server_sin->sin_family = AF_INET;
	server_sin->sin_addr.s_addr = acl_DaemonAddress();
    }
#else
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = acl_DaemonAddress();
#endif
    if (daemon_port == 0) {
	if (!(serv = getservbyname("ftp", "tcp"))) {
	    syslog(LOG_ERR, "Cannot find service ftp: %m");
	    exit(1);
	}
	SET_SOCK_PORT(server, serv->s_port);
	daemon_port = ntohs(serv->s_port);
    }
    else
	SET_SOCK_PORT(server, htons(daemon_port));

    if (bind(lsock, (struct sockaddr *) &server, SOCK_LEN(server)) < 0) {
	syslog(LOG_ERR, "Cannot bind socket: %m");
	exit(1);
    }

    listen(lsock, MAX_BACKLOG);

    sprintf(proctitle, "accepting connections on port %i", daemon_port);
    setproctitle("%s", proctitle);

    while (1) {
	int pid;
	int msgsock;

	addrlen = sizeof(his_addr);
	msgsock = accept(lsock, (struct sockaddr *) &his_addr, &addrlen);
	if (msgsock < 0) {
	    int severity = LOG_ERR;

	    if (errno == EINTR || errno == ECONNABORTED)
		severity = LOG_INFO;
	    syslog(severity, "Accept failed: %m");
	    sleep(1);
	    continue;
	}

	/* Fork off a handler */
	pid = fork();
	if (pid < 0) {
	    syslog(LOG_ERR, "failed to fork: %m");
	    close(msgsock);
	    sleep(1);
	    continue;
	}
	if (pid == 0) {
	    /* I am that forked off child */
	    /* Only parent needs lsock */
	    close(lsock);
	    closelog();
	    /* Make sure that stdin/stdout are the new socket */
	    dup2(msgsock, 0);
	    dup2(msgsock, 1);
	    if (msgsock != 0 && msgsock != 1)
		close(msgsock);
#ifdef FACILITY
	    openlog("ftpd", LOG_PID | LOG_NDELAY, FACILITY);
#else
	    openlog("ftpd", LOG_PID);
#endif
	    setup_paths();
	    access_init();
	    return;
	}

	/* I am the parent */
	close(msgsock);

	/* Quick check to see if any of the forked off children have
	 * terminated. */
	while ((pid = waitpid((pid_t) -1, (int *) 0, WNOHANG)) > 0) {
	    /* A child has finished */
	}

	access_init();
    }
}

#endif /* DAEMON */

#ifdef RATIO
int is_downloadfree(char *fname)
{
    char        rpath[MAXPATHLEN];
    char	class[BUFSIZ];
    char        *cp;
    int		which;
    struct aclmember *entry = NULL;

    if( wu_realpath(fname,rpath,chroot_path) == NULL )
        return 0;

    (void) acl_getclass(class);

    if (debug)
	syslog(LOG_DEBUG, "class: %s, fname: %s, rpath: %s", class, fname, rpath);

    while( getaclentry("dl-free-dir",&entry) ) {
        if( ARG0 == NULL )
            continue;
        if( strncmp(rpath,ARG0,strlen(ARG0)) == 0 ) {
	    if( ARG1 == NULL )
		return 1;
	    else for(which = 1; (which < MAXARGS) && ARG[which]; which++) {
		if( strcmp(class,ARG[which]) == 0 )
		    return 1;
	    }
        }
    }
    while( getaclentry("dl-free",&entry) ) {
        if( ARG0 == NULL )
            continue;
        if( *(ARG0) != '/' ) {  /* compare basename */
            if( (cp = strrchr(rpath,'/')) == NULL ) {
                cp = rpath;
            }
            else {
                ++cp;
            }
            if( strcmp(cp,ARG0) == 0 ) {
		if( ARG1 == NULL )
		    return 1;
		else for(which = 1; (which < MAXARGS) && ARG[which]; which++) {
		    if( strcmp(class,ARG[which]) == 0 )
		    return 1;
		}
            }
        }
        else {  /* compare real path */
            if( strcmp(rpath,ARG0) == 0 ) {
		if( ARG1 == NULL )
		    return 1;
		else for(which = 1; (which < MAXARGS) && ARG[which] ; which++) {
		    if( strcmp(class,ARG[which]) == 0 )
		    return 1;
		}
            }
        }
    }
    return 0;
}
#endif /* RATIO */

int pasv_allowed(char *remoteaddr)
{
    char class[MAXPATHLEN];
    int which;
    struct aclmember *entry = NULL;
    (void) acl_getclass(class);
    while (getaclentry("pasv-allow", &entry)) {
	if ((ARG0 != NULL) && (strcasecmp(class, ARG0) == 0))
	    for (which = 1; (which < MAXARGS) && (ARG[which] != NULL); which++) {
		if (hostmatch(ARG[which], remoteaddr, NULL))
		    return 1;
	    }
    }
    return 0;
}

int port_allowed(char *remoteaddr)
{
    char class[MAXPATHLEN];
    int which;
    struct aclmember *entry = NULL;
    (void) acl_getclass(class);
    while (getaclentry("port-allow", &entry)) {
	if ((ARG0 != NULL) && (strcasecmp(class, ARG0) == 0))
	    for (which = 1; (which < MAXARGS) && (ARG[which] != NULL); which++) {
		if (hostmatch(ARG[which], remoteaddr, NULL))
		    return 1;
	    }
    }
    return 0;
}

#ifdef MAIL_ADMIN
char *email(char *full_address)
{
    /* Get the plain address part from an e-mail address
       (i.e. remove realname) */

    static char *email_buf = NULL;
    char *addr, *ptr;

    if (email_buf != NULL)
	free(email_buf);

    email_buf = (char *) malloc(strlen(full_address) + 1);
    addr = email_buf;
    memset(addr, 0, strlen(full_address) + 1);
    strcpy(addr, full_address);

    /* Realname <user@host> type address */
    if ((ptr = (char *) strchr(addr, '<')) != NULL) {
	addr = ++ptr;
	if ((ptr = (char *) strchr(addr, '>')) != NULL)
	    *ptr = '\0';
    }

    /* user@host (Realname) type address */
    if (((char *) strchr(addr, ' ')) != NULL)
	addr[strchr(addr, ' ') - addr] = '\0';

    return addr;
}

FILE *SockOpen(char *host, int clientPort)
{
    int sock;
    unsigned long inaddr;
    struct sockaddr_in ad;
    FILE *fp;
#ifdef INET6
    struct sockaddr_in6 ad6;
    struct addrinfo hints, *result, *res;
    int af = AF_INET;
#else
    struct hostent *hp;
#endif

    memset(&ad, 0, sizeof(ad));
    ad.sin_family = AF_INET;

#ifdef INET6
    memset(&ad6, 0, sizeof(ad6));
    ad6.sin6_family = AF_INET6;

    memset(&hints, 0, sizeof(hints));
    hints.ai_flags = AI_CANONNAME;
    hints.ai_family = PF_UNSPEC;

    if (getaddrinfo(host, NULL, &hints, &result) != 0)
	return (FILE *) NULL;

    for (res = result; res; res = res->ai_next) {
	af = res->ai_family;
	if (af == AF_INET)
	    memcpy(&ad.sin_addr, &((struct sockaddr_in *)res->ai_addr)->sin_addr, sizeof(struct in_addr));
	else if (af == AF_INET6)
	    memcpy(&ad6.sin6_addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, sizeof(struct in6_addr));
	else
	    continue;

	if (af == AF_INET6) {
	    ad6.sin6_port = htons(clientPort);
	    sock = socket(AF_INET6, SOCK_STREAM, 0);
	    if (sock < 0)
		continue;
	    if (connect(sock, (struct sockaddr *) &ad6, sizeof(ad6)) != -1)
		break;
	    close(sock);
	}
	else {
	    ad.sin_port = htons(clientPort);
	    sock = socket(AF_INET, SOCK_STREAM, 0);
	    if (sock < 0)
		continue;
	    if (connect(sock, (struct sockaddr *) &ad, sizeof(ad)) != -1)
		break;
	    close(sock);
	}
    }
    freeaddrinfo(result);
    if (!res)
	return (FILE *) NULL;
#else
    inaddr = inet_addr(host);
    if (inaddr != (unsigned long) -1)
	memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr));
    else {
	hp = gethostbyname(host);
	if (hp == NULL)
	    return (FILE *) NULL;
	memcpy(&ad.sin_addr, hp->h_addr, hp->h_length);
    }
    ad.sin_port = htons(clientPort);
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
	return (FILE *) NULL;
    if (connect(sock, (struct sockaddr *) &ad, sizeof(ad)) < 0) {
	close(sock);
	return (FILE *) NULL;
    }
#endif /* INET6 */

    fp = fdopen(sock, "r+");
    setvbuf(fp, NULL, _IOLBF, 2048);
    return (fp);
}

int SockPrintf(FILE *sockfp, char *format,...)
{
    va_list ap;
    char buf[16384];

    va_start(ap, format);
    vsnprintf(buf, sizeof(buf), format, ap);
    buf[sizeof(buf) - 1] = '\0';
    va_end(ap);
    return SockWrite(buf, 1, strlen(buf), sockfp);
}

int SockWrite(char *buf, int size, int len, FILE *sockfp)
{
    return (fwrite(buf, size, len, sockfp));
}

char *SockGets(FILE *sockfp, char *buf, int len)
{
    return (fgets(buf, len, sockfp));
}

int SockPuts(FILE *sockfp, char *buf)
{
    int rc;

    if ((rc = SockWrite(buf, 1, strlen(buf), sockfp)))
	return rc;
    return SockWrite("\r\n", 1, 2, sockfp);
}

int Reply(FILE *sockfp)
{
    char *reply, *rec, *separator;
    int ret = 0;

    if ((reply = (char *) malloc(BUFSIZ)) == NULL)
	return ret;
    memset(reply, 0, 1024);
    do {
	rec = SockGets(sockfp, reply, BUFSIZ);
	if (rec != NULL) {
	    ret = strtol(reply, &separator, 10);
	}
	else
	    ret = 250;
    } while ((rec != NULL) && (separator[0] != ' '));
    free(reply);
    fflush(sockfp);	/* Solaris bug: need to clear buf before fwrite() */
    return ret;
}

int Send(FILE *sockfp, char *format,...)
{
    va_list ap;
    char buf[16384];

    va_start(ap, format);
    vsnprintf(buf, sizeof(buf), format, ap);
    buf[sizeof(buf) - 1] = '\0';
    va_end(ap);
    SockWrite(buf, 1, strlen(buf), sockfp);
    return Reply(sockfp);
}
#endif /* MAIL_ADMIN */


/*
 * fixpath
 *
 * In principal, this is similar to realpath() or the mapping chdir function.
 * It removes unnecessary path components.  We do this to put a stop to
 * attempts to cause a memory starvation DoS.
 *
 */

void fixpath(char *path)
{
    int abs = 0;
    char *in;
    char *out;

    if (*path == '/') {
	abs = 1;
	path++;
    }
    else if (*path == '~') {
	do
	    path++;
	while ((*path != '\0') && (*path != '/'));
	if (*path == '/')
	    path++;
    }
    in = path;
    out = path;
    while (*in != '\0') {
	if (*in == '/')
	    in++;
	else if ((in[0] == '.') && ((in[1] == '/') || (in[1] == '\0'))) {
	    in++;
	    if (*in == '/')
		in++;
	}
	else if ((in[0] == '.') && (in[1] == '.') && ((in[2] == '/') || (in[2] == '\0'))) {
	    if (out == path) {
		if (abs) {
		    in++;
		    in++;
		    if (*in == '/')
			in++;
		}
		else {
		    *out++ = *in++;
		    *out++ = *in++;
		    if (*in == '/')
			*out++ = *in++;
		    path = out;
		}
	    }
	    else {
		out--;
		while ((out != path) && (*--out != '/'));
		in++;
		in++;
		if (*in == '/')
		    in++;
	    }
	}
	else {
	    do
		*out++ = *in++;
	    while ((*in != '\0') && (*in != '/'));
	    if (*in == '/')
		*out++ = *in++;
	}
    }
    *out = '\0';
}

#if defined(SOLARIS_2)

/* Callback function to cleanup_nscd()'s fdwalk().
 * If "fd" has the same inode, device as nscd door
 * it returns 1 otherwise it returns 0.
 */
int close_nsdoor(void *cb_data, int fd)
{
    struct stat fd_buf;
    struct stat *nsdoor_buf = (struct stat *) cb_data;

    if (fstat(fd, &fd_buf) != 0) {
	return (0);
    }

    if ((nsdoor_buf->st_dev == fd_buf.st_dev) && 
	(nsdoor_buf->st_ino == fd_buf.st_ino)) {
	close(fd);
	return (1);
    }

    return (0);
}

/* Walk through the list of open file descriptors
 * of the ftp dameon and find the nscd door fd and
 * close it.
 */
void cleanup_nscd()
{
    struct stat nsdoor_buf;

    if (stat(NAME_SERVICE_DOOR, &nsdoor_buf) == 0) {
	fdwalk(close_nsdoor, &nsdoor_buf);
    }
}

#endif