From: Oleg Drokin <green@linuxhacker.ru>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
linux-kernel@vger.kernel.org, devel@driverdev.osuosl.org
Cc: Andrew Korty <ajk@iu.edu>, Oleg Drokin <oleg.drokin@intel.com>
Subject: [PATCH 04/47] staging/lustre/gss: Shared key mechanism & flavors
Date: Sun, 27 Apr 2014 13:06:28 -0400 [thread overview]
Message-ID: <1398618431-29757-5-git-send-email-green@linuxhacker.ru> (raw)
In-Reply-To: <1398618431-29757-1-git-send-email-green@linuxhacker.ru>
From: Andrew Korty <ajk@iu.edu>
Implement security flavors and GSSAPI mechanism to perform shared key
authentication (ski) and encryption (skpi).
Signed-off-by: Andrew Korty <ajk@iu.edu>
Reviewed-on: http://review.whamcloud.com/8629
Intel-bug-id: https://jira.hpdd.intel.com/browse/LU-3289
Reviewed-by: Andreas Dilger <andreas.dilger@intel.com>
Reviewed-by: Ken Hornstein <kenh@cmf.nrl.navy.mil>
Signed-off-by: Oleg Drokin <oleg.drokin@intel.com>
---
drivers/staging/lustre/lustre/include/lustre_sec.h | 17 ++
drivers/staging/lustre/lustre/ptlrpc/gss/Makefile | 3 +-
.../lustre/lustre/ptlrpc/gss/gss_internal.h | 3 +
.../staging/lustre/lustre/ptlrpc/gss/gss_sk_mech.c | 226 +++++++++++++++++++++
drivers/staging/lustre/lustre/ptlrpc/gss/sec_gss.c | 8 +-
drivers/staging/lustre/lustre/ptlrpc/sec.c | 8 +
6 files changed, 263 insertions(+), 2 deletions(-)
create mode 100644 drivers/staging/lustre/lustre/ptlrpc/gss/gss_sk_mech.c
diff --git a/drivers/staging/lustre/lustre/include/lustre_sec.h b/drivers/staging/lustre/lustre/include/lustre_sec.h
index 40d463f..e46c0e5 100644
--- a/drivers/staging/lustre/lustre/include/lustre_sec.h
+++ b/drivers/staging/lustre/lustre/include/lustre_sec.h
@@ -103,6 +103,7 @@ enum sptlrpc_mech_plain {
enum sptlrpc_mech_gss {
SPTLRPC_MECH_GSS_NULL = 0,
SPTLRPC_MECH_GSS_KRB5 = 1,
+ SPTLRPC_MECH_GSS_SK = 2,
SPTLRPC_MECH_GSS_MAX,
};
@@ -180,6 +181,10 @@ enum sptlrpc_bulk_service {
MAKE_BASE_SUBFLVR(SPTLRPC_MECH_GSS_KRB5, SPTLRPC_SVC_INTG)
#define SPTLRPC_SUBFLVR_KRB5P \
MAKE_BASE_SUBFLVR(SPTLRPC_MECH_GSS_KRB5, SPTLRPC_SVC_PRIV)
+#define SPTLRPC_SUBFLVR_SKI \
+ MAKE_BASE_SUBFLVR(SPTLRPC_MECH_GSS_SK, SPTLRPC_SVC_INTG)
+#define SPTLRPC_SUBFLVR_SKPI \
+ MAKE_BASE_SUBFLVR(SPTLRPC_MECH_GSS_SK, SPTLRPC_SVC_PRIV)
/*
* "end user" flavors
@@ -226,6 +231,18 @@ enum sptlrpc_bulk_service {
SPTLRPC_SVC_PRIV, \
SPTLRPC_BULK_DEFAULT, \
SPTLRPC_BULK_SVC_PRIV)
+#define SPTLRPC_FLVR_SKI \
+ MAKE_FLVR(SPTLRPC_POLICY_GSS, \
+ SPTLRPC_MECH_GSS_SK, \
+ SPTLRPC_SVC_INTG, \
+ SPTLRPC_BULK_DEFAULT, \
+ SPTLRPC_BULK_SVC_PRIV)
+#define SPTLRPC_FLVR_SKPI \
+ MAKE_FLVR(SPTLRPC_POLICY_GSS, \
+ SPTLRPC_MECH_GSS_SK, \
+ SPTLRPC_SVC_PRIV, \
+ SPTLRPC_BULK_DEFAULT, \
+ SPTLRPC_BULK_SVC_PRIV)
#define SPTLRPC_FLVR_DEFAULT SPTLRPC_FLVR_NULL
diff --git a/drivers/staging/lustre/lustre/ptlrpc/gss/Makefile b/drivers/staging/lustre/lustre/ptlrpc/gss/Makefile
index ab16596..bf16b97 100644
--- a/drivers/staging/lustre/lustre/ptlrpc/gss/Makefile
+++ b/drivers/staging/lustre/lustre/ptlrpc/gss/Makefile
@@ -2,7 +2,8 @@ obj-$(CONFIG_LUSTRE_FS) := ptlrpc_gss.o
ptlrpc_gss-y := sec_gss.o gss_bulk.o gss_cli_upcall.o gss_svc_upcall.o \
gss_rawobj.o lproc_gss.o gss_generic_token.o \
- gss_mech_switch.o gss_krb5_mech.o gss_null_mech.o
+ gss_mech_switch.o gss_krb5_mech.o gss_null_mech.o \
+ gss_sk_mech.o
ccflags-y := -I$(src)/../include
diff --git a/drivers/staging/lustre/lustre/ptlrpc/gss/gss_internal.h b/drivers/staging/lustre/lustre/ptlrpc/gss/gss_internal.h
index 1a0c7d5..a693a4a 100644
--- a/drivers/staging/lustre/lustre/ptlrpc/gss/gss_internal.h
+++ b/drivers/staging/lustre/lustre/ptlrpc/gss/gss_internal.h
@@ -506,6 +506,9 @@ void cleanup_null_module(void);
int __init init_kerberos_module(void);
void __exit cleanup_kerberos_module(void);
+/* gss_sk_mech.c */
+int __init init_sk_module(void);
+void cleanup_sk_module(void);
/* debug */
static inline
diff --git a/drivers/staging/lustre/lustre/ptlrpc/gss/gss_sk_mech.c b/drivers/staging/lustre/lustre/ptlrpc/gss/gss_sk_mech.c
new file mode 100644
index 0000000..df31b18
--- /dev/null
+++ b/drivers/staging/lustre/lustre/ptlrpc/gss/gss_sk_mech.c
@@ -0,0 +1,226 @@
+/*
+ * GPL HEADER START
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 only,
+ * as published by the Free Software Foundation.
+ *
+ * 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 version 2 for more details (a copy is included
+ * in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; If not, see
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * GPL HEADER END
+ */
+/*
+ * Copyright (C) 2013, Trustees of Indiana University
+ * Author: Andrew Korty <ajk@iu.edu>
+ */
+
+#define DEBUG_SUBSYSTEM S_SEC
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/crypto.h>
+#include <linux/mutex.h>
+
+#include <obd.h>
+#include <obd_class.h>
+#include <obd_support.h>
+
+#include "gss_err.h"
+#include "gss_internal.h"
+#include "gss_api.h"
+#include "gss_asn1.h"
+
+struct sk_ctx {
+};
+
+static
+__u32 gss_import_sec_context_sk(rawobj_t *inbuf, struct gss_ctx *gss_context)
+{
+ struct sk_ctx *sk_context;
+
+ if (inbuf == NULL || inbuf->data == NULL)
+ return GSS_S_FAILURE;
+
+ OBD_ALLOC_PTR(sk_context);
+ if (sk_context == NULL)
+ return GSS_S_FAILURE;
+
+ gss_context->internal_ctx_id = sk_context;
+ CDEBUG(D_SEC, "succesfully imported sk context\n");
+
+ return GSS_S_COMPLETE;
+}
+
+static
+__u32 gss_copy_reverse_context_sk(struct gss_ctx *gss_context_old,
+ struct gss_ctx *gss_context_new)
+{
+ struct sk_ctx *sk_context_old;
+ struct sk_ctx *sk_context_new;
+
+ OBD_ALLOC_PTR(sk_context_new);
+ if (sk_context_new == NULL)
+ return GSS_S_FAILURE;
+
+ sk_context_old = gss_context_old->internal_ctx_id;
+ memcpy(sk_context_new, sk_context_old, sizeof(*sk_context_new));
+ gss_context_new->internal_ctx_id = sk_context_new;
+ CDEBUG(D_SEC, "succesfully copied reverse sk context\n");
+
+ return GSS_S_COMPLETE;
+}
+
+static
+__u32 gss_inquire_context_sk(struct gss_ctx *gss_context,
+ unsigned long *endtime)
+{
+ *endtime = 0;
+ return GSS_S_COMPLETE;
+}
+
+static
+__u32 gss_get_mic_sk(struct gss_ctx *gss_context,
+ int message_count,
+ rawobj_t *messages,
+ int iov_count,
+ lnet_kiov_t *iovs,
+ rawobj_t *token)
+{
+ token->data = NULL;
+ token->len = 0;
+
+ return GSS_S_COMPLETE;
+}
+
+static
+__u32 gss_verify_mic_sk(struct gss_ctx *gss_context,
+ int message_count,
+ rawobj_t *messages,
+ int iov_count,
+ lnet_kiov_t *iovs,
+ rawobj_t *token)
+{
+ return GSS_S_COMPLETE;
+}
+
+static
+__u32 gss_wrap_sk(struct gss_ctx *gss_context, rawobj_t *gss_header,
+ rawobj_t *message, int message_buffer_length,
+ rawobj_t *token)
+{
+ return GSS_S_COMPLETE;
+}
+
+static
+__u32 gss_unwrap_sk(struct gss_ctx *gss_context, rawobj_t *gss_header,
+ rawobj_t *token, rawobj_t *message)
+{
+ return GSS_S_COMPLETE;
+}
+
+static
+__u32 gss_prep_bulk_sk(struct gss_ctx *gss_context,
+ struct ptlrpc_bulk_desc *desc)
+{
+ return GSS_S_COMPLETE;
+}
+
+static
+__u32 gss_wrap_bulk_sk(struct gss_ctx *gss_context,
+ struct ptlrpc_bulk_desc *desc, rawobj_t *token,
+ int adj_nob)
+{
+ return GSS_S_COMPLETE;
+}
+
+static
+__u32 gss_unwrap_bulk_sk(struct gss_ctx *gss_context,
+ struct ptlrpc_bulk_desc *desc,
+ rawobj_t *token, int adj_nob)
+{
+ return GSS_S_COMPLETE;
+}
+
+static
+void gss_delete_sec_context_sk(void *internal_context)
+{
+ struct sk_ctx *sk_context = internal_context;
+
+ OBD_FREE_PTR(sk_context);
+}
+
+int gss_display_sk(struct gss_ctx *gss_context, char *buf, int bufsize)
+{
+ return snprintf(buf, bufsize, "sk");
+}
+
+static struct gss_api_ops gss_sk_ops = {
+ .gss_import_sec_context = gss_import_sec_context_sk,
+ .gss_copy_reverse_context = gss_copy_reverse_context_sk,
+ .gss_inquire_context = gss_inquire_context_sk,
+ .gss_get_mic = gss_get_mic_sk,
+ .gss_verify_mic = gss_verify_mic_sk,
+ .gss_wrap = gss_wrap_sk,
+ .gss_unwrap = gss_unwrap_sk,
+ .gss_prep_bulk = gss_prep_bulk_sk,
+ .gss_wrap_bulk = gss_wrap_bulk_sk,
+ .gss_unwrap_bulk = gss_unwrap_bulk_sk,
+ .gss_delete_sec_context = gss_delete_sec_context_sk,
+ .gss_display = gss_display_sk,
+};
+
+static struct subflavor_desc gss_sk_sfs[] = {
+ {
+ .sf_subflavor = SPTLRPC_SUBFLVR_SKI,
+ .sf_qop = 0,
+ .sf_service = SPTLRPC_SVC_INTG,
+ .sf_name = "ski"
+ },
+ {
+ .sf_subflavor = SPTLRPC_SUBFLVR_SKPI,
+ .sf_qop = 0,
+ .sf_service = SPTLRPC_SVC_PRIV,
+ .sf_name = "skpi"
+ },
+};
+
+/*
+ * currently we leave module owner NULL
+ */
+static struct gss_api_mech gss_sk_mech = {
+ .gm_owner = NULL, /*THIS_MODULE, */
+ .gm_name = "sk",
+ .gm_oid = (rawobj_t) {
+ 12,
+ "\053\006\001\004\001\311\146\215\126\001\000\001",
+ },
+ .gm_ops = &gss_sk_ops,
+ .gm_sf_num = 2,
+ .gm_sfs = gss_sk_sfs,
+};
+
+int __init init_sk_module(void)
+{
+ int status;
+
+ status = lgss_mech_register(&gss_sk_mech);
+ if (status)
+ CERROR("Failed to register sk gss mechanism!\n");
+
+ return status;
+}
+
+void cleanup_sk_module(void)
+{
+ lgss_mech_unregister(&gss_sk_mech);
+}
diff --git a/drivers/staging/lustre/lustre/ptlrpc/gss/sec_gss.c b/drivers/staging/lustre/lustre/ptlrpc/gss/sec_gss.c
index a3b4b21..91a43d1 100644
--- a/drivers/staging/lustre/lustre/ptlrpc/gss/sec_gss.c
+++ b/drivers/staging/lustre/lustre/ptlrpc/gss/sec_gss.c
@@ -2840,12 +2840,16 @@ int __init sptlrpc_gss_init(void)
if (rc)
goto out_null;
+ rc = init_sk_module();
+ if (rc)
+ goto out_kerberos;
+
/* register policy after all other stuff be initialized, because it
* might be in used immediately after the registration. */
rc = gss_init_keyring();
if (rc)
- goto out_kerberos;
+ goto out_sk;
#ifdef HAVE_GSS_PIPEFS
rc = gss_init_pipefs();
@@ -2862,6 +2866,8 @@ out_keyring:
gss_exit_keyring();
#endif
+out_sk:
+ cleanup_sk_module();
out_kerberos:
cleanup_kerberos_module();
out_null:
diff --git a/drivers/staging/lustre/lustre/ptlrpc/sec.c b/drivers/staging/lustre/lustre/ptlrpc/sec.c
index 639791c..a6d0f73 100644
--- a/drivers/staging/lustre/lustre/ptlrpc/sec.c
+++ b/drivers/staging/lustre/lustre/ptlrpc/sec.c
@@ -167,6 +167,10 @@ __u32 sptlrpc_name2flavor_base(const char *name)
return SPTLRPC_FLVR_KRB5I;
if (!strcmp(name, "krb5p"))
return SPTLRPC_FLVR_KRB5P;
+ if (!strcmp(name, "ski"))
+ return SPTLRPC_FLVR_SKI;
+ if (!strcmp(name, "skpi"))
+ return SPTLRPC_FLVR_SKPI;
return SPTLRPC_FLVR_INVALID;
}
@@ -190,6 +194,10 @@ const char *sptlrpc_flavor2name_base(__u32 flvr)
return "krb5i";
else if (base == SPTLRPC_FLVR_BASE(SPTLRPC_FLVR_KRB5P))
return "krb5p";
+ else if (base == SPTLRPC_FLVR_BASE(SPTLRPC_FLVR_SKI))
+ return "ski";
+ else if (base == SPTLRPC_FLVR_BASE(SPTLRPC_FLVR_SKPI))
+ return "skpi";
CERROR("invalid wire flavor 0x%x\n", flvr);
return "invalid";
--
1.8.5.3
next prev parent reply other threads:[~2014-04-27 17:24 UTC|newest]
Thread overview: 65+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-04-27 17:06 [PATCH 00/47] Lustre fixes and cleanups Oleg Drokin
2014-04-27 17:06 ` [PATCH 01/47] staging/lustre/ptlrpc: Fix assertion failure of null_alloc_rs() Oleg Drokin
2014-04-27 17:06 ` [PATCH 02/47] staging/lustre/ptlrpc: Remove log message about export timer update Oleg Drokin
2014-04-27 17:06 ` [PATCH 03/47] staging/lustre/gss: gssnull security flavor Oleg Drokin
2014-04-27 17:06 ` Oleg Drokin [this message]
2014-04-27 17:20 ` [PATCH 04/47] staging/lustre/gss: Shared key mechanism & flavors Greg Kroah-Hartman
2014-04-27 17:06 ` [PATCH 05/47] staging/lustre/osc: don't activate deactivated obd_import Oleg Drokin
2014-04-27 17:06 ` [PATCH 06/47] staging/lustre/lnet: Dropped messages are not accounted correctly Oleg Drokin
2014-04-27 17:06 ` [PATCH 07/47] staging/lustre/ldlm: Hold lock when clearing flag Oleg Drokin
2014-04-27 17:06 ` [PATCH 08/47] staging/lustre/clio: clear nowait flag agl lock re-enqueue Oleg Drokin
2014-04-27 17:06 ` [PATCH 09/47] staging/lustre/ptlrpc: don't try to recover no_recov connection Oleg Drokin
2014-04-27 17:06 ` [PATCH 10/47] staging/lustre/gss: fix few issues found by Klocwork Insight tool Oleg Drokin
2014-04-27 17:06 ` [PATCH 11/47] staging/lustre/ptlrpc: add rpc_cache Oleg Drokin
2014-04-29 9:46 ` Dan Carpenter
2014-04-30 3:22 ` Oleg Drokin
2014-04-27 17:06 ` [PATCH 12/47] staging/lustre: restore __GFP_WAIT flag to memalloc calls Oleg Drokin
2014-04-27 17:06 ` [PATCH 13/47] staging/lustre/gss: fix uninitialized variable Oleg Drokin
2014-04-27 17:06 ` [PATCH 14/47] staging/lustre: quiet console permission error messages Oleg Drokin
2014-04-27 17:06 ` [PATCH 15/47] staging/lustre/lov: remove unused lov llog code Oleg Drokin
2014-04-27 17:06 ` [PATCH 16/47] staging/lustre/obdclass: remove uses of lov_stripe_md Oleg Drokin
2014-04-27 17:06 ` [PATCH 17/47] staging/lustre/hsm: count NULL terminator in hai_zero/hal_size Oleg Drokin
2014-04-27 17:06 ` [PATCH 18/47] staging/lustre/hsm: HSM requests not delivered Oleg Drokin
2014-04-29 9:08 ` Dan Carpenter
2014-04-30 3:31 ` Oleg Drokin
2014-04-27 17:06 ` [PATCH 19/47] staging/lustre: fix permission problem of setfacl Oleg Drokin
2014-04-27 17:06 ` [PATCH 20/47] staging/lustre/llite: issue OST_SYNC for fsync() Oleg Drokin
2014-04-27 17:06 ` [PATCH 21/47] staging/lustre/llite: deadlock taking lli_trunc_sem during file write Oleg Drokin
2014-04-27 17:06 ` [PATCH 22/47] staging/lustre/lov: to not hold sub locks at initialization Oleg Drokin
2014-04-27 17:06 ` [PATCH 23/47] staging/lustre: Limit reply buffer size Oleg Drokin
2014-04-27 17:06 ` [PATCH 24/47] staging/lustre/llite: Avoid statahead thread start/stop deadlocks Oleg Drokin
2014-04-27 17:06 ` [PATCH 25/47] stagaing/lustre: Improve statahead debug messages Oleg Drokin
2014-04-27 17:06 ` [PATCH 26/47] staging/lustre/llite: access layout version under a lock Oleg Drokin
2014-04-27 17:06 ` [PATCH 27/47] staging/lustre: shrink lu_object_header by 8 bytes on x86_64 Oleg Drokin
2014-04-27 17:06 ` [PATCH 28/47] staging/lustre/ldlm: fix NULL pointer dereference Oleg Drokin
2014-04-27 17:06 ` [PATCH 29/47] staging/lustre/lnet: lnet: fix issues found by Klocwork Insight tool Oleg Drokin
2014-04-27 17:25 ` Greg Kroah-Hartman
2014-04-27 17:06 ` [PATCH 30/47] staging/lustre/mdc: fix issue " Oleg Drokin
2014-04-29 10:20 ` Dan Carpenter
2014-04-27 17:06 ` [PATCH 31/47] staging/lustre/libcfs: fix issues " Oleg Drokin
2014-04-27 17:06 ` [PATCH 32/47] staging/lustre/lnet: NI shutdown may loop forever Oleg Drokin
2014-04-27 17:06 ` [PATCH 33/47] staging/lustre: remove lustre/include/ioctl.h Oleg Drokin
2014-04-27 17:06 ` [PATCH 34/47] staging/lustre/libcfs: add CPU table functions for uniprocessor Oleg Drokin
2014-04-29 10:35 ` Dan Carpenter
2014-04-27 17:06 ` [PATCH 35/47] staging/lustre: replace semaphores with mutexes Oleg Drokin
2014-04-27 17:07 ` [PATCH 36/47] staging/lustre/clio: replace semaphore with mutex Oleg Drokin
2014-04-27 17:07 ` [PATCH 37/47] staging/lustre/llite: Do not rate limit dirty page discard warning Oleg Drokin
2014-04-27 17:07 ` [PATCH 38/47] staging/lustre/lloop: avoid panic during blockdev_info Oleg Drokin
2014-04-27 17:07 ` [PATCH 39/47] staging/lustre/clio: Solve a race in cl_lock_put Oleg Drokin
2014-04-27 17:07 ` [PATCH 40/47] staging/lustre/mdc: use cl_max_mds_md to pack getattr RPC Oleg Drokin
2014-04-27 17:07 ` [PATCH 41/47] staging/lustre/llite: remove dead code Oleg Drokin
2014-04-29 11:02 ` Dan Carpenter
2014-04-29 19:16 ` Hammond, John
2014-04-29 20:17 ` Dan Carpenter
2014-04-30 3:21 ` Oleg Drokin
2014-04-30 8:01 ` Dan Carpenter
2014-04-29 11:12 ` Richard Weinberger
2014-04-27 17:07 ` [PATCH 42/47] staging/lustre: remove assertion of spin_is_locked() Oleg Drokin
2014-04-27 17:07 ` [PATCH 43/47] staging/lustre/osc: Update inode timestamp for lockless IO as well Oleg Drokin
2014-04-27 17:07 ` [PATCH 44/47] staging/lustre: Always clamp cdls_delay between min and max Oleg Drokin
2014-04-27 17:07 ` [PATCH 45/47] staging/lustre: pass fsync() range through RPC/IO stack Oleg Drokin
2014-04-27 17:07 ` [PATCH 46/47] staging/lustre: Fix unsafe userspace access in many proc files Oleg Drokin
2014-04-27 17:30 ` Greg Kroah-Hartman
2014-04-27 17:07 ` [PATCH 47/47] staging/lustre/llite: prevent buffer overflow in fiemap Oleg Drokin
2014-04-27 17:33 ` [PATCH 00/47] Lustre fixes and cleanups Greg Kroah-Hartman
2014-04-27 18:28 ` Oleg Drokin
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-all 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=1398618431-29757-5-git-send-email-green@linuxhacker.ru \
--to=green@linuxhacker.ru \
--cc=ajk@iu.edu \
--cc=devel@driverdev.osuosl.org \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=oleg.drokin@intel.com \
/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;
as well as URLs for NNTP newsgroup(s).