/***************************************
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
data.c -- Code for handling the data connection and picking fds to select.
***************************************/
#include <sys/ioctl.h>
#include <fcntl.h>
#include <syslog.h>
#include "data.h"
#include "control.h"
#include "cache.h"
#include "vscan.h"
#include "transdata.h"
#include "ssl.h"
void client_data_connect(void);
void server_data_connect(void);
void writebuf2client(void);
void writebuf2server(void);
void forwarddata2client(void);
void forwarddata2server(void);
void closecd(void);
void closesd(void);
void bwlimit(int bytes, int rate);
/* ------------------------------------------------------------- **
** Setup fd sets for reading/writing. We always listen to the client's
** control stream, and other fds are selected on depending on state.
** ------------------------------------------------------------- */
int setup_fds(fd_set * reads, fd_set * writes)
{
int n;
FD_ZERO(reads);
FD_ZERO(writes);
FD_SET(info->client_control.fd, reads);
n = info->client_control.fd;
if(info->server_control.fd != -1) {
FD_SET(info->server_control.fd, reads);
if(info->server_control.fd > n)
n = info->server_control.fd;
}
if(info->server_data.fd != -1) {
if(sstr_len(info->client_data.buf) == 0)
FD_SET(info->server_data.fd, reads);
if(sstr_len(info->server_data.buf) != 0)
FD_SET(info->server_data.fd, writes);
if(info->server_data.fd > n)
n = info->server_data.fd;
}
if(info->client_data.fd != -1) {
if(sstr_len(info->server_data.buf) == 0)
FD_SET(info->client_data.fd, reads);
if(sstr_len(info->client_data.buf) != 0)
FD_SET(info->client_data.fd, writes);
if(info->client_data.fd > n)
n = info->client_data.fd;
}
if(info->client_listen != -1) {
FD_SET(info->client_listen, reads);
if(info->client_listen > n)
n = info->client_listen;
}
if(info->server_listen != -1) {
FD_SET(info->server_listen, reads);
if(info->server_listen > n)
n = info->server_listen;
}
return (n);
}
/* ------------------------------------------------------------- **
** Check the fd_sets, and do data connection forwarding if any.
** ------------------------------------------------------------- */
void do_dataforward(fd_set * reads, fd_set * writes)
{
if(info->client_listen != -1 && FD_ISSET(info->client_listen, reads))
client_data_connect();
if(info->server_listen != -1 && FD_ISSET(info->server_listen, reads))
server_data_connect();
if(info->server_data.fd != -1)
if(FD_ISSET(info->server_data.fd, reads))
forwarddata2client();
if(info->server_data.fd != -1)
if(FD_ISSET(info->server_data.fd, writes))
writebuf2server();
if(info->client_data.fd != -1)
if(FD_ISSET(info->client_data.fd, reads))
forwarddata2server();
if(info->client_data.fd != -1)
if(FD_ISSET(info->client_data.fd, writes))
writebuf2client();
}
/* ------------------------------------------------------------- **
** Client just connected to the data line
** ------------------------------------------------------------- */
void client_data_connect()
{
int len = sizeof(info->client_data.address);
write_log(VERBOSE, " Client has connected to proxy data line");
info->client_data.fd = accept(info->client_listen,
(struct sockaddr *) &info->
client_data.address, &len);
if(config.sameaddress) {
if(info->client_data.address.sin_addr.s_addr !=
info->client_control.address.sin_addr.s_addr) {
write_log(ATTACK,
"Blocked %s from connecting to data line",
addr2name(info->client_data.address.
sin_addr));
rclose(&info->client_data.fd);
return;
}
}
il_free(); /* Remove the ipchains entry for intercepting this
* data connection.*/
sstr_empty(info->client_data.buf);
if(info->mode == PACONV)
return;
/* Cache code could have already connected us. */
if(info->server_data.fd == -1)
connect_server_data();
}
/* ------------------------------------------------------------- **
** Server just connected to the data line
** ------------------------------------------------------------- */
void server_data_connect()
{
int len = sizeof(info->server_data.address);
write_log(VERBOSE, " Server has connected to proxy data line");
info->server_data.fd = accept(info->server_listen,
(struct sockaddr *) &info->
server_data.address, &len);
if(config.sameaddress) {
if(info->server_data.address.sin_addr.s_addr !=
info->server_control.address.sin_addr.s_addr) {
write_log(ATTACK,
"Blocked %s from connecting to data line",
addr2name(info->server_data.address.
sin_addr));
ssl_shutdown(&info->ssl_sd);
rclose(&info->server_data.fd);
return;
}
}
sstr_empty(info->server_data.buf);
info->ssl_sd = ssl_initfd(info->server_data.fd, SSL_DATA);
if(info->mode == PACONV)
return;
connect_client_data();
}
/*Connect to server data port. Initialise ssl on it if necessary*/
int connect_server_data()
{
info->server_data.fd =
connect_to_socket(&info->server_data.address,
&config.tcpoutaddr, config.pasvports);
if(info->server_data.fd == -1) {
write_log(ERROR, "Failed to contact server data port");
rclose(&info->client_data.fd);
return (-1);
}
sstr_empty(info->server_data.buf);
/*This shouldn't need to be non blocking as data is only read
*when select says it is there. Non blocking makes ssl really
*tricky*/
/*fcntl(info->server_data.fd, F_SETFL, O_NONBLOCK); */
info->ssl_sd = ssl_initfd(info->server_data.fd, SSL_DATA);
return (0);
}
int connect_client_data()
{
/*FIXME should use server_data.address.sin_addr, but 20 as port */
if(config.transdata)
info->client_data.fd =
transp_connect(info->client_data.address,
info->server_data.address);
else {
info->client_data.fd =
connect_to_socket(&info->client_data.address,
config.listen.s_addr ? &config.
listen : NULL, config.actvports);
}
if(info->client_data.fd == -1) {
write_log(ERROR, "Failed to contact client data port");
ssl_shutdown(&info->ssl_sd);
rclose(&info->server_data.fd);
return (-1);
}
sstr_empty(info->client_data.buf);
fcntl(info->client_data.fd, F_SETFL, O_NONBLOCK);
return (0);
}
/* ------------------------------------------------------------- **
** Forward as much data as possible from client-->server, and store
** the rest in server_data.buf. If maxrate is set then read at most
** 1/4 of a second's worth.
** ------------------------------------------------------------- */
void forwarddata2server()
{
int i;
i = sstr_append_read(info->client_data.fd, info->server_data.buf,
config.maxulrate ? config.maxulrate / 4 : 0);
if(i < 1) { /*Socket close or error */
closecd();
return;
}
if(config.maxulrate)
bwlimit(i, config.maxulrate);
if(info->state != UPLOAD)
sstr_empty(info->server_data.buf);
if(info->server_data.fd != -1)
writebuf2server();
}
/* ------------------------------------------------------------- **
** Forward as much data as possible from server-->client, and store
** the rest in client_data.buf. If maxrate is set then read at most
** 1/4 of a second's worth.
**
** The vscan_inc_data(), cache_inc_data() order is because http
** caching uses cache_inc_data to strip http headers which must be
** done before data reaches the virus scanner, while local caching
** uses it to write file data to file which musn't be done until the
** vscan code has emptied the buffer. Without caching cache_inc_data
** has no effect so it makes no difference. Ugly I know, but I could
** think of no better way...
** ------------------------------------------------------------- */
void forwarddata2client()
{
int i, dlrate;
dlrate = info->cached ? config.cachedlrate : config.maxdlrate;
dlrate = dlrate ? dlrate / 4 : 0;
if(info->ssl_sd)
i = ssl_append_read(info->ssl_sd,
info->client_data.buf, dlrate);
else
i = sstr_append_read(info->server_data.fd,
info->client_data.buf, dlrate);
if(i < 1) { /*Socket close or error */
if(!vscan_switchover())
closesd();
return;
}
if(dlrate)
bwlimit(i, dlrate);
if(info->state != DOWNLOAD)
sstr_empty(info->client_data.buf);
if(config.cachemod && *config.cachemod == 'h') { /*http caching */
cache_inc_data(info->client_data.buf);
vscan_inc(info->client_data.buf);
} else { /*local caching */
vscan_inc(info->client_data.buf);
cache_inc_data(info->client_data.buf);
}
if(info->client_data.fd != -1)
writebuf2client();
}
/* ------------------------------------------------------------- **
** Flush as much buffer as we can
** ------------------------------------------------------------- */
void writebuf2client()
{
int i;
i = sstr_write(info->client_data.fd, info->client_data.buf, 0);
if(i == -1) {
if(errno == EAGAIN)
return;
if(errno != EPIPE)
debug_perr("writebuf2client()");
closecd();
return;
}
sstr_split(info->client_data.buf, NULL, 0, i);
if(sstr_len(info->client_data.buf) == 0 && info->server_data.fd == -1)
closecd();
debug(".");
}
/* ------------------------------------------------------------- **
** Flush as much buffer as we can
** ------------------------------------------------------------- */
void writebuf2server()
{
int i;
if(info->ssl_sd)
i = ssl_write(info->ssl_sd, info->server_data.buf);
else
i = sstr_write(info->server_data.fd, info->server_data.buf,
0);
if(i == -1) {
if(errno == EAGAIN)
return;
if(errno != EPIPE)
debug_perr("writebuf2server()");
closesd();
return;
}
sstr_split(info->server_data.buf, NULL, 0, i);
if(sstr_len(info->server_data.buf) == 0 && info->client_data.fd == -1)
closesd();
debug(".");
}
/* ------------------------------------------------------------- **
** Called for all data transfers. If more bytes transferred since the
** last call than permitted in the elapsed time then pause. The value
** of last doesn't get changed between transfers even if there's a big
** pause in between, but this only means that the first BUF_LEN bytes
** of the new transfer get passed through immediately. Shouldn't be a
** problem. Maybe I can even call it a feature -- fast transfer of
** small files. ;)
**
** We sleep only once there is ALLOW_DISCREPANCY seconds worth of
** sleeping to do. If this is too small we are really inaccurate in
** our transfer rate - nanosleep() may have a granularity of around
** 10ms. If it is too big then the transfer speed will wax and wane a
** bit but be more accurate in the long run. Not sure what the best
** value is.
**
** bytes is no. of bytes waiting to be transferred. rate is bytes/sec
** ------------------------------------------------------------- */
#define ALLOW_DISCREPANCY 0.5
void bwlimit(int bytes, int rate)
{
#ifdef HAVE_NANOSLEEP
static struct timeval last = { 0, 0 };
static int bytecnt = 0;
struct timeval now;
double actualtime, mintime;
if(!last.tv_sec) {
gettimeofday(&last, NULL);
return;
}
bytecnt += bytes;
gettimeofday(&now, NULL);
actualtime = (now.tv_sec - last.tv_sec) +
(double) (now.tv_usec - last.tv_usec) / (double) 1000000;
mintime = (double) bytecnt / (double) rate;
if(actualtime < mintime) {
struct timespec ts;
double sleep_time = mintime - actualtime;
if(sleep_time < ALLOW_DISCREPANCY)
return;
ts.tv_sec = (time_t) sleep_time;
ts.tv_nsec = (long) ((sleep_time - ts.tv_sec) *
(double) 1000000000);
nanosleep(&ts, &ts);
gettimeofday(&last, NULL);
bytecnt = 0;
debug("|");
} else {
gettimeofday(&last, NULL);
bytecnt = 0;
}
#endif
}
/* ------------------------------------------------------------- **
** Close client data socket, and if there is nothing left to flush
** close the other one too. With downloads we only come here if the
** client aborted - we need to abort virus scanning.
** ------------------------------------------------------------- */
void closecd(void)
{
write_log(VERBOSE, "Closing client data connection");
info->state = NEITHER;
rclose(&info->client_data.fd);
if(sstr_len(info->server_data.buf) == 0) {
write_log(VERBOSE, "Closing server data connection");
ssl_shutdown(&info->ssl_sd);
rclose(&info->server_data.fd);
vscan_abort();
cache_close_data();
}
xfer_log();
}
/* ------------------------------------------------------------- **
** Close data socket, and if there is nothing left to flush close the
** other one too.
** ------------------------------------------------------------- */
void closesd(void)
{
write_log(VERBOSE, "Closing server data connection");
ssl_shutdown(&info->ssl_sd);
rclose(&info->server_data.fd);
info->state = NEITHER;
vscan_end();
cache_close_data();
if(sstr_len(info->client_data.buf) == 0) {
write_log(VERBOSE, "Closing client data connection");
rclose(&info->client_data.fd);
}
xfer_log();
}
syntax highlighted by Code2HTML, v. 0.9.1