/*
 *  Copyright (C) 2002 - 2007 Tomasz Kojm <tkojm@clamav.net>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston,
 *  MA 02110-1301, USA.
 */

#ifdef	_MSC_VER
#include <winsock.h>
#endif

#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#ifdef	HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#include <time.h>
#include <sys/stat.h>
#include <errno.h>
#ifndef	C_WINDOWS
#include <sys/time.h>
#include <sys/wait.h>
#endif

#if HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif

#ifndef	C_WINDOWS
#include <sys/socket.h>
#include <sys/ioctl.h>
#endif

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif

#include <pthread.h>
/* submitted by breiter@wolfereiter.com: do not use poll(2) on Interix */
#ifdef C_INTERIX
#undef HAVE_POLL
#undef HAVE_POLL_H
#endif

#if HAVE_POLL
#if HAVE_POLL_H
#include <poll.h>
#else /* HAVE_POLL_H */
#undef HAVE_POLL
#if HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif /* HAVE_SYS_SELECT_H */
#endif /* HAVE_POLL_H */
#endif /* HAVE_POLL */

#include "shared/cfgparser.h"
#include "shared/output.h"

#include "session.h"
#include "others.h"

#define ENV_FILE  "CLAM_VIRUSEVENT_FILENAME"
#define ENV_VIRUS "CLAM_VIRUSEVENT_VIRUSNAME"

#ifdef	C_WINDOWS
void virusaction(const char *filename, const char *virname, const struct cfgstruct *copt)
{
    if(cfgopt(copt, "VirusEvent")->enabled)
	logg("^VirusEvent is not supported on this platform");	/* Yet */
}

#else
static pthread_mutex_t virusaction_lock = PTHREAD_MUTEX_INITIALIZER;

void virusaction(const char *filename, const char *virname, const struct cfgstruct *copt)
{
	pid_t pid;
	const struct cfgstruct *cpt;
	char *buffer, *pt, *cmd, *buffer_file, *buffer_vir;
	size_t j;
	char *env[4];

	if(!(cpt = cfgopt(copt, "VirusEvent"))->enabled)
		return;

	env[0] = getenv("PATH");
	j = env[0] ? 1 : 0;
	/* Allocate env vars.. to be portable env vars should not be freed */
	buffer_file = (char *) malloc(strlen(ENV_FILE) + strlen(filename) + 2);
	if(buffer_file) {
		sprintf(buffer_file, "%s=%s", ENV_FILE, filename);
		env[j++] = buffer_file;
	}

	buffer_vir = (char *) malloc(strlen(ENV_VIRUS) + strlen(virname) + 2);
	if(buffer_vir) {
		sprintf(buffer_vir, "%s=%s", ENV_VIRUS, virname);
		env[j++] = buffer_vir;
	}
	env[j++] = NULL;

	cmd = strdup(cpt->strarg);

	if(cmd && (pt = strstr(cmd, "%v"))) {
		buffer = (char *) malloc(strlen(cmd) + strlen(virname) + 10);
		if(buffer) {
			*pt = 0; pt += 2;
			strcpy(buffer, cmd);
			strcat(buffer, virname);
			strcat(buffer, pt);
			free(cmd);
			cmd = strdup(buffer);
			free(buffer);
		}
	}

	if(!cmd) {
		free(buffer_file);
		free(buffer_vir);
		return;
	}
	pthread_mutex_lock(&virusaction_lock);
	/* We can only call async-signal-safe functions after fork(). */
	pid = fork();

	if ( pid == 0 ) {
		/* child... */
		/* WARNING: this is uninterruptable ! */
		exit(execle("/bin/sh", "sh", "-c", cmd, NULL, env));
	} else if (pid > 0) {
		pthread_mutex_unlock(&virusaction_lock);
		/* parent */
		waitpid(pid, NULL, 0);
	} else {
		pthread_mutex_unlock(&virusaction_lock);
		/* error.. */
		logg("!VirusAction: fork failed.\n");
	}
	free(cmd);
	free(buffer_file);
	free(buffer_vir);
}
#endif /* C_WINDOWS */

int poll_fds(int *fds, int nfds, int timeout_sec, int check_signals)
{
	int retval;
	int i;
#ifdef HAVE_POLL
	struct pollfd poll_1[1];
	struct pollfd *poll_data = poll_1;

    if (nfds>1) {
	poll_data = malloc(nfds*sizeof(*poll_data));
	if(!poll_data) {
	    logg("!poll_fds: Can't allocate memory for poll_data\n");
	    return -1;
	}
    }

    for (i=0; i<nfds; i++) {
	poll_data[i].fd = fds[i];
	poll_data[i].events = POLLIN;
	poll_data[i].revents = 0;
    }

    if (timeout_sec > 0) {
    	timeout_sec *= 1000;
    }
    while (1) {
    	retval = poll(poll_data, nfds, timeout_sec);
	if (retval == -1) {
	    if (errno == EINTR && !check_signals) {
		continue;
	    }
	    if (nfds>1)
		free(poll_data);
	    return -1;
	}
	if (nfds>1) {
	    if (retval>0) {
		for (i=0; i<nfds; i++) {
		    if (poll_data[i].revents) {
			retval = i+1;
			break;
		    }
		}
	    }
	    free(poll_data);
	}
	return retval;
    }

#else
	fd_set rfds;
	struct timeval tv;
	int maxfd = 0;

    for (i=0; i<nfds; i++) {
#ifndef	C_WINDOWS
	if (fds[i] >= DEFAULT_FD_SETSIZE) {
	    return -1;
	}
#endif
	if (fds[i] > maxfd)
	    maxfd = fds[i];
    }

    while (1) {
	FD_ZERO(&rfds);
	for (i=0; i<nfds; i++)
	    FD_SET(fds[i], &rfds);
	tv.tv_sec = timeout_sec;
	tv.tv_usec = 0;

	retval = select(maxfd+1, &rfds, NULL, NULL,
			(timeout_sec>0 ? &tv : NULL));
	if (retval == -1) {
	    if (errno == EINTR && !check_signals) {
		continue;
	    }
	    return -1;
	}
	if ((nfds>1) && (retval>0)) {
	    for (i=0; i<nfds; i++) {
		if (FD_ISSET(fds[i],&rfds)) {
		    retval = i+1;
		    break;
		}
	    }
	}
	return retval;
    }
#endif

    return -1;
}

int poll_fd(int fd, int timeout_sec, int check_signals)
{
    return poll_fds(&fd, 1, timeout_sec, check_signals);
}

int is_fd_connected(int fd)
{
#ifdef HAVE_POLL
	struct pollfd poll_data[1];
	int count;

    poll_data[0].fd = fd;
    poll_data[0].events = POLLIN;
    poll_data[0].revents = 0;

    if ((count=poll(poll_data, 1, 0)) == -1) {
    	if (errno == EINTR) {
		return 1;
	}
	return 0;
    }
    if (count == 0) {
    	return 1;
    }
    if (poll_data[0].revents & POLLHUP) {
	return 0;
    }
    if ((poll_data[0].revents & POLLIN) && (ioctl(fd, FIONREAD, &count) == 0)) {
    	if (count == 0) {
		return 0;
	}
    }
    return 1;

#else
	fd_set rfds;
	struct timeval tv;
	char buff[1];

#ifndef	C_WINDOWS
    if (fd >= DEFAULT_FD_SETSIZE) {
        return 1;
    }
#endif

    FD_ZERO(&rfds);
    FD_SET(fd, &rfds);
    tv.tv_sec = 0;
    tv.tv_usec = 0;
    if (select(fd+1, &rfds, NULL, NULL, &tv) <= 0) {
	return 1;
    }
    if (FD_ISSET(fd, &rfds)) {
	if (recv(fd, buff, 1, MSG_PEEK) != 1) {
	    return 0;
	}
    }
    return 1;
#endif
}

/* Function: writen
	Try hard to write the specified number of bytes
*/
int writen(int fd, void *buff, unsigned int count)
{
	int retval;
	unsigned int todo;
	unsigned char *current;
 
    todo = count;
    current = (unsigned char *) buff;
 
    do {
	retval = write(fd, current, todo);
	if (retval < 0) {
	    if (errno == EINTR) {
		continue;
	    }
	    return -1;
	}
	todo -= retval;
	current += retval;
    } while (todo > 0);
 
    return count;
}

/* FD Support Submitted by Richard Lyons <frob-clamav*webcentral.com.au> */
/*
   This procedure does timed clamd command and delimited input processing.  
   It is complex for several reasons:
       2) Newline delimited commands are indicated by a command which is prefixed by an 'n' character.  
          This character serves to indicate that the command will contain a newline which will cause
          command data to be read until the command input buffer is full or a newline is encountered.
          Once the delimiter is encountered, the data is returned without the prefixing 'n' byte.
       3) Legacy clamd clients presented commands which may or may not have been delimited by a newline.
          If a command happens to be delimted by a newline, then only that command (and its newline) is
          read and passed back, otherwise, all data read (in a single read) which fits in the specified
          buffer will be returned.
*/
int readsock(int sockfd, char *buf, size_t size, unsigned char delim, int timeout_sec, int force_delim, int read_command)
{
	ssize_t n;
	size_t boff = 0;
	char *pdelim;
	time_t starttime, timenow;

    time(&starttime);
    while(1) {
	time(&timenow);
	switch(poll_fd(sockfd, (timeout_sec && ((timeout_sec-(timenow-starttime)) > 0)) ? timeout_sec-(timenow-starttime) : 0, 0)) {
	    case 0: /* timeout */
		return -2;
	    case -1:
		if(errno == EINTR)
		    continue;
		return -1;
	}
	break;
    }
    n = recv(sockfd, buf, size, MSG_PEEK);
    if(n < 0)
	return -1;
    if(read_command) {
	if((n >= 1) && (buf[0] == 'n')) { /* Newline delimited command */
	    force_delim = 1;
	    delim = '\n';
	}
    }
    while(boff < size) {
	if(force_delim) {
	    pdelim = memchr(buf, delim, n+boff);
	    if(pdelim) {
		n = recv(sockfd, buf+boff, pdelim-buf+1-boff, 0);
		break;
	    } else {
		n = recv(sockfd, buf+boff, n, 0);
		if(n < 0)
		    return -1;
		if((boff+n) == size)
		    break;
		boff += n;
	    }
	} else {
	    pdelim = memchr(buf, delim, n+boff);
	    if(pdelim)
		n = recv(sockfd, buf+boff, pdelim-buf+1-boff, 0);
	    else
		n = recv(sockfd, buf+boff, size-boff, 0);
	    break;
	}
	while(1) {
	    time(&timenow);
	    switch(poll_fd(sockfd, ((timeout_sec-(timenow-starttime)) > 0) ? timeout_sec-(timenow-starttime) : 0, 0)) {
		case 0: /* timeout */
		    return -2;
		case -1:
		    if(errno == EINTR)
			continue;
		    return -1;
	    }
	    break;
	}
        n = recv(sockfd, buf+boff, size-boff, MSG_PEEK);
	if(n < 0)
	    return -1;
	if(n == 0)
	    break;
    }
    if(n < 0)
	return -1;
    n += boff;
    if(read_command) {
	if((n >= 1) && (buf[0] == 'n')) { /* Need to strip leading 'n' from command to attain standard command */
	    --n;
	    memcpy(buf, buf+1, n);
	    buf[n] = '\0';
	}
	return !strncmp(buf, "FD", 2) ? -1 : n; /* an explicit FD command is invalid */
    }
    return n;
}