/*
This file is part of GNUnet.
Copyright (C) 2012-2014 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
GNUnet 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
Affero 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 .
*/
/**
* @author Martin Schanzenbach
* @author Christian Grothoff
* @file src/gns/gnunet-gns-proxy.c
* @brief HTTP(S) proxy that rewrites URIs and fakes certificats to make GNS work
* with legacy browsers
*
* TODO:
* - double-check queueing logic
*/
#include "platform.h"
#include
#if HAVE_CURL_CURL_H
#include
#elif HAVE_GNURL_CURL_H
#include
#endif
#include
#include
#include
#include
#if HAVE_GNUTLS_DANE
#include
#endif
#include
#include "gnunet_util_lib.h"
#include "gnunet_gns_service.h"
#include "gnunet_identity_service.h"
#include "gns.h"
/**
* FIXME: GnuTLS right now sometimes rejects valid certs, so as a
* VERY temporary workaround we just WARN the user instead of
* dropping the page. THIS SHOULD NOT BE USED IN PRODUCTION,
* set to 1 in production!!! FIXME!!!
*/
#define FIXED_CERT_VALIDATION_BUG 0
/**
* Default Socks5 listen port.
*/
#define GNUNET_GNS_PROXY_PORT 7777
/**
* Maximum supported length for a URI.
* Should die. @deprecated
*/
#define MAX_HTTP_URI_LENGTH 2048
/**
* Size of the buffer for the data upload / download. Must be
* enough for curl, thus CURL_MAX_WRITE_SIZE is needed here (16k).
*/
#define IO_BUFFERSIZE CURL_MAX_WRITE_SIZE
/**
* Size of the read/write buffers for Socks. Uses
* 256 bytes for the hostname (at most), plus a few
* bytes overhead for the messages.
*/
#define SOCKS_BUFFERSIZE (256 + 32)
/**
* Port for plaintext HTTP.
*/
#define HTTP_PORT 80
/**
* Port for HTTPS.
*/
#define HTTPS_PORT 443
/**
* Largest allowed size for a PEM certificate.
*/
#define MAX_PEM_SIZE (10 * 1024)
/**
* After how long do we clean up unused MHD TLS instances?
*/
#define MHD_CACHE_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 5)
/**
* After how long do we clean up Socks5 handles that failed to show any activity
* with their respective MHD instance?
*/
#define HTTP_HANDSHAKE_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15)
/**
* Log curl error.
*
* @param level log level
* @param fun name of curl_easy-function that gave the error
* @param rc return code from curl
*/
#define LOG_CURL_EASY(level,fun,rc) \
GNUNET_log (level, \
_("%s failed at %s:%d: `%s'\n"), \
fun, \
__FILE__, \
__LINE__, \
curl_easy_strerror (rc))
/* *************** Socks protocol definitions (move to TUN?) ****************** */
/**
* Which SOCKS version do we speak?
*/
#define SOCKS_VERSION_5 0x05
/**
* Flag to set for 'no authentication'.
*/
#define SOCKS_AUTH_NONE 0
/**
* Commands in Socks5.
*/
enum Socks5Commands
{
/**
* Establish TCP/IP stream.
*/
SOCKS5_CMD_TCP_STREAM = 1,
/**
* Establish TCP port binding.
*/
SOCKS5_CMD_TCP_PORT = 2,
/**
* Establish UDP port binding.
*/
SOCKS5_CMD_UDP_PORT = 3
};
/**
* Address types in Socks5.
*/
enum Socks5AddressType
{
/**
* IPv4 address.
*/
SOCKS5_AT_IPV4 = 1,
/**
* IPv4 address.
*/
SOCKS5_AT_DOMAINNAME = 3,
/**
* IPv6 address.
*/
SOCKS5_AT_IPV6 = 4
};
/**
* Status codes in Socks5 response.
*/
enum Socks5StatusCode
{
SOCKS5_STATUS_REQUEST_GRANTED = 0,
SOCKS5_STATUS_GENERAL_FAILURE = 1,
SOCKS5_STATUS_CONNECTION_NOT_ALLOWED_BY_RULE = 2,
SOCKS5_STATUS_NETWORK_UNREACHABLE = 3,
SOCKS5_STATUS_HOST_UNREACHABLE = 4,
SOCKS5_STATUS_CONNECTION_REFUSED_BY_HOST = 5,
SOCKS5_STATUS_TTL_EXPIRED = 6,
SOCKS5_STATUS_COMMAND_NOT_SUPPORTED = 7,
SOCKS5_STATUS_ADDRESS_TYPE_NOT_SUPPORTED = 8
};
/**
* Client hello in Socks5 protocol.
*/
struct Socks5ClientHelloMessage
{
/**
* Should be #SOCKS_VERSION_5.
*/
uint8_t version;
/**
* How many authentication methods does the client support.
*/
uint8_t num_auth_methods;
/* followed by supported authentication methods, 1 byte per method */
};
/**
* Server hello in Socks5 protocol.
*/
struct Socks5ServerHelloMessage
{
/**
* Should be #SOCKS_VERSION_5.
*/
uint8_t version;
/**
* Chosen authentication method, for us always #SOCKS_AUTH_NONE,
* which skips the authentication step.
*/
uint8_t auth_method;
};
/**
* Client socks request in Socks5 protocol.
*/
struct Socks5ClientRequestMessage
{
/**
* Should be #SOCKS_VERSION_5.
*/
uint8_t version;
/**
* Command code, we only uspport #SOCKS5_CMD_TCP_STREAM.
*/
uint8_t command;
/**
* Reserved, always zero.
*/
uint8_t resvd;
/**
* Address type, an `enum Socks5AddressType`.
*/
uint8_t addr_type;
/*
* Followed by either an ip4/ipv6 address or a domain name with a
* length field (uint8_t) in front (depending on @e addr_type).
* followed by port number in network byte order (uint16_t).
*/
};
/**
* Server response to client requests in Socks5 protocol.
*/
struct Socks5ServerResponseMessage
{
/**
* Should be #SOCKS_VERSION_5.
*/
uint8_t version;
/**
* Status code, an `enum Socks5StatusCode`
*/
uint8_t reply;
/**
* Always zero.
*/
uint8_t reserved;
/**
* Address type, an `enum Socks5AddressType`.
*/
uint8_t addr_type;
/*
* Followed by either an ip4/ipv6 address or a domain name with a
* length field (uint8_t) in front (depending on @e addr_type).
* followed by port number in network byte order (uint16_t).
*/
};
/* *********************** Datastructures for HTTP handling ****************** */
/**
* A structure for CA cert/key
*/
struct ProxyCA
{
/**
* The certificate
*/
gnutls_x509_crt_t cert;
/**
* The private key
*/
gnutls_x509_privkey_t key;
};
/**
* Structure for GNS certificates
*/
struct ProxyGNSCertificate
{
/**
* The certificate as PEM
*/
char cert[MAX_PEM_SIZE];
/**
* The private key as PEM
*/
char key[MAX_PEM_SIZE];
};
/**
* A structure for all running Httpds
*/
struct MhdHttpList
{
/**
* DLL for httpds
*/
struct MhdHttpList *prev;
/**
* DLL for httpds
*/
struct MhdHttpList *next;
/**
* the domain name to server (only important for TLS)
*/
char *domain;
/**
* The daemon handle
*/
struct MHD_Daemon *daemon;
/**
* Optional proxy certificate used
*/
struct ProxyGNSCertificate *proxy_cert;
/**
* The task ID
*/
struct GNUNET_SCHEDULER_Task *httpd_task;
/**
* is this an ssl daemon?
*/
int is_ssl;
};
/* ***************** Datastructures for Socks handling **************** */
/**
* The socks phases.
*/
enum SocksPhase
{
/**
* We're waiting to get the client hello.
*/
SOCKS5_INIT,
/**
* We're waiting to get the initial request.
*/
SOCKS5_REQUEST,
/**
* We are currently resolving the destination.
*/
SOCKS5_RESOLVING,
/**
* We're in transfer mode.
*/
SOCKS5_DATA_TRANSFER,
/**
* Finish writing the write buffer, then clean up.
*/
SOCKS5_WRITE_THEN_CLEANUP,
/**
* Socket has been passed to MHD, do not close it anymore.
*/
SOCKS5_SOCKET_WITH_MHD,
/**
* We've started receiving upload data from MHD.
*/
SOCKS5_SOCKET_UPLOAD_STARTED,
/**
* We've finished receiving upload data from MHD.
*/
SOCKS5_SOCKET_UPLOAD_DONE,
/**
* We've finished uploading data via CURL and can now download.
*/
SOCKS5_SOCKET_DOWNLOAD_STARTED,
/**
* We've finished receiving download data from cURL.
*/
SOCKS5_SOCKET_DOWNLOAD_DONE
};
/**
* A header list
*/
struct HttpResponseHeader
{
/**
* DLL
*/
struct HttpResponseHeader *next;
/**
* DLL
*/
struct HttpResponseHeader *prev;
/**
* Header type
*/
char *type;
/**
* Header value
*/
char *value;
};
/**
* A structure for socks requests
*/
struct Socks5Request
{
/**
* DLL.
*/
struct Socks5Request *next;
/**
* DLL.
*/
struct Socks5Request *prev;
/**
* The client socket
*/
struct GNUNET_NETWORK_Handle *sock;
/**
* Handle to GNS lookup, during #SOCKS5_RESOLVING phase.
*/
struct GNUNET_GNS_LookupWithTldRequest *gns_lookup;
/**
* Client socket read task
*/
struct GNUNET_SCHEDULER_Task *rtask;
/**
* Client socket write task
*/
struct GNUNET_SCHEDULER_Task *wtask;
/**
* Timeout task
*/
struct GNUNET_SCHEDULER_Task *timeout_task;
/**
* Read buffer
*/
char rbuf[SOCKS_BUFFERSIZE];
/**
* Write buffer
*/
char wbuf[SOCKS_BUFFERSIZE];
/**
* Buffer we use for moving data between MHD and curl (in both directions).
*/
char io_buf[IO_BUFFERSIZE];
/**
* MHD HTTP instance handling this request, NULL for none.
*/
struct MhdHttpList *hd;
/**
* MHD connection for this request.
*/
struct MHD_Connection *con;
/**
* MHD response object for this request.
*/
struct MHD_Response *response;
/**
* the domain name to server (only important for TLS)
*/
char *domain;
/**
* DNS Legacy Host Name as given by GNS, NULL if not given.
*/
char *leho;
/**
* Payload of the (last) DANE record encountered.
*/
char *dane_data;
/**
* The URL to fetch
*/
char *url;
/**
* Handle to cURL
*/
CURL *curl;
/**
* HTTP request headers for the curl request.
*/
struct curl_slist *headers;
/**
* DNS->IP mappings resolved through GNS
*/
struct curl_slist *hosts;
/**
* HTTP response code to give to MHD for the response.
*/
unsigned int response_code;
/**
* Number of bytes in @e dane_data.
*/
size_t dane_data_len;
/**
* Number of bytes already in read buffer
*/
size_t rbuf_len;
/**
* Number of bytes already in write buffer
*/
size_t wbuf_len;
/**
* Number of bytes already in the IO buffer.
*/
size_t io_len;
/**
* Once known, what's the target address for the connection?
*/
struct sockaddr_storage destination_address;
/**
* The socks state
*/
enum SocksPhase state;
/**
* Desired destination port.
*/
uint16_t port;
/**
* Headers from response
*/
struct HttpResponseHeader *header_head;
/**
* Headers from response
*/
struct HttpResponseHeader *header_tail;
/**
* X.509 Certificate status
*/
int ssl_checked;
/**
* Was the hostname resolved via GNS?
*/
int is_gns;
/**
* Did we suspend MHD processing?
*/
int suspended;
/**
* Did we pause CURL processing?
*/
int curl_paused;
};
/* *********************** Globals **************************** */
/**
* The port the proxy is running on (default 7777)
*/
static uint16_t port = GNUNET_GNS_PROXY_PORT;
/**
* The CA file (pem) to use for the proxy CA
*/
static char *cafile_opt;
/**
* The listen socket of the proxy for IPv4
*/
static struct GNUNET_NETWORK_Handle *lsock4;
/**
* The listen socket of the proxy for IPv6
*/
static struct GNUNET_NETWORK_Handle *lsock6;
/**
* The listen task ID for IPv4
*/
static struct GNUNET_SCHEDULER_Task * ltask4;
/**
* The listen task ID for IPv6
*/
static struct GNUNET_SCHEDULER_Task * ltask6;
/**
* The cURL download task (curl multi API).
*/
static struct GNUNET_SCHEDULER_Task * curl_download_task;
/**
* The cURL multi handle
*/
static CURLM *curl_multi;
/**
* Handle to the GNS service
*/
static struct GNUNET_GNS_Handle *gns_handle;
/**
* Disable IPv6.
*/
static int disable_v6;
/**
* DLL for http/https daemons
*/
static struct MhdHttpList *mhd_httpd_head;
/**
* DLL for http/https daemons
*/
static struct MhdHttpList *mhd_httpd_tail;
/**
* Daemon for HTTP (we have one per X.509 certificate, and then one for
* all HTTP connections; this is the one for HTTP, not HTTPS).
*/
static struct MhdHttpList *httpd;
/**
* DLL of active socks requests.
*/
static struct Socks5Request *s5r_head;
/**
* DLL of active socks requests.
*/
static struct Socks5Request *s5r_tail;
/**
* The CA for X.509 certificate generation
*/
static struct ProxyCA proxy_ca;
/**
* Response we return on cURL failures.
*/
static struct MHD_Response *curl_failure_response;
/**
* Our configuration.
*/
static const struct GNUNET_CONFIGURATION_Handle *cfg;
/* ************************* Global helpers ********************* */
/**
* Run MHD now, we have extra data ready for the callback.
*
* @param hd the daemon to run now.
*/
static void
run_mhd_now (struct MhdHttpList *hd);
/**
* Clean up s5r handles.
*
* @param s5r the handle to destroy
*/
static void
cleanup_s5r (struct Socks5Request *s5r)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Cleaning up socks request\n");
if (NULL != s5r->curl)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Cleaning up cURL handle\n");
curl_multi_remove_handle (curl_multi,
s5r->curl);
curl_easy_cleanup (s5r->curl);
s5r->curl = NULL;
}
if (s5r->suspended)
{
s5r->suspended = GNUNET_NO;
MHD_resume_connection (s5r->con);
}
curl_slist_free_all (s5r->headers);
if (NULL != s5r->hosts)
{
curl_slist_free_all (s5r->hosts);
}
if ( (NULL != s5r->response) &&
(curl_failure_response != s5r->response) )
{
MHD_destroy_response (s5r->response);
s5r->response = NULL;
}
if (NULL != s5r->rtask)
{
GNUNET_SCHEDULER_cancel (s5r->rtask);
s5r->rtask = NULL;
}
if (NULL != s5r->timeout_task)
{
GNUNET_SCHEDULER_cancel (s5r->timeout_task);
s5r->timeout_task = NULL;
}
if (NULL != s5r->wtask)
{
GNUNET_SCHEDULER_cancel (s5r->wtask);
s5r->wtask = NULL;
}
if (NULL != s5r->gns_lookup)
{
GNUNET_GNS_lookup_with_tld_cancel (s5r->gns_lookup);
s5r->gns_lookup = NULL;
}
if (NULL != s5r->sock)
{
if (SOCKS5_SOCKET_WITH_MHD <= s5r->state)
GNUNET_NETWORK_socket_free_memory_only_ (s5r->sock);
else
GNUNET_NETWORK_socket_close (s5r->sock);
s5r->sock = NULL;
}
GNUNET_CONTAINER_DLL_remove (s5r_head,
s5r_tail,
s5r);
GNUNET_free_non_null (s5r->domain);
GNUNET_free_non_null (s5r->leho);
GNUNET_free_non_null (s5r->url);
GNUNET_free_non_null (s5r->dane_data);
GNUNET_free (s5r);
}
/* ************************* HTTP handling with cURL *********************** */
static void
curl_download_prepare ();
/**
* Callback for MHD response generation. This function is called from
* MHD whenever MHD expects to get data back. Copies data from the
* io_buf, if available.
*
* @param cls closure with our `struct Socks5Request`
* @param pos in buffer
* @param buf where to copy data
* @param max available space in @a buf
* @return number of bytes written to @a buf
*/
static ssize_t
mhd_content_cb (void *cls,
uint64_t pos,
char* buf,
size_t max)
{
struct Socks5Request *s5r = cls;
size_t bytes_to_copy;
if ( (SOCKS5_SOCKET_UPLOAD_STARTED == s5r->state) ||
(SOCKS5_SOCKET_UPLOAD_DONE == s5r->state) )
{
/* we're still not done with the upload, do not yet
start the download, the IO buffer is still full
with upload data. */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Pausing MHD download %s%s, not yet ready for download\n",
s5r->domain,
s5r->url);
return 0; /* not yet ready for data download */
}
bytes_to_copy = GNUNET_MIN (max,
s5r->io_len);
if ( (0 == bytes_to_copy) &&
(SOCKS5_SOCKET_DOWNLOAD_DONE != s5r->state) )
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Pausing MHD download %s%s, no data available\n",
s5r->domain,
s5r->url);
if (NULL != s5r->curl)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Continuing CURL interaction for %s%s\n",
s5r->domain,
s5r->url);
if (GNUNET_YES == s5r->curl_paused)
{
s5r->curl_paused = GNUNET_NO;
curl_easy_pause (s5r->curl,
CURLPAUSE_CONT);
}
curl_download_prepare ();
}
if (GNUNET_NO == s5r->suspended)
{
MHD_suspend_connection (s5r->con);
s5r->suspended = GNUNET_YES;
}
return 0; /* more data later */
}
if ( (0 == bytes_to_copy) &&
(SOCKS5_SOCKET_DOWNLOAD_DONE == s5r->state) )
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Completed MHD download %s%s\n",
s5r->domain,
s5r->url);
return MHD_CONTENT_READER_END_OF_STREAM;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Writing %llu/%llu bytes to %s%s\n",
(unsigned long long) bytes_to_copy,
(unsigned long long) s5r->io_len,
s5r->domain,
s5r->url);
GNUNET_memcpy (buf,
s5r->io_buf,
bytes_to_copy);
memmove (s5r->io_buf,
&s5r->io_buf[bytes_to_copy],
s5r->io_len - bytes_to_copy);
s5r->io_len -= bytes_to_copy;
if ( (NULL != s5r->curl) &&
(GNUNET_YES == s5r->curl_paused) )
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Continuing CURL interaction for %s%s\n",
s5r->domain,
s5r->url);
s5r->curl_paused = GNUNET_NO;
curl_easy_pause (s5r->curl,
CURLPAUSE_CONT);
}
return bytes_to_copy;
}
/**
* Check that the website has presented us with a valid X.509 certificate.
* The certificate must either match the domain name or the LEHO name
* (or, if available, the TLSA record).
*
* @param s5r request to check for.
* @return #GNUNET_OK if the certificate is valid
*/
static int
check_ssl_certificate (struct Socks5Request *s5r)
{
unsigned int cert_list_size;
const gnutls_datum_t *chainp;
const struct curl_tlssessioninfo *tlsinfo;
char certdn[GNUNET_DNSPARSER_MAX_NAME_LENGTH + 3];
size_t size;
gnutls_x509_crt_t x509_cert;
int rc;
const char *name;
s5r->ssl_checked = GNUNET_YES;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Checking X.509 certificate\n");
if (CURLE_OK !=
curl_easy_getinfo (s5r->curl,
CURLINFO_TLS_SESSION,
(struct curl_slist **) &tlsinfo))
return GNUNET_SYSERR;
if (CURLSSLBACKEND_GNUTLS != tlsinfo->backend)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
_("Unsupported CURL TLS backend %d\n"),
tlsinfo->backend);
return GNUNET_SYSERR;
}
chainp = gnutls_certificate_get_peers (tlsinfo->internals,
&cert_list_size);
if ( (! chainp) ||
(0 == cert_list_size) )
return GNUNET_SYSERR;
size = sizeof (certdn);
/* initialize an X.509 certificate structure. */
gnutls_x509_crt_init (&x509_cert);
gnutls_x509_crt_import (x509_cert,
chainp,
GNUTLS_X509_FMT_DER);
if (0 != (rc = gnutls_x509_crt_get_dn_by_oid (x509_cert,
GNUTLS_OID_X520_COMMON_NAME,
0, /* the first and only one */
0 /* no DER encoding */,
certdn,
&size)))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_("Failed to fetch CN from cert: %s\n"),
gnutls_strerror(rc));
gnutls_x509_crt_deinit (x509_cert);
return GNUNET_SYSERR;
}
/* check for TLSA/DANE records */
#if HAVE_GNUTLS_DANE
if (NULL != s5r->dane_data)
{
char *dd[] = { s5r->dane_data, NULL };
int dlen[] = { s5r->dane_data_len, 0};
dane_state_t dane_state;
dane_query_t dane_query;
unsigned int verify;
/* FIXME: add flags to gnutls to NOT read UNBOUND_ROOT_KEY_FILE here! */
if (0 != (rc = dane_state_init (&dane_state,
#ifdef DANE_F_IGNORE_DNSSEC
DANE_F_IGNORE_DNSSEC |
#endif
DANE_F_IGNORE_LOCAL_RESOLVER)))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_("Failed to initialize DANE: %s\n"),
dane_strerror(rc));
gnutls_x509_crt_deinit (x509_cert);
return GNUNET_SYSERR;
}
if (0 != (rc = dane_raw_tlsa (dane_state,
&dane_query,
dd,
dlen,
GNUNET_YES,
GNUNET_NO)))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_("Failed to parse DANE record: %s\n"),
dane_strerror(rc));
dane_state_deinit (dane_state);
gnutls_x509_crt_deinit (x509_cert);
return GNUNET_SYSERR;
}
if (0 != (rc = dane_verify_crt_raw (dane_state,
chainp,
cert_list_size,
gnutls_certificate_type_get (tlsinfo->internals),
dane_query,
0, 0,
&verify)))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_("Failed to verify TLS connection using DANE: %s\n"),
dane_strerror(rc));
dane_query_deinit (dane_query);
dane_state_deinit (dane_state);
gnutls_x509_crt_deinit (x509_cert);
return GNUNET_SYSERR;
}
if (0 != verify)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_("Failed DANE verification failed with GnuTLS verify status code: %u\n"),
verify);
dane_query_deinit (dane_query);
dane_state_deinit (dane_state);
gnutls_x509_crt_deinit (x509_cert);
return GNUNET_SYSERR;
}
dane_query_deinit (dane_query);
dane_state_deinit (dane_state);
/* success! */
}
else
#endif
{
/* try LEHO or ordinary domain name X509 verification */
name = s5r->domain;
if (NULL != s5r->leho)
name = s5r->leho;
if (NULL != name)
{
if (0 == (rc = gnutls_x509_crt_check_hostname (x509_cert,
name)))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_("TLS certificate subject name (%s) does not match `%s': %d\n"),
certdn,
name,
rc);
#if FIXED_CERT_VALIDATION_BUG
gnutls_x509_crt_deinit (x509_cert);
return GNUNET_SYSERR;
#endif
}
}
else
{
/* we did not even have the domain name!? */
GNUNET_break (0);
return GNUNET_SYSERR;
}
}
gnutls_x509_crt_deinit (x509_cert);
return GNUNET_OK;
}
/**
* We're getting an HTTP response header from cURL. Convert it to the
* MHD response headers. Mostly copies the headers, but makes special
* adjustments to "Set-Cookie" and "Location" headers as those may need
* to be changed from the LEHO to the domain the browser expects.
*
* @param buffer curl buffer with a single line of header data; not 0-terminated!
* @param size curl blocksize
* @param nmemb curl blocknumber
* @param cls our `struct Socks5Request *`
* @return size of processed bytes
*/
static size_t
curl_check_hdr (void *buffer,
size_t size,
size_t nmemb,
void *cls)
{
struct Socks5Request *s5r = cls;
struct HttpResponseHeader *header;
size_t bytes = size * nmemb;
char *ndup;
const char *hdr_type;
const char *cookie_domain;
char *hdr_val;
char *new_cookie_hdr;
char *new_location;
size_t offset;
size_t delta_cdomain;
int domain_matched;
char *tok;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Receiving HTTP response header from CURL\n");
/* first, check TLS certificate */
if ( (GNUNET_YES != s5r->ssl_checked) &&
(HTTPS_PORT == s5r->port))
{
if (GNUNET_OK != check_ssl_certificate (s5r))
return 0;
}
ndup = GNUNET_strndup (buffer,
bytes);
hdr_type = strtok (ndup,
":");
if (NULL == hdr_type)
{
GNUNET_free (ndup);
return bytes;
}
hdr_val = strtok (NULL,
"");
if (NULL == hdr_val)
{
GNUNET_free (ndup);
return bytes;
}
if (' ' == *hdr_val)
hdr_val++;
/* custom logic for certain header types */
new_cookie_hdr = NULL;
if ( (NULL != s5r->leho) &&
(0 == strcasecmp (hdr_type,
MHD_HTTP_HEADER_SET_COOKIE)) )
{
new_cookie_hdr = GNUNET_malloc (strlen (hdr_val) +
strlen (s5r->domain) + 1);
offset = 0;
domain_matched = GNUNET_NO; /* make sure we match domain at most once */
for (tok = strtok (hdr_val, ";"); NULL != tok; tok = strtok (NULL, ";"))
{
if ( (0 == strncasecmp (tok,
" domain",
strlen (" domain"))) &&
(GNUNET_NO == domain_matched) )
{
domain_matched = GNUNET_YES;
cookie_domain = tok + strlen (" domain") + 1;
if (strlen (cookie_domain) < strlen (s5r->leho))
{
delta_cdomain = strlen (s5r->leho) - strlen (cookie_domain);
if (0 == strcasecmp (cookie_domain,
s5r->leho + delta_cdomain))
{
offset += sprintf (new_cookie_hdr + offset,
" domain=%s;",
s5r->domain);
continue;
}
}
else if (0 == strcmp (cookie_domain,
s5r->leho))
{
offset += sprintf (new_cookie_hdr + offset,
" domain=%s;",
s5r->domain);
continue;
}
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_("Cookie domain `%s' supplied by server is invalid\n"),
tok);
}
GNUNET_memcpy (new_cookie_hdr + offset,
tok,
strlen (tok));
offset += strlen (tok);
new_cookie_hdr[offset++] = ';';
}
hdr_val = new_cookie_hdr;
}
new_location = NULL;
if (0 == strcasecmp (MHD_HTTP_HEADER_TRANSFER_ENCODING,
hdr_type))
{
/* Ignore transfer encoding, set automatically by MHD if required */
goto cleanup;
}
if (0 == strcasecmp (MHD_HTTP_HEADER_LOCATION,
hdr_type))
{
char *leho_host;
GNUNET_asprintf (&leho_host,
(HTTPS_PORT != s5r->port)
? "http://%s"
: "https://%s",
s5r->leho);
if (0 == strncmp (leho_host,
hdr_val,
strlen (leho_host)))
{
GNUNET_asprintf (&new_location,
"%s%s%s",
(HTTPS_PORT != s5r->port)
? "http://"
: "https://",
s5r->domain,
hdr_val + strlen (leho_host));
hdr_val = new_location;
}
GNUNET_free (leho_host);
}
/* MHD does not allow certain characters in values, remove those */
if (NULL != (tok = strchr (hdr_val, '\n')))
*tok = '\0';
if (NULL != (tok = strchr (hdr_val, '\r')))
*tok = '\0';
if (NULL != (tok = strchr (hdr_val, '\t')))
*tok = '\0';
if (0 != strlen (hdr_val)) /* Rely in MHD to set those */
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Adding header %s: %s to MHD response\n",
hdr_type,
hdr_val);
header = GNUNET_new (struct HttpResponseHeader);
header->type = GNUNET_strdup (hdr_type);
header->value = GNUNET_strdup (hdr_val);
GNUNET_CONTAINER_DLL_insert (s5r->header_head,
s5r->header_tail,
header);
}
cleanup:
GNUNET_free (ndup);
GNUNET_free_non_null (new_cookie_hdr);
GNUNET_free_non_null (new_location);
return bytes;
}
/**
* Create an MHD response object in @a s5r matching the
* information we got from curl.
*
* @param s5r the request for which we convert the response
* @return #GNUNET_OK on success, #GNUNET_SYSERR if response was
* already initialized before
*/
static int
create_mhd_response_from_s5r (struct Socks5Request *s5r)
{
long resp_code;
double content_length;
if (NULL != s5r->response)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Response already set!\n");
return GNUNET_SYSERR;
}
GNUNET_break (CURLE_OK ==
curl_easy_getinfo (s5r->curl,
CURLINFO_RESPONSE_CODE,
&resp_code));
GNUNET_break (CURLE_OK ==
curl_easy_getinfo (s5r->curl,
CURLINFO_CONTENT_LENGTH_DOWNLOAD,
&content_length));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Creating MHD response with code %d and size %d for %s%s\n",
(int) resp_code,
(int) content_length,
s5r->domain,
s5r->url);
s5r->response_code = resp_code;
s5r->response = MHD_create_response_from_callback ((-1 == content_length)
? MHD_SIZE_UNKNOWN
: content_length,
IO_BUFFERSIZE,
&mhd_content_cb,
s5r,
NULL);
for (struct HttpResponseHeader *header = s5r->header_head;
NULL != header;
header = header->next)
{
GNUNET_break (MHD_YES ==
MHD_add_response_header (s5r->response,
header->type,
header->value));
}
if (NULL != s5r->leho)
{
char *cors_hdr;
GNUNET_asprintf (&cors_hdr,
(HTTPS_PORT == s5r->port)
? "https://%s"
: "http://%s",
s5r->leho);
GNUNET_break (MHD_YES ==
MHD_add_response_header (s5r->response,
MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
cors_hdr));
GNUNET_free (cors_hdr);
}
/* force connection to be closed after each request, as we
do not support HTTP pipelining (yet, FIXME!) */
/*GNUNET_break (MHD_YES ==
MHD_add_response_header (s5r->response,
MHD_HTTP_HEADER_CONNECTION,
"close"));*/
MHD_resume_connection (s5r->con);
s5r->suspended = GNUNET_NO;
return GNUNET_OK;
}
/**
* Handle response payload data from cURL. Copies it into our `io_buf` to make
* it available to MHD.
*
* @param ptr pointer to the data
* @param size number of blocks of data
* @param nmemb blocksize
* @param ctx our `struct Socks5Request *`
* @return number of bytes handled
*/
static size_t
curl_download_cb (void *ptr,
size_t size,
size_t nmemb,
void* ctx)
{
struct Socks5Request *s5r = ctx;
size_t total = size * nmemb;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Receiving %ux%u bytes for `%s%s' from cURL\n",
(unsigned int) size,
(unsigned int) nmemb,
s5r->domain,
s5r->url);
if (NULL == s5r->response)
GNUNET_assert (GNUNET_OK ==
create_mhd_response_from_s5r (s5r));
if ( (SOCKS5_SOCKET_UPLOAD_STARTED == s5r->state) ||
(SOCKS5_SOCKET_UPLOAD_DONE == s5r->state) )
{
/* we're still not done with the upload, do not yet
start the download, the IO buffer is still full
with upload data. */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Pausing CURL download `%s%s', waiting for UPLOAD to finish\n",
s5r->domain,
s5r->url);
s5r->curl_paused = GNUNET_YES;
return CURL_WRITEFUNC_PAUSE; /* not yet ready for data download */
}
if (sizeof (s5r->io_buf) - s5r->io_len < total)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Pausing CURL `%s%s' download, not enough space %llu %llu %llu\n",
s5r->domain,
s5r->url,
(unsigned long long) sizeof (s5r->io_buf),
(unsigned long long) s5r->io_len,
(unsigned long long) total);
s5r->curl_paused = GNUNET_YES;
return CURL_WRITEFUNC_PAUSE; /* not enough space */
}
GNUNET_memcpy (&s5r->io_buf[s5r->io_len],
ptr,
total);
s5r->io_len += total;
if (GNUNET_YES == s5r->suspended)
{
MHD_resume_connection (s5r->con);
s5r->suspended = GNUNET_NO;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Received %llu bytes of payload via cURL from %s\n",
(unsigned long long) total,
s5r->domain);
if (s5r->io_len == total)
run_mhd_now (s5r->hd);
return total;
}
/**
* cURL callback for uploaded (PUT/POST) data. Copies it into our `io_buf`
* to make it available to MHD.
*
* @param buf where to write the data
* @param size number of bytes per member
* @param nmemb number of members available in @a buf
* @param cls our `struct Socks5Request` that generated the data
* @return number of bytes copied to @a buf
*/
static size_t
curl_upload_cb (void *buf,
size_t size,
size_t nmemb,
void *cls)
{
struct Socks5Request *s5r = cls;
size_t len = size * nmemb;
size_t to_copy;
if ( (0 == s5r->io_len) &&
(SOCKS5_SOCKET_UPLOAD_DONE != s5r->state) )
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Pausing CURL UPLOAD %s%s, need more data\n",
s5r->domain,
s5r->url);
return CURL_READFUNC_PAUSE;
}
if ( (0 == s5r->io_len) &&
(SOCKS5_SOCKET_UPLOAD_DONE == s5r->state) )
{
s5r->state = SOCKS5_SOCKET_DOWNLOAD_STARTED;
if (GNUNET_YES == s5r->curl_paused)
{
s5r->curl_paused = GNUNET_NO;
curl_easy_pause (s5r->curl,
CURLPAUSE_CONT);
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Completed CURL UPLOAD %s%s\n",
s5r->domain,
s5r->url);
return 0; /* upload finished, can now download */
}
if ( (SOCKS5_SOCKET_UPLOAD_STARTED != s5r->state) &&
(SOCKS5_SOCKET_UPLOAD_DONE != s5r->state) )
{
GNUNET_break (0);
return CURL_READFUNC_ABORT;
}
to_copy = GNUNET_MIN (s5r->io_len,
len);
GNUNET_memcpy (buf,
s5r->io_buf,
to_copy);
memmove (s5r->io_buf,
&s5r->io_buf[to_copy],
s5r->io_len - to_copy);
s5r->io_len -= to_copy;
if (s5r->io_len + to_copy == sizeof (s5r->io_buf))
run_mhd_now (s5r->hd); /* got more space for upload now */
return to_copy;
}
/* ************************** main loop of cURL interaction ****************** */
/**
* Task that is run when we are ready to receive more data
* from curl
*
* @param cls closure
*/
static void
curl_task_download (void *cls);
/**
* Ask cURL for the select() sets and schedule cURL operations.
*/
static void
curl_download_prepare ()
{
CURLMcode mret;
fd_set rs;
fd_set ws;
fd_set es;
int max;
struct GNUNET_NETWORK_FDSet *grs;
struct GNUNET_NETWORK_FDSet *gws;
long to;
struct GNUNET_TIME_Relative rtime;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Scheduling CURL interaction\n");
if (NULL != curl_download_task)
{
GNUNET_SCHEDULER_cancel (curl_download_task);
curl_download_task = NULL;
}
max = -1;
FD_ZERO (&rs);
FD_ZERO (&ws);
FD_ZERO (&es);
if (CURLM_OK != (mret = curl_multi_fdset (curl_multi,
&rs,
&ws,
&es,
&max)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"%s failed at %s:%d: `%s'\n",
"curl_multi_fdset", __FILE__, __LINE__,
curl_multi_strerror (mret));
return;
}
to = -1;
GNUNET_break (CURLM_OK ==
curl_multi_timeout (curl_multi,
&to));
if (-1 == to)
rtime = GNUNET_TIME_UNIT_FOREVER_REL;
else
rtime = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
to);
if (-1 != max)
{
grs = GNUNET_NETWORK_fdset_create ();
gws = GNUNET_NETWORK_fdset_create ();
GNUNET_NETWORK_fdset_copy_native (grs,
&rs,
max + 1);
GNUNET_NETWORK_fdset_copy_native (gws,
&ws,
max + 1);
curl_download_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
rtime,
grs,
gws,
&curl_task_download,
curl_multi);
GNUNET_NETWORK_fdset_destroy (gws);
GNUNET_NETWORK_fdset_destroy (grs);
}
else
{
curl_download_task = GNUNET_SCHEDULER_add_delayed (rtime,
&curl_task_download,
curl_multi);
}
}
/**
* Task that is run when we are ready to receive more data from curl.
*
* @param cls closure, NULL
*/
static void
curl_task_download (void *cls)
{
int running;
int msgnum;
struct CURLMsg *msg;
CURLMcode mret;
struct Socks5Request *s5r;
curl_download_task = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Running CURL interaction\n");
do
{
running = 0;
mret = curl_multi_perform (curl_multi,
&running);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Checking CURL multi status: %d\n",
mret);
while (NULL != (msg = curl_multi_info_read (curl_multi,
&msgnum)))
{
GNUNET_break (CURLE_OK ==
curl_easy_getinfo (msg->easy_handle,
CURLINFO_PRIVATE,
(char **) &s5r ));
if (NULL == s5r)
{
GNUNET_break (0);
continue;
}
switch (msg->msg)
{
case CURLMSG_NONE:
/* documentation says this is not used */
GNUNET_break (0);
break;
case CURLMSG_DONE:
switch (msg->data.result)
{
case CURLE_OK:
case CURLE_GOT_NOTHING:
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"CURL download %s%s completed.\n",
s5r->domain,
s5r->url);
if (NULL == s5r->response)
{
GNUNET_assert (GNUNET_OK ==
create_mhd_response_from_s5r (s5r));
}
s5r->state = SOCKS5_SOCKET_DOWNLOAD_DONE;
if (GNUNET_YES == s5r->suspended)
{
MHD_resume_connection (s5r->con);
s5r->suspended = GNUNET_NO;
}
run_mhd_now (s5r->hd);
break;
default:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Download curl %s%s failed: %s\n",
s5r->domain,
s5r->url,
curl_easy_strerror (msg->data.result));
/* FIXME: indicate error somehow? close MHD connection badly as well? */
s5r->state = SOCKS5_SOCKET_DOWNLOAD_DONE;
if (GNUNET_YES == s5r->suspended)
{
MHD_resume_connection (s5r->con);
s5r->suspended = GNUNET_NO;
}
run_mhd_now (s5r->hd);
break;
}
if (NULL == s5r->response)
s5r->response = curl_failure_response;
break;
case CURLMSG_LAST:
/* documentation says this is not used */
GNUNET_break (0);
break;
default:
/* unexpected status code */
GNUNET_break (0);
break;
}
};
} while (mret == CURLM_CALL_MULTI_PERFORM);
if (CURLM_OK != mret)
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"%s failed at %s:%d: `%s'\n",
"curl_multi_perform", __FILE__, __LINE__,
curl_multi_strerror (mret));
if (0 == running)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Suspending cURL multi loop, no more events pending\n");
if (NULL != curl_download_task)
{
GNUNET_SCHEDULER_cancel (curl_download_task);
curl_download_task = NULL;
}
return; /* nothing more in progress */
}
curl_download_prepare ();
}
/* ********************************* MHD response generation ******************* */
/**
* Read HTTP request header field from the request. Copies the fields
* over to the 'headers' that will be given to curl. However, 'Host'
* is substituted with the LEHO if present. We also change the
* 'Connection' header value to "close" as the proxy does not support
* pipelining.
*
* @param cls our `struct Socks5Request`
* @param kind value kind
* @param key field key
* @param value field value
* @return #MHD_YES to continue to iterate
*/
static int
con_val_iter (void *cls,
enum MHD_ValueKind kind,
const char *key,
const char *value)
{
struct Socks5Request *s5r = cls;
char *hdr;
if ( (0 == strcasecmp (MHD_HTTP_HEADER_HOST,
key)) &&
(NULL != s5r->leho) )
value = s5r->leho;
if (0 == strcasecmp (MHD_HTTP_HEADER_CONTENT_LENGTH,
key))
return MHD_YES;
if (0 == strcasecmp (MHD_HTTP_HEADER_ACCEPT_ENCODING,
key))
return MHD_YES;
GNUNET_asprintf (&hdr,
"%s: %s",
key,
value);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Adding HEADER `%s' to HTTP request\n",
hdr);
s5r->headers = curl_slist_append (s5r->headers,
hdr);
GNUNET_free (hdr);
return MHD_YES;
}
/**
* Main MHD callback for handling requests.
*
* @param cls unused
* @param con MHD connection handle
* @param url the url in the request
* @param meth the HTTP method used ("GET", "PUT", etc.)
* @param ver the HTTP version string (i.e. "HTTP/1.1")
* @param upload_data the data being uploaded (excluding HEADERS,
* for a POST that fits into memory and that is encoded
* with a supported encoding, the POST data will NOT be
* given in upload_data and is instead available as
* part of MHD_get_connection_values; very large POST
* data *will* be made available incrementally in
* upload_data)
* @param upload_data_size set initially to the size of the
* @a upload_data provided; the method must update this
* value to the number of bytes NOT processed;
* @param con_cls pointer to location where we store the `struct Request`
* @return #MHD_YES if the connection was handled successfully,
* #MHD_NO if the socket must be closed due to a serious
* error while handling the request
*/
static int
create_response (void *cls,
struct MHD_Connection *con,
const char *url,
const char *meth,
const char *ver,
const char *upload_data,
size_t *upload_data_size,
void **con_cls)
{
struct Socks5Request *s5r = *con_cls;
char *curlurl;
char ipstring[INET6_ADDRSTRLEN];
char ipaddr[INET6_ADDRSTRLEN + 2];
const struct sockaddr *sa;
const struct sockaddr_in *s4;
const struct sockaddr_in6 *s6;
uint16_t port;
size_t left;
if (NULL == s5r)
{
GNUNET_break (0);
return MHD_NO;
}
s5r->con = con;
/* Fresh connection. */
if (SOCKS5_SOCKET_WITH_MHD == s5r->state)
{
/* first time here, initialize curl handle */
if (s5r->is_gns)
{
sa = (const struct sockaddr *) &s5r->destination_address;
switch (sa->sa_family)
{
case AF_INET:
s4 = (const struct sockaddr_in *) &s5r->destination_address;
if (NULL == inet_ntop (AF_INET,
&s4->sin_addr,
ipstring,
sizeof (ipstring)))
{
GNUNET_break (0);
return MHD_NO;
}
GNUNET_snprintf (ipaddr,
sizeof (ipaddr),
"%s",
ipstring);
port = ntohs (s4->sin_port);
break;
case AF_INET6:
s6 = (const struct sockaddr_in6 *) &s5r->destination_address;
if (NULL == inet_ntop (AF_INET6,
&s6->sin6_addr,
ipstring,
sizeof (ipstring)))
{
GNUNET_break (0);
return MHD_NO;
}
GNUNET_snprintf (ipaddr,
sizeof (ipaddr),
"%s",
ipstring);
port = ntohs (s6->sin6_port);
break;
default:
GNUNET_break (0);
return MHD_NO;
}
}
else
{
port = s5r->port;
}
if (NULL == s5r->curl)
s5r->curl = curl_easy_init ();
if (NULL == s5r->curl)
return MHD_queue_response (con,
MHD_HTTP_INTERNAL_SERVER_ERROR,
curl_failure_response);
curl_easy_setopt (s5r->curl,
CURLOPT_HEADERFUNCTION,
&curl_check_hdr);
curl_easy_setopt (s5r->curl,
CURLOPT_HEADERDATA,
s5r);
curl_easy_setopt (s5r->curl,
CURLOPT_FOLLOWLOCATION,
0);
if (s5r->is_gns)
curl_easy_setopt (s5r->curl,
CURLOPT_IPRESOLVE,
CURL_IPRESOLVE_V4);
curl_easy_setopt (s5r->curl,
CURLOPT_CONNECTTIMEOUT,
600L);
curl_easy_setopt (s5r->curl,
CURLOPT_TIMEOUT,
600L);
curl_easy_setopt (s5r->curl,
CURLOPT_NOSIGNAL,
1L);
curl_easy_setopt (s5r->curl,
CURLOPT_HTTP_CONTENT_DECODING,
0);
curl_easy_setopt (s5r->curl,
CURLOPT_NOSIGNAL,
1L);
curl_easy_setopt (s5r->curl,
CURLOPT_PRIVATE,
s5r);
curl_easy_setopt (s5r->curl,
CURLOPT_VERBOSE,
0L);
/**
* Pre-populate cache to resolve Hostname.
* This is necessary as the DNS name in the CURLOPT_URL is used
* for SNI http://de.wikipedia.org/wiki/Server_Name_Indication
*/
if (NULL != s5r->leho)
{
char *curl_hosts;
GNUNET_asprintf (&curl_hosts,
"%s:%d:%s",
s5r->leho,
port,
ipaddr);
s5r->hosts = curl_slist_append (NULL,
curl_hosts);
curl_easy_setopt (s5r->curl,
CURLOPT_RESOLVE,
s5r->hosts);
GNUNET_free (curl_hosts);
}
if (s5r->is_gns)
{
GNUNET_asprintf (&curlurl,
(HTTPS_PORT != s5r->port)
? "http://%s:%d%s"
: "https://%s:%d%s",
(NULL != s5r->leho)
? s5r->leho
: ipaddr,
port,
s5r->url);
}
else
{
GNUNET_asprintf (&curlurl,
(HTTPS_PORT != s5r->port)
? "http://%s:%d%s"
: "https://%s:%d%s",
s5r->domain,
port,
s5r->url);
}
curl_easy_setopt (s5r->curl,
CURLOPT_URL,
curlurl);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Launching %s CURL interaction, fetching `%s'\n",
(s5r->is_gns) ? "GNS" : "DNS",
curlurl);
GNUNET_free (curlurl);
if (0 == strcasecmp (meth,
MHD_HTTP_METHOD_PUT))
{
s5r->state = SOCKS5_SOCKET_UPLOAD_STARTED;
curl_easy_setopt (s5r->curl,
CURLOPT_UPLOAD,
1L);
curl_easy_setopt (s5r->curl,
CURLOPT_WRITEFUNCTION,
&curl_download_cb);
curl_easy_setopt (s5r->curl,
CURLOPT_WRITEDATA,
s5r);
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (s5r->curl,
CURLOPT_READFUNCTION,
&curl_upload_cb));
curl_easy_setopt (s5r->curl,
CURLOPT_READDATA,
s5r);
{
const char *us;
long upload_size;
us = MHD_lookup_connection_value (con,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_CONTENT_LENGTH);
if ( (1 == sscanf (us,
"%ld",
&upload_size)) &&
(upload_size >= 0) )
{
curl_easy_setopt (s5r->curl,
CURLOPT_INFILESIZE,
upload_size);
}
}
}
else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_POST))
{
s5r->state = SOCKS5_SOCKET_UPLOAD_STARTED;
curl_easy_setopt (s5r->curl,
CURLOPT_POST,
1L);
curl_easy_setopt (s5r->curl,
CURLOPT_WRITEFUNCTION,
&curl_download_cb);
curl_easy_setopt (s5r->curl,
CURLOPT_WRITEDATA,
s5r);
curl_easy_setopt (s5r->curl,
CURLOPT_READFUNCTION,
&curl_upload_cb);
curl_easy_setopt (s5r->curl,
CURLOPT_READDATA,
s5r);
{
const char *us;
long upload_size;
us = MHD_lookup_connection_value (con,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_CONTENT_LENGTH);
if ( (NULL != us) && (1 == sscanf (us,
"%ld",
&upload_size)) &&
(upload_size >= 0) )
{
curl_easy_setopt (s5r->curl,
CURLOPT_INFILESIZE,
upload_size);
} else {
curl_easy_setopt (s5r->curl,
CURLOPT_INFILESIZE,
upload_size);
}
}
}
else if (0 == strcasecmp (meth,
MHD_HTTP_METHOD_HEAD))
{
s5r->state = SOCKS5_SOCKET_DOWNLOAD_STARTED;
curl_easy_setopt (s5r->curl,
CURLOPT_NOBODY,
1L);
}
else if (0 == strcasecmp (meth,
MHD_HTTP_METHOD_OPTIONS))
{
s5r->state = SOCKS5_SOCKET_DOWNLOAD_STARTED;
curl_easy_setopt (s5r->curl,
CURLOPT_CUSTOMREQUEST,
"OPTIONS");
}
else if (0 == strcasecmp (meth,
MHD_HTTP_METHOD_GET))
{
s5r->state = SOCKS5_SOCKET_DOWNLOAD_STARTED;
curl_easy_setopt (s5r->curl,
CURLOPT_HTTPGET,
1L);
curl_easy_setopt (s5r->curl,
CURLOPT_WRITEFUNCTION,
&curl_download_cb);
curl_easy_setopt (s5r->curl,
CURLOPT_WRITEDATA,
s5r);
}
else
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_("Unsupported HTTP method `%s'\n"),
meth);
curl_easy_cleanup (s5r->curl);
s5r->curl = NULL;
return MHD_NO;
}
if (0 == strcasecmp (ver, MHD_HTTP_VERSION_1_0))
{
curl_easy_setopt (s5r->curl,
CURLOPT_HTTP_VERSION,
CURL_HTTP_VERSION_1_0);
}
else if (0 == strcasecmp (ver, MHD_HTTP_VERSION_1_1))
{
curl_easy_setopt (s5r->curl,
CURLOPT_HTTP_VERSION,
CURL_HTTP_VERSION_1_1);
}
else
{
curl_easy_setopt (s5r->curl,
CURLOPT_HTTP_VERSION,
CURL_HTTP_VERSION_NONE);
}
if (HTTPS_PORT == s5r->port)
{
curl_easy_setopt (s5r->curl,
CURLOPT_USE_SSL,
CURLUSESSL_ALL);
if (NULL != s5r->dane_data)
curl_easy_setopt (s5r->curl,
CURLOPT_SSL_VERIFYPEER,
0L);
else
curl_easy_setopt (s5r->curl,
CURLOPT_SSL_VERIFYPEER,
1L);
/* Disable cURL checking the hostname, as we will check ourselves
as only we have the domain name or the LEHO or the DANE record */
curl_easy_setopt (s5r->curl,
CURLOPT_SSL_VERIFYHOST,
0L);
}
else
{
curl_easy_setopt (s5r->curl,
CURLOPT_USE_SSL,
CURLUSESSL_NONE);
}
if (CURLM_OK !=
curl_multi_add_handle (curl_multi,
s5r->curl))
{
GNUNET_break (0);
curl_easy_cleanup (s5r->curl);
s5r->curl = NULL;
return MHD_NO;
}
MHD_get_connection_values (con,
MHD_HEADER_KIND,
&con_val_iter,
s5r);
curl_easy_setopt (s5r->curl,
CURLOPT_HTTPHEADER,
s5r->headers);
curl_download_prepare ();
return MHD_YES;
}
/* continuing to process request */
if (0 != *upload_data_size)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Processing %u bytes UPLOAD\n",
(unsigned int) *upload_data_size);
/* FIXME: This must be set or a header with Transfer-Encoding: chunked. Else
* upload callback is not called!
*/
curl_easy_setopt (s5r->curl,
CURLOPT_POSTFIELDSIZE,
*upload_data_size);
left = GNUNET_MIN (*upload_data_size,
sizeof (s5r->io_buf) - s5r->io_len);
GNUNET_memcpy (&s5r->io_buf[s5r->io_len],
upload_data,
left);
s5r->io_len += left;
*upload_data_size -= left;
GNUNET_assert (NULL != s5r->curl);
if (GNUNET_YES == s5r->curl_paused)
{
s5r->curl_paused = GNUNET_NO;
curl_easy_pause (s5r->curl,
CURLPAUSE_CONT);
}
return MHD_YES;
}
if (SOCKS5_SOCKET_UPLOAD_STARTED == s5r->state)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Finished processing UPLOAD\n");
s5r->state = SOCKS5_SOCKET_UPLOAD_DONE;
}
if (NULL == s5r->response)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Waiting for HTTP response for %s%s...\n",
s5r->domain,
s5r->url);
MHD_suspend_connection (con);
s5r->suspended = GNUNET_YES;
return MHD_YES;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Queueing response for %s%s with MHD\n",
s5r->domain,
s5r->url);
run_mhd_now (s5r->hd);
return MHD_queue_response (con,
s5r->response_code,
s5r->response);
}
/* ******************** MHD HTTP setup and event loop ******************** */
/**
* Function called when MHD decides that we are done with a request.
*
* @param cls NULL
* @param connection connection handle
* @param con_cls value as set by the last call to
* the MHD_AccessHandlerCallback, should be our `struct Socks5Request *`
* @param toe reason for request termination (ignored)
*/
static void
mhd_completed_cb (void *cls,
struct MHD_Connection *connection,
void **con_cls,
enum MHD_RequestTerminationCode toe)
{
struct Socks5Request *s5r = *con_cls;
if (NULL == s5r)
return;
if (MHD_REQUEST_TERMINATED_COMPLETED_OK != toe)
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"MHD encountered error handling request: %d\n",
toe);
if (NULL != s5r->curl)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Removing cURL handle (MHD interaction complete)\n");
curl_multi_remove_handle (curl_multi,
s5r->curl);
curl_slist_free_all (s5r->headers);
s5r->headers = NULL;
curl_easy_reset (s5r->curl);
s5r->rbuf_len = 0;
s5r->wbuf_len = 0;
s5r->io_len = 0;
curl_download_prepare ();
}
if ( (NULL != s5r->response) &&
(curl_failure_response != s5r->response) )
MHD_destroy_response (s5r->response);
for (struct HttpResponseHeader *header = s5r->header_head;
NULL != header;
header = s5r->header_head)
{
GNUNET_CONTAINER_DLL_remove (s5r->header_head,
s5r->header_tail,
header);
GNUNET_free (header->type);
GNUNET_free (header->value);
GNUNET_free (header);
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Finished request for %s\n",
s5r->url);
GNUNET_free (s5r->url);
s5r->state = SOCKS5_SOCKET_WITH_MHD;
s5r->url = NULL;
s5r->response = NULL;
*con_cls = NULL;
}
/**
* Function called when MHD connection is opened or closed.
*
* @param cls NULL
* @param connection connection handle
* @param con_cls value as set by the last call to
* the MHD_AccessHandlerCallback, should be our `struct Socks5Request *`
* @param toe connection notification type
*/
static void
mhd_connection_cb (void *cls,
struct MHD_Connection *connection,
void **con_cls,
enum MHD_ConnectionNotificationCode cnc)
{
struct Socks5Request *s5r;
const union MHD_ConnectionInfo *ci;
int sock;
switch (cnc)
{
case MHD_CONNECTION_NOTIFY_STARTED:
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connection started...\n");
ci = MHD_get_connection_info (connection,
MHD_CONNECTION_INFO_CONNECTION_FD);
if (NULL == ci)
{
GNUNET_break (0);
return;
}
sock = ci->connect_fd;
for (s5r = s5r_head; NULL != s5r; s5r = s5r->next)
{
if (GNUNET_NETWORK_get_fd (s5r->sock) == sock)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Context set...\n");
s5r->ssl_checked = GNUNET_NO;
*con_cls = s5r;
break;
}
}
break;
case MHD_CONNECTION_NOTIFY_CLOSED:
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Connection closed... cleaning up\n");
s5r = *con_cls;
if (NULL == s5r)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Connection stale!\n");
return;
}
cleanup_s5r (s5r);
curl_download_prepare ();
*con_cls = NULL;
break;
default:
GNUNET_break (0);
}
}
/**
* Function called when MHD first processes an incoming connection.
* Gives us the respective URI information.
*
* We use this to associate the `struct MHD_Connection` with our
* internal `struct Socks5Request` data structure (by checking
* for matching sockets).
*
* @param cls the HTTP server handle (a `struct MhdHttpList`)
* @param url the URL that is being requested
* @param connection MHD connection object for the request
* @return the `struct Socks5Request` that this @a connection is for
*/
static void *
mhd_log_callback (void *cls,
const char *url,
struct MHD_Connection *connection)
{
struct Socks5Request *s5r;
const union MHD_ConnectionInfo *ci;
ci = MHD_get_connection_info (connection,
MHD_CONNECTION_INFO_SOCKET_CONTEXT);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Processing %s\n", url);
if (NULL == ci)
{
GNUNET_break (0);
return NULL;
}
s5r = ci->socket_context;
if (NULL != s5r->url)
{
GNUNET_break (0);
return NULL;
}
s5r->url = GNUNET_strdup (url);
if (NULL != s5r->timeout_task)
{
GNUNET_SCHEDULER_cancel (s5r->timeout_task);
s5r->timeout_task = NULL;
}
GNUNET_assert (s5r->state == SOCKS5_SOCKET_WITH_MHD);
return s5r;
}
/**
* Kill the given MHD daemon.
*
* @param hd daemon to stop
*/
static void
kill_httpd (struct MhdHttpList *hd)
{
GNUNET_CONTAINER_DLL_remove (mhd_httpd_head,
mhd_httpd_tail,
hd);
GNUNET_free_non_null (hd->domain);
MHD_stop_daemon (hd->daemon);
if (NULL != hd->httpd_task)
{
GNUNET_SCHEDULER_cancel (hd->httpd_task);
hd->httpd_task = NULL;
}
GNUNET_free_non_null (hd->proxy_cert);
if (hd == httpd)
httpd = NULL;
GNUNET_free (hd);
}
/**
* Task run whenever HTTP server is idle for too long. Kill it.
*
* @param cls the `struct MhdHttpList *`
*/
static void
kill_httpd_task (void *cls)
{
struct MhdHttpList *hd = cls;
hd->httpd_task = NULL;
kill_httpd (hd);
}
/**
* Task run whenever HTTP server operations are pending.
*
* @param cls the `struct MhdHttpList *` of the daemon that is being run
*/
static void
do_httpd (void *cls);
/**
* Schedule MHD. This function should be called initially when an
* MHD is first getting its client socket, and will then automatically
* always be called later whenever there is work to be done.
*
* @param hd the daemon to schedule
*/
static void
schedule_httpd (struct MhdHttpList *hd)
{
fd_set rs;
fd_set ws;
fd_set es;
struct GNUNET_NETWORK_FDSet *wrs;
struct GNUNET_NETWORK_FDSet *wws;
int max;
int haveto;
MHD_UNSIGNED_LONG_LONG timeout;
struct GNUNET_TIME_Relative tv;
FD_ZERO (&rs);
FD_ZERO (&ws);
FD_ZERO (&es);
max = -1;
if (MHD_YES !=
MHD_get_fdset (hd->daemon,
&rs,
&ws,
&es,
&max))
{
kill_httpd (hd);
return;
}
haveto = MHD_get_timeout (hd->daemon,
&timeout);
if (MHD_YES == haveto)
tv.rel_value_us = (uint64_t) timeout * 1000LL;
else
tv = GNUNET_TIME_UNIT_FOREVER_REL;
if (-1 != max)
{
wrs = GNUNET_NETWORK_fdset_create ();
wws = GNUNET_NETWORK_fdset_create ();
GNUNET_NETWORK_fdset_copy_native (wrs, &rs, max + 1);
GNUNET_NETWORK_fdset_copy_native (wws, &ws, max + 1);
}
else
{
wrs = NULL;
wws = NULL;
}
if (NULL != hd->httpd_task)
{
GNUNET_SCHEDULER_cancel (hd->httpd_task);
hd->httpd_task = NULL;
}
if ( (MHD_YES != haveto) &&
(-1 == max) &&
(hd != httpd) )
{
/* daemon is idle, kill after timeout */
hd->httpd_task = GNUNET_SCHEDULER_add_delayed (MHD_CACHE_TIMEOUT,
&kill_httpd_task,
hd);
}
else
{
hd->httpd_task =
GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
tv, wrs, wws,
&do_httpd, hd);
}
if (NULL != wrs)
GNUNET_NETWORK_fdset_destroy (wrs);
if (NULL != wws)
GNUNET_NETWORK_fdset_destroy (wws);
}
/**
* Task run whenever HTTP server operations are pending.
*
* @param cls the `struct MhdHttpList` of the daemon that is being run
*/
static void
do_httpd (void *cls)
{
struct MhdHttpList *hd = cls;
hd->httpd_task = NULL;
MHD_run (hd->daemon);
schedule_httpd (hd);
}
/**
* Run MHD now, we have extra data ready for the callback.
*
* @param hd the daemon to run now.
*/
static void
run_mhd_now (struct MhdHttpList *hd)
{
if (NULL != hd->httpd_task)
GNUNET_SCHEDULER_cancel (hd->httpd_task);
hd->httpd_task = GNUNET_SCHEDULER_add_now (&do_httpd,
hd);
}
/**
* Read file in filename
*
* @param filename file to read
* @param size pointer where filesize is stored
* @return NULL on error
*/
static void*
load_file (const char* filename,
unsigned int* size)
{
void *buffer;
uint64_t fsize;
if (GNUNET_OK !=
GNUNET_DISK_file_size (filename,
&fsize,
GNUNET_YES,
GNUNET_YES))
return NULL;
if (fsize > MAX_PEM_SIZE)
return NULL;
*size = (unsigned int) fsize;
buffer = GNUNET_malloc (*size);
if (fsize !=
GNUNET_DISK_fn_read (filename,
buffer,
(size_t) fsize))
{
GNUNET_free (buffer);
return NULL;
}
return buffer;
}
/**
* Load PEM key from file
*
* @param key where to store the data
* @param keyfile path to the PEM file
* @return #GNUNET_OK on success
*/
static int
load_key_from_file (gnutls_x509_privkey_t key,
const char* keyfile)
{
gnutls_datum_t key_data;
int ret;
key_data.data = load_file (keyfile,
&key_data.size);
if (NULL == key_data.data)
return GNUNET_SYSERR;
ret = gnutls_x509_privkey_import (key, &key_data,
GNUTLS_X509_FMT_PEM);
if (GNUTLS_E_SUCCESS != ret)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
_("Unable to import private key from file `%s'\n"),
keyfile);
}
GNUNET_free_non_null (key_data.data);
return (GNUTLS_E_SUCCESS != ret) ? GNUNET_SYSERR : GNUNET_OK;
}
/**
* Load cert from file
*
* @param crt struct to store data in
* @param certfile path to pem file
* @return #GNUNET_OK on success
*/
static int
load_cert_from_file (gnutls_x509_crt_t crt,
const char* certfile)
{
gnutls_datum_t cert_data;
int ret;
cert_data.data = load_file (certfile,
&cert_data.size);
if (NULL == cert_data.data)
return GNUNET_SYSERR;
ret = gnutls_x509_crt_import (crt,
&cert_data,
GNUTLS_X509_FMT_PEM);
if (GNUTLS_E_SUCCESS != ret)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
_("Unable to import certificate from `%s'\n"),
certfile);
}
GNUNET_free_non_null (cert_data.data);
return (GNUTLS_E_SUCCESS != ret) ? GNUNET_SYSERR : GNUNET_OK;
}
/**
* Generate new certificate for specific name
*
* @param name the subject name to generate a cert for
* @return a struct holding the PEM data, NULL on error
*/
static struct ProxyGNSCertificate *
generate_gns_certificate (const char *name)
{
unsigned int serial;
size_t key_buf_size;
size_t cert_buf_size;
gnutls_x509_crt_t request;
time_t etime;
struct tm *tm_data;
struct ProxyGNSCertificate *pgc;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Generating x.509 certificate for `%s'\n",
name);
GNUNET_break (GNUTLS_E_SUCCESS == gnutls_x509_crt_init (&request));
GNUNET_break (GNUTLS_E_SUCCESS == gnutls_x509_crt_set_key (request, proxy_ca.key));
pgc = GNUNET_new (struct ProxyGNSCertificate);
gnutls_x509_crt_set_dn_by_oid (request,
GNUTLS_OID_X520_COUNTRY_NAME,
0,
"ZZ",
strlen ("ZZ"));
gnutls_x509_crt_set_dn_by_oid (request,
GNUTLS_OID_X520_ORGANIZATION_NAME,
0,
"GNU Name System",
strlen ("GNU Name System"));
gnutls_x509_crt_set_dn_by_oid (request,
GNUTLS_OID_X520_COMMON_NAME,
0,
name,
strlen (name));
GNUNET_break (GNUTLS_E_SUCCESS ==
gnutls_x509_crt_set_version (request,
3));
gnutls_rnd (GNUTLS_RND_NONCE,
&serial,
sizeof (serial));
gnutls_x509_crt_set_serial (request,
&serial,
sizeof (serial));
etime = time (NULL);
tm_data = localtime (&etime);
tm_data->tm_hour--;
etime = mktime(tm_data);
gnutls_x509_crt_set_activation_time (request,
etime);
tm_data->tm_year++;
etime = mktime (tm_data);
gnutls_x509_crt_set_expiration_time (request,
etime);
gnutls_x509_crt_sign2 (request,
proxy_ca.cert,
proxy_ca.key,
GNUTLS_DIG_SHA512,
0);
key_buf_size = sizeof (pgc->key);
cert_buf_size = sizeof (pgc->cert);
gnutls_x509_crt_export (request,
GNUTLS_X509_FMT_PEM,
pgc->cert,
&cert_buf_size);
gnutls_x509_privkey_export (proxy_ca.key,
GNUTLS_X509_FMT_PEM,
pgc->key,
&key_buf_size);
gnutls_x509_crt_deinit (request);
return pgc;
}
/**
* Function called by MHD with errors, suppresses them all.
*
* @param cls closure
* @param fm format string (`printf()`-style)
* @param ap arguments to @a fm
*/
static void
mhd_error_log_callback (void *cls,
const char *fm,
va_list ap)
{
/* do nothing */
}
/**
* Lookup (or create) an TLS MHD instance for a particular domain.
*
* @param domain the domain the TLS daemon has to serve
* @return NULL on error
*/
static struct MhdHttpList *
lookup_ssl_httpd (const char* domain)
{
struct MhdHttpList *hd;
struct ProxyGNSCertificate *pgc;
if (NULL == domain)
{
GNUNET_break (0);
return NULL;
}
for (hd = mhd_httpd_head; NULL != hd; hd = hd->next)
if ( (NULL != hd->domain) &&
(0 == strcmp (hd->domain, domain)) )
return hd;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Starting fresh MHD HTTPS instance for domain `%s'\n",
domain);
pgc = generate_gns_certificate (domain);
hd = GNUNET_new (struct MhdHttpList);
hd->is_ssl = GNUNET_YES;
hd->domain = GNUNET_strdup (domain);
hd->proxy_cert = pgc;
hd->daemon = MHD_start_daemon (MHD_USE_DEBUG | MHD_USE_SSL | MHD_USE_NO_LISTEN_SOCKET | MHD_ALLOW_SUSPEND_RESUME,
0,
NULL, NULL,
&create_response, hd,
MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16,
MHD_OPTION_NOTIFY_COMPLETED, &mhd_completed_cb, NULL,
MHD_OPTION_NOTIFY_CONNECTION, &mhd_connection_cb, NULL,
MHD_OPTION_URI_LOG_CALLBACK, &mhd_log_callback, NULL,
MHD_OPTION_EXTERNAL_LOGGER, &mhd_error_log_callback, NULL,
MHD_OPTION_HTTPS_MEM_KEY, pgc->key,
MHD_OPTION_HTTPS_MEM_CERT, pgc->cert,
MHD_OPTION_END);
if (NULL == hd->daemon)
{
GNUNET_free (pgc);
GNUNET_free (hd);
return NULL;
}
GNUNET_CONTAINER_DLL_insert (mhd_httpd_head,
mhd_httpd_tail,
hd);
return hd;
}
/**
* Task run when a Socks5Request somehow fails to be associated with
* an MHD connection (i.e. because the client never speaks HTTP after
* the SOCKS5 handshake). Clean up.
*
* @param cls the `struct Socks5Request *`
*/
static void
timeout_s5r_handshake (void *cls)
{
struct Socks5Request *s5r = cls;
s5r->timeout_task = NULL;
cleanup_s5r (s5r);
}
/**
* We're done with the Socks5 protocol, now we need to pass the
* connection data through to the final destination, either
* direct (if the protocol might not be HTTP), or via MHD
* (if the port looks like it should be HTTP).
*
* @param s5r socks request that has reached the final stage
*/
static void
setup_data_transfer (struct Socks5Request *s5r)
{
struct MhdHttpList *hd;
int fd;
const struct sockaddr *addr;
socklen_t len;
char *domain;
switch (s5r->port)
{
case HTTPS_PORT:
GNUNET_asprintf (&domain,
"%s",
s5r->domain);
hd = lookup_ssl_httpd (domain);
if (NULL == hd)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
_("Failed to start HTTPS server for `%s'\n"),
s5r->domain);
cleanup_s5r (s5r);
GNUNET_free (domain);
return;
}
break;
case HTTP_PORT:
default:
domain = NULL;
GNUNET_assert (NULL != httpd);
hd = httpd;
break;
}
fd = GNUNET_NETWORK_get_fd (s5r->sock);
addr = GNUNET_NETWORK_get_addr (s5r->sock);
len = GNUNET_NETWORK_get_addrlen (s5r->sock);
s5r->state = SOCKS5_SOCKET_WITH_MHD;
if (MHD_YES !=
MHD_add_connection (hd->daemon,
fd,
addr,
len))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
_("Failed to pass client to MHD\n"));
cleanup_s5r (s5r);
GNUNET_free_non_null (domain);
return;
}
s5r->hd = hd;
schedule_httpd (hd);
s5r->timeout_task = GNUNET_SCHEDULER_add_delayed (HTTP_HANDSHAKE_TIMEOUT,
&timeout_s5r_handshake,
s5r);
GNUNET_free_non_null (domain);
}
/* ********************* SOCKS handling ************************* */
/**
* Write data from buffer to socks5 client, then continue with state machine.
*
* @param cls the closure with the `struct Socks5Request`
*/
static void
do_write (void *cls)
{
struct Socks5Request *s5r = cls;
ssize_t len;
s5r->wtask = NULL;
len = GNUNET_NETWORK_socket_send (s5r->sock,
s5r->wbuf,
s5r->wbuf_len);
if (len <= 0)
{
/* write error: connection closed, shutdown, etc.; just clean up */
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Write Error\n");
cleanup_s5r (s5r);
return;
}
memmove (s5r->wbuf,
&s5r->wbuf[len],
s5r->wbuf_len - len);
s5r->wbuf_len -= len;
if (s5r->wbuf_len > 0)
{
/* not done writing */
s5r->wtask =
GNUNET_SCHEDULER_add_write_net (GNUNET_TIME_UNIT_FOREVER_REL,
s5r->sock,
&do_write, s5r);
return;
}
/* we're done writing, continue with state machine! */
switch (s5r->state)
{
case SOCKS5_INIT:
GNUNET_assert (0);
break;
case SOCKS5_REQUEST:
GNUNET_assert (NULL != s5r->rtask);
break;
case SOCKS5_DATA_TRANSFER:
setup_data_transfer (s5r);
return;
case SOCKS5_WRITE_THEN_CLEANUP:
cleanup_s5r (s5r);
return;
default:
GNUNET_break (0);
break;
}
}
/**
* Return a server response message indicating a failure to the client.
*
* @param s5r request to return failure code for
* @param sc status code to return
*/
static void
signal_socks_failure (struct Socks5Request *s5r,
enum Socks5StatusCode sc)
{
struct Socks5ServerResponseMessage *s_resp;
s_resp = (struct Socks5ServerResponseMessage *) &s5r->wbuf[s5r->wbuf_len];
memset (s_resp, 0, sizeof (struct Socks5ServerResponseMessage));
s_resp->version = SOCKS_VERSION_5;
s_resp->reply = sc;
s5r->state = SOCKS5_WRITE_THEN_CLEANUP;
if (NULL != s5r->wtask)
s5r->wtask =
GNUNET_SCHEDULER_add_write_net (GNUNET_TIME_UNIT_FOREVER_REL,
s5r->sock,
&do_write, s5r);
}
/**
* Return a server response message indicating success.
*
* @param s5r request to return success status message for
*/
static void
signal_socks_success (struct Socks5Request *s5r)
{
struct Socks5ServerResponseMessage *s_resp;
s_resp = (struct Socks5ServerResponseMessage *) &s5r->wbuf[s5r->wbuf_len];
s_resp->version = SOCKS_VERSION_5;
s_resp->reply = SOCKS5_STATUS_REQUEST_GRANTED;
s_resp->reserved = 0;
s_resp->addr_type = SOCKS5_AT_IPV4;
/* zero out IPv4 address and port */
memset (&s_resp[1],
0,
sizeof (struct in_addr) + sizeof (uint16_t));
s5r->wbuf_len += sizeof (struct Socks5ServerResponseMessage) +
sizeof (struct in_addr) + sizeof (uint16_t);
if (NULL == s5r->wtask)
s5r->wtask =
GNUNET_SCHEDULER_add_write_net (GNUNET_TIME_UNIT_FOREVER_REL,
s5r->sock,
&do_write, s5r);
}
/**
* Process GNS results for target domain.
*
* @param cls the `struct Socks5Request *`
* @param tld #GNUNET_YES if this was a GNS TLD.
* @param rd_count number of records returned
* @param rd record data
*/
static void
handle_gns_result (void *cls,
int tld,
uint32_t rd_count,
const struct GNUNET_GNSRECORD_Data *rd)
{
struct Socks5Request *s5r = cls;
const struct GNUNET_GNSRECORD_Data *r;
int got_ip;
s5r->gns_lookup = NULL;
s5r->is_gns = tld;
got_ip = GNUNET_NO;
for (uint32_t i=0;irecord_type)
{
case GNUNET_DNSPARSER_TYPE_A:
{
struct sockaddr_in *in;
if (sizeof (struct in_addr) != r->data_size)
{
GNUNET_break_op (0);
break;
}
if (GNUNET_YES == got_ip)
break;
if (GNUNET_OK !=
GNUNET_NETWORK_test_pf (PF_INET))
break;
got_ip = GNUNET_YES;
in = (struct sockaddr_in *) &s5r->destination_address;
in->sin_family = AF_INET;
GNUNET_memcpy (&in->sin_addr,
r->data,
r->data_size);
in->sin_port = htons (s5r->port);
#if HAVE_SOCKADDR_IN_SIN_LEN
in->sin_len = sizeof (*in);
#endif
}
break;
case GNUNET_DNSPARSER_TYPE_AAAA:
{
struct sockaddr_in6 *in;
if (sizeof (struct in6_addr) != r->data_size)
{
GNUNET_break_op (0);
break;
}
if (GNUNET_YES == got_ip)
break;
if (GNUNET_YES == disable_v6)
break;
if (GNUNET_OK !=
GNUNET_NETWORK_test_pf (PF_INET6))
break;
/* FIXME: allow user to disable IPv6 per configuration option... */
got_ip = GNUNET_YES;
in = (struct sockaddr_in6 *) &s5r->destination_address;
in->sin6_family = AF_INET6;
GNUNET_memcpy (&in->sin6_addr,
r->data,
r->data_size);
in->sin6_port = htons (s5r->port);
#if HAVE_SOCKADDR_IN_SIN_LEN
in->sin6_len = sizeof (*in);
#endif
}
break;
case GNUNET_GNSRECORD_TYPE_VPN:
GNUNET_break (0); /* should have been translated within GNS */
break;
case GNUNET_GNSRECORD_TYPE_LEHO:
GNUNET_free_non_null (s5r->leho);
s5r->leho = GNUNET_strndup (r->data,
r->data_size);
break;
case GNUNET_GNSRECORD_TYPE_BOX:
{
const struct GNUNET_GNSRECORD_BoxRecord *box;
if (r->data_size < sizeof (struct GNUNET_GNSRECORD_BoxRecord))
{
GNUNET_break_op (0);
break;
}
box = r->data;
if ( (ntohl (box->record_type) != GNUNET_DNSPARSER_TYPE_TLSA) ||
(ntohs (box->protocol) != IPPROTO_TCP) ||
(ntohs (box->service) != s5r->port) )
break; /* BOX record does not apply */
GNUNET_free_non_null (s5r->dane_data);
s5r->dane_data_len = r->data_size - sizeof (struct GNUNET_GNSRECORD_BoxRecord);
s5r->dane_data = GNUNET_malloc (s5r->dane_data_len);
GNUNET_memcpy (s5r->dane_data,
&box[1],
s5r->dane_data_len);
break;
}
default:
/* don't care */
break;
}
}
if ( (GNUNET_YES != got_ip) &&
(GNUNET_YES == tld) )
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Name resolution failed to yield useful IP address.\n");
signal_socks_failure (s5r,
SOCKS5_STATUS_GENERAL_FAILURE);
return;
}
s5r->state = SOCKS5_DATA_TRANSFER;
signal_socks_success (s5r);
}
/**
* Remove the first @a len bytes from the beginning of the read buffer.
*
* @param s5r the handle clear the read buffer for
* @param len number of bytes in read buffer to advance
*/
static void
clear_from_s5r_rbuf (struct Socks5Request *s5r,
size_t len)
{
GNUNET_assert (len <= s5r->rbuf_len);
memmove (s5r->rbuf,
&s5r->rbuf[len],
s5r->rbuf_len - len);
s5r->rbuf_len -= len;
}
/**
* Read data from incoming Socks5 connection
*
* @param cls the closure with the `struct Socks5Request`
*/
static void
do_s5r_read (void *cls)
{
struct Socks5Request *s5r = cls;
const struct Socks5ClientHelloMessage *c_hello;
struct Socks5ServerHelloMessage *s_hello;
const struct Socks5ClientRequestMessage *c_req;
ssize_t rlen;
size_t alen;
const struct GNUNET_SCHEDULER_TaskContext *tc;
s5r->rtask = NULL;
tc = GNUNET_SCHEDULER_get_task_context ();
if ( (NULL != tc->read_ready) &&
(GNUNET_NETWORK_fdset_isset (tc->read_ready,
s5r->sock)) )
{
rlen = GNUNET_NETWORK_socket_recv (s5r->sock,
&s5r->rbuf[s5r->rbuf_len],
sizeof (s5r->rbuf) - s5r->rbuf_len);
if (rlen <= 0)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"socks5 client disconnected.\n");
cleanup_s5r (s5r);
return;
}
s5r->rbuf_len += rlen;
}
s5r->rtask = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
s5r->sock,
&do_s5r_read, s5r);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Processing %zu bytes of socks data in state %d\n",
s5r->rbuf_len,
s5r->state);
switch (s5r->state)
{
case SOCKS5_INIT:
c_hello = (const struct Socks5ClientHelloMessage*) &s5r->rbuf;
if ( (s5r->rbuf_len < sizeof (struct Socks5ClientHelloMessage)) ||
(s5r->rbuf_len < sizeof (struct Socks5ClientHelloMessage) + c_hello->num_auth_methods) )
return; /* need more data */
if (SOCKS_VERSION_5 != c_hello->version)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
_("Unsupported socks version %d\n"),
(int) c_hello->version);
cleanup_s5r (s5r);
return;
}
clear_from_s5r_rbuf (s5r,
sizeof (struct Socks5ClientHelloMessage) + c_hello->num_auth_methods);
GNUNET_assert (0 == s5r->wbuf_len);
s_hello = (struct Socks5ServerHelloMessage *) &s5r->wbuf;
s5r->wbuf_len = sizeof (struct Socks5ServerHelloMessage);
s_hello->version = SOCKS_VERSION_5;
s_hello->auth_method = SOCKS_AUTH_NONE;
GNUNET_assert (NULL == s5r->wtask);
s5r->wtask = GNUNET_SCHEDULER_add_write_net (GNUNET_TIME_UNIT_FOREVER_REL,
s5r->sock,
&do_write, s5r);
s5r->state = SOCKS5_REQUEST;
return;
case SOCKS5_REQUEST:
c_req = (const struct Socks5ClientRequestMessage *) &s5r->rbuf;
if (s5r->rbuf_len < sizeof (struct Socks5ClientRequestMessage))
return;
switch (c_req->command)
{
case SOCKS5_CMD_TCP_STREAM:
/* handled below */
break;
default:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
_("Unsupported socks command %d\n"),
(int) c_req->command);
signal_socks_failure (s5r,
SOCKS5_STATUS_COMMAND_NOT_SUPPORTED);
return;
}
switch (c_req->addr_type)
{
case SOCKS5_AT_IPV4:
{
const struct in_addr *v4 = (const struct in_addr *) &c_req[1];
const uint16_t *port = (const uint16_t *) &v4[1];
struct sockaddr_in *in;
s5r->port = ntohs (*port);
alen = sizeof (struct in_addr);
if (s5r->rbuf_len < sizeof (struct Socks5ClientRequestMessage) +
alen + sizeof (uint16_t))
return; /* need more data */
in = (struct sockaddr_in *) &s5r->destination_address;
in->sin_family = AF_INET;
in->sin_addr = *v4;
in->sin_port = *port;
#if HAVE_SOCKADDR_IN_SIN_LEN
in->sin_len = sizeof (*in);
#endif
s5r->state = SOCKS5_DATA_TRANSFER;
}
break;
case SOCKS5_AT_IPV6:
{
const struct in6_addr *v6 = (const struct in6_addr *) &c_req[1];
const uint16_t *port = (const uint16_t *) &v6[1];
struct sockaddr_in6 *in;
s5r->port = ntohs (*port);
alen = sizeof (struct in6_addr);
if (s5r->rbuf_len < sizeof (struct Socks5ClientRequestMessage) +
alen + sizeof (uint16_t))
return; /* need more data */
in = (struct sockaddr_in6 *) &s5r->destination_address;
in->sin6_family = AF_INET6;
in->sin6_addr = *v6;
in->sin6_port = *port;
#if HAVE_SOCKADDR_IN_SIN_LEN
in->sin6_len = sizeof (*in);
#endif
s5r->state = SOCKS5_DATA_TRANSFER;
}
break;
case SOCKS5_AT_DOMAINNAME:
{
const uint8_t *dom_len;
const char *dom_name;
const uint16_t *port;
dom_len = (const uint8_t *) &c_req[1];
alen = *dom_len + 1;
if (s5r->rbuf_len < sizeof (struct Socks5ClientRequestMessage) +
alen + sizeof (uint16_t))
return; /* need more data */
dom_name = (const char *) &dom_len[1];
port = (const uint16_t*) &dom_name[*dom_len];
s5r->domain = GNUNET_strndup (dom_name,
*dom_len);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Requested connection is to http%s://%s:%d\n",
(HTTPS_PORT == s5r->port) ? "s" : "",
s5r->domain,
ntohs (*port));
s5r->state = SOCKS5_RESOLVING;
s5r->port = ntohs (*port);
s5r->gns_lookup = GNUNET_GNS_lookup_with_tld (gns_handle,
s5r->domain,
GNUNET_DNSPARSER_TYPE_A,
GNUNET_NO /* only cached */,
&handle_gns_result,
s5r);
break;
}
default:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
_("Unsupported socks address type %d\n"),
(int) c_req->addr_type);
signal_socks_failure (s5r,
SOCKS5_STATUS_ADDRESS_TYPE_NOT_SUPPORTED);
return;
}
clear_from_s5r_rbuf (s5r,
sizeof (struct Socks5ClientRequestMessage) +
alen + sizeof (uint16_t));
if (0 != s5r->rbuf_len)
{
/* read more bytes than healthy, why did the client send more!? */
GNUNET_break_op (0);
signal_socks_failure (s5r,
SOCKS5_STATUS_GENERAL_FAILURE);
return;
}
if (SOCKS5_DATA_TRANSFER == s5r->state)
{
/* if we are not waiting for GNS resolution, signal success */
signal_socks_success (s5r);
}
/* We are done reading right now */
GNUNET_SCHEDULER_cancel (s5r->rtask);
s5r->rtask = NULL;
return;
case SOCKS5_RESOLVING:
GNUNET_assert (0);
return;
case SOCKS5_DATA_TRANSFER:
GNUNET_assert (0);
return;
default:
GNUNET_assert (0);
return;
}
}
/**
* Accept new incoming connections
*
* @param cls the closure with the lsock4 or lsock6
* @param tc the scheduler context
*/
static void
do_accept (void *cls)
{
struct GNUNET_NETWORK_Handle *lsock = cls;
struct GNUNET_NETWORK_Handle *s;
struct Socks5Request *s5r;
GNUNET_assert (NULL != lsock);
if (lsock == lsock4)
ltask4 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
lsock,
&do_accept,
lsock);
else if (lsock == lsock6)
ltask6 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
lsock,
&do_accept,
lsock);
else
GNUNET_assert (0);
s = GNUNET_NETWORK_socket_accept (lsock,
NULL,
NULL);
if (NULL == s)
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"accept");
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Got an inbound connection, waiting for data\n");
s5r = GNUNET_new (struct Socks5Request);
GNUNET_CONTAINER_DLL_insert (s5r_head,
s5r_tail,
s5r);
s5r->sock = s;
s5r->state = SOCKS5_INIT;
s5r->rtask = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
s5r->sock,
&do_s5r_read,
s5r);
}
/* ******************* General / main code ********************* */
/**
* Task run on shutdown
*
* @param cls closure
*/
static void
do_shutdown (void *cls)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Shutting down...\n");
/* MHD requires resuming before destroying the daemons */
for (struct Socks5Request *s5r = s5r_head;
NULL != s5r;
s5r = s5r->next)
{
if (s5r->suspended)
{
s5r->suspended = GNUNET_NO;
MHD_resume_connection (s5r->con);
}
}
while (NULL != mhd_httpd_head)
kill_httpd (mhd_httpd_head);
while (NULL != s5r_head)
cleanup_s5r (s5r_head);
if (NULL != lsock4)
{
GNUNET_NETWORK_socket_close (lsock4);
lsock4 = NULL;
}
if (NULL != lsock6)
{
GNUNET_NETWORK_socket_close (lsock6);
lsock6 = NULL;
}
if (NULL != curl_multi)
{
curl_multi_cleanup (curl_multi);
curl_multi = NULL;
}
if (NULL != gns_handle)
{
GNUNET_GNS_disconnect (gns_handle);
gns_handle = NULL;
}
if (NULL != curl_download_task)
{
GNUNET_SCHEDULER_cancel (curl_download_task);
curl_download_task = NULL;
}
if (NULL != ltask4)
{
GNUNET_SCHEDULER_cancel (ltask4);
ltask4 = NULL;
}
if (NULL != ltask6)
{
GNUNET_SCHEDULER_cancel (ltask6);
ltask6 = NULL;
}
gnutls_x509_crt_deinit (proxy_ca.cert);
gnutls_x509_privkey_deinit (proxy_ca.key);
gnutls_global_deinit ();
}
/**
* Create an IPv4 listen socket bound to our port.
*
* @return NULL on error
*/
static struct GNUNET_NETWORK_Handle *
bind_v4 ()
{
struct GNUNET_NETWORK_Handle *ls;
struct sockaddr_in sa4;
int eno;
memset (&sa4, 0, sizeof (sa4));
sa4.sin_family = AF_INET;
sa4.sin_port = htons (port);
#if HAVE_SOCKADDR_IN_SIN_LEN
sa4.sin_len = sizeof (sa4);
#endif
ls = GNUNET_NETWORK_socket_create (AF_INET,
SOCK_STREAM,
0);
if (NULL == ls)
return NULL;
if (GNUNET_OK !=
GNUNET_NETWORK_socket_bind (ls,
(const struct sockaddr *) &sa4,
sizeof (sa4)))
{
eno = errno;
GNUNET_NETWORK_socket_close (ls);
errno = eno;
return NULL;
}
return ls;
}
/**
* Create an IPv6 listen socket bound to our port.
*
* @return NULL on error
*/
static struct GNUNET_NETWORK_Handle *
bind_v6 ()
{
struct GNUNET_NETWORK_Handle *ls;
struct sockaddr_in6 sa6;
int eno;
memset (&sa6, 0, sizeof (sa6));
sa6.sin6_family = AF_INET6;
sa6.sin6_port = htons (port);
#if HAVE_SOCKADDR_IN_SIN_LEN
sa6.sin6_len = sizeof (sa6);
#endif
ls = GNUNET_NETWORK_socket_create (AF_INET6,
SOCK_STREAM,
0);
if (NULL == ls)
return NULL;
if (GNUNET_OK !=
GNUNET_NETWORK_socket_bind (ls,
(const struct sockaddr *) &sa6,
sizeof (sa6)))
{
eno = errno;
GNUNET_NETWORK_socket_close (ls);
errno = eno;
return NULL;
}
return ls;
}
/**
* Main function that will be run
*
* @param cls closure
* @param args remaining command-line arguments
* @param cfgfile name of the configuration file used (for saving, can be NULL!)
* @param c configuration
*/
static void
run (void *cls,
char *const *args,
const char *cfgfile,
const struct GNUNET_CONFIGURATION_Handle *c)
{
char* cafile_cfg = NULL;
char* cafile;
struct MhdHttpList *hd;
cfg = c;
if (NULL == (curl_multi = curl_multi_init ()))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to create cURL multi handle!\n");
return;
}
cafile = cafile_opt;
if (NULL == cafile)
{
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_filename (cfg,
"gns-proxy",
"PROXY_CACERT",
&cafile_cfg))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"gns-proxy",
"PROXY_CACERT");
return;
}
cafile = cafile_cfg;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Using `%s' as CA\n",
cafile);
gnutls_global_init ();
gnutls_x509_crt_init (&proxy_ca.cert);
gnutls_x509_privkey_init (&proxy_ca.key);
if ( (GNUNET_OK !=
load_cert_from_file (proxy_ca.cert,
cafile)) ||
(GNUNET_OK !=
load_key_from_file (proxy_ca.key,
cafile)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
_("Failed to load X.509 key and certificate from `%s'\n"),
cafile);
gnutls_x509_crt_deinit (proxy_ca.cert);
gnutls_x509_privkey_deinit (proxy_ca.key);
gnutls_global_deinit ();
GNUNET_free_non_null (cafile_cfg);
return;
}
GNUNET_free_non_null (cafile_cfg);
if (NULL == (gns_handle = GNUNET_GNS_connect (cfg)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unable to connect to GNS!\n");
gnutls_x509_crt_deinit (proxy_ca.cert);
gnutls_x509_privkey_deinit (proxy_ca.key);
gnutls_global_deinit ();
return;
}
GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
NULL);
/* Open listen socket for socks proxy */
lsock6 = bind_v6 ();
if (NULL == lsock6)
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"bind");
}
else
{
if (GNUNET_OK !=
GNUNET_NETWORK_socket_listen (lsock6,
5))
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"listen");
GNUNET_NETWORK_socket_close (lsock6);
lsock6 = NULL;
}
else
{
ltask6 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
lsock6,
&do_accept,
lsock6);
}
}
lsock4 = bind_v4 ();
if (NULL == lsock4)
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"bind");
}
else
{
if (GNUNET_OK !=
GNUNET_NETWORK_socket_listen (lsock4,
5))
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"listen");
GNUNET_NETWORK_socket_close (lsock4);
lsock4 = NULL;
}
else
{
ltask4 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
lsock4,
&do_accept,
lsock4);
}
}
if ( (NULL == lsock4) &&
(NULL == lsock6) )
{
GNUNET_SCHEDULER_shutdown ();
return;
}
if (0 != curl_global_init (CURL_GLOBAL_WIN32))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"cURL global init failed!\n");
GNUNET_SCHEDULER_shutdown ();
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Proxy listens on port %u\n",
(unsigned int) port);
/* start MHD daemon for HTTP */
hd = GNUNET_new (struct MhdHttpList);
hd->daemon = MHD_start_daemon (MHD_USE_DEBUG | MHD_USE_NO_LISTEN_SOCKET | MHD_ALLOW_SUSPEND_RESUME,
0,
NULL, NULL,
&create_response, hd,
MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16,
MHD_OPTION_NOTIFY_COMPLETED, &mhd_completed_cb, NULL,
MHD_OPTION_NOTIFY_CONNECTION, &mhd_connection_cb, NULL,
MHD_OPTION_URI_LOG_CALLBACK, &mhd_log_callback, NULL,
MHD_OPTION_END);
if (NULL == hd->daemon)
{
GNUNET_free (hd);
GNUNET_SCHEDULER_shutdown ();
return;
}
httpd = hd;
GNUNET_CONTAINER_DLL_insert (mhd_httpd_head,
mhd_httpd_tail,
hd);
}
/**
* The main function for gnunet-gns-proxy.
*
* @param argc number of arguments from the command line
* @param argv command line arguments
* @return 0 ok, 1 on error
*/
int
main (int argc,
char *const *argv)
{
struct GNUNET_GETOPT_CommandLineOption options[] = {
GNUNET_GETOPT_option_uint16 ('p',
"port",
NULL,
gettext_noop ("listen on specified port (default: 7777)"),
&port),
GNUNET_GETOPT_option_string ('a',
"authority",
NULL,
gettext_noop ("pem file to use as CA"),
&cafile_opt),
GNUNET_GETOPT_option_flag ('6',
"disable-ivp6",
gettext_noop ("disable use of IPv6"),
&disable_v6),
GNUNET_GETOPT_OPTION_END
};
static const char* page =
"gnunet-gns-proxy"
"cURL fail";
int ret;
if (GNUNET_OK !=
GNUNET_STRINGS_get_utf8_args (argc, argv,
&argc, &argv))
return 2;
GNUNET_log_setup ("gnunet-gns-proxy",
"WARNING",
NULL);
curl_failure_response
= MHD_create_response_from_buffer (strlen (page),
(void *) page,
MHD_RESPMEM_PERSISTENT);
ret =
(GNUNET_OK ==
GNUNET_PROGRAM_run (argc, argv,
"gnunet-gns-proxy",
_("GNUnet GNS proxy"),
options,
&run, NULL)) ? 0 : 1;
MHD_destroy_response (curl_failure_response);
GNUNET_free_non_null ((char *) argv);
return ret;
}
/* end of gnunet-gns-proxy.c */