/* http.c - HTTP protocol handler * Copyright (C) 1999, 2001, 2002, 2003, 2004 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 #ifdef _WIN32 #include #else #include #include #include #include #include #include #include #include #endif #include "util.h" #include "iobuf.h" #include "i18n.h" #include "http.h" #include "srv.h" #ifdef _WIN32 #define sock_close(a) closesocket(a) #else #define sock_close(a) close(a) #endif #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, const char *proxy ); static byte *build_rel_path( PARSED_URI uri ); static int parse_response( HTTP_HD hd ); static int connect_server( const char *server, ushort port, unsigned int flags, const char *srvtag ); static int write_server( int sock, const char *data, size_t length ); #ifdef _WIN32 static void deinit_sockets (void) { WSACleanup(); } static void init_sockets (void) { static int initialized; static WSADATA wsdata; if (initialized) return; if( WSAStartup( 0x0101, &wsdata ) ) { log_error ("error initializing socket library: ec=%d\n", (int)WSAGetLastError () ); return; } if( wsdata.wVersion < 0x0001 ) { log_error ("socket library version is %x.%x - but 1.1 needed\n", LOBYTE(wsdata.wVersion), HIBYTE(wsdata.wVersion)); WSACleanup(); return; } atexit ( deinit_sockets ); initialized = 1; } #endif /*_WIN32*/ static byte bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; /**************** * create a radix64 encoded string. */ /* TODO: This is a duplicate of code in g10/armor.c. Better to use a single copy in strgutil.c */ static char * make_radix64_string( const byte *data, size_t len ) { char *buffer, *p; buffer = p = m_alloc( (len+2)/3*4 + 1 ); for( ; len >= 3 ; len -= 3, data += 3 ) { *p++ = bintoasc[(data[0] >> 2) & 077]; *p++ = bintoasc[(((data[0] <<4)&060)|((data[1] >> 4)&017))&077]; *p++ = bintoasc[(((data[1]<<2)&074)|((data[2]>>6)&03))&077]; *p++ = bintoasc[data[2]&077]; } if( len == 2 ) { *p++ = bintoasc[(data[0] >> 2) & 077]; *p++ = bintoasc[(((data[0] <<4)&060)|((data[1] >> 4)&017))&077]; *p++ = bintoasc[((data[1]<<2)&074)]; } else if( len == 1 ) { *p++ = bintoasc[(data[0] >> 2) & 077]; *p++ = bintoasc[(data[0] <<4)&060]; } *p = 0; return buffer; } int http_open( HTTP_HD hd, HTTP_REQ_TYPE reqtype, const char *url, unsigned int flags, const char *proxy ) { int rc; if( !(reqtype == HTTP_REQ_GET || reqtype == HTTP_REQ_POST) ) return G10ERR_INV_ARG; /* initialize the handle */ memset( hd, 0, sizeof *hd ); hd->sock = -1; hd->initialized = 1; hd->req_type = reqtype; hd->flags = flags; rc = parse_uri( &hd->uri, url ); if( !rc ) { rc = send_request( hd, proxy ); if( !rc ) { hd->fp_write = iobuf_sockopen( hd->sock , "w" ); if( hd->fp_write ) return 0; rc = G10ERR_GENERAL; } } if( !hd->fp_read && !hd->fp_write && hd->sock != -1 ) sock_close( hd->sock ); iobuf_close( hd->fp_read ); iobuf_close( hd->fp_write); release_parsed_uri( hd->uri ); hd->initialized = 0; return rc; } void http_start_data( HTTP_HD hd ) { iobuf_flush ( hd->fp_write ); if( !hd->in_data ) { write_server (hd->sock, "\r\n", 2); hd->in_data = 1; } } int http_wait_response( HTTP_HD hd, unsigned int *ret_status ) { int rc; http_start_data( hd ); /* make sure that we are in the data */ #if 0 hd->sock = dup( hd->sock ); if( hd->sock == -1 ) return G10ERR_GENERAL; #endif iobuf_ioctl (hd->fp_write, 1, 1, NULL); /* keep the socket open */ iobuf_close (hd->fp_write); hd->fp_write = NULL; if ( !(hd->flags & HTTP_FLAG_NO_SHUTDOWN) ) shutdown( hd->sock, 1 ); hd->in_data = 0; hd->fp_read = iobuf_sockopen( hd->sock , "r" ); if( !hd->fp_read ) return G10ERR_GENERAL; rc = parse_response( hd ); if( !rc && ret_status ) *ret_status = hd->status_code; return rc; } int http_open_document( HTTP_HD hd, const char *document, unsigned int flags, const char *proxy ) { int rc; rc = http_open( hd, HTTP_REQ_GET, document, flags, proxy ); if( rc ) return rc; rc = http_wait_response( hd, NULL ); if( rc ) http_close( hd ); return rc; } void http_close( HTTP_HD hd ) { if( !hd || !hd->initialized ) return; if( !hd->fp_read && !hd->fp_write && hd->sock != -1 ) sock_close( hd->sock ); 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")==0) uri->port = 80; else if(strcmp(uri->scheme,"hkp")==0) uri->port = 11371; 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; /* Check for username/password encoding */ if((p3=strchr(p,'@'))) { uri->auth=p; *p3++='\0'; p=p3; } strlwr( p ); uri->host = p; if( (p3=strchr( p, ':' )) ) { *p3++ = 0; uri->port = atoi( p3 ); } 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 */ /* 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 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 char *proxy ) { const byte *server; byte *request, *p; ushort port; int rc; char *auth=NULL; server = *hd->uri->host? hd->uri->host : "localhost"; port = hd->uri->port? hd->uri->port : 80; if(proxy) { PARSED_URI uri; rc = parse_uri( &uri, proxy ); if (rc) { log_error("invalid HTTP proxy (%s): %s\n",proxy,g10_errstr(rc)); release_parsed_uri( uri ); return G10ERR_NETWORK; } hd->sock = connect_server( *uri->host? uri->host : "localhost", uri->port? uri->port : 80, 0, NULL ); if(uri->auth) { char *x=make_radix64_string(uri->auth,strlen(uri->auth)); auth=m_alloc(50+strlen(x)); sprintf(auth,"Proxy-Authorization: Basic %s\r\n",x); m_free(x); } release_parsed_uri( uri ); } else { hd->sock = connect_server( server, port, hd->flags, hd->uri->scheme ); if(hd->uri->auth) { char *x=make_radix64_string(hd->uri->auth,strlen(hd->uri->auth)); auth=m_alloc(50+strlen(x)); sprintf(auth,"Authorization: Basic %s\r\n",x); m_free(x); } } if( hd->sock == -1 ) return G10ERR_NETWORK; p = build_rel_path( hd->uri ); request=m_alloc(strlen(server)*2 + strlen(p) + (auth?strlen(auth):0) + 65); if( proxy ) sprintf( request, "%s http://%s:%hu%s%s HTTP/1.0\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", server, port, *p == '/'? "":"/", p, auth?auth:"" ); else { char portstr[15]; if(port!=80) sprintf(portstr,":%u",port); sprintf( request, "%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, (port!=80)?portstr:"", auth?auth:""); } m_free(p); rc = write_server( hd->sock, request, strlen(request) ); m_free( request ); m_free(auth); 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, "%;?&" ); /* todo: build 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; /* todo: add params */ for( r=uri->query; r; r = r->next ) { *p++ = r == uri->query? '?':'&'; n = insert_escapes( p, r->name, "%;?&=" ); p += n; *p++ = '='; /* todo: use valuelen */ n = insert_escapes( p, r->value, "%;?&=" ); p += n; } *p = 0; return rel_path; } /*********************** * Parse the response from a server. * Returns: errorcode and sets some fileds in the handle */ static int parse_response( HTTP_HD hd ) { byte *line, *p, *p2; 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; /* todo: 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] ) { /* malformed HTTP statuscode - 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 = 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 0; } #ifdef TEST 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 static int connect_server( const char *server, ushort port, unsigned int flags, const char *srvtag ) { int sock=-1,srv,srvcount=0,connected=0,hostfound=0; struct srventry *srvlist=NULL; #ifdef _WIN32 unsigned long inaddr; init_sockets(); /* Win32 gethostbyname doesn't handle IP addresses internally, so we try inet_addr first on that platform only. */ if((inaddr=inet_addr(server))!=INADDR_NONE) { struct sockaddr_in addr; memset(&addr,0,sizeof(addr)); if((sock=socket(AF_INET,SOCK_STREAM,0))==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))==0) return sock; else { sock_close(sock); return -1; } } #endif #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]; strcpy(srvname,"_"); strcat(srvname,srvtag); strcat(srvname,"._tcp."); strcat(srvname,server); srvcount=getsrv(srvname,&srvlist); } } #endif if(srvlist==NULL) { /* Either we're not using SRV, or the SRV lookup failed. Make up a fake SRV record. */ srvlist=m_alloc_clear(sizeof(struct srventry)); srvlist->port=port; strncpy(srvlist->target,server,MAXDNAME); srvlist->target[MAXDNAME-1]='\0'; srvcount=1; } #ifdef HAVE_GETADDRINFO for(srv=0;srvai_next) { if((sock=socket(ai->ai_family,ai->ai_socktype,ai->ai_protocol))==-1) { log_error("error creating socket: %s\n",strerror(errno)); freeaddrinfo(res); return -1; } if(connect(sock,ai->ai_addr,ai->ai_addrlen)==0) { connected=1; break; } sock_close(sock); } freeaddrinfo(res); if(ai) break; } #else /* !HAVE_GETADDRINFO */ for(srv=0;srvh_addrtype,SOCK_STREAM,0))==-1) { log_error("error creating socket: %s\n",strerror(errno)); return -1; } addr.sin_family=host->h_addrtype; if(addr.sin_family!=AF_INET) { log_error("%s: unknown address family\n",srvlist[srv].target); return -1; } addr.sin_port=htons(srvlist[srv].port); /* Try all A records until one responds. */ while(host->h_addr_list[i]) { if(host->h_length!=4) { log_error("%s: illegal address length\n",srvlist[srv].target); return -1; } memcpy(&addr.sin_addr,host->h_addr_list[i],host->h_length); if(connect(sock,(struct sockaddr *)&addr,sizeof(addr))==0) { connected=1; break; } i++; } if(host->h_addr_list[i]) break; sock_close(sock); } #endif /* !HAVE_GETADDRINFO */ m_free(srvlist); if(!connected) { int err=errno; #ifdef _WIN32 if(hostfound) log_error("%s: Unable to connect: ec=%d\n",server,(int)WSAGetLastError()); else log_error("%s: Host not found: ec=%d\n",server,(int)WSAGetLastError()); #else if(hostfound) log_error("%s: %s\n",server,strerror(err)); else log_error("%s: Host not found\n",server); #endif if(sock!=-1) sock_close(sock); errno=err; return -1; } return sock; } static int write_server( int sock, const char *data, size_t length ) { int nleft; nleft = length; while( nleft > 0 ) { #ifdef _WIN32 int nwritten; nwritten = send (sock, data, nleft, 0); if ( nwritten == SOCKET_ERROR ) { log_info ("write failed: ec=%d\n", (int)WSAGetLastError ()); return G10ERR_NETWORK; } #else int nwritten = write( sock, 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; } log_info("write failed: %s\n", strerror(errno)); return G10ERR_NETWORK; } #endif 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 == 1 ) { start_server(); return 0; } 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 = http_open_document( &hd, *argv, 0, NULL ); 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); http_close( &hd ); return 0; } #endif /*TEST*/