From: Paul Moore <paul.moore@hp.com>
To: selinux@tycho.nsa.gov, linux-security-module@vger.kernel.org
Cc: vyekkirala@TrustedCS.com, chanson@TrustedCS.com
Subject: [RFC PATCH v8 10/18] SELinux: Add a network node caching mechanism similar to the sel_netif_*() functions
Date: Fri, 14 Dec 2007 16:50:46 -0500 [thread overview]
Message-ID: <20071214215046.10069.12365.stgit@flek.lan> (raw)
In-Reply-To: <20071214213548.10069.59135.stgit@flek.lan>
This patch adds a SELinux IP address/node SID caching mechanism similar to the
sel_netif_*() functions. The node SID queries in the SELinux hooks files are
also modified to take advantage of this new functionality. In addition, remove
the address length information from the sk_buff parsing routines as it is
redundant since we already have the address family.
---
security/selinux/Makefile | 9 +
security/selinux/hooks.c | 33 ++-
security/selinux/include/netnode.h | 32 +++
security/selinux/include/objsec.h | 9 +
security/selinux/netnode.c | 351 ++++++++++++++++++++++++++++++++++++
5 files changed, 417 insertions(+), 17 deletions(-)
diff --git a/security/selinux/Makefile b/security/selinux/Makefile
index dc3502e..00afd85 100644
--- a/security/selinux/Makefile
+++ b/security/selinux/Makefile
@@ -4,7 +4,14 @@
obj-$(CONFIG_SECURITY_SELINUX) := selinux.o ss/
-selinux-y := avc.o hooks.o selinuxfs.o netlink.o nlmsgtab.o netif.o exports.o
+selinux-y := avc.o \
+ hooks.o \
+ selinuxfs.o \
+ netlink.o \
+ nlmsgtab.o \
+ netif.o \
+ netnode.o \
+ exports.o
selinux-$(CONFIG_SECURITY_NETWORK_XFRM) += xfrm.o
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index e429a8c..05b58cc 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -76,6 +76,7 @@
#include "avc.h"
#include "objsec.h"
#include "netif.h"
+#include "netnode.h"
#include "xfrm.h"
#include "netlabel.h"
@@ -3158,7 +3159,7 @@ out:
#endif /* IPV6 */
static int selinux_parse_skb(struct sk_buff *skb, struct avc_audit_data *ad,
- char **addrp, int *len, int src, u8 *proto)
+ char **addrp, int src, u8 *proto)
{
int ret = 0;
@@ -3167,7 +3168,6 @@ static int selinux_parse_skb(struct sk_buff *skb, struct avc_audit_data *ad,
ret = selinux_parse_skb_ipv4(skb, ad, proto);
if (ret || !addrp)
break;
- *len = 4;
*addrp = (char *)(src ? &ad->u.net.v4info.saddr :
&ad->u.net.v4info.daddr);
break;
@@ -3177,7 +3177,6 @@ static int selinux_parse_skb(struct sk_buff *skb, struct avc_audit_data *ad,
ret = selinux_parse_skb_ipv6(skb, ad, proto);
if (ret || !addrp)
break;
- *len = 16;
*addrp = (char *)(src ? &ad->u.net.v6info.saddr :
&ad->u.net.v6info.daddr);
break;
@@ -3377,7 +3376,7 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in
break;
}
- err = security_node_sid(family, addrp, addrlen, &sid);
+ err = sel_netnode_sid(addrp, family, &sid);
if (err)
goto out;
@@ -3589,7 +3588,8 @@ static int selinux_socket_unix_may_send(struct socket *sock,
}
static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb,
- struct avc_audit_data *ad, u16 family, char *addrp, int len)
+ struct avc_audit_data *ad,
+ u16 family, char *addrp)
{
int err = 0;
u32 netif_perm, node_perm, node_sid, if_sid, recv_perm = 0;
@@ -3649,7 +3649,7 @@ static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb,
if (err)
goto out;
- err = security_node_sid(family, addrp, len, &node_sid);
+ err = sel_netnode_sid(addrp, family, &node_sid);
if (err)
goto out;
@@ -3678,7 +3678,7 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
{
u16 family;
char *addrp;
- int len, err = 0;
+ int err = 0;
struct avc_audit_data ad;
struct sk_security_struct *sksec = sk->sk_security;
@@ -3694,13 +3694,12 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
ad.u.net.netif = skb->iif;
ad.u.net.family = family;
- err = selinux_parse_skb(skb, &ad, &addrp, &len, 1, NULL);
+ err = selinux_parse_skb(skb, &ad, &addrp, 1, NULL);
if (err)
goto out;
if (selinux_compat_net)
- err = selinux_sock_rcv_skb_compat(sk, skb, &ad, family,
- addrp, len);
+ err = selinux_sock_rcv_skb_compat(sk, skb, &ad, family, addrp);
else
err = avc_has_perm(sksec->sid, skb->secmark, SECCLASS_PACKET,
PACKET__RECV, &ad);
@@ -3922,9 +3921,11 @@ out:
#ifdef CONFIG_NETFILTER
-static int selinux_ip_postroute_last_compat(struct sock *sk, struct net_device *dev,
+static int selinux_ip_postroute_last_compat(struct sock *sk,
+ struct net_device *dev,
struct avc_audit_data *ad,
- u16 family, char *addrp, int len)
+ u16 family,
+ char *addrp)
{
int err = 0;
u32 netif_perm, node_perm, node_sid, if_sid, send_perm = 0;
@@ -3975,7 +3976,7 @@ static int selinux_ip_postroute_last_compat(struct sock *sk, struct net_device *
if (err)
goto out;
- err = security_node_sid(family, addrp, len, &node_sid);
+ err = sel_netnode_sid(addrp, family, &node_sid);
if (err)
goto out;
@@ -4009,7 +4010,7 @@ static unsigned int selinux_ip_postroute_last(unsigned int hooknum,
u16 family)
{
char *addrp;
- int len, err = 0;
+ int err = 0;
struct sock *sk;
struct avc_audit_data ad;
struct net_device *dev = (struct net_device *)out;
@@ -4026,13 +4027,13 @@ static unsigned int selinux_ip_postroute_last(unsigned int hooknum,
ad.u.net.netif = dev->ifindex;
ad.u.net.family = family;
- err = selinux_parse_skb(skb, &ad, &addrp, &len, 0, &proto);
+ err = selinux_parse_skb(skb, &ad, &addrp, 0, &proto);
if (err)
goto out;
if (selinux_compat_net)
err = selinux_ip_postroute_last_compat(sk, dev, &ad,
- family, addrp, len);
+ family, addrp);
else
err = avc_has_perm(sksec->sid, skb->secmark, SECCLASS_PACKET,
PACKET__SEND, &ad);
diff --git a/security/selinux/include/netnode.h b/security/selinux/include/netnode.h
new file mode 100644
index 0000000..1b94450
--- /dev/null
+++ b/security/selinux/include/netnode.h
@@ -0,0 +1,32 @@
+/*
+ * Network node table
+ *
+ * SELinux must keep a mapping of network nodes to labels/SIDs. This
+ * mapping is maintained as part of the normal policy but a fast cache is
+ * needed to reduce the lookup overhead since most of these queries happen on
+ * a per-packet basis.
+ *
+ * Author: Paul Moore <paul.moore@hp.com>
+ *
+ */
+
+/*
+ * (c) Copyright Hewlett-Packard Development Company, L.P., 2007
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License 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 for more details.
+ *
+ */
+
+#ifndef _SELINUX_NETNODE_H
+#define _SELINUX_NETNODE_H
+
+int sel_netnode_sid(void *addr, u16 family, u32 *sid);
+
+#endif
diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h
index e41a2aa..6140734 100644
--- a/security/selinux/include/objsec.h
+++ b/security/selinux/include/objsec.h
@@ -99,6 +99,15 @@ struct netif_security_struct {
u32 sid; /* SID for this interface */
};
+struct netnode_security_struct {
+ union {
+ __be32 ipv4; /* IPv4 node address */
+ struct in6_addr ipv6; /* IPv6 node address */
+ } addr;
+ u32 sid; /* SID for this node */
+ u16 family; /* address family */
+};
+
struct sk_security_struct {
struct sock *sk; /* back pointer to sk object */
u32 sid; /* SID of this object */
diff --git a/security/selinux/netnode.c b/security/selinux/netnode.c
new file mode 100644
index 0000000..0cd66b8
--- /dev/null
+++ b/security/selinux/netnode.c
@@ -0,0 +1,351 @@
+/*
+ * Network node table
+ *
+ * SELinux must keep a mapping of network nodes to labels/SIDs. This
+ * mapping is maintained as part of the normal policy but a fast cache is
+ * needed to reduce the lookup overhead since most of these queries happen on
+ * a per-packet basis.
+ *
+ * Author: Paul Moore <paul.moore@hp.com>
+ *
+ * This code is heavily based on the "netif" concept originally developed by
+ * James Morris <jmorris@redhat.com>
+ * (see security/selinux/netif.c for more information)
+ *
+ */
+
+/*
+ * (c) Copyright Hewlett-Packard Development Company, L.P., 2007
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License 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 for more details.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/rcupdate.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <asm/bug.h>
+
+#include "objsec.h"
+
+#define SEL_NETNODE_HASH_SIZE 256
+#define SEL_NETNODE_HASH_BKT_LIMIT 16
+
+struct sel_netnode {
+ struct netnode_security_struct nsec;
+
+ struct list_head list;
+ struct rcu_head rcu;
+};
+
+/* NOTE: we are using a combined hash table for both IPv4 and IPv6, the reason
+ * for this is that I suspect most users will not make heavy use of both
+ * address families at the same time so one table will usually end up wasted,
+ * if this becomes a problem we can always add a hash table for each address
+ * family later */
+
+static LIST_HEAD(sel_netnode_list);
+static DEFINE_SPINLOCK(sel_netnode_lock);
+static struct list_head sel_netnode_hash[SEL_NETNODE_HASH_SIZE];
+
+/**
+ * sel_netnode_free - Frees a node entry
+ * @p: the entry's RCU field
+ *
+ * Description:
+ * This function is designed to be used as a callback to the call_rcu()
+ * function so that memory allocated to a hash table node entry can be
+ * released safely.
+ *
+ */
+static void sel_netnode_free(struct rcu_head *p)
+{
+ struct sel_netnode *node = container_of(p, struct sel_netnode, rcu);
+ kfree(node);
+}
+
+/**
+ * sel_netnode_hashfn_ipv4 - IPv4 hashing function for the node table
+ * @addr: IPv4 address
+ *
+ * Description:
+ * This is the IPv4 hashing function for the node interface table, it returns
+ * the bucket number for the given IP address.
+ *
+ */
+static u32 sel_netnode_hashfn_ipv4(__be32 addr)
+{
+ /* at some point we should determine if the mismatch in byte order
+ * affects the hash function dramatically */
+ return (addr & (SEL_NETNODE_HASH_SIZE - 1));
+}
+
+/**
+ * sel_netnode_hashfn_ipv6 - IPv6 hashing function for the node table
+ * @addr: IPv6 address
+ *
+ * Description:
+ * This is the IPv6 hashing function for the node interface table, it returns
+ * the bucket number for the given IP address.
+ *
+ */
+static u32 sel_netnode_hashfn_ipv6(const struct in6_addr *addr)
+{
+ /* just hash the least significant 32 bits to keep things fast (they
+ * are the most likely to be different anyway), we can revisit this
+ * later if needed */
+ return (addr->s6_addr32[3] & (SEL_NETNODE_HASH_SIZE - 1));
+}
+
+/**
+ * sel_netnode_find - Search for a node record
+ * @addr: IP address
+ * @family: address family
+ *
+ * Description:
+ * Search the network node table and return the record matching @addr. If an
+ * entry can not be found in the table return NULL.
+ *
+ */
+static struct sel_netnode *sel_netnode_find(const void *addr, u16 family)
+{
+ u32 idx;
+ struct sel_netnode *node;
+
+ switch (family) {
+ case PF_INET:
+ idx = sel_netnode_hashfn_ipv4(*(__be32 *)addr);
+ break;
+ case PF_INET6:
+ idx = sel_netnode_hashfn_ipv6(addr);
+ break;
+ default:
+ BUG();
+ }
+
+ list_for_each_entry_rcu(node, &sel_netnode_hash[idx], list)
+ if (node->nsec.family == family)
+ switch (family) {
+ case PF_INET:
+ if (node->nsec.addr.ipv4 == *(__be32 *)addr)
+ return node;
+ break;
+ case PF_INET6:
+ if (ipv6_addr_cmp(&node->nsec.addr.ipv6,
+ addr) == 0)
+ return node;
+ break;
+ }
+
+ return NULL;
+}
+
+/**
+ * sel_netnode_insert - Insert a new node into the table
+ * @node: the new node record
+ *
+ * Description:
+ * Add a new node record to the network address hash table. Returns zero on
+ * success, negative values on failure.
+ *
+ */
+static int sel_netnode_insert(struct sel_netnode *node)
+{
+ u32 idx;
+ u32 count = 0;
+ struct sel_netnode *iter;
+
+ switch (node->nsec.family) {
+ case PF_INET:
+ idx = sel_netnode_hashfn_ipv4(node->nsec.addr.ipv4);
+ break;
+ case PF_INET6:
+ idx = sel_netnode_hashfn_ipv6(&node->nsec.addr.ipv6);
+ break;
+ default:
+ BUG();
+ }
+ list_add_rcu(&node->list, &sel_netnode_hash[idx]);
+
+ /* we need to impose a limit on the growth of the hash table so check
+ * this bucket to make sure it is within the specified bounds */
+ list_for_each_entry(iter, &sel_netnode_hash[idx], list)
+ if (++count > SEL_NETNODE_HASH_BKT_LIMIT) {
+ list_del_rcu(&iter->list);
+ call_rcu(&iter->rcu, sel_netnode_free);
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * sel_netnode_destroy - Remove a node record from the table
+ * @node: the existing node record
+ *
+ * Description:
+ * Remove an existing node record from the network address table.
+ *
+ */
+static void sel_netnode_destroy(struct sel_netnode *node)
+{
+ list_del_rcu(&node->list);
+ call_rcu(&node->rcu, sel_netnode_free);
+}
+
+/**
+ * sel_netnode_sid_slow - Lookup the SID of a network address using the policy
+ * @addr: the IP address
+ * @family: the address family
+ * @sid: node SID
+ *
+ * Description:
+ * This function determines the SID of a network address by quering the
+ * security policy. The result is added to the network address table to
+ * speedup future queries. Returns zero on success, negative values on
+ * failure.
+ *
+ */
+static int sel_netnode_sid_slow(void *addr, u16 family, u32 *sid)
+{
+ int ret;
+ struct sel_netnode *node;
+ struct sel_netnode *new = NULL;
+
+ spin_lock_bh(&sel_netnode_lock);
+ node = sel_netnode_find(addr, family);
+ if (node != NULL) {
+ *sid = node->nsec.sid;
+ ret = 0;
+ goto out;
+ }
+ new = kzalloc(sizeof(*new), GFP_ATOMIC);
+ if (new == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ switch (family) {
+ case PF_INET:
+ ret = security_node_sid(PF_INET,
+ addr, sizeof(struct in_addr),
+ &new->nsec.sid);
+ new->nsec.addr.ipv4 = *(__be32 *)addr;
+ break;
+ case PF_INET6:
+ ret = security_node_sid(PF_INET6,
+ addr, sizeof(struct in6_addr),
+ &new->nsec.sid);
+ memcpy(&new->nsec.addr.ipv6, addr,
+ sizeof(new->nsec.addr.ipv6));
+ break;
+ default:
+ BUG();
+ }
+ if (ret != 0)
+ goto out;
+ new->nsec.family = family;
+ ret = sel_netnode_insert(new);
+ if (ret != 0)
+ goto out;
+ *sid = new->nsec.sid;
+
+out:
+ spin_unlock_bh(&sel_netnode_lock);
+ if (ret != 0)
+ kfree(new);
+ return ret;
+}
+
+/**
+ * sel_netnode_sid - Lookup the SID of a network address
+ * @addr: the IP address
+ * @family: the address family
+ * @sid: node SID
+ *
+ * Description:
+ * This function determines the SID of a network address using the fastest
+ * method possible. First the address table is queried, but if an entry
+ * can't be found then the policy is queried and the result is added to the
+ * table to speedup future queries. Returns zero on success, negative values
+ * on failure.
+ *
+ */
+int sel_netnode_sid(void *addr, u16 family, u32 *sid)
+{
+ struct sel_netnode *node;
+
+ rcu_read_lock();
+ node = sel_netnode_find(addr, family);
+ if (node != NULL) {
+ *sid = node->nsec.sid;
+ rcu_read_unlock();
+ return 0;
+ }
+ rcu_read_unlock();
+
+ return sel_netnode_sid_slow(addr, family, sid);
+}
+
+/**
+ * sel_netnode_flush - Flush the entire network address table
+ *
+ * Description:
+ * Remove all entries from the network address table.
+ *
+ */
+static void sel_netnode_flush(void)
+{
+ u32 idx;
+ struct sel_netnode *node;
+
+ spin_lock_bh(&sel_netnode_lock);
+ for (idx = 0; idx < SEL_NETNODE_HASH_SIZE; idx++)
+ list_for_each_entry(node, &sel_netnode_hash[idx], list)
+ sel_netnode_destroy(node);
+ spin_unlock_bh(&sel_netnode_lock);
+}
+
+static int sel_netnode_avc_callback(u32 event, u32 ssid, u32 tsid,
+ u16 class, u32 perms, u32 *retained)
+{
+ if (event == AVC_CALLBACK_RESET) {
+ sel_netnode_flush();
+ synchronize_net();
+ }
+ return 0;
+}
+
+static __init int sel_netnode_init(void)
+{
+ int iter;
+ int ret;
+
+ if (!selinux_enabled)
+ return 0;
+
+ for (iter = 0; iter < SEL_NETNODE_HASH_SIZE; iter++)
+ INIT_LIST_HEAD(&sel_netnode_hash[iter]);
+
+ ret = avc_add_callback(sel_netnode_avc_callback, AVC_CALLBACK_RESET,
+ SECSID_NULL, SECSID_NULL, SECCLASS_NULL, 0);
+ if (ret != 0)
+ panic("avc_add_callback() failed, error %d\n", ret);
+
+ return ret;
+}
+
+__initcall(sel_netnode_init);
--
This message was distributed to subscribers of the selinux mailing list.
If you no longer wish to subscribe, send mail to majordomo@tycho.nsa.gov with
the words "unsubscribe selinux" without quotes as the message.
next prev parent reply other threads:[~2007-12-14 21:57 UTC|newest]
Thread overview: 38+ messages / expand[flat|nested] mbox.gz Atom feed top
2007-12-14 21:49 [RFC PATCH v8 00/18] Update to the labeled networking patches for 2.6.25 Paul Moore
2007-12-14 21:49 ` [RFC PATCH v8 01/18] NetLabel: Remove unneeded RCU read locks Paul Moore
2007-12-14 21:50 ` [RFC PATCH v8 02/18] NetLabel: Cleanup the LSM domain hash functions Paul Moore
2007-12-14 21:50 ` [RFC PATCH v8 03/18] NetLabel: Consolidate the LSM domain mapping/hashing locks Paul Moore
2007-12-14 21:50 ` [RFC PATCH v8 04/18] NetLabel: Add secid token support to the NetLabel secattr struct Paul Moore
2007-12-14 21:50 ` [RFC PATCH v8 05/18] LSM: Add secctx_to_secid() LSM hook Paul Moore
2007-12-17 19:49 ` Stephen Smalley
2007-12-17 20:42 ` Casey Schaufler
2007-12-18 8:25 ` James Morris
2007-12-18 13:44 ` Paul Moore
2007-12-14 21:50 ` [RFC PATCH v8 06/18] LSM: Add inet_sys_snd_skb() " Paul Moore
2007-12-17 19:45 ` Stephen Smalley
2007-12-17 20:48 ` Paul Moore
2007-12-14 21:50 ` [RFC PATCH v8 07/18] NetLabel: Add IP address family information to the netlbl_skbuff_getattr() function Paul Moore
2007-12-14 21:50 ` [RFC PATCH v8 08/18] SELinux: Convert the netif code to use ifindex values Paul Moore
2007-12-14 21:50 ` [RFC PATCH v8 09/18] SELinux: Only store the network interface's ifindex Paul Moore
2007-12-17 19:56 ` Stephen Smalley
2007-12-17 20:51 ` Paul Moore
2007-12-14 21:50 ` Paul Moore [this message]
2007-12-17 20:35 ` [RFC PATCH v8 10/18] SELinux: Add a network node caching mechanism similar to the sel_netif_*() functions Stephen Smalley
2007-12-17 20:56 ` Paul Moore
2007-12-18 8:16 ` James Morris
2007-12-18 13:26 ` Stephen Smalley
2007-12-18 13:37 ` Paul Moore
2007-12-14 21:50 ` [RFC PATCH v8 11/18] SELinux: Add a capabilities bitmap to SELinux policy version 22 Paul Moore
2007-12-14 21:50 ` [RFC PATCH v8 12/18] SELinux: Add new peer permissions to the Flask definitions Paul Moore
2007-12-14 21:51 ` [RFC PATCH v8 13/18] SELinux: Better integration between peer labeling subsystems Paul Moore
2007-12-14 21:51 ` [RFC PATCH v8 14/18] SELinux: Enable dynamic enable/disable of the network access checks Paul Moore
2007-12-14 21:51 ` [RFC PATCH v8 15/18] SELinux: Allow NetLabel to directly cache SIDs Paul Moore
2007-12-14 21:51 ` [RFC PATCH v8 16/18] NetLabel: Introduce static network labels for unlabeled connections Paul Moore
2007-12-14 21:51 ` [RFC PATCH v8 17/18] NetLabel: Add auditing to the static labeling mechanism Paul Moore
2007-12-14 21:51 ` [RFC PATCH v8 18/18] SELinux: Add network ingress and egress control permission checks Paul Moore
2007-12-16 16:47 ` Paul Moore
2007-12-17 20:05 ` Stephen Smalley
2007-12-17 21:06 ` Paul Moore
2007-12-18 13:59 ` Paul Moore
2007-12-18 15:14 ` Stephen Smalley
2007-12-18 17:03 ` Paul Moore
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=20071214215046.10069.12365.stgit@flek.lan \
--to=paul.moore@hp.com \
--cc=chanson@TrustedCS.com \
--cc=linux-security-module@vger.kernel.org \
--cc=selinux@tycho.nsa.gov \
--cc=vyekkirala@TrustedCS.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.