libclamav/www.c
1bdaf813
 /*
c9a070c9
  *  Copyright (C) 2014, 2017 Cisco and/or its affiliates. All rights reserved.
1bdaf813
  *
  *  Author: Shawn Webb
  *
  *  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.
  */
 
e5e6be98
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
f2571e34
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
4a4b84da
 #if HAVE_UNISTD_H
 #include <unistd.h>
 #endif
 
f2571e34
 #include <ctype.h>
 
 #include <sys/types.h>
212b72c4
 #include <fcntl.h>
 
 #include <errno.h>
4a4b84da
 
 #if !defined(_WIN32)
f2571e34
 #include <sys/socket.h>
ff506263
 #if HAVE_SYS_SELECT_H
e6786fef
 #include <sys/select.h>
ff506263
 #else
 #include <sys/time.h>
 #endif
f2571e34
 #include <netinet/in.h>
 #include <netdb.h>
4a4b84da
 #endif
f2571e34
 
e5e6be98
 #include "platform.h"
 
f2571e34
 #include "libclamav/others.h"
 #include "libclamav/clamav.h"
 #include "libclamav/www.h"
 
4e1236c8
 int connect_host(const char *host, const char *port, uint32_t timeout, int useAsync)
f2571e34
 {
c9a070c9
     int sockfd = -1;
     struct addrinfo hints, *servinfo = NULL, *p = NULL;
     int flags = 0, error;
00b9ef3b
     socklen_t len;
     fd_set read_fds, write_fds;
212b72c4
     struct timeval tv;
1725b8a3
 #ifdef _WIN32
 	int iResult;
 	WSADATA wsaData;
 
 	/* Force initialization of Windows sockets, even if it already happened elsewhere */
 	iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
 	if (iResult != 0)
 		return -1;
 #endif
f2571e34
 
     memset(&hints, 0x00, sizeof(struct addrinfo));
     hints.ai_family = AF_UNSPEC;
     hints.ai_socktype = SOCK_STREAM;
 
     if (getaddrinfo(host, port, &hints, &servinfo))
         return -1;
 
     for (p = servinfo; p != NULL; p = p->ai_next) {
         sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
         if (sockfd < 0)
             continue;
 
212b72c4
         if (useAsync) {
             flags = fcntl(sockfd, F_GETFL, 0);
00b9ef3b
             if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) < 0) {
e5e6be98
                 closesocket(sockfd);
00b9ef3b
                 continue;
             }
212b72c4
         }
 
00b9ef3b
         if ((error = connect(sockfd, p->ai_addr, p->ai_addrlen))) {
64b1b3e1
             if (useAsync) {
                 if (errno != EINPROGRESS) {
e5e6be98
                     closesocket(sockfd);
64b1b3e1
                     continue;
                 }
00b9ef3b
                 errno = 0;
64b1b3e1
 
                 FD_ZERO(&write_fds);
00b9ef3b
                 FD_ZERO(&read_fds);
                 FD_SET(sockfd, &read_fds);
64b1b3e1
                 FD_SET(sockfd, &write_fds);
 
8fc15040
                 /* TODO: Make this timeout configurable */
4e1236c8
                 tv.tv_sec = timeout;
64b1b3e1
                 tv.tv_usec = 0;
00b9ef3b
                 if (select(sockfd + 1, &read_fds, &write_fds, NULL, &tv) <= 0) {
e5e6be98
                     closesocket(sockfd);
00b9ef3b
                     continue;
                 }
64b1b3e1
 
00b9ef3b
                 if (FD_ISSET(sockfd, &read_fds) || FD_ISSET(sockfd, &write_fds)) {
                     len = sizeof(error);
                     if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
e5e6be98
                         closesocket(sockfd);
00b9ef3b
                         continue;
                     }
                 } else {
e5e6be98
                     closesocket(sockfd);
64b1b3e1
                     continue;
                 }
             } else {
e5e6be98
                 closesocket(sockfd);
212b72c4
                 continue;
             }
f2571e34
         }
 
00b9ef3b
 
f2571e34
         /* Connected to host */
         break;
     }
 
     if (!(p)) {
         freeaddrinfo(servinfo);
0c58ddd2
         if (sockfd >= 0)
e5e6be98
             closesocket(sockfd);
f2571e34
         return -1;
     }
 
     freeaddrinfo(servinfo);
 
20ed22bc
     /* Return to using a synchronous socket to make Linux happy */
c9a070c9
     if (useAsync && (sockfd >= 0)) {
00b9ef3b
         if (fcntl(sockfd, F_SETFL, flags) < 0) {
e5e6be98
             closesocket(sockfd);
00b9ef3b
             return -1;
         }
     }
20ed22bc
 
f2571e34
     return sockfd;
 }
 
 size_t encoded_size(const char *postdata)
 {
     const char *p;
     size_t len=0;
 
     for (p = postdata; *p != '\0'; p++)
         len += isalnum(*p) ? 1 : 3;
 
     return len;
 }
 
 char *encode_data(const char *postdata)
 {
     char *buf;
     size_t bufsz, i, j;
 
     bufsz = encoded_size(postdata);
     if (bufsz == 0)
         return NULL;
 
     buf = cli_calloc(1, bufsz+1);
     if (!(buf))
         return NULL;
 
     for (i=0, j=0; postdata[i] != '\0'; i++) {
         if (isalnum(postdata[i])) {
             buf[j++] = postdata[i];
         } else {
             sprintf(buf+j, "%%%02x", postdata[i]);
             j += 3;
         }
     }
 
     return buf;
 }
 
4e1236c8
 void submit_post(const char *host, const char *port, const char *method, const char *url, const char *postdata, uint32_t timeout)
f2571e34
 {
00b9ef3b
     int sockfd, n;
f2571e34
     unsigned int i;
f721faca
     char *buf, *encoded=NULL;
00b9ef3b
     size_t bufsz;
     ssize_t recvsz;
54e9f2f9
     char chunkedlen[21];
8fc15040
     fd_set readfds;
     struct timeval tv;
f721faca
     char *acceptable_methods[] = {
         "GET",
         "PUT",
         "POST",
         NULL
     };
 
     for (i=0; acceptable_methods[i] != NULL; i++)
         if (!strcmp(method, acceptable_methods[i]))
             break;
f2571e34
 
f721faca
     if (acceptable_methods[i] == NULL)
         return;
f2571e34
 
f721faca
     bufsz = strlen(method);
7042d882
     bufsz += sizeof("   HTTP/1.1") + 2; /* Yes. Three blank spaces. +1 for the \n */
f2571e34
     bufsz += strlen(url);
7042d882
     bufsz += sizeof("Host: \r\n");
b79ea1a3
     bufsz += strlen(host);
7042d882
     bufsz += sizeof("Connection: Close\r\n");
     bufsz += 4; /* +4 for \r\n\r\n */
f721faca
 
     if (!strcmp(method, "POST") || !strcmp(method, "PUT")) {
         encoded = encode_data(postdata);
         if (!(encoded))
             return;
         snprintf(chunkedlen, sizeof(chunkedlen), "%zu", strlen(encoded));
7042d882
         bufsz += sizeof("Content-Type: application/x-www-form-urlencoded\r\n");
         bufsz += sizeof("Content-Length: \r\n");
f721faca
         bufsz += strlen(chunkedlen);
         bufsz += strlen(encoded);
     }
f2571e34
 
     buf = cli_calloc(1, bufsz);
     if (!(buf)) {
f721faca
         if ((encoded))
             free(encoded);
 
f2571e34
         return;
     }
 
7042d882
     snprintf(buf, bufsz, "%s %s HTTP/1.1\r\n", method, url);
     snprintf(buf+strlen(buf), bufsz-strlen(buf), "Host: %s\r\n", host);
     snprintf(buf+strlen(buf), bufsz-strlen(buf), "Connection: Close\r\n");
f721faca
 
     if (!strcmp(method, "POST") || !strcmp(method, "PUT")) {
ede7a1f0
         snprintf(buf+strlen(buf), bufsz-strlen(buf), "Content-Type: application/x-www-form-urlencoded\r\n");
7042d882
         snprintf(buf+strlen(buf), bufsz-strlen(buf), "Content-Length: %s\r\n", chunkedlen);
         snprintf(buf+strlen(buf), bufsz-strlen(buf), "\r\n");
f721faca
         snprintf(buf+strlen(buf), bufsz-strlen(buf), "%s", encoded);
         free(encoded);
     }
76801f99
 #if defined(_WIN32)
 	sockfd = connect_host(host, port, timeout, 0);
 #else
4e1236c8
     sockfd = connect_host(host, port, timeout, 1);
76801f99
 #endif
f2571e34
     if (sockfd < 0) {
         free(buf);
         return;
     }
 
690a27b8
     cli_dbgmsg("stats - Connected to %s:%s\n", host, port);
 
cd94be7a
     if ((size_t)send(sockfd, buf, strlen(buf), 0) != (size_t)strlen(buf)) {
e5e6be98
         closesocket(sockfd);
00b9ef3b
         free(buf);
         return;
212b72c4
     }
f2571e34
 
690a27b8
     cli_dbgmsg("stats - Sending %s\n", buf);
 
b79ea1a3
     while (1) {
8fc15040
         FD_ZERO(&readfds);
         FD_SET(sockfd, &readfds);
 
cfeb7723
         /*
          * Check to make sure the stats submitted okay (so that we don't kill the HTTP request
00b9ef3b
          * while it's being processed). Give a ten-second timeout so we don't have a major
          * impact on scanning.
cfeb7723
          */
4e1236c8
         tv.tv_sec = timeout;
8fc15040
         tv.tv_usec = 0;
00b9ef3b
         if ((n = select(sockfd+1, &readfds, NULL, NULL, &tv)) <= 0)
             break;
b79ea1a3
 
8fc15040
         if (FD_ISSET(sockfd, &readfds)) {
             memset(buf, 0x00, bufsz);
b8451749
             if ((recvsz = recv(sockfd, buf, bufsz-1, 0) <= 0))
8fc15040
                 break;
 
0c58ddd2
             buf[bufsz-1] = '\0';
 
27d0dd6a
             cli_dbgmsg("stats - received: %s\n", buf);
 
690a27b8
             if (strstr(buf, "STATOK")) {
                 cli_dbgmsg("stats - Data received okay\n");
8fc15040
                 break;
690a27b8
             }
8fc15040
         }
b79ea1a3
     }
 
e5e6be98
     closesocket(sockfd);
f2571e34
     free(buf);
 }