/*
* Soft: Perform a GET query to a remote HTTP/HTTPS server.
* Set a timer to compute global remote server response
* time.
*
* Part: HTTP asynchronous engine.
*
* Authors: Alexandre Cassen, <acassen@linux-vs.org>
*
* 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.
*
* 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.
*
* Copyright (C) 2001-2017 Alexandre Cassen, <acassen@gmail.com>
*/
#include "config.h"
/* system includes */
#include <openssl/err.h>
/* keepalived includes */
#include "utils.h"
#include "html.h"
/* genhash includes */
#include "include/http.h"
#include "include/layer4.h"
/* GET processing command */
static const char *request_template =
"GET %s HTTP/1.%d\r\n"
"User-Agent: KeepAlive GenHash Client\r\n"
"%s"
"Host: %s%s\r\n\r\n";
static const char *request_template_ipv6 =
"GET %s HTTP/1.%d\r\n"
"User-Agent: KeepAlive GenHash Client\r\n"
"%s"
"Host: [%s]%s\r\n\r\n";
/* Output delimiters */
#define DELIM_BEGIN "-----------------------["
#define DELIM_END "]-----------------------\n"
#define HTTP_HEADER_HEXA DELIM_BEGIN" HTTP Header Buffer "DELIM_END
#define HTTP_HEADER_ASCII DELIM_BEGIN" HTTP Header Ascii Buffer "DELIM_END
#define HTML_HEADER_HEXA DELIM_BEGIN" HTML Buffer "DELIM_END
#define HTML_HASH DELIM_BEGIN" HTML hash resulting "DELIM_END
#define HTML_HASH_FINAL DELIM_BEGIN" HTML hash final resulting "DELIM_END
/*
* The global design of this checker is the following :
*
* - All the actions are done asynchronously.
* - All the actions handle timeout connection.
* - All the actions handle error from low layer to upper
* layers.
*
* The global synopsis of the inter-thread-call is :
*
* http_request_thread (send SSL GET request)
* v
* / \
* / \
* v v
* http_read_thread ssl_read_thread (perform HTTP|SSL stream)
* v v
* ------------------------------
* finalize / epilog
*/
const hash_t hashes[hash_guard] = {
[hash_md5] = {
(hash_init_f) MD5_Init,
(hash_update_f) MD5_Update,
(hash_final_f) MD5_Final,
MD5_DIGEST_LENGTH,
"MD5",
"MD5SUM",
},
#ifdef _WITH_SHA1_
[hash_sha1] = {
(hash_init_f) SHA1_Init,
(hash_update_f) SHA1_Update,
(hash_final_f) SHA1_Final,
SHA_DIGEST_LENGTH,
"SHA1",
"SHA1SUM",
}
#endif
};
#define HASH_LENGTH(sock) ((sock)->hash->length)
#define HASH_LABEL(sock) ((sock)->hash->label)
#define HASH_INIT(sock) ((sock)->hash->init(&(sock)->context))
#define HASH_UPDATE(sock, buf, len) \
if ((sock)->content_len == -1 || (sock)->rx_bytes < (sock)->content_len) \
((sock)->hash->update(&(sock)->context, (buf), (sock)->content_len == -1 || (sock)->content_len - (sock)->rx_bytes >= len ? len : (sock)->content_len - (sock)->rx_bytes))
#define HASH_FINAL(sock, digest) \
((sock)->hash->final((digest), &(sock)->context))
/* free allocated pieces */
static void
free_all(thread_ref_t thread)
{
SOCK *sock_obj = THREAD_ARG(thread);
#ifdef _GENHASH_DEBUG_
fprintf(stderr, "Total read size read = %d Bytes, fd:%d\n",
sock_obj->total_size, sock_obj->fd);
#endif
if (sock_obj->buffer)
FREE(sock_obj->buffer);
/*
* Decrement the current global get number.
* => free the reserved thread
*/
req->response_time = timer_long(timer_now());
thread_add_terminate_event(thread->master);
}
/* Simple epilog functions. */
int
epilog(thread_ref_t thread)
{
#ifdef _GENHASH_DEBUG_
fprintf(stderr, "Timeout on URL : [%s]\n", req->url);
#endif
free_all(thread);
return 0;
}
/* Simple finalization function */
int
finalize(thread_ref_t thread)
{
SOCK *sock_obj = THREAD_ARG(thread);
unsigned char digest_length = HASH_LENGTH(sock_obj);
unsigned char *digest = MALLOC(digest_length);
int i;
/* Compute final hash digest */
HASH_FINAL(sock_obj, digest);
if (req->verbose) {
printf("\n");
printf(HTML_HASH);
dump_buffer((char *) digest, digest_length, stdout, 0);
printf(HTML_HASH_FINAL);
}
printf("%s = ", HASH_LABEL(sock_obj));
for (i = 0; i < digest_length; i++)
printf("%02x", digest[i]);
if (sock_obj->content_len != -1 && sock_obj->content_len != sock_obj->rx_bytes)
printf ("\nWARNING - Content-Length (%zd) does not match received bytes (%zd).", sock_obj->content_len, sock_obj->rx_bytes);
printf("\n\n");
#ifdef _GENHASH_DEBUG_
fprintf(stderr, "Finalize : [%s]\n", req->url);
#endif
free_all(thread);
FREE(digest);
return 0;
}
/* Dump HTTP header */
static void
http_dump_header(char *buffer, size_t size)
{
dump_buffer(buffer, size, stdout, 0);
printf(HTTP_HEADER_ASCII);
printf("%*s\n", (int)size, buffer);
}
static ssize_t
find_content_len(char *buffer, size_t size)
{
const char *content_len_str = "Content-Length:";
unsigned long content_len;
bool valid_len = false;
char sav_char = buffer[size];
char *p;
char *end;
buffer[size] = '\0';
p = strstr(buffer, content_len_str);
if (p &&
(p == buffer || p[-1] == '\r' || p[-1] == '\n')) {
p += strlen(content_len_str);
content_len = strtoul(p, &end, 10);
/* Make sure we have read to the end of the line */
if (!*end || *end == '\r' || *end == '\n')
valid_len = true;
}
buffer[size] = sav_char;
if (valid_len)
return (ssize_t)content_len;
return -1;
}
/* Process incoming stream */
int
http_process_stream(SOCK * sock_obj, int r)
{
sock_obj->size += r;
sock_obj->total_size += r;
if (!sock_obj->extracted) {
if (req->verbose)
printf(HTTP_HEADER_HEXA);
if ((sock_obj->extracted = extract_html(sock_obj->buffer, (size_t)sock_obj->size))) {
sock_obj->content_len =
find_content_len(sock_obj->buffer + (sock_obj->size - r),
(size_t)((sock_obj->extracted - sock_obj->buffer) - (sock_obj->size - r)));
if (req->verbose)
http_dump_header(sock_obj->buffer + (sock_obj->size - r),
(size_t)((sock_obj->extracted - sock_obj->buffer) - (sock_obj->size - r)));
r = sock_obj->size - (int)(sock_obj->extracted - sock_obj->buffer);
if (r) {
if (req->verbose) {
printf(HTML_HEADER_HEXA);
dump_buffer(sock_obj->extracted, (size_t)r, stdout, 0);
}
memmove(sock_obj->buffer, sock_obj->extracted, (size_t)r);
HASH_UPDATE(sock_obj, sock_obj->buffer, r);
sock_obj->rx_bytes += r;
r = 0;
}
sock_obj->size = r;
} else {
sock_obj->content_len = find_content_len(sock_obj->buffer + (sock_obj->size - r), (size_t)r);
if (req->verbose)
http_dump_header(sock_obj->buffer + (sock_obj->size - r), (size_t)r);
/* minimize buffer using no 2*CR/LF found yet */
if (sock_obj->size > 4) {
memmove(sock_obj->buffer,
sock_obj->buffer + sock_obj->size - 4, 4);
sock_obj->size = 4;
}
}
} else if (sock_obj->size) {
if (req->verbose)
dump_buffer(sock_obj->buffer, (size_t)r, stdout, 0);
HASH_UPDATE(sock_obj, sock_obj->buffer, sock_obj->size);
sock_obj->rx_bytes += sock_obj->size;
sock_obj->size = 0;
}
return 0;
}
/* Asynchronous HTTP stream reader */
static void
http_read_thread(thread_ref_t thread)
{
SOCK *sock_obj = THREAD_ARG(thread);
ssize_t r = 0;
/* Handle read timeout */
if (thread->type == THREAD_READ_TIMEOUT) {
exit_code = 1;
epilog(thread);
return;
}
/* read the HTTP stream */
r = MAX_BUFFER_LENGTH - sock_obj->size;
if (r <= 0) {
/* defensive check, should not occur */
fprintf(stderr, "HTTP socket buffer overflow (not consumed)\n");
r = MAX_BUFFER_LENGTH;
}
memset(sock_obj->buffer + sock_obj->size, 0, (size_t)r);
r = read(thread->u.f.fd, sock_obj->buffer + sock_obj->size, (size_t)r);
#ifdef _GENHASH_DEBUG_
fprintf(stderr, " [l:%zd,fd:%d]\n", r, sock_obj->fd);
#endif
if (r == 0) { /* EOF */
/* All the HTTP stream has been parsed */
finalize(thread);
} else if (r == -1) { /* error */
/* We have encountered a real read error */
#ifdef _GENHASH_DEBUG_
fprintf(stderr, "Read error with server [%s]:%d: %s\n",
req->ipaddress, ntohs(req->addr_port),
strerror(errno));
#endif
exit_code = 1;
epilog(thread);
return;
} else {
/* Handle the response stream */
http_process_stream(sock_obj, (int)r);
/*
* Register next http stream reader.
* Register itself to not perturbe global I/O multiplexer.
*/
thread_add_read(thread->master, http_read_thread, sock_obj,
thread->u.f.fd, req->timeout, true);
}
}
/* remote Web server is connected, send it the get url query. */
void
http_request_thread(thread_ref_t thread)
{
SOCK *sock_obj = THREAD_ARG(thread);
char *str_request;
const char *request_host;
const char *request_host_port;
int ret = 0;
char *str;
/* Handle read timeout */
if (thread->type == THREAD_WRITE_TIMEOUT) {
exit_code = 1;
epilog(thread);
return;
}
/* Allocate & clean the GET string */
str_request = (char *) MALLOC(GET_BUFFER_LENGTH);
if (req->vhost) {
/* If vhost was defined we don't need to override it's port */
request_host = req->vhost;
str = (char*) MALLOC(1);
*str = '\0';
request_host_port = str;
} else {
request_host = req->ipaddress;
/* Allocate a buffer for the port string ( ":" [0-9][0-9][0-9][0-9][0-9] "\0" ) */
str = (char*) MALLOC(7);
snprintf(str, 7, ":%d", ntohs(req->addr_port));
request_host_port = str;
}
snprintf(str_request, GET_BUFFER_LENGTH,
(req->dst && req->dst->ai_family == AF_INET6 && !req->vhost) ? request_template_ipv6 : request_template,
req->url,
req->http_protocol == HTTP_PROTOCOL_1_1 || req->http_protocol == HTTP_PROTOCOL_1_1K ? 1 : 0,
req->http_protocol == HTTP_PROTOCOL_1_0C || req->http_protocol == HTTP_PROTOCOL_1_1 ? "Connection: close\r\n" :
req->http_protocol == HTTP_PROTOCOL_1_0K || req->http_protocol == HTTP_PROTOCOL_1_1K ? "Connection: keep-alive\r\n" : "",
request_host, request_host_port);
FREE_CONST(request_host_port);
/* Send the GET request to remote Web server */
#ifdef _GENHASH_DEBUG_
fprintf(stderr, "Sending GET request [%s] on fd:%d\n", req->url, sock_obj->fd);
#endif
if (req->ssl)
ret = ssl_send_request(sock_obj->ssl, str_request, (int)strlen(str_request));
else
ret = (send(sock_obj->fd, str_request, strlen(str_request), 0) != -1) ? 1 : 0;
FREE(str_request);
if (!ret) {
fprintf(stderr, "Cannot send get request to [%s]:%d.\n",
req->ipaddress,
ntohs(req->addr_port));
exit_code = 1;
epilog(thread);
return;
}
/* Allocate & clean the get buffer */
sock_obj->buffer = (char *) MALLOC(MAX_BUFFER_LENGTH);
/* Initalize the hash context */
sock_obj->hash = &hashes[req->hash];
HASH_INIT(sock_obj);
sock_obj->rx_bytes = 0;
/* Register asynchronous http/ssl read thread */
if (req->ssl)
thread_add_read(thread->master, ssl_read_thread, sock_obj,
sock_obj->fd, req->timeout, true);
else
thread_add_read(thread->master, http_read_thread, sock_obj,
sock_obj->fd, req->timeout, true);
}