mirror of
https://github.com/ipxe/ipxe
synced 2025-12-23 21:41:43 +03:00
[http] Rewrite HTTP core to support content encodings
Rewrite the HTTP core to allow for the addition of arbitrary content encoding mechanisms, such as PeerDist and gzip. The core now exposes http_open() which can be used to create requests with an explicitly selected HTTP method, an optional requested content range, and an optional request body. A simple wrapper provides the preexisting behaviour of creating either a GET request or an application/x-www-form-urlencoded POST request (if the URI includes parameters). The HTTP SAN interface is now implemented using the generic block device translator. Individual blocks are requested using http_open() to create a range request. Server connections are now managed via a connection pool; this allows for multiple requests to the same server (e.g. for SAN blocks) to be completely unaware of each other. Repeated HTTPS connections to the same server can reuse a pooled connection, avoiding the per-connection overhead of establishing a TLS session (which can take several seconds if using a client certificate). Support for HTTP SAN booting and for the Basic and Digest authentication schemes is now optional and can be controlled via the SANBOOT_PROTO_HTTP, HTTP_AUTH_BASIC, and HTTP_AUTH_DIGEST build configuration options in config/general.h. Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
@@ -30,26 +30,20 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <ipxe/open.h>
|
||||
#include <ipxe/http.h>
|
||||
#include <ipxe/features.h>
|
||||
|
||||
FEATURE ( FEATURE_PROTOCOL, "HTTP", DHCP_EB_FEATURE_HTTP, 1 );
|
||||
|
||||
/**
|
||||
* Initiate an HTTP connection
|
||||
*
|
||||
* @v xfer Data transfer interface
|
||||
* @v uri Uniform Resource Identifier
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int http_open ( struct interface *xfer, struct uri *uri ) {
|
||||
return http_open_filter ( xfer, uri, HTTP_PORT, NULL );
|
||||
}
|
||||
|
||||
/** HTTP URI opener */
|
||||
struct uri_opener http_uri_opener __uri_opener = {
|
||||
.scheme = "http",
|
||||
.open = http_open,
|
||||
.open = http_open_uri,
|
||||
};
|
||||
|
||||
/** HTTP URI scheme */
|
||||
struct http_scheme http_scheme __http_scheme = {
|
||||
.name = "http",
|
||||
.port = HTTP_PORT,
|
||||
};
|
||||
|
||||
190
src/net/tcp/httpauth.c
Normal file
190
src/net/tcp/httpauth.c
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
|
||||
*
|
||||
* This program 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 any later version.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Hyper Text Transfer Protocol (HTTP) authentication
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <strings.h>
|
||||
#include <errno.h>
|
||||
#include <ipxe/http.h>
|
||||
|
||||
/**
|
||||
* Identify authentication scheme
|
||||
*
|
||||
* @v http HTTP transaction
|
||||
* @v name Scheme name
|
||||
* @ret auth Authentication scheme, or NULL
|
||||
*/
|
||||
static struct http_authentication * http_authentication ( const char *name ) {
|
||||
struct http_authentication *auth;
|
||||
|
||||
/* Identify authentication scheme */
|
||||
for_each_table_entry ( auth, HTTP_AUTHENTICATIONS ) {
|
||||
if ( strcasecmp ( name, auth->name ) == 0 )
|
||||
return auth;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** An HTTP "WWW-Authenticate" response field */
|
||||
struct http_www_authenticate_field {
|
||||
/** Name */
|
||||
const char *name;
|
||||
/** Offset */
|
||||
size_t offset;
|
||||
};
|
||||
|
||||
/** Define an HTTP "WWW-Authenticate" response field */
|
||||
#define HTTP_WWW_AUTHENTICATE_FIELD( _name ) { \
|
||||
.name = #_name, \
|
||||
.offset = offsetof ( struct http_transaction, \
|
||||
response.auth._name ), \
|
||||
}
|
||||
|
||||
/**
|
||||
* Set HTTP "WWW-Authenticate" response field value
|
||||
*
|
||||
* @v http HTTP transaction
|
||||
* @v field Response field
|
||||
* @v value Field value
|
||||
*/
|
||||
static inline void
|
||||
http_www_auth_field ( struct http_transaction *http,
|
||||
struct http_www_authenticate_field *field, char *value ) {
|
||||
char **ptr;
|
||||
|
||||
ptr = ( ( ( void * ) http ) + field->offset );
|
||||
*ptr = value;
|
||||
}
|
||||
|
||||
/** HTTP "WWW-Authenticate" fields */
|
||||
static struct http_www_authenticate_field http_www_auth_fields[] = {
|
||||
HTTP_WWW_AUTHENTICATE_FIELD ( realm ),
|
||||
HTTP_WWW_AUTHENTICATE_FIELD ( qop ),
|
||||
HTTP_WWW_AUTHENTICATE_FIELD ( algorithm ),
|
||||
HTTP_WWW_AUTHENTICATE_FIELD ( nonce ),
|
||||
HTTP_WWW_AUTHENTICATE_FIELD ( opaque ),
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse HTTP "WWW-Authenticate" header
|
||||
*
|
||||
* @v http HTTP transaction
|
||||
* @v line Remaining header line
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int http_parse_www_authenticate ( struct http_transaction *http,
|
||||
char *line ) {
|
||||
struct http_www_authenticate_field *field;
|
||||
char *name;
|
||||
char *key;
|
||||
char *value;
|
||||
unsigned int i;
|
||||
|
||||
/* Get scheme name */
|
||||
name = http_token ( &line, NULL );
|
||||
if ( ! name ) {
|
||||
DBGC ( http, "HTTP %p malformed WWW-Authenticate \"%s\"\n",
|
||||
http, value );
|
||||
return -EPROTO;
|
||||
}
|
||||
|
||||
/* Identify scheme */
|
||||
http->response.auth.auth = http_authentication ( name );
|
||||
if ( ! http->response.auth.auth ) {
|
||||
DBGC ( http, "HTTP %p unrecognised authentication scheme "
|
||||
"\"%s\"\n", http, name );
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
/* Process fields */
|
||||
while ( ( key = http_token ( &line, &value ) ) ) {
|
||||
for ( i = 0 ; i < ( sizeof ( http_www_auth_fields ) /
|
||||
sizeof ( http_www_auth_fields[0] ) ) ; i++){
|
||||
field = &http_www_auth_fields[i];
|
||||
if ( strcasecmp ( key, field->name ) == 0 )
|
||||
http_www_auth_field ( http, field, value );
|
||||
}
|
||||
}
|
||||
|
||||
/* Allow HTTP request to be retried if the request had not
|
||||
* already tried authentication.
|
||||
*/
|
||||
if ( ! http->request.auth.auth )
|
||||
http->response.flags |= HTTP_RESPONSE_RETRY;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** HTTP "WWW-Authenticate" header */
|
||||
struct http_response_header
|
||||
http_response_www_authenticate __http_response_header = {
|
||||
.name = "WWW-Authenticate",
|
||||
.parse = http_parse_www_authenticate,
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct HTTP "Authorization" header
|
||||
*
|
||||
* @v http HTTP transaction
|
||||
* @v buf Buffer
|
||||
* @v len Length of buffer
|
||||
* @ret len Length of header value, or negative error
|
||||
*/
|
||||
static int http_format_authorization ( struct http_transaction *http,
|
||||
char *buf, size_t len ) {
|
||||
struct http_authentication *auth = http->request.auth.auth;
|
||||
size_t used;
|
||||
int auth_len;
|
||||
int rc;
|
||||
|
||||
/* Do nothing unless we have an authentication scheme */
|
||||
if ( ! auth )
|
||||
return 0;
|
||||
|
||||
/* Construct header */
|
||||
used = snprintf ( buf, len, "%s ", auth->name );
|
||||
auth_len = auth->format ( http, ( buf + used ),
|
||||
( ( used < len ) ? ( len - used ) : 0 ) );
|
||||
if ( auth_len < 0 ) {
|
||||
rc = auth_len;
|
||||
return rc;
|
||||
}
|
||||
used += auth_len;
|
||||
|
||||
return used;
|
||||
}
|
||||
|
||||
/** HTTP "Authorization" header */
|
||||
struct http_request_header http_request_authorization __http_request_header = {
|
||||
.name = "Authorization",
|
||||
.format = http_format_authorization,
|
||||
};
|
||||
102
src/net/tcp/httpbasic.c
Normal file
102
src/net/tcp/httpbasic.c
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
|
||||
*
|
||||
* This program 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 any later version.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Hyper Text Transfer Protocol (HTTP) Basic authentication
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <ipxe/uri.h>
|
||||
#include <ipxe/base64.h>
|
||||
#include <ipxe/http.h>
|
||||
|
||||
/* Disambiguate the various error causes */
|
||||
#define EACCES_USERNAME __einfo_error ( EINFO_EACCES_USERNAME )
|
||||
#define EINFO_EACCES_USERNAME \
|
||||
__einfo_uniqify ( EINFO_EACCES, 0x01, \
|
||||
"No username available for Basic authentication" )
|
||||
|
||||
/**
|
||||
* Perform HTTP Basic authentication
|
||||
*
|
||||
* @v http HTTP transaction
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int http_basic_authenticate ( struct http_transaction *http ) {
|
||||
struct http_request_auth *req = &http->request.auth;
|
||||
|
||||
/* Record username and password */
|
||||
if ( ! http->uri->user ) {
|
||||
DBGC ( http, "HTTP %p has no username for Basic "
|
||||
"authentication\n", http );
|
||||
return -EACCES_USERNAME;
|
||||
}
|
||||
req->username = http->uri->user;
|
||||
req->password = ( http->uri->password ? http->uri->password : "" );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct HTTP "Authorization" header for Basic authentication
|
||||
*
|
||||
* @v http HTTP transaction
|
||||
* @v buf Buffer
|
||||
* @v len Length of buffer
|
||||
* @ret len Length of header value, or negative error
|
||||
*/
|
||||
static int http_format_basic_auth ( struct http_transaction *http,
|
||||
char *buf, size_t len ) {
|
||||
struct http_request_auth *req = &http->request.auth;
|
||||
size_t user_pw_len = ( strlen ( req->username ) + 1 /* ":" */ +
|
||||
strlen ( req->password ) );
|
||||
char user_pw[ user_pw_len + 1 /* NUL */ ];
|
||||
|
||||
/* Sanity checks */
|
||||
assert ( req->username != NULL );
|
||||
assert ( req->password != NULL );
|
||||
|
||||
/* Construct "user:password" string */
|
||||
snprintf ( user_pw, sizeof ( user_pw ), "%s:%s",
|
||||
req->username, req->password );
|
||||
|
||||
/* Construct response */
|
||||
return base64_encode ( user_pw, user_pw_len, buf, len );
|
||||
}
|
||||
|
||||
/** HTTP Basic authentication scheme */
|
||||
struct http_authentication http_basic_auth __http_authentication = {
|
||||
.name = "Basic",
|
||||
.authenticate = http_basic_authenticate,
|
||||
.format = http_format_basic_auth,
|
||||
};
|
||||
|
||||
/* Drag in HTTP authentication support */
|
||||
REQUIRING_SYMBOL ( http_basic_auth );
|
||||
REQUIRE_OBJECT ( httpauth );
|
||||
134
src/net/tcp/httpblock.c
Normal file
134
src/net/tcp/httpblock.c
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
|
||||
*
|
||||
* This program 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 any later version.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Hyper Text Transfer Protocol (HTTP) block device
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <ipxe/uaccess.h>
|
||||
#include <ipxe/blocktrans.h>
|
||||
#include <ipxe/blockdev.h>
|
||||
#include <ipxe/acpi.h>
|
||||
#include <ipxe/http.h>
|
||||
|
||||
/** Block size used for HTTP block device requests */
|
||||
#define HTTP_BLKSIZE 512
|
||||
|
||||
/**
|
||||
* Read from block device
|
||||
*
|
||||
* @v http HTTP transaction
|
||||
* @v data Data interface
|
||||
* @v lba Starting logical block address
|
||||
* @v count Number of logical blocks
|
||||
* @v buffer Data buffer
|
||||
* @v len Length of data buffer
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
int http_block_read ( struct http_transaction *http, struct interface *data,
|
||||
uint64_t lba, unsigned int count, userptr_t buffer,
|
||||
size_t len ) {
|
||||
struct http_request_range range;
|
||||
int rc;
|
||||
|
||||
/* Sanity check */
|
||||
assert ( len == ( count * HTTP_BLKSIZE ) );
|
||||
|
||||
/* Construct request range descriptor */
|
||||
range.start = ( lba * HTTP_BLKSIZE );
|
||||
range.len = len;
|
||||
|
||||
/* Start a range request to retrieve the block(s) */
|
||||
if ( ( rc = http_open ( data, &http_get, http->uri, &range,
|
||||
NULL ) ) != 0 )
|
||||
goto err_open;
|
||||
|
||||
/* Insert block device translator */
|
||||
if ( ( rc = block_translate ( data, buffer, len ) ) != 0 ) {
|
||||
DBGC ( http, "HTTP %p could not insert block translator: %s\n",
|
||||
http, strerror ( rc ) );
|
||||
goto err_translate;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_translate:
|
||||
intf_restart ( data, rc );
|
||||
err_open:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read block device capacity
|
||||
*
|
||||
* @v control Control interface
|
||||
* @v data Data interface
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
int http_block_read_capacity ( struct http_transaction *http,
|
||||
struct interface *data ) {
|
||||
int rc;
|
||||
|
||||
/* Start a HEAD request to retrieve the capacity */
|
||||
if ( ( rc = http_open ( data, &http_head, http->uri, NULL,
|
||||
NULL ) ) != 0 )
|
||||
goto err_open;
|
||||
|
||||
/* Insert block device translator */
|
||||
if ( ( rc = block_translate ( data, UNULL, HTTP_BLKSIZE ) ) != 0 ) {
|
||||
DBGC ( http, "HTTP %p could not insert block translator: %s\n",
|
||||
http, strerror ( rc ) );
|
||||
goto err_translate;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_translate:
|
||||
intf_restart ( data, rc );
|
||||
err_open:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe device in ACPI table
|
||||
*
|
||||
* @v http HTTP transaction
|
||||
* @v acpi ACPI table
|
||||
* @v len Length of ACPI table
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
int http_acpi_describe ( struct http_transaction *http,
|
||||
struct acpi_description_header *acpi, size_t len ) {
|
||||
|
||||
DBGC ( http, "HTTP %p cannot yet describe device in an ACPI table\n",
|
||||
http );
|
||||
( void ) acpi;
|
||||
( void ) len;
|
||||
return 0;
|
||||
}
|
||||
309
src/net/tcp/httpconn.c
Normal file
309
src/net/tcp/httpconn.c
Normal file
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
|
||||
*
|
||||
* This program 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 any later version.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Hyper Text Transfer Protocol (HTTP) connection management
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <byteswap.h>
|
||||
#include <ipxe/tcpip.h>
|
||||
#include <ipxe/uri.h>
|
||||
#include <ipxe/timer.h>
|
||||
#include <ipxe/xfer.h>
|
||||
#include <ipxe/open.h>
|
||||
#include <ipxe/pool.h>
|
||||
#include <ipxe/http.h>
|
||||
|
||||
/** HTTP pooled connection expiry time */
|
||||
#define HTTP_CONN_EXPIRY ( 10 * TICKS_PER_SEC )
|
||||
|
||||
/** HTTP connection pool */
|
||||
static LIST_HEAD ( http_connection_pool );
|
||||
|
||||
/**
|
||||
* Identify HTTP scheme
|
||||
*
|
||||
* @v uri URI
|
||||
* @ret scheme HTTP scheme, or NULL
|
||||
*/
|
||||
static struct http_scheme * http_scheme ( struct uri *uri ) {
|
||||
struct http_scheme *scheme;
|
||||
|
||||
/* Sanity check */
|
||||
if ( ! uri->scheme )
|
||||
return NULL;
|
||||
|
||||
/* Identify scheme */
|
||||
for_each_table_entry ( scheme, HTTP_SCHEMES ) {
|
||||
if ( strcmp ( uri->scheme, scheme->name ) == 0 )
|
||||
return scheme;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Free HTTP connection
|
||||
*
|
||||
* @v refcnt Reference count
|
||||
*/
|
||||
static void http_conn_free ( struct refcnt *refcnt ) {
|
||||
struct http_connection *conn =
|
||||
container_of ( refcnt, struct http_connection, refcnt );
|
||||
|
||||
/* Free connection */
|
||||
uri_put ( conn->uri );
|
||||
free ( conn );
|
||||
}
|
||||
|
||||
/**
|
||||
* Close HTTP connection
|
||||
*
|
||||
* @v conn HTTP connection
|
||||
* @v rc Reason for close
|
||||
*/
|
||||
static void http_conn_close ( struct http_connection *conn, int rc ) {
|
||||
|
||||
/* Remove from connection pool, if applicable */
|
||||
pool_del ( &conn->pool );
|
||||
|
||||
/* Shut down interfaces */
|
||||
intf_shutdown ( &conn->socket, rc );
|
||||
intf_shutdown ( &conn->xfer, rc );
|
||||
if ( rc == 0 ) {
|
||||
DBGC2 ( conn, "HTTPCONN %p closed %s://%s\n",
|
||||
conn, conn->scheme->name, conn->uri->host );
|
||||
} else {
|
||||
DBGC ( conn, "HTTPCONN %p closed %s://%s: %s\n",
|
||||
conn, conn->scheme->name, conn->uri->host,
|
||||
strerror ( rc ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect idle HTTP connection
|
||||
*
|
||||
* @v pool Pooled connection
|
||||
*/
|
||||
static void http_conn_expired ( struct pooled_connection *pool ) {
|
||||
struct http_connection *conn =
|
||||
container_of ( pool, struct http_connection, pool );
|
||||
|
||||
/* Close connection */
|
||||
http_conn_close ( conn, 0 /* Not an error to close idle connection */ );
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive data from transport layer interface
|
||||
*
|
||||
* @v http HTTP connection
|
||||
* @v iobuf I/O buffer
|
||||
* @v meta Transfer metadata
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int http_conn_socket_deliver ( struct http_connection *conn,
|
||||
struct io_buffer *iobuf,
|
||||
struct xfer_metadata *meta ) {
|
||||
|
||||
/* Mark connection as alive */
|
||||
pool_alive ( &conn->pool );
|
||||
|
||||
/* Pass on to data transfer interface */
|
||||
return xfer_deliver ( &conn->xfer, iobuf, meta );
|
||||
}
|
||||
|
||||
/**
|
||||
* Close HTTP connection transport layer interface
|
||||
*
|
||||
* @v http HTTP connection
|
||||
* @v rc Reason for close
|
||||
*/
|
||||
static void http_conn_socket_close ( struct http_connection *conn, int rc ) {
|
||||
|
||||
/* If we are reopenable (i.e. we are a recycled connection
|
||||
* from the connection pool, and we have received no data from
|
||||
* the underlying socket since we were pooled), then suggest
|
||||
* that the client should reopen the connection.
|
||||
*/
|
||||
if ( pool_is_reopenable ( &conn->pool ) )
|
||||
pool_reopen ( &conn->xfer );
|
||||
|
||||
/* Close the connection */
|
||||
http_conn_close ( conn, rc );
|
||||
}
|
||||
|
||||
/**
|
||||
* Recycle this connection after closing
|
||||
*
|
||||
* @v http HTTP connection
|
||||
*/
|
||||
static void http_conn_xfer_recycle ( struct http_connection *conn ) {
|
||||
|
||||
/* Mark connection as recyclable */
|
||||
pool_recyclable ( &conn->pool );
|
||||
DBGC2 ( conn, "HTTPCONN %p keepalive enabled\n", conn );
|
||||
}
|
||||
|
||||
/**
|
||||
* Close HTTP connection data transfer interface
|
||||
*
|
||||
* @v conn HTTP connection
|
||||
* @v rc Reason for close
|
||||
*/
|
||||
static void http_conn_xfer_close ( struct http_connection *conn, int rc ) {
|
||||
|
||||
/* Add to the connection pool if keepalive is enabled and no
|
||||
* error occurred.
|
||||
*/
|
||||
if ( ( rc == 0 ) && pool_is_recyclable ( &conn->pool ) ) {
|
||||
intf_restart ( &conn->xfer, rc );
|
||||
pool_add ( &conn->pool, &http_connection_pool,
|
||||
HTTP_CONN_EXPIRY );
|
||||
DBGC2 ( conn, "HTTPCONN %p pooled %s://%s\n",
|
||||
conn, conn->scheme->name, conn->uri->host );
|
||||
return;
|
||||
}
|
||||
|
||||
/* Otherwise, close the connection */
|
||||
http_conn_close ( conn, rc );
|
||||
}
|
||||
|
||||
/** HTTP connection socket interface operations */
|
||||
static struct interface_operation http_conn_socket_operations[] = {
|
||||
INTF_OP ( xfer_deliver, struct http_connection *,
|
||||
http_conn_socket_deliver ),
|
||||
INTF_OP ( intf_close, struct http_connection *,
|
||||
http_conn_socket_close ),
|
||||
};
|
||||
|
||||
/** HTTP connection socket interface descriptor */
|
||||
static struct interface_descriptor http_conn_socket_desc =
|
||||
INTF_DESC_PASSTHRU ( struct http_connection, socket,
|
||||
http_conn_socket_operations, xfer );
|
||||
|
||||
/** HTTP connection data transfer interface operations */
|
||||
static struct interface_operation http_conn_xfer_operations[] = {
|
||||
INTF_OP ( pool_recycle, struct http_connection *,
|
||||
http_conn_xfer_recycle ),
|
||||
INTF_OP ( intf_close, struct http_connection *,
|
||||
http_conn_xfer_close ),
|
||||
};
|
||||
|
||||
/** HTTP connection data transfer interface descriptor */
|
||||
static struct interface_descriptor http_conn_xfer_desc =
|
||||
INTF_DESC_PASSTHRU ( struct http_connection, xfer,
|
||||
http_conn_xfer_operations, socket );
|
||||
|
||||
/**
|
||||
* Connect to an HTTP server
|
||||
*
|
||||
* @v xfer Data transfer interface
|
||||
* @v uri Connection URI
|
||||
* @ret rc Return status code
|
||||
*
|
||||
* HTTP connections are pooled. The caller should be prepared to
|
||||
* receive a pool_reopen() message.
|
||||
*/
|
||||
int http_connect ( struct interface *xfer, struct uri *uri ) {
|
||||
struct http_connection *conn;
|
||||
struct http_scheme *scheme;
|
||||
struct sockaddr_tcpip server;
|
||||
struct interface *socket;
|
||||
int rc;
|
||||
|
||||
/* Identify scheme */
|
||||
scheme = http_scheme ( uri );
|
||||
if ( ! scheme )
|
||||
return -ENOTSUP;
|
||||
|
||||
/* Sanity check */
|
||||
if ( ! uri->host )
|
||||
return -EINVAL;
|
||||
|
||||
/* Look for a reusable connection in the pool */
|
||||
list_for_each_entry ( conn, &http_connection_pool, pool.list ) {
|
||||
|
||||
/* Sanity checks */
|
||||
assert ( conn->uri != NULL );
|
||||
assert ( conn->uri->host != NULL );
|
||||
|
||||
/* Reuse connection, if possible */
|
||||
if ( ( scheme == conn->scheme ) &&
|
||||
( strcmp ( uri->host, conn->uri->host ) == 0 ) ) {
|
||||
|
||||
/* Remove from connection pool, stop timer,
|
||||
* attach to parent interface, and return.
|
||||
*/
|
||||
pool_del ( &conn->pool );
|
||||
intf_plug_plug ( &conn->xfer, xfer );
|
||||
DBGC2 ( conn, "HTTPCONN %p reused %s://%s\n",
|
||||
conn, conn->scheme->name, conn->uri->host );
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Allocate and initialise structure */
|
||||
conn = zalloc ( sizeof ( *conn ) );
|
||||
ref_init ( &conn->refcnt, http_conn_free );
|
||||
conn->uri = uri_get ( uri );
|
||||
conn->scheme = scheme;
|
||||
intf_init ( &conn->socket, &http_conn_socket_desc, &conn->refcnt );
|
||||
intf_init ( &conn->xfer, &http_conn_xfer_desc, &conn->refcnt );
|
||||
pool_init ( &conn->pool, http_conn_expired, &conn->refcnt );
|
||||
|
||||
/* Open socket */
|
||||
memset ( &server, 0, sizeof ( server ) );
|
||||
server.st_port = htons ( uri_port ( uri, scheme->port ) );
|
||||
socket = &conn->socket;
|
||||
if ( scheme->filter &&
|
||||
( ( rc = scheme->filter ( socket, uri->host, &socket ) ) != 0 ) )
|
||||
goto err_filter;
|
||||
if ( ( rc = xfer_open_named_socket ( socket, SOCK_STREAM,
|
||||
( struct sockaddr * ) &server,
|
||||
uri->host, NULL ) ) != 0 )
|
||||
goto err_open;
|
||||
|
||||
/* Attach to parent interface, mortalise self, and return */
|
||||
intf_plug_plug ( &conn->xfer, xfer );
|
||||
ref_put ( &conn->refcnt );
|
||||
|
||||
DBGC2 ( conn, "HTTPCONN %p created %s://%s:%d\n", conn,
|
||||
conn->scheme->name, conn->uri->host, ntohs ( server.st_port ) );
|
||||
return 0;
|
||||
|
||||
err_open:
|
||||
err_filter:
|
||||
DBGC2 ( conn, "HTTPCONN %p could not create %s://%s: %s\n",
|
||||
conn, conn->scheme->name, conn->uri->host, strerror ( rc ) );
|
||||
http_conn_close ( conn, rc );
|
||||
ref_put ( &conn->refcnt );
|
||||
return rc;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
234
src/net/tcp/httpdigest.c
Normal file
234
src/net/tcp/httpdigest.c
Normal file
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
|
||||
*
|
||||
* This program 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 any later version.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* You can also choose to distribute this program under the terms of
|
||||
* the Unmodified Binary Distribution Licence (as given in the file
|
||||
* COPYING.UBDL), provided that you have satisfied its requirements.
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Hyper Text Transfer Protocol (HTTP) Digest authentication
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <strings.h>
|
||||
#include <ipxe/uri.h>
|
||||
#include <ipxe/md5.h>
|
||||
#include <ipxe/base16.h>
|
||||
#include <ipxe/vsprintf.h>
|
||||
#include <ipxe/http.h>
|
||||
|
||||
/* Disambiguate the various error causes */
|
||||
#define EACCES_USERNAME __einfo_error ( EINFO_EACCES_USERNAME )
|
||||
#define EINFO_EACCES_USERNAME \
|
||||
__einfo_uniqify ( EINFO_EACCES, 0x01, \
|
||||
"No username available for Digest authentication" )
|
||||
|
||||
/**
|
||||
* Initialise HTTP Digest
|
||||
*
|
||||
* @v ctx Digest context
|
||||
* @v string Initial string
|
||||
*/
|
||||
static void http_digest_init ( struct md5_context *ctx ) {
|
||||
|
||||
/* Initialise MD5 digest */
|
||||
digest_init ( &md5_algorithm, ctx );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update HTTP Digest with new data
|
||||
*
|
||||
* @v ctx Digest context
|
||||
* @v string String to append
|
||||
*/
|
||||
static void http_digest_update ( struct md5_context *ctx, const char *string ) {
|
||||
static const char colon = ':';
|
||||
|
||||
/* Add (possibly colon-separated) field to MD5 digest */
|
||||
if ( ctx->len )
|
||||
digest_update ( &md5_algorithm, ctx, &colon, sizeof ( colon ) );
|
||||
digest_update ( &md5_algorithm, ctx, string, strlen ( string ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalise HTTP Digest
|
||||
*
|
||||
* @v ctx Digest context
|
||||
* @v out Buffer for digest output
|
||||
* @v len Buffer length
|
||||
*/
|
||||
static void http_digest_final ( struct md5_context *ctx, char *out,
|
||||
size_t len ) {
|
||||
uint8_t digest[MD5_DIGEST_SIZE];
|
||||
|
||||
/* Finalise and base16-encode MD5 digest */
|
||||
digest_final ( &md5_algorithm, ctx, digest );
|
||||
base16_encode ( digest, sizeof ( digest ), out, len );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform HTTP Digest authentication
|
||||
*
|
||||
* @v http HTTP transaction
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int http_digest_authenticate ( struct http_transaction *http ) {
|
||||
struct http_request_auth *req = &http->request.auth;
|
||||
struct http_response_auth *rsp = &http->response.auth;
|
||||
char ha1[ base16_encoded_len ( MD5_DIGEST_SIZE ) + 1 /* NUL */ ];
|
||||
char ha2[ base16_encoded_len ( MD5_DIGEST_SIZE ) + 1 /* NUL */ ];
|
||||
static const char md5sess[] = "MD5-sess";
|
||||
static const char md5[] = "MD5";
|
||||
struct md5_context ctx;
|
||||
|
||||
/* Check for required response parameters */
|
||||
if ( ! rsp->realm ) {
|
||||
DBGC ( http, "HTTP %p has no realm for Digest authentication\n",
|
||||
http );
|
||||
return -EINVAL;
|
||||
}
|
||||
if ( ! rsp->nonce ) {
|
||||
DBGC ( http, "HTTP %p has no nonce for Digest authentication\n",
|
||||
http );
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Record username and password */
|
||||
if ( ! http->uri->user ) {
|
||||
DBGC ( http, "HTTP %p has no username for Digest "
|
||||
"authentication\n", http );
|
||||
return -EACCES_USERNAME;
|
||||
}
|
||||
req->username = http->uri->user;
|
||||
req->password = ( http->uri->password ? http->uri->password : "" );
|
||||
|
||||
/* Handle quality of protection */
|
||||
if ( rsp->qop ) {
|
||||
|
||||
/* Use "auth" in subsequent request */
|
||||
req->qop = "auth";
|
||||
|
||||
/* Generate a client nonce */
|
||||
snprintf ( req->cnonce, sizeof ( req->cnonce ),
|
||||
"%08lx", random() );
|
||||
|
||||
/* Determine algorithm */
|
||||
req->algorithm = md5;
|
||||
if ( rsp->algorithm &&
|
||||
( strcasecmp ( rsp->algorithm, md5sess ) == 0 ) ) {
|
||||
req->algorithm = md5sess;
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate HA1 */
|
||||
http_digest_init ( &ctx );
|
||||
http_digest_update ( &ctx, req->username );
|
||||
http_digest_update ( &ctx, rsp->realm );
|
||||
http_digest_update ( &ctx, req->password );
|
||||
http_digest_final ( &ctx, ha1, sizeof ( ha1 ) );
|
||||
if ( req->algorithm == md5sess ) {
|
||||
http_digest_init ( &ctx );
|
||||
http_digest_update ( &ctx, ha1 );
|
||||
http_digest_update ( &ctx, rsp->nonce );
|
||||
http_digest_update ( &ctx, req->cnonce );
|
||||
http_digest_final ( &ctx, ha1, sizeof ( ha1 ) );
|
||||
}
|
||||
|
||||
/* Generate HA2 */
|
||||
http_digest_init ( &ctx );
|
||||
http_digest_update ( &ctx, http->request.method->name );
|
||||
http_digest_update ( &ctx, http->request.uri );
|
||||
http_digest_final ( &ctx, ha2, sizeof ( ha2 ) );
|
||||
|
||||
/* Generate response */
|
||||
http_digest_init ( &ctx );
|
||||
http_digest_update ( &ctx, ha1 );
|
||||
http_digest_update ( &ctx, rsp->nonce );
|
||||
if ( req->qop ) {
|
||||
http_digest_update ( &ctx, HTTP_DIGEST_NC );
|
||||
http_digest_update ( &ctx, req->cnonce );
|
||||
http_digest_update ( &ctx, req->qop );
|
||||
}
|
||||
http_digest_update ( &ctx, ha2 );
|
||||
http_digest_final ( &ctx, req->response, sizeof ( req->response ) );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct HTTP "Authorization" header for Digest authentication
|
||||
*
|
||||
* @v http HTTP transaction
|
||||
* @v buf Buffer
|
||||
* @v len Length of buffer
|
||||
* @ret len Length of header value, or negative error
|
||||
*/
|
||||
static int http_format_digest_auth ( struct http_transaction *http,
|
||||
char *buf, size_t len ) {
|
||||
struct http_request_auth *req = &http->request.auth;
|
||||
struct http_response_auth *rsp = &http->response.auth;
|
||||
size_t used = 0;
|
||||
|
||||
/* Sanity checks */
|
||||
assert ( rsp->realm != NULL );
|
||||
assert ( rsp->nonce != NULL );
|
||||
assert ( req->username != NULL );
|
||||
if ( req->qop ) {
|
||||
assert ( req->algorithm != NULL );
|
||||
assert ( req->cnonce[0] != '\0' );
|
||||
}
|
||||
assert ( req->response[0] != '\0' );
|
||||
|
||||
/* Construct response */
|
||||
used += ssnprintf ( ( buf + used ), ( len - used ),
|
||||
"realm=\"%s\", nonce=\"%s\", uri=\"%s\", "
|
||||
"username=\"%s\"", rsp->realm, rsp->nonce,
|
||||
http->request.uri, req->username );
|
||||
if ( rsp->opaque ) {
|
||||
used += ssnprintf ( ( buf + used ), ( len - used ),
|
||||
", opaque=\"%s\"", rsp->opaque );
|
||||
}
|
||||
if ( req->qop ) {
|
||||
used += ssnprintf ( ( buf + used ), ( len - used ),
|
||||
", qop=%s, algorithm=%s, cnonce=\"%s\", "
|
||||
"nc=" HTTP_DIGEST_NC, req->qop,
|
||||
req->algorithm, req->cnonce );
|
||||
}
|
||||
used += ssnprintf ( ( buf + used ), ( len - used ),
|
||||
", response=\"%s\"", req->response );
|
||||
|
||||
return used;
|
||||
}
|
||||
|
||||
/** HTTP Digest authentication scheme */
|
||||
struct http_authentication http_digest_auth __http_authentication = {
|
||||
.name = "Digest",
|
||||
.authenticate = http_digest_authenticate,
|
||||
.format = http_format_digest_auth,
|
||||
};
|
||||
|
||||
/* Drag in HTTP authentication support */
|
||||
REQUIRING_SYMBOL ( http_digest_auth );
|
||||
REQUIRE_OBJECT ( httpauth );
|
||||
@@ -30,7 +30,6 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <ipxe/open.h>
|
||||
#include <ipxe/tls.h>
|
||||
#include <ipxe/http.h>
|
||||
@@ -38,19 +37,15 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
|
||||
FEATURE ( FEATURE_PROTOCOL, "HTTPS", DHCP_EB_FEATURE_HTTPS, 1 );
|
||||
|
||||
/**
|
||||
* Initiate an HTTPS connection
|
||||
*
|
||||
* @v xfer Data transfer interface
|
||||
* @v uri Uniform Resource Identifier
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
static int https_open ( struct interface *xfer, struct uri *uri ) {
|
||||
return http_open_filter ( xfer, uri, HTTPS_PORT, add_tls );
|
||||
}
|
||||
|
||||
/** HTTPS URI opener */
|
||||
struct uri_opener https_uri_opener __uri_opener = {
|
||||
.scheme = "https",
|
||||
.open = https_open,
|
||||
.open = http_open_uri,
|
||||
};
|
||||
|
||||
/** HTTP URI scheme */
|
||||
struct http_scheme https_scheme __http_scheme = {
|
||||
.name = "https",
|
||||
.port = HTTPS_PORT,
|
||||
.filter = add_tls,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user