| /* |
| * 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); |
| } |
| |