diff --git a/common/ChangeLog b/common/ChangeLog index 647f7d582..4d07a4919 100644 --- a/common/ChangeLog +++ b/common/ChangeLog @@ -1,3 +1,25 @@ +2011-02-08 Werner Koch + + * http.c (connect_server): Add arg R_HOST_NOT_FOUND. + +2011-02-07 Werner Koch + + * http.c (my_socket_new, my_socket_ref, my_socket_unref): New. + (cookie_close, cookie_read, cookie_write, http_close, _http_open) + (send_request): Replace use of an socket integer by the new socket + object. + (_http_raw_connect): New. + (fp_onclose_notification): New. + (_http_raw_connect, _http_wait_response, http_close): Register and + unregister this notification. + * http.h (http_raw_connect): New. + + * http.h (parsed_uri_s): Add field IS_OPAQUE. + (http_req_t): Add HTTP_REQ_OPAQUE. + * http.c (do_parse_uri): Parse unknown schemes into PATH. + (my_socket_new, my_socket_ref, my_socket_unref): New. + (send_request): Simplify save_errno stuff. + 2011-02-03 Werner Koch * status.h (STATUS_DECRYPTION_INFO): New. diff --git a/common/estream.c b/common/estream.c index bc820513e..a73d1f2c4 100644 --- a/common/estream.c +++ b/common/estream.c @@ -3041,9 +3041,14 @@ es_fclose (estream_t stream) already registered notification; for this to work the value of FNC and FNC_VALUE must be the same as with the registration and FNC_VALUE must be a unique value. No error will be returned if - MODE is 0. Unregistered should only be used in the error case - because it may not remove memory internall allocated for the - onclose handler. + MODE is 0. + + FIXME: I think the next comment is not anymore correct: + Unregister should only be used in the error case because it may not + be able to remove memory internally allocated for the onclose + handler. + + FIXME: Unregister is not thread safe. The notification will be called right before the stream is closed. It may not call any estream function for STREAM, neither direct nor diff --git a/common/http.c b/common/http.c index f8628e622..7df84576f 100644 --- a/common/http.c +++ b/common/http.c @@ -161,13 +161,25 @@ 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); + unsigned int flags, const char *srvtag, + int *r_host_not_found); static gpg_error_t write_server (int sock, const char *data, size_t length); 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); + +/* A socket object used to a allow ref counting of sockets. */ +struct my_socket_s +{ + int fd; /* The actual socket - shall never be -1. */ + int refcount; /* Number of references to this socket. */ +}; +typedef struct my_socket_s *my_socket_t; + + +/* Cookie function structure and cookie object. */ static es_cookie_io_functions_t cookie_functions = { cookie_read, @@ -178,8 +190,8 @@ static es_cookie_io_functions_t cookie_functions = struct cookie_s { - /* File descriptor or -1 if already closed. */ - int fd; + /* Socket object or NULL if already closed. */ + my_socket_t sock; /* TLS session context or NULL if not used. */ gnutls_session_t tls_session; @@ -213,7 +225,7 @@ typedef struct header_s *header_t; struct http_context_s { unsigned int status_code; - int sock; + my_socket_t sock; unsigned int in_data:1; unsigned int is_http_0_9:1; estream_t fp_read; @@ -279,6 +291,77 @@ init_sockets (void) #endif /*HAVE_W32_SYSTEM && !HTTP_NO_WSASTARTUP*/ +/* Create a new socket object. Returns NULL and closes FD if not + enough memory is available. */ +static my_socket_t +my_socket_new (int fd) +{ + my_socket_t so; + + so = xtrymalloc (sizeof *so); + if (!so) + { + int save_errno = errno; + sock_close (fd); + gpg_err_set_errno (save_errno); + return NULL; + } + so->fd = fd; + so->refcount = 1; + /* log_debug ("my_socket_new(%d): object %p for fd %d created\n", */ + /* lnr, so, so->fd); */ + return so; +} +/* #define my_socket_new(a) _my_socket_new ((a),__LINE__) */ + +/* Bump up the reference counter for the socket object SO. */ +static my_socket_t +my_socket_ref (my_socket_t so) +{ + so->refcount++; + /* log_debug ("my_socket_ref(%d): object %p for fd %d refcount now %d\n", */ + /* lnr, so, so->fd, so->refcount); */ + return so; +} +/* #define my_socket_ref(a) _my_socket_ref ((a),__LINE__) */ + +/* Bump down the reference counter for the socket object SO. If SO + has no more references, close the socket and release the + object. */ +static void +my_socket_unref (my_socket_t so) +{ + if (so) + { + so->refcount--; + /* log_debug ("my_socket_unref(%d): object %p for fd %d ref now %d\n", */ + /* lnr, so, so->fd, so->refcount); */ + if (!so->refcount) + { + sock_close (so->fd); + xfree (so); + } + } +} +/* #define my_socket_unref(a) _my_socket_unref ((a),__LINE__) */ + + +/* This notification function is called by estream whenever stream is + closed. Its purpose is to mark the the closing in the handle so + that a http_close won't accidentally close the estream. The function + http_close removes this notification so that it won't be called if + http_close was used before an es_fclose. */ +static void +fp_onclose_notification (estream_t stream, void *opaque) +{ + http_t hd = opaque; + + if (hd->fp_read && hd->fp_read == stream) + hd->fp_read = NULL; + else if (hd->fp_write && hd->fp_write == stream) + hd->fp_write = NULL; +} + /* * Helper function to create an HTTP header with hex encoded data. A @@ -343,7 +426,7 @@ http_register_tls_callback ( gpg_error_t (*cb) (http_t, void *, int) ) /* 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. */ + 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, @@ -362,7 +445,6 @@ _http_open (http_t *r_hd, http_req_t reqtype, const char *url, 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; @@ -373,8 +455,7 @@ _http_open (http_t *r_hd, http_req_t reqtype, const char *url, if (err) { - if (!hd->fp_read && !hd->fp_write && hd->sock != -1) - sock_close (hd->sock); + my_socket_unref (hd->sock); if (hd->fp_read) es_fclose (hd->fp_read); if (hd->fp_write) @@ -387,6 +468,105 @@ _http_open (http_t *r_hd, http_req_t reqtype, const char *url, } +/* This function is useful to connect to a generic TCP service using + this http abstraction layer. This has the advantage of providing + service tags and an estream interface. */ +gpg_error_t +_http_raw_connect (http_t *r_hd, const char *server, unsigned short port, + unsigned int flags, const char *srvtag, + gpg_err_source_t errsource) +{ + gpg_error_t err = 0; + int sock; + http_t hd; + cookie_t cookie; + int hnf; + + *r_hd = NULL; + + /* Create the handle. */ + hd = xtrycalloc (1, sizeof *hd); + if (!hd) + return gpg_error_from_syserror (); + hd->req_type = HTTP_REQ_OPAQUE; + hd->flags = flags; + + /* Connect. */ + sock = connect_server (server, port, hd->flags, srvtag, &hnf); + if (sock == -1) + { + err = gpg_err_make (errsource, (hnf? GPG_ERR_UNKNOWN_HOST + :gpg_err_code_from_syserror ())); + xfree (hd); + return err; + } + hd->sock = my_socket_new (sock); + if (!hd->sock) + { + err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); + xfree (hd); + return err; + } + + /* Setup estreams for reading and writing. */ + cookie = xtrycalloc (1, sizeof *cookie); + if (!cookie) + { + err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); + goto leave; + } + cookie->sock = my_socket_ref (hd->sock); + hd->fp_write = es_fopencookie (cookie, "w", cookie_functions); + if (!hd->fp_write) + { + err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); + my_socket_unref (cookie->sock); + xfree (cookie); + goto leave; + } + hd->write_cookie = cookie; /* Cookie now owned by FP_WRITE. */ + + cookie = xtrycalloc (1, sizeof *cookie); + if (!cookie) + { + err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); + goto leave; + } + cookie->sock = my_socket_ref (hd->sock); + hd->fp_read = es_fopencookie (cookie, "r", cookie_functions); + if (!hd->fp_read) + { + err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); + my_socket_unref (cookie->sock); + xfree (cookie); + goto leave; + } + hd->read_cookie = cookie; /* Cookie now owned by FP_READ. */ + + /* Register close notification to interlock the use of es_fclose in + http_close and in user code. */ + err = es_onclose (hd->fp_write, 1, fp_onclose_notification, hd); + if (!err) + err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd); + + leave: + if (err) + { + if (hd->fp_read) + es_fclose (hd->fp_read); + if (hd->fp_write) + es_fclose (hd->fp_write); + my_socket_unref (hd->sock); + xfree (hd); + } + else + *r_hd = hd; + return err; +} + + + + void http_start_data (http_t hd) { @@ -410,12 +590,12 @@ _http_wait_response (http_t hd, gpg_err_source_t errsource) /* Make sure that we are in the data. */ http_start_data (hd); - /* Close the write stream but keep the socket open. */ + /* Close the write stream. Note that the reference counted socket + object keeps the actual system socket open. */ cookie = hd->write_cookie; if (!cookie) return gpg_err_make (errsource, GPG_ERR_INTERNAL); - cookie->keep_socket = 1; es_fclose (hd->fp_write); hd->fp_write = NULL; /* The close has released the cookie and thus we better set it to NULL. */ @@ -425,14 +605,14 @@ _http_wait_response (http_t hd, gpg_err_source_t errsource) 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); + shutdown (hd->sock->fd, 1); hd->in_data = 0; /* Create a new cookie and a stream for reading. */ cookie = xtrycalloc (1, sizeof *cookie); if (!cookie) return gpg_err_make (errsource, gpg_err_code_from_syserror ()); - cookie->fd = hd->sock; + cookie->sock = my_socket_ref (hd->sock); if (hd->uri->use_tls) cookie->tls_session = hd->tls_context; @@ -440,12 +620,18 @@ _http_wait_response (http_t hd, gpg_err_source_t errsource) hd->fp_read = es_fopencookie (cookie, "r", cookie_functions); if (!hd->fp_read) { + err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); + my_socket_unref (cookie->sock); xfree (cookie); hd->read_cookie = NULL; - return gpg_err_make (errsource, gpg_err_code_from_syserror ()); + return err; } err = parse_response (hd); + + if (!err) + err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd); + return err; } @@ -480,8 +666,15 @@ 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); + + /* First remove the close notifications for the streams. */ + if (hd->fp_read) + es_onclose (hd->fp_read, 0, fp_onclose_notification, hd); + if (hd->fp_write) + es_onclose (hd->fp_write, 0, fp_onclose_notification, hd); + + /* Now we can close the streams. */ + my_socket_unref (hd->sock); if (hd->fp_read && !keep_read_stream) es_fclose (hd->fp_read); if (hd->fp_write) @@ -577,6 +770,7 @@ do_parse_uri (parsed_uri_t uri, int only_local_part, int no_scheme_check) uri->params = uri->query = NULL; uri->use_tls = 0; uri->is_http = 0; + uri->opaque = 0; /* A quick validity check. */ if (strspn (p, VALID_URI_CHARS) != n) @@ -614,14 +808,9 @@ do_parse_uri (parsed_uri_t uri, int only_local_part, int no_scheme_check) p = p2; - /* Find the hostname */ - if (*p != '/') - return GPG_ERR_INV_URI; /* Does not start with a slash. */ - - p++; - if (*p == '/') /* There seems to be a hostname. */ + if (*p == '/' && p[1] == '/' ) /* There seems to be a hostname. */ { - p++; + p += 2; if ((p2 = strchr (p, '/'))) *p2++ = 0; @@ -659,6 +848,15 @@ do_parse_uri (parsed_uri_t uri, int only_local_part, int no_scheme_check) return GPG_ERR_BAD_URI; /* Hostname incudes a Nul. */ p = p2 ? p2 : NULL; } + else if (uri->is_http) + return GPG_ERR_INV_URI; /* No Leading double slash for HTTP. */ + else + { + uri->opaque = 1; + uri->path = p; + return 0; + } + } /* End global URI part. */ /* Parse the pathname part */ @@ -888,7 +1086,8 @@ send_request (http_t hd, const char *auth, const char *http_proxy = NULL; char *proxy_authstr = NULL; char *authstr = NULL; - int save_errno; + int sock; + int hnf; tls_session = hd->tls_context; if (hd->uri->use_tls && !tls_session) @@ -906,6 +1105,7 @@ send_request (http_t hd, const char *auth, && *http_proxy )) { parsed_uri_t uri; + int save_errno; if (proxy) http_proxy = proxy; @@ -932,32 +1132,42 @@ send_request (http_t hd, const char *auth, } } - hd->sock = connect_server (*uri->host ? uri->host : "localhost", - uri->port ? uri->port : 80, - hd->flags, srvtag); + sock = connect_server (*uri->host ? uri->host : "localhost", + uri->port ? uri->port : 80, + hd->flags, srvtag, &hnf); save_errno = errno; http_release_parsed_uri (uri); + if (sock == -1) + gpg_err_set_errno (save_errno); } else { - hd->sock = connect_server (server, port, hd->flags, srvtag); - save_errno = errno; + sock = connect_server (server, port, hd->flags, srvtag, &hnf); } - if (hd->sock == -1) + if (sock == -1) { xfree (proxy_authstr); - return gpg_err_make (errsource, (save_errno - ? gpg_err_code_from_errno (save_errno) - : GPG_ERR_NOT_FOUND)); + return gpg_err_make (errsource, (hnf? GPG_ERR_UNKNOWN_HOST + : gpg_err_code_from_syserror ())); } + hd->sock = my_socket_new (sock); + if (!hd->sock) + { + xfree (proxy_authstr); + return gpg_err_make (errsource, gpg_err_code_from_syserror ()); + } + + #ifdef HTTP_USE_GNUTLS if (hd->uri->use_tls) { int rc; - gnutls_transport_set_ptr (tls_session, (gnutls_transport_ptr_t)hd->sock); + my_socket_ref (hd->sock); + gnutls_transport_set_ptr (tls_session, + (gnutls_transport_ptr_t)(hd->sock->fd)); do { rc = gnutls_handshake (tls_session); @@ -1069,7 +1279,7 @@ send_request (http_t hd, const char *auth, err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); goto leave; } - cookie->fd = hd->sock; + cookie->sock = my_socket_ref (hd->sock); hd->write_cookie = cookie; if (hd->uri->use_tls) cookie->tls_session = tls_session; @@ -1078,6 +1288,7 @@ send_request (http_t hd, const char *auth, if (!hd->fp_write) { err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); + my_socket_unref (cookie->sock); xfree (cookie); hd->write_cookie = NULL; } @@ -1469,7 +1680,7 @@ start_server () error. ERRNO is set on error. */ static int connect_server (const char *server, unsigned short port, - unsigned int flags, const char *srvtag) + unsigned int flags, const char *srvtag, int *r_host_not_found) { int sock = -1; int srvcount = 0; @@ -1483,6 +1694,7 @@ connect_server (const char *server, unsigned short port, /* Not currently using the flags */ (void)flags; + *r_host_not_found = 0; #ifdef HAVE_W32_SYSTEM #ifndef HTTP_NO_WSASTARTUP @@ -1655,6 +1867,8 @@ connect_server (const char *server, unsigned short port, server, hostfound? strerror (last_errno):"host not found"); #endif + if (!hostfound) + *r_host_not_found = 1; if (sock != -1) sock_close (sock); gpg_err_set_errno (last_errno); @@ -1758,12 +1972,12 @@ cookie_read (void *cookie, void *buffer, size_t size) do { #ifdef HAVE_PTH - nread = pth_read (c->fd, buffer, size); + nread = pth_read (c->sock->fd, buffer, size); #elif defined(HAVE_W32_SYSTEM) /* Under Windows we need to use recv for a socket. */ - nread = recv (c->fd, buffer, size, 0); + nread = recv (c->sock->fd, buffer, size, 0); #else - nread = read (c->fd, buffer, size); + nread = read (c->sock->fd, buffer, size); #endif } while (nread == -1 && errno == EINTR); @@ -1819,7 +2033,7 @@ cookie_write (void *cookie, const void *buffer, size_t size) else #endif /*HTTP_USE_GNUTLS*/ { - if ( write_server (c->fd, buffer, size) ) + if ( write_server (c->sock->fd, buffer, size) ) { gpg_err_set_errno (EIO); nwritten = -1; @@ -1844,28 +2058,29 @@ cookie_close (void *cookie) if (c->tls_session && !c->keep_socket) { gnutls_bye (c->tls_session, GNUTLS_SHUT_RDWR); + my_socket_unref (c->sock); } #endif /*HTTP_USE_GNUTLS*/ - if (c->fd != -1 && !c->keep_socket) - sock_close (c->fd); + if (c->sock && !c->keep_socket) + my_socket_unref (c->sock); xfree (c); return 0; } - /**** Test code ****/ #ifdef TEST +#ifdef HTTP_USE_GNUTLS static gpg_error_t verify_callback (http_t hd, void *tls_context, int reserved) { log_info ("verification of certificates skipped\n"); return 0; } - +#endif /*HTTP_USE_GNUTLS*/ /* static void */ @@ -1938,7 +2153,7 @@ main (int argc, char **argv) http_register_tls_callback (verify_callback); #endif /*HTTP_USE_GNUTLS*/ - rc = http_parse_uri (&uri, *argv, 0); + rc = http_parse_uri (&uri, *argv, 1); if (rc) { log_error ("`%s': %s\n", *argv, gpg_strerror (rc)); @@ -1946,35 +2161,41 @@ main (int argc, char **argv) } 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) + if (uri->opaque) + printf ("Value : %s\n", uri->path); + else { - 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'); + printf ("Auth : %s\n", uri->auth? uri->auth:"[none]"); + 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, 0, NULL, tls_session); + rc = http_open_document (&hd, *argv, NULL, 0, NULL, tls_session, NULL, NULL); if (rc) { log_error ("can't get `%s': %s\n", *argv, gpg_strerror (rc)); @@ -2010,6 +2231,6 @@ main (int argc, char **argv) /* 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" +compile-command: "gcc -I.. -I../gl -DTEST -DHAVE_CONFIG_H -Wall -O2 -g -o http-test http.c -L. -lcommon -lgcrypt -lpth -lgnutls" End: */ diff --git a/common/http.h b/common/http.h index 50c478c84..7a11b84f3 100644 --- a/common/http.h +++ b/common/http.h @@ -40,7 +40,8 @@ struct parsed_uri_s char *scheme; /* Pointer to the scheme string (always lowercase). */ unsigned int is_http:1; /* This is a HTTP style URI. */ unsigned int use_tls:1; /* Whether TLS should be used. */ - char *auth; /* username/password for basic auth */ + unsigned int opaque:1;/* Unknown scheme; PATH has the rest. */ + char *auth; /* username/password for basic auth. */ char *host; /* Host (converted to lowercase). */ unsigned short port; /* Port (always set if the host is set). */ char *path; /* Path. */ @@ -54,7 +55,8 @@ typedef enum { HTTP_REQ_GET = 1, HTTP_REQ_HEAD = 2, - HTTP_REQ_POST = 3 + HTTP_REQ_POST = 3, + HTTP_REQ_OPAQUE = 4 /* Internal use. */ } http_req_t; @@ -79,6 +81,13 @@ gpg_error_t _http_parse_uri (parsed_uri_t *ret_uri, const char *uri, void http_release_parsed_uri (parsed_uri_t uri); +gpg_error_t _http_raw_connect (http_t *r_hd, + const char *server, unsigned short port, + unsigned int flags, const char *srvtag, + gpg_err_source_t errsource); +#define http_raw_connect(a,b,c,d,e) \ + _http_raw_connect ((a),(b),(c),(d),(e), GPG_ERR_SOURCE_DEFAULT) + gpg_error_t _http_open (http_t *r_hd, http_req_t reqtype, const char *url, const char *auth, diff --git a/dirmngr/ChangeLog b/dirmngr/ChangeLog index 3a01b97af..1e575e1e9 100644 --- a/dirmngr/ChangeLog +++ b/dirmngr/ChangeLog @@ -1,3 +1,9 @@ +2011-02-08 Werner Koch + + * server.c (cmd_ks_fetch): New. + * ks-action.c (ks_action_fetch): New. + * ks-engine-finger.c: New. + 2011-02-03 Werner Koch * Makefile.am (dirmngr_LDADD): Remove -llber. diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am index d5aecc7ab..a030f3861 100644 --- a/dirmngr/Makefile.am +++ b/dirmngr/Makefile.am @@ -50,7 +50,8 @@ dirmngr_SOURCES = dirmngr.c dirmngr.h server.c crlcache.c crlfetch.c \ ldapserver.h ldapserver.c certcache.c certcache.h \ cdb.h cdblib.c ldap.c misc.c dirmngr-err.h w32-ldap-help.h \ ocsp.c ocsp.h validate.c validate.h ldap-wrapper.h $(ldap_url) \ - ks-action.c ks-action.h ks-engine.h ks-engine-hkp.c + ks-action.c ks-action.h ks-engine.h \ + ks-engine-hkp.c ks-engine-finger.c if USE_LDAPWRAPPER dirmngr_SOURCES += ldap-wrapper.c diff --git a/dirmngr/ks-action.c b/dirmngr/ks-action.c index 50f0d5063..dff49979f 100644 --- a/dirmngr/ks-action.c +++ b/dirmngr/ks-action.c @@ -132,7 +132,7 @@ ks_action_get (ctrl_t ctrl, strlist_t patterns, estream_t outfp) else { err = copy_stream (infp, outfp); - /* Reading from the keyserver should nver fail, thus + /* Reading from the keyserver should never fail, thus return this error. */ es_fclose (infp); infp = NULL; @@ -149,6 +149,49 @@ ks_action_get (ctrl_t ctrl, strlist_t patterns, estream_t outfp) } +/* Retrive keys from URL and write the result to the provided output + stream OUTFP. */ +gpg_error_t +ks_action_fetch (ctrl_t ctrl, const char *url, estream_t outfp) +{ + gpg_error_t err = 0; + estream_t infp; + parsed_uri_t parsed_uri; /* The broken down URI. */ + + if (!url) + return gpg_error (GPG_ERR_INV_URI); + + err = http_parse_uri (&parsed_uri, url, 1); + if (err) + return err; + + if (parsed_uri->is_http) + { + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + } + else if (!parsed_uri->opaque) + { + err = gpg_error (GPG_ERR_INV_URI); + } + else if (!strcmp (parsed_uri->scheme, "finger")) + { + err = ks_finger_get (ctrl, parsed_uri, &infp); + if (!err) + { + err = copy_stream (infp, outfp); + /* Reading from the finger serrver should not fail, thus + return this error. */ + es_fclose (infp); + } + } + else + err = gpg_error (GPG_ERR_INV_URI); + + http_release_parsed_uri (parsed_uri); + return err; +} + + /* Send an OpenPGP key to all keyservers. The key in {DATA,DATALEN} is expected in OpenPGP binary transport format. */ diff --git a/dirmngr/ks-action.h b/dirmngr/ks-action.h index b3bd3fc46..bba53bc04 100644 --- a/dirmngr/ks-action.h +++ b/dirmngr/ks-action.h @@ -22,6 +22,7 @@ gpg_error_t ks_action_search (ctrl_t ctrl, strlist_t patterns, estream_t outfp); gpg_error_t ks_action_get (ctrl_t ctrl, strlist_t patterns, estream_t outfp); +gpg_error_t ks_action_fetch (ctrl_t ctrl, const char *url, estream_t outfp); gpg_error_t ks_action_put (ctrl_t ctrl, const void *data, size_t datalen); diff --git a/dirmngr/ks-engine-finger.c b/dirmngr/ks-engine-finger.c new file mode 100644 index 000000000..c54e34351 --- /dev/null +++ b/dirmngr/ks-engine-finger.c @@ -0,0 +1,101 @@ +/* ks-engine-finger.c - HKP keyserver engine + * Copyright (C) 2011 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 3 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, see . + */ + +#include + +#include +#include +#include +#include + +#include "dirmngr.h" +#include "misc.h" +#include "userids.h" +#include "ks-engine.h" + + +/* Get the key from URI which is expected to specify a finger scheme. + On success R_FP has an open stream to read the data. */ +gpg_error_t +ks_finger_get (ctrl_t ctrl, parsed_uri_t uri, estream_t *r_fp) +{ + gpg_error_t err; + estream_t fp; + char *server; + char *name; + http_t http; + + (void)ctrl; + *r_fp = NULL; + + if (strcmp (uri->scheme, "finger") || !uri->opaque || !uri->path) + return gpg_error (GPG_ERR_INV_ARG); + + name = xtrystrdup (uri->path); + if (!name) + return gpg_error_from_syserror (); + + server = strchr (name, '@'); + if (!server) + { + err = gpg_error (GPG_ERR_INV_URI); + xfree (name); + return err; + } + *server++ = 0; + + err = http_raw_connect (&http, server, 79, 0, NULL); + if (err) + { + xfree (name); + return err; + } + + fp = http_get_write_ptr (http); + if (!fp) + { + err = gpg_error (GPG_ERR_INTERNAL); + http_close (http, 0); + xfree (name); + return err; + } + + if (es_fputs (name, fp) || es_fputs ("\r\n", fp) || es_fflush (fp)) + { + err = gpg_error_from_syserror (); + http_close (http, 0); + xfree (name); + return err; + } + xfree (name); + es_fclose (fp); + + fp = http_get_read_ptr (http); + if (!fp) + { + err = gpg_error (GPG_ERR_INTERNAL); + http_close (http, 0); + return err; + } + + http_close (http, 1 /* Keep read ptr. */); + + *r_fp = fp; + return 0; +} diff --git a/dirmngr/ks-engine.h b/dirmngr/ks-engine.h index 304fc4d1a..50f42be4b 100644 --- a/dirmngr/ks-engine.h +++ b/dirmngr/ks-engine.h @@ -31,6 +31,9 @@ gpg_error_t ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, gpg_error_t ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen); +/*-- ks-engine-finger.c --*/ +gpg_error_t ks_finger_get (ctrl_t ctrl, parsed_uri_t uri, estream_t *r_fp); + #endif /*DIRMNGR_KS_ENGINE_H*/ diff --git a/dirmngr/server.c b/dirmngr/server.c index 86b21b67b..403a13692 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -1541,6 +1541,34 @@ cmd_ks_get (assuan_context_t ctx, char *line) } +static const char hlp_ks_fetch[] = + "KS_FETCH \n" + "\n" + "Get the key(s) from URL."; +static gpg_error_t +cmd_ks_fetch (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + estream_t outfp; + + /* No options for now. */ + line = skip_options (line); + + /* Setup an output stream and perform the get. */ + outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); + if (!outfp) + err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); + else + { + err = ks_action_fetch (ctrl, line, outfp); + es_fclose (outfp); + } + + return leave_cmd (ctx, err); +} + + static const char hlp_ks_put[] = "KS_PUT\n" @@ -1742,6 +1770,7 @@ register_commands (assuan_context_t ctx) { "KEYSERVER", cmd_keyserver, hlp_keyserver }, { "KS_SEARCH", cmd_ks_search, hlp_ks_search }, { "KS_GET", cmd_ks_get, hlp_ks_get }, + { "KS_FETCH", cmd_ks_fetch, hlp_ks_fetch }, { "KS_PUT", cmd_ks_put, hlp_ks_put }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr }, diff --git a/g10/ChangeLog b/g10/ChangeLog index 8d850a65f..8594110f5 100644 --- a/g10/ChangeLog +++ b/g10/ChangeLog @@ -1,3 +1,8 @@ +2011-02-08 Werner Koch + + * call-dirmngr.c (gpg_dirmngr_ks_fetch): New. + * keyserver.c (keyserver_fetch): Rewrite to use dirmngr. + 2011-02-07 Werner Koch * seskey.c (encode_md_value): Truncate to MDLEN and not to QBYTES diff --git a/g10/call-dirmngr.c b/g10/call-dirmngr.c index 10c0e568c..09ade4eb9 100644 --- a/g10/call-dirmngr.c +++ b/g10/call-dirmngr.c @@ -354,7 +354,7 @@ gpg_dirmngr_ks_search (ctrl_t ctrl, const char *searchstr, -/* Data callback for the KS_GET command. */ +/* Data callback for the KS_GET and KS_FETCH commands. */ static gpg_error_t ks_get_data_cb (void *opaque, const void *data, size_t datalen) { @@ -448,6 +448,65 @@ gpg_dirmngr_ks_get (ctrl_t ctrl, char **pattern, estream_t *r_fp) } +/* Run the KS_FETCH and pass URL as argument. On success an estream + object is returned to retrieve the keys. On error an error code is + returned and NULL stored at R_FP. + + The url is expected to point to a small set of keys; in many cases + only to one key. However, schemes like finger may return several + keys. Note that the configured keyservers are ignored by the + KS_FETCH command. */ +gpg_error_t +gpg_dirmngr_ks_fetch (ctrl_t ctrl, const char *url, estream_t *r_fp) +{ + gpg_error_t err; + assuan_context_t ctx; + struct ks_get_parm_s parm; + char *line = NULL; + + memset (&parm, 0, sizeof parm); + + *r_fp = NULL; + + err = open_context (ctrl, &ctx); + if (err) + return err; + + line = strconcat ("KS_FETCH -- ", url, NULL); + if (!line) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (strlen (line) + 2 >= ASSUAN_LINELENGTH) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + + parm.memfp = es_fopenmem (0, "rwb"); + if (!parm.memfp) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = assuan_transact (ctx, line, ks_get_data_cb, &parm, + NULL, NULL, NULL, NULL); + if (err) + goto leave; + + es_rewind (parm.memfp); + *r_fp = parm.memfp; + parm.memfp = NULL; + + leave: + es_fclose (parm.memfp); + xfree (line); + close_context (ctrl, ctx); + return err; +} + + /* Handle the KS_PUT inquiries. */ static gpg_error_t diff --git a/g10/keyserver.c b/g10/keyserver.c index 2f055ada5..be0049a18 100644 --- a/g10/keyserver.c +++ b/g10/keyserver.c @@ -1641,54 +1641,53 @@ keyserver_put (ctrl_t ctrl, strlist_t keyspecs, } - int keyserver_fetch (ctrl_t ctrl, strlist_t urilist) { - KEYDB_SEARCH_DESC desc; + gpg_error_t err; strlist_t sl; - unsigned int options=opt.keyserver_options.import_options; + estream_t datastream; + unsigned int options = opt.keyserver_options.import_options; /* Switch on fast-import, since fetch can handle more than one import and we don't want each set to rebuild the trustdb. Instead we do it once at the end. */ - opt.keyserver_options.import_options|=IMPORT_FAST; + opt.keyserver_options.import_options |= IMPORT_FAST; - /* A dummy desc since we're not actually fetching a particular key - ID */ - memset(&desc,0,sizeof(desc)); - desc.mode=KEYDB_SEARCH_MODE_EXACT; - - for(sl=urilist;sl;sl=sl->next) + for (sl=urilist; sl; sl=sl->next) { - struct keyserver_spec *spec; + if (!opt.quiet) + log_info (_("requesting key from `%s'\n"), sl->d); - spec=parse_keyserver_uri(sl->d,1,NULL,0); - if(spec) - { - int rc; + err = gpg_dirmngr_ks_fetch (ctrl, sl->d, &datastream); + if (!err) + { + void *stats_handle; - rc = keyserver_get (ctrl, &desc, 1, spec); - if(rc) - log_info (_("WARNING: unable to fetch URI %s: %s\n"), - sl->d,g10_errstr(rc)); + stats_handle = import_new_stats_handle(); + import_keys_es_stream (ctrl, datastream, stats_handle, NULL, NULL, + opt.keyserver_options.import_options); - free_keyserver_spec(spec); - } + import_print_stats (stats_handle); + import_release_stats_handle (stats_handle); + } else - log_info (_("WARNING: unable to parse URI %s\n"),sl->d); + log_info (_("WARNING: unable to fetch URI %s: %s\n"), + sl->d, gpg_strerror (err)); + es_fclose (datastream); } - opt.keyserver_options.import_options=options; + opt.keyserver_options.import_options = options; /* If the original options didn't have fast import, and the trustdb is dirty, rebuild. */ - if(!(opt.keyserver_options.import_options&IMPORT_FAST)) - trustdb_check_or_update(); + if (!(opt.keyserver_options.import_options&IMPORT_FAST)) + trustdb_check_or_update (); return 0; } + /* Import key in a CERT or pointed to by a CERT */ int keyserver_import_cert (ctrl_t ctrl,