clamd/others.c
b151ef55
 /*
4bed6861
  *  Copyright (C) 2002 - 2005 Tomasz Kojm <tkojm@clamav.net>
b151ef55
  *
  *  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
30738099
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
b151ef55
  */
 
b3483842
 #ifdef	_MSC_VER
 #include <winsock.h>
 #endif
 
24791abc
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
b151ef55
 #include <stdio.h>
 #include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
b3483842
 #ifdef	HAVE_UNISTD_H
b151ef55
 #include <unistd.h>
b3483842
 #endif
b151ef55
 #include <fcntl.h>
 #include <time.h>
 #include <sys/stat.h>
7cf38d70
 #include <errno.h>
b3483842
 #ifndef	C_WINDOWS
17975de9
 #include <sys/time.h>
236b30a3
 #include <sys/wait.h>
b3483842
 #endif
236b30a3
 
5c2dfa16
 #if HAVE_SYS_PARAM_H
 #include <sys/param.h>
 #endif
 
b3483842
 #ifndef	C_WINDOWS
f4cbee32
 #include <sys/socket.h>
5cd30424
 #include <sys/ioctl.h>
b3483842
 #endif
ee218f69
 
17975de9
 #if HAVE_SYS_TYPES_H
 #include <sys/types.h>
 #endif
4a5581d9
 #if HAVE_SYS_FILIO_H
 #include <sys/filio.h>
 #endif
ee218f69
 #ifdef HAVE_SYS_UIO_H
 #include <sys/uio.h>
 #endif
7cf38d70
 
618a038b
 /* submitted by breiter@wolfereiter.com: do not use poll(2) on Interix */
 #ifdef C_INTERIX
 #undef HAVE_POLL
 #undef HAVE_POLL_H
 #endif
 
7cf38d70
 #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 */
b151ef55
 
e303cf29
 #include "shared/memory.h"
 #include "shared/cfgparser.h"
 #include "shared/output.h"
 
ee218f69
 #include "session.h"
b0674c6e
 #include "others.h"
b151ef55
 
a500bc14
 #define ENV_FILE  "CLAM_VIRUSEVENT_FILENAME"
 #define ENV_VIRUS "CLAM_VIRUSEVENT_VIRUSNAME"
 
b3483842
 #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
 
a500bc14
 void virusaction(const char *filename, const char *virname, const struct cfgstruct *copt)
c72178a4
 {
a500bc14
 	pid_t pid;
c72178a4
 	struct cfgstruct *cpt;
 
5c4d94a9
     if(!(cpt = cfgopt(copt, "VirusEvent"))->enabled)
c72178a4
 	return;
 
a500bc14
     /* 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;
c72178a4
 
a500bc14
 	cmd = strdup(cpt->strarg);
 
 	if((pt = strstr(cmd, "%v"))) {
 	    buffer = (char *) mcalloc(strlen(cmd) + strlen(virname) + 10, sizeof(char));
 	    *pt = 0; pt += 2;
 	    strcpy(buffer, cmd);
 	    strcat(buffer, virname);
 	    strcat(buffer, pt);
 	    free(cmd);
 	    cmd = strdup(buffer);
 	    free(buffer);
 	}
 	
 	/* Allocate env vars.. to be portable env vars should not be freed */
 	buffer = (char *) mcalloc(strlen(ENV_FILE) + strlen(filename) + 2, sizeof(char));
 	sprintf(buffer, "%s=%s", ENV_FILE, filename);
 	putenv(buffer);
 
 	buffer = (char *) mcalloc(strlen(ENV_VIRUS) + strlen(virname) + 2, sizeof(char));
 	sprintf(buffer, "%s=%s", ENV_VIRUS, virname);
 	putenv(buffer);
 	
     
 	/* WARNING: this is uninterruptable ! */
 	exit(system(cmd));
 	
 	/* The below is not reached but is here for completeness to remind
 	   maintainers that this buffer is still allocated.. */
c72178a4
 	free(cmd);
a500bc14
     } else if (pid > 0) {
 	/* parent */      
 	waitpid(pid, NULL, 0);
     } else {
 	/* error.. */
 	logg("!VirusAction: fork failed.\n");
c72178a4
     }
 }
b3483842
 #endif /* C_WINDOWS */
7cf38d70
 
596b1e64
 int poll_fds(int *fds, int nfds, int timeout_sec)
7cf38d70
 {
 	int retval;
596b1e64
 	int i;
7cf38d70
 #ifdef HAVE_POLL
596b1e64
 	struct pollfd poll_1[1];
 	struct pollfd *poll_data = poll_1;
7cf38d70
 
e303cf29
     if (nfds>1) {
 	poll_data = mmalloc(nfds*sizeof(*poll_data));
 	if(!poll_data) {
 	    logg("!poll_fds: Can't allocate memory for poll_data\n");
 	    return -1;
 	}
     }
 
596b1e64
     for (i=0; i<nfds; i++) {
 	poll_data[i].fd = fds[i];
 	poll_data[i].events = POLLIN;
 	poll_data[i].revents = 0;
     }
7cf38d70
 
65d1b047
     if (timeout_sec > 0) {
     	timeout_sec *= 1000;
     }
7cf38d70
     while (1) {
596b1e64
     	retval = poll(poll_data, nfds, timeout_sec);
7cf38d70
 	if (retval == -1) {
    	    if (errno == EINTR) {
 		continue;
 	    }
596b1e64
 	    if (nfds>1)
 		free(poll_data);
7cf38d70
 	    return -1;
 	}
596b1e64
 	if (nfds>1) {
 	    if (retval>0) {
 		for (i=0; i<nfds; i++) {
 		    if (poll_data[i].revents) {
 			retval = i+1;
 			break;
 		    }
 		}
 	    }
 	    free(poll_data);
 	}
7cf38d70
 	return retval;
     }
 
 #else
 	fd_set rfds;
 	struct timeval tv;
596b1e64
 	int maxfd = 0;
7cf38d70
 
596b1e64
     for (i=0; i<nfds; i++) {
b3483842
 #ifndef	C_WINDOWS
596b1e64
 	if (fds[i] >= DEFAULT_FD_SETSIZE) {
 	    return -1;
 	}
b3483842
 #endif
596b1e64
 	if (fds[i] > maxfd)
 	    maxfd = fds[i];
7cf38d70
     }
 
     while (1) {
 	FD_ZERO(&rfds);
596b1e64
 	for (i=0; i<nfds; i++)
 	    FD_SET(fds[i], &rfds);
7cf38d70
 	tv.tv_sec = timeout_sec;
 	tv.tv_usec = 0;
 
596b1e64
 	retval = select(maxfd+1, &rfds, NULL, NULL,
65d1b047
 			(timeout_sec>0 ? &tv : NULL));
7cf38d70
 	if (retval == -1) {
 	    if (errno == EINTR) {
 		continue;
 	    }
 	    return -1;
 	}
596b1e64
 	if ((nfds>1) && (retval>0)) {
 	    for (i=0; i<nfds; i++) {
 		if (FD_ISSET(fds[i],&rfds)) {
 		    retval = i+1;
 		    break;
 		}
 	    }
 	}
7cf38d70
 	return retval;
     }
 #endif
 
     return -1;
 }
f4cbee32
 
596b1e64
 int poll_fd(int fd, int timeout_sec)
 {
     return poll_fds(&fd, 1, timeout_sec);
 }
 
f4cbee32
 int is_fd_connected(int fd)
 {
 #ifdef HAVE_POLL
 	struct pollfd poll_data[1];
5cd30424
 	int count;
f4cbee32
 
     poll_data[0].fd = fd;
     poll_data[0].events = POLLIN;
     poll_data[0].revents = 0;
 
5cd30424
     if ((count=poll(poll_data, 1, 0)) == -1) {
     	if (errno == EINTR) {
 		return 1;
 	}
58fccc1c
 	return 0;
f4cbee32
     }
5cd30424
     if (count == 0) {
     	return 1;
     }
f4cbee32
     if (poll_data[0].revents & POLLHUP) {
 	return 0;
     }
5cd30424
     if ((poll_data[0].revents & POLLIN) && (ioctl(fd, FIONREAD, &count) == 0)) {
     	if (count == 0) {
 		return 0;
 	}
     }
f4cbee32
     return 1;
 
 #else
 	fd_set rfds;
 	struct timeval tv;
 	char buff[1];
 
b3483842
 #ifndef	C_WINDOWS
f4cbee32
     if (fd >= DEFAULT_FD_SETSIZE) {
         return 1;
     }
b3483842
 #endif
f4cbee32
 
     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
 }
60e606c2
 
 /* 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;
 }
ee218f69
 
ce246dd4
 /* 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)
ee218f69
 {
 	int fd;
 	ssize_t n;
ce246dd4
 	size_t boff = 0;
 	char *pdelim;
5ada8707
 #ifdef HAVE_RECVMSG
ee218f69
 	struct msghdr msg;
 	struct iovec iov[1];
5ada8707
 #endif
ee218f69
 #ifdef HAVE_CONTROL_IN_MSGHDR
879f4c06
 #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
ee218f69
 	struct cmsghdr *cmsg;
 	char tmp[CMSG_SPACE(sizeof(fd))];
 #endif
ce246dd4
 	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)) {
 	    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 */
5ada8707
 #ifdef HAVE_RECVMSG
ce246dd4
 	    iov[0].iov_base = buf;
 	    iov[0].iov_len = size;
 	    memset(&msg, 0, sizeof(msg));
 	    msg.msg_iov = iov;
 	    msg.msg_iovlen = 1;
5ada8707
 #endif
ee218f69
 #ifdef HAVE_ACCRIGHTS_IN_MSGHDR
ce246dd4
 	    msg.msg_accrights = (caddr_t)&fd;
 	    msg.msg_accrightslen = sizeof(fd);
ee218f69
 #endif
 #ifdef HAVE_CONTROL_IN_MSGHDR
ce246dd4
 	    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);
ee218f69
 #endif
ce246dd4
 	    if (n <= 0)
 		return n;
 	    errno = EBADF;
ee218f69
 #ifdef HAVE_ACCRIGHTS_IN_MSGHDR
ce246dd4
 	    if(msg.msg_accrightslen != sizeof(fd))
 		return -1;
ee218f69
 #endif
 #ifdef HAVE_CONTROL_IN_MSGHDR
ce246dd4
 	    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);
ee218f69
 #endif
ce246dd4
 	    if(fd < 0)
 		return -1;
 	    n = snprintf(buf, size, "FD %d", fd);
e303cf29
 	    if((size_t) n >= size)
ce246dd4
 		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);
 	    switch(poll_fd(sockfd, ((timeout_sec-(timenow-starttime)) > 0) ? timeout_sec-(timenow-starttime) : 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;
     }
     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 */
     }
ee218f69
     return n;
 }