/* * Copyright (C), 2000-2007 by the monit project group. * All Rights Reserved. * * 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 3 of the License, or * (at your option) any later version. * * 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, see . */ #include #ifdef HAVE_STDIO_H #include #endif #ifdef HAVE_ERRNO_H #include #endif #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_REGEX_H #include #endif #ifdef HAVE_SYS_SOCKET_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_NETINET_IN_H #include #endif #include "md5.h" #include "sha.h" #include "base64.h" #include "protocol.h" #include "httpstatus.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. * * @author Jan-Henrik Haukeland, * @author Martin Pala, * @version \$Id: http.c,v 1.54 2007/10/16 03:05:41 hauk Exp $ * @file */ /* ------------------------------------------------------------- Definitions */ #undef READ_SIZE #define READ_SIZE 8192 #define LINE_SIZE 512 /* -------------------------------------------------------------- Prototypes */ static int check_request(Socket_T s, Port_T P); static char *get_auth_header(Port_T P, char *auth, int l); static int do_regex(Socket_T s, long content_length, Request_T R); static int check_request_checksum(Socket_T s, long content_length, char *checksum, int hashtype); /* ------------------------------------------------------------------ Public */ int check_http(Socket_T s) { Port_T P; char host[STRLEN]; char auth[STRLEN]= {0}; const char *request= NULL; ASSERT(s); P= socket_get_Port(s); ASSERT(P); request= P->request?P->request:"/"; if(socket_print(s, "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, Util_getHTTPHostHeader(s, host, STRLEN), prog, VERSION, get_auth_header(P, auth, STRLEN)) < 0) { LogError("HTTP: error sending data -- %s\n", STRERROR); return FALSE; } return check_request(s, P); } /* ----------------------------------------------------------------- Private */ /** * 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 s, Port_T P) { int status; char buf[LINE_SIZE]; long content_length= -1; if(! socket_readln(s, buf, LINE_SIZE)) { LogError("HTTP: error receiving data -- %s\n", STRERROR); return FALSE; } Util_chomp(buf); if(! sscanf(buf, "%*s %d", &status)) { LogError("HTTP error: cannot parse HTTP status in response: %s\n", buf); return FALSE; } if(status >= 400) { LogError("HTTP error: Server returned status %d\n", status); return FALSE; } /* Get Content-Length header value */ while(NULL != socket_readln(s, buf, LINE_SIZE)) { if((buf[0] == '\r' && buf[1] == '\n') || (buf[0] == '\n')) break; Util_chomp(buf); if(Util_startsWith(buf, "Content-Length")) { if(! sscanf(buf, "%*s%*[: ]%ld", &content_length)) { LogError("HTTP error: parsing Content-Length response header '%s'\n", buf); return FALSE; } if(content_length < 0) { LogError("HTTP error: Illegal Content-Length response header '%s'\n", buf); return FALSE; } } } if(P->url_request && P->url_request->regex) { if(! do_regex(s, content_length, P->url_request)) { LogError("HTTP error: Failed regular expression test on content" " returned from server\n"); return FALSE; } } if(P->request_checksum) { return check_request_checksum(s, content_length, P->request_checksum, P->request_hashtype); } return TRUE; } static int check_request_checksum(Socket_T s, long content_length, char *checksum, int hashtype) { int n; long size; MD_T result; char buf[READ_SIZE]; unsigned char hash[STRLEN]; int keylength=0; if(content_length <= 0) { DEBUG("HTTP warning: Response does not contain a valid Content-Length\n" "Cannot compute checksum\n"); return TRUE; } switch (hashtype) { case HASH_MD5: { struct md5_ctx ctx; md5_init_ctx(&ctx); while(content_length > 0) { size= content_length>READ_SIZE?READ_SIZE:content_length; n= socket_read(s, buf, size); if(n<0) break; md5_process_bytes(buf, n, &ctx); content_length -= n; } md5_finish_ctx(&ctx, hash); keylength=16; /* Raw key bytes not string chars! */ break; } case HASH_SHA1: { struct sha_ctx ctx; sha_init_ctx(&ctx); while(content_length > 0) { size= content_length>READ_SIZE?READ_SIZE:content_length; n= socket_read(s, buf, size); if(n<0) break; sha_process_bytes(buf, n, &ctx); content_length -= n; } sha_finish_ctx(&ctx, hash); keylength=20; /* Raw key bytes not string chars! */ break; } default: DEBUG("HTTP warning: Unknown hash type\n"); return FALSE; } if(strncasecmp(Util_digest2Bytes(hash, keylength, result), checksum, keylength*2) != 0) { DEBUG("HTTP warning: Document checksum mismatch\n"); return FALSE; } else { DEBUG("HTTP: Succeeded testing document checksum\n"); } return TRUE; } static int do_regex(Socket_T s, long content_length, Request_T R) { int n; int size= 0; int rv= TRUE; int length= 0; char *buf= NULL; #ifdef HAVE_REGEX_H int regex_return; #else char *regex_return; #endif if(R->regex == NULL) { return TRUE; } if(content_length == 0) { LogError("HTTP error: Cannot test regex -- No content returned " "from server\n"); return FALSE; } 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; n= 0; size= 0; length= content_length; buf= xmalloc(content_length + 1); do { n= socket_read(s, &buf[size], length); if(n<=0) break; size+= n; length-= n; } while(length>0); if(size==0) { rv= FALSE; LogError("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); switch(R->operator) { case OPERATOR_EQUAL: if(regex_return!=0) { rv= FALSE; } else { DEBUG("HTTP: Regular expression test succeeded\n"); } break; case OPERATOR_NOTEQUAL: if(regex_return == 0) { rv= FALSE; } else { DEBUG("HTTP: Regular expression test succeeded\n"); } break; default: LogError("HTTP error: Invalid content operator\n"); } #else /* w/o regex support */ regex_return= strstr(buf, R->regex); switch(R->operator) { case OPERATOR_EQUAL: if(!regex_return) { rv= FALSE; DEBUG("HTTP: Regular expression does not match\n"); } break; case OPERATOR_NOTEQUAL: if(regex_return) { rv= FALSE; DEBUG("HTTP: Regular expression match\n"); } break; default: LogError("HTTP error: Invalid content operator\n"); } #endif error: FREE(buf); return rv; } 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; }