mirror of
https://git.proxmox.com/git/efi-boot-shim
synced 2025-05-28 17:57:04 +00:00
843 lines
21 KiB
C
843 lines
21 KiB
C
// SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
|
|
/*
|
|
* Copyright 2015 SUSE LINUX GmbH <glin@suse.com>
|
|
*
|
|
* Significant portions of this code are derived from Tianocore
|
|
* (http://tianocore.sf.net) and are Copyright 2009-2012 Intel
|
|
* Corporation.
|
|
*/
|
|
#include "shim.h"
|
|
|
|
static UINTN
|
|
ascii_to_int (CONST CHAR8 *str)
|
|
{
|
|
UINTN u;
|
|
CHAR8 c;
|
|
|
|
// skip preceeding white space
|
|
while (*str && *str == ' ') {
|
|
str += 1;
|
|
}
|
|
|
|
// convert digits
|
|
u = 0;
|
|
while ((c = *(str++))) {
|
|
if (c >= '0' && c <= '9') {
|
|
u = (u * 10) + c - '0';
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return u;
|
|
}
|
|
|
|
static UINTN
|
|
convert_http_status_code (EFI_HTTP_STATUS_CODE status_code)
|
|
{
|
|
if (status_code >= HTTP_STATUS_100_CONTINUE &&
|
|
status_code < HTTP_STATUS_200_OK) {
|
|
return (status_code - HTTP_STATUS_100_CONTINUE + 100);
|
|
} else if (status_code >= HTTP_STATUS_200_OK &&
|
|
status_code < HTTP_STATUS_300_MULTIPLE_CHIOCES) {
|
|
return (status_code - HTTP_STATUS_200_OK + 200);
|
|
} else if (status_code >= HTTP_STATUS_300_MULTIPLE_CHIOCES &&
|
|
status_code < HTTP_STATUS_400_BAD_REQUEST) {
|
|
return (status_code - HTTP_STATUS_300_MULTIPLE_CHIOCES + 300);
|
|
} else if (status_code >= HTTP_STATUS_400_BAD_REQUEST &&
|
|
status_code < HTTP_STATUS_500_INTERNAL_SERVER_ERROR) {
|
|
return (status_code - HTTP_STATUS_400_BAD_REQUEST + 400);
|
|
} else if (status_code >= HTTP_STATUS_500_INTERNAL_SERVER_ERROR) {
|
|
return (status_code - HTTP_STATUS_500_INTERNAL_SERVER_ERROR + 500);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Convert an HTTP status code to an EFI status code. */
|
|
static EFI_STATUS
|
|
efi_status_from_http_status(EFI_HTTP_STATUS_CODE status_code)
|
|
{
|
|
switch (status_code) {
|
|
case HTTP_STATUS_400_BAD_REQUEST:
|
|
case HTTP_STATUS_411_LENGTH_REQUIRED:
|
|
case HTTP_STATUS_413_REQUEST_ENTITY_TOO_LARGE:
|
|
case HTTP_STATUS_414_REQUEST_URI_TOO_LARGE:
|
|
case HTTP_STATUS_415_UNSUPPORTED_MEDIA_TYPE:
|
|
case HTTP_STATUS_416_REQUESTED_RANGE_NOT_SATISFIED:
|
|
case HTTP_STATUS_417_EXPECTATION_FAILED:
|
|
return EFI_INVALID_PARAMETER;
|
|
case HTTP_STATUS_401_UNAUTHORIZED:
|
|
case HTTP_STATUS_402_PAYMENT_REQUIRED:
|
|
case HTTP_STATUS_403_FORBIDDEN:
|
|
case HTTP_STATUS_407_PROXY_AUTHENTICATION_REQUIRED:
|
|
return EFI_ACCESS_DENIED;
|
|
case HTTP_STATUS_404_NOT_FOUND:
|
|
case HTTP_STATUS_410_GONE:
|
|
return EFI_NOT_FOUND;
|
|
case HTTP_STATUS_405_METHOD_NOT_ALLOWED:
|
|
case HTTP_STATUS_501_NOT_IMPLEMENTED:
|
|
return EFI_UNSUPPORTED;
|
|
case HTTP_STATUS_406_NOT_ACCEPTABLE:
|
|
return EFI_NO_MEDIA;
|
|
case HTTP_STATUS_408_REQUEST_TIME_OUT:
|
|
case HTTP_STATUS_504_GATEWAY_TIME_OUT:
|
|
return EFI_TIMEOUT;
|
|
case HTTP_STATUS_409_CONFLICT:
|
|
case HTTP_STATUS_412_PRECONDITION_FAILED:
|
|
return EFI_MEDIA_CHANGED;
|
|
case HTTP_STATUS_500_INTERNAL_SERVER_ERROR:
|
|
case HTTP_STATUS_502_BAD_GATEWAY:
|
|
return EFI_DEVICE_ERROR;
|
|
case HTTP_STATUS_503_SERVICE_UNAVAILABLE:
|
|
return EFI_NOT_READY;
|
|
case HTTP_STATUS_505_HTTP_VERSION_NOT_SUPPORTED:
|
|
return EFI_INCOMPATIBLE_VERSION;
|
|
default:
|
|
/* Use a generic HTTP error for anything else. */
|
|
return EFI_HTTP_ERROR;
|
|
}
|
|
}
|
|
|
|
static EFI_DEVICE_PATH *devpath;
|
|
static EFI_MAC_ADDRESS mac_addr;
|
|
static IPv4_DEVICE_PATH ip4_node;
|
|
static IPv6_DEVICE_PATH ip6_node;
|
|
static BOOLEAN is_ip6;
|
|
static CHAR8 *uri;
|
|
|
|
BOOLEAN
|
|
find_httpboot (EFI_HANDLE device)
|
|
{
|
|
EFI_DEVICE_PATH *unpacked;
|
|
EFI_DEVICE_PATH *Node;
|
|
MAC_ADDR_DEVICE_PATH *MacNode;
|
|
URI_DEVICE_PATH *UriNode;
|
|
UINTN uri_size;
|
|
BOOLEAN ip_found = FALSE;
|
|
BOOLEAN ret = FALSE;
|
|
|
|
if (uri) {
|
|
FreePool(uri);
|
|
uri = NULL;
|
|
}
|
|
|
|
devpath = DevicePathFromHandle(device);
|
|
if (!devpath) {
|
|
perror(L"Failed to get device path\n");
|
|
return FALSE;
|
|
}
|
|
|
|
unpacked = UnpackDevicePath(devpath);
|
|
if (!unpacked) {
|
|
perror(L"Failed to unpack device path\n");
|
|
return FALSE;
|
|
}
|
|
Node = unpacked;
|
|
|
|
/* Traverse the device path to find IPv4()/.../Uri() or
|
|
* IPv6()/.../Uri() */
|
|
while (!IsDevicePathEnd(Node)) {
|
|
/* Save the MAC node so we can match the net card later */
|
|
if (DevicePathType(Node) == MESSAGING_DEVICE_PATH &&
|
|
DevicePathSubType(Node) == MSG_MAC_ADDR_DP) {
|
|
MacNode = (MAC_ADDR_DEVICE_PATH *)Node;
|
|
CopyMem(&mac_addr, &MacNode->MacAddress,
|
|
sizeof(EFI_MAC_ADDRESS));
|
|
} else if (DevicePathType(Node) == MESSAGING_DEVICE_PATH &&
|
|
(DevicePathSubType(Node) == MSG_IPv4_DP ||
|
|
DevicePathSubType(Node) == MSG_IPv6_DP)) {
|
|
/* Save the IP node so we can set up the connection */
|
|
/* later */
|
|
if (DevicePathSubType(Node) == MSG_IPv6_DP) {
|
|
CopyMem(&ip6_node, Node,
|
|
sizeof(IPv6_DEVICE_PATH));
|
|
is_ip6 = TRUE;
|
|
} else {
|
|
CopyMem(&ip4_node, Node,
|
|
sizeof(IPv4_DEVICE_PATH));
|
|
is_ip6 = FALSE;
|
|
}
|
|
|
|
ip_found = TRUE;
|
|
} else if (ip_found == TRUE &&
|
|
(DevicePathType(Node) == MESSAGING_DEVICE_PATH &&
|
|
DevicePathSubType(Node) == MSG_URI_DP)) {
|
|
EFI_DEVICE_PATH *NextNode;
|
|
|
|
/* Check if the URI node is the last node since the */
|
|
/* RAMDISK node could be appended, and we don't need */
|
|
/* to download the second stage loader in that case. */
|
|
NextNode = NextDevicePathNode(Node);
|
|
if (!IsDevicePathEnd(NextNode))
|
|
goto out;
|
|
|
|
/* Save the current URI */
|
|
UriNode = (URI_DEVICE_PATH *)Node;
|
|
uri_size = strlen(UriNode->Uri);
|
|
uri = AllocatePool(uri_size + 1);
|
|
if (!uri) {
|
|
perror(L"Failed to allocate uri\n");
|
|
goto out;
|
|
}
|
|
CopyMem(uri, UriNode->Uri, uri_size + 1);
|
|
ret = TRUE;
|
|
goto out;
|
|
}
|
|
Node = NextDevicePathNode(Node);
|
|
}
|
|
out:
|
|
FreePool(unpacked);
|
|
return ret;
|
|
}
|
|
|
|
static EFI_STATUS
|
|
generate_next_uri (CONST CHAR8 *current_uri, CONST CHAR8 *next_loader,
|
|
CHAR8 **uri)
|
|
{
|
|
CONST CHAR8 *ptr;
|
|
UINTN next_len;
|
|
UINTN path_len = 0;
|
|
UINTN count = 0;
|
|
|
|
if (strncmp(current_uri, (CHAR8 *)"http://", 7) == 0) {
|
|
ptr = current_uri + 7;
|
|
count += 7;
|
|
} else if (strncmp(current_uri, (CHAR8 *)"https://", 8) == 0) {
|
|
ptr = current_uri + 8;
|
|
count += 8;
|
|
} else {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
/* Extract the path */
|
|
next_len = strlen(next_loader);
|
|
while (*ptr != '\0') {
|
|
count++;
|
|
if (*ptr == '/')
|
|
path_len = count;
|
|
ptr++;
|
|
}
|
|
|
|
*uri = AllocatePool(sizeof(CHAR8) * (path_len + next_len + 1));
|
|
if (!*uri)
|
|
return EFI_OUT_OF_RESOURCES;
|
|
|
|
CopyMem(*uri, (void *)current_uri, path_len);
|
|
CopyMem(*uri + path_len, (void *)next_loader, next_len);
|
|
(*uri)[path_len + next_len] = '\0';
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
static EFI_STATUS
|
|
extract_hostname (CONST CHAR8 *url, CHAR8 **hostname)
|
|
{
|
|
CONST CHAR8 *ptr, *start;
|
|
UINTN host_len = 0;
|
|
|
|
if (strncmp(url, (CHAR8 *)"http://", 7) == 0)
|
|
start = url + 7;
|
|
else if (strncmp(url, (CHAR8 *)"https://", 8) == 0)
|
|
start = url + 8;
|
|
else
|
|
return EFI_INVALID_PARAMETER;
|
|
|
|
ptr = start;
|
|
while (*ptr != '/' && *ptr != '\0') {
|
|
host_len++;
|
|
ptr++;
|
|
}
|
|
|
|
*hostname = AllocatePool(sizeof(CHAR8) * (host_len + 1));
|
|
if (!*hostname)
|
|
return EFI_OUT_OF_RESOURCES;
|
|
|
|
CopyMem(*hostname, (void *)start, host_len);
|
|
(*hostname)[host_len] = '\0';
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
#define SAME_MAC_ADDR(a, b) (!CompareMem(a, b, sizeof(EFI_MAC_ADDRESS)))
|
|
|
|
static EFI_HANDLE
|
|
get_nic_handle (EFI_MAC_ADDRESS *mac)
|
|
{
|
|
EFI_DEVICE_PATH *unpacked = NULL;
|
|
EFI_DEVICE_PATH *Node;
|
|
EFI_DEVICE_PATH *temp_path = NULL;
|
|
MAC_ADDR_DEVICE_PATH *MacNode;
|
|
EFI_HANDLE handle = NULL;
|
|
EFI_HANDLE *buffer;
|
|
UINTN NoHandles;
|
|
UINTN i;
|
|
EFI_STATUS efi_status;
|
|
|
|
/* Get the list of handles that support the HTTP service binding
|
|
protocol */
|
|
efi_status = BS->LocateHandleBuffer(ByProtocol,
|
|
&EFI_HTTP_BINDING_GUID,
|
|
NULL, &NoHandles, &buffer);
|
|
if (EFI_ERROR(efi_status))
|
|
return NULL;
|
|
|
|
for (i = 0; i < NoHandles; i++) {
|
|
temp_path = DevicePathFromHandle(buffer[i]);
|
|
|
|
/* Match the MAC address */
|
|
unpacked = UnpackDevicePath(temp_path);
|
|
if (!unpacked) {
|
|
perror(L"Failed to unpack device path\n");
|
|
continue;
|
|
}
|
|
Node = unpacked;
|
|
while (!IsDevicePathEnd(Node)) {
|
|
if (DevicePathType(Node) == MESSAGING_DEVICE_PATH &&
|
|
DevicePathSubType(Node) == MSG_MAC_ADDR_DP) {
|
|
MacNode = (MAC_ADDR_DEVICE_PATH *)Node;
|
|
if (SAME_MAC_ADDR(mac, &MacNode->MacAddress)) {
|
|
handle = buffer[i];
|
|
goto out;
|
|
}
|
|
}
|
|
Node = NextDevicePathNode(Node);
|
|
}
|
|
FreePool(unpacked);
|
|
unpacked = NULL;
|
|
}
|
|
|
|
out:
|
|
if (unpacked)
|
|
FreePool(unpacked);
|
|
FreePool(buffer);
|
|
|
|
return handle;
|
|
}
|
|
|
|
static BOOLEAN
|
|
is_unspecified_ip6addr (EFI_IPv6_ADDRESS ip6)
|
|
{
|
|
UINT8 i;
|
|
|
|
for (i = 0; i<16; i++) {
|
|
if (ip6.Addr[i] != 0)
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static inline void
|
|
print_ip6_addr(EFI_IPv6_ADDRESS ip6addr)
|
|
{
|
|
perror(L"%x:%x:%x:%x:%x:%x:%x:%x\n",
|
|
ip6addr.Addr[0] << 8 | ip6addr.Addr[1],
|
|
ip6addr.Addr[2] << 8 | ip6addr.Addr[3],
|
|
ip6addr.Addr[4] << 8 | ip6addr.Addr[5],
|
|
ip6addr.Addr[6] << 8 | ip6addr.Addr[7],
|
|
ip6addr.Addr[8] << 8 | ip6addr.Addr[9],
|
|
ip6addr.Addr[10] << 8 | ip6addr.Addr[11],
|
|
ip6addr.Addr[12] << 8 | ip6addr.Addr[13],
|
|
ip6addr.Addr[14] << 8 | ip6addr.Addr[15]);
|
|
}
|
|
|
|
static EFI_STATUS
|
|
set_ip6(EFI_HANDLE *nic, IPv6_DEVICE_PATH *ip6node)
|
|
{
|
|
EFI_IP6_CONFIG_PROTOCOL *ip6cfg;
|
|
EFI_IP6_CONFIG_MANUAL_ADDRESS ip6;
|
|
EFI_IPv6_ADDRESS gateway;
|
|
EFI_STATUS efi_status;
|
|
|
|
efi_status = BS->HandleProtocol(nic, &EFI_IP6_CONFIG_GUID,
|
|
(VOID **)&ip6cfg);
|
|
if (EFI_ERROR(efi_status))
|
|
return efi_status;
|
|
|
|
ip6.Address = ip6node->LocalIpAddress;
|
|
ip6.PrefixLength = ip6node->PrefixLength;
|
|
ip6.IsAnycast = FALSE;
|
|
efi_status = ip6cfg->SetData(ip6cfg, Ip6ConfigDataTypeManualAddress,
|
|
sizeof(ip6), &ip6);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to set IPv6 Address:\nIP: ");
|
|
print_ip6_addr(ip6.Address);
|
|
perror(L"Prefix Length: %u\n", ip6.PrefixLength);
|
|
return efi_status;
|
|
}
|
|
|
|
gateway = ip6node->GatewayIpAddress;
|
|
if (is_unspecified_ip6addr(gateway))
|
|
return EFI_SUCCESS;
|
|
|
|
efi_status = ip6cfg->SetData(ip6cfg, Ip6ConfigDataTypeGateway,
|
|
sizeof(gateway), &gateway);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to set IPv6 Gateway:\nIP: ");
|
|
print_ip6_addr(gateway);
|
|
return efi_status;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
static BOOLEAN
|
|
is_unspecified_ip4addr (EFI_IPv4_ADDRESS ip4)
|
|
{
|
|
UINT8 i;
|
|
|
|
for (i = 0; i<4; i++) {
|
|
if (ip4.Addr[i] != 0)
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static inline void
|
|
print_ip4_addr(EFI_IPv4_ADDRESS ip4addr)
|
|
{
|
|
perror(L"%u.%u.%u.%u\n",
|
|
ip4addr.Addr[0], ip4addr.Addr[1],
|
|
ip4addr.Addr[2], ip4addr.Addr[3]);
|
|
}
|
|
|
|
static EFI_STATUS
|
|
set_ip4(EFI_HANDLE *nic, IPv4_DEVICE_PATH *ip4node)
|
|
{
|
|
EFI_IP4_CONFIG2_PROTOCOL *ip4cfg2;
|
|
EFI_IP4_CONFIG2_MANUAL_ADDRESS ip4;
|
|
EFI_IPv4_ADDRESS gateway;
|
|
EFI_STATUS efi_status;
|
|
|
|
efi_status = BS->HandleProtocol(nic, &EFI_IP4_CONFIG2_GUID,
|
|
(VOID **)&ip4cfg2);
|
|
if (EFI_ERROR(efi_status))
|
|
return efi_status;
|
|
|
|
ip4.Address = ip4node->LocalIpAddress;
|
|
ip4.SubnetMask = ip4node->SubnetMask;
|
|
efi_status = ip4cfg2->SetData(ip4cfg2, Ip4Config2DataTypeManualAddress,
|
|
sizeof(ip4), &ip4);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to Set IPv4 Address:\nIP: ");
|
|
print_ip4_addr(ip4.Address);
|
|
perror(L"Mask: ");
|
|
print_ip4_addr(ip4.SubnetMask);
|
|
return efi_status;
|
|
}
|
|
|
|
gateway = ip4node->GatewayIpAddress;
|
|
if (is_unspecified_ip4addr(gateway))
|
|
return EFI_SUCCESS;
|
|
|
|
efi_status = ip4cfg2->SetData(ip4cfg2, Ip4Config2DataTypeGateway,
|
|
sizeof(gateway), &gateway);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to Set IPv4 Gateway:\nGateway: ");
|
|
print_ip4_addr(gateway);
|
|
return efi_status;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
static VOID EFIAPI
|
|
httpnotify (EFI_EVENT Event UNUSED, VOID *Context)
|
|
{
|
|
*((BOOLEAN *) Context) = TRUE;
|
|
}
|
|
|
|
static EFI_STATUS
|
|
configure_http (EFI_HTTP_PROTOCOL *http, BOOLEAN is_ip6)
|
|
{
|
|
EFI_HTTP_CONFIG_DATA http_mode;
|
|
EFI_HTTPv4_ACCESS_POINT ip4node;
|
|
EFI_HTTPv6_ACCESS_POINT ip6node;
|
|
|
|
/* Configure HTTP */
|
|
ZeroMem(&http_mode, sizeof(http_mode));
|
|
http_mode.HttpVersion = HttpVersion11;
|
|
/* use the default time out */
|
|
http_mode.TimeOutMillisec = 0;
|
|
|
|
if (!is_ip6) {
|
|
http_mode.LocalAddressIsIPv6 = FALSE;
|
|
ZeroMem(&ip4node, sizeof(ip4node));
|
|
ip4node.UseDefaultAddress = TRUE;
|
|
http_mode.AccessPoint.IPv4Node = &ip4node;
|
|
} else {
|
|
http_mode.LocalAddressIsIPv6 = TRUE;
|
|
ZeroMem(&ip6node, sizeof(ip6node));
|
|
http_mode.AccessPoint.IPv6Node = &ip6node;
|
|
}
|
|
|
|
return http->Configure(http, &http_mode);
|
|
}
|
|
|
|
static EFI_STATUS
|
|
send_http_request (EFI_HTTP_PROTOCOL *http, CHAR8 *hostname, CHAR8 *uri)
|
|
{
|
|
EFI_HTTP_TOKEN tx_token;
|
|
EFI_HTTP_MESSAGE tx_message;
|
|
EFI_HTTP_REQUEST_DATA request;
|
|
EFI_HTTP_HEADER headers[3];
|
|
BOOLEAN request_done;
|
|
CHAR16 *Url = NULL;
|
|
EFI_STATUS efi_status;
|
|
EFI_STATUS event_status;
|
|
|
|
/* Convert the ascii string to the UCS2 string */
|
|
Url = PoolPrint(L"%a", uri);
|
|
if (!Url)
|
|
return EFI_OUT_OF_RESOURCES;
|
|
|
|
request.Method = HttpMethodGet;
|
|
request.Url = Url;
|
|
|
|
/* Prepare the HTTP headers */
|
|
headers[0].FieldName = (CHAR8 *)"Host";
|
|
headers[0].FieldValue = hostname;
|
|
headers[1].FieldName = (CHAR8 *)"Accept";
|
|
headers[1].FieldValue = (CHAR8 *)"*/*";
|
|
headers[2].FieldName = (CHAR8 *)"User-Agent";
|
|
headers[2].FieldValue = (CHAR8 *)"UefiHttpBoot/1.0";
|
|
|
|
tx_message.Data.Request = &request;
|
|
tx_message.HeaderCount = 3;
|
|
tx_message.Headers = headers;
|
|
tx_message.BodyLength = 0;
|
|
tx_message.Body = NULL;
|
|
|
|
tx_token.Status = EFI_NOT_READY;
|
|
tx_token.Message = &tx_message;
|
|
tx_token.Event = NULL;
|
|
request_done = FALSE;
|
|
efi_status = BS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_NOTIFY,
|
|
httpnotify, &request_done,
|
|
&tx_token.Event);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to Create Event for HTTP request: %r\n",
|
|
efi_status);
|
|
goto no_event;
|
|
}
|
|
|
|
/* Send out the request */
|
|
efi_status = http->Request(http, &tx_token);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"HTTP request failed: %r\n", efi_status);
|
|
goto error;
|
|
}
|
|
|
|
/* Wait for the response */
|
|
while (!request_done)
|
|
http->Poll(http);
|
|
|
|
if (EFI_ERROR(tx_token.Status)) {
|
|
perror(L"HTTP request: %r\n", tx_token.Status);
|
|
efi_status = tx_token.Status;
|
|
}
|
|
|
|
error:
|
|
event_status = BS->CloseEvent(tx_token.Event);
|
|
if (EFI_ERROR(event_status)) {
|
|
perror(L"Failed to close Event for HTTP request: %r\n",
|
|
event_status);
|
|
}
|
|
|
|
no_event:
|
|
if (Url)
|
|
FreePool(Url);
|
|
|
|
return efi_status;
|
|
}
|
|
|
|
static EFI_STATUS
|
|
receive_http_response(EFI_HTTP_PROTOCOL *http, VOID **buffer, UINT64 *buf_size)
|
|
{
|
|
EFI_HTTP_TOKEN rx_token;
|
|
EFI_HTTP_MESSAGE rx_message;
|
|
EFI_HTTP_RESPONSE_DATA response;
|
|
EFI_HTTP_STATUS_CODE http_status;
|
|
BOOLEAN response_done;
|
|
UINTN i, j, downloaded;
|
|
CHAR8 rx_buffer[9216];
|
|
EFI_STATUS efi_status;
|
|
EFI_STATUS event_status;
|
|
|
|
/* Initialize the rx message and buffer */
|
|
response.StatusCode = HTTP_STATUS_UNSUPPORTED_STATUS;
|
|
rx_message.Data.Response = &response;
|
|
rx_message.HeaderCount = 0;
|
|
rx_message.Headers = 0;
|
|
rx_message.BodyLength = sizeof(rx_buffer);
|
|
rx_message.Body = rx_buffer;
|
|
|
|
rx_token.Status = EFI_NOT_READY;
|
|
rx_token.Message = &rx_message;
|
|
rx_token.Event = NULL;
|
|
response_done = FALSE;
|
|
efi_status = BS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_NOTIFY,
|
|
httpnotify, &response_done,
|
|
&rx_token.Event);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to Create Event for HTTP response: %r\n",
|
|
efi_status);
|
|
goto no_event;
|
|
}
|
|
|
|
/* Notify the firmware to receive the HTTP messages */
|
|
efi_status = http->Response(http, &rx_token);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"HTTP response failed: %r\n", efi_status);
|
|
goto error;
|
|
}
|
|
|
|
/* Wait for the response */
|
|
while (!response_done)
|
|
http->Poll(http);
|
|
|
|
if (EFI_ERROR(rx_token.Status)) {
|
|
perror(L"HTTP response: %r\n", rx_token.Status);
|
|
efi_status = rx_token.Status;
|
|
goto error;
|
|
}
|
|
|
|
/* Check the HTTP status code */
|
|
http_status = rx_token.Message->Data.Response->StatusCode;
|
|
if (http_status != HTTP_STATUS_200_OK) {
|
|
perror(L"HTTP Status Code: %d\n",
|
|
convert_http_status_code(http_status));
|
|
efi_status = efi_status_from_http_status(http_status);
|
|
goto error;
|
|
}
|
|
|
|
/* Check the length of the file */
|
|
for (i = 0; i < rx_message.HeaderCount; i++) {
|
|
if (!strcasecmp(rx_message.Headers[i].FieldName,
|
|
(CHAR8 *)"Content-Length")) {
|
|
*buf_size = ascii_to_int(rx_message.Headers[i].FieldValue);
|
|
for(j = 0; j < i; j++) {
|
|
if (!strcasecmp(rx_message.Headers[i].FieldName,
|
|
(CHAR8 *)"Content-Length")) {
|
|
if (*buf_size != ascii_to_int(rx_message.Headers[j].FieldValue)) {
|
|
perror(L"Content-Length is invalid\n");
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (*buf_size == 0) {
|
|
perror(L"Failed to get Content-Length\n");
|
|
goto error;
|
|
}
|
|
|
|
if (*buf_size < rx_message.BodyLength) {
|
|
efi_status = EFI_BAD_BUFFER_SIZE;
|
|
perror(L"Invalid Content-Length\n");
|
|
goto error;
|
|
}
|
|
|
|
*buffer = AllocatePool(*buf_size);
|
|
if (!*buffer) {
|
|
perror(L"Failed to allocate new rx buffer\n");
|
|
goto error;
|
|
}
|
|
|
|
downloaded = rx_message.BodyLength;
|
|
|
|
CopyMem(*buffer, rx_buffer, downloaded);
|
|
|
|
/* Retreive the rest of the message */
|
|
while (downloaded < *buf_size) {
|
|
if (rx_message.Headers) {
|
|
FreePool(rx_message.Headers);
|
|
}
|
|
rx_message.Headers = NULL;
|
|
rx_message.HeaderCount = 0;
|
|
rx_message.Data.Response = NULL;
|
|
rx_message.BodyLength = sizeof(rx_buffer);
|
|
rx_message.Body = rx_buffer;
|
|
|
|
rx_token.Status = EFI_NOT_READY;
|
|
response_done = FALSE;
|
|
|
|
efi_status = http->Response(http, &rx_token);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"HTTP response failed: %r\n", efi_status);
|
|
goto error;
|
|
}
|
|
|
|
while (!response_done)
|
|
http->Poll(http);
|
|
|
|
if (EFI_ERROR(rx_token.Status)) {
|
|
perror(L"HTTP response: %r\n", rx_token.Status);
|
|
efi_status = rx_token.Status;
|
|
goto error;
|
|
}
|
|
|
|
if (rx_message.BodyLength + downloaded > *buf_size) {
|
|
efi_status = EFI_BAD_BUFFER_SIZE;
|
|
goto error;
|
|
}
|
|
|
|
CopyMem(*buffer + downloaded, rx_buffer, rx_message.BodyLength);
|
|
|
|
downloaded += rx_message.BodyLength;
|
|
}
|
|
|
|
error:
|
|
event_status = BS->CloseEvent(rx_token.Event);
|
|
if (EFI_ERROR(event_status)) {
|
|
perror(L"Failed to close Event for HTTP response: %r\n",
|
|
event_status);
|
|
}
|
|
|
|
no_event:
|
|
if (EFI_ERROR(efi_status) && *buffer)
|
|
FreePool(*buffer);
|
|
|
|
return efi_status;
|
|
}
|
|
|
|
static EFI_STATUS
|
|
http_fetch (EFI_HANDLE image, EFI_HANDLE device,
|
|
CHAR8 *hostname, CHAR8 *uri, BOOLEAN is_ip6,
|
|
VOID **buffer, UINT64 *buf_size)
|
|
{
|
|
EFI_SERVICE_BINDING *service;
|
|
EFI_HANDLE http_handle;
|
|
EFI_HTTP_PROTOCOL *http;
|
|
EFI_STATUS efi_status;
|
|
EFI_STATUS child_status;
|
|
|
|
*buffer = NULL;
|
|
*buf_size = 0;
|
|
|
|
/* Open HTTP Service Binding Protocol */
|
|
efi_status = BS->OpenProtocol(device, &EFI_HTTP_BINDING_GUID,
|
|
(VOID **) &service, image, NULL,
|
|
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
|
|
if (EFI_ERROR(efi_status))
|
|
return efi_status;
|
|
|
|
/* Create the ChildHandle from the Service Binding */
|
|
/* Set the handle to NULL to request a new handle */
|
|
http_handle = NULL;
|
|
efi_status = service->CreateChild(service, &http_handle);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to create the ChildHandle\n");
|
|
return efi_status;
|
|
}
|
|
|
|
/* Get the http protocol */
|
|
efi_status = BS->HandleProtocol(http_handle, &EFI_HTTP_PROTOCOL_GUID,
|
|
(VOID **) &http);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to get http\n");
|
|
goto error;
|
|
}
|
|
|
|
efi_status = configure_http(http, is_ip6);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to configure http: %r\n", efi_status);
|
|
goto error;
|
|
}
|
|
|
|
efi_status = send_http_request(http, hostname, uri);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to send HTTP request: %r\n", efi_status);
|
|
goto error;
|
|
}
|
|
|
|
efi_status = receive_http_response(http, buffer, buf_size);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to receive HTTP response: %r\n", efi_status);
|
|
goto error;
|
|
}
|
|
|
|
error:
|
|
child_status = service->DestroyChild(service, http_handle);
|
|
if (EFI_ERROR(efi_status)) {
|
|
return efi_status;
|
|
} else if (EFI_ERROR(child_status)) {
|
|
return child_status;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
EFI_STATUS
|
|
httpboot_fetch_buffer (EFI_HANDLE image, VOID **buffer, UINT64 *buf_size,
|
|
CHAR8 *name)
|
|
{
|
|
EFI_STATUS efi_status;
|
|
EFI_HANDLE nic;
|
|
CHAR8 *next_loader;
|
|
CHAR8 *next_uri = NULL;
|
|
CHAR8 *hostname = NULL;
|
|
|
|
if (!uri)
|
|
return EFI_NOT_READY;
|
|
|
|
next_loader = (CHAR8 *)AllocatePool((strlen(name) + 1) * sizeof (CHAR8));
|
|
translate_slashes(next_loader, name);
|
|
|
|
/* Create the URI for the next loader based on the original URI */
|
|
efi_status = generate_next_uri(uri, next_loader, &next_uri);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Next URI: %a, %r\n", next_uri, efi_status);
|
|
goto error;
|
|
}
|
|
|
|
/* Extract the hostname (or IP) from URI */
|
|
efi_status = extract_hostname(uri, &hostname);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"hostname: %a, %r\n", hostname, efi_status);
|
|
goto error;
|
|
}
|
|
|
|
/* Get the handle that associates with the NIC we are using and
|
|
also supports the HTTP service binding protocol */
|
|
nic = get_nic_handle(&mac_addr);
|
|
if (!nic) {
|
|
efi_status = EFI_NOT_FOUND;
|
|
goto error;
|
|
}
|
|
|
|
/* UEFI stops DHCP after fetching the image and stores the related
|
|
information in the device path node. We have to set up the
|
|
connection on our own for the further operations. */
|
|
if (!is_ip6)
|
|
efi_status = set_ip4(nic, &ip4_node);
|
|
else
|
|
efi_status = set_ip6(nic, &ip6_node);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to set IP for HTTPBoot: %r\n", efi_status);
|
|
goto error;
|
|
}
|
|
|
|
/* Use HTTP protocl to fetch the remote file */
|
|
efi_status = http_fetch (image, nic, hostname, next_uri, is_ip6,
|
|
buffer, buf_size);
|
|
if (EFI_ERROR(efi_status)) {
|
|
perror(L"Failed to fetch image: %r\n", efi_status);
|
|
goto error;
|
|
}
|
|
|
|
error:
|
|
FreePool(uri);
|
|
uri = NULL;
|
|
if (next_uri)
|
|
FreePool(next_uri);
|
|
if (hostname)
|
|
FreePool(hostname);
|
|
|
|
return efi_status;
|
|
}
|