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
7708ddfc
 #ifdef HAVE_SYS_UIO_H
 #include <sys/uio.h>
 #endif
c5e7d5cd
 
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
 
557b40cc
 void virusaction(const char *filename, const char *virname, const struct cfgstruct *copt)
0249f9d2
 {
557b40cc
 	pid_t pid;
1095156a
 	const struct cfgstruct *cpt;
0249f9d2
 
81837459
     if(!(cpt = cfgopt(copt, "VirusEvent"))->enabled)
0249f9d2
 	return;
 
557b40cc
     /* NB: we need to fork here since this function modifies the environment. 
        (Modifications to the env. are not reentrant, but we need to be.) */
     pid = fork();
 
     if ( pid == 0 ) {
 	/* child... */
 	char *buffer, *pt, *cmd;
0249f9d2
 
557b40cc
 	cmd = strdup(cpt->strarg);
 
 	if((pt = strstr(cmd, "%v"))) {
8ca8a18e
 	    buffer = (char *) malloc(strlen(cmd) + strlen(virname) + 10);
557b40cc
 	    *pt = 0; pt += 2;
 	    strcpy(buffer, cmd);
 	    strcat(buffer, virname);
 	    strcat(buffer, pt);
 	    free(cmd);
 	    cmd = strdup(buffer);
 	    free(buffer);
 	}
8ca8a18e
 
557b40cc
 	/* Allocate env vars.. to be portable env vars should not be freed */
8ca8a18e
 	buffer = (char *) malloc(strlen(ENV_FILE) + strlen(filename) + 2);
557b40cc
 	sprintf(buffer, "%s=%s", ENV_FILE, filename);
 	putenv(buffer);
 
8ca8a18e
 	buffer = (char *) malloc(strlen(ENV_VIRUS) + strlen(virname) + 2);
557b40cc
 	sprintf(buffer, "%s=%s", ENV_VIRUS, virname);
 	putenv(buffer);
8ca8a18e
 
557b40cc
 	/* WARNING: this is uninterruptable ! */
 	exit(system(cmd));
8ca8a18e
 
557b40cc
 	/* The below is not reached but is here for completeness to remind
 	   maintainers that this buffer is still allocated.. */
0249f9d2
 	free(cmd);
557b40cc
     } else if (pid > 0) {
 	/* parent */      
 	waitpid(pid, NULL, 0);
     } else {
 	/* error.. */
 	logg("!VirusAction: fork failed.\n");
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:
        1) FD commands are delivered on Unix domain sockets via recvnsg() on platforms which can do this.  
           These command messages are accompanied by a single byte of data which is a NUL character.
        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
 {
 	int fd;
 	ssize_t n;
442684f8
 	size_t boff = 0;
 	char *pdelim;
d43a8a5c
 #ifdef HAVE_RECVMSG
7708ddfc
 	struct msghdr msg;
 	struct iovec iov[1];
d43a8a5c
 #endif
7708ddfc
 #ifdef HAVE_CONTROL_IN_MSGHDR
609a42f0
 #ifndef CMSG_SPACE
 #define CMSG_SPACE(len)	    (_CMSG_ALIGN(sizeof(struct cmsghdr)) + _CMSG_ALIGN(len))
 #endif
 #ifndef CMSG_LEN
 #define CMSG_LEN(len)	    (_CMSG_ALIGN(sizeof(struct cmsghdr)) + (len))
 #endif
7708ddfc
 	struct cmsghdr *cmsg;
 	char tmp[CMSG_SPACE(sizeof(fd))];
 #endif
442684f8
 	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);
     if(read_command) {
     	if((n >= 1) && (buf[0] == 0)) { /* FD message */
d43a8a5c
 #ifdef HAVE_RECVMSG
442684f8
 	    iov[0].iov_base = buf;
 	    iov[0].iov_len = size;
 	    memset(&msg, 0, sizeof(msg));
 	    msg.msg_iov = iov;
 	    msg.msg_iovlen = 1;
d43a8a5c
 #endif
7708ddfc
 #ifdef HAVE_ACCRIGHTS_IN_MSGHDR
442684f8
 	    msg.msg_accrights = (caddr_t)&fd;
 	    msg.msg_accrightslen = sizeof(fd);
7708ddfc
 #endif
 #ifdef HAVE_CONTROL_IN_MSGHDR
442684f8
 	    msg.msg_control = tmp;
 	    msg.msg_controllen = sizeof(tmp);
 #endif
 #if defined(HAVE_RECVMSG) && !defined(C_OS2) && !defined(INCOMPLETE_CMSG)
 	    n = recvmsg(sockfd, &msg, 0);
 #else
 	    n = recv(sockfd, buf, size, 0);
7708ddfc
 #endif
442684f8
 	    if (n <= 0)
 		return n;
 	    errno = EBADF;
7708ddfc
 #ifdef HAVE_ACCRIGHTS_IN_MSGHDR
442684f8
 	    if(msg.msg_accrightslen != sizeof(fd))
 		return -1;
7708ddfc
 #endif
 #ifdef HAVE_CONTROL_IN_MSGHDR
442684f8
 	    cmsg = CMSG_FIRSTHDR(&msg);
 	    if(cmsg == NULL)
 		return -1;
 	    if(cmsg->cmsg_type != SCM_RIGHTS)
 		return -1;
 	    if(cmsg->cmsg_len != CMSG_LEN(sizeof(fd)))
 		return -1;
 	    fd = *(int *)CMSG_DATA(cmsg);
7708ddfc
 #endif
442684f8
 	    if(fd < 0)
 		return -1;
 	    n = snprintf(buf, size, "FD %d", fd);
bd8603aa
 	    if((size_t) n >= size)
442684f8
 		return -1;
 	    return n;
 	}
 	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((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;
     }
     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;
 }