clamd/others.c
e3aaff8e
 /*
8ca8a18e
  *  Copyright (C) 2002 - 2007 Tomasz Kojm <tkojm@clamav.net>
e3aaff8e
  *
  *  This program is free software; you can redistribute it and/or modify
bb34cb31
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
e3aaff8e
  *
  *  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
48b7b4a7
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
e3aaff8e
  */
 
67118e92
 #ifdef	_MSC_VER
 #include <winsock.h>
 #endif
 
98ac8d19
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
e3aaff8e
 #include <stdio.h>
 #include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
67118e92
 #ifdef	HAVE_UNISTD_H
e3aaff8e
 #include <unistd.h>
67118e92
 #endif
e3aaff8e
 #include <fcntl.h>
 #include <time.h>
 #include <sys/stat.h>
c5e7d5cd
 #include <errno.h>
67118e92
 #ifndef	C_WINDOWS
13d4160d
 #include <sys/time.h>
7bbd5f7f
 #include <sys/wait.h>
67118e92
 #endif
7bbd5f7f
 
d32fc9fe
 #if HAVE_SYS_PARAM_H
 #include <sys/param.h>
 #endif
 
67118e92
 #ifndef	C_WINDOWS
e0909dc6
 #include <sys/socket.h>
3d595166
 #include <sys/ioctl.h>
67118e92
 #endif
7708ddfc
 
fc83da82
 #ifdef HAVE_SYS_TYPES_H
13d4160d
 #include <sys/types.h>
 #endif
fc83da82
 #ifdef HAVE_SYS_FILIO_H
09a1f3a1
 #include <sys/filio.h>
 #endif
c5e7d5cd
 
1642ffba
 #include <pthread.h>
feeaa333
 /* submitted by breiter@wolfereiter.com: do not use poll(2) on Interix */
 #ifdef C_INTERIX
 #undef HAVE_POLL
 #undef HAVE_POLL_H
 #endif
 
c5e7d5cd
 #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 */
e3aaff8e
 
bd8603aa
 #include "shared/cfgparser.h"
 #include "shared/output.h"
 
7708ddfc
 #include "session.h"
079229d6
 #include "others.h"
e3aaff8e
 
557b40cc
 #define ENV_FILE  "CLAM_VIRUSEVENT_FILENAME"
 #define ENV_VIRUS "CLAM_VIRUSEVENT_VIRUSNAME"
 
67118e92
 #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
1642ffba
 static pthread_mutex_t virusaction_lock = PTHREAD_MUTEX_INITIALIZER;
 
557b40cc
 void virusaction(const char *filename, const char *virname, const struct cfgstruct *copt)
0249f9d2
 {
557b40cc
 	pid_t pid;
1095156a
 	const struct cfgstruct *cpt;
751f8470
 	char *buffer, *pt, *cmd, *buffer_file, *buffer_vir;
 	size_t j;
 	char *env[4];
0249f9d2
 
751f8470
 	if(!(cpt = cfgopt(copt, "VirusEvent"))->enabled)
 		return;
0249f9d2
 
751f8470
 	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;
 	}
557b40cc
 
751f8470
 	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;
0249f9d2
 
557b40cc
 	cmd = strdup(cpt->strarg);
 
29676a32
 	if(cmd && (pt = strstr(cmd, "%v"))) {
751f8470
 		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);
 		}
29676a32
 	}
557b40cc
 
e357da7b
 	if(!cmd) {
 		free(buffer_file);
 		free(buffer_vir);
751f8470
 		return;
e357da7b
 	}
1642ffba
 	pthread_mutex_lock(&virusaction_lock);
751f8470
 	/* 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) {
1642ffba
 		pthread_mutex_unlock(&virusaction_lock);
751f8470
 		/* parent */
 		waitpid(pid, NULL, 0);
 	} else {
1642ffba
 		pthread_mutex_unlock(&virusaction_lock);
751f8470
 		/* error.. */
 		logg("!VirusAction: fork failed.\n");
29676a32
 	}
751f8470
 	free(cmd);
 	free(buffer_file);
 	free(buffer_vir);
0249f9d2
 }
67118e92
 #endif /* C_WINDOWS */
c5e7d5cd
 
f22f13d9
 int poll_fds(int *fds, int nfds, int timeout_sec, int check_signals)
c5e7d5cd
 {
 	int retval;
57358cc8
 	int i;
c5e7d5cd
 #ifdef HAVE_POLL
57358cc8
 	struct pollfd poll_1[1];
 	struct pollfd *poll_data = poll_1;
c5e7d5cd
 
bd8603aa
     if (nfds>1) {
8ca8a18e
 	poll_data = malloc(nfds*sizeof(*poll_data));
bd8603aa
 	if(!poll_data) {
 	    logg("!poll_fds: Can't allocate memory for poll_data\n");
 	    return -1;
 	}
     }
 
57358cc8
     for (i=0; i<nfds; i++) {
 	poll_data[i].fd = fds[i];
 	poll_data[i].events = POLLIN;
 	poll_data[i].revents = 0;
     }
c5e7d5cd
 
5b9ac68d
     if (timeout_sec > 0) {
     	timeout_sec *= 1000;
     }
c5e7d5cd
     while (1) {
57358cc8
     	retval = poll(poll_data, nfds, timeout_sec);
c5e7d5cd
 	if (retval == -1) {
f22f13d9
 	    if (errno == EINTR && !check_signals) {
c5e7d5cd
 		continue;
 	    }
57358cc8
 	    if (nfds>1)
 		free(poll_data);
c5e7d5cd
 	    return -1;
 	}
57358cc8
 	if (nfds>1) {
 	    if (retval>0) {
 		for (i=0; i<nfds; i++) {
 		    if (poll_data[i].revents) {
 			retval = i+1;
 			break;
 		    }
 		}
 	    }
 	    free(poll_data);
 	}
c5e7d5cd
 	return retval;
     }
 
 #else
 	fd_set rfds;
 	struct timeval tv;
57358cc8
 	int maxfd = 0;
c5e7d5cd
 
57358cc8
     for (i=0; i<nfds; i++) {
67118e92
 #ifndef	C_WINDOWS
57358cc8
 	if (fds[i] >= DEFAULT_FD_SETSIZE) {
 	    return -1;
 	}
67118e92
 #endif
57358cc8
 	if (fds[i] > maxfd)
 	    maxfd = fds[i];
c5e7d5cd
     }
 
     while (1) {
 	FD_ZERO(&rfds);
57358cc8
 	for (i=0; i<nfds; i++)
 	    FD_SET(fds[i], &rfds);
c5e7d5cd
 	tv.tv_sec = timeout_sec;
 	tv.tv_usec = 0;
 
57358cc8
 	retval = select(maxfd+1, &rfds, NULL, NULL,
5b9ac68d
 			(timeout_sec>0 ? &tv : NULL));
c5e7d5cd
 	if (retval == -1) {
17758235
 	    if (errno == EINTR && !check_signals) {
c5e7d5cd
 		continue;
 	    }
 	    return -1;
 	}
57358cc8
 	if ((nfds>1) && (retval>0)) {
 	    for (i=0; i<nfds; i++) {
 		if (FD_ISSET(fds[i],&rfds)) {
 		    retval = i+1;
 		    break;
 		}
 	    }
 	}
c5e7d5cd
 	return retval;
     }
 #endif
 
     return -1;
 }
e0909dc6
 
f22f13d9
 int poll_fd(int fd, int timeout_sec, int check_signals)
57358cc8
 {
f22f13d9
     return poll_fds(&fd, 1, timeout_sec, check_signals);
57358cc8
 }
 
e0909dc6
 int is_fd_connected(int fd)
 {
 #ifdef HAVE_POLL
 	struct pollfd poll_data[1];
3d595166
 	int count;
e0909dc6
 
     poll_data[0].fd = fd;
     poll_data[0].events = POLLIN;
     poll_data[0].revents = 0;
 
3d595166
     if ((count=poll(poll_data, 1, 0)) == -1) {
     	if (errno == EINTR) {
 		return 1;
 	}
262f8208
 	return 0;
e0909dc6
     }
3d595166
     if (count == 0) {
     	return 1;
     }
e0909dc6
     if (poll_data[0].revents & POLLHUP) {
 	return 0;
     }
3d595166
     if ((poll_data[0].revents & POLLIN) && (ioctl(fd, FIONREAD, &count) == 0)) {
     	if (count == 0) {
 		return 0;
 	}
     }
e0909dc6
     return 1;
 
 #else
 	fd_set rfds;
 	struct timeval tv;
 	char buff[1];
 
67118e92
 #ifndef	C_WINDOWS
e0909dc6
     if (fd >= DEFAULT_FD_SETSIZE) {
         return 1;
     }
67118e92
 #endif
e0909dc6
 
     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
 }
94709323
 
 /* 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;
 }
7708ddfc
 
442684f8
 /* 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)
7708ddfc
 {
 	ssize_t n;
442684f8
 	size_t boff = 0;
 	char *pdelim;
 	time_t starttime, timenow;
 
     time(&starttime);
     while(1) {
 	time(&timenow);
f22f13d9
 	switch(poll_fd(sockfd, (timeout_sec && ((timeout_sec-(timenow-starttime)) > 0)) ? timeout_sec-(timenow-starttime) : 0, 0)) {
442684f8
 	    case 0: /* timeout */
 		return -2;
 	    case -1:
 		if(errno == EINTR)
 		    continue;
 		return -1;
 	}
 	break;
     }
     n = recv(sockfd, buf, size, MSG_PEEK);
17f74f9f
     if(n < 0)
 	return -1;
442684f8
     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);
72ce4b70
 		if(n < 0)
 		    return -1;
442684f8
 		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);
f22f13d9
 	    switch(poll_fd(sockfd, ((timeout_sec-(timenow-starttime)) > 0) ? timeout_sec-(timenow-starttime) : 0, 0)) {
442684f8
 		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;
     }
17f74f9f
     if(n < 0)
 	return -1;
442684f8
     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 */
     }
7708ddfc
     return n;
 }