./src/vscan.c
Patch: Allow scanfile to appear within another argument when calling virus scanner
+- /***************************************
+-
+- 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
+-
+- vscan.c -- Called from localcache.c if virus scanning is enabled. We
+- have two phases of operation: INCOMING and OUTGOING.
+-
+- When RETR is received it is forwarded to the server and we enter
+- INCOMING mode. All incoming data is written to a temporary file, and
+- the buffer length zeroed so it doesn't get written to either cache
+- or client. The server's 150 reply is intercepted, and a multiline
+- 150 reply started instead - a line at a time every few seconds to
+- prevent timeouts.
+-
+- On data connection close during INCOMING we scan the temporary file.
+- If infected we send an error and return -1. If clean we switch to
+- OUTGOING mode, and reopen the file for reading. This fd is returned
+- and will become the new server_data fd.
+-
+- During the OUTGOING phase we do nothing. The data read from our
+- temporary file will be sent to both client and cache file. On close
+- we delete the temporary file.
+-
+- problems:
+- o Uploads not scanned
+- o Sensitive to order of calls in l_retr_end and l_inc_data.
+- o The file is written to disk on two occasions.
+-
+- TODO Modify localcache.c to delete cache file header on failed scan.
+- ***************************************/
+- #include <sys/stat.h>
+- #include <sys/wait.h>
+- #include <fcntl.h>
+-
+- #include "common.h"
+- #include "control.h"
+- #include "cache.h"
+- #include "vscan.h"
+-
+- static char *argv[20];
+-
+- static enum { NONE, INCOMING, OUTGOING } status = NONE;
+- static enum { STARTING, FINISHED, NOTHING } expected_reply = NOTHING;
+- static int size;
+- static int tsize;
+- static int fd = -1;
+- static char scanfile[BUF_LEN];
+- static int sfindex=-1;
+- static time_t lastprog;
+-
+- int vscan_scan(void);
+-
+- /*This function run as root to allow making tmp dir*/
+- int vscan_init(void)
+- {
+- int i;
+- char *p = config.vscanner;
+-
+- if(!config.vscanner)
+- return 0;
+-
+- for(i = 0; i < 19; i++) {
+- while(*p != 0 && *p++ != '"');
+- if(*p == 0)
+- break;
+- argv[i] = p;
+- while(*p != 0 && *p != '"')
+- p++;
+- if(*p == 0)
+- break;
+- *p++ = 0;
+- if(strstr(argv[i], "%s"))
+- sfindex = i;
+- if(!strcmp(argv[i], "%s"))
+- argv[i] = scanfile;
+- }
+- argv[i] = NULL;
+-
+- if(make_tmpdir() == -1)
+- return (-1);
+-
+- snprintf(scanfile, BUF_LEN, "%s/tmp/VS_%d", config.chroot, getpid());
+- write_log(VERBOSE, "VS: Virus scanner temp file is %s", scanfile);
+- return 0;
+- }
+-
+- void vscan_new(int sz)
+- {
+- if(!config.vscanner)
+- return;
+- fd = creat(scanfile, S_IRUSR | S_IWUSR);
+- status = INCOMING;
+- expected_reply = STARTING;
+- time(&lastprog);
+- size = sz;
+- tsize = 0;
+- write_log(VERBOSE, "VS: Downloading to temporary file");
+- }
+-
+- void vscan_inc(sstr * inc)
+- {
+- time_t tmp;
+-
+- if(!config.vscanner)
+- return;
+- if(status == INCOMING) {
+- tsize += sstr_len(inc);
+- sstr_write(fd, inc, 0);
+- sstr_empty(inc);
+- time(&tmp);
+- if(config.vscanpm && tmp - lastprog > config.vscanpm &&
+- expected_reply != STARTING) {
+- sstr *msg;
+- msg = sstr_init(500);
+- if(size)
+- sstr_apprintf(msg,
+- "150-Downloaded %d/%d bytes to proxy",
+- tsize, size);
+- else
+- sstr_apprintf(msg,
+- "150-Downloaded %d bytes to proxy",
+- tsize);
+- send_message(0, msg);
+- sstr_free(msg);
+- lastprog = tmp;
+- }
+- }
+- }
+-
+- int vscan_switchover(void)
+- {
+- int tmp;
+-
+- if(status != INCOMING)
+- return FALSE;
+-
+- rclose(&fd);
+-
+- status = OUTGOING;
+- if(!vscan_scan()) {
+- write_log(VERBOSE, "VS: Scan failed");
+- if(config.vscanpm)
+- send_cmessage(150, "Not starting Transfer");
+- send_cmessage(451, "File contains virus. Aborting");
+- unlink(scanfile);
+- status = NONE;
+- info->virus = TRUE;
+- return FALSE;
+- }
+- info->virus = FALSE;
+- write_log(VERBOSE, "VS: Scan complete. Changing fd");
+- send_cmessage(150, "Starting Transfer");
+- tmp = open(scanfile, O_RDONLY);
+- unlink(scanfile);
+- if(dup2(tmp, info->server_data.fd) == -1) {
+- debug_perr("dup2");
+- die(ERROR, "Error changing file descriptors in vscan", 0, 0,
+- -1);
+- }
+- close(tmp);
+- return TRUE;
+- }
+-
+- int vscan_end(void)
+- {
+- if(status == INCOMING)
+- die(ERROR, "In vscan_end() and shouldn't be", 0, 0, -1);
+- if(status == OUTGOING) {
+- status = NONE;
+- write_log(VERBOSE, "VS: Finished forwarding scanned file");
+- send_cmessage(226, "Transfer Complete");
+- return (VSCAN_OK);
+- }
+- return (VSCAN_OK);
+- }
+-
+- void vscan_abort(void)
+- {
+- if(status == INCOMING)
+- unlink(scanfile);
+- status = NONE;
+- }
+-
+- int vscan_parsed_reply(int code, sstr * msg)
+- {
+- switch (expected_reply) {
+- case NOTHING:
+- return (FALSE);
+- case STARTING:
+- if(code <= 0)
+- return (TRUE);
+- if(code > 299) { /*Failure */
+- expected_reply = NOTHING;
+- status = NONE;
+- close(fd);
+- unlink(scanfile);
+- return (FALSE);
+- }
+- if(config.vscanpm) {
+- send_cmessage(-150, "Starting Transfer");
+- send_cmessage(0, "150-There'll be a delay while we "
+- "scan for viruses");
+- }
+- expected_reply = FINISHED;
+- return (TRUE);
+- case FINISHED:
+- if(code <= 0)
+- return (TRUE);
+- expected_reply = NOTHING;
+- if(code > 299) { /*Failure */
+- status = NONE;
+- close(fd);
+- if(config.vscanpm)
+- send_cmessage(150, "Error. Aborting.");
+- return (FALSE);
+- }
+- return (TRUE);
+- }
+-
+- if(status == INCOMING)
+- return (TRUE);
+- return (FALSE);
+- }
+-
+- int vscan_scan(void)
+- {
+- int i;
+- int fd, fdlimit;
+-
+- write_log(VERBOSE, "VS: Now scanning file");
+- if(config.vscanpm)
+- send_cmessage(0, "150-Scanning file for viruses");
+-
+- switch (fork()) {
+- case 0: /*Child */
+- fdlimit = sysconf(_SC_OPEN_MAX);
+- for(fd = 0; fd < fdlimit; fd++)
+- close(fd);
+- open("/dev/null", O_RDWR);
+- dup(0);
+- dup(0);
+-
+- if(sfindex>0) {
+- char *p = argv[sfindex];
+- argv[sfindex] = malloc(strlen(p) + strlen(scanfile));
+- sprintf(argv[sfindex], p, scanfile);
+- }
+-
+- execvp(argv[0], argv);
+- die(ERROR, "Failed to exec virus scanner", 0, 0, -1);
+- case -1:
+- die(ERROR, "Error forking for virus scanner", 0, 0, -1);
+- break;
+- /*FIXME*/ default:
+- break;
+- }
+- wait(&i);
+- if(!WIFEXITED(i))
+- return (FALSE);
+-
+- return (WEXITSTATUS(i) == config.vscanok);
+- }
+-