diff options
Diffstat (limited to 'dirmngr/http.c')
-rw-r--r-- | dirmngr/http.c | 1861 |
1 files changed, 1861 insertions, 0 deletions
diff --git a/dirmngr/http.c b/dirmngr/http.c new file mode 100644 index 000000000..b10ba254e --- /dev/null +++ b/dirmngr/http.c @@ -0,0 +1,1861 @@ +/* http.c - HTTP protocol handler + * Copyright (C) 1999, 2001, 2002, 2003, 2004, + * 2006, 2009 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +/* Simple HTTP client implementation. We try to keep the code as + self-contained as possible. There are some contraints however: + + - estream is required. We now require estream because it provides a + very useful and portable asprintf implementation and the fopencookie + function. + - stpcpy is required + - fixme: list other requirements. + + + - With HTTP_USE_GNUTLS support for https is provided (this also + requires estream). + - With HTTP_NO_WSASTARTUP the socket initialization is not done + under Windows. This is useful if the socket layer has already + been initialized elsewhere. This also avoids the installation of + an exit handler to cleanup the socket layer. +*/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <unistd.h> + +#ifdef HAVE_W32_SYSTEM +# include <windows.h> +#else /*!HAVE_W32_SYSTEM*/ +# include <sys/types.h> +# include <sys/socket.h> +# include <sys/time.h> +# include <time.h> +# include <netinet/in.h> +# include <arpa/inet.h> +# include <netdb.h> +#endif /*!HAVE_W32_SYSTEM*/ + +#include <pth.h> + +#ifdef HTTP_USE_GNUTLS +# include <gnutls/gnutls.h> +/* For non-understandable reasons GNUTLS dropped the _t suffix from + all types. yes, ISO-C might be read as this but there are still + other name space conflicts and using _t is actually a Good + Thing. */ +typedef gnutls_session gnutls_session_t; +typedef gnutls_transport_ptr gnutls_transport_ptr_t; +#endif /*HTTP_USE_GNUTLS*/ + +#ifdef TEST +#undef USE_DNS_SRV +#endif + +#include "util.h" +#include "i18n.h" +#include "http.h" +#ifdef USE_DNS_SRV +#include "srv.h" +#else /*!USE_DNS_SRV*/ +/* If we are not compiling with SRV record support we provide stub + data structures. */ +#ifndef MAXDNAME +#define MAXDNAME 1025 +#endif +struct srventry +{ + unsigned short priority; + unsigned short weight; + unsigned short port; + int run_count; + char target[MAXDNAME]; +}; +#endif/*!USE_DNS_SRV*/ + + +#ifdef HAVE_W32_SYSTEM +#define sock_close(a) closesocket(a) +#else +#define sock_close(a) close(a) +#endif + +#ifndef EAGAIN +#define EAGAIN EWOULDBLOCK +#endif + +#define HTTP_PROXY_ENV "http_proxy" +#define MAX_LINELEN 20000 /* Max. length of a HTTP header line. */ +#define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "01234567890@" \ + "!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~" + +/* A long counter type. */ +#ifdef HAVE_STRTOULL +typedef unsigned long long longcounter_t; +#define counter_strtoul(a) strtoull ((a), NULL, 10) +#else +typedef unsigned long longcounter_t; +#define counter_strtoul(a) strtoul ((a), NULL, 10) +#endif + +#ifndef HTTP_USE_GNUTLS +typedef void * gnutls_session_t; +#endif + +static gpg_error_t do_parse_uri (parsed_uri_t uri, int only_local_part); +static int remove_escapes (char *string); +static int insert_escapes (char *buffer, const char *string, + const char *special); +static uri_tuple_t parse_tuple (char *string); +static gpg_error_t send_request (http_t hd, + const char *auth, const char *proxy); +static char *build_rel_path (parsed_uri_t uri); +static gpg_error_t parse_response (http_t hd); + +static int connect_server (const char *server, unsigned short port, + unsigned int flags, const char *srvtag); + +static ssize_t cookie_read (void *cookie, void *buffer, size_t size); +static ssize_t cookie_write (void *cookie, const void *buffer, size_t size); +static int cookie_close (void *cookie); + +static es_cookie_io_functions_t cookie_functions = + { + cookie_read, + cookie_write, + NULL, + cookie_close + }; + +struct cookie_s +{ + int fd; /* File descriptor or -1 if already closed. */ + gnutls_session_t tls_session; /* TLS session context or NULL if not used. */ + + /* The remaining content length and a flag telling whether to use + the content length. */ + longcounter_t content_length; + unsigned int content_length_valid:1; + + /* Flag to communicate with the close handler. */ + unsigned int keep_socket:1; +}; +typedef struct cookie_s *cookie_t; + + +#ifdef HTTP_USE_GNUTLS +static gpg_error_t (*tls_callback) (http_t, gnutls_session_t, int); +#endif /*HTTP_USE_GNUTLS*/ + + +/* An object to save header lines. */ +struct header_s +{ + struct header_s *next; + char *value; /* The value of the header (malloced). */ + char name[1]; /* The name of the header (canonicalized). */ +}; +typedef struct header_s *header_t; + + +/* Our handle context. */ +struct http_context_s +{ + unsigned int status_code; + int sock; + unsigned int in_data:1; + unsigned int is_http_0_9:1; + estream_t fp_read; + estream_t fp_write; + void *write_cookie; + void *read_cookie; + void *tls_context; + parsed_uri_t uri; + http_req_t req_type; + char *buffer; /* Line buffer. */ + size_t buffer_size; + unsigned int flags; + header_t headers; /* Received headers. */ +}; + + + + +#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP) + +#if GNUPG_MAJOR_VERSION == 1 +#define REQ_WINSOCK_MAJOR 1 +#define REQ_WINSOCK_MINOR 1 +#else +#define REQ_WINSOCK_MAJOR 2 +#define REQ_WINSOCK_MINOR 2 +#endif + + +static void +deinit_sockets (void) +{ + WSACleanup(); +} + +static void +init_sockets (void) +{ + static int initialized; + static WSADATA wsdata; + + if (initialized) + return; + + if ( WSAStartup( MAKEWORD (REQ_WINSOCK_MINOR, REQ_WINSOCK_MAJOR), &wsdata ) ) + { + log_error ("error initializing socket library: ec=%d\n", + (int)WSAGetLastError () ); + return; + } + if ( LOBYTE(wsdata.wVersion) != REQ_WINSOCK_MAJOR + || HIBYTE(wsdata.wVersion) != REQ_WINSOCK_MINOR ) + { + log_error ("socket library version is %x.%x - but %d.%d needed\n", + LOBYTE(wsdata.wVersion), HIBYTE(wsdata.wVersion), + REQ_WINSOCK_MAJOR, REQ_WINSOCK_MINOR); + WSACleanup(); + return; + } + atexit ( deinit_sockets ); + initialized = 1; +} +#endif /*HAVE_W32_SYSTEM && !HTTP_NO_WSASTARTUP*/ + + + +/* + * Helper function to create an HTTP header with hex encoded data. A + * new buffer is returned. This buffer is the concatenation of the + * string PREFIX, the hex-encoded DATA of length LEN and the string + * SUFFIX. On error NULL is returned and ERRNO set. + */ +static char * +make_header_line (const char *prefix, const char *suffix, + const void *data, size_t len ) +{ + static unsigned char bintoasc[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + const unsigned int *s = data; + char *buffer, *p; + + buffer = xtrymalloc (strlen (prefix) + (len+2)/3*4 + strlen (suffix) + 1); + if (!buffer) + return NULL; + p = stpcpy (buffer, prefix); + for ( ; len >= 3 ; len -= 3, s += 3 ) + { + *p++ = bintoasc[(s[0] >> 2) & 077]; + *p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077]; + *p++ = bintoasc[(((s[1]<<2)&074)|((s[2]>>6)&03))&077]; + *p++ = bintoasc[s[2]&077]; + } + if ( len == 2 ) + { + *p++ = bintoasc[(s[0] >> 2) & 077]; + *p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077]; + *p++ = bintoasc[((s[1]<<2)&074)]; + *p++ = '='; + } + else if ( len == 1 ) + { + *p++ = bintoasc[(s[0] >> 2) & 077]; + *p++ = bintoasc[(s[0] <<4)&060]; + *p++ = '='; + *p++ = '='; + } + strcpy (p, suffix); + return buffer; +} + + + + +void +http_register_tls_callback ( gpg_error_t (*cb) (http_t, void *, int) ) +{ +#ifdef HTTP_USE_GNUTLS + tls_callback = (gpg_error_t (*) (http_t, gnutls_session_t, int))cb; +#else + (void)cb; +#endif +} + + + +/* Start a HTTP retrieval and return on success in R_HD a context + pointer for completing the the request and to wait for the + response. */ +gpg_error_t +http_open (http_t *r_hd, http_req_t reqtype, const char *url, + const char *auth, unsigned int flags, const char *proxy, + void *tls_context) +{ + gpg_error_t err; + http_t hd; + + *r_hd = NULL; + + if (!(reqtype == HTTP_REQ_GET || reqtype == HTTP_REQ_POST)) + return gpg_error (GPG_ERR_INV_ARG); + + /* Make need_header default unless ignore_cl is set. We might want + to drop the need_header entirely. */ + if (!(flags & HTTP_FLAG_IGNORE_CL)) + flags |= HTTP_FLAG_NEED_HEADER; + + /* Create the handle. */ + hd = xtrycalloc (1, sizeof *hd); + if (!hd) + return gpg_error_from_syserror (); + hd->sock = -1; + hd->req_type = reqtype; + hd->flags = flags; + hd->tls_context = tls_context; + + err = http_parse_uri (&hd->uri, url); + if (!err) + err = send_request (hd, auth, proxy); + + if (err) + { + if (!hd->fp_read && !hd->fp_write && hd->sock != -1) + sock_close (hd->sock); + if (hd->fp_read) + es_fclose (hd->fp_read); + if (hd->fp_write) + es_fclose (hd->fp_write); + http_release_parsed_uri (hd->uri); + xfree (hd); + } + else + *r_hd = hd; + return err; +} + + +void +http_start_data (http_t hd) +{ + if (!hd->in_data) + { + es_fputs ("\r\n", hd->fp_write); + es_fflush (hd->fp_write); + hd->in_data = 1; + } + else + es_fflush (hd->fp_write); +} + + +gpg_error_t +http_wait_response (http_t hd) +{ + gpg_error_t err; + cookie_t cookie; + + /* Make sure that we are in the data. */ + http_start_data (hd); + + cookie = hd->write_cookie; + if (!cookie) + return gpg_error (GPG_ERR_INTERNAL); + + /* Close the write stream but keep the socket open. */ + cookie->keep_socket = 1; + es_fclose (hd->fp_write); + hd->fp_write = NULL; + hd->write_cookie = NULL; + + /* Shutdown one end of the socket is desired. As per HTTP/1.0 this + is not required but some very old servers (e.g. the original pksd + key server didn't worked without it. */ + if ((hd->flags & HTTP_FLAG_SHUTDOWN)) + shutdown (hd->sock, 1); + hd->in_data = 0; + + /* Create a new cookie and a stream for reading. */ + cookie = xtrycalloc (1, sizeof *cookie); + if (!cookie) + return gpg_error_from_syserror (); + cookie->fd = hd->sock; + if (hd->uri->use_tls) + cookie->tls_session = hd->tls_context; + + hd->read_cookie = cookie; + hd->fp_read = es_fopencookie (cookie, "r", cookie_functions); + if (!hd->fp_read) + { + xfree (cookie); + hd->read_cookie = NULL; + return gpg_error_from_syserror (); + } + + err = parse_response (hd); + return err; +} + + +/* Convenience function to send a request and wait for the response. + Closes the handle on error. If PROXY is not NULL, this value will + be used as an HTTP proxy and any enabled $http_proxy gets + ignored. */ +gpg_error_t +http_open_document (http_t *r_hd, const char *document, + const char *auth, unsigned int flags, const char *proxy, + void *tls_context) +{ + gpg_error_t err; + + err = http_open (r_hd, HTTP_REQ_GET, document, auth, flags, + proxy, tls_context); + if (err) + return err; + + err = http_wait_response (*r_hd); + if (err) + http_close (*r_hd, 0); + + return err; +} + + +void +http_close (http_t hd, int keep_read_stream) +{ + if (!hd) + return; + if (!hd->fp_read && !hd->fp_write && hd->sock != -1) + sock_close (hd->sock); + if (hd->fp_read && !keep_read_stream) + es_fclose (hd->fp_read); + if (hd->fp_write) + es_fclose (hd->fp_write); + http_release_parsed_uri (hd->uri); + while (hd->headers) + { + header_t tmp = hd->headers->next; + xfree (hd->headers->value); + xfree (hd->headers); + hd->headers = tmp; + } + xfree (hd->buffer); + xfree (hd); +} + + +estream_t +http_get_read_ptr (http_t hd) +{ + return hd?hd->fp_read:NULL; +} + +estream_t +http_get_write_ptr (http_t hd) +{ + return hd?hd->fp_write:NULL; +} + +unsigned int +http_get_status_code (http_t hd) +{ + return hd?hd->status_code:0; +} + + + +/* + * Parse an URI and put the result into the newly allocated RET_URI. + * The caller must always use release_parsed_uri() to releases the + * resources (even on error). + */ +gpg_error_t +http_parse_uri (parsed_uri_t * ret_uri, const char *uri) +{ + *ret_uri = xcalloc (1, sizeof **ret_uri + strlen (uri)); + strcpy ((*ret_uri)->buffer, uri); + return do_parse_uri (*ret_uri, 0); +} + +void +http_release_parsed_uri (parsed_uri_t uri) +{ + if (uri) + { + uri_tuple_t r, r2; + + for (r = uri->query; r; r = r2) + { + r2 = r->next; + xfree (r); + } + xfree (uri); + } +} + + +static gpg_error_t +do_parse_uri (parsed_uri_t uri, int only_local_part) +{ + uri_tuple_t *tail; + char *p, *p2, *p3, *pp; + int n; + + p = uri->buffer; + n = strlen (uri->buffer); + + /* Initialize all fields to an empty string or an empty list. */ + uri->scheme = uri->host = uri->path = p + n; + uri->port = 0; + uri->params = uri->query = NULL; + uri->use_tls = 0; + + /* A quick validity check. */ + if (strspn (p, VALID_URI_CHARS) != n) + return gpg_error (GPG_ERR_BAD_URI); /* Invalid characters found. */ + + if (!only_local_part) + { + /* Find the scheme. */ + if (!(p2 = strchr (p, ':')) || p2 == p) + return gpg_error (GPG_ERR_BAD_URI); /* No scheme. */ + *p2++ = 0; + for (pp=p; *pp; pp++) + *pp = tolower (*(unsigned char*)pp); + uri->scheme = p; + if (!strcmp (uri->scheme, "http")) + uri->port = 80; +#ifdef HTTP_USE_GNUTLS + else if (!strcmp (uri->scheme, "https")) + { + uri->port = 443; + uri->use_tls = 1; + } +#endif + else + return gpg_error (GPG_ERR_INV_URI); /* Unsupported scheme */ + + p = p2; + + /* Find the hostname */ + if (*p != '/') + return gpg_error (GPG_ERR_INV_URI); /* Does not start with a slash. */ + + p++; + if (*p == '/') /* There seems to be a hostname. */ + { + p++; + if ((p2 = strchr (p, '/'))) + *p2++ = 0; + + /* Check for username/password encoding */ + if ((p3 = strchr (p, '@'))) + { + uri->auth = p; + *p3++ = '\0'; + p = p3; + } + + for (pp=p; *pp; pp++) + *pp = tolower (*(unsigned char*)pp); + uri->host = p; + if ((p3 = strchr (p, ':'))) + { + *p3++ = 0; + uri->port = atoi (p3); + } + + uri->host = p; + if ((n = remove_escapes (uri->host)) < 0) + return gpg_error (GPG_ERR_BAD_URI); + if (n != strlen (p)) + return gpg_error (GPG_ERR_BAD_URI); /* Hostname incudes a Nul. */ + p = p2 ? p2 : NULL; + } + } /* End global URI part. */ + + /* Parse the pathname part */ + if (!p || !*p) + return 0; /* We don't have a path. Okay. */ + + /* TODO: Here we have to check params. */ + + /* Do we have a query part? */ + if ((p2 = strchr (p, '?'))) + *p2++ = 0; + + uri->path = p; + if ((n = remove_escapes (p)) < 0) + return gpg_error (GPG_ERR_BAD_URI); + if (n != strlen (p)) + return gpg_error (GPG_ERR_BAD_URI); /* Path includes a Nul. */ + p = p2 ? p2 : NULL; + + if (!p || !*p) + return 0; /* We don't have a query string. Okay. */ + + /* Now parse the query string. */ + tail = &uri->query; + for (;;) + { + uri_tuple_t elem; + + if ((p2 = strchr (p, '&'))) + *p2++ = 0; + if (!(elem = parse_tuple (p))) + return gpg_error (GPG_ERR_BAD_URI); + *tail = elem; + tail = &elem->next; + + if (!p2) + break; /* Ready. */ + p = p2; + } + + return 0; +} + + +/* + * Remove all %xx escapes; this is done in-place. Returns: New length + * of the string. + */ +static int +remove_escapes (char *string) +{ + int n = 0; + unsigned char *p, *s; + + for (p = s = (unsigned char*)string; *s; s++) + { + if (*s == '%') + { + if (s[1] && s[2] && isxdigit (s[1]) && isxdigit (s[2])) + { + s++; + *p = *s >= '0' && *s <= '9' ? *s - '0' : + *s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10; + *p <<= 4; + s++; + *p |= *s >= '0' && *s <= '9' ? *s - '0' : + *s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10; + p++; + n++; + } + else + { + *p++ = *s++; + if (*s) + *p++ = *s++; + if (*s) + *p++ = *s++; + if (*s) + *p = 0; + return -1; /* Bad URI. */ + } + } + else + { + *p++ = *s; + n++; + } + } + *p = 0; /* Make sure to keep a string terminator. */ + return n; +} + + +static int +insert_escapes (char *buffer, const char *string, + const char *special) +{ + const unsigned char *s = (const unsigned char*)string; + int n = 0; + + for (; *s; s++) + { + if (strchr (VALID_URI_CHARS, *s) && !strchr (special, *s)) + { + if (buffer) + *(unsigned char*)buffer++ = *s; + n++; + } + else + { + if (buffer) + { + sprintf (buffer, "%%%02X", *s); + buffer += 3; + } + n += 3; + } + } + return n; +} + + +/* Allocate a new string from STRING using standard HTTP escaping as + well as escaping of characters given in SPECIALS. A common pattern + for SPECIALS is "%;?&=". However it depends on the needs, for + example "+" and "/: often needs to be escaped too. Returns NULL on + failure and sets ERRNO. */ +char * +http_escape_string (const char *string, const char *specials) +{ + int n; + char *buf; + + n = insert_escapes (NULL, string, specials); + buf = xtrymalloc (n+1); + if (buf) + { + insert_escapes (buf, string, specials); + buf[n] = 0; + } + return buf; +} + + + +static uri_tuple_t +parse_tuple (char *string) +{ + char *p = string; + char *p2; + int n; + uri_tuple_t tuple; + + if ((p2 = strchr (p, '='))) + *p2++ = 0; + if ((n = remove_escapes (p)) < 0) + return NULL; /* Bad URI. */ + if (n != strlen (p)) + return NULL; /* Name with a Nul in it. */ + tuple = xtrycalloc (1, sizeof *tuple); + if (!tuple) + return NULL; /* Out of core. */ + tuple->name = p; + if (!p2) /* We have only the name, so we assume an empty value string. */ + { + tuple->value = p + strlen (p); + tuple->valuelen = 0; + tuple->no_value = 1; /* Explicitly mark that we have seen no '='. */ + } + else /* Name and value. */ + { + if ((n = remove_escapes (p2)) < 0) + { + xfree (tuple); + return NULL; /* Bad URI. */ + } + tuple->value = p2; + tuple->valuelen = n; + } + return tuple; +} + + +/* + * Send a HTTP request to the server + * Returns 0 if the request was successful + */ +static gpg_error_t +send_request (http_t hd, const char *auth, const char *proxy) +{ + gnutls_session_t tls_session; + gpg_error_t err; + const char *server; + char *request, *p; + unsigned short port; + const char *http_proxy = NULL; + char *proxy_authstr = NULL; + char *authstr = NULL; + int save_errno; + cookie_t cookie; + + + tls_session = hd->tls_context; + if (hd->uri->use_tls && !tls_session) + { + log_error ("TLS requested but no GNUTLS context provided\n"); + return gpg_error (GPG_ERR_INTERNAL); + } + + server = *hd->uri->host ? hd->uri->host : "localhost"; + port = hd->uri->port ? hd->uri->port : 80; + + if ( (proxy && *proxy) + || ( (hd->flags & HTTP_FLAG_TRY_PROXY) + && (http_proxy = getenv (HTTP_PROXY_ENV)) + && *http_proxy )) + { + parsed_uri_t uri; + + if (proxy) + http_proxy = proxy; + + err = http_parse_uri (&uri, http_proxy); + if (err) + { + log_error ("invalid HTTP proxy (%s): %s\n", + http_proxy, gpg_strerror (err)); + http_release_parsed_uri (uri); + return gpg_error (GPG_ERR_CONFIGURATION); + + } + + if (uri->auth) + { + remove_escapes (uri->auth); + proxy_authstr = make_header_line ("Proxy-Authorization: Basic ", + "\r\n", + uri->auth, strlen(uri->auth)); + if (!proxy_authstr) + { + err = gpg_error_from_syserror (); + http_release_parsed_uri (uri); + return err; + } + } + + hd->sock = connect_server (*uri->host ? uri->host : "localhost", + uri->port ? uri->port : 80, + hd->flags, hd->uri->scheme); + save_errno = errno; + http_release_parsed_uri (uri); + } + else + { + hd->sock = connect_server (server, port, hd->flags, hd->uri->scheme); + save_errno = errno; + } + + if (hd->sock == -1) + { + xfree (proxy_authstr); + return (save_errno + ? gpg_error_from_errno (save_errno) + : gpg_error (GPG_ERR_NOT_FOUND)); + } + +#ifdef HTTP_USE_GNUTLS + if (hd->uri->use_tls) + { + int rc; + + gnutls_transport_set_ptr (tls_session, (gnutls_transport_ptr_t)hd->sock); + do + { + rc = gnutls_handshake (tls_session); + } + while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN); + if (rc < 0) + { + log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc)); + xfree (proxy_authstr); + return gpg_error (GPG_ERR_NETWORK); + } + + if (tls_callback) + { + err = tls_callback (hd, tls_session, 0); + if (err) + { + log_info ("TLS connection authentication failed: %s\n", + gpg_strerror (err)); + xfree (proxy_authstr); + return err; + } + } + } +#endif /*HTTP_USE_GNUTLS*/ + + if (auth || hd->uri->auth) + { + char *myauth; + + if (auth) + { + myauth = xtrystrdup (auth); + if (!myauth) + { + xfree (proxy_authstr); + return gpg_error_from_syserror (); + } + remove_escapes (myauth); + } + else + { + remove_escapes (hd->uri->auth); + myauth = hd->uri->auth; + } + + authstr = make_header_line ("Authorization: Basic %s", "\r\n", + myauth, strlen (myauth)); + if (auth) + xfree (myauth); + + if (!authstr) + { + xfree (proxy_authstr); + return gpg_error_from_syserror (); + } + } + + p = build_rel_path (hd->uri); + if (!p) + return gpg_error_from_syserror (); + + if (http_proxy && *http_proxy) + { + request = es_asprintf + ("%s http://%s:%hu%s%s HTTP/1.0\r\n%s%s", + hd->req_type == HTTP_REQ_GET ? "GET" : + hd->req_type == HTTP_REQ_HEAD ? "HEAD" : + hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS", + server, port, *p == '/' ? "" : "/", p, + authstr ? authstr : "", + proxy_authstr ? proxy_authstr : ""); + } + else + { + char portstr[35]; + + if (port == 80) + *portstr = 0; + else + snprintf (portstr, sizeof portstr, ":%u", port); + + request = es_asprintf + ("%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s", + hd->req_type == HTTP_REQ_GET ? "GET" : + hd->req_type == HTTP_REQ_HEAD ? "HEAD" : + hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS", + *p == '/' ? "" : "/", p, server, portstr, + authstr? authstr:""); + } + xfree (p); + if (!request) + { + err = gpg_error_from_syserror (); + xfree (authstr); + xfree (proxy_authstr); + return err; + } + + /* First setup estream so that we can write even the first line + using estream. This is also required for the sake of gnutls. */ + cookie = xtrycalloc (1, sizeof *cookie); + if (!cookie) + { + err = gpg_error_from_syserror (); + goto leave; + } + cookie->fd = hd->sock; + hd->write_cookie = cookie; + if (hd->uri->use_tls) + cookie->tls_session = tls_session; + hd->fp_write = es_fopencookie (cookie, "w", cookie_functions); + if (!hd->fp_write) + { + xfree (cookie); + hd->write_cookie = NULL; + err = gpg_error_from_syserror (); + } + else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) + err = gpg_error_from_syserror (); + else + err = 0; + + leave: + es_free (request); + xfree (authstr); + xfree (proxy_authstr); + + return err; +} + + +/* + * Build the relative path from the parsed URI. Minimal + * implementation. May return NULL in case of memory failure; errno + * is then set accordingly. + */ +static char * +build_rel_path (parsed_uri_t uri) +{ + uri_tuple_t r; + char *rel_path, *p; + int n; + + /* Count the needed space. */ + n = insert_escapes (NULL, uri->path, "%;?&"); + /* TODO: build params. */ + for (r = uri->query; r; r = r->next) + { + n++; /* '?'/'&' */ + n += insert_escapes (NULL, r->name, "%;?&="); + if (!r->no_value) + { + n++; /* '=' */ + n += insert_escapes (NULL, r->value, "%;?&="); + } + } + n++; + + /* Now allocate and copy. */ + p = rel_path = xtrymalloc (n); + if (!p) + return NULL; + n = insert_escapes (p, uri->path, "%;?&"); + p += n; + /* TODO: add params. */ + for (r = uri->query; r; r = r->next) + { + *p++ = r == uri->query ? '?' : '&'; + n = insert_escapes (p, r->name, "%;?&="); + p += n; + if (!r->no_value) + { + *p++ = '='; + /* TODO: Use valuelen. */ + n = insert_escapes (p, r->value, "%;?&="); + p += n; + } + } + *p = 0; + return rel_path; +} + + +/* Transform a header name into a standard capitalized format; e.g. + "Content-Type". Conversion stops at the colon. As usual we don't + use the localized versions of ctype.h. */ +static void +capitalize_header_name (char *name) +{ + int first = 1; + + for (; *name && *name != ':'; name++) + { + if (*name == '-') + first = 1; + else if (first) + { + if (*name >= 'a' && *name <= 'z') + *name = *name - 'a' + 'A'; + first = 0; + } + else if (*name >= 'A' && *name <= 'Z') + *name = *name - 'A' + 'a'; + } +} + + +/* Store an HTTP header line in LINE away. Line continuation is + supported as well as merging of headers with the same name. This + function may modify LINE. */ +static gpg_error_t +store_header (http_t hd, char *line) +{ + size_t n; + char *p, *value; + header_t h; + + n = strlen (line); + if (n && line[n-1] == '\n') + { + line[--n] = 0; + if (n && line[n-1] == '\r') + line[--n] = 0; + } + if (!n) /* we are never called to hit this. */ + return gpg_error (GPG_ERR_BUG); + if (*line == ' ' || *line == '\t') + { + /* Continuation. This won't happen too often as it is not + recommended. We use a straightforward implementaion. */ + if (!hd->headers) + return gpg_error (GPG_ERR_PROTOCOL_VIOLATION); + n += strlen (hd->headers->value); + p = xtrymalloc (n+1); + if (!p) + return gpg_error_from_syserror (); + strcpy (stpcpy (p, hd->headers->value), line); + xfree (hd->headers->value); + hd->headers->value = p; + return 0; + } + + capitalize_header_name (line); + p = strchr (line, ':'); + if (!p) + return gpg_error (GPG_ERR_PROTOCOL_VIOLATION); + *p++ = 0; + while (*p == ' ' || *p == '\t') + p++; + value = p; + + for (h=hd->headers; h; h = h->next) + if ( !strcmp (h->name, line) ) + break; + if (h) + { + /* We have already seen a line with that name. Thus we assume + it is a comma separated list and merge them. */ + p = xtrymalloc (strlen (h->value) + 1 + strlen (value)+ 1); + if (!p) + return gpg_error_from_syserror (); + strcpy (stpcpy (stpcpy (p, h->value), ","), value); + xfree (h->value); + h->value = p; + return 0; + } + + /* Append a new header. */ + h = xtrymalloc (sizeof *h + strlen (line)); + if (!h) + return gpg_error_from_syserror (); + strcpy (h->name, line); + h->value = xtrymalloc (strlen (value)+1); + if (!h->value) + { + xfree (h); + return gpg_error_from_syserror (); + } + strcpy (h->value, value); + h->next = hd->headers; + hd->headers = h; + + return 0; +} + + +/* Return the header NAME from the last response. The returned value + is valid as along as HD has not been closed and no othe request has + been send. If the header was not found, NULL is returned. Name + must be canonicalized, that is the first letter of each dash + delimited part must be uppercase and all other letters lowercase. + Note that the context must have been opened with the + HTTP_FLAG_NEED_HEADER. */ +const char * +http_get_header (http_t hd, const char *name) +{ + header_t h; + + for (h=hd->headers; h; h = h->next) + if ( !strcmp (h->name, name) ) + return h->value; + return NULL; +} + + + +/* + * Parse the response from a server. + * Returns: Errorcode and sets some files in the handle + */ +static gpg_error_t +parse_response (http_t hd) +{ + char *line, *p, *p2; + size_t maxlen, len; + cookie_t cookie = hd->read_cookie; + const char *s; + + /* Delete old header lines. */ + while (hd->headers) + { + header_t tmp = hd->headers->next; + xfree (hd->headers->value); + xfree (hd->headers); + hd->headers = tmp; + } + + /* Wait for the status line. */ + do + { + maxlen = MAX_LINELEN; + len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen); + line = hd->buffer; + if (!line) + return gpg_error_from_syserror (); /* Out of core. */ + if (!maxlen) + return gpg_error (GPG_ERR_TRUNCATED); /* Line has been truncated. */ + if (!len) + return gpg_error (GPG_ERR_EOF); + if ( (hd->flags & HTTP_FLAG_LOG_RESP) ) + log_info ("RESP: `%.*s'\n", + (int)strlen(line)-(*line&&line[1]?2:0),line); + } + while (!*line); + + if ((p = strchr (line, '/'))) + *p++ = 0; + if (!p || strcmp (line, "HTTP")) + return 0; /* Assume http 0.9. */ + + if ((p2 = strpbrk (p, " \t"))) + { + *p2++ = 0; + p2 += strspn (p2, " \t"); + } + if (!p2) + return 0; /* Also assume http 0.9. */ + p = p2; + /* TODO: Add HTTP version number check. */ + if ((p2 = strpbrk (p, " \t"))) + *p2++ = 0; + if (!isdigit ((unsigned int)p[0]) || !isdigit ((unsigned int)p[1]) + || !isdigit ((unsigned int)p[2]) || p[3]) + { + /* Malformed HTTP status code - assume http 0.9. */ + hd->is_http_0_9 = 1; + hd->status_code = 200; + return 0; + } + hd->status_code = atoi (p); + + /* Skip all the header lines and wait for the empty line. */ + do + { + maxlen = MAX_LINELEN; + len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen); + line = hd->buffer; + if (!line) + return gpg_error_from_syserror (); /* Out of core. */ + /* Note, that we can silently ignore truncated lines. */ + if (!len) + return gpg_error (GPG_ERR_EOF); + /* Trim line endings of empty lines. */ + if ((*line == '\r' && line[1] == '\n') || *line == '\n') + *line = 0; + if ( (hd->flags & HTTP_FLAG_LOG_RESP) ) + log_info ("RESP: `%.*s'\n", + (int)strlen(line)-(*line&&line[1]?2:0),line); + if ( (hd->flags & HTTP_FLAG_NEED_HEADER) && *line ) + { + gpg_error_t err = store_header (hd, line); + if (err) + return err; + } + } + while (len && *line); + + cookie->content_length_valid = 0; + if (!(hd->flags & HTTP_FLAG_IGNORE_CL)) + { + s = http_get_header (hd, "Content-Length"); + if (s) + { + cookie->content_length_valid = 1; + cookie->content_length = counter_strtoul (s); + } + } + + return 0; +} + +#if 0 +static int +start_server () +{ + struct sockaddr_in mya; + struct sockaddr_in peer; + int fd, client; + fd_set rfds; + int addrlen; + int i; + + if ((fd = socket (AF_INET, SOCK_STREAM, 0)) == -1) + { + log_error ("socket() failed: %s\n", strerror (errno)); + return -1; + } + i = 1; + if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (byte *) & i, sizeof (i))) + log_info ("setsockopt(SO_REUSEADDR) failed: %s\n", strerror (errno)); + + mya.sin_family = AF_INET; + memset (&mya.sin_addr, 0, sizeof (mya.sin_addr)); + mya.sin_port = htons (11371); + + if (bind (fd, (struct sockaddr *) &mya, sizeof (mya))) + { + log_error ("bind to port 11371 failed: %s\n", strerror (errno)); + sock_close (fd); + return -1; + } + + if (listen (fd, 5)) + { + log_error ("listen failed: %s\n", strerror (errno)); + sock_close (fd); + return -1; + } + + for (;;) + { + FD_ZERO (&rfds); + FD_SET (fd, &rfds); + + if (select (fd + 1, &rfds, NULL, NULL, NULL) <= 0) + continue; /* ignore any errors */ + + if (!FD_ISSET (fd, &rfds)) + continue; + + addrlen = sizeof peer; + client = accept (fd, (struct sockaddr *) &peer, &addrlen); + if (client == -1) + continue; /* oops */ + + log_info ("connect from %s\n", inet_ntoa (peer.sin_addr)); + + fflush (stdout); + fflush (stderr); + if (!fork ()) + { + int c; + FILE *fp; + + fp = fdopen (client, "r"); + while ((c = getc (fp)) != EOF) + putchar (c); + fclose (fp); + exit (0); + } + sock_close (client); + } + + + return 0; +} +#endif + +/* Actually connect to a server. Returns the file descripto or -1 on + error. ERRNO is set on error. */ +static int +connect_server (const char *server, unsigned short port, + unsigned int flags, const char *srvtag) +{ + int sock = -1; + int srvcount = 0; + int hostfound = 0; + int srv, connected; + int last_errno = 0; + struct srventry *serverlist = NULL; + +#ifdef HAVE_W32_SYSTEM + unsigned long inaddr; + +#ifndef HTTP_NO_WSASTARTUP + init_sockets (); +#endif + /* Win32 gethostbyname doesn't handle IP addresses internally, so we + try inet_addr first on that platform only. */ + inaddr = inet_addr(server); + if ( inaddr != INADDR_NONE ) + { + struct sockaddr_in addr; + + memset(&addr,0,sizeof(addr)); + + sock = socket(AF_INET,SOCK_STREAM,0); + if ( sock==INVALID_SOCKET ) + { + log_error("error creating socket: ec=%d\n",(int)WSAGetLastError()); + return -1; + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + memcpy (&addr.sin_addr,&inaddr,sizeof(inaddr)); + + if (!connect (sock,(struct sockaddr *)&addr,sizeof(addr)) ) + return sock; + sock_close(sock); + return -1; + } +#endif /*HAVE_W32_SYSTEM*/ + +#ifdef USE_DNS_SRV + /* Do the SRV thing */ + if ((flags & HTTP_FLAG_TRY_SRV) && srvtag) + { + /* We're using SRV, so append the tags. */ + if (1+strlen (srvtag) + 6 + strlen (server) + 1 <= MAXDNAME) + { + char srvname[MAXDNAME]; + + stpcpy (stpcpy (stpcpy (stpcpy (srvname,"_"), srvtag), + "._tcp."), server); + srvcount = getsrv (srvname, &serverlist); + } + } +#else /*!USE_DNS_SRV*/ + (void)flags; + (void)srvtag; +#endif /*!USE_DNS_SRV*/ + + if (!serverlist) + { + /* Either we're not using SRV, or the SRV lookup failed. Make + up a fake SRV record. */ + serverlist = xtrycalloc (1, sizeof *serverlist); + if (!serverlist) + return -1; /* Out of core. */ + serverlist->port = port; + strncpy (serverlist->target, server, MAXDNAME); + serverlist->target[MAXDNAME-1] = '\0'; + srvcount = 1; + } + +#ifdef HAVE_GETADDRINFO + connected = 0; + for (srv=0; srv < srvcount && !connected; srv++) + { + struct addrinfo hints, *res, *ai; + char portstr[35]; + + sprintf (portstr, "%hu", port); + memset (&hints, 0, sizeof (hints)); + hints.ai_socktype = SOCK_STREAM; + if (getaddrinfo (serverlist[srv].target, portstr, &hints, &res)) + continue; /* Not found - try next one. */ + hostfound = 1; + + for (ai = res; ai && !connected; ai = ai->ai_next) + { + if (sock != -1) + sock_close (sock); + sock = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock == -1) + { + int save_errno = errno; + log_error ("error creating socket: %s\n", strerror (errno)); + freeaddrinfo (res); + xfree (serverlist); + errno = save_errno; + return -1; + } + + if (connect (sock, ai->ai_addr, ai->ai_addrlen)) + last_errno = errno; + else + connected = 1; + } + freeaddrinfo (res); + } +#else /* !HAVE_GETADDRINFO */ + connected = 0; + for (srv=0; srv < srvcount && !connected; srv++) + { + int i; + struct hostent *host = NULL; + struct sockaddr_in addr; + + /* Note: This code is not thread-safe. */ + + memset (&addr, 0, sizeof (addr)); + host = gethostbyname (serverlist[srv].target); + if (!host) + continue; + hostfound = 1; + + if (sock != -1) + sock_close (sock); + sock = socket (host->h_addrtype, SOCK_STREAM, 0); + if (sock == -1) + { + log_error (_("error creating socket: %s\n"), strerror (errno)); + xfree (serverlist); + return -1; + } + + addr.sin_family = host->h_addrtype; + if (addr.sin_family != AF_INET) + { + log_error ("unknown address family for `%s'\n", + serverlist[srv].target); + xfree (serverlist); + return -1; + } + addr.sin_port = htons (serverlist[srv].port); + if (host->h_length != 4) + { + log_error ("illegal address length for `%s'\n", + serverlist[srv].target); + xfree (serverlist); + return -1; + } + + /* Try all A records until one responds. */ + for (i = 0; host->h_addr_list[i] && !connected; i++) + { + memcpy (&addr.sin_addr, host->h_addr_list[i], host->h_length); + if (connect (sock, (struct sockaddr *) &addr, sizeof (addr))) + last_errno = errno; + else + { + connected = 1; + break; + } + } + } +#endif /* !HAVE_GETADDRINFO */ + + xfree (serverlist); + + if (!connected) + { +#ifdef HAVE_W32_SYSTEM + log_error ("can't connect to `%s': %s%sec=%d\n", + server, + hostfound? "":_("host not found"), + hostfound? "":" - ", (int)WSAGetLastError()); +#else + log_error ("can't connect to `%s': %s\n", + server, + hostfound? strerror (last_errno):"host not found"); +#endif + if (sock != -1) + sock_close (sock); + errno = last_errno; + return -1; + } + return sock; +} + + + + +/* Read handler for estream. */ +static ssize_t +cookie_read (void *cookie, void *buffer, size_t size) +{ + cookie_t c = cookie; + int nread; + + if (c->content_length_valid) + { + if (!c->content_length) + return 0; /* EOF */ + if (c->content_length < size) + size = c->content_length; + } + +#ifdef HTTP_USE_GNUTLS + if (c->tls_session) + { + again: + nread = gnutls_record_recv (c->tls_session, buffer, size); + if (nread < 0) + { + if (nread == GNUTLS_E_INTERRUPTED) + goto again; + if (nread == GNUTLS_E_AGAIN) + { + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 50000; + select (0, NULL, NULL, NULL, &tv); + goto again; + } + if (nread == GNUTLS_E_REHANDSHAKE) + goto again; /* A client is allowed to just ignore this request. */ + log_info ("TLS network read failed: %s\n", gnutls_strerror (nread)); + errno = EIO; + return -1; + } + } + else +#endif /*HTTP_USE_GNUTLS*/ + { + do + { + nread = pth_read (c->fd, buffer, size); + } + while (nread == -1 && errno == EINTR); + } + + if (c->content_length_valid && nread > 0) + { + if (nread < c->content_length) + c->content_length -= nread; + else + c->content_length = 0; + } + + return nread; +} + +/* Write handler for estream. */ +static ssize_t +cookie_write (void *cookie, const void *buffer, size_t size) +{ + cookie_t c = cookie; + int nwritten = 0; + +#ifdef HTTP_USE_GNUTLS + if (c->tls_session) + { + int nleft = size; + while (nleft > 0) + { + nwritten = gnutls_record_send (c->tls_session, buffer, nleft); + if (nwritten <= 0) + { + if (nwritten == GNUTLS_E_INTERRUPTED) + continue; + if (nwritten == GNUTLS_E_AGAIN) + { + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 50000; + select (0, NULL, NULL, NULL, &tv); + continue; + } + log_info ("TLS network write failed: %s\n", + gnutls_strerror (nwritten)); + errno = EIO; + return -1; + } + nleft -= nwritten; + buffer += nwritten; + } + } + else +#endif /*HTTP_USE_GNUTLS*/ + { + do + { + nwritten = pth_write (c->fd, buffer, size); + } + while (nwritten == -1 && errno == EINTR); + } + + return nwritten; +} + +/* Close handler for estream. */ +static int +cookie_close (void *cookie) +{ + cookie_t c = cookie; + + if (!c) + return 0; + +#ifdef HTTP_USE_GNUTLS + if (c->tls_session && !c->keep_socket) + { + /* This indicates that the read end has been closed. */ + gnutls_bye (c->tls_session, GNUTLS_SHUT_RDWR); + } +#endif /*HTTP_USE_GNUTLS*/ + if (c->fd != -1 && !c->keep_socket) + sock_close (c->fd); + + xfree (c); + return 0; +} + + + + +/**** Test code ****/ +#ifdef TEST + +static gpg_error_t +verify_callback (http_t hd, void *tls_context, int reserved) +{ + log_info ("verification of certificates skipped\n"); + return 0; +} + + + +/* static void */ +/* my_gnutls_log (int level, const char *text) */ +/* { */ +/* fprintf (stderr, "gnutls:L%d: %s", level, text); */ +/* } */ + +int +main (int argc, char **argv) +{ + int rc; + parsed_uri_t uri; + uri_tuple_t r; + http_t hd; + int c; + gnutls_session_t tls_session = NULL; +#ifdef HTTP_USE_GNUTLS + gnutls_certificate_credentials certcred; + const int certprio[] = { GNUTLS_CRT_X509, 0 }; +#endif /*HTTP_USE_GNUTLS*/ + header_t hdr; + + es_init (); + log_set_prefix ("http-test", 1 | 4); + if (argc == 1) + { + /*start_server (); */ + return 0; + } + + if (argc != 2) + { + fprintf (stderr, "usage: http-test uri\n"); + return 1; + } + argc--; + argv++; + +#ifdef HTTP_USE_GNUTLS + rc = gnutls_global_init (); + if (rc) + log_error ("gnutls_global_init failed: %s\n", gnutls_strerror (rc)); + rc = gnutls_certificate_allocate_credentials (&certcred); + if (rc) + log_error ("gnutls_certificate_allocate_credentials failed: %s\n", + gnutls_strerror (rc)); +/* rc = gnutls_certificate_set_x509_trust_file */ +/* (certcred, "ca.pem", GNUTLS_X509_FMT_PEM); */ +/* if (rc) */ +/* log_error ("gnutls_certificate_set_x509_trust_file failed: %s\n", */ +/* gnutls_strerror (rc)); */ + rc = gnutls_init (&tls_session, GNUTLS_CLIENT); + if (rc) + log_error ("gnutls_init failed: %s\n", gnutls_strerror (rc)); + rc = gnutls_set_default_priority (tls_session); + if (rc) + log_error ("gnutls_set_default_priority failed: %s\n", + gnutls_strerror (rc)); + rc = gnutls_certificate_type_set_priority (tls_session, certprio); + if (rc) + log_error ("gnutls_certificate_type_set_priority failed: %s\n", + gnutls_strerror (rc)); + rc = gnutls_credentials_set (tls_session, GNUTLS_CRD_CERTIFICATE, certcred); + if (rc) + log_error ("gnutls_credentials_set failed: %s\n", gnutls_strerror (rc)); +/* gnutls_global_set_log_function (my_gnutls_log); */ +/* gnutls_global_set_log_level (4); */ + + http_register_tls_callback (verify_callback); +#endif /*HTTP_USE_GNUTLS*/ + + rc = http_parse_uri (&uri, *argv); + if (rc) + { + log_error ("`%s': %s\n", *argv, gpg_strerror (rc)); + http_release_parsed_uri (uri); + return 1; + } + + printf ("Scheme: %s\n", uri->scheme); + printf ("Host : %s\n", uri->host); + printf ("Port : %u\n", uri->port); + printf ("Path : %s\n", uri->path); + for (r = uri->params; r; r = r->next) + { + printf ("Params: %s", r->name); + if (!r->no_value) + { + printf ("=%s", r->value); + if (strlen (r->value) != r->valuelen) + printf (" [real length=%d]", (int) r->valuelen); + } + putchar ('\n'); + } + for (r = uri->query; r; r = r->next) + { + printf ("Query : %s", r->name); + if (!r->no_value) + { + printf ("=%s", r->value); + if (strlen (r->value) != r->valuelen) + printf (" [real length=%d]", (int) r->valuelen); + } + putchar ('\n'); + } + http_release_parsed_uri (uri); + uri = NULL; + + rc = http_open_document (&hd, *argv, NULL, + HTTP_FLAG_NEED_HEADER, + NULL, tls_session); + if (rc) + { + log_error ("can't get `%s': %s\n", *argv, gpg_strerror (rc)); + return 1; + } + log_info ("open_http_document succeeded; status=%u\n", + http_get_status_code (hd)); + for (hdr = hd->headers; hdr; hdr = hdr->next) + printf ("HDR: %s: %s\n", hdr->name, hdr->value); + switch (http_get_status_code (hd)) + { + case 200: + while ((c = es_getc (http_get_read_ptr (hd))) != EOF) + putchar (c); + break; + case 301: + case 302: + printf ("Redirected to `%s'\n", http_get_header (hd, "Location")); + break; + } + http_close (hd, 0); + +#ifdef HTTP_USE_GNUTLS + gnutls_deinit (tls_session); + gnutls_certificate_free_credentials (certcred); + gnutls_global_deinit (); +#endif /*HTTP_USE_GNUTLS*/ + + return 0; +} +#endif /*TEST*/ + +/* +Local Variables: +compile-command: "gcc -I.. -I../gl -DTEST -DHAVE_CONFIG_H -Wall -O2 -g -o http-test http.c -L. -lcommon -L../jnlib -ljnlib -lgcrypt -lpth -lgnutls" +End: +*/ |