* [PATCH -v3 2/4 cifs] NTLMv2/NTLMSSP ntlmv2 within ntlmssp autentication code
@ 2010-09-13 19:15 shirishpargaonkar-Re5JQEeQqe8AvxtiuMwx3w
[not found] ` <1284405322-31202-1-git-send-email-shirishpargaonkar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
0 siblings, 1 reply; 3+ messages in thread
From: shirishpargaonkar-Re5JQEeQqe8AvxtiuMwx3w @ 2010-09-13 19:15 UTC (permalink / raw)
To: smfrench-Re5JQEeQqe8AvxtiuMwx3w
Cc: linux-cifs-u79uwXL29TY76Z2rM5mHXA, Shirish Pargaonkar
From: Shirish Pargaonkar <shirishpargaonkar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Attribue Value (AV) pairs or Target Info (TI) pairs are part of
ntlmv2 authentication.
Structure ntlmv2_resp had only definition for two av pairs.
So removed it, and now allocation of av pairs is dynamic.
For servers like Windows 7/2008, av pairs sent by server in
challege packet (type 2 in the ntlmssp exchange/negotiation) can
vary.
Server sends them during ntlmssp negotiation. So when ntlmssp is used
as an authentication mechanism, type 2 challenge packet from server
has this information. Pluck it and use the entire blob for
authenticaiton purpose. If user has not specified, extract
(netbios) domain name from the av pairs which is used to calculate
ntlmv2 hash. Servers like Windows 7 are particular about the AV pair
blob.
Servers like Windows 2003, are not very strict about the contents
of av pair blob used during ntlmv2 authentication.
So when security mechanism such as ntlmv2 is used (not ntlmv2 in ntlmssp),
there is no negotiation and so genereate a minimal blob that gets
used in ntlmv2 authentication as well as gets sent.
Fields tilen and tilbob are session specific. AV pair values are defined.
To calculate ntlmv2 response we need ti/av pair blob.
For sec mech like ntlmssp, the blob is plucked from type 2 response from
the server. From this blob, netbios name of the domain is retrieved,
if user has not already provided, to be included in the Target String
as part of ntlmv2 hash calculations.
For sec mech like ntlmv2, create a minimal, two av pair blob.
The allocated blob is freed in case of error. In case there is no error,
this blob is used in calculating ntlmv2 response (in CalcNTLMv2_response)
and is also copied on the response to the server, and then freed.
The type 3 ntlmssp response is prepared on a buffer,
5 * sizeof of struct _AUTHENTICATE_MESSAGE, an empirical value large
enough to hold _AUTHENTICATE_MESSAGE plus a blob with max possible
10 values as part of ntlmv2 response and lmv2 keys and domain, user,
workstation names etc.
Also, kerberos gets selected as a default mechanism if server supports it,
over the other security mechanisms.
Signed-off-by: Shirish Pargaonkar <shirishpargaonkar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
---
fs/cifs/cifsencrypt.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++---
fs/cifs/cifsglob.h | 2 +
fs/cifs/cifspdu.h | 1 -
fs/cifs/cifsproto.h | 2 +-
fs/cifs/cifssmb.c | 16 ++++---
fs/cifs/connect.c | 2 +
fs/cifs/ntlmssp.h | 15 ++++++
7 files changed, 144 insertions(+), 15 deletions(-)
diff --git a/fs/cifs/cifsencrypt.c b/fs/cifs/cifsencrypt.c
index eed70ca..960c948 100644
--- a/fs/cifs/cifsencrypt.c
+++ b/fs/cifs/cifsencrypt.c
@@ -27,6 +27,7 @@
#include "md5.h"
#include "cifs_unicode.h"
#include "cifsproto.h"
+#include "ntlmssp.h"
#include <linux/ctype.h>
#include <linux/random.h>
@@ -262,6 +263,87 @@ void calc_lanman_hash(const char *password, const char *cryptkey, bool encrypt,
}
#endif /* CIFS_WEAK_PW_HASH */
+/* This is just a filler for ntlmv2 type of security mechanisms.
+ * Older servers are not very particular about the contents of av pairs
+ * in the blob and for sec mechs like ntlmv2, there is no negotiation
+ * as in ntlmssp, so unless domain and server netbios and dns names
+ * are specified, there is no way to obtain name. In case of ntlmssp,
+ * server provides that info in type 2 challenge packet
+ */
+static int
+build_avpair_blob(struct cifsSesInfo *ses)
+{
+ struct ntlmssp2_name *attrptr;
+
+ ses->tilen = 2 * sizeof(struct ntlmssp2_name);
+ ses->tiblob = kzalloc(ses->tilen, GFP_KERNEL);
+ if (!ses->tiblob) {
+ ses->tilen = 0;
+ cERROR(1, "Challenge target info allocation failure");
+ return -ENOMEM;
+ }
+ attrptr = (struct ntlmssp2_name *) ses->tiblob;
+ attrptr->type = cpu_to_le16(NTLMSSP_DOMAIN_TYPE);
+
+ return 0;
+}
+
+/* Server has provided av pairs/target info in the type 2 challenge
+ * packet and we have plucked it and stored within smb session.
+ * We parse that blob here to find netbios domain name to be used
+ * as part of ntlmv2 authentication (in Target String), if not already
+ * specified on the command line.
+ * If this function returns without any error but without fetching
+ * domain name, authentication may fail against some server but
+ * may not fail against other (those who are not very particular
+ * about target string i.e. for some, just user name might suffice.
+ */
+static int
+find_domain_name(struct cifsSesInfo *ses)
+{
+ unsigned int attrsize;
+ unsigned int type;
+ unsigned char *blobptr;
+ unsigned char *blobend;
+ struct ntlmssp2_name *attrptr;
+
+ if (!ses->tilen || !ses->tiblob)
+ return 0;
+
+ if (ses->tilen < sizeof(struct ntlmssp2_name))
+ return 0;
+
+ blobend = ses->tiblob + ses->tilen;
+ blobptr = ses->tiblob;
+ attrptr = (struct ntlmssp2_name *) blobptr;
+
+ while (blobptr <= blobend) {
+ type = le16_to_cpu(attrptr->type);
+ if (type == NTLMSSP_AV_EOL)
+ break;
+ blobptr += 2; /* advance attr type */
+ attrsize = le16_to_cpu(attrptr->length);
+ blobptr += 2; /* advance attr size */
+ if (type == NTLMSSP_AV_NB_DOMAIN_NAME) {
+ if (!ses->domainName) {
+ ses->domainName =
+ kmalloc(attrsize + 1, GFP_KERNEL);
+ if (!ses->domainName)
+ return -ENOMEM;
+ cifs_from_ucs2(ses->domainName,
+ (__le16 *)blobptr,
+ attrptr->length,
+ attrptr->length,
+ load_nls_default(), false);
+ }
+ }
+ blobptr += attrsize; /* advance attr value */
+ attrptr = (struct ntlmssp2_name *) blobptr;
+ }
+
+ return 0;
+}
+
static int calc_ntlmv2_hash(struct cifsSesInfo *ses,
const struct nls_table *nls_cp)
{
@@ -321,7 +403,8 @@ calc_exit_2:
return rc;
}
-void setup_ntlmv2_rsp(struct cifsSesInfo *ses, char *resp_buf,
+int
+setup_ntlmv2_rsp(struct cifsSesInfo *ses, char *resp_buf,
const struct nls_table *nls_cp)
{
int rc;
@@ -333,15 +416,29 @@ void setup_ntlmv2_rsp(struct cifsSesInfo *ses, char *resp_buf,
buf->time = cpu_to_le64(cifs_UnixTimeToNT(CURRENT_TIME));
get_random_bytes(&buf->client_chal, sizeof(buf->client_chal));
buf->reserved2 = 0;
- buf->names[0].type = cpu_to_le16(NTLMSSP_DOMAIN_TYPE);
- buf->names[0].length = 0;
- buf->names[1].type = 0;
- buf->names[1].length = 0;
+
+ if (ses->server->secType == RawNTLMSSP) {
+ if (!ses->domainName) {
+ rc = find_domain_name(ses);
+ if (rc) {
+ cERROR(1, "error %d finding domain name", rc);
+ goto setup_ntlmv2_rsp_ret;
+ }
+ }
+ } else {
+ rc = build_avpair_blob(ses);
+ if (rc) {
+ cERROR(1, "error %d building av pair blob", rc);
+ return rc;
+ }
+ }
/* calculate buf->ntlmv2_hash */
rc = calc_ntlmv2_hash(ses, nls_cp);
- if (rc)
+ if (rc) {
cERROR(1, "could not get v2 hash rc %d", rc);
+ goto setup_ntlmv2_rsp_ret;
+ }
CalcNTLMv2_response(ses, resp_buf);
/* now calculate the MAC key for NTLMv2 */
@@ -352,6 +449,14 @@ void setup_ntlmv2_rsp(struct cifsSesInfo *ses, char *resp_buf,
memcpy(&ses->server->session_key.data.ntlmv2.resp, resp_buf,
sizeof(struct ntlmv2_resp));
ses->server->session_key.len = 16 + sizeof(struct ntlmv2_resp);
+
+ return 0;
+
+setup_ntlmv2_rsp_ret:
+ kfree(ses->tiblob);
+ ses->tilen = 0;
+
+ return rc;
}
void CalcNTLMv2_response(const struct cifsSesInfo *ses,
@@ -365,6 +470,10 @@ void CalcNTLMv2_response(const struct cifsSesInfo *ses,
hmac_md5_update(v2_session_response+8,
sizeof(struct ntlmv2_resp) - 8, &context);
+ if (ses->tilen)
+ hmac_md5_update(ses->tiblob,
+ ses->tilen, &context);
+
hmac_md5_final(v2_session_response, &context);
/* cifs_dump_mem("v2_sess_rsp: ", v2_session_response, 32); */
}
diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
index 14dfa9a..c68f31c 100644
--- a/fs/cifs/cifsglob.h
+++ b/fs/cifs/cifsglob.h
@@ -222,6 +222,8 @@ struct cifsSesInfo {
char userName[MAX_USERNAME_SIZE + 1];
char *domainName;
char *password;
+ unsigned int tilen; /* length of the target info blob */
+ unsigned char *tiblob; /* target info blob in challenge response */
bool need_reconnect:1; /* connection reset, uid now invalid */
};
/* no more than one of the following three session flags may be set */
diff --git a/fs/cifs/cifspdu.h b/fs/cifs/cifspdu.h
index 14d036d..b0f4b56 100644
--- a/fs/cifs/cifspdu.h
+++ b/fs/cifs/cifspdu.h
@@ -663,7 +663,6 @@ struct ntlmv2_resp {
__le64 time;
__u64 client_chal; /* random */
__u32 reserved2;
- struct ntlmssp2_name names[2];
/* array of name entries could follow ending in minimum 4 byte struct */
} __attribute__((packed));
diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
index 3f4fa81..c155479 100644
--- a/fs/cifs/cifsproto.h
+++ b/fs/cifs/cifsproto.h
@@ -367,7 +367,7 @@ extern int cifs_verify_signature(struct smb_hdr *,
extern int cifs_calculate_session_key(struct session_key *key, const char *rn,
const char *pass);
extern void CalcNTLMv2_response(const struct cifsSesInfo *, char *);
-extern void setup_ntlmv2_rsp(struct cifsSesInfo *, char *,
+extern int setup_ntlmv2_rsp(struct cifsSesInfo *, char *,
const struct nls_table *);
#ifdef CONFIG_CIFS_WEAK_PW_HASH
extern void calc_lanman_hash(const char *password, const char *cryptkey,
diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c
index c65c341..13c854e 100644
--- a/fs/cifs/cifssmb.c
+++ b/fs/cifs/cifssmb.c
@@ -603,13 +603,15 @@ CIFSSMBNegotiate(unsigned int xid, struct cifsSesInfo *ses)
rc = 0;
else
rc = -EINVAL;
-
- if (server->sec_kerberos || server->sec_mskerberos)
- server->secType = Kerberos;
- else if (server->sec_ntlmssp)
- server->secType = RawNTLMSSP;
- else
- rc = -EOPNOTSUPP;
+ if (server->secType == Kerberos) {
+ if (!server->sec_kerberos &&
+ !server->sec_mskerberos)
+ rc = -EOPNOTSUPP;
+ } else if (server->secType == RawNTLMSSP) {
+ if (!server->sec_ntlmssp)
+ rc = -EOPNOTSUPP;
+ } else
+ rc = -EOPNOTSUPP;
}
} else
server->capabilities &= ~CAP_EXTENDED_SECURITY;
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
index 67dad54..f8891a2 100644
--- a/fs/cifs/connect.c
+++ b/fs/cifs/connect.c
@@ -1740,6 +1740,8 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)
if (ses == NULL)
goto get_ses_fail;
+ ses->tilen = 0;
+ ses->tiblob = NULL;
/* new SMB session uses our server ref */
ses->server = server;
if (server->addr.sockAddr6.sin6_family == AF_INET6)
diff --git a/fs/cifs/ntlmssp.h b/fs/cifs/ntlmssp.h
index 49c9a4e..5d52e4a 100644
--- a/fs/cifs/ntlmssp.h
+++ b/fs/cifs/ntlmssp.h
@@ -61,6 +61,21 @@
#define NTLMSSP_NEGOTIATE_KEY_XCH 0x40000000
#define NTLMSSP_NEGOTIATE_56 0x80000000
+/* Define AV Pair Field IDs */
+enum av_field_type {
+ NTLMSSP_AV_EOL = 0,
+ NTLMSSP_AV_NB_COMPUTER_NAME,
+ NTLMSSP_AV_NB_DOMAIN_NAME,
+ NTLMSSP_AV_DNS_COMPUTER_NAME,
+ NTLMSSP_AV_DNS_DOMAIN_NAME,
+ NTLMSSP_AV_DNS_TREE_NAME,
+ NTLMSSP_AV_FLAGS,
+ NTLMSSP_AV_TIMESTAMP,
+ NTLMSSP_AV_RESTRICTION,
+ NTLMSSP_AV_TARGET_NAME,
+ NTLMSSP_AV_CHANNEL_BINDINGS
+};
+
/* Although typedefs are not commonly used for structure definitions */
/* in the Linux kernel, in this particular case they are useful */
/* to more closely match the standards document for NTLMSSP from */
--
1.6.0.2
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH -v3 2/4 cifs] NTLMv2/NTLMSSP ntlmv2 within ntlmssp autentication code
[not found] ` <1284405322-31202-1-git-send-email-shirishpargaonkar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
@ 2010-09-14 11:23 ` Jeff Layton
[not found] ` <20100914072333.67649ee2-4QP7MXygkU+dMjc06nkz3ljfA9RmPOcC@public.gmane.org>
0 siblings, 1 reply; 3+ messages in thread
From: Jeff Layton @ 2010-09-14 11:23 UTC (permalink / raw)
To: shirishpargaonkar-Re5JQEeQqe8AvxtiuMwx3w
Cc: smfrench-Re5JQEeQqe8AvxtiuMwx3w,
linux-cifs-u79uwXL29TY76Z2rM5mHXA
On Mon, 13 Sep 2010 14:15:22 -0500
shirishpargaonkar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org wrote:
> From: Shirish Pargaonkar <shirishpargaonkar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>
>
> Attribue Value (AV) pairs or Target Info (TI) pairs are part of
> ntlmv2 authentication.
> Structure ntlmv2_resp had only definition for two av pairs.
> So removed it, and now allocation of av pairs is dynamic.
> For servers like Windows 7/2008, av pairs sent by server in
> challege packet (type 2 in the ntlmssp exchange/negotiation) can
> vary.
>
> Server sends them during ntlmssp negotiation. So when ntlmssp is used
> as an authentication mechanism, type 2 challenge packet from server
> has this information. Pluck it and use the entire blob for
> authenticaiton purpose. If user has not specified, extract
> (netbios) domain name from the av pairs which is used to calculate
> ntlmv2 hash. Servers like Windows 7 are particular about the AV pair
> blob.
>
> Servers like Windows 2003, are not very strict about the contents
> of av pair blob used during ntlmv2 authentication.
> So when security mechanism such as ntlmv2 is used (not ntlmv2 in ntlmssp),
> there is no negotiation and so genereate a minimal blob that gets
> used in ntlmv2 authentication as well as gets sent.
>
> Fields tilen and tilbob are session specific. AV pair values are defined.
>
> To calculate ntlmv2 response we need ti/av pair blob.
>
> For sec mech like ntlmssp, the blob is plucked from type 2 response from
> the server. From this blob, netbios name of the domain is retrieved,
> if user has not already provided, to be included in the Target String
> as part of ntlmv2 hash calculations.
>
> For sec mech like ntlmv2, create a minimal, two av pair blob.
>
> The allocated blob is freed in case of error. In case there is no error,
> this blob is used in calculating ntlmv2 response (in CalcNTLMv2_response)
> and is also copied on the response to the server, and then freed.
>
> The type 3 ntlmssp response is prepared on a buffer,
> 5 * sizeof of struct _AUTHENTICATE_MESSAGE, an empirical value large
> enough to hold _AUTHENTICATE_MESSAGE plus a blob with max possible
> 10 values as part of ntlmv2 response and lmv2 keys and domain, user,
> workstation names etc.
>
> Also, kerberos gets selected as a default mechanism if server supports it,
> over the other security mechanisms.
>
>
> Signed-off-by: Shirish Pargaonkar <shirishpargaonkar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> ---
> fs/cifs/cifsencrypt.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++---
> fs/cifs/cifsglob.h | 2 +
> fs/cifs/cifspdu.h | 1 -
> fs/cifs/cifsproto.h | 2 +-
> fs/cifs/cifssmb.c | 16 ++++---
> fs/cifs/connect.c | 2 +
> fs/cifs/ntlmssp.h | 15 ++++++
> 7 files changed, 144 insertions(+), 15 deletions(-)
>
> diff --git a/fs/cifs/cifsencrypt.c b/fs/cifs/cifsencrypt.c
> index eed70ca..960c948 100644
> --- a/fs/cifs/cifsencrypt.c
> +++ b/fs/cifs/cifsencrypt.c
> @@ -27,6 +27,7 @@
> #include "md5.h"
> #include "cifs_unicode.h"
> #include "cifsproto.h"
> +#include "ntlmssp.h"
> #include <linux/ctype.h>
> #include <linux/random.h>
>
> @@ -262,6 +263,87 @@ void calc_lanman_hash(const char *password, const char *cryptkey, bool encrypt,
> }
> #endif /* CIFS_WEAK_PW_HASH */
>
> +/* This is just a filler for ntlmv2 type of security mechanisms.
> + * Older servers are not very particular about the contents of av pairs
> + * in the blob and for sec mechs like ntlmv2, there is no negotiation
> + * as in ntlmssp, so unless domain and server netbios and dns names
> + * are specified, there is no way to obtain name. In case of ntlmssp,
> + * server provides that info in type 2 challenge packet
> + */
> +static int
> +build_avpair_blob(struct cifsSesInfo *ses)
> +{
> + struct ntlmssp2_name *attrptr;
> +
> + ses->tilen = 2 * sizeof(struct ntlmssp2_name);
> + ses->tiblob = kzalloc(ses->tilen, GFP_KERNEL);
> + if (!ses->tiblob) {
> + ses->tilen = 0;
> + cERROR(1, "Challenge target info allocation failure");
> + return -ENOMEM;
> + }
> + attrptr = (struct ntlmssp2_name *) ses->tiblob;
> + attrptr->type = cpu_to_le16(NTLMSSP_DOMAIN_TYPE);
> +
> + return 0;
> +}
> +
> +/* Server has provided av pairs/target info in the type 2 challenge
> + * packet and we have plucked it and stored within smb session.
> + * We parse that blob here to find netbios domain name to be used
> + * as part of ntlmv2 authentication (in Target String), if not already
> + * specified on the command line.
> + * If this function returns without any error but without fetching
> + * domain name, authentication may fail against some server but
> + * may not fail against other (those who are not very particular
> + * about target string i.e. for some, just user name might suffice.
> + */
> +static int
> +find_domain_name(struct cifsSesInfo *ses)
> +{
> + unsigned int attrsize;
> + unsigned int type;
> + unsigned char *blobptr;
> + unsigned char *blobend;
> + struct ntlmssp2_name *attrptr;
> +
> + if (!ses->tilen || !ses->tiblob)
> + return 0;
> +
> + if (ses->tilen < sizeof(struct ntlmssp2_name))
> + return 0;
> +
> + blobend = ses->tiblob + ses->tilen;
> + blobptr = ses->tiblob;
> + attrptr = (struct ntlmssp2_name *) blobptr;
> +
> + while (blobptr <= blobend) {
> + type = le16_to_cpu(attrptr->type);
> + if (type == NTLMSSP_AV_EOL)
> + break;
> + blobptr += 2; /* advance attr type */
> + attrsize = le16_to_cpu(attrptr->length);
> + blobptr += 2; /* advance attr size */
Again, there's no check to see whether any of these
lengths fall within the blob. It would be trivial for an
attacker with access to a compromised server to craft a
NTLMSSP response that has a NTLMSSP_AV_NB_DOMAIN_NAME
field with a huge length, far beyond the end of the
blob. This code will then likely oops in cifs_from_ucs2.
> + if (type == NTLMSSP_AV_NB_DOMAIN_NAME) {
> + if (!ses->domainName) {
> + ses->domainName =
> + kmalloc(attrsize + 1, GFP_KERNEL);
> + if (!ses->domainName)
> + return -ENOMEM;
> + cifs_from_ucs2(ses->domainName,
> + (__le16 *)blobptr,
> + attrptr->length,
> + attrptr->length,
> + load_nls_default(), false);
^^^^^^
You can probably break here, right?
> + }
> + }
> + blobptr += attrsize; /* advance attr value */
> + attrptr = (struct ntlmssp2_name *) blobptr;
> + }
> +
> + return 0;
> +}
> +
> static int calc_ntlmv2_hash(struct cifsSesInfo *ses,
> const struct nls_table *nls_cp)
> {
> @@ -321,7 +403,8 @@ calc_exit_2:
> return rc;
> }
>
> -void setup_ntlmv2_rsp(struct cifsSesInfo *ses, char *resp_buf,
> +int
> +setup_ntlmv2_rsp(struct cifsSesInfo *ses, char *resp_buf,
> const struct nls_table *nls_cp)
> {
> int rc;
> @@ -333,15 +416,29 @@ void setup_ntlmv2_rsp(struct cifsSesInfo *ses, char *resp_buf,
> buf->time = cpu_to_le64(cifs_UnixTimeToNT(CURRENT_TIME));
> get_random_bytes(&buf->client_chal, sizeof(buf->client_chal));
> buf->reserved2 = 0;
> - buf->names[0].type = cpu_to_le16(NTLMSSP_DOMAIN_TYPE);
> - buf->names[0].length = 0;
> - buf->names[1].type = 0;
> - buf->names[1].length = 0;
> +
> + if (ses->server->secType == RawNTLMSSP) {
> + if (!ses->domainName) {
> + rc = find_domain_name(ses);
> + if (rc) {
> + cERROR(1, "error %d finding domain name", rc);
> + goto setup_ntlmv2_rsp_ret;
> + }
> + }
> + } else {
> + rc = build_avpair_blob(ses);
> + if (rc) {
> + cERROR(1, "error %d building av pair blob", rc);
> + return rc;
> + }
> + }
>
> /* calculate buf->ntlmv2_hash */
> rc = calc_ntlmv2_hash(ses, nls_cp);
> - if (rc)
> + if (rc) {
> cERROR(1, "could not get v2 hash rc %d", rc);
> + goto setup_ntlmv2_rsp_ret;
> + }
> CalcNTLMv2_response(ses, resp_buf);
>
> /* now calculate the MAC key for NTLMv2 */
> @@ -352,6 +449,14 @@ void setup_ntlmv2_rsp(struct cifsSesInfo *ses, char *resp_buf,
> memcpy(&ses->server->session_key.data.ntlmv2.resp, resp_buf,
> sizeof(struct ntlmv2_resp));
> ses->server->session_key.len = 16 + sizeof(struct ntlmv2_resp);
> +
> + return 0;
> +
> +setup_ntlmv2_rsp_ret:
> + kfree(ses->tiblob);
> + ses->tilen = 0;
> +
> + return rc;
So, this blob only gets freed when there's an problem calculating the
hash? What's the value in keeping this around after session setup is
done?
> }
>
> void CalcNTLMv2_response(const struct cifsSesInfo *ses,
> @@ -365,6 +470,10 @@ void CalcNTLMv2_response(const struct cifsSesInfo *ses,
> hmac_md5_update(v2_session_response+8,
> sizeof(struct ntlmv2_resp) - 8, &context);
>
> + if (ses->tilen)
> + hmac_md5_update(ses->tiblob,
> + ses->tilen, &context);
> +
^^^^^^^
nit: that looks like it would fit on one line
> hmac_md5_final(v2_session_response, &context);
> /* cifs_dump_mem("v2_sess_rsp: ", v2_session_response, 32); */
> }
> diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
> index 14dfa9a..c68f31c 100644
> --- a/fs/cifs/cifsglob.h
> +++ b/fs/cifs/cifsglob.h
> @@ -222,6 +222,8 @@ struct cifsSesInfo {
> char userName[MAX_USERNAME_SIZE + 1];
> char *domainName;
> char *password;
> + unsigned int tilen; /* length of the target info blob */
> + unsigned char *tiblob; /* target info blob in challenge response */
> bool need_reconnect:1; /* connection reset, uid now invalid */
> };
> /* no more than one of the following three session flags may be set */
> diff --git a/fs/cifs/cifspdu.h b/fs/cifs/cifspdu.h
> index 14d036d..b0f4b56 100644
> --- a/fs/cifs/cifspdu.h
> +++ b/fs/cifs/cifspdu.h
> @@ -663,7 +663,6 @@ struct ntlmv2_resp {
> __le64 time;
> __u64 client_chal; /* random */
> __u32 reserved2;
> - struct ntlmssp2_name names[2];
> /* array of name entries could follow ending in minimum 4 byte struct */
> } __attribute__((packed));
>
> diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
> index 3f4fa81..c155479 100644
> --- a/fs/cifs/cifsproto.h
> +++ b/fs/cifs/cifsproto.h
> @@ -367,7 +367,7 @@ extern int cifs_verify_signature(struct smb_hdr *,
> extern int cifs_calculate_session_key(struct session_key *key, const char *rn,
> const char *pass);
> extern void CalcNTLMv2_response(const struct cifsSesInfo *, char *);
> -extern void setup_ntlmv2_rsp(struct cifsSesInfo *, char *,
> +extern int setup_ntlmv2_rsp(struct cifsSesInfo *, char *,
> const struct nls_table *);
> #ifdef CONFIG_CIFS_WEAK_PW_HASH
> extern void calc_lanman_hash(const char *password, const char *cryptkey,
> diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c
> index c65c341..13c854e 100644
> --- a/fs/cifs/cifssmb.c
> +++ b/fs/cifs/cifssmb.c
> @@ -603,13 +603,15 @@ CIFSSMBNegotiate(unsigned int xid, struct cifsSesInfo *ses)
> rc = 0;
> else
> rc = -EINVAL;
> -
> - if (server->sec_kerberos || server->sec_mskerberos)
> - server->secType = Kerberos;
> - else if (server->sec_ntlmssp)
> - server->secType = RawNTLMSSP;
> - else
> - rc = -EOPNOTSUPP;
> + if (server->secType == Kerberos) {
> + if (!server->sec_kerberos &&
> + !server->sec_mskerberos)
> + rc = -EOPNOTSUPP;
> + } else if (server->secType == RawNTLMSSP) {
> + if (!server->sec_ntlmssp)
> + rc = -EOPNOTSUPP;
> + } else
> + rc = -EOPNOTSUPP;
> }
> } else
> server->capabilities &= ~CAP_EXTENDED_SECURITY;
> diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
> index 67dad54..f8891a2 100644
> --- a/fs/cifs/connect.c
> +++ b/fs/cifs/connect.c
> @@ -1740,6 +1740,8 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)
> if (ses == NULL)
> goto get_ses_fail;
>
> + ses->tilen = 0;
> + ses->tiblob = NULL;
> /* new SMB session uses our server ref */
> ses->server = server;
> if (server->addr.sockAddr6.sin6_family == AF_INET6)
> diff --git a/fs/cifs/ntlmssp.h b/fs/cifs/ntlmssp.h
> index 49c9a4e..5d52e4a 100644
> --- a/fs/cifs/ntlmssp.h
> +++ b/fs/cifs/ntlmssp.h
> @@ -61,6 +61,21 @@
> #define NTLMSSP_NEGOTIATE_KEY_XCH 0x40000000
> #define NTLMSSP_NEGOTIATE_56 0x80000000
>
> +/* Define AV Pair Field IDs */
> +enum av_field_type {
> + NTLMSSP_AV_EOL = 0,
> + NTLMSSP_AV_NB_COMPUTER_NAME,
> + NTLMSSP_AV_NB_DOMAIN_NAME,
> + NTLMSSP_AV_DNS_COMPUTER_NAME,
> + NTLMSSP_AV_DNS_DOMAIN_NAME,
> + NTLMSSP_AV_DNS_TREE_NAME,
> + NTLMSSP_AV_FLAGS,
> + NTLMSSP_AV_TIMESTAMP,
> + NTLMSSP_AV_RESTRICTION,
> + NTLMSSP_AV_TARGET_NAME,
> + NTLMSSP_AV_CHANNEL_BINDINGS
> +};
> +
> /* Although typedefs are not commonly used for structure definitions */
> /* in the Linux kernel, in this particular case they are useful */
> /* to more closely match the standards document for NTLMSSP from */
--
Jeff Layton <jlayton-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH -v3 2/4 cifs] NTLMv2/NTLMSSP ntlmv2 within ntlmssp autentication code
[not found] ` <20100914072333.67649ee2-4QP7MXygkU+dMjc06nkz3ljfA9RmPOcC@public.gmane.org>
@ 2010-09-14 11:33 ` Shirish Pargaonkar
0 siblings, 0 replies; 3+ messages in thread
From: Shirish Pargaonkar @ 2010-09-14 11:33 UTC (permalink / raw)
To: Jeff Layton
Cc: smfrench-Re5JQEeQqe8AvxtiuMwx3w,
linux-cifs-u79uwXL29TY76Z2rM5mHXA
On Tue, Sep 14, 2010 at 6:23 AM, Jeff Layton <jlayton-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org> wrote:
> On Mon, 13 Sep 2010 14:15:22 -0500
> shirishpargaonkar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org wrote:
>
>> From: Shirish Pargaonkar <shirishpargaonkar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>>
>>
>> Attribue Value (AV) pairs or Target Info (TI) pairs are part of
>> ntlmv2 authentication.
>> Structure ntlmv2_resp had only definition for two av pairs.
>> So removed it, and now allocation of av pairs is dynamic.
>> For servers like Windows 7/2008, av pairs sent by server in
>> challege packet (type 2 in the ntlmssp exchange/negotiation) can
>> vary.
>>
>> Server sends them during ntlmssp negotiation. So when ntlmssp is used
>> as an authentication mechanism, type 2 challenge packet from server
>> has this information. Pluck it and use the entire blob for
>> authenticaiton purpose. If user has not specified, extract
>> (netbios) domain name from the av pairs which is used to calculate
>> ntlmv2 hash. Servers like Windows 7 are particular about the AV pair
>> blob.
>>
>> Servers like Windows 2003, are not very strict about the contents
>> of av pair blob used during ntlmv2 authentication.
>> So when security mechanism such as ntlmv2 is used (not ntlmv2 in ntlmssp),
>> there is no negotiation and so genereate a minimal blob that gets
>> used in ntlmv2 authentication as well as gets sent.
>>
>> Fields tilen and tilbob are session specific. AV pair values are defined.
>>
>> To calculate ntlmv2 response we need ti/av pair blob.
>>
>> For sec mech like ntlmssp, the blob is plucked from type 2 response from
>> the server. From this blob, netbios name of the domain is retrieved,
>> if user has not already provided, to be included in the Target String
>> as part of ntlmv2 hash calculations.
>>
>> For sec mech like ntlmv2, create a minimal, two av pair blob.
>>
>> The allocated blob is freed in case of error. In case there is no error,
>> this blob is used in calculating ntlmv2 response (in CalcNTLMv2_response)
>> and is also copied on the response to the server, and then freed.
>>
>> The type 3 ntlmssp response is prepared on a buffer,
>> 5 * sizeof of struct _AUTHENTICATE_MESSAGE, an empirical value large
>> enough to hold _AUTHENTICATE_MESSAGE plus a blob with max possible
>> 10 values as part of ntlmv2 response and lmv2 keys and domain, user,
>> workstation names etc.
>>
>> Also, kerberos gets selected as a default mechanism if server supports it,
>> over the other security mechanisms.
>>
>>
>> Signed-off-by: Shirish Pargaonkar <shirishpargaonkar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>> ---
>> fs/cifs/cifsencrypt.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++---
>> fs/cifs/cifsglob.h | 2 +
>> fs/cifs/cifspdu.h | 1 -
>> fs/cifs/cifsproto.h | 2 +-
>> fs/cifs/cifssmb.c | 16 ++++---
>> fs/cifs/connect.c | 2 +
>> fs/cifs/ntlmssp.h | 15 ++++++
>> 7 files changed, 144 insertions(+), 15 deletions(-)
>>
>> diff --git a/fs/cifs/cifsencrypt.c b/fs/cifs/cifsencrypt.c
>> index eed70ca..960c948 100644
>> --- a/fs/cifs/cifsencrypt.c
>> +++ b/fs/cifs/cifsencrypt.c
>> @@ -27,6 +27,7 @@
>> #include "md5.h"
>> #include "cifs_unicode.h"
>> #include "cifsproto.h"
>> +#include "ntlmssp.h"
>> #include <linux/ctype.h>
>> #include <linux/random.h>
>>
>> @@ -262,6 +263,87 @@ void calc_lanman_hash(const char *password, const char *cryptkey, bool encrypt,
>> }
>> #endif /* CIFS_WEAK_PW_HASH */
>>
>> +/* This is just a filler for ntlmv2 type of security mechanisms.
>> + * Older servers are not very particular about the contents of av pairs
>> + * in the blob and for sec mechs like ntlmv2, there is no negotiation
>> + * as in ntlmssp, so unless domain and server netbios and dns names
>> + * are specified, there is no way to obtain name. In case of ntlmssp,
>> + * server provides that info in type 2 challenge packet
>> + */
>> +static int
>> +build_avpair_blob(struct cifsSesInfo *ses)
>> +{
>> + struct ntlmssp2_name *attrptr;
>> +
>> + ses->tilen = 2 * sizeof(struct ntlmssp2_name);
>> + ses->tiblob = kzalloc(ses->tilen, GFP_KERNEL);
>> + if (!ses->tiblob) {
>> + ses->tilen = 0;
>> + cERROR(1, "Challenge target info allocation failure");
>> + return -ENOMEM;
>> + }
>> + attrptr = (struct ntlmssp2_name *) ses->tiblob;
>> + attrptr->type = cpu_to_le16(NTLMSSP_DOMAIN_TYPE);
>> +
>> + return 0;
>> +}
>> +
>> +/* Server has provided av pairs/target info in the type 2 challenge
>> + * packet and we have plucked it and stored within smb session.
>> + * We parse that blob here to find netbios domain name to be used
>> + * as part of ntlmv2 authentication (in Target String), if not already
>> + * specified on the command line.
>> + * If this function returns without any error but without fetching
>> + * domain name, authentication may fail against some server but
>> + * may not fail against other (those who are not very particular
>> + * about target string i.e. for some, just user name might suffice.
>> + */
>> +static int
>> +find_domain_name(struct cifsSesInfo *ses)
>> +{
>> + unsigned int attrsize;
>> + unsigned int type;
>> + unsigned char *blobptr;
>> + unsigned char *blobend;
>> + struct ntlmssp2_name *attrptr;
>> +
>> + if (!ses->tilen || !ses->tiblob)
>> + return 0;
>> +
>> + if (ses->tilen < sizeof(struct ntlmssp2_name))
>> + return 0;
>> +
>> + blobend = ses->tiblob + ses->tilen;
>> + blobptr = ses->tiblob;
>> + attrptr = (struct ntlmssp2_name *) blobptr;
>> +
>> + while (blobptr <= blobend) {
>> + type = le16_to_cpu(attrptr->type);
>> + if (type == NTLMSSP_AV_EOL)
>> + break;
>> + blobptr += 2; /* advance attr type */
>> + attrsize = le16_to_cpu(attrptr->length);
>> + blobptr += 2; /* advance attr size */
>
> Again, there's no check to see whether any of these
> lengths fall within the blob. It would be trivial for an
> attacker with access to a compromised server to craft a
> NTLMSSP response that has a NTLMSSP_AV_NB_DOMAIN_NAME
> field with a huge length, far beyond the end of the
> blob. This code will then likely oops in cifs_from_ucs2.
OK, will address this.
>
>> + if (type == NTLMSSP_AV_NB_DOMAIN_NAME) {
>> + if (!ses->domainName) {
>> + ses->domainName =
>> + kmalloc(attrsize + 1, GFP_KERNEL);
>> + if (!ses->domainName)
>> + return -ENOMEM;
>> + cifs_from_ucs2(ses->domainName,
>> + (__le16 *)blobptr,
>> + attrptr->length,
>> + attrptr->length,
>> + load_nls_default(), false);
> ^^^^^^
> You can probably break here, right?
>> + }
>> + }
>> + blobptr += attrsize; /* advance attr value */
>> + attrptr = (struct ntlmssp2_name *) blobptr;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> static int calc_ntlmv2_hash(struct cifsSesInfo *ses,
>> const struct nls_table *nls_cp)
>> {
>> @@ -321,7 +403,8 @@ calc_exit_2:
>> return rc;
>> }
>>
>> -void setup_ntlmv2_rsp(struct cifsSesInfo *ses, char *resp_buf,
>> +int
>> +setup_ntlmv2_rsp(struct cifsSesInfo *ses, char *resp_buf,
>> const struct nls_table *nls_cp)
>> {
>> int rc;
>> @@ -333,15 +416,29 @@ void setup_ntlmv2_rsp(struct cifsSesInfo *ses, char *resp_buf,
>> buf->time = cpu_to_le64(cifs_UnixTimeToNT(CURRENT_TIME));
>> get_random_bytes(&buf->client_chal, sizeof(buf->client_chal));
>> buf->reserved2 = 0;
>> - buf->names[0].type = cpu_to_le16(NTLMSSP_DOMAIN_TYPE);
>> - buf->names[0].length = 0;
>> - buf->names[1].type = 0;
>> - buf->names[1].length = 0;
>> +
>> + if (ses->server->secType == RawNTLMSSP) {
>> + if (!ses->domainName) {
>> + rc = find_domain_name(ses);
>> + if (rc) {
>> + cERROR(1, "error %d finding domain name", rc);
>> + goto setup_ntlmv2_rsp_ret;
>> + }
>> + }
>> + } else {
>> + rc = build_avpair_blob(ses);
>> + if (rc) {
>> + cERROR(1, "error %d building av pair blob", rc);
>> + return rc;
>> + }
>> + }
>>
>> /* calculate buf->ntlmv2_hash */
>> rc = calc_ntlmv2_hash(ses, nls_cp);
>> - if (rc)
>> + if (rc) {
>> cERROR(1, "could not get v2 hash rc %d", rc);
>> + goto setup_ntlmv2_rsp_ret;
>> + }
>> CalcNTLMv2_response(ses, resp_buf);
>>
>> /* now calculate the MAC key for NTLMv2 */
>> @@ -352,6 +449,14 @@ void setup_ntlmv2_rsp(struct cifsSesInfo *ses, char *resp_buf,
>> memcpy(&ses->server->session_key.data.ntlmv2.resp, resp_buf,
>> sizeof(struct ntlmv2_resp));
>> ses->server->session_key.len = 16 + sizeof(struct ntlmv2_resp);
>> +
>> + return 0;
>> +
>> +setup_ntlmv2_rsp_ret:
>> + kfree(ses->tiblob);
>> + ses->tilen = 0;
>> +
>> + return rc;
>
> So, this blob only gets freed when there's an problem calculating the
> hash? What's the value in keeping this around after session setup is
> done?
>
This blob will get freed in build_ntlmssp_negotiate_blob() once the blob
is copied while building type 3 packet of ntlmssp negotiation.
That is in patch 3/4.
>> }
>>
>> void CalcNTLMv2_response(const struct cifsSesInfo *ses,
>> @@ -365,6 +470,10 @@ void CalcNTLMv2_response(const struct cifsSesInfo *ses,
>> hmac_md5_update(v2_session_response+8,
>> sizeof(struct ntlmv2_resp) - 8, &context);
>>
>> + if (ses->tilen)
>> + hmac_md5_update(ses->tiblob,
>> + ses->tilen, &context);
>> +
> ^^^^^^^
> nit: that looks like it would fit on one line
Yes, will fix that.
>
>> hmac_md5_final(v2_session_response, &context);
>> /* cifs_dump_mem("v2_sess_rsp: ", v2_session_response, 32); */
>> }
>> diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h
>> index 14dfa9a..c68f31c 100644
>> --- a/fs/cifs/cifsglob.h
>> +++ b/fs/cifs/cifsglob.h
>> @@ -222,6 +222,8 @@ struct cifsSesInfo {
>> char userName[MAX_USERNAME_SIZE + 1];
>> char *domainName;
>> char *password;
>> + unsigned int tilen; /* length of the target info blob */
>> + unsigned char *tiblob; /* target info blob in challenge response */
>> bool need_reconnect:1; /* connection reset, uid now invalid */
>> };
>> /* no more than one of the following three session flags may be set */
>> diff --git a/fs/cifs/cifspdu.h b/fs/cifs/cifspdu.h
>> index 14d036d..b0f4b56 100644
>> --- a/fs/cifs/cifspdu.h
>> +++ b/fs/cifs/cifspdu.h
>> @@ -663,7 +663,6 @@ struct ntlmv2_resp {
>> __le64 time;
>> __u64 client_chal; /* random */
>> __u32 reserved2;
>> - struct ntlmssp2_name names[2];
>> /* array of name entries could follow ending in minimum 4 byte struct */
>> } __attribute__((packed));
>>
>> diff --git a/fs/cifs/cifsproto.h b/fs/cifs/cifsproto.h
>> index 3f4fa81..c155479 100644
>> --- a/fs/cifs/cifsproto.h
>> +++ b/fs/cifs/cifsproto.h
>> @@ -367,7 +367,7 @@ extern int cifs_verify_signature(struct smb_hdr *,
>> extern int cifs_calculate_session_key(struct session_key *key, const char *rn,
>> const char *pass);
>> extern void CalcNTLMv2_response(const struct cifsSesInfo *, char *);
>> -extern void setup_ntlmv2_rsp(struct cifsSesInfo *, char *,
>> +extern int setup_ntlmv2_rsp(struct cifsSesInfo *, char *,
>> const struct nls_table *);
>> #ifdef CONFIG_CIFS_WEAK_PW_HASH
>> extern void calc_lanman_hash(const char *password, const char *cryptkey,
>> diff --git a/fs/cifs/cifssmb.c b/fs/cifs/cifssmb.c
>> index c65c341..13c854e 100644
>> --- a/fs/cifs/cifssmb.c
>> +++ b/fs/cifs/cifssmb.c
>> @@ -603,13 +603,15 @@ CIFSSMBNegotiate(unsigned int xid, struct cifsSesInfo *ses)
>> rc = 0;
>> else
>> rc = -EINVAL;
>> -
>> - if (server->sec_kerberos || server->sec_mskerberos)
>> - server->secType = Kerberos;
>> - else if (server->sec_ntlmssp)
>> - server->secType = RawNTLMSSP;
>> - else
>> - rc = -EOPNOTSUPP;
>> + if (server->secType == Kerberos) {
>> + if (!server->sec_kerberos &&
>> + !server->sec_mskerberos)
>> + rc = -EOPNOTSUPP;
>> + } else if (server->secType == RawNTLMSSP) {
>> + if (!server->sec_ntlmssp)
>> + rc = -EOPNOTSUPP;
>> + } else
>> + rc = -EOPNOTSUPP;
>> }
>> } else
>> server->capabilities &= ~CAP_EXTENDED_SECURITY;
>> diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c
>> index 67dad54..f8891a2 100644
>> --- a/fs/cifs/connect.c
>> +++ b/fs/cifs/connect.c
>> @@ -1740,6 +1740,8 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)
>> if (ses == NULL)
>> goto get_ses_fail;
>>
>> + ses->tilen = 0;
>> + ses->tiblob = NULL;
>> /* new SMB session uses our server ref */
>> ses->server = server;
>> if (server->addr.sockAddr6.sin6_family == AF_INET6)
>> diff --git a/fs/cifs/ntlmssp.h b/fs/cifs/ntlmssp.h
>> index 49c9a4e..5d52e4a 100644
>> --- a/fs/cifs/ntlmssp.h
>> +++ b/fs/cifs/ntlmssp.h
>> @@ -61,6 +61,21 @@
>> #define NTLMSSP_NEGOTIATE_KEY_XCH 0x40000000
>> #define NTLMSSP_NEGOTIATE_56 0x80000000
>>
>> +/* Define AV Pair Field IDs */
>> +enum av_field_type {
>> + NTLMSSP_AV_EOL = 0,
>> + NTLMSSP_AV_NB_COMPUTER_NAME,
>> + NTLMSSP_AV_NB_DOMAIN_NAME,
>> + NTLMSSP_AV_DNS_COMPUTER_NAME,
>> + NTLMSSP_AV_DNS_DOMAIN_NAME,
>> + NTLMSSP_AV_DNS_TREE_NAME,
>> + NTLMSSP_AV_FLAGS,
>> + NTLMSSP_AV_TIMESTAMP,
>> + NTLMSSP_AV_RESTRICTION,
>> + NTLMSSP_AV_TARGET_NAME,
>> + NTLMSSP_AV_CHANNEL_BINDINGS
>> +};
>> +
>> /* Although typedefs are not commonly used for structure definitions */
>> /* in the Linux kernel, in this particular case they are useful */
>> /* to more closely match the standards document for NTLMSSP from */
>
>
> --
> Jeff Layton <jlayton-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
>
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2010-09-14 11:33 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-09-13 19:15 [PATCH -v3 2/4 cifs] NTLMv2/NTLMSSP ntlmv2 within ntlmssp autentication code shirishpargaonkar-Re5JQEeQqe8AvxtiuMwx3w
[not found] ` <1284405322-31202-1-git-send-email-shirishpargaonkar-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
2010-09-14 11:23 ` Jeff Layton
[not found] ` <20100914072333.67649ee2-4QP7MXygkU+dMjc06nkz3ljfA9RmPOcC@public.gmane.org>
2010-09-14 11:33 ` Shirish Pargaonkar
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.