/***************************************

    This is part of frox: A simple transparent FTP proxy
    Copyright (C) 2000 James Hollingshead

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    misc.c - Miscellaneous stuff. Maybe this is getting unwieldy
             enough to want splitting...
  ***************************************/


#include <stdarg.h>
#include <syslog.h>
#include <netdb.h>
#include <stdlib.h>
#include <sys/param.h>
#include <sys/uio.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <sys/stat.h>
#include <ctype.h>


#include "common.h"
#include "control.h"
#include "transdata.h"
#include "misc.h"
#include "vscan.h"

/* ------------------------------------------------------------- **
** Listens on socket. If portrange != NULL then picks a port from the range
** in portrange, otherwises uses the value from listen_address.
** ------------------------------------------------------------- */
int listen_on_socket(struct sockaddr_in *listen_address, int portrange[2])
{
	int sockfd;

	sockfd = socket(AF_INET, SOCK_STREAM, 0);

	if(!portrange) {
		if(bind(sockfd, (struct sockaddr *) listen_address,
			sizeof(*listen_address))) {
			debug_perr("bind");
			close(sockfd);
			return (-1);
		}
	} else {
		if(bind_me(sockfd, listen_address, portrange)) {
			debug_perr("bind_me");
			close(sockfd);
			return (-1);
		}
	}
	if(listen(sockfd, 5)) {
		close(sockfd);
		debug_perr("listen");
		return (-1);
	}

	return (sockfd);
}

/* ------------------------------------------------------------- **
** Connects to address. Local port is picked from within portrange.
** ------------------------------------------------------------- */
int connect_to_socket(const struct sockaddr_in *to,
		      const struct in_addr *from_addr, int portrange[2])
{
	int sockfd;
	struct sockaddr_in from;

	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		debug_perr("socket");
		die(ERROR, "Socket error while making connection", 0, 0, -1);
	}

	memset(&from, 0, sizeof(from));
	from.sin_family = AF_INET;
	if(from_addr)
		from.sin_addr = *from_addr;

	if(bind_me(sockfd, &from, portrange)) {
		debug_perr("bind_me");
		close(sockfd);
		return (-1);
	}

	if(connect(sockfd, (struct sockaddr *) to, sizeof(*to))) {
		int i = errno;
		write_log(ERROR, "%s when trying to connect to %s",
			  strerror(i), inet_ntoa(to->sin_addr));
		close(sockfd);
		return (-1);
	}

	return (sockfd);
}


/* ------------------------------------------------------------- **
** Try to bind socket "fd" to address "address", with a port picked
** at random from within portrange.
** ------------------------------------------------------------- */
int bind_me(int fd, struct sockaddr_in *address, int portrange[2])
{
	int i, j;

	for(i = 0; i <= portrange[1] - portrange[0]; i++) {
		j = (rand() % (portrange[1] - portrange[0])) + portrange[0];
		address->sin_port = htons(j);

		if(bind(fd, (struct sockaddr *) address,
			sizeof(*address)) == 0)
			break;
		if(errno != EADDRINUSE) {
			return (-1);
		}
	}

	return (i > (portrange[1] - portrange[0]) ? -1 : 0);
}

/* ------------------------------------------------------------- **
** Convert a comma separted values address:port to a sockaddr_in 
** in network order
** ------------------------------------------------------------- */
struct sockaddr_in com2n(int a1, int a2, int a3, int a4, int p1, int p2)
{
	struct sockaddr_in ret;

	ret.sin_addr.s_addr = htonl(a4 + (a3 << 8) + (a2 << 16) + (a1 << 24));
	ret.sin_port = htons((p1 << 8) + p2);
	ret.sin_family = AF_INET;

	return (ret);
}


/* ------------------------------------------------------------- **
** Convert network order address to comma separated values
** ------------------------------------------------------------- */
void n2com(struct sockaddr_in address, int *a1, int *a2, int *a3, int *a4,
	   int *p1, int *p2)
{
	address.sin_addr.s_addr = ntohl(address.sin_addr.s_addr);
	address.sin_port = ntohs(address.sin_port);
	*a1 = (address.sin_addr.s_addr & 0xFF000000) >> 24;
	*a2 = (address.sin_addr.s_addr & 0x00FF0000) >> 16;
	*a3 = (address.sin_addr.s_addr & 0x0000FF00) >> 8;
	*a4 = (address.sin_addr.s_addr & 0x000000FF);
	*p1 = (address.sin_port & 0xFF00) >> 8;
	*p2 = (address.sin_port & 0x00FF);
}

/* ------------------------------------------------------------- **
** Extract a comma delimited address/port from buf. buf is unchecked
** user input so make sure we don't do anything stupid here...
** ------------------------------------------------------------- */
struct sockaddr_in extract_address(const sstr * buf)
{
	int i, a[6];
	struct sockaddr_in tmp;
	sstr *p = sstr_dup(buf);

	memset(&tmp, 0, sizeof(tmp));

	sstr_split(p, NULL, 0, sstr_pbrk2(p, "01234456789"));
	for(i = 0; i < 6; i++) {
		a[i] = sstr_atoi(p);
		if(a[i] < 0 || a[i] > 255) {
			sstr_free(p);
			write_log(ATTACK,
				  "PORT/PASV command number out of range");
			return tmp;
		}
		if(i != 5 && sstr_token(p, NULL, ",", 0) == -1) {
			sstr_free(p);
			return (tmp);
		}
	}

	tmp = com2n(a[0], a[1], a[2], a[3], a[4], a[5]);
	sstr_free(p);
	return (tmp);
}

/* Is u16 a valid port*/
int valid_uint16(int u16)
{
	return ((u16 >= 0) && (u16 < 65536));
}

/* Close fd if it isn't -1, and reset it to -1 */
int rclose(int *fd)
{
	int i;
	if(*fd == -1)
		return 0;
	i = close(*fd);
	*fd = -1;
	return i;
}

int do_chroot(void)
{
	if(config.dontchroot)
		return (0);
	if(chroot(config.chroot) != 0 || chdir("/") != 0) {
		write_log(ERROR, "Failed to chroot.");
		return (-1);
	}

	write_log(IMPORT, "Chrooted to %s", config.chroot);

	strip_filenames();

	return (0);
}

int droppriv(void)
{
	if(config.uid == 0) {
#ifdef ENFORCE_DROPPRIV
		write_log(ERROR, "Running frox as root is not allowed. "
			  "Set \"User\" to another value in the config file");
		write_log(ERROR, "Alternatively you may recompile giving "
			  "--enable-run-as-root to ./configure");
		exit(-1);
#else
		write_log(IMPORT, "WARNING frox set to run as root");
#endif
	}

	if(config.gid != 0) {
		setgid(config.gid);
		setgid(config.gid);
	}
	if(config.uid != 0) {
		setuid(config.uid);
		setuid(config.uid);
		write_log(IMPORT, "Dropped privileges");
	}
	return (0);
}

/*Write a log of a file transfer. upload states whether an upload or
  download. virus is 1 (contains virus), 0 (clean), or -1 (not
  scanned) */
void xfer_log(void)
{
	if(!info->needs_logging)
		return;
	if(!config.xferlogging)
		return;
	write_log(-1, "%s %s ftp://%s@%s/%s%s %s%s",
		  inet_ntoa(info->client_control.address.sin_addr),
		  info->upload ? "UPLOADED" : "DOWNLOADED",
		  sstr_buf(info->username),
		  sstr_buf(info->server_name),
		  sstr_buf(info->strictpath),
		  sstr_buf(info->filename),
		  info->virus == -1 ? "" :
		  (info->virus ? " VIRUS_INFECTED" : " VIRUS_CLEAN"),
		  info->cached ? " CACHE_HIT" : "");
	info->needs_logging = FALSE;
}

void write_log(int priority, const char *msg, ...)
{
	char *buf = NULL;
	int sz = MAX_LINE_LEN, n;
	va_list argptr;
	time_t tstamp;

	if(priority > config.loglevel)
		return;
	do {			/* Modified from printf(3) man page */
		if((buf = realloc(buf, sz)) == NULL)
			die(ERROR, "Out of memory.", 0, 0, -1);

		va_start(argptr, msg);
		n = vsnprintf(buf, sz, msg, argptr);
		va_end(argptr);

		if(n == -1)	/* glibc 2.0 */
			sz *= 2;	/* Try a bigger buffer */
		else if(n >= sz)	/* C99 compliant / glibc 2.1 */
			sz = n + 1;	/* precisely what is needed */
		else
			break;	/*It worked */
	} while(1);

	if(config.logfile) {
		sstr *s;
		s = sstr_init(0);
		time(&tstamp);
		sstr_cpy2(s, ctime(&tstamp));
		sstr_setchar(s, sstr_chr(s, '\n'), ' ');
		sstr_apprintf(s, "frox[%d] %s\n", getpid(), buf);
		sstr_write(2, s, 0);
		sstr_free(s);
	} else {
		if(priority == ERROR || priority == ATTACK) {
			syslog(LOG_ERR | LOG_DAEMON, "%s\n", buf);
		} else {
			syslog(LOG_NOTICE | LOG_DAEMON, "%s\n", buf);
		}
	}
	free(buf);
}

/* ------------------------------------------------------------- **
** Return hostname of address, or failing that the IP as a string
** ------------------------------------------------------------- */
sstr *addr2name(struct in_addr address)
{
	struct hostent *hostinfo;
	static sstr *buf = NULL;

	if(!buf)
		buf = sstr_init(MAX_LINE_LEN);
	sstr_cpy2(buf, inet_ntoa(address));

	if((hostinfo = gethostbyaddr((char *) &address,
				     sizeof(address), AF_INET)) != NULL)
		sstr_apprintf(buf, "(%s)", hostinfo->h_name);

	return (buf);
}

int resolve_addr(const struct in_addr *address, sstr * fqdn)
{
	struct hostent *hostinfo;

	if((hostinfo = gethostbyaddr((char *) address,
				     sizeof(*address), AF_INET)) != NULL) {
		if(gethostbyname((char *) hostinfo->h_name) != NULL) {
			sstr_cpy2(fqdn, hostinfo->h_name);
			return 0;
		}
	}
	sstr_cpy2(fqdn, inet_ntoa(*address));
	return -1;
}

/* Escape non printable characters, and any characters from extras in the url
 * with the %xx equivalent. If % must be escaped then include it in extras.
 */
int urlescape(sstr * url, char *extras)
{
	int i;
	sstr *tmp = sstr_init(0);

	for(i = 0; i < sstr_len(url); i++) {
		char c = sstr_getchar(url, i);
		if(strchr(extras, c) || !isprint(c)) {
			sstr_ncat(tmp, url, i);
			sstr_apprintf(tmp, "%%%x", c);
			sstr_split(url, NULL, 0, i + 1);
			i = -1;
		}
	}
	sstr_cat(tmp, url);
	sstr_cpy(url, tmp);
	sstr_free(tmp);
	return 0;
}

int make_tmpdir(void)
{
	sstr *name;
	struct stat tmp;

	name = sstr_init(0);
	sstr_apprintf(name, "%s/tmp", config.chroot);

	if(stat(sstr_buf(name), &tmp) == -1) {
		if(mkdir(sstr_buf(name), S_IRWXU) == -1) {
			write_log(ERROR, "Unable to make tmp dir %s",
				  sstr_buf(name));
			sstr_free(name);
			return (-1);
		}
		chown(sstr_buf(name), config.uid, config.gid);
		sstr_free(name);
	}
	return 0;
}

/*
 * Quit the program.
 */
void die(int loglevel, const char *lmessage,
	 int mcode, const char *message, int exitcode)
{
	if(message)
		send_cmessage(mcode, message);
	if(lmessage)
		write_log(loglevel, lmessage);
	write_log(INFO, "Closing session");
	kill_procs();
	vscan_abort();
	exit(exitcode);
}

void kill_procs(void)
{
	if(cmgrpid)
		kill(cmgrpid, SIGTERM);
	if(tdatapid)
		kill(tdatapid, SIGTERM);
}

void sstrerr(void)
{
	die(ERROR, "sstr internal failure. Exiting", 0, 0, -1);
}

#if defined(USE_LCACHE) || defined(TRANS_DATA)
/* Pinched from vsftpd. */
int send_fd(int sock_fd, int send_fd, char sendchar)
{
	int retval;
	struct msghdr msg;
	struct cmsghdr *p_cmsg;
	struct iovec vec;
	char cmsgbuf[CMSG_SPACE(sizeof(send_fd))];
	int *p_fds;
	msg.msg_control = cmsgbuf;
	msg.msg_controllen = sizeof(cmsgbuf);
	p_cmsg = CMSG_FIRSTHDR(&msg);
	p_cmsg->cmsg_level = SOL_SOCKET;
	p_cmsg->cmsg_type = SCM_RIGHTS;
	p_cmsg->cmsg_len = CMSG_LEN(sizeof(send_fd));
	p_fds = (int *) CMSG_DATA(p_cmsg);
	*p_fds = send_fd;
	msg.msg_controllen = p_cmsg->cmsg_len;

	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = &vec;
	msg.msg_iovlen = 1;
	msg.msg_flags = 0;
	/* "To pass file descriptors or credentials you need to send/read at
	 * least on byte" (man 7 unix)
	 */
	vec.iov_base = &sendchar;
	vec.iov_len = sizeof(sendchar);
	retval = sendmsg(sock_fd, &msg, 0);
	if(retval != 1) {
		debug_perr("sendmsg");
		return -1;
	}
	return 0;
}

char recv_fd(int sock_fd, int *recv_fd)
{
	int retval;
	struct msghdr msg;
	char recvchar = 0;
	struct iovec vec;
	char cmsgbuf[CMSG_SPACE(sizeof(*recv_fd))];
	struct cmsghdr *p_cmsg;
	int *p_fd;
	vec.iov_base = &recvchar;
	vec.iov_len = sizeof(recvchar);
	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_iov = &vec;
	msg.msg_iovlen = 1;
	msg.msg_control = cmsgbuf;
	msg.msg_controllen = sizeof(cmsgbuf);
	msg.msg_flags = 0;
	/* In case something goes wrong, set the fd to -1 before the syscall */
	p_fd = (int *) CMSG_DATA(CMSG_FIRSTHDR(&msg));
	*p_fd = -1;
	retval = recvmsg(sock_fd, &msg, 0);
	if(retval != 1) {
		debug_perr("recvmsg");
		return (0);
	}

	p_cmsg = CMSG_FIRSTHDR(&msg);
	if(p_cmsg == NULL) {
		write_log(VERBOSE, "no passed fd");
		return (recvchar);
	}
	/* We used to verify the returned cmsg_level, cmsg_type and
	 * cmsg_len here, but Linux 2.0 totally uselessly fails to
	 * fill these in.  */
	p_fd = (int *) CMSG_DATA(p_cmsg);
	*recv_fd = *p_fd;
	if(*recv_fd == -1) {
		write_log(VERBOSE, "no passed fd");
	}
	return recvchar;
}
#endif /*LCACHE || TRANSDATA */

#ifdef USE_LCACHE

void set_write_lock(int fd)
{
	struct flock lck;
	int i;

	lck.l_type = F_WRLCK;
	lck.l_whence = SEEK_SET;
	lck.l_start = 0;
	lck.l_len = 0;
	i = fcntl(fd, F_SETLK, &lck);

	if(i == -1) {
		debug_perr("Setting file lock");
		die(ERROR, "Error setting file lock", 0, 0, -1);
	}
}

int set_read_lock(int fd)
{
	struct flock lck;

	lck.l_type = F_RDLCK;
	lck.l_whence = SEEK_SET;
	lck.l_start = 0;
	lck.l_len = 0;
	return fcntl(fd, F_SETLK, &lck);
}

#endif /*USE_LCACHE */

#ifdef ENABLE_CHANGEPROC
/*Below is taken and altered slightly from proftpd. It is a bit of a hack
 *and therefore disabled by default. We move the environment variables
 *to another location so they are still availiable, and check how much
 *space we have. */
static char **Argv;
extern char *__progname, *__progname_full;
static char *LastArgv;

void init_set_proc_title(int argc, char *argv[], char *envp[])
{
	int i, envpsize;
	extern char **environ;
	char **p;

	for(i = envpsize = 0; envp[i] != NULL; i++)
		envpsize += strlen(envp[i]) + 1;

	if((p = (char **) malloc((i + 1) * sizeof(char *))) != NULL) {
		environ = p;

		for(i = 0; envp[i] != NULL; i++) {
			if((environ[i] = malloc(strlen(envp[i]) + 1)) != NULL)
				strcpy(environ[i], envp[i]);
		}

		environ[i] = NULL;
	}

	/* Run through argv[] and envp[] checking how much contiguous space we
	 * have. This is the area we can overwrite - start stored in Argv,
	 * and end in LastArgv */

	Argv = argv;
	for(i = 0; i < argc; i++)
		if(!i || (LastArgv + 1 == argv[i]))
			LastArgv = argv[i] + strlen(argv[i]);
	for(i = 0; envp[i] != NULL; i++)
		if((LastArgv + 1) == envp[i])
			LastArgv = envp[i] + strlen(envp[i]);

	/* make glibc happy */
	__progname = strdup("frox");
	__progname_full = strdup(argv[0]);
}

void set_proc_title(char *fmt, ...)
{
	va_list msg;
	static char statbuf[8192];
	char *p;
	int i, maxlen = (LastArgv - Argv[0]) - 2;

	va_start(msg, fmt);

	memset(statbuf, 0, sizeof(statbuf));
	vsnprintf(statbuf, sizeof(statbuf), fmt, msg);

	va_end(msg);

	i = strlen(statbuf);

	sprintf(Argv[0], "%s", statbuf);
	p = &Argv[0][i];

	while(p < LastArgv)
		*p++ = '\0';
	Argv[1] = ((void *) 0);
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1