Previous patch

Home

Next patch

./src/control.c

Patch: update to version 0.7.2

+/***************************************
+
+    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 "ccp.h"
+#include "os.h"
+
+int extract_clientcmd(sstr * buf, sstr ** cmd, sstr ** arg);
+int extract_servercmd(sstr * buf, int *code, sstr ** msg);
+- void do_linecontrol(sstr * buf, int reply);
+
+/* ------------------------------------------------------------- **
+**  Sets up the control connection to the server and initialises
+- **  session info.
+** ------------------------------------------------------------- */
+void init_session(int fd, struct sockaddr_in source)
+{
+-   info = (session_info *) malloc(sizeof(session_info));
+-   if (info == NULL) quit(MALLOC);
+-   memset(info, 0, sizeof(session_info));
+
+-   info->listen = info->client_data.fd = info->server_data.fd = -1;
+-   info->server_control.fd = -1;
+- 
+-   info->server_control.buf = sstr_init(BUF_LEN);
+-   info->client_control.buf = sstr_init(BUF_LEN);
+-   info->server_data.buf = sstr_init(BUF_LEN);
+-   info->client_data.buf = sstr_init(BUF_LEN);
+-   info->server_name = NULL;
+- 
+-   if (config.apconv) info->mode = APCONV;
+-   else info->mode = ACTIVE;
+-   info->state = NEITHER;
+- 
+-   info->client_control.address = source;
+-   info->client_control.fd = fd;
+-   get_orig_dest(fd, &info->server_control.address);
+-   info->apparent_server_address = info->server_control.address;
+- 
+  write_log(INFO, "Connect from %s",
+      sstr_buf(addr2name(info->client_control.address.sin_addr)));
+
+  ccp_changedest();
+-   ntp_changedest(); 
+  info->final_server_address = info->server_control.address;
+-   ccp_init();
+-   ntp_changedest();
+
+-   if(config.ftpproxy.sin_addr.s_addr) {
+-     info->server_control.address = config.ftpproxy;
+-   }
+- 
+
+-   /*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) {
+-     write_log(ERROR, "Attempt to connect to self");
+-     send_cmessage(421, "Proxy tried to loop. Closing connection");
+-     exit(0);
+-   }
+
+-   if(!info->server_name) set_server_name();
+-   write_log(INFO, "... to %s(%s)",
+-       inet_ntoa(info->final_server_address.sin_addr),
+-       inet_ntoa(info->server_control.address.sin_addr),
+-       sstr_buf(info->server_name));
+- 
+-   if (!config_connectionok(&info->client_control.address,
+-          &info->final_server_address)) {
+-          &info->server_control.address)) {
+-     write_log(ATTACK, "Denied by ACLs.");
+-     send_cmessage(501, "Connection denied. Bye");
+-     close(info->client_control.fd);
+-     exit(0);
+-   }
+
+-   debug("Connecting to server...");
+
+  info->server_control.fd =
+-     connect_to_socket(info->server_control.address, CTRL);
+
+-   if (info->server_control.fd == -1) {
+-   if ((info->server_control.fd =
+-        connect_to_socket(info->server_control.address, CTRL)) == -1) {
+-     write(info->client_control.fd,
+-           "501 Proxy unable to contact ftp server\n", 39);
+-     write_log(ERROR,
+-         "Connection closed -- unable to contact server");
+-     close(info->client_control.fd);
+-     exit(0);
+-   }
+
+-   debug("OK\n");
+
+-   ftpcmds_init();    /* Needs doing before cache_init() */
+-   if (config.cachemod)
+-     cache_init();  /*Needs doing before ntp_senduser() */
+- 
+-   ntp_senduser();
+- 
+-   debug2("Apparent address = %s\n",
+-          sstr_buf(addr2name(info->apparent_server_address.sin_addr)));
+-   debug2("Real address = %s\n",
+-          sstr_buf(addr2name(info->final_server_address.sin_addr)));
+-   debug2("Proxy address = %s\n",
+-          sstr_buf(addr2name(info->server_control.address.sin_addr)));
+- 
+-   run_proxy();
+}
+
+/* ------------------------------------------------------------- **
+**  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)
+-     quit(CLIENT_RUBBISH);
+}
+
+/* ------------------------------------------------------------- **
+** 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 (!cache_parsed_reply(code, arg) && 
+-         !vscan_parsed_reply(code, arg))
+      send_message(code, arg);
+
+-   if (i < 0)
+-     quit(SERVER_RUBBISH);
+}
+
+void parse_client_cmd(sstr * cmd, sstr * arg)
+{
+-   cmd_struct *p;
+
+-   if(!ccp_allowcmd(cmd, arg)) return;
+-   for (p = ftp_cmds; *p->name != 0; p++) {
+-     if (!sstr_casecmp2(cmd, p->name)) {
+-       p->cmd(cmd, arg);
+-       if(ccp_allowcmd(cmd, arg))
+-         p->cmd(cmd, arg);
+-       return;
+    }
+  }
+-   write_log(ERROR, "Command %s not implemented\n", 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)
+-       quit(SOCKET);
+}
+
+/* ------------------------------------------------------------- **
+** 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:
+-       quit(SERVER_RUBBISH);
+    case 0:
+-       if(*code > 0) return;
+      continue;
+    case 1:
+-       if (get_control_line(GET_SRVR) <= 0)
+-         quit(SOCKET);
+    }
+-   }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);
+
+-   debug2("  C: \033[31m%s\033[37m", sstr_buf(buf));
+-   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);
+
+-   debug2("  S: \033[34m%s\033[37m", sstr_buf(buf));
+  sstr_write(info->client_control.fd, buf, 0);
+  sstr_free(buf);
+}
+
+/* ------------------------------------------------------------- **
+** 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");
+-       quit(NO_MESSAGE);
+    }
+
+    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)
+-         quit(CLIENT_CLOSE);
+-       if (i < 0)
+-         quit(CLIENT_RUBBISH);  /*Buffer full/fd close*/
+-       if (sstr_hasline(info->client_control.buf))
+        ret |= GET_CLNT;
+    }
+
+-     if (info->server_control.fd != -1 &&
+-         FD_ISSET(info->server_control.fd, &reads)) {
+-       i = sstr_append_read(info->server_control.fd,
+-                info->server_control.buf, 0);
+-       if (i == 0)
+-         quit(SERVER_CLOSE);
+-       if (i < 0)
+-         quit(SERVER_RUBBISH);  /*Buffer full/fd close */
+-       if (sstr_hasline(info->server_control.buf))
+        ret |= GET_SRVR;
+    }
+
+-   } while (!(ret & which));
+  return (ret);
+}
+
+- /* ------------------------------------------------------------- **
+- ** Process buf to remove telnet line control stuff, and reply on fd
+- ** reply. FIXME this is quite ugly.
+- ** ------------------------------------------------------------- */
+- void do_linecontrol(sstr * buf, int reply)
+- {
+-   int i;
+-   sstr *tmp;
+- 
+-   tmp = sstr_init(3);
+- 
+-   while ((i = sstr_chr(buf, IAC)) != -1) {
+-     switch (sstr_getchar(buf, i + 1) & 255) {
+-     case WILL:
+-       if (sstr_split(buf, tmp, i, 3) == -1)
+-         quit(CLIENT_RUBBISH);
+-       sstr_setchar(tmp, 1, DONT);
+-       send(reply, sstr_buf(tmp), 3, MSG_OOB);
+-       break;
+-     case DO:
+-       if (sstr_split(buf, tmp, i, 3) == -1)
+-         quit(CLIENT_RUBBISH);
+-       sstr_setchar(tmp, 1, WONT);
+-       send(reply, sstr_buf(tmp), 3, MSG_OOB);
+-       break;
+-     case WONT:
+-     case DONT:
+-       if (sstr_split(buf, NULL, i, 3) == -1)
+-         quit(CLIENT_RUBBISH);
+-       break;
+-     case IAC:
+-       if (sstr_split(buf, NULL, i, 1) == -1)
+-         quit(CLIENT_RUBBISH);
+-       break;
+-     case IP:
+-     case DM:
+-       if (sstr_split(buf, NULL, i, 2) == -1)
+-         quit(CLIENT_RUBBISH);
+-       break;
+-     default:
+-       if (sstr_split(buf, NULL, i, 2) == -1)
+-         quit(CLIENT_RUBBISH);
+-       break;
+-     }
+-   }
+-   sstr_free(tmp);
+- }
+- 
+/***************************************************************************
+ *
+ * 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;
+
+-   do_linecontrol(buf, info->client_control.fd);
+- 
+-   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);
+  }
+
+-   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;
+
+-   do_linecontrol(buf, info->client_control.fd);
+- 
+-   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;
+  }
+
+-   sstr_makeprintable(msg, '?');
+
+  return (0);
+}
+
+- 
+- 
+- 
+-