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