public inbox for devel@edk2.groups.io
 help / color / mirror / Atom feed
From: "Nickle Wang via groups.io" <nicklew=nvidia.com@groups.io>
To: <devel@edk2.groups.io>
Cc: Abner Chang <abner.chang@amd.com>,
	Igor Kulchytskyy <igork@ami.com>,
	"Nick Ramirez" <nramirez@nvidia.com>
Subject: [edk2-devel] [edk2-redfish-client][PATCH v2] RedfishClientPkg: introduce RedfishBootstrapAccountDxe
Date: Tue, 14 May 2024 20:38:10 +0800	[thread overview]
Message-ID: <20240514123810.28086-1-nicklew@nvidia.com> (raw)

-Introduce RedfishBootstrapAccountDxe to delete bootstrap
account from /redfish/v1/AccountService/Accounts after BIOS
finished all Redfish jobs. The bootstrap account won't be
available to other application. So deleting bootstrap account
helps to release resource at BMC.
- After bootstrap account is deleted at BMC, the Redfish service
instance is no longer usable. Close Redfish service instance to
release the HTTP connection between BIOS and BMC.

Signed-off-by: Nickle Wang <nicklew@nvidia.com>
Cc: Abner Chang <abner.chang@amd.com>
Cc: Igor Kulchytskyy <igork@ami.com>
Cc: Nick Ramirez <nramirez@nvidia.com>
---
 .../RedfishClientComponents.dsc.inc           |   1 +
 .../RedfishBootstrapAccountDxe.inf            |  53 +++
 .../RedfishBootstrapAccountDxe.h              |  58 +++
 .../RedfishBootstrapAccountDxe.c              | 337 ++++++++++++++++++
 RedfishClientPkg/RedfishClient.fdf.inc        |   1 +
 5 files changed, 450 insertions(+)
 create mode 100644 RedfishClientPkg/RedfishBootstrapAccountDxe/RedfishBootstrapAccountDxe.inf
 create mode 100644 RedfishClientPkg/RedfishBootstrapAccountDxe/RedfishBootstrapAccountDxe.h
 create mode 100644 RedfishClientPkg/RedfishBootstrapAccountDxe/RedfishBootstrapAccountDxe.c

diff --git a/RedfishClientPkg/RedfishClientComponents.dsc.inc b/RedfishClientPkg/RedfishClientComponents.dsc.inc
index 42fc0c299..fe5248b62 100644
--- a/RedfishClientPkg/RedfishClientComponents.dsc.inc
+++ b/RedfishClientPkg/RedfishClientComponents.dsc.inc
@@ -20,6 +20,7 @@
   RedfishClientPkg/HiiToRedfishMemoryDxe/HiiToRedfishMemoryDxe.inf
   RedfishClientPkg/HiiToRedfishBootDxe/HiiToRedfishBootDxe.inf
   RedfishClientPkg/HiiToRedfishBiosDxe/HiiToRedfishBiosDxe.inf
+  RedfishClientPkg/RedfishBootstrapAccountDxe/RedfishBootstrapAccountDxe.inf
 !endif
   #
   # Below two modules should be pulled in by build tool.
diff --git a/RedfishClientPkg/RedfishBootstrapAccountDxe/RedfishBootstrapAccountDxe.inf b/RedfishClientPkg/RedfishBootstrapAccountDxe/RedfishBootstrapAccountDxe.inf
new file mode 100644
index 000000000..4073e95f4
--- /dev/null
+++ b/RedfishClientPkg/RedfishBootstrapAccountDxe/RedfishBootstrapAccountDxe.inf
@@ -0,0 +1,53 @@
+## @file
+#  This driver deletes bootstrap account in BMC after BIOS Redfish finished
+#  all jobs
+#
+#  (C) Copyright 2021 Hewlett Packard Enterprise Development LP<BR>
+#  Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+#
+#  SPDX-License-Identifier: BSD-2-Clause-Patent
+#
+##
+
+[Defines]
+  INF_VERSION               = 0x0001000b
+  BASE_NAME                 = RedfishBootstrapAccountDxe
+  FILE_GUID                 = 87555253-2F7E-45FC-B469-FD35B2E51210
+  MODULE_TYPE               = DXE_DRIVER
+  VERSION_STRING            = 1.0
+  ENTRY_POINT               = RedfishBootstrapAccountEntryPoint
+  UNLOAD_IMAGE              = RedfishBootstrapAccountUnload
+
+[Packages]
+  MdePkg/MdePkg.dec
+  MdeModulePkg/MdeModulePkg.dec
+  RedfishPkg/RedfishPkg.dec
+  RedfishClientPkg/RedfishClientPkg.dec
+
+[Sources]
+  RedfishBootstrapAccountDxe.h
+  RedfishBootstrapAccountDxe.c
+
+[LibraryClasses]
+  BaseLib
+  BaseMemoryLib
+  DebugLib
+  MemoryAllocationLib
+  PrintLib
+  RedfishEventLib
+  RedfishFeatureUtilityLib
+  RedfishDebugLib
+  RedfishVersionLib
+  RedfishHttpLib
+  UefiLib
+  UefiBootServicesTableLib
+  UefiRuntimeServicesTableLib
+  UefiDriverEntryPoint
+
+[Protocols]
+  gEdkIIRedfishConfigHandlerProtocolGuid  ## CONSUMES ##
+  gEdkIIRedfishCredentialProtocolGuid     ## CONSUMES ##
+  gEfiRestExProtocolGuid                  ## CONSUMES ##
+
+[Depex]
+  gEdkIIRedfishCredentialProtocolGuid
diff --git a/RedfishClientPkg/RedfishBootstrapAccountDxe/RedfishBootstrapAccountDxe.h b/RedfishClientPkg/RedfishBootstrapAccountDxe/RedfishBootstrapAccountDxe.h
new file mode 100644
index 000000000..5262f1e6b
--- /dev/null
+++ b/RedfishClientPkg/RedfishBootstrapAccountDxe/RedfishBootstrapAccountDxe.h
@@ -0,0 +1,58 @@
+/** @file
+  Common header file for RedfishBootstrapAccountDxe driver.
+
+  (C) Copyright 2021-2022 Hewlett Packard Enterprise Development LP<BR>
+  Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+  SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#ifndef REDFISH_BOOTSTRAP_ACCOUNT_DXE_H_
+#define REDFISH_BOOTSTRAP_ACCOUNT_DXE_H_
+
+#include <Uefi.h>
+#include <RedfishBase.h>
+
+//
+// Libraries
+//
+#include <Library/BaseLib.h>
+#include <Library/BaseMemoryLib.h>
+#include <Library/DebugLib.h>
+
+#include <Library/MemoryAllocationLib.h>
+#include <Library/PrintLib.h>
+#include <Library/RedfishEventLib.h>
+#include <Library/RedfishFeatureUtilityLib.h>
+#include <Library/RedfishDebugLib.h>
+#include <Library/RedfishVersionLib.h>
+#include <Library/RedfishHttpLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Library/UefiDriverEntryPoint.h>
+#include <Library/UefiLib.h>
+#include <Library/UefiRuntimeServicesTableLib.h>
+
+#include <Protocol/EdkIIRedfishConfigHandler.h>
+#include <Protocol/EdkIIRedfishCredential.h>
+#include <Protocol/RestEx.h>
+
+#define REDFISH_BOOTSTRAP_ACCOUNT_DEBUG         DEBUG_VERBOSE
+#define REDFISH_MANAGER_ACCOUNT_COLLECTION_URI  L"AccountService/Accounts"
+#define REDFISH_URI_LENGTH                      128
+
+//
+// Definitions of REDFISH_BOOTSTRAP_ACCOUNT_PRIVATE
+//
+typedef struct {
+  EFI_HANDLE                               ImageHandle;
+  EFI_HANDLE                               RestExHandle;
+  REDFISH_SERVICE                          RedfishService;
+  EFI_EVENT                                RedfishEvent;
+  EDKII_REDFISH_CONFIG_HANDLER_PROTOCOL    Protocol;
+} REDFISH_BOOTSTRAP_ACCOUNT_PRIVATE;
+
+#define REDFISH_BOOTSTRAP_ACCOUNT_PRIVATE_FROM_PROTOCOL(This) \
+          BASE_CR ((This), REDFISH_BOOTSTRAP_ACCOUNT_PRIVATE, Protocol)
+
+#endif
diff --git a/RedfishClientPkg/RedfishBootstrapAccountDxe/RedfishBootstrapAccountDxe.c b/RedfishClientPkg/RedfishBootstrapAccountDxe/RedfishBootstrapAccountDxe.c
new file mode 100644
index 000000000..8944bea4b
--- /dev/null
+++ b/RedfishClientPkg/RedfishBootstrapAccountDxe/RedfishBootstrapAccountDxe.c
@@ -0,0 +1,337 @@
+/** @file
+  This driver deletes bootstrap account in BMC after BIOS Redfish finished
+  all jobs.
+
+  (C) Copyright 2021-2022 Hewlett Packard Enterprise Development LP<BR>
+  Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+  SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#include "RedfishBootstrapAccountDxe.h"
+
+REDFISH_BOOTSTRAP_ACCOUNT_PRIVATE  *mBootstrapPrivate = NULL;
+
+/**
+  Close Redfish service instance by calling RestEx protocol to release instance.
+
+  @param[in]  RestExHandle      Handle of RestEx protocol.
+
+  @retval EFI_SUCCESS           The Redfish service is closed successfully.
+  @retval EFI_INVALID_PARAMETER RestExHandle is NULL.
+  @retval Others                Error occurs.
+
+**/
+EFI_STATUS
+CloseRedfishService (
+  IN EFI_HANDLE  RestExHandle
+  )
+{
+  EFI_REST_EX_PROTOCOL  *RestEx;
+  EFI_STATUS            Status;
+
+  if (RestExHandle == NULL) {
+    return EFI_INVALID_PARAMETER;
+  }
+
+  Status = gBS->HandleProtocol (
+                  RestExHandle,
+                  &gEfiRestExProtocolGuid,
+                  (VOID **)&RestEx
+                  );
+  if (!EFI_ERROR (Status)) {
+    Status = RestEx->Configure (RestEx, NULL);
+    DEBUG ((REDFISH_BOOTSTRAP_ACCOUNT_DEBUG, "%a: release RestEx instance: %r\n", __func__, Status));
+  }
+
+  return Status;
+}
+
+/**
+  Callback function executed when the AfterProvisioning event group is signaled.
+
+  @param[in]   Event    Event whose notification function is being invoked.
+  @param[out]  Context  Pointer to the Context buffer
+
+**/
+VOID
+EFIAPI
+RedfishBootstrapAccountOnRedfishAfterProvisioning (
+  IN  EFI_EVENT  Event,
+  OUT VOID       *Context
+  )
+{
+  EFI_STATUS                         Status;
+  REDFISH_BOOTSTRAP_ACCOUNT_PRIVATE  *Private;
+  EDKII_REDFISH_CREDENTIAL_PROTOCOL  *credentialProtocol;
+  EDKII_REDFISH_AUTH_METHOD          AuthMethod;
+  CHAR8                              *AccountName;
+  CHAR8                              *AccountCredential;
+  CHAR16                             TargetUri[REDFISH_URI_LENGTH];
+  CHAR16                             *RedfishVersion;
+  REDFISH_RESPONSE                   RedfishResponse;
+
+  RedfishVersion = NULL;
+
+  Private = (REDFISH_BOOTSTRAP_ACCOUNT_PRIVATE *)Context;
+  if ((Private == NULL) || (Private->RedfishService == NULL)) {
+    DEBUG ((DEBUG_ERROR, "%a: Redfish service is not available\n", __func__));
+    return;
+  }
+
+  //
+  // Locate Redfish Credential Protocol to get credential for
+  // accessing to Redfish service.
+  //
+  Status = gBS->LocateProtocol (
+                  &gEdkIIRedfishCredentialProtocolGuid,
+                  NULL,
+                  (VOID **)&credentialProtocol
+                  );
+  if (EFI_ERROR (Status)) {
+    DEBUG ((REDFISH_BOOTSTRAP_ACCOUNT_DEBUG, "%a: No Redfish Credential Protocol is installed on system.", __func__));
+    return;
+  }
+
+  //
+  // This won't create new bootstrapping account at BMC.
+  // The RedfishPlatformCredentialIpmiLib under RedfishPkg keeps
+  // bootstrapping account in UEFI variable for the use during boot time.
+  // And this variable gets deleted at exit-boot-service event.
+  // We read this cached bootstrapping account here and delete this
+  // account at BMC side. So, no bootstrapping account stays in both
+  // BMC and host side after host boots into OS.
+  //
+  Status = credentialProtocol->GetAuthInfo (
+                                 credentialProtocol,
+                                 &AuthMethod,
+                                 &AccountName,
+                                 &AccountCredential
+                                 );
+  if (EFI_ERROR (Status)) {
+    DEBUG ((DEBUG_ERROR, "%a: can not get bootstrap account information: %r\n", __func__, Status));
+    return;
+  }
+
+  //
+  // Carving the URI
+  //
+  RedfishVersion = RedfishGetVersion (Private->RedfishService);
+  if (RedfishVersion == NULL) {
+    DEBUG ((DEBUG_ERROR, "%a: can not get Redfish version\n", __func__));
+    return;
+  }
+
+  UnicodeSPrint (TargetUri, (sizeof (CHAR16) * REDFISH_URI_LENGTH), L"%s%s/%a", RedfishVersion, REDFISH_MANAGER_ACCOUNT_COLLECTION_URI, AccountName);
+
+  DEBUG ((REDFISH_BOOTSTRAP_ACCOUNT_DEBUG, "%a: bootstrap account:    %a\n", __func__, AccountName));
+  DEBUG ((REDFISH_BOOTSTRAP_ACCOUNT_DEBUG, "%a: bootstrap credential: %a\n", __func__, AccountCredential));
+  DEBUG ((REDFISH_BOOTSTRAP_ACCOUNT_DEBUG, "%a: bootstrap URI:        %s\n", __func__, TargetUri));
+
+  //
+  // Remove bootstrap account at /redfish/v1/AccountService/Account
+  //
+  ZeroMem (&RedfishResponse, sizeof (REDFISH_RESPONSE));
+  Status = RedfishHttpDeleteResource (
+             Private->RedfishService,
+             TargetUri,
+             &RedfishResponse
+             );
+  if (EFI_ERROR (Status)) {
+    DEBUG ((DEBUG_ERROR, "%a: can not remove bootstrap account at BMC: %r", __func__, Status));
+    DumpRedfishResponse (__func__, DEBUG_ERROR, &RedfishResponse);
+  } else {
+    DEBUG ((REDFISH_BOOTSTRAP_ACCOUNT_DEBUG, "%a: bootstrap account: %a is removed from: %s\n", __func__, AccountName, REDFISH_MANAGER_ACCOUNT_COLLECTION_URI));
+  }
+
+  //
+  // Clean credential
+  //
+  ZeroMem (AccountName, AsciiStrSize (AccountName));
+  ZeroMem (AccountCredential, AsciiStrSize (AccountCredential));
+
+  //
+  // Since the bootstrap account is deleted at BMC, the Redfish service instance is no longer usable.
+  // Close Redfish service instance to release the HTTP connection between BIOS and BMC.
+  //
+  Status = CloseRedfishService (Private->RestExHandle);
+  if (EFI_ERROR (Status)) {
+    DEBUG ((DEBUG_ERROR, "%a: cannot close Redfish service instance: %r\n", __func__, Status));
+  }
+
+  RedfishHttpFreeResponse (&RedfishResponse);
+
+  return;
+}
+
+/**
+  Initialize a Redfish configure handler.
+
+  This function will be called by the Redfish config driver to initialize each Redfish configure
+  handler.
+
+  @param[in]   This                     Pointer to EDKII_REDFISH_CONFIG_HANDLER_PROTOCOL instance.
+  @param[in]   RedfishConfigServiceInfo Redfish service informaiton.
+
+  @retval EFI_SUCCESS                  The handler has been initialized successfully.
+  @retval EFI_DEVICE_ERROR             Failed to create or configure the REST EX protocol instance.
+  @retval EFI_ALREADY_STARTED          This handler has already been initialized.
+  @retval Other                        Error happens during the initialization.
+
+**/
+EFI_STATUS
+EFIAPI
+RedfishBootstrapAccountInit (
+  IN  EDKII_REDFISH_CONFIG_HANDLER_PROTOCOL  *This,
+  IN  REDFISH_CONFIG_SERVICE_INFORMATION     *RedfishConfigServiceInfo
+  )
+{
+  REDFISH_BOOTSTRAP_ACCOUNT_PRIVATE  *Private;
+
+  Private = REDFISH_BOOTSTRAP_ACCOUNT_PRIVATE_FROM_PROTOCOL (This);
+
+  Private->RedfishService = RedfishCreateService (RedfishConfigServiceInfo);
+  if (Private->RedfishService == NULL) {
+    return EFI_DEVICE_ERROR;
+  }
+
+  Private->RestExHandle = RedfishConfigServiceInfo->RedfishServiceRestExHandle;
+
+  return EFI_SUCCESS;
+}
+
+/**
+  Stop a Redfish configure handler.
+
+  @param[in]   This                Pointer to EDKII_REDFISH_CONFIG_HANDLER_PROTOCOL instance.
+
+  @retval EFI_SUCCESS              This handler has been stoped successfully.
+  @retval Others                   Some error happened.
+
+**/
+EFI_STATUS
+EFIAPI
+RedfishBootstrapAccountStop (
+  IN  EDKII_REDFISH_CONFIG_HANDLER_PROTOCOL  *This
+  )
+{
+  REDFISH_BOOTSTRAP_ACCOUNT_PRIVATE  *Private;
+
+  Private = REDFISH_BOOTSTRAP_ACCOUNT_PRIVATE_FROM_PROTOCOL (This);
+
+  if (Private->RedfishService != NULL) {
+    RedfishCleanupService (Private->RedfishService);
+    Private->RedfishService = NULL;
+  }
+
+  return EFI_SUCCESS;
+}
+
+EDKII_REDFISH_CONFIG_HANDLER_PROTOCOL  mRedfishConfigHandler = {
+  RedfishBootstrapAccountInit,
+  RedfishBootstrapAccountStop
+};
+
+/**
+  Unloads an image.
+
+  @param[in]  ImageHandle           Handle that identifies the image to be unloaded.
+
+  @retval EFI_SUCCESS           The image has been unloaded.
+  @retval EFI_INVALID_PARAMETER ImageHandle is not a valid image handle.
+
+**/
+EFI_STATUS
+EFIAPI
+RedfishBootstrapAccountUnload (
+  IN EFI_HANDLE  ImageHandle
+  )
+{
+  EFI_STATUS  Status;
+
+  if (mBootstrapPrivate == NULL) {
+    return EFI_SUCCESS;
+  }
+
+  if (mBootstrapPrivate->RedfishEvent != NULL) {
+    gBS->CloseEvent (mBootstrapPrivate->RedfishEvent);
+  }
+
+  Status = gBS->UninstallProtocolInterface (
+                  mBootstrapPrivate->ImageHandle,
+                  &gEdkIIRedfishConfigHandlerProtocolGuid,
+                  (VOID *)&mBootstrapPrivate->Protocol
+                  );
+  if (EFI_ERROR (Status)) {
+    DEBUG ((DEBUG_ERROR, "%a: can not uninstall Redfish config handler protocol: %r\n", __func__, Status));
+  }
+
+  FreePool (mBootstrapPrivate);
+  mBootstrapPrivate = NULL;
+
+  return EFI_SUCCESS;
+}
+
+/**
+  This is the declaration of an EFI image entry point. This entry point is
+  the same for UEFI Applications, UEFI OS Loaders, and UEFI Drivers including
+  both device drivers and bus drivers.
+
+  @param[in]  ImageHandle       The firmware allocated handle for the UEFI image.
+  @param[in]  SystemTable       A pointer to the EFI System Table.
+
+  @retval EFI_SUCCESS           The operation completed successfully.
+  @retval Others                An unexpected error occurred.
+**/
+EFI_STATUS
+EFIAPI
+RedfishBootstrapAccountEntryPoint (
+  IN EFI_HANDLE        ImageHandle,
+  IN EFI_SYSTEM_TABLE  *SystemTable
+  )
+{
+  EFI_STATUS  Status;
+
+  if (mBootstrapPrivate != NULL) {
+    return EFI_ALREADY_STARTED;
+  }
+
+  mBootstrapPrivate = AllocateZeroPool (sizeof (REDFISH_BOOTSTRAP_ACCOUNT_PRIVATE));
+  if (mBootstrapPrivate == NULL) {
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  CopyMem (&mBootstrapPrivate->Protocol, &mRedfishConfigHandler, sizeof (EDKII_REDFISH_CONFIG_HANDLER_PROTOCOL));
+  Status = gBS->InstallProtocolInterface (
+                  &ImageHandle,
+                  &gEdkIIRedfishConfigHandlerProtocolGuid,
+                  EFI_NATIVE_INTERFACE,
+                  &mBootstrapPrivate->Protocol
+                  );
+  if (EFI_ERROR (Status)) {
+    DEBUG ((DEBUG_ERROR, "%a: can not install Redfish config handler protocol: %r\n", __func__, Status));
+    goto ON_ERROR;
+  }
+
+  //
+  // Register after provisioning event to remove bootstrap account.
+  //
+  Status = CreateAfterProvisioningEvent (
+             RedfishBootstrapAccountOnRedfishAfterProvisioning,
+             (VOID *)mBootstrapPrivate,
+             &mBootstrapPrivate->RedfishEvent
+             );
+  if (EFI_ERROR (Status)) {
+    DEBUG ((DEBUG_ERROR, "%a: failed to register after-provisioning event: %r\n", __func__, Status));
+    goto ON_ERROR;
+  }
+
+  return EFI_SUCCESS;
+
+ON_ERROR:
+
+  RedfishBootstrapAccountUnload (ImageHandle);
+
+  return Status;
+}
diff --git a/RedfishClientPkg/RedfishClient.fdf.inc b/RedfishClientPkg/RedfishClient.fdf.inc
index 154f641b2..47e5093f2 100644
--- a/RedfishClientPkg/RedfishClient.fdf.inc
+++ b/RedfishClientPkg/RedfishClient.fdf.inc
@@ -15,6 +15,7 @@
   INF RedfishClientPkg/RedfishFeatureCoreDxe/RedfishFeatureCoreDxe.inf
   INF RedfishClientPkg/RedfishETagDxe/RedfishETagDxe.inf
   INF RedfishClientPkg/RedfishConfigLangMapDxe/RedfishConfigLangMapDxe.inf
+  INF RedfishClientPkg/RedfishBootstrapAccountDxe/RedfishBootstrapAccountDxe.inf
   INF RedfishClientPkg/Features/Memory/V1_7_1/Dxe/MemoryDxe.inf
   INF RedfishClientPkg/Features/MemoryCollectionDxe/MemoryCollectionDxe.inf
   INF RedfishClientPkg/Features/ComputerSystem/v1_5_0/Dxe/ComputerSystemDxe.inf
-- 
2.34.1



-=-=-=-=-=-=-=-=-=-=-=-
Groups.io Links: You receive all messages sent to this group.
View/Reply Online (#118889): https://edk2.groups.io/g/devel/message/118889
Mute This Topic: https://groups.io/mt/106093445/7686176
Group Owner: devel+owner@edk2.groups.io
Unsubscribe: https://edk2.groups.io/g/devel/unsub [rebecca@openfw.io]
-=-=-=-=-=-=-=-=-=-=-=-



                 reply	other threads:[~2024-05-14 12:38 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-list from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20240514123810.28086-1-nicklew@nvidia.com \
    --to=devel@edk2.groups.io \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox