/***************************************
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
ccp.c -- ftp-proxy like command control program. This file is
something of a mess now because it contains two completely
different implementations plus wrappers to call one or the
other depending on the "UseOldCCP" config file variable.
The ccp_old_... functions will die in the next major
release.
Note that the interface here is low level. A badly written
ccp script can cause the connection to hang. CCP scripts
will also get relatively unsanitised data. Commands should
fit ftp command syntax, and be less than MAX_LINE_LEN bytes
long with all non printable characters purged, but that is
all that is guaranteed.
***************************************/
#include <sys/wait.h>
#include <stdio.h>
#include "common.h"
#include "control.h"
#include "ccp.h"
#ifndef HAVE_SETENV
#define setenv(A, B, C)
#endif
static int exec_ccp_daemon(void);
static void check_serverok(void);
static int process_reply(sstr * cmd, sstr * arg);
static void ccp_new_changedest(void);
static int ccp_new_allowcmd(sstr * cmd, sstr * arg);
static int ccp_new_allowmsg(int *code, sstr * msg);
static void ccp_old_init(void);
static int ccp_old_allowcmd(sstr * cmd, sstr * arg);
static int stdin_fd;
static FILE *stdout_fp;
static int dont_reenter = 0;
void ccp_changedest(void)
{
if(config.oldccp)
ccp_old_init();
else
ccp_new_changedest();
}
int ccp_allowcmd(sstr * cmd, sstr * arg)
{
if(config.oldccp)
return ccp_old_allowcmd(cmd, arg);
else
return ccp_new_allowcmd(cmd, arg);
}
int ccp_allowmsg(int *code, sstr * msg)
{
if(config.oldccp)
return TRUE;
else
return ccp_new_allowmsg(code, msg);
}
/*************************
**** New interface ******
*************************/
static void ccp_new_changedest(void)
{
if(!config.ccpcmd)
return;
exec_ccp_daemon();
check_serverok();
}
static int exec_ccp_daemon(void)
{
int stdin_fds[2], stdout_fds[2];
static char *argv[2];
argv[0] = config.ccpcmd;
argv[1] = NULL;
pipe(stdin_fds);
pipe(stdout_fds);
switch (fork()) {
case 0: /*Child */
close(stdout_fds[0]);
close(stdin_fds[1]);
dup2(stdout_fds[1], 1);
dup2(stdin_fds[0], 0);
close(stdout_fds[1]);
close(stdin_fds[0]);
execvp(argv[0], argv);
die(ERROR, "Failed to exec ccp prog", 0, NULL, -1);
case -1:
debug_err("Error");
die(ERROR, "Unable to fork", 0, NULL, -1);
default:
break;
}
close(stdout_fds[1]);
close(stdin_fds[0]);
stdout_fp = fdopen(stdout_fds[0], "r");
stdin_fd = stdin_fds[1];
return 0;
}
static void check_serverok(void)
{
sstr *buf;
buf = sstr_init(MAX_LINE_LEN + 10);
sstr_apprintf(buf, "I %s ",
inet_ntoa(info->client_control.address.sin_addr));
sstr_apprintf(buf, "%s %s",
inet_ntoa(info->server_control.address.sin_addr),
sstr_len(info->server_name) ?
sstr_buf(info->server_name) : "X");
sstr_apprintf(buf, " %d\n",
htons(info->server_control.address.sin_port));
sstr_write(stdin_fd, buf, 0);
sstr_free(buf);
process_reply(NULL, NULL);
}
static int ccp_new_allowcmd(sstr * cmd, sstr * arg)
{
sstr *buf;
if(!config.ccpcmd)
return TRUE;
if(dont_reenter)
return dont_reenter--;
buf = sstr_init(MAX_LINE_LEN + 10);
sstr_apprintf(buf, "C %s %s\n", sstr_buf(cmd), sstr_buf(arg));
sstr_write(stdin_fd, buf, 0);
sstr_free(buf);
if(process_reply(cmd, arg) != 'C')
return TRUE;
dont_reenter = 1;
send_message(sstr_atoi(cmd), arg);
return FALSE;
}
static int ccp_new_allowmsg(int *code, sstr * msg)
{
sstr *buf;
if(!config.ccpcmd)
return TRUE;
if(dont_reenter)
return dont_reenter--;
buf = sstr_init(MAX_LINE_LEN + 10);
sstr_apprintf(buf, "S %d %s\n", *code, sstr_buf(msg));
sstr_write(stdin_fd, buf, 0);
switch (process_reply(buf, msg)) {
case 'S':
dont_reenter = 1;
parse_client_cmd(buf, msg);
sstr_free(buf);
return FALSE;
case 'C':
*code = sstr_atoi(buf);
sstr_free(buf);
return TRUE;
default:
return TRUE;
}
}
static int process_reply(sstr * cmd, sstr * arg)
{
sstr *buf;
buf = sstr_init(MAX_LINE_LEN + 10);
for(;;) {
sstr_fgets(buf, stdout_fp);
switch (sstr_getchar(buf, 0)) {
case 'R': /*Redirect */
sstr_split(buf, NULL, 0, 2);
sstr_split(buf, NULL, sstr_len(buf) - 1, 1);
inet_aton(sstr_buf(buf),
&info->server_control.address.sin_addr);
info->final_server_address =
info->server_control.address;
sstr_free(buf);
return 0;
case 'S': /* A command to write to the server */
sstr_split(buf, NULL, 0, 2);
sstr_token(buf, cmd, " ", 0);
sstr_token(buf, arg, "\r\n", 0);
sstr_free(buf);
return 'S';
case 'C': /* A message to write to the client */
sstr_split(buf, NULL, 0, 2);
sstr_token(buf, cmd, " ", 0);
sstr_token(buf, arg, "\r\n", 0);
sstr_free(buf);
return 'C';
case 'L': /* A log message. Action to follow */
sstr_split(buf, NULL, 0, 2);
sstr_split(buf, NULL, sstr_len(buf) - 1, 1);
write_log(IMPORT, sstr_buf(buf));
break;
case 'Q': /* Close session */
die(ERROR, "CCP requested exit. Closing session", 0,
NULL, 0);
break;
case 'X': /* No change */
sstr_free(buf);
return 0;
default:
die(ERROR, "Unknown code from CCP progeam", 0, 0, 0);
break;
}
}
}
/*************************
**** Old interface ******
*************************/
/*This will all go sometime soon. Maybe it will be replaced with a
wrapper to ues around legacy ccps.*/
#define EPR "FROX_"
static int exec_old_ccp(void);
static void ccp_old_init(void)
{
sstr *buf;
if(!config.ccpcmd)
return;
buf = sstr_init(MAX_LINE_LEN);
setenv(EPR "CLIENT",
inet_ntoa(info->client_control.address.sin_addr), 1);
setenv(EPR "SERVER",
inet_ntoa(info->server_control.address.sin_addr), 1);
setenv(EPR "SERVERNAME", sstr_buf(info->server_name), 1);
sstr_apprintf(buf, "%lu-%u", time(NULL), getpid());
setenv(EPR "SESSION", sstr_buf(buf), 1);
setenv(EPR "COMMAND", "+NEW", 1);
exec_old_ccp();
sstr_free(buf);
}
static int ccp_old_allowcmd(sstr * cmd, sstr * arg)
{
if(!config.ccpcmd)
return TRUE;
if(!sstr_cmp2(cmd, "USER")) {
setenv(EPR "SERVER",
inet_ntoa(info->server_control.address.sin_addr), 1);
setenv(EPR "SERVERNAME", sstr_buf(info->server_name), 1);
setenv(EPR "USER", sstr_buf(arg), 1);
}
setenv(EPR "COMMAND", sstr_buf(cmd), 1);
setenv(EPR "PARAMETER", sstr_buf(arg), 1);
return (exec_old_ccp());
}
static int exec_old_ccp(void)
{
int log_fds[2], message_fds[2], i;
static char *argv[2];
sstr *buf;
argv[0] = config.ccpcmd;
argv[1] = NULL;
pipe(log_fds);
pipe(message_fds);
switch (fork()) {
case 0: /*Child */
close(log_fds[0]);
close(message_fds[0]);
dup2(log_fds[1], 1);
dup2(message_fds[1], 2);
execvp(argv[0], argv);
die(ERROR, "Failed to exec ccp prog", 0, NULL, 0);
case -1:
close(log_fds[1]);
close(message_fds[1]);
debug_err("Error");
break;
/*FIXME*/ default:
break;
}
wait(&i);
if(!WIFEXITED(i)) {
close(log_fds[0]);
close(message_fds[0]);
die(ERROR, "CCP program exited abnormally", 0, NULL, -1);
}
buf = sstr_init(MAX_LINE_LEN);
sstr_append_read(log_fds[0], buf, MAX_LINE_LEN);
if(sstr_len(buf) > 0)
write_log(IMPORT, sstr_buf(buf));
close(log_fds[0]);
sstr_empty(buf);
sstr_append_read(message_fds[0], buf, MAX_LINE_LEN);
if(sstr_len(buf) > 0)
sstr_write(info->client_control.fd, buf, 0);
close(message_fds[0]);
sstr_free(buf);
switch (WEXITSTATUS(i)) {
case 0:
return TRUE;
case 1:
return FALSE;
case 2:
die(ERROR, "CCP requested exit. Closing session", 0, NULL, 0);
}
die(ERROR, "CCP exited with unknown exit code", 0, NULL, -1);
return FALSE;
}
syntax highlighted by Code2HTML, v. 0.9.1