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