diff --git a/TODO b/TODO index 7157a9883..55380b5fb 100644 --- a/TODO +++ b/TODO @@ -27,7 +27,11 @@ Important > gpg: keyblock resource `/home/jam/.gnupg/pubring.gpg': file open error > gpg: OOPS in close enum_keyblocks - ignored - + > Indeed, comparing zero to 0xfe returns 2, not -something, and this is +> the problem. This seems to fix it, but I don't know how you want to +> handle this. +> + I'll better write a autoconf test as memcmp is used all over the place. Needed ------ @@ -69,4 +73,6 @@ Nice to have * change the fake_data stuff to mpi_set_opaque * How about letting something like 'gpg --version -v', list the effective options. + * Stats about used random numbers. + diff --git a/g10/options.skel b/g10/options.skel index 4b6a3fce6..3e6777bb7 100644 --- a/g10/options.skel +++ b/g10/options.skel @@ -62,5 +62,6 @@ lock-once # you will be asked in such a case whether GnuPG should try to # import the key from that server (server do syncronize with each # others and DNS Round-Robin may give you a random server each time). -#keyserver keys.pgp.net +# Use "host -l pgp.net | grep www" to figure out a keyserver. +#keyserver wwwkeys.eu.pgp.net diff --git a/include/http.h b/include/http.h new file mode 100644 index 000000000..a439b4b81 --- /dev/null +++ b/include/http.h @@ -0,0 +1,62 @@ +/* http.h - HTTP protocol handler + * Copyright (C) 1999 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ +#ifndef G10_HTTP_H +#define G10_HTTP_H 1 + +#include "iobuf.h" + +struct uri_tuple { + struct uri_tuple *next; + const char *name; /* a pointer into name */ + char *value; /* a pointer to value (a Nul is always appended) */ + size_t valuelen; /* and the real length of the value */ + /* because the value may contain embedded Nuls */ +}; +typedef struct uri_tuple *URI_TUPLE; + +struct parsed_uri { + /* all these pointers point into buffer; most stuff is not escaped */ + char *scheme; /* pointer to the scheme string (lowercase) */ + char *host; /* host (converted to lowercase) */ + ushort port; /* port (always set if the host is set) */ + char *path; /* the path */ + URI_TUPLE params; /* ";xxxxx" */ + URI_TUPLE query; /* "?xxx=yyy" */ + char buffer[1]; /* buffer which holds a (modified) copy of the URI */ +}; +typedef struct parsed_uri *PARSED_URI; + +struct http_context { + int initialized; + unsigned int status_code; + int socket; + IOBUF fp_read; + IOBUF fp_write; + int is_http_0_9; + PARSED_URI uri; + byte *buffer; /* line buffer */ + unsigned buffer_size; +}; +typedef struct http_context *HTTP_HD; + +int open_http_document( HTTP_HD hd, const char *document, unsigned int flags ); +void close_http_document( HTTP_HD hd ); + +#endif /*G10_HTTP_H*/ diff --git a/util/http.c b/util/http.c new file mode 100644 index 000000000..949cdb322 --- /dev/null +++ b/util/http.c @@ -0,0 +1,625 @@ +/* http.c - HTTP protocol handler + * Copyright (C) 1999 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" +#include "iobuf.h" +#include "i18n.h" + +#include "http.h" + +#define MAX_LINELEN 20000 /* max. length of a HTTP line */ +#define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "01234567890@" \ + "!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~" + +#ifndef EAGAIN + #define EAGAIN EWOULDBLOCK +#endif + +static int parse_uri( PARSED_URI *ret_uri, const char *uri ); +static void release_parsed_uri( PARSED_URI uri ); +static int do_parse_uri( PARSED_URI uri, int only_local_part ); +static int remove_escapes( byte *string ); +static int insert_escapes( byte *buffer, const byte *string, + const byte *special ); +static URI_TUPLE parse_tuple( byte *string ); +static int send_request( HTTP_HD hd ); +static byte *build_rel_path( PARSED_URI uri ); +static int parse_response( HTTP_HD hd ); + +static int connect_server( const char *server, ushort port ); +static int write_server( int socket, const char *data, size_t length ); + + + +int +open_http_document( HTTP_HD hd, const char *document, unsigned int flags ) +{ + int rc; + + if( flags ) + return G10ERR_INV_ARG; + + /* initialize the handle */ + memset( hd, 0, sizeof *hd ); + hd->socket = -1; + + rc = parse_uri( &hd->uri, document ); + if( rc ) + goto failure; + + rc = send_request( hd ); + if( rc ) + goto failure; + + hd->fp_read = iobuf_fdopen( hd->socket , "r" ); + if( !hd->fp_read ) + goto failure; + + rc = parse_response( hd ); + if( rc == -1 ) { /* no response from server */ + /* Hmmm, should we set some errro variable or map errors */ + goto failure; + } + + if( !rc ) + hd->is_http_0_9 = 1; + else + hd->status_code = rc ; + + hd->initialized = 1; + return 0; + + failure: + if( !hd->fp_read && !hd->fp_write ) + close( hd->socket ); + iobuf_close( hd->fp_read ); + iobuf_close( hd->fp_write); + release_parsed_uri( hd->uri ); + + return rc; +} + +void +close_http_document( HTTP_HD hd ) +{ + if( !hd || !hd->initialized ) + return; + if( !hd->fp_read && !hd->fp_write ) + close( hd->socket ); + iobuf_close( hd->fp_read ); + iobuf_close( hd->fp_write ); + release_parsed_uri( hd->uri ); + m_free( hd->buffer ); + hd->initialized = 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 an error). + */ +static int +parse_uri( PARSED_URI *ret_uri, const char *uri ) +{ + *ret_uri = m_alloc_clear( sizeof(**ret_uri) + strlen(uri) ); + strcpy( (*ret_uri)->buffer, uri ); + return do_parse_uri( *ret_uri, 0 ); +} + +static void +release_parsed_uri( PARSED_URI uri ) +{ + if( uri ) + { + URI_TUPLE r, r2; + + for( r = uri->query; r; r = r2 ) { + r2 = r->next; + m_free( r ); + } + m_free( uri ); + } +} + +static int +do_parse_uri( PARSED_URI uri, int only_local_part ) +{ + URI_TUPLE *tail; + char *p, *p2, *p3; + 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; + + /* a quick validity check */ + if( strspn( p, VALID_URI_CHARS) != n ) + return G10ERR_BAD_URI; /* invalid characters found */ + + if( !only_local_part ) { + /* find the scheme */ + if( !(p2 = strchr( p, ':' ) ) || p2 == p ) + return G10ERR_BAD_URI; /* No scheme */ + *p2++ = 0; + strlwr( p ); + uri->scheme = p; + if( !strcmp( uri->scheme, "http" ) ) + ; + else if( !strcmp( uri->scheme, "x-hkp" ) ) /* same as HTTP */ + ; + else + return G10ERR_INVALID_URI; /* Unsupported scheme */ + + p = p2; + + /* find the hostname */ + if( *p != '/' ) + return G10ERR_INVALID_URI; /* does not start with a slash */ + + p++; + if( *p == '/' ) { /* there seems to be a hostname */ + p++; + if( (p2 = strchr(p, '/')) ) + *p2++ = 0; + strlwr( p ); + uri->host = p; + if( (p3=strchr( p, ':' )) ) { + *p3++ = 0; + uri->port = atoi( p3 ); + } + else + uri->port = 80; + uri->host = p; + if( (n = remove_escapes( uri->host )) < 0 ) + return G10ERR_BAD_URI; + if( n != strlen( p ) ) + return G10ERR_BAD_URI; /* hostname with a Nul in it */ + p = p2 ? p2 : NULL; + } + } /* end global URI part */ + + /* parse the pathname part */ + if( !p || !*p ) /* we don't have a path */ + return 0; /* and this is okay */ + + /* fixme: 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 G10ERR_BAD_URI; + if( n != strlen( p ) ) + return G10ERR_BAD_URI; /* path with a Nul in it */ + p = p2 ? p2 : NULL; + + if( !p || !*p ) /* we don't have a query string */ + return 0; /* okay */ + + /* now parse the query string */ + tail = &uri->query; + for(;;) { + URI_TUPLE elem; + + if( (p2 = strchr( p, '&' )) ) + *p2++ = 0; + if( !(elem = parse_tuple( p )) ) + return G10ERR_BAD_URI; + *tail = elem; + tail = &elem->next; + + if( !p2 ) + break; /* ready */ + p = p2; + } + + return 0; +} + + + +/**************** + * Remove all %xx escapes; this is done inplace. + * Returns: new length of the string. + */ +static int +remove_escapes( byte *string ) +{ + int n = 0; + byte *p, *s; + + for(p=s=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; /* always keep a string terminator */ + return n; +} + + +static int +insert_escapes( byte *buffer, const byte *string, const byte *special ) +{ + int n = 0; + + for( ; *string; string++ ) { + if( strchr( VALID_URI_CHARS, *string ) + && !strchr( special, *string ) ) { + if( buffer ) + *buffer++ = *string; + n++; + } + else { + if( buffer ) { + sprintf( buffer, "%02X", *string ); + buffer += 3; + } + n += 3; + } + } + return n; +} + + + + + +static URI_TUPLE +parse_tuple( byte *string ) +{ + byte *p = string; + byte *p2; + int n; + URI_TUPLE 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 = m_alloc_clear( sizeof *tuple ); + 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; + } + else { /* name and value */ + if( (n = remove_escapes( p2 )) < 0 ) { + m_free( 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 int +send_request( HTTP_HD hd ) +{ + const byte *server; + byte *request, *p; + ushort port; + int rc; + + server = *hd->uri->host? hd->uri->host : "localhost"; + port = hd->uri->port? hd->uri->port : 80; + + hd->socket = connect_server( server, port ); + if( hd->socket == -1 ) + return G10ERR_NETWORK; + + p = build_rel_path( hd->uri ); + request = m_alloc( strlen(p) + 20 ); + sprintf( request, "GET %s%s HTTP/1.0\r\n\r\n", *p == '/'? "":"/", p ); + m_free(p); + + rc = write_server( hd->socket, request, strlen(request) ); + m_free( request ); + shutdown( hd->socket, 1 ); + + return rc; +} + + + + +/**************** + * Build the relative path from the parsed URI. + * Minimal implementation. + */ +static byte* +build_rel_path( PARSED_URI uri ) +{ + URI_TUPLE r; + byte *rel_path, *p; + int n; + + /* count the needed space */ + n = insert_escapes( NULL, uri->path, "%;?&" ); + /* fixme: add params */ + for( r=uri->query; r; r = r->next ) { + n++; /* '?'/'&' */ + n += insert_escapes( NULL, r->name, "%;?&=" ); + n++; /* '='*/ + n += insert_escapes( NULL, r->value, "%;?&=" ); + } + n++; + + /* now allocate and copy */ + p = rel_path = m_alloc( n ); + n = insert_escapes( p, uri->path, "%;?&" ); + p += n; + /* fixme: add params */ + for( r=uri->query; r; r = r->next ) { + *p++ = r == uri->query? '?':'&'; + n = insert_escapes( p, r->name, "%;?&=" ); + p += n; + *p++ = '='; + /* fixme: use valuelen */ + n = insert_escapes( p, r->value, "%;?&=" ); + p += n; + } + *p = 0; + return rel_path; +} + + + +/*********************** + * Parse the response from a server. + * Returns: -1 for no response or error + * 0 for a http 0.9 response + * nnn http 1.0 status code + */ +static int +parse_response( HTTP_HD hd ) +{ + byte *line, *p, *p2; + int status; + unsigned maxlen, len; + + /* Wait for the status line */ + do { + maxlen = MAX_LINELEN; + len = iobuf_read_line( hd->fp_read, &hd->buffer, + &hd->buffer_size, &maxlen ); + line = hd->buffer; + if( !maxlen ) + return -1; /* line has been truncated */ + if( !len ) + return -1; /* eof */ + } 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; /* assume http 0.9 */ + p = p2; + /* fixme: add HTTP version number check here */ + if( (p2 = strpbrk( p, " \t" ) ) ) + *p2++ = 0; + if( !isdigit(p[0]) || !isdigit(p[1]) || !isdigit(p[2]) || p[3] ) + return 0; /* malformed HTTP statuscode - assume HTTP 0.9 */ + status = atoi( p ); + + /* skip all the header lines and wait for the empty line */ + do { + maxlen = MAX_LINELEN; + len = iobuf_read_line( hd->fp_read, &hd->buffer, + &hd->buffer_size, &maxlen ); + line = hd->buffer; + /* we ignore truncated lines */ + if( !len ) + return -1; /* eof */ + /* time lineendings */ + if( (*line == '\r' && line[1] == '\n') || *line == '\n' ) + *line = 0; + } while( len && *line ); + + return status; +} + + + + + + + + + + +static int +connect_server( const char *server, ushort port ) +{ + struct sockaddr_in addr; + struct hostent *host; + int sd; + + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + host = gethostbyname((char*)server); + if( !host ) + return -1; + + addr.sin_addr = *(struct in_addr*)host->h_addr; + + sd = socket(AF_INET, SOCK_STREAM, 0); + if( sd == -1 ) + return -1; + + if( connect( sd, (struct sockaddr *)&addr, sizeof addr) == -1 ) { + close(sd); + return -1; + } + + return sd; +} + + +static int +write_server( int socket, const char *data, size_t length ) +{ + int nleft, nwritten; + + nleft = length; + while( nleft > 0 ) { + nwritten = write( socket, data, nleft ); + if( nwritten == -1 ) { + if( errno == EINTR ) + continue; + if( errno == EAGAIN ) { + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 50000; + select(0, NULL, NULL, NULL, &tv); + continue; + } + return G10ERR_NETWORK; + } + nleft -=nwritten; + data += nwritten; + } + + return 0; +} + + +/**** Test code ****/ +#ifdef TEST + +int +main(int argc, char **argv) +{ + int rc; + PARSED_URI uri; + URI_TUPLE r; + struct http_context hd; + int c; + + log_set_name("http-test"); + if( argc != 2 ) { + fprintf(stderr,"usage: http-test uri\n"); + return 1; + } + argc--; argv++; + + rc = parse_uri( &uri, *argv ); + if( rc ) { + log_error("`%s': %s\n", *argv, g10_errstr(rc)); + 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=%s", r->name, 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=%s", r->name, r->value ); + if( strlen( r->value ) != r->valuelen ) + printf(" [real length=%d]", (int)r->valuelen ); + putchar('\n'); + } + release_parsed_uri( uri ); uri = NULL; + + rc = open_http_document( &hd, *argv, 0 ); + if( rc ) { + log_error("can't get `%s': %s\n", *argv, g10_errstr(rc)); + return 1; + } + log_info("open_http_document succeeded; status=%u\n", hd.status_code ); + while( (c=iobuf_get( hd.fp_read)) != -1 ) + putchar(c); + close_http_document( &hd ); + return 0; +} +#endif /*TEST*/