#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
/*
* This is a CCP for frox. The sample code is licensed under the GPL
* as seen in COPYING. This CCP tries to implement transparent
* redirection of ftp clients to local mirrors. The mirrors are
* defined in..., well the mirrors structure. First field is the host
* name of the remote server, second is the host name of the mirror,
* and third is the path on the mirror which corresponds to root on
* the server.
*
* I try to make the client unaware that there are directories below
* them. "cdup", and "cd .." are blocked, and the hidden part of the
* path is stripped from the reply to a "pwd" request. I haven't
* tested extensively though. Also I suspect bad things will happen if
* one of the mirror servers doesn't log you in to the root directory.
*
* I suspect that this program would be a third the length if it were
* written in Perl. :) */
/*
* Basically if we read an "I" it is followed by session initialisation
* data, a "C" and it is followed by a message from the client, and an
* "S" and it is followed by a message from the server.
*
* If we write an "X" frox will forward on the message it just sent
* us. Anything else and we are responsible for doing it ourselves. If
* we write "S ..." we send a message to the server, and "C ....." a
* message to the client. "L ......" sends a log message, and should be
* followed by an action. "Q" tells frox to exit this session.
*
* We can't use "C" or "S" to reply to an "I", but we can reply with
* "R ......." where the R is followed by an IP address. Frox will redirect
* the session to this IP.
*/
struct mirror_table{
const char *remote;
const char *mirror;
const char *dir;
struct in_addr *r_addrs, *m_addrs;
int n_raddrs, n_maddrs;
};
struct mirror_table mirrors[] = {
{ "ftp.debian.org", "localhost", "/pub/mirrors/debian" },
{ "ftp.tucows.com", "ftp.mirror.localnet", "/pub/mirrors/tucows" },
{ 0, 0, 0}
};
char *edit_cmds[] = {"SMNT", "APPE", "RNFR", "RNTO", "DELE", "RMD",
"MKD", "RETR", "SIZE", "STOR", "LIST", "NLST",
"STAT", "CWD", "CDUP", 0};
int edit_codes[] = {257, 150, 0};
int m_no=-1;
#define BLEN 1024 /*Must be greater than frox's MAX_LINE_LEN*/
#define ccp_command(a, b) printf("S %s %s\n", a, b)
#define ccp_message(a, b) printf("C %d %s\n", a, b)
#define ccp_passthrough() printf("X\n")
#define ccp_redirect(a) printf("R %s\n", a)
#define ccp_log(a) printf("L %s\n", a)
#define ccp_quit() printf("Q\n")
int init_mirrors(void);
int check_ip(char *buf);
void change_ip(void);
int edit_cmd(char *buf);
int edit_msg(char *buf);
int confirm_cwd(void);
int allow_path(char *arg, int *l);
int main(void)
{
char buf[BLEN];
if(init_mirrors()==-1) {
ccp_log("CCP unable to resolve mirrors list");
ccp_quit();
return -1;
}
while(fgets(buf, 1023, stdin)) {
switch(*buf) {
case 'C':
if(m_no<0) ccp_passthrough();
else edit_cmd(buf);
break;
case 'S':
if(m_no<0) ccp_passthrough();
else edit_msg(buf);
break;
case 'I':
m_no = check_ip(buf);
if(m_no<0) ccp_passthrough();
else change_ip();
break;
}
fflush(stdout);
}
return 0;
}
int init_mirrors(void)
{
int i, j;
char **p;
struct hostent *hostinfo;
for(i=0;mirrors[i].remote; i++) {
hostinfo = gethostbyname(mirrors[i].remote);
if (!hostinfo || hostinfo->h_addrtype != AF_INET)
return(-1);
for(p=hostinfo->h_addr_list, j=0; *p; p++, j++);
mirrors[i].r_addrs=malloc(sizeof(struct in_addr) * j);
mirrors[i].n_raddrs=j;
for(j=0;j<mirrors[i].n_raddrs;j++) {
mirrors[i].r_addrs[j] = *((struct in_addr *)
hostinfo->h_addr_list[j]);
}
hostinfo = gethostbyname(mirrors[i].mirror);
if (!hostinfo || hostinfo->h_addrtype != AF_INET)
return(-1);
for(p=hostinfo->h_addr_list, j=0; *p; p++, j++);
mirrors[i].m_addrs=malloc(sizeof(struct in_addr) * j);
mirrors[i].n_maddrs=j;
for(j=0;j<mirrors[i].n_maddrs;j++) {
mirrors[i].m_addrs[j] = *((struct in_addr *)
hostinfo->h_addr_list[j]);
}
}
return 0;
}
int check_ip(char *buf)
{
int i, j;
char tmp[20];
struct in_addr match;
sscanf(buf, "I %*s %s %*s", tmp);
inet_aton(tmp, &match);
for(i=0;mirrors[i].remote; i++) {
for(j=0;j<mirrors[i].n_raddrs;j++) {
if(match.s_addr==mirrors[i].r_addrs[j].s_addr)
return i;
}
}
return -1;
}
void change_ip(void)
{
char buf[BLEN];
int j;
j = (int) (mirrors[m_no].n_maddrs * rand()/(RAND_MAX+1.0));
sprintf(buf, "Redirecting connection for %s to local mirror",
mirrors[m_no].remote);
ccp_log(buf);
ccp_redirect(inet_ntoa(mirrors[m_no].m_addrs[j]));
}
/* If command does not involve a path and does not change directory,
send it through unchanged. Otherwise check the path to see it
doesn't attempt to "../" above our fake root directory. Finally we
edit any absolute or empty paths by prepending the path of the fake
root directory.*/
int edit_cmd(char *buf)
{
static int level=0, inroot=1;
int newlevel=level;
char *cmd, *arg, **p, tmp[BLEN];
cmd=buf+2;
for(arg = cmd;*arg!=' ';arg++);
*arg++=0;
if(strlen(arg)>0)
arg[strlen(arg)-1]=0; /*Strip trailing \n*/
for(p=edit_cmds;*p;p++)
if(!strcmp(*p, cmd)) break;
if(!*p) {
ccp_passthrough();
return 0;
}
if(!allow_path(arg, &newlevel)) {
ccp_message(550, "No such directory");
return -1;
}
if(!strcmp(cmd, "CDUP")) {
if(!level) {
ccp_message(550, "No such directory");
return -1;
}
newlevel=level-1;
}
/*We need to prepend the fake root path for absolute paths,
for relative paths where we have not yet done a chdir into
the fake root tree, and for empty paths where the server
will interpret this as meaning the current directory*/
if((!inroot && *arg!='/') || (!strcmp(cmd, "STAT") && *arg==0)) {
ccp_passthrough();
} else {
strcpy(tmp, mirrors[m_no].dir);
strcat(tmp, "/");
strcat(tmp, arg);
ccp_command(cmd, tmp);
}
if(!strcmp(cmd, "CDUP") || !strcmp(cmd, "CWD")) {
if(confirm_cwd()) {
level = newlevel;
inroot = 0;
}
}
return 1;
}
int allow_path(char *arg, int *l)
{
char *p=arg;
if(*p=='/') {
*l=0;
p++;
}
while(*p){
if(!strncmp(p, "../", 3) || !strcmp(p, "..")) (*l)--;
else (*l)++;
if(*l<0) return 0;
for(;*p && *p!='\n' && *p!='/'; p++);
if(!*p) break;
while(*++p=='/');
}
return 1;
}
int confirm_cwd(void)
{
int code;
char buf[BLEN];
fflush(stdout);
if(!fgets(buf, 1023, stdin))
exit(0);
if(*buf != 'S') {
ccp_log("L CCP was expecting an S. Sorry - exiting");
ccp_quit();
exit(0);
}
code=atoi(buf+2);
ccp_passthrough();
return (code<300);
}
int edit_msg(char *buf)
{
int *cp, i, code;
char *cmd, *arg, *p;
cmd=buf+2;
for(arg = cmd;*arg!=' ';arg++);
*arg++=0;
if(strlen(arg)>0)
arg[strlen(arg)-1]=0; /*Strip trailing \n*/
code=atoi(cmd);
for(cp=edit_codes;*cp;cp++)
if(*cp==code) break;
if(!*cp) {
ccp_passthrough();
return 0;
}
p=strstr(arg, mirrors[m_no].dir);
if(!p) {
ccp_passthrough();
return 0;
}
i=strlen(mirrors[m_no].dir);
if(*(p+i)!='/') {
p++;
i--;
}
memmove(p, p+i, strlen(p+i)+1);
ccp_message(code, arg);
return 1;
}
syntax highlighted by Code2HTML, v. 0.9.1