/***************************************
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
control.c -- forward and parse the control stream.
***************************************/
#include <sys/ioctl.h>
#include <fcntl.h>
#include <ctype.h>
#include "control.h"
#include "common.h"
#include "ftp-cmds.h"
#include "data.h"
#include "ntp.h"
#include "ccp.h"
#include "vscan.h"
#include "cache.h"
#include "os.h"
#include "ssl.h"
int extract_clientcmd(sstr * buf, sstr ** cmd, sstr ** arg);
int extract_servercmd(sstr * buf, int *code, sstr ** msg);
static session_info *init_info(int fd, struct sockaddr_in source);
static void connect_to_server(void);
/* ------------------------------------------------------------- **
** Sets up the control connection to the server and initialises
** session info.
** ------------------------------------------------------------- */
void init_session(int fd, struct sockaddr_in source)
{
info = init_info(fd, source);
write_log(INFO, "Connect from %s",
sstr_buf(addr2name(info->client_control.address.sin_addr)));
/*FIXME have a login function which deals with ntp and cache */
ccp_changedest();
ntp_changedest();
info->final_server_address = info->server_control.address;
write_log(INFO, "... to %s(%s)",
inet_ntoa(info->final_server_address.sin_addr),
sstr_buf(info->server_name));
#ifdef ENABLE_CHANGEPROC
set_proc_title("frox: %s <-> %s",
inet_ntoa(info->client_control.address.sin_addr),
inet_ntoa(info->final_server_address.sin_addr));
#endif
connect_to_server();
ntp_senduser();
run_proxy();
}
static void connect_to_server(void)
{
/*Check for loops. Won't work if Listen undefined */
if(info->server_control.address.sin_addr.s_addr
== config.listen_address.sin_addr.s_addr &&
info->server_control.address.sin_port
== config.listen_address.sin_port)
die(ERROR, "Attempt to connect to self. "
"Do you need to set DoNTP to yes?",
421, "Proxy tried to loop. Closing connection", 0);
if(info->server_control.address.sin_addr.s_addr == 0) {
if(!config.ntp)
die(ERROR,
"Frox unable to determine destination address. "
"Do you need to set DoNTP to yes?",
501, "Unable to contact server", 0);
else if(config.ntpdest.sin_addr.s_addr)
die(ERROR,
"Frox unable to determine destination address. "
"Try commenting out NTPAddress",
501, "Unable to contact server", 0);
else
die(ERROR,
"Frox unable to determine detination address. "
"See FAQ for troubleshooting.", 501,
"Unable to contact server", 0);
}
resolve_addr(&info->final_server_address.sin_addr, info->server_name);
if(!config_connectionok(&info->client_control.address,
&info->final_server_address,
info->username ? sstr_buf(info->
username) : 0))
die(ATTACK, "Denied by ACLs.", 501, "Connection denied. Bye",
0);
if(config.ftpproxy.sin_addr.s_addr)
info->server_control.address = config.ftpproxy;
write_log(VERBOSE, "Connecting to server...");
info->server_control.fd =
connect_to_socket(&info->server_control.address,
&config.tcpoutaddr, config.contports);
if(info->server_control.fd == -1)
die(ERROR, "Connection closed -- unable to contact server",
501, "Proxy unable to contact ftp server", 0);
write_log(VERBOSE, " OK");
if(config.loglevel >= VERBOSE) { /*Save the overhead of DNS lookups */
write_log(VERBOSE, "Apparent address = %s",
sstr_buf(addr2name
(info->apparent_server_address.sin_addr)));
write_log(VERBOSE, "Real address = %s",
sstr_buf(addr2name
(info->final_server_address.sin_addr)));
write_log(VERBOSE, "Proxy address = %s",
sstr_buf(addr2name
(info->server_control.address.sin_addr)));
}
ssl_init();
}
/* ------------------------------------------------------------- **
** Allocate and initialise session_info structure. Also determine
** destination address if possible.
** ------------------------------------------------------------- */
static session_info *init_info(int fd, struct sockaddr_in source)
{
session_info *i;
i = (session_info *) malloc(sizeof(session_info));
if(i == NULL)
die(ERROR, "Malloc failed", 0, NULL, -1);
memset(i, 0, sizeof(session_info));
i->server_listen = i->client_listen = -1;
i->client_data.fd = i->server_data.fd = -1;
i->server_control.fd = -1;
i->server_control.buf = sstr_init(BUF_LEN);
i->client_control.buf = sstr_init(BUF_LEN);
i->server_data.buf = sstr_init(DATA_BUF_LEN);
i->client_data.buf = sstr_init(DATA_BUF_LEN);
i->last_command = sstr_init(0);
i->server_name = sstr_init(0);
i->username = sstr_init(0);
i->passwd = sstr_init(0);
i->strictpath = sstr_init(0);
i->cmd_arrays[CACHE_CMDS] = NULL;
i->cmd_arrays[FTP_CMDS] = ftp_cmds;
i->filename = sstr_init(0);
if(config.apconv)
i->mode = APCONV;
else
i->mode = ACTIVE;
i->state = NEITHER;
i->greeting = AWAITED;
i->client_control.address = source;
i->client_control.fd = fd;
get_orig_dest(fd, &i->server_control.address);
i->apparent_server_address = i->server_control.address;
return i;
}
/* ------------------------------------------------------------- **
** The main loop for each session.
** ------------------------------------------------------------- */
void run_proxy()
{
int i;
do {
i = get_control_line(GET_BOTH);
if(i & GET_CLNT)
client_control_forward();
if(i & GET_SRVR)
server_control_forward();
} while(TRUE);
}
/* ------------------------------------------------------------- **
** Forward buffered control stream data from client -> server.
** ------------------------------------------------------------- */
void client_control_forward()
{
int i;
sstr *cmd, *arg;
while((i =
extract_clientcmd(info->client_control.buf, &cmd, &arg)) == 0)
parse_client_cmd(cmd, arg);
if(i < 0)
die(ATTACK,
"Client is sending us a badly formed control stream.",
421, "You are sending me rubbish. Goodbye.", -1);
}
/* ------------------------------------------------------------- **
** Forward buffered control stream data from server -> client.
** ------------------------------------------------------------- */
void server_control_forward()
{
int i, code;
sstr *arg;
while((i =
extract_servercmd(info->server_control.buf, &code, &arg)) == 0)
if(!ssl_parsed_reply(code, arg) && /*Always returns 0 */
!cache_parsed_reply(code, arg) &&
!vscan_parsed_reply(code, arg))
send_message(code, arg);
if(i < 0)
die(ATTACK,
"Server is sending us a badly formed control stream.",
421,
"FTP server is sending you rubbish! Closing connection.",
-1);
}
/* ------------------------------------------------------------- **
** Work through the arrays of commands to find a matching function.
** Currently there are only two arrays - one with specific functions
** for during caching, and one with the defaults. An empty entry in
** the list should match all commands.
** ------------------------------------------------------------- */
void parse_client_cmd(sstr * cmd, sstr * arg)
{
struct cmd_struct *p;
int i;
if(!ccp_allowcmd(cmd, arg))
return;
sstr_cpy(info->last_command, cmd);
for(i = 0; i < NUM_CMD_ARRAYS; i++) {
if(!info->cmd_arrays[i])
continue;
for(p = info->cmd_arrays[i]; p && p->cmd; p++) {
if(!*p->name || !sstr_casecmp2(cmd, p->name)) {
p->cmd(cmd, arg);
return;
}
}
}
write_log(ERROR, "Command %s not implemented", sstr_buf(cmd));
send_cmessage(502, "Command not implemented.");
}
/* ------------------------------------------------------------- **
** Get one command from client.
** ------------------------------------------------------------- */
void get_command(sstr ** cmd, sstr ** arg)
{
int i;
while((i =
extract_clientcmd(info->client_control.buf, cmd, arg)) == 1)
if(get_control_line(GET_CLNT) <= 0)
die(ERROR, "Arrrghh. Shouldn't be here", 0, NULL, -1);
}
/* ------------------------------------------------------------- **
** Get one reply from server. Ignore multi-line replies.
** ------------------------------------------------------------- */
void get_message(int *code, sstr ** msg)
{
do {
switch (extract_servercmd
(info->server_control.buf, code, msg)) {
case -1:
die(ATTACK,
"Server is sending us a badly formed control stream.",
421, "FTP server is sending you rubbish!"
"Closing connection.", -1);
case 0:
#ifdef DEBUG
fprintf(stderr, " s: \033[32m%d %s\033[37m\n",
*code, msg ? sstr_buf(*msg) : "");
#endif
if(*code > 0)
return;
continue;
case 1:
if(get_control_line(GET_SRVR) <= 0)
die(ERROR, "Arrghh - shoudln't be here",
0, NULL, -1);
}
} while(TRUE);
}
void send_cmessage(int code, const char *msg)
{
sstr *smsg;
smsg = sstr_dup2(msg);
send_message(code, smsg);
sstr_free(smsg);
}
void send_ccommand(const char *cmd, const char *arg)
{
sstr *scmd, *sarg;
scmd = sstr_dup2(cmd);
sarg = sstr_dup2(arg);
send_command(scmd, sarg);
sstr_free(scmd);
sstr_free(sarg);
}
/* ------------------------------------------------------------- **
** Send command to server
** ------------------------------------------------------------- */
void send_command(sstr * cmd, sstr * arg)
{
sstr *buf;
buf = sstr_init(MAX_LINE_LEN + 10);
sstr_cat(buf, cmd);
if(sstr_len(arg) != 0) {
sstr_ncat2(buf, " ", 1);
sstr_cat(buf, arg);
}
sstr_ncat2(buf, "\r\n", 2);
#ifdef DEBUG
fprintf(stderr, " C: \033[31m%s\033[37m", sstr_buf(buf));
#else
write_log(VERBOSE, " C: %s", sstr_buf(buf));
#endif
if(info->ssl_sc)
ssl_write(info->ssl_sc, buf);
else
sstr_write(info->server_control.fd, buf, 0);
sstr_free(buf);
}
/* ------------------------------------------------------------- **
** Send message to client
** ------------------------------------------------------------- */
void send_message(int code, sstr * msg)
{
sstr *buf;
if(!ccp_allowmsg(&code, msg))
return;
buf = sstr_init(MAX_LINE_LEN + 10);
if(code != 0)
sstr_apprintf(buf, "%d%c", abs(code), code > 0 ? ' ' : '-');
sstr_cat(buf, msg);
sstr_ncat2(buf, "\r\n", 2);
#ifdef DEBUG
fprintf(stderr, " S: \033[34m%s\033[37m", sstr_buf(buf));
#else
write_log(VERBOSE, " S: %s", sstr_buf(buf));
#endif
sstr_write(info->client_control.fd, buf, 0);
sstr_free(buf);
}
int read_srvrctrl_data(void)
{
if(info->ssl_sc)
return ssl_append_read(info->ssl_sc,
info->server_control.buf, 0);
else
return sstr_append_read(info->server_control.fd,
info->server_control.buf, 0);
}
/* ------------------------------------------------------------- **
** Central select bit. Deals with data connection
** forwarding/listening, and quits on ctrl connection close. Once
** there is a complete line read from one of the control connctions
** specified in "which" (GET_SRVR, GET_CTRL, or GET_SRVR|GET_CTRL) we
** return.
**
** The line read into the control connection buffer on function return
** contains a newline (\n), and is NULL terminated at some point
** beyond that. No other checking has been done on it.
**
** Return value is one of GET_SRVR, GET_CTRL or GET_SRVR|GET_CTRL.
** ------------------------------------------------------------- */
int get_control_line(int which)
{
int ret = 0, i;
fd_set reads, writes;
do {
i = setup_fds(&reads, &writes);
alarm(config.timeout);
if(select(i + 1, &reads, &writes, NULL, NULL) == -1) {
if(errno == EINTR)
continue;
debug_perr("select");
die(0, NULL, 0, NULL, -1);
}
do_dataforward(&reads, &writes);
if(FD_ISSET(info->client_control.fd, &reads)) {
i = sstr_append_read(info->client_control.fd,
info->client_control.buf, 0);
if(i == 0)
die(INFO, "Client closed connection",
0, NULL, 0);
if(i < 0)
die(ATTACK,
"Client flooding control connection", 421,
"You're sending rubbish. Goodbye", -1);
if(sstr_hasline(info->client_control.buf))
ret |= GET_CLNT;
}
if(info->server_control.fd != -1 &&
FD_ISSET(info->server_control.fd, &reads)) {
i = read_srvrctrl_data();
if(i == 0) {
if(!cache_transferring())
die(ERROR,
"Server closed the control connection",
0, NULL, 0);
else {
rclose(&info->server_control.fd);
write_log(INFO,
"Server closed connection. Keeping going until cache done");
}
}
if(i < 0)
die(ATTACK,
"Server flooding the control connection",
421, "Server is sending rubbish."
"Closing connection", -1);
if(sstr_hasline(info->server_control.buf))
ret |= GET_SRVR;
}
} while(!(ret & which));
return (ret);
}
/***************************************************************************
*
* Functions which read the raw control stream --- be careful
*
**************************************************************************/
/* ------------------------------------------------------------- **
** Removes one line of control stream from buf (up to '\r\n'). <= 5
** chars are returned in cmd (up to 4 + \0), and <= MAX_LINE_LEN in
** arg. Must accept any NULL terminated buf and give sane output.
**
** returns 0 on success, 1 if there is not a complete line in
** buf, and -X on non-sane buf. contents of buf unchanged on
** return(1), undefined on return(-X)
** ------------------------------------------------------------- */
int extract_clientcmd(sstr * buf, sstr ** pcmd, sstr ** parg)
{
static sstr *cmd = NULL, *arg = NULL;
if(!cmd)
cmd = sstr_init(4);
if(!arg)
arg = sstr_init(MAX_LINE_LEN);
if(pcmd)
*pcmd = cmd;
if(parg)
*parg = arg;
if(!sstr_hasline(buf))
return (1);
switch (sstr_token(buf, cmd, " \t\n\r", 0)) {
case -1: /*Token doesn't fit in cmd */
return (-1);
case ' ':
case '\t':
sstr_token(buf, arg, "\r\n", 0);
break;
default: /*end of line */
sstr_empty(arg);
}
if(!config.nonasciiok)
sstr_makeprintable(arg, '?');
return (0);
}
/* ------------------------------------------------------------- **
** As extract_clientcmd, but returns the code as an int.
** If code==0 then we are in a multiline. code<0 means we are
** starting a multiline.
** ------------------------------------------------------------- */
int extract_servercmd(sstr * buf, int *code, sstr ** pmsg)
{
static int multiline = 0;
static sstr *scode = NULL, *msg = NULL;
if(!scode)
scode = sstr_init(4);
if(!msg)
msg = sstr_init(MAX_LINE_LEN);
if(pmsg)
*pmsg = msg;
if(!sstr_hasline(buf))
return (1);
if(sstr_token(buf, msg, "\r\n", 0) == -1)
return (-1);
if(!multiline ||
(sstr_atoi(msg) == multiline && sstr_getchar(msg, 3) == ' ')) {
multiline = 0;
if(sstr_split(msg, scode, 0, 4) == -1)
return (-1);
*code = sstr_atoi(scode);
switch (sstr_getchar(scode, 3)) {
case '-':
multiline = *code;
*code = -*code;
case ' ':
break;
default:
return (-1);
}
} else {
*code = 0;
}
if(!config.nonasciiok)
sstr_makeprintable(msg, '?');
return (0);
}
syntax highlighted by Code2HTML, v. 0.9.1