/*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2019 Intel Corporation * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include /* Poll timeout in milliseconds */ static const int EFIHTTP_POLL_TIMEOUT = 300000; static EFI_GUID http_guid = EFI_HTTP_PROTOCOL_GUID; static EFI_GUID httpsb_guid = EFI_HTTP_SERVICE_BINDING_PROTOCOL_GUID; static EFI_GUID ip4config2_guid = EFI_IP4_CONFIG2_PROTOCOL_GUID; static bool efihttp_init_done = false; static int efihttp_dev_init(void); static int efihttp_dev_strategy(void *devdata, int rw, daddr_t blk, size_t size, char *buf, size_t *rsize); static int efihttp_dev_open(struct open_file *f, ...); static int efihttp_dev_close(struct open_file *f); static int efihttp_fs_open(const char *path, struct open_file *f); static int efihttp_fs_close(struct open_file *f); static int efihttp_fs_read(struct open_file *f, void *buf, size_t size, size_t *resid); static int efihttp_fs_write(struct open_file *f, const void *buf, size_t size, size_t *resid); static off_t efihttp_fs_seek(struct open_file *f, off_t offset, int where); static int efihttp_fs_stat(struct open_file *f, struct stat *sb); static int efihttp_fs_readdir(struct open_file *f, struct dirent *d); struct open_efihttp { EFI_HTTP_PROTOCOL *http; EFI_HANDLE http_handle; EFI_HANDLE dev_handle; char *uri_base; }; struct file_efihttp { ssize_t size; off_t offset; char *path; bool is_dir; }; struct devsw efihttp_dev = { .dv_name = "http", .dv_type = DEVT_NET, .dv_init = efihttp_dev_init, .dv_strategy = efihttp_dev_strategy, .dv_open = efihttp_dev_open, .dv_close = efihttp_dev_close, .dv_ioctl = noioctl, .dv_print = NULL, .dv_cleanup = nullsys, }; struct fs_ops efihttp_fsops = { .fs_name = "efihttp", .fo_open = efihttp_fs_open, .fo_close = efihttp_fs_close, .fo_read = efihttp_fs_read, .fo_write = efihttp_fs_write, .fo_seek = efihttp_fs_seek, .fo_stat = efihttp_fs_stat, .fo_readdir = efihttp_fs_readdir, }; static void EFIAPI notify(EFI_EVENT event __unused, void *context) { bool *b; b = (bool *)context; *b = true; } static int setup_ipv4_config2(EFI_HANDLE handle, MAC_ADDR_DEVICE_PATH *mac, IPv4_DEVICE_PATH *ipv4, DNS_DEVICE_PATH *dns) { EFI_IP4_CONFIG2_PROTOCOL *ip4config2; EFI_STATUS status; status = BS->OpenProtocol(handle, &ip4config2_guid, (void **)&ip4config2, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); if (ipv4 != NULL) { if (mac != NULL) { setenv("boot.netif.hwaddr", ether_sprintf((u_char *)mac->MacAddress.Addr), 1); } setenv("boot.netif.ip", inet_ntoa(*(struct in_addr *)ipv4->LocalIpAddress.Addr), 1); setenv("boot.netif.netmask", intoa(*(n_long *)ipv4->SubnetMask.Addr), 1); setenv("boot.netif.gateway", inet_ntoa(*(struct in_addr *)ipv4->GatewayIpAddress.Addr), 1); status = ip4config2->SetData(ip4config2, Ip4Config2DataTypePolicy, sizeof(EFI_IP4_CONFIG2_POLICY), &(EFI_IP4_CONFIG2_POLICY) { Ip4Config2PolicyStatic }); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); status = ip4config2->SetData(ip4config2, Ip4Config2DataTypeManualAddress, sizeof(EFI_IP4_CONFIG2_MANUAL_ADDRESS), &(EFI_IP4_CONFIG2_MANUAL_ADDRESS) { .Address = ipv4->LocalIpAddress, .SubnetMask = ipv4->SubnetMask }); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); if (ipv4->GatewayIpAddress.Addr[0] != 0) { status = ip4config2->SetData(ip4config2, Ip4Config2DataTypeGateway, sizeof(EFI_IPv4_ADDRESS), &ipv4->GatewayIpAddress); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); } if (dns != NULL) { status = ip4config2->SetData(ip4config2, Ip4Config2DataTypeDnsServer, sizeof(EFI_IPv4_ADDRESS), &dns->DnsServerIp); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); } } else { status = ip4config2->SetData(ip4config2, Ip4Config2DataTypePolicy, sizeof(EFI_IP4_CONFIG2_POLICY), &(EFI_IP4_CONFIG2_POLICY) { Ip4Config2PolicyDhcp }); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); } return (0); } static int efihttp_dev_init(void) { EFI_DEVICE_PATH *imgpath, *devpath; URI_DEVICE_PATH *uri; EFI_HANDLE handle; EFI_STATUS status; int err; bool found_http; imgpath = efi_lookup_image_devpath(IH); if (imgpath == NULL) return (ENXIO); devpath = imgpath; found_http = false; for (; !IsDevicePathEnd(devpath); devpath = NextDevicePathNode(devpath)) { if (DevicePathType(devpath) != MESSAGING_DEVICE_PATH || DevicePathSubType(devpath) != MSG_URI_DP) continue; uri = (URI_DEVICE_PATH *)devpath; if (strncmp("http", (const char *)uri->Uri, 4) == 0) found_http = true; } if (!found_http) return (ENXIO); status = BS->LocateDevicePath(&httpsb_guid, &imgpath, &handle); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); err = efi_register_handles(&efihttp_dev, &handle, NULL, 1); if (!err) efihttp_init_done = true; return (err); } static int efihttp_dev_strategy(void *devdata __unused, int rw __unused, daddr_t blk __unused, size_t size __unused, char *buf __unused, size_t *rsize __unused) { return (EIO); } static int efihttp_dev_open(struct open_file *f, ...) { EFI_HTTP_CONFIG_DATA config; EFI_HTTPv4_ACCESS_POINT config_access; DNS_DEVICE_PATH *dns; EFI_DEVICE_PATH *devpath, *imgpath; EFI_SERVICE_BINDING_PROTOCOL *sb; IPv4_DEVICE_PATH *ipv4; MAC_ADDR_DEVICE_PATH *mac; URI_DEVICE_PATH *uri; struct devdesc *dev; struct open_efihttp *oh; char *c; EFI_HANDLE handle; EFI_STATUS status; int err, len; if (!efihttp_init_done) return (ENXIO); imgpath = efi_lookup_image_devpath(IH); if (imgpath == NULL) return (ENXIO); devpath = imgpath; status = BS->LocateDevicePath(&httpsb_guid, &devpath, &handle); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); mac = NULL; ipv4 = NULL; dns = NULL; uri = NULL; for (; !IsDevicePathEnd(imgpath); imgpath = NextDevicePathNode(imgpath)) { if (DevicePathType(imgpath) != MESSAGING_DEVICE_PATH) continue; switch (DevicePathSubType(imgpath)) { case MSG_MAC_ADDR_DP: mac = (MAC_ADDR_DEVICE_PATH *)imgpath; break; case MSG_IPv4_DP: ipv4 = (IPv4_DEVICE_PATH *)imgpath; break; case MSG_DNS_DP: dns = (DNS_DEVICE_PATH *)imgpath; break; case MSG_URI_DP: uri = (URI_DEVICE_PATH *)imgpath; break; default: break; } } if (uri == NULL) return (ENXIO); err = setup_ipv4_config2(handle, mac, ipv4, dns); if (err) return (err); oh = calloc(1, sizeof(struct open_efihttp)); if (!oh) return (ENOMEM); oh->dev_handle = handle; dev = (struct devdesc *)f->f_devdata; dev->d_opendata = oh; status = BS->OpenProtocol(handle, &httpsb_guid, (void **)&sb, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR(status)) { err = efi_status_to_errno(status); goto end; } status = sb->CreateChild(sb, &oh->http_handle); if (EFI_ERROR(status)) { err = efi_status_to_errno(status); goto end; } status = BS->OpenProtocol(oh->http_handle, &http_guid, (void **)&oh->http, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR(status)) { sb->DestroyChild(sb, oh->http_handle); err = efi_status_to_errno(status); goto end; } config.HttpVersion = HttpVersion11; config.TimeOutMillisec = 0; config.LocalAddressIsIPv6 = FALSE; config.AccessPoint.IPv4Node = &config_access; config_access.UseDefaultAddress = TRUE; config_access.LocalPort = 0; status = oh->http->Configure(oh->http, &config); if (EFI_ERROR(status)) { sb->DestroyChild(sb, oh->http_handle); err = efi_status_to_errno(status); goto end; } /* * Here we make attempt to construct a "base" URI by stripping * the last two path components from the loaded URI under the * assumption that it is something like: * * http://127.0.0.1/foo/boot/loader.efi * * hoping to arriving at: * * http://127.0.0.1/foo/ */ len = DevicePathNodeLength(&uri->Header) - sizeof(URI_DEVICE_PATH); oh->uri_base = malloc(len + 1); if (oh->uri_base == NULL) { err = ENOMEM; goto end; } strncpy(oh->uri_base, (const char *)uri->Uri, len); oh->uri_base[len] = '\0'; c = strrchr(oh->uri_base, '/'); if (c != NULL) *c = '\0'; c = strrchr(oh->uri_base, '/'); if (c != NULL && *(c + 1) != '\0') *(c + 1) = '\0'; err = 0; end: if (err != 0) { free(dev->d_opendata); dev->d_opendata = NULL; } return (err); } static int efihttp_dev_close(struct open_file *f) { EFI_SERVICE_BINDING_PROTOCOL *sb; struct devdesc *dev; struct open_efihttp *oh; EFI_STATUS status; dev = (struct devdesc *)f->f_devdata; oh = (struct open_efihttp *)dev->d_opendata; status = BS->OpenProtocol(oh->dev_handle, &httpsb_guid, (void **)&sb, IH, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); sb->DestroyChild(sb, oh->http_handle); free(oh->uri_base); free(oh); dev->d_opendata = NULL; return (0); } static int _efihttp_fs_open(const char *path, struct open_file *f) { EFI_HTTP_CONFIG_DATA config; EFI_HTTPv4_ACCESS_POINT config_access; EFI_HTTP_TOKEN token; EFI_HTTP_MESSAGE message; EFI_HTTP_REQUEST_DATA request; EFI_HTTP_RESPONSE_DATA response; EFI_HTTP_HEADER headers[3]; char *host, *hostp; char *c; struct devdesc *dev; struct open_efihttp *oh; struct file_efihttp *fh; EFI_STATUS status; UINTN i; int polltime; bool done; dev = (struct devdesc *)f->f_devdata; oh = (struct open_efihttp *)dev->d_opendata; fh = calloc(1, sizeof(struct file_efihttp)); if (fh == NULL) return (ENOMEM); f->f_fsdata = fh; fh->path = strdup(path); /* * Reset the HTTP state. * * EDK II's persistent HTTP connection handling is graceless, * assuming that all connections are persistent regardless of * any Connection: header or HTTP version reported by the * server, and failing to send requests when a more sane * implementation would seem to be just reestablishing the * closed connection. * * In the hopes of having some robustness, we indicate to the * server that we will close the connection by using a * Connection: close header. And then here we manually * unconfigure and reconfigure the http instance to force the * connection closed. */ memset(&config, 0, sizeof(config)); memset(&config_access, 0, sizeof(config_access)); config.AccessPoint.IPv4Node = &config_access; status = oh->http->GetModeData(oh->http, &config); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); status = oh->http->Configure(oh->http, NULL); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); status = oh->http->Configure(oh->http, &config); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); /* Send the read request */ done = false; status = BS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, notify, &done, &token.Event); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); /* extract the host portion of the URL */ host = strdup(oh->uri_base); if (host == NULL) return (ENOMEM); hostp = host; /* Remove the protocol scheme */ c = strchr(host, '/'); if (c != NULL && *(c + 1) == '/') hostp = (c + 2); /* Remove any path information */ c = strchr(hostp, '/'); if (c != NULL) *c = '\0'; token.Status = EFI_NOT_READY; token.Message = &message; message.Data.Request = &request; message.HeaderCount = 3; message.Headers = headers; message.BodyLength = 0; message.Body = NULL; request.Method = HttpMethodGet; request.Url = calloc(strlen(oh->uri_base) + strlen(path) + 1, 2); headers[0].FieldName = (CHAR8 *)"Host"; headers[0].FieldValue = (CHAR8 *)hostp; headers[1].FieldName = (CHAR8 *)"Connection"; headers[1].FieldValue = (CHAR8 *)"close"; headers[2].FieldName = (CHAR8 *)"Accept"; headers[2].FieldValue = (CHAR8 *)"*/*"; cpy8to16(oh->uri_base, request.Url, strlen(oh->uri_base)); cpy8to16(path, request.Url + strlen(oh->uri_base), strlen(path)); status = oh->http->Request(oh->http, &token); free(request.Url); free(host); if (EFI_ERROR(status)) { BS->CloseEvent(token.Event); return (efi_status_to_errno(status)); } polltime = 0; while (!done && polltime < EFIHTTP_POLL_TIMEOUT) { status = oh->http->Poll(oh->http); if (EFI_ERROR(status)) break; if (!done) { delay(100 * 1000); polltime += 100; } } BS->CloseEvent(token.Event); if (EFI_ERROR(token.Status)) return (efi_status_to_errno(token.Status)); /* Wait for the read response */ done = false; status = BS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, notify, &done, &token.Event); if (EFI_ERROR(status)) return (efi_status_to_errno(status)); token.Status = EFI_NOT_READY; token.Message = &message; message.Data.Response = &response; message.HeaderCount = 0; message.Headers = NULL; message.BodyLength = 0; message.Body = NULL; response.StatusCode = HTTP_STATUS_UNSUPPORTED_STATUS; status = oh->http->Response(oh->http, &token); if (EFI_ERROR(status)) { BS->CloseEvent(token.Event); return (efi_status_to_errno(status)); } polltime = 0; while (!done && polltime < EFIHTTP_POLL_TIMEOUT) { status = oh->http->Poll(oh->http); if (EFI_ERROR(status)) break; if (!done) { delay(100 * 1000); polltime += 100; } } BS->CloseEvent(token.Event); if (EFI_ERROR(token.Status)) { BS->FreePool(message.Headers); return (efi_status_to_errno(token.Status)); } if (response.StatusCode != HTTP_STATUS_200_OK) { BS->FreePool(message.Headers); return (EIO); } fh->size = 0; fh->is_dir = false; for (i = 0; i < message.HeaderCount; i++) { if (strcasecmp((const char *)message.Headers[i].FieldName, "Content-Length") == 0) fh->size = strtoul((const char *) message.Headers[i].FieldValue, NULL, 10); else if (strcasecmp((const char *)message.Headers[i].FieldName, "Content-type") == 0) { if (strncmp((const char *)message.Headers[i].FieldValue, "text/html", 9) == 0) fh->is_dir = true; } } return (0); } static int efihttp_fs_open(const char *path, struct open_file *f) { char *path_slash; int err; if (!efihttp_init_done) return (ENXIO); /* * If any path fails to open, try with a trailing slash in * case it's a directory. */ err = _efihttp_fs_open(path, f); if (err != 0) { /* * Work around a bug in the EFI HTTP implementation which * causes a crash if the http instance isn't torn down * between requests. * See https://bugzilla.tianocore.org/show_bug.cgi?id=1917 */ efihttp_dev_close(f); efihttp_dev_open(f); path_slash = malloc(strlen(path) + 2); if (path_slash == NULL) return (ENOMEM); strcpy(path_slash, path); strcat(path_slash, "/"); err = _efihttp_fs_open(path_slash, f); free(path_slash); } return (err); } static int efihttp_fs_close(struct open_file *f __unused) { return (0); } static int _efihttp_fs_read(struct open_file *f, void *buf, size_t size, size_t *resid) { EFI_HTTP_TOKEN token; EFI_HTTP_MESSAGE message; EFI_STATUS status; struct devdesc *dev; struct open_efihttp *oh; struct file_efihttp *fh; bool done; int polltime; fh = (struct file_efihttp *)f->f_fsdata; if (fh->size > 0 && fh->offset >= fh->size) { if (resid != NULL) *resid = size; return 0; } dev = (struct devdesc *)f->f_devdata; oh = (struct open_efihttp *)dev->d_opendata; done = false; status = BS->CreateEvent(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, notify, &done, &token.Event); if (EFI_ERROR(status)) { return (efi_status_to_errno(status)); } token.Status = EFI_NOT_READY; token.Message = &message; message.Data.Request = NULL; message.HeaderCount = 0; message.Headers = NULL; message.BodyLength = size; message.Body = buf; status = oh->http->Response(oh->http, &token); if (status == EFI_CONNECTION_FIN) { if (resid) *resid = size; return (0); } else if (EFI_ERROR(status)) { BS->CloseEvent(token.Event); return (efi_status_to_errno(status)); } polltime = 0; while (!done && polltime < EFIHTTP_POLL_TIMEOUT) { status = oh->http->Poll(oh->http); if (EFI_ERROR(status)) break; if (!done) { delay(100 * 1000); polltime += 100; } } BS->CloseEvent(token.Event); if (token.Status == EFI_CONNECTION_FIN) { if (resid) *resid = size; return (0); } else if (EFI_ERROR(token.Status)) return (efi_status_to_errno(token.Status)); if (resid) *resid = size - message.BodyLength; fh->offset += message.BodyLength; return (0); } static int efihttp_fs_read(struct open_file *f, void *buf, size_t size, size_t *resid) { size_t res; int err = 0; while (size > 0) { err = _efihttp_fs_read(f, buf, size, &res); if (err != 0 || res == size) goto end; buf += (size - res); size = res; } end: if (resid) *resid = size; return (err); } static int efihttp_fs_write(struct open_file *f __unused, const void *buf __unused, size_t size __unused, size_t *resid __unused) { return (EIO); } static off_t efihttp_fs_seek(struct open_file *f, off_t offset, int where) { struct file_efihttp *fh; char *path; void *buf; size_t res, res2; int err; fh = (struct file_efihttp *)f->f_fsdata; if (where == SEEK_SET && fh->offset == offset) return (0); if (where == SEEK_SET && fh->offset < offset) { buf = malloc(1500); if (buf == NULL) return (ENOMEM); res = offset - fh->offset; while (res > 0) { err = _efihttp_fs_read(f, buf, min(1500, res), &res2); if (err != 0) { free(buf); return (err); } res -= min(1500, res) - res2; } free(buf); return (0); } else if (where == SEEK_SET) { path = fh->path; fh->path = NULL; efihttp_fs_close(f); /* * Work around a bug in the EFI HTTP implementation which * causes a crash if the http instance isn't torn down * between requests. * See https://bugzilla.tianocore.org/show_bug.cgi?id=1917 */ efihttp_dev_close(f); efihttp_dev_open(f); err = efihttp_fs_open(path, f); free(path); if (err != 0) return (err); return efihttp_fs_seek(f, offset, where); } return (EIO); } static int efihttp_fs_stat(struct open_file *f, struct stat *sb) { struct file_efihttp *fh; fh = (struct file_efihttp *)f->f_fsdata; memset(sb, 0, sizeof(*sb)); sb->st_nlink = 1; sb->st_mode = 0777 | (fh->is_dir ? S_IFDIR : S_IFREG); sb->st_size = fh->size; return (0); } static int efihttp_fs_readdir(struct open_file *f, struct dirent *d) { static char *dirbuf = NULL, *db2, *cursor; static int dirbuf_len = 0; char *end; struct file_efihttp *fh; fh = (struct file_efihttp *)f->f_fsdata; if (dirbuf_len < fh->size) { db2 = realloc(dirbuf, fh->size); if (db2 == NULL) { free(dirbuf); return (ENOMEM); } else dirbuf = db2; dirbuf_len = fh->size; } if (fh->offset != fh->size) { efihttp_fs_seek(f, 0, SEEK_SET); efihttp_fs_read(f, dirbuf, dirbuf_len, NULL); cursor = dirbuf; } cursor = strstr(cursor, "d_type = DT_DIR; } else d->d_type = DT_REG; memcpy(d->d_name, cursor, end - cursor); d->d_name[end - cursor] = '\0'; return (0); }