mirror of https://github.com/Cisco-Talos/clamav
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
549 lines
12 KiB
549 lines
12 KiB
/*
|
|
* 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
|
|
|
|
/* for Solaris, so that both FDPassing and IPV6 work */
|
|
#define __EXTENSIONS__
|
|
/* must be first because it may define _XOPEN_SOURCE */
|
|
#include "shared/fdpassing.h"
|
|
#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>
|
|
#include <netdb.h>
|
|
#include <sys/uio.h>
|
|
|
|
#include "shared/output.h"
|
|
#include "shared/optparser.h"
|
|
#include "libclamav/others.h"
|
|
#include "netcode.h"
|
|
|
|
#ifdef HAVE_STRERROR_R
|
|
#define strerror_print(msg) \
|
|
strerror_r(errno, er, sizeof(er)); \
|
|
logg(msg": %s\n", er);
|
|
#else
|
|
#define strerror_print(msg) \
|
|
logg(msg"\n");
|
|
#endif
|
|
|
|
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;
|
|
char *tempdir = NULL;
|
|
|
|
/* for connect and send */
|
|
#define TIMEOUT 30
|
|
/* for recv */
|
|
long readtimeout;
|
|
|
|
|
|
static int nc_socket(struct CP_ENTRY *cpe) {
|
|
int flags, s = socket(cpe->server->sa_family, SOCK_STREAM, 0);
|
|
char er[256];
|
|
|
|
if (s == -1) {
|
|
strerror_print("!Failed to create socket");
|
|
return -1;
|
|
}
|
|
flags = fcntl(s, F_GETFL, 0);
|
|
if (flags == -1) {
|
|
strerror_print("!fcntl_get failed");
|
|
close(s);
|
|
return -1;
|
|
}
|
|
flags |= O_NONBLOCK;
|
|
if (fcntl(s, F_SETFL, flags) == -1) {
|
|
strerror_print("!fcntl_set failed");
|
|
close(s);
|
|
return -1;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
|
|
static int nc_connect(int s, struct CP_ENTRY *cpe) {
|
|
time_t timeout = time(NULL) + TIMEOUT;
|
|
int res = connect(s, cpe->server, cpe->socklen);
|
|
struct timeval tv;
|
|
char er[256];
|
|
|
|
if (!res) return 0;
|
|
if (errno != EINPROGRESS) {
|
|
strerror_print("*connect failed");
|
|
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;
|
|
|
|
if (res == -1 && errno == EINTR && ((now = time(NULL)) < timeout)) {
|
|
tv.tv_sec = timeout - now;
|
|
tv.tv_usec = 0;
|
|
continue;
|
|
}
|
|
logg("*Failed to establish a connection to clamd\n");
|
|
close(s);
|
|
return -1;
|
|
}
|
|
if (getsockopt(s, SOL_SOCKET, SO_ERROR, &s_err, &s_len) || s_err) {
|
|
logg("*Failed to establish a connection to clamd\n");
|
|
close(s);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
int nc_send(int s, const void *buff, size_t len) {
|
|
char *buf = (char *)buff;
|
|
|
|
while(len) {
|
|
int res = send(s, buf, len, 0);
|
|
time_t timeout = time(NULL) + TIMEOUT;
|
|
struct timeval tv;
|
|
char er[256];
|
|
|
|
if(res!=-1) {
|
|
len-=res;
|
|
buf+=res;
|
|
continue;
|
|
}
|
|
if(errno != EAGAIN && errno != EWOULDBLOCK) {
|
|
strerror_print("!send failed");
|
|
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;
|
|
|
|
if (res == -1 && errno == EINTR && ((now = time(NULL)) < timeout)) {
|
|
tv.tv_sec = timeout - now;
|
|
tv.tv_usec = 0;
|
|
continue;
|
|
}
|
|
logg("!Failed stream to clamd\n");
|
|
close(s);
|
|
return 1;
|
|
}
|
|
len-=s_len;
|
|
buf+=s_len;
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
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];
|
|
strerror_print("!clamfi_eom: FD send failed");
|
|
close(s);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
char *nc_recv(int s) {
|
|
char buf[128], *ret=NULL;
|
|
time_t now, timeout = time(NULL) + readtimeout;
|
|
struct timeval tv;
|
|
fd_set fds;
|
|
int res;
|
|
unsigned int len = 0;
|
|
|
|
while(1) {
|
|
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);
|
|
|
|
res = select(s+1, &fds, NULL, NULL, &tv);
|
|
if(res<1) {
|
|
if (res != -1 || errno != EINTR)
|
|
timeout = 0;
|
|
continue;
|
|
}
|
|
|
|
res = recv(s, &buf[len], sizeof(buf) - len, 0);
|
|
if(res==-1) {
|
|
char er[256];
|
|
strerror_print("!recv failed after successful select");
|
|
close(s);
|
|
return NULL;
|
|
}
|
|
len += res;
|
|
if(len && buf[len-1] == '\n') break;
|
|
if(len >= sizeof(buf)) {
|
|
logg("!Overlong reply from clamd\n");
|
|
close(s);
|
|
return NULL;
|
|
}
|
|
}
|
|
if(!(ret = (char *)malloc(len+1))) {
|
|
logg("!malloc(%d) failed\n", len+1);
|
|
close(s);
|
|
return NULL;
|
|
}
|
|
memcpy(ret, buf, len);
|
|
ret[len]='\0';
|
|
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;
|
|
|
|
if(s>=0 && !nc_send(s, "nPING\n", 6) && (reply = nc_recv(s))) {
|
|
cpe->dead = strcmp(reply, "PONG\n")!=0;
|
|
free(reply);
|
|
close(s);
|
|
} else cpe->dead = 1;
|
|
return;
|
|
}
|
|
|
|
|
|
int nc_connect_rand(int *main, int *alt, int *local) {
|
|
struct CP_ENTRY *cpe = cpool_get_rand(main);
|
|
|
|
if(!cpe) return 1;
|
|
*local = (cpe->server->sa_family == AF_UNIX);
|
|
if(*local) {
|
|
char *unlinkme;
|
|
if(cli_gentempfd(tempdir, &unlinkme, alt) != CL_SUCCESS) {
|
|
logg("!Failed to create temporary file\n");
|
|
close(*main);
|
|
return 1;
|
|
}
|
|
unlink(unlinkme);
|
|
free(unlinkme);
|
|
} else {
|
|
char *reply=NULL, *port;
|
|
int nport;
|
|
struct CP_ENTRY new_cpe;
|
|
union {
|
|
struct sockaddr_in sa4;
|
|
struct sockaddr_in6 sa6;
|
|
} sa;
|
|
|
|
if(nc_send(*main, "nSTREAM\n", 8) || !(reply = nc_recv(*main)) || !(port = strstr(reply, "PORT"))) {
|
|
logg("!Failed to communicate with clamd\n");
|
|
if(reply) {
|
|
free(reply);
|
|
close(*main);
|
|
}
|
|
return 1;
|
|
}
|
|
port+=5;
|
|
sscanf(port, "%d", &nport);
|
|
free(reply);
|
|
if(cpe->server->sa_family == AF_INET && cpe->socklen == sizeof(struct sockaddr_in)) {
|
|
memcpy(&sa, cpe->server, sizeof(struct sockaddr_in));
|
|
sa.sa4.sin_port = htons(nport);
|
|
new_cpe.socklen = sizeof(struct sockaddr_in);
|
|
} else if(cpe->server->sa_family == AF_INET6 && cpe->socklen == sizeof(struct sockaddr_in6)) {
|
|
memcpy(&sa, cpe->server, sizeof(struct sockaddr_in6));
|
|
sa.sa6.sin6_port = htons(nport);
|
|
new_cpe.socklen = sizeof(struct sockaddr_in6);
|
|
} else {
|
|
logg("!WTF WHY AM I DOING HERE???\n");
|
|
close(*main);
|
|
return 1;
|
|
}
|
|
new_cpe.server = (struct sockaddr *)&sa;
|
|
if ((*alt = nc_connect_entry(&new_cpe)) == -1) {
|
|
logg("!Failed to communicate with clamd for streaming\n");
|
|
close(*main);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int resolve(char *name, uint32_t *family, uint32_t *host) {
|
|
struct addrinfo hints, *res;
|
|
|
|
if(!name) {
|
|
/* l->basehost[0] = l->basehost[1] = l->basehost[2] = l->basehost[3] = 0; DONT BOTHER*/
|
|
*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);
|
|
/* host[1] = host[2] = host[3] = 0; DONT BOTHER*/
|
|
} 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) {
|
|
host[i>>2] = u;
|
|
j = u = 0;
|
|
}
|
|
}
|
|
} else {
|
|
logg("!Unsupported address type for LocalNet %s\n", name);
|
|
freeaddrinfo(res);
|
|
return 1;
|
|
}
|
|
freeaddrinfo(res);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static struct LOCALNET *localnet(char *name, char *mask) {
|
|
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) {
|
|
l->mask[0] = l->mask[1] = l->mask[2] = l->mask[3] = 0x0;
|
|
return l;
|
|
}
|
|
|
|
if(!mask || !*mask) nmask = 32 + 96*(l->family == INET6_HOST);
|
|
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;
|
|
}
|
|
|
|
l->mask[0] = l->mask[1] = l->mask[2] = l->mask[3] = 0;
|
|
for(i=0; i<nmask; i++)
|
|
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];
|
|
|
|
return l;
|
|
}
|
|
|
|
|
|
static int islocalnet(uint32_t family, uint32_t *host) {
|
|
struct LOCALNET* l = lnet;
|
|
|
|
if(!l) return 0;
|
|
while(l) {
|
|
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;
|
|
l=l->next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
int islocalnet_name(char *name) {
|
|
uint32_t host[4], family;
|
|
|
|
if(!lnet) return 0;
|
|
if(resolve(name, &family, host)) {
|
|
logg("*Cannot resolv %s\n", name);
|
|
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);
|
|
}
|
|
|
|
|
|
void localnets_free(void) {
|
|
while(lnet) {
|
|
struct LOCALNET *l = lnet->next;
|
|
|
|
free(lnet);
|
|
lnet = l;
|
|
}
|
|
}
|
|
|
|
|
|
int localnets_init(struct optstruct *opts) {
|
|
const struct optstruct *opt;
|
|
|
|
if((opt = optget(opts, "LocalNet"))->enabled) {
|
|
while(opt) {
|
|
char *lnetname = opt->strarg;
|
|
struct LOCALNET *l;
|
|
char *mask = strrchr(lnetname, '/');
|
|
|
|
if(mask) {
|
|
*mask='\0';
|
|
mask++;
|
|
}
|
|
if(!strcasecmp(lnetname, "local")) lnetname = NULL;
|
|
if((l = localnet(lnetname, mask)) == NULL) {
|
|
localnets_free();
|
|
return 1;
|
|
}
|
|
l->next = lnet;
|
|
lnet = l;
|
|
opt = opt->nextarg;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Local Variables:
|
|
* mode: c
|
|
* c-basic-offset: 4
|
|
* tab-width: 8
|
|
* End:
|
|
* vim: set cindent smartindent autoindent softtabstop=4 shiftwidth=4 tabstop=8:
|
|
*/
|
|
|