diff options
author | jcid <devnull@localhost> | 2007-10-07 00:36:34 +0200 |
---|---|---|
committer | jcid <devnull@localhost> | 2007-10-07 00:36:34 +0200 |
commit | 93715c46a99c96d6c866968312691ec9ab0f6a03 (patch) | |
tree | 573f19ec6aa740844f53a7c0eb7114f04096bf64 /dpi/https.c |
Initial revision
Diffstat (limited to 'dpi/https.c')
-rw-r--r-- | dpi/https.c | 713 |
1 files changed, 713 insertions, 0 deletions
diff --git a/dpi/https.c b/dpi/https.c new file mode 100644 index 00000000..bbfc7ae4 --- /dev/null +++ b/dpi/https.c @@ -0,0 +1,713 @@ +/* + * Dpi for HTTPS. + * + * + * + * W A R N I N G + * + * One of the important things to have in mind is about whether + * unix domain sockets (UDS) are secure for https. I mean, root can always + * snoop on sockets (regardless of permissions) so he'd be able to "see" all + * the traffic. OTOH, if someone has root access on a machine he can do + * anything, and that includes modifying the binaries, peeking-up in + * memory space, installing a key-grabber, ... + * + * + * Copyright 2003, 2004 Jorge Arellano Cid <jcid@dillo.org> + * Copyright 2004 Garrett Kajmowicz <gkajmowi@tbaytel.net> + * + * 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. + * + * As a special exception permission is granted to link the code of + * the https dillo plugin with the OpenSSL project's "OpenSSL" + * library, and distribute the linked executables, without including + * the source code for OpenSSL in the source distribution. You must + * obey the GNU General Public License, version 2, in all respects + * for all of the code used other than "OpenSSL". + * + */ + +/* + * TODO: a lot of things, this is just a bare bones example. + * + * For instance: + * - Handle cookies (now that they arrive with the dpip tag, it needs + * testing). + * - Certificate authentication (asking the user in case it can't be verified) + * - Certificate management. + * - Session caching ... + * + */ + +#include <config.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <sys/un.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <sys/wait.h> +#include <errno.h> +#include <sys/time.h> +#include <sys/stat.h> + +#include "../dpip/dpip.h" +#include "dpiutil.h" + +/* + * Debugging macros + */ +#define _MSG(...) +#define MSG(...) printf("[https dpi]: " __VA_ARGS__) + + + +#define ENABLE_SSL +/* #undef ENABLE_SSL */ +#ifdef ENABLE_SSL + +#include <openssl/ssl.h> +#include <openssl/rand.h> + +static int get_network_connection(char * url); +static int handle_certificate_problem(SSL * ssl_connection); +static int save_certificate_home(X509 * cert); + +#endif + + + +/*---------------------------------------------------------------------------*/ +/* + * Global variables + */ +static char *root_url = NULL; /*Holds the URL we are connecting to*/ +static SockHandler *sh; + + +#ifdef ENABLE_SSL + +/* + * Read the answer dpip tag for a dialog and return the number for + * the user-selected alternative. + * Return: (-1: parse error, 0: window closed, 1-5 alt. number) + */ +static int dialog_get_answer_number(void) +{ + int response_number = -1; + char *dpip_tag, *response; + + /* Read the dpi command from STDIN */ + dpip_tag = sock_handler_read(sh); + response = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "msg"); + response_number = (response) ? strtol (response, NULL, 10) : -1; + dFree(dpip_tag); + dFree(response); + + return response_number; +} + + +/* + * This function does all of the work with SSL + */ +static void yes_ssl_support(void) +{ + /* The following variable will be set to 1 in the event of + * an error and skip any further processing + */ + int exit_error = 0; + SSL_CTX * ssl_context = NULL; + SSL * ssl_connection = NULL; + + char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *http_query = NULL; + char buf[4096]; + int retval = 0; + int network_socket = -1; + + + MSG("{In https.filter.dpi}\n"); + + /*Initialize library*/ + SSL_load_error_strings(); + SSL_library_init(); + if (RAND_status() != 1){ + /*Insufficient entropy. Deal with it?*/ + MSG("Insufficient random entropy\n"); + } + + /*Create context and SSL object*/ + if (exit_error == 0){ + ssl_context = SSL_CTX_new(SSLv23_client_method()); + if (ssl_context == NULL){ + MSG("Error creating SSL context\n"); + exit_error = 1; + } + } + + /*Set directory to load certificates from*/ + /*FIXME - provide for sysconfdir variables and such*/ + if (exit_error == 0){ + if (SSL_CTX_load_verify_locations( + ssl_context, NULL, "/etc/ssl/certs/" ) == 0){ + MSG("Error opening system x509 certificate location\n"); + exit_error = 1; + } + } + + if (exit_error == 0){ + snprintf(buf, 4095, "%s/.dillo/certs/", dGethomedir()); + if (SSL_CTX_load_verify_locations(ssl_context, NULL, buf )==0){ + MSG("Error opening user x509 certificate location\n"); + exit_error = 1; + } + } + + if (exit_error == 0){ + ssl_connection = SSL_new(ssl_context); + if (ssl_connection == NULL){ + MSG("Error creating SSL connection\n"); + exit_error = 1; + } + } + + if (exit_error == 0){ + /* Need to do the following if we want to deal with all + * possible ciphers + */ + SSL_set_cipher_list(ssl_connection, "ALL"); + + /* Need to do this if we want to have the option of dealing + * with self-signed certs + */ + SSL_set_verify(ssl_connection, SSL_VERIFY_NONE, 0); + + /*Get the network address and command to be used*/ + dpip_tag = sock_handler_read(sh); + cmd = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cmd"); + url = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "url"); + http_query = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "query"); + + if (cmd == NULL || url == NULL || http_query == NULL){ + MSG("***Value of cmd, url or http_query is NULL" + " - cannot continue\n"); + exit_error = 1; + } + } + + if (exit_error == 0){ + network_socket = get_network_connection(url); + if (network_socket<0){ + MSG("Network socket create error\n"); + exit_error = 1; + } + } + + + if (exit_error == 0){ + /* Configure SSL to use network file descriptor */ + if (SSL_set_fd(ssl_connection, network_socket) == 0){ + MSG("Error connecting network socket to SSL\n"); + exit_error = 1; + } + } + + if (exit_error == 0){ + /*Actually do SSL connection handshake*/ + if (SSL_connect(ssl_connection) != 1){ + MSG("SSL_connect failed\n"); + exit_error = 1; + } + } + + /*Use handle error function to decide what to do*/ + if (exit_error == 0){ + if (handle_certificate_problem(ssl_connection) < 0){ + MSG("Certificate verification error\n"); + exit_error = 1; + } + } + + if (exit_error == 0) { + char *d_cmd; + + /*Send query we want*/ + SSL_write(ssl_connection, http_query, (int)strlen(http_query)); + + /*Analyse response from server*/ + + /*Send dpi command*/ + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + /*Send remaining data*/ + + while ((retval = SSL_read(ssl_connection, buf, 4096)) > 0 ){ + sock_handler_write(sh, 0, buf, (size_t)retval); + } + } + + /*Begin cleanup of all resources used*/ + dFree(dpip_tag); + dFree(cmd); + dFree(url); + dFree(http_query); + + if (network_socket != -1){ + close(network_socket); + network_socket = -1; + } + if (ssl_connection != NULL){ + SSL_free(ssl_connection); + ssl_connection = NULL; + } + if (ssl_context != NULL){ + SSL_CTX_free(ssl_context); + ssl_context = NULL; + } +} + +/* + * The following function attempts to open up a connection to the + * remote server and return the file descriptor number of the + * socket. Returns -1 in the event of an error + */ +static int get_network_connection(char * url) +{ + struct sockaddr_in address; + struct hostent *hp; + + int s; + int url_offset = 0; + int portnum = 443; + unsigned int url_look_up_length = 0; + char * url_look_up = NULL; + + /*Determine how much of url we chop off as unneeded*/ + if (dStrncasecmp(url, "https://", 8) == 0){ + url_offset = 8; + } + + /*Find end of URL*/ + + if (strpbrk(url+url_offset, ":/") != NULL){ + url_look_up_length = strpbrk(url+url_offset, ":/") - (url+url_offset); + url_look_up = dStrndup(url+url_offset, url_look_up_length); + + /*Check for port number*/ + if (strchr(url+url_offset, ':') == + (url + url_offset + url_look_up_length)){ + portnum = atoi(url + url_offset + url_look_up_length + 1); + } + } else { + url_look_up = url + url_offset; + } + + root_url = dStrdup(url_look_up); + hp=gethostbyname(url_look_up); + + /*url_look_uip no longer needed, so free if neccessary*/ + if (url_look_up_length != 0){ + dFree(url_look_up); + } + + if (hp == NULL){ + MSG("gethostbyname() failed\n"); + return -1; + } + + memset(&address,0,sizeof(address)); + memcpy((char *)&address.sin_addr, hp->h_addr, (size_t)hp->h_length); + address.sin_family=hp->h_addrtype; + address.sin_port= htons((u_short)portnum); + + s = socket(hp->h_addrtype, SOCK_STREAM, 0); + if (connect(s, (struct sockaddr *)&address, sizeof(address)) != 0){ + close(s); + s = -1; + MSG("errno: %i\n", errno); + } + return s; +} + + +/* This function is run only when the certificate cannot + * be completely trusted. This will notify the user and + * allow the user to decide what to do. It may save the + * certificate to the user's .dillo directory if it is + * trusted. + * Return value: -1 on abort, 0 or higher on continue + */ +static int handle_certificate_problem(SSL * ssl_connection) +{ + int response_number; + int retval = -1; + long st; + char *cn, *cn_end; + char buf[4096], *d_cmd, *msg; + + X509 * remote_cert; + + remote_cert = SSL_get_peer_certificate(ssl_connection); + if (remote_cert == NULL){ + /*Inform user that remote system cannot be trusted*/ + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "The remote system is NOT presenting a certificate.\n" + "This site CAN NOT be trusted. Sending data is NOT SAFE.\n" + "What do I do?", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + /*Read the user's response*/ + response_number = dialog_get_answer_number(); + + /*Abort on anything but "Continue"*/ + if (response_number == 1){ + retval = 0; + } + + } else { + /*Figure out if (and why) the remote system can't be trusted*/ + st = SSL_get_verify_result(ssl_connection); + switch (st) { + case X509_V_OK: /*Everything is Kosher*/ + retval = 0; + break; + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + /*Either self signed and untrusted*/ + /*Extract CN from certificate name information*/ + cn = strstr(remote_cert->name, "/CN=") + 4; + if (cn == NULL) + break; + + if ((cn_end = strstr(cn, "/")) == NULL ) + cn_end = cn + strlen(cn); + + strncpy(buf, cn, (size_t) (cn_end - cn)); + + /*Add terminating NULL*/ + buf[cn_end - cn] = 0; + + msg = dStrconcat("The remote certificate is self-signed and " + "untrusted.\nFor address: ", buf, NULL); + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s alt3=%s", + "dialog", msg, "Continue", "Cancel", "Trust Certificate"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + dFree(msg); + + response_number = dialog_get_answer_number(); + switch (response_number){ + case 1: + retval = 0; + break; + case 2: + break; + case 3: + /*Save certificate to a file here and recheck the chain*/ + /*Potential security problems because we are writing + *to the filesystem*/ + save_certificate_home(remote_cert); + retval = 1; + break; + default: + break; + } + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "The issuer for the remote certificate cannot be found\n" + "The authenticity of the remote certificate cannot be trusted", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + case X509_V_ERR_CRL_SIGNATURE_FAILURE: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "The remote certificate signature could not be read\n" + "or is invalid and should not be trusted", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_CRL_NOT_YET_VALID: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "Part of the remote certificate is not yet valid\n" + "Certificates usually have a range of dates over which\n" + "they are to be considered valid, and the certificate\n" + "presented has a starting validity after today's date\n" + "You should be cautious about using this site", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_CRL_HAS_EXPIRED: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "The remote certificate has expired. The certificate\n" + "wasn't designed to last this long. You should avoid \n" + "this site.", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: + case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "There was an error in the certificate presented.\n" + "Some of the certificate data was improperly formatted\n" + "making it impossible to determine if the certificate\n" + "is valid. You should not trust this certificate.", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + case X509_V_ERR_INVALID_CA: + case X509_V_ERR_INVALID_PURPOSE: + case X509_V_ERR_CERT_UNTRUSTED: + case X509_V_ERR_CERT_REJECTED: + case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "One of the certificates in the chain is being used\n" + "incorrectly (possibly due to configuration problems\n" + "with the remote system. The connection should not\n" + "be trusted", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + response_number = dialog_get_answer_number(); + if (response_number == 1) { + retval = 0; + } + break; + case X509_V_ERR_SUBJECT_ISSUER_MISMATCH: + case X509_V_ERR_AKID_SKID_MISMATCH: + case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH: + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", + "Some of the information presented by the remote system\n" + "does not match other information presented\n" + "This may be an attempt to evesdrop on communications", + "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + default: /*Need to add more options later*/ + snprintf(buf, 80, + "The remote certificate cannot be verified (code %ld)", st); + d_cmd = a_Dpip_build_cmd( + "cmd=%s msg=%s alt1=%s alt2=%s", + "dialog", buf, "Continue", "Cancel"); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + response_number = dialog_get_answer_number(); + /*abort on anything but "Continue"*/ + if (response_number == 1){ + retval = 0; + } + } + X509_free(remote_cert); + remote_cert = 0; + } + + return retval; +} + +/* + * Save certificate with a hashed filename. + * Return: 0 on success, 1 on failure. + */ +static int save_certificate_home(X509 * cert) +{ + char buf[4096]; + + FILE * fp = NULL; + unsigned int i = 0; + int retval = 1; + + /*Attempt to create .dillo/certs blindly - check later*/ + snprintf(buf,4096,"%s/.dillo/", dGethomedir()); + mkdir(buf, 01777); + snprintf(buf,4096,"%s/.dillo/certs/", dGethomedir()); + mkdir(buf, 01777); + + do{ + snprintf(buf, 4096, "%s/.dillo/certs/%lx.%u", + dGethomedir(), X509_subject_name_hash(cert), i); + + fp=fopen(buf, "r"); + if (fp == NULL){ + /*File name doesn't exist so we can use it safely*/ + fp=fopen(buf, "w"); + if (fp == NULL){ + MSG("Unable to open cert save file in home dir\n"); + break; + } else { + PEM_write_X509(fp, cert); + fclose(fp); + MSG("Wrote certificate\n"); + retval = 0; + break; + } + } else { + fclose(fp); + } + i++; + /*Don't loop too many times - just give up*/ + } while( i < 1024 ); + + return retval; +} + + + +#else + + +/* + * Call this function to display an error message if SSL support + * isn't available for some reason + */ +static void no_ssl_support(void) +{ + char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *http_query = NULL; + char *d_cmd; + + /* Read the dpi command from STDIN */ + dpip_tag = sock_handler_read(sh); + + MSG("{In https.filter.dpi}\n"); + MSG("no_ssl_support version\n"); + + cmd = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "cmd"); + url = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "url"); + http_query = a_Dpip_get_attr(dpip_tag, strlen(dpip_tag), "query"); + + MSG("{ cmd: %s}\n", cmd); + MSG("{ url: %s}\n", url); + MSG("{ http_query:\n%s}\n", http_query); + + MSG("{ sending dpip cmd...}\n"); + + d_cmd = a_Dpip_build_cmd("cmd=%s url=%s", "start_send_page", url); + sock_handler_write_str(sh, 1, d_cmd); + dFree(d_cmd); + + MSG("{ dpip cmd sent.}\n"); + + MSG("{ sending HTML...}\n"); + + sock_handler_printf(sh, 1, + "Content-type: text/html\n\n" + "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n" + "<html><body><pre>\n" + "<b>Hi!\n\n" + " This is the https dpi that just got a request to send\n" + " the following HTTP query:\n{</b>\n" + "<code>%s</code>\n" + "<b>}</b>\n\n" + " <b>*** Dillo's prototype plugin for https support" + " is disabled now ***</b>\n\n" + " If you want to test this <b>alpha</b> support code, just remove\n" + " line 65 from https.c, recompile and reinstall.\n\n" + " (beware that this https support is very limited now)\n\n" + " To use https and SSL, you must have \n" + " the OpenSSL development libraries installed. Check your\n" + " O/S distribution provider, or check out\n" + " <a href=\"http://www.openssl.org\">www.openssl.org</a>\n\n" + " --\n" + "</pre></body></html>\n", + http_query + ); + MSG("{ HTML content sent.}\n"); + + dFree(cmd); + dFree(url); + dFree(http_query); + dFree(dpip_tag); + + MSG("{ exiting https.dpi}\n"); + +} + +#endif + + +/*---------------------------------------------------------------------------*/ +int main(void) +{ + /* Initialize the SockHandler for this filter dpi */ + sh = sock_handler_new(STDIN_FILENO, STDOUT_FILENO, 8*1024); + +#ifdef ENABLE_SSL + yes_ssl_support(); +#else + no_ssl_support(); +#endif + + /* Finish the SockHandler */ + sock_handler_close(sh); + sock_handler_free(sh); + + dFree(root_url); + + MSG("{ exiting https.dpi}\n"); + + return 0; +} + |