clamav-milter/netcode.c
4c237bcf
 /*
56163e7d
  *  Copyright (C) 2015 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
4c237bcf
  *  Copyright (C)2008 Sourcefire, Inc.
  *
  *  Author: aCaB <acab@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.
  */
 
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
aff7b2a9
 /* for Solaris, so that both FDPassing and IPV6 work */
5c428be3
 #if !defined(__EXTENSIONS__)
aff7b2a9
 #define __EXTENSIONS__
5c428be3
 #endif
bfd89d7c
 /* must be first because it may define _XOPEN_SOURCE */
 #include "shared/fdpassing.h"
4c237bcf
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <sys/time.h>
 #include <sys/select.h>
 #include <time.h>
 #include <errno.h>
d5a65a34
 #include <netdb.h>
188f8292
 #include <sys/uio.h>
 
60d8d2c3
 #include "libclamav/clamav.h"
4c237bcf
 #include "shared/output.h"
278dc6b3
 #include "shared/optparser.h"
87620def
 #include "libclamav/others.h"
4c237bcf
 #include "netcode.h"
 
e68d70e7
 #define strerror_print(msg) logg(msg": %s\n", cli_strerror(errno, er, sizeof(er)))
4c237bcf
 
6840d862
 enum {
     NON_SMTP,
     INET_HOST,
     INET6_HOST
 };
 
 struct LOCALNET {
     struct LOCALNET *next;
     /* most significant first */
     uint32_t basehost[4];
     uint32_t mask[4];
     uint32_t family;
 };
 
 struct LOCALNET *lnet = NULL;
87620def
 char *tempdir = NULL;
6840d862
 
87620def
 /* for connect and send */
 #define TIMEOUT 30
4c237bcf
 /* for recv */
 long readtimeout;
 
6840d862
 
278dc6b3
 static int nc_socket(struct CP_ENTRY *cpe) {
4c237bcf
     int flags, s = socket(cpe->server->sa_family, SOCK_STREAM, 0);
bca134bc
     char er[256];
4c237bcf
 
bca134bc
     if (s == -1) {
943b2bf0
 	strerror_print("!Failed to create socket");
bca134bc
 	return -1;
     }
4c237bcf
     flags = fcntl(s, F_GETFL, 0);
     if (flags == -1) {
943b2bf0
 	strerror_print("!fcntl_get failed");
4c237bcf
 	close(s);
 	return -1;
     }
     flags |= O_NONBLOCK;
     if (fcntl(s, F_SETFL, flags) == -1) {
943b2bf0
 	strerror_print("!fcntl_set failed");
4c237bcf
 	close(s);
 	return -1;
     }
     return s;
 }
 
 
278dc6b3
 static int nc_connect(int s, struct CP_ENTRY *cpe) {
4c237bcf
     time_t timeout = time(NULL) + TIMEOUT;
     int res = connect(s, cpe->server, cpe->socklen);
     struct timeval tv;
bca134bc
     char er[256];
4c237bcf
 
     if (!res) return 0;
     if (errno != EINPROGRESS) {
943b2bf0
 	strerror_print("*connect failed");
4c237bcf
 	close(s);
 	return -1;
     }
 
     tv.tv_sec = TIMEOUT;
     tv.tv_usec = 0;
     while(1) {
 	fd_set fds;
 	int s_err;
 	socklen_t s_len = sizeof(s_err);
 
 	FD_ZERO(&fds);
 	FD_SET(s, &fds);
 	res = select(s+1, NULL, &fds, NULL, &tv);
 	if(res < 1) {
 	    time_t now;
bca134bc
 
4c237bcf
 	    if (res == -1 && errno == EINTR && ((now = time(NULL)) < timeout)) {
 		tv.tv_sec = timeout - now;
 		tv.tv_usec = 0;
 		continue;
 	    }
1e5deac0
 	    logg("*Failed to establish a connection to clamd\n");
4c237bcf
 	    close(s);
 	    return -1;
 	}
138b5aba
 	if(getsockopt(s, SOL_SOCKET, SO_ERROR, &s_err, (socklen_t *)&s_len) || s_err) {
1e5deac0
 	    logg("*Failed to establish a connection to clamd\n");
4c237bcf
 	    close(s);
 	    return -1;
 	}
 	return 0;
     }
 }
 
 
4432aeb7
 int nc_send(int s, const void *buff, size_t len) {
     char *buf = (char *)buff;
 
4c237bcf
     while(len) {
 	int res = send(s, buf, len, 0);
 	time_t timeout = time(NULL) + TIMEOUT;
 	struct timeval tv;
bca134bc
 	char er[256];
4c237bcf
 
762d3dd6
 	if(!res) {
 	    logg("!Connection closed while sending data\n");
 	    close(s);
 	    return 1;
 	}
4c237bcf
 	if(res!=-1) {
 	    len-=res;
 	    buf+=res;
 	    continue;
 	}
 	if(errno != EAGAIN && errno != EWOULDBLOCK) {
943b2bf0
 	    strerror_print("!send failed");
4c237bcf
 	    close(s);
 	    return 1;
 	}
 
 	tv.tv_sec = TIMEOUT;
 	tv.tv_usec = 0;
 	while(1) {
 	    fd_set fds;
 
 	    FD_ZERO(&fds);
 	    FD_SET(s, &fds);
 	    res = select(s+1, NULL, &fds, NULL, &tv);
 	    if(res < 1) {
 		time_t now;
bca134bc
 
4c237bcf
 		if (res == -1 && errno == EINTR && ((now = time(NULL)) < timeout)) {
 		    tv.tv_sec = timeout - now;
 		    tv.tv_usec = 0;
 		    continue;
 		}
804caabc
 		logg("!Failed to stream to clamd\n");
4c237bcf
 		close(s);
 		return 1;
 	    }
 	    break;
 	}
     }
     return 0;
 }
 
 
bca134bc
 int nc_sendmsg(int s, int fd) {
     struct iovec iov[1];
     struct msghdr msg;
     struct cmsghdr *cmsg;
     int ret;
     unsigned char fdbuf[CMSG_SPACE(sizeof(int))];
     char dummy[]="";
 
     iov[0].iov_base = dummy;
     iov[0].iov_len = 1;
     memset(&msg, 0, sizeof(msg));
     msg.msg_control = fdbuf;
     msg.msg_iov = iov;
     msg.msg_iovlen = 1;
     msg.msg_controllen = CMSG_LEN(sizeof(int));
     cmsg = CMSG_FIRSTHDR(&msg);
     cmsg->cmsg_len = CMSG_LEN(sizeof(int));
     cmsg->cmsg_level = SOL_SOCKET;
     cmsg->cmsg_type = SCM_RIGHTS;
     *(int *)CMSG_DATA(cmsg) = fd;
     /* FIXME: nonblock code needed (?) */
 
     if((ret = sendmsg(s, &msg, 0)) == -1) {
 	char er[256];
943b2bf0
 	strerror_print("!clamfi_eom: FD send failed");
bca134bc
 	close(s);
     }
     return ret;
 }
 
4c237bcf
 char *nc_recv(int s) {
1e5deac0
     char buf[128], *ret=NULL;
     time_t now, timeout = time(NULL) + readtimeout;
4c237bcf
     struct timeval tv;
     fd_set fds;
     int res;
1e5deac0
     unsigned int len = 0;
4c237bcf
 
     while(1) {
1e5deac0
 	now = time(NULL);
 	if(now >= timeout) {
 	    logg("!Timed out while reading clamd reply\n");
 	    close(s);
 	    return NULL;
 	}
 	tv.tv_sec = timeout - now;
 	tv.tv_usec = 0;
 
 	FD_ZERO(&fds);
 	FD_SET(s, &fds);
 
de0efa3d
 	res = select(s+1, &fds, NULL, NULL, readtimeout ? &tv : NULL);
4c237bcf
 	if(res<1) {
1e5deac0
 	    if (res != -1 || errno != EINTR)
 		timeout = 0;
 	    continue;
 	}
bca134bc
 
1e5deac0
 	res = recv(s, &buf[len], sizeof(buf) - len, 0);
762d3dd6
 	if(!res) {
 	    logg("!Connection closed while reading from socket\n");
 	    close(s);
 	    return NULL;
 	}
1e5deac0
 	if(res==-1) {
 	    char er[256];
322a0ea6
 	    if (errno == EAGAIN)
 		continue;
943b2bf0
 	    strerror_print("!recv failed after successful select");
1e5deac0
 	    close(s);
 	    return NULL;
 	}
 	len += res;
 	if(len && buf[len-1] == '\n') break;
 	if(len >= sizeof(buf)) {
 	    logg("!Overlong reply from clamd\n");
4c237bcf
 	    close(s);
 	    return NULL;
 	}
bca134bc
     }
1e5deac0
     if(!(ret = (char *)malloc(len+1))) {
 	logg("!malloc(%d) failed\n", len+1);
4c237bcf
 	close(s);
 	return NULL;
     }
1e5deac0
     memcpy(ret, buf, len);
     ret[len]='\0';
4c237bcf
     return ret;
 }
 
 
 int nc_connect_entry(struct CP_ENTRY *cpe) {
     int s = nc_socket(cpe);
     if(s==-1) return -1;
     return nc_connect(s, cpe) ? -1 : s;
 }
 
 
 void nc_ping_entry(struct CP_ENTRY *cpe) {
     int s = nc_connect_entry(cpe);
     char *reply;
bca134bc
 
1ca7d190
     if(s>=0) {
 	if(!nc_send(s, "nPING\n", 6) && (reply = nc_recv(s))) {
 	    cpe->dead = strcmp(reply, "PONG\n")!=0;
 	    free(reply);
 	    close(s);
 	    return;
 	}
8132fa0a
 	close(s);
1ca7d190
     }
     cpe->dead = 1;
4c237bcf
 }
 
 
 int nc_connect_rand(int *main, int *alt, int *local) {
d5a65a34
     struct CP_ENTRY *cpe = cpool_get_rand(main);
4c237bcf
 
     if(!cpe) return 1;
d5a65a34
     *local = (cpe->server->sa_family == AF_UNIX);
4c237bcf
     if(*local) {
87620def
 	char *unlinkme;
 	if(cli_gentempfd(tempdir, &unlinkme, alt) != CL_SUCCESS) {
4c237bcf
 	    logg("!Failed to create temporary file\n");
 	    close(*main);
 	    return 1;
 	}
87620def
 	unlink(unlinkme);
8132fa0a
 	free(unlinkme);
f15c2f8d
 	if(nc_send(*main, "nFILDES\n", 8)) {
 	    logg("!FD scan request failed\n");
 	    close(*alt);
 	    close(*main);
 	    return 1;
 	}
4c237bcf
     } else {
aecb594f
 	if(nc_send(*main, "nINSTREAM\n", 10)) {
4c237bcf
 	    logg("!Failed to communicate with clamd\n");
 	    close(*main);
 	    return 1;
 	}
     }
     return 0;
 }
 
99b2cb15
 
278dc6b3
 static int resolve(char *name, uint32_t *family, uint32_t *host) {
d5a65a34
     struct addrinfo hints, *res;
 
99b2cb15
     if(!name) {
6840d862
 	/* 	l->basehost[0] = l->basehost[1] = l->basehost[2] = l->basehost[3] = 0; DONT BOTHER*/
d5a65a34
 	*family = NON_SMTP;
 	return 0;
     }
 
     memset(&hints, 0, sizeof(hints));
     hints.ai_family = AF_UNSPEC;
     hints.ai_socktype = SOCK_STREAM;
 
     if(getaddrinfo(name, NULL, &hints, &res)) {
 	logg("!Can't resolve LocalNet hostname %s\n", name);
 	return 1;
     }
     if(res->ai_addrlen == sizeof(struct sockaddr_in) && res->ai_addr->sa_family == AF_INET) {
 	struct sockaddr_in *sa = (struct sockaddr_in *)res->ai_addr;
 
 	*family = INET_HOST;
 	host[0] = htonl(sa->sin_addr.s_addr);
6840d862
 	/* 	host[1] = host[2] = host[3] = 0; DONT BOTHER*/
d5a65a34
     } else if(res->ai_addrlen == sizeof(struct sockaddr_in6) && res->ai_addr->sa_family == AF_INET6) {
 	struct sockaddr_in6 *sa = (struct sockaddr_in6 *)res->ai_addr;
 	unsigned int i, j;
 	uint32_t u = 0;
 
 	*family = INET6_HOST;
 	for(i=0, j=0; i<16; i++) {
 	    u += (sa->sin6_addr.s6_addr[i] << (8*j));
 	    if(++j == 4) {
6840d862
 		host[i>>2] = u;
d5a65a34
 		j = u = 0;
 	    }
 	}
     } else {
 	logg("!Unsupported address type for LocalNet %s\n", name);
 	freeaddrinfo(res);
 	return 1;
     }
     freeaddrinfo(res);
     return 0;
 }
 
 
278dc6b3
 static struct LOCALNET *localnet(char *name, char *mask) {
d5a65a34
     struct LOCALNET *l = (struct LOCALNET *)malloc(sizeof(*l));
     uint32_t nmask;
     unsigned int i;
 
     if(!l) {
 	logg("!Out of memory while resolving LocalNet\n");
 	return NULL;
     }
 
     if(resolve(name, &l->family, l->basehost)) {
 	free(l);
 	return NULL;
     }
 
     if(l->family == NON_SMTP) {
6840d862
 	l->mask[0] = l->mask[1] = l->mask[2] = l->mask[3] = 0x0;
d5a65a34
 	return l;
     }
6840d862
 
     if(!mask || !*mask) nmask = 32 + 96*(l->family == INET6_HOST);
d5a65a34
     else nmask = atoi(mask);
 
     if((l->family == INET6_HOST && nmask > 128) || (l->family == INET_HOST && nmask > 32)) {
 	logg("!Bad netmask '/%s' for LocalNet %s\n", mask, name);
 	free(l);
 	return NULL;
     }
6840d862
 
d5a65a34
     l->mask[0] = l->mask[1] = l->mask[2] = l->mask[3] = 0;
     for(i=0; i<nmask; i++)
6840d862
 	l->mask[i>>5] |= 1<<(31-(i & 31));
 
     l->basehost[0] &= l->mask[0];
     l->basehost[1] &= l->mask[1];
     l->basehost[2] &= l->mask[2];
     l->basehost[3] &= l->mask[3];
 
d5a65a34
     return l;
 }
 
 
99b2cb15
 static int islocalnet(uint32_t family, uint32_t *host) {
6840d862
     struct LOCALNET* l = lnet;
d5a65a34
 
6840d862
     if(!l) return 0;
d5a65a34
     while(l) {
6840d862
 	if(
 	   (l->family == family) &&
 	   (l->basehost[0] == (host[0] & l->mask[0])) && (l->basehost[1] == (host[1] & l->mask[1])) &&
 	   (l->basehost[2] == (host[2] & l->mask[2])) && (l->basehost[3] == (host[3] & l->mask[3]))
 	   ) return 1;
d5a65a34
 	l=l->next;
     }
     return 0;
 }
 
99b2cb15
 
 int islocalnet_name(char *name) {
     uint32_t host[4], family;
 
     if(!lnet) return 0;
     if(resolve(name, &family, host)) {
1e5deac0
 	logg("*Cannot resolv %s\n", name);
99b2cb15
 	return 0;
     }
     return islocalnet(family, host);
 }
 
 
 int islocalnet_sock(struct sockaddr *sa) {
     uint32_t host[4], family;
 
     if(!lnet) return 0;
 
     if(sa->sa_family == AF_INET) {
 	struct sockaddr_in *sa4 = (struct sockaddr_in *)sa;
 
 	family = INET_HOST;
 	host[0] = htonl(sa4->sin_addr.s_addr);
     } else if(sa->sa_family == AF_INET6) {
 	struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)sa;
 	unsigned int i, j;
 	uint32_t u = 0;
 
 	family = INET6_HOST;
 	for(i=0, j=0; i<16; i++) {
 	    u += (sa6->sin6_addr.s6_addr[i] << (8*j));
 	    if(++j == 4) {
 		host[i>>2] = u;
 		j = u = 0;
 	    }
 	}
     } else return 0;
     return islocalnet(family, host);
 }
 
 
6840d862
 void localnets_free(void) {
     while(lnet) {
 	struct LOCALNET *l = lnet->next;
99b2cb15
 
6840d862
 	free(lnet);
 	lnet = l;
     }   
 }
 
99b2cb15
 
278dc6b3
 int localnets_init(struct optstruct *opts) {
     const struct optstruct *opt;
6840d862
 
278dc6b3
     if((opt = optget(opts, "LocalNet"))->enabled) {
 	while(opt) {
 	    char *lnetname = opt->strarg;
6840d862
 	    struct LOCALNET *l;
58481352
 	    char *mask = strrchr(lnetname, *PATHSEP);
6840d862
 
 	    if(mask) {
 		*mask='\0';
 		mask++;
 	    }
99b2cb15
 	    if(!strcasecmp(lnetname, "local")) lnetname = NULL;
 	    if((l = localnet(lnetname, mask)) == NULL) {
6840d862
 		localnets_free();
 		return 1;
 	    }
 	    l->next = lnet;
 	    lnet = l;
278dc6b3
 	    opt = opt->nextarg;
6840d862
 	}
     }
     return 0;
 }
 
4c237bcf
 /*
  * Local Variables:
  * mode: c
  * c-basic-offset: 4
  * tab-width: 8
  * End: 
  * vim: set cindent smartindent autoindent softtabstop=4 shiftwidth=4 tabstop=8: 
  */