blob: c916f61bf33b6d998683b39a30cf71403439ef51 [file] [log] [blame] [edit]
/*
* Copyright (C) Tildeslash Ltd. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3.
*
* 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 Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
*
* You must obey the GNU Affero General Public License in all respects
* for all of the code used other than OpenSSL.
*/
#include "config.h"
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_REGEX_H
#include <regex.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#include "md5.h"
#include "sha1.h"
#include "base64.h"
#include "protocol.h"
#include "httpstatus.h"
#include "util/Str.h"
/**
* A HTTP test.
*
* We send the following request to the server:
* 'GET / HTTP/1.1' ... if request statement isn't defined
* 'GET /custom/page HTTP/1.1' ... if request statement is defined
* and check the server's status code.
*
* If the statement defines hostname, it's used in the 'Host:' header
* otherwise a default (empty) Host header is set.
*
* If the status code is >= 400, an error has occurred.
* Return TRUE if the status code is OK, otherwise FALSE.
* @file
*/
/* ------------------------------------------------------------- Definitions */
#undef READ_SIZE
#define READ_SIZE 8192
#define LINE_SIZE 512
/* ----------------------------------------------------------------- Private */
static int do_regex(Socket_T socket, int content_length, Request_T R) {
int n, size = 0, length = 0, rv = FALSE, regex_return;
char *buf = NULL;
if (content_length == 0) {
socket_setError(socket, "HTTP error: No content returned from server\n");
return FALSE;
} else if (content_length < 0) { /* Not defined in response */
content_length = HTTP_CONTENT_MAX;
} else if (content_length > HTTP_CONTENT_MAX) {
content_length = HTTP_CONTENT_MAX;
}
length = content_length;
buf = ALLOC(content_length + 1);
do {
n = socket_read(socket, &buf[size], length);
if (n <= 0)
break;
size += n;
length -= n;
} while (length > 0);
if (size == 0) {
socket_setError(socket, "HTTP error: receiving data -- %s\n", STRERROR);
goto error;
}
buf[size] = 0;
#ifdef HAVE_REGEX_H
regex_return = regexec(R->regex, buf, 0, NULL, 0);
#else
regex_return = strstr(buf, R->regex) ? 0 : 1;
#endif
switch (R->operator) {
case OPERATOR_EQUAL:
if (regex_return == 0) {
rv = TRUE;
DEBUG("HTTP: Regular expression matches\n");
} else {
#ifdef HAVE_REGEX_H
char errbuf[STRLEN];
regerror(regex_return, NULL, errbuf, sizeof(errbuf));
socket_setError(socket, "HTTP error: Regular expression doesn't match: %s\n", errbuf);
#else
socket_setError(socket, "HTTP error: Regular expression doesn't match\n");
#endif
}
break;
case OPERATOR_NOTEQUAL:
if (regex_return == 0) {
socket_setError(socket, "HTTP error: Regular expression matches\n");
} else {
rv = TRUE;
DEBUG("HTTP: Regular expression doesn't match\n");
}
break;
default:
socket_setError(socket, "HTTP error: Invalid content operator\n");
}
error:
FREE(buf);
return rv;
}
static int check_request_checksum(Socket_T socket, int content_length, char *checksum, int hashtype) {
int n, keylength = 0;
MD_T result, hash;
md5_context_t ctx_md5;
sha1_context_t ctx_sha1;
char buf[READ_SIZE];
if (content_length <= 0) {
DEBUG("HTTP warning: Response does not contain a valid Content-Length -- cannot compute checksum\n");
return TRUE;
}
switch (hashtype) {
case HASH_MD5:
md5_init(&ctx_md5);
for (n = 0; content_length > 0; content_length -= n) {
if ((n = socket_read(socket, buf, content_length > sizeof(buf) ? sizeof(buf) : content_length)) < 0)
break;
md5_append(&ctx_md5, (const md5_byte_t *)buf, n);
}
md5_finish(&ctx_md5, (md5_byte_t *)hash);
keylength = 16; /* Raw key bytes not string chars! */
break;
case HASH_SHA1:
sha1_init(&ctx_sha1);
for (n = 0; content_length > 0; content_length -= n) {
if ((n = socket_read(socket, buf, content_length > sizeof(buf) ? sizeof(buf) : content_length)) < 0)
break;
sha1_append(&ctx_sha1, (md5_byte_t *)buf, n);
}
sha1_finish(&ctx_sha1, (md5_byte_t *)hash);
keylength = 20; /* Raw key bytes not string chars! */
break;
default:
socket_setError(socket, "HTTP checksum error: Unknown hash type\n");
return FALSE;
}
if (strncasecmp(Util_digest2Bytes((unsigned char *)hash, keylength, result), checksum, keylength * 2) != 0) {
socket_setError(socket, "HTTP checksum error: Document checksum mismatch\n");
return FALSE;
} else {
DEBUG("HTTP: Succeeded testing document checksum\n");
}
return TRUE;
}
/**
* Check that the server returns a valid HTTP response as well as checksum
* or content regex if required
* @param s A socket
* @return TRUE if the response is valid otherwise FALSE
*/
static int check_request(Socket_T socket, Port_T P) {
int status, content_length = -1;
char buf[LINE_SIZE];
if (! socket_readln(socket, buf, LINE_SIZE)) {
socket_setError(socket, "HTTP: Error receiving data -- %s\n", STRERROR);
return FALSE;
}
Str_chomp(buf);
if (! sscanf(buf, "%*s %d", &status)) {
socket_setError(socket, "HTTP error: Cannot parse HTTP status in response: %s\n", buf);
return FALSE;
}
if (status >= 400) {
socket_setError(socket, "HTTP error: Server returned status %d\n", status);
return FALSE;
}
/* Get Content-Length header value */
while (socket_readln(socket, buf, LINE_SIZE)) {
if ((buf[0] == '\r' && buf[1] == '\n') || (buf[0] == '\n'))
break;
Str_chomp(buf);
if (Str_startsWith(buf, "Content-Length")) {
if (! sscanf(buf, "%*s%*[: ]%d", &content_length)) {
socket_setError(socket, "HTTP error: Parsing Content-Length response header '%s'\n", buf);
return FALSE;
}
if (content_length < 0) {
socket_setError(socket, "HTTP error: Illegal Content-Length response header '%s'\n", buf);
return FALSE;
}
}
}
if (P->url_request && P->url_request->regex && ! do_regex(socket, content_length, P->url_request))
return FALSE;
if (P->request_checksum)
return check_request_checksum(socket, content_length, P->request_checksum, P->request_hashtype);
return TRUE;
}
static char *get_auth_header(Port_T P, char *auth, int l) {
char *b64;
char buf[STRLEN];
char *username = NULL;
char *password = NULL;
if (P->url_request) {
URL_T U = P->url_request->url;
if (U) {
username = U->user;
password = U->password;
}
}
if (! (username && password))
return auth;
snprintf(buf, STRLEN, "%s:%s", username, password);
if (! (b64 = encode_base64(strlen(buf), (unsigned char *)buf)) )
return auth;
snprintf(auth, l, "Authorization: Basic %s\r\n", b64);
FREE(b64);
return auth;
}
/* ------------------------------------------------------------------ Public */
int check_http(Socket_T socket) {
Port_T P;
char host[STRLEN];
char auth[STRLEN]= {0};
const char *request = NULL;
const char *hostheader = NULL;
ASSERT(socket);
P = socket_get_Port(socket);
ASSERT(P);
request = P->request ? P->request : "/";
Util_getHTTPHostHeader(socket, host, STRLEN);
hostheader = P->request_hostheader ? P->request_hostheader : host;
if (socket_print(socket,
"GET %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Accept: */*\r\n"
"Connection: close\r\n"
"User-Agent: %s/%s\r\n"
"%s\r\n",
request, hostheader, prog, VERSION,
get_auth_header(P, auth, STRLEN)) < 0) {
socket_setError(socket, "HTTP: error sending data -- %s\n", STRERROR);
return FALSE;
}
return check_request(socket, P);
}