Previous patch

Home

Next patch

./src/cache.c

Patch: Additional logging in NTP

+/***************************************
+
+    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
+
+  cache.c -- Generic code for caching.
+
+Overview:
+
+   cache_init() redirects USER in ftp_cmds to come here. If user is
+   anonymous we redirect all the other stuff we are going to need to
+   parse or intercept for caching to come here too.
+
+   We intercept RETR commands, and get all the info we need about the
+   filename (size, last modification date, and uri).
+
+   We call the specific cache code (in squidcache.c or localcache.c)
+   with all this information, and it has the opportunity to return a
+   file descriptor which will return the file.
+
+   Specific cache code gets called with all incoming data in case it
+   wishes to either alter it (ie. strip HTTP headers), or store it.
+
+  Current Problems include: 
+      o STAT does nothing useful.
+      o If we start doing anything with USER or 220 cmds then we have
+        the potential to interact with non-transparent proxying code.
+
+  ***************************************/
+
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "common.h"
+#include "control.h"
+#include "ftp-cmds.h"
+#include "transdata.h"
+#include "cache.h"
+
+void cache_stat(sstr * cmd, sstr * arg);
+void cache_abor(sstr * cmd, sstr * arg);
+void cache_mode(sstr * cmd, sstr * arg);
+void cache_stru(sstr * cmd, sstr * arg);
+void cache_type(sstr * cmd, sstr * arg);
+void cache_rest(sstr * cmd, sstr * arg);
+void cache_retr(sstr * cmd, sstr * arg);
+void cache_notallowed(sstr * cmd, sstr * arg);
+int setup_fileinfo(sstr * filename);
+void strip2bs(sstr * p);
+
+extern void abor_parse(sstr * cmd, sstr * arg);
+extern void xfer_command(sstr * cmd, sstr * arg);
+
+static int nooping = FALSE;
+static int working = FALSE;
+static int type = 0;    /*Ascii or Binary */
+static int noop = 0;    /*No of NOOPs sent */
+static time_t last_noop;
+static int offset = 0;
+
+struct filedetails {
+  sstr *host;
+  sstr *path;
+  sstr *filename;
+  int size;
+  sstr *mdtm;
+};
+
+struct filedetails fileinfo = { NULL, NULL, NULL, 0, NULL };
+
+#define NOOP_INTERVAL 30  /*Seconds */
+
+static struct cmd_struct cache_list[] = {
+  {"MODE", cache_mode},
+  {"STRU", cache_stru},
+  {"TYPE", cache_type},
+  {"REST", cache_rest},
+  {"RETR", cache_retr},
+  {"", NULL}
+};
+
+static struct cmd_struct xfer_list[] = {
+  {"ABOR", cache_abor},
+  {"STAT", cache_stat},
+  {"", cache_notallowed},
+  {"", NULL}
+};
+
+static int (*retr_start) (const sstr * host, const sstr * file,
+        const sstr * mdtm, int size, int offset, int type);
+void (*inc_data) (sstr * inc);
+static int (*retr_end) (void);
+static void which_caches(struct options *c, int *local, int *http);
+
+/* ------------------------------------------------------------- **
+** Called at frox start. Fork the cache manager if it is needed.
+** ------------------------------------------------------------- */
+int cache_geninit(void)
+{
+  int need_local = 0, need_http = 0;
+
+  which_caches(&config, &need_local, &need_http);
+
+  if(need_local) {
+#ifdef USE_LCACHE
+    if(l_geninit() == -1)
+      die(ERROR, "Unable to init local cache", 0, 0, -1);
+#else
+    die(ERROR, "Local caching not compiled in", 0, 0, -1);
+#endif
+  }
+#ifndef USE_HCACHE
+  if(need_http)
+    die(ERROR, "HTTP caching not compiled in", 0, 0, -1);
+#endif
+
+
+  fileinfo.host = sstr_init(MAX_LINE_LEN);
+  fileinfo.path = sstr_init(MAX_LINE_LEN);
+  fileinfo.filename = sstr_init(MAX_LINE_LEN);
+  fileinfo.mdtm = sstr_init(MAX_LINE_LEN);
+
+  return 0;
+}
+
+/*
+ * Check whether we need local and/or http caching in any of the config
+ * file subsections.
+ */
+static void which_caches(struct options *c, int *local, int *http)
+{
+  int i;
+
+  if(c->cachemod) {
+    if(!strcasecmp(c->cachemod, "Local"))
+      *local = 1;
+    else if(!strcasecmp(c->cachemod, "HTTP"))
+      *http = 1;
+    else if(strcasecmp(c->cachemod, "None"))
+      die(ERROR, "Unrecognised cache module", 0, 0, -1);
+  }
+
+  for(i = 0; i < c->subsecs.num; i++)
+    which_caches(&c->subsecs.list[i].config, local, http);
+}
+
+/* ------------------------------------------------------------- **
+** Setup the cache command list.
+** ------------------------------------------------------------- */
+void cache_init(void)
+{
+  if(!info->anonymous) {
+    if(config.cacheall) {  /*Caching a non anonymous connection */
+      config.strictcache = TRUE;
+    } else {
+      config.cachemod = NULL;
+      return;
+    }
+  }
+
+  if(!config.cachemod)
+    return;
+  if(!strcasecmp(config.cachemod, "None")) {
+    config.cachemod = NULL;
+    return;
+  }
+
+  info->cmd_arrays[CACHE_CMDS] = cache_list;
+  if(!strcasecmp(config.cachemod, "Local")) {
+    retr_start = l_retr_start;
+    inc_data = l_inc_data;
+    retr_end = l_retr_end;
+  } else {
+    retr_start = s_retr_start;
+    inc_data = NULL;  /* s_retr_start() does all incoming data
+             processing before it returns. */
+    retr_end = s_retr_end;
+  }
+}
+
+/* ------------------------------------------------------------- **
+** Commands to intercept at all times. TODO Consider moving stru and
+** mode functions to ftp-cmds.c They are pretty much obsolete, and
+** probably not used by anyone any more. 
+** ------------------------------------------------------------- */
+void cache_mode(sstr * cmd, sstr * arg)
+{
+  if(sstr_getchar(arg, 0) == 'S')
+    send_cmessage(200, "Command okay");
+  else
+    send_cmessage(504, "Only stream mode implemented");
+}
+
+void cache_stru(sstr * cmd, sstr * arg)
+{
+  if(sstr_getchar(arg, 0) == 'F')
+    send_cmessage(200, "Command okay");
+  else
+    send_cmessage(504, "Only file structure implemented");
+}
+
+void cache_type(sstr * cmd, sstr * arg)
+{
+  if(sstr_getchar(arg, 0) == 'I') {
+    type = 1;
+    send_command(cmd, arg);
+  } else if(!sstr_casecmp2(arg, "AN")
+      || !sstr_casecmp2(arg, "A")) {
+    type = 0;
+    send_command(cmd, arg);
+  } else
+    send_cmessage(504, "Only types I and AN implemented");
+}
+
+void cache_rest(sstr * cmd, sstr * arg)
+{
+#ifdef DO_VSCAN
+        if(config.vscanner && config.vsnorest) 
+                return send_cmessage(502, "Command not implemented.");
+#endif
+
+  write_log(VERBOSE, "cache.c intercepted REST");
+  offset = sstr_atoi(arg);
+
+  send_command(cmd, arg);
+}
+
+void cache_notallowed(sstr * cmd, sstr * arg)
+{
+  write_log(ERROR, "Command %s not allowed during cache xfer",
+      sstr_buf(cmd));
+  send_cmessage(502, "Command not implemented.");
+}
+
+/* ------------------------------------------------------------- **
+** Commands to intercept during transfer
+** ------------------------------------------------------------- */
+void cache_stat(sstr * cmd, sstr * arg)
+{
+  send_cmessage(213, "Retrieving file through cache");
+}
+
+void cache_abor(sstr * cmd, sstr * arg)
+{
+  int code;
+  while(noop > 0) {
+    get_message(&code, NULL);
+    noop--;
+  }
+  nooping = FALSE;
+
+  rclose(&info->client_data.fd);
+  rclose(&info->server_data.fd);
+  info->state = NEITHER;
+
+  info->cmd_arrays[CACHE_CMDS] = cache_list;
+
+  write_log(VERBOSE, "cache.c intercepted ABOR");
+  send_cmessage(426, "transfer aborted");
+  send_cmessage(226, "Closing data connection");
+  working = FALSE;
+  if(info->server_control.fd == -1)
+    die(INFO, "Server timed out during cache transfer", 421,
+        "Sorry - timed out", 0);
+}
+
+/* ------------------------------------------------------------- **
+** The important bit. Intercept RETR commands, find out all we need to
+** about the file, and then pass all the info to the caching function. 
+** ------------------------------------------------------------- */
+void cache_retr(sstr * cmd, sstr * arg)
+{
+  int i = -1, code;
+  sstr *msg;
+
+  /* Clear these now. If cache code has stuff it needs sent it
+   * can put it into these buffers.*/
+  sstr_empty(info->client_data.buf);
+  sstr_empty(info->server_data.buf);
+
+  if(setup_fileinfo(arg) == 0)
+    i = retr_start(fileinfo.host, fileinfo.path, fileinfo.mdtm,
+             fileinfo.size, offset, type);
+
+  if(i == -1) {    /*Cache can't return file for us. Do it ourselves. */
+    sstr *tmp;
+    tmp = sstr_init(10);
+    nooping = FALSE;
+    if(offset != 0) {  /*Send another REST since we sent loads
+           * of rubbish since the last one. */
+      sstr_apprintf(tmp, "%d", offset);
+      offset = 0;
+      send_ccommand("REST", sstr_buf(tmp));
+      get_message(&code, &msg);
+      if(code != 350) {
+        send_cmessage(503,
+                "Can't do REST after all!");
+        sstr_free(tmp);
+        return;
+      }
+    }
+    sstr_cpy2(tmp, "RETR");
+    xfer_command(tmp, arg);
+    sstr_free(tmp);
+    return;
+  }
+
+  /*Cache is retrieving the file - it will deal with REST, so
+     reset it to 0 just in case */
+  if(offset != 0) {
+    send_ccommand("REST", "0");
+    get_message(&code, &msg);
+    offset = 0;
+  }
+  info->needs_logging = TRUE;
+  info->virus = -1;
+  sstr_cpy(info->filename, arg);
+  urlescape(info->filename, "% ;/");
+
+  /*Set up everything as if this were a normal connection */
+  rclose(&info->server_data.fd);
+  info->server_data.fd = i;
+  if(info->client_data.fd == -1 && info->mode != PASSIVE) {
+    if(config.transdata) {
+      struct sockaddr_in tmp =
+        info->apparent_server_address;
+      tmp.sin_port = htons(20);
+      info->client_data.fd =
+        transp_connect(info->client_data.address,
+                 tmp);
+    } else
+      info->client_data.fd =
+        connect_to_socket(&info->client_data.address,
+              &config.tcpoutaddr,
+              config.actvports);
+    if(info->client_data.fd == -1) {
+      write_log(ERROR,
+          "Unable to connect to client data port");
+      cache_close_data();
+      return;
+    }
+  }
+
+  info->cmd_arrays[CACHE_CMDS] = xfer_list;
+
+  nooping = TRUE;
+  working = TRUE;
+  time(&last_noop);
+}
+
+int setup_fileinfo(sstr * filename)
+{
+  int code;
+  sstr *msg;
+
+  if(!strcasecmp(config.cachemod, "Local") || !config.forcehttp) {
+    send_ccommand("SIZE", sstr_buf(filename));
+    get_message(&code, &msg);
+    if(code / 100 != 2) {
+      write_log(VERBOSE,
+          "SIZE not accepted - aborting caching");
+      return (-1);
+    }
+    fileinfo.size = sstr_atoi(msg);
+    write_log(VERBOSE, "Cache: Filesize is %d", fileinfo.size);
+
+    send_ccommand("MDTM", sstr_buf(filename));
+    get_message(&code, &msg);
+    if(code / 100 != 2) {
+      write_log(VERBOSE,
+          "MDTM not accepted - aborting caching");
+      return (-1);
+    }
+    sstr_cpy(fileinfo.mdtm, msg);
+    write_log(VERBOSE, "Cache: MDTM is %s",
+        sstr_buf(fileinfo.mdtm));
+  }
+
+  if(!config.strictcache) {
+    if(sstr_getchar(filename, 0) != '/') {
+      send_ccommand("PWD", "");
+      get_message(&code, &msg);
+      if(sstr_getchar(msg, 0) != '"')
+        sstr_token(msg, NULL, "\"", 0);
+      sstr_token(msg, fileinfo.path, "\"", 0);
+      if(sstr_getchar(fileinfo.path,
+          sstr_len(fileinfo.path) - 1) != '/')
+        sstr_ncat2(fileinfo.path, "/", 1);
+      urlescape(fileinfo.path, " %;");
+    } else {  /* Absolute path given in filename */
+      sstr_empty(fileinfo.path);
+    }
+  } else {
+    sstr_ncpy2(fileinfo.path, "/", 1);
+    sstr_cat(fileinfo.path, info->strictpath);
+  }
+
+  if(config.usefqdn && sstr_len(info->server_name))
+    sstr_cpy(fileinfo.host, info->server_name);
+  else
+    sstr_cpy2(fileinfo.host,
+        inet_ntoa(info->final_server_address.sin_addr));
+
+  if(ntohs(info->final_server_address.sin_port) != 21)
+    sstr_apprintf(fileinfo.host, ":%d",
+            ntohs(info->final_server_address.sin_port));
+
+  sstr_cpy(fileinfo.filename, filename);
+  urlescape(fileinfo.filename, config.strictcache ? "% ;/" : "% ;");
+  sstr_cat(fileinfo.path, fileinfo.filename);
+
+  sstr_cpy(fileinfo.filename, filename);
+
+  if(!config.strictcache)
+    strip2bs(fileinfo.path);
+  info->state = DOWNLOAD;
+
+  return 0;
+}
+
+/* ------------------------------------------------------------- **
+** Deal with server replies if we need to. 
+** ------------------------------------------------------------- */
+int cache_parsed_reply(int code, sstr * msg)
+{
+  if(!config.cachemod)
+    return (FALSE);
+  if(noop > 0) {
+    if(code > 0)
+      noop--;
+    write_log(VERBOSE, "Got NOOP reply");
+    return (TRUE);
+  }
+  if(working && code == 421) {
+    write_log(VERBOSE,
+        "Discarding server close during cache retrieval");
+    return TRUE;
+  }
+  return FALSE;
+}
+
+void cache_inc_data(sstr * buf)
+{
+  if(!config.cachemod)
+    return;
+
+  if(nooping && time(NULL) - last_noop > NOOP_INTERVAL &&
+     info->server_control.fd != -1) {
+    write(info->server_control.fd, "NOOP\r\n", 6);
+    noop++;
+    time(&last_noop);
+    write_log(VERBOSE, "Sent NOOP");
+  }
+  if(inc_data)
+    inc_data(buf);
+}
+
+int cache_close_data(void)
+{
+  int code, i;
+  sstr *msg;
+
+  if(!config.cachemod)
+    return (-1);
+
+  while(noop > 0) {
+    get_message(&code, &msg);
+    noop--;
+  }
+
+  nooping = FALSE;
+  info->cmd_arrays[CACHE_CMDS] = cache_list;
+
+  i = retr_end();
+  working = FALSE;
+  if(info->server_control.fd == -1)
+    die(INFO, "Server timed out during cache transfer", 421,
+        "Sorry - timed out", 0);
+  return i;
+}
+
+int cache_transferring(void)
+{
+  return working;
+}
+
+/*Strip multiple "//" from string */
+void strip2bs(sstr * p)
+{
+  int i;
+  for(i = 0; i < sstr_len(p) - 1; i++) {
+    while(sstr_getchar(p, i) == '/'
+          && sstr_getchar(p, i + 1) == '/')
+      sstr_split(p, NULL, i, 1);
+  }
+}
+