* [PATCH 2/4] smb: client: parse av pair type 4 in CHALLENGE_MESSAGE
2025-01-03 22:28 [PATCH 1/4] smb: client: introduce av_for_each_entry() helper Paulo Alcantara
@ 2025-01-03 22:28 ` Paulo Alcantara
2025-01-03 22:28 ` [PATCH 3/4] smb: client: fix DFS mount against old servers with NTLMSSP Paulo Alcantara
2025-01-03 22:28 ` [PATCH 4/4] smb: client: parse DNS domain name from domain= option Paulo Alcantara
2 siblings, 0 replies; 5+ messages in thread
From: Paulo Alcantara @ 2025-01-03 22:28 UTC (permalink / raw)
To: smfrench; +Cc: linux-cifs, Paulo Alcantara
Parse FQDN of the domain in CHALLENGE_MESSAGE message as it's gonna be
useful when mounting DFS shares against old Windows Servers (2012 R2
or earlier) that return not fully qualified hostnames for DFS targets
by default.
Signed-off-by: Paulo Alcantara (Red Hat) <pc@manguebit.com>
---
fs/smb/client/cifsencrypt.c | 63 ++++++++++++++++++++++++-------------
fs/smb/client/cifsglob.h | 1 +
fs/smb/client/misc.c | 1 +
3 files changed, 43 insertions(+), 22 deletions(-)
diff --git a/fs/smb/client/cifsencrypt.c b/fs/smb/client/cifsencrypt.c
index 981897ec4dcd..e69968e88fe7 100644
--- a/fs/smb/client/cifsencrypt.c
+++ b/fs/smb/client/cifsencrypt.c
@@ -348,31 +348,37 @@ static struct ntlmssp2_name *find_next_av(struct cifs_ses *ses,
return av;
}
-/* 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.
+/*
+ * Check if server has provided av pair of @type in the NTLMSSP
+ * CHALLENGE_MESSAGE blob.
*/
-static int find_domain_name(struct cifs_ses *ses)
+static int find_av_name(struct cifs_ses *ses, u16 type, char **name, u16 maxlen)
{
const struct nls_table *nlsc = ses->local_nls;
struct ntlmssp2_name *av;
- u16 len;
+ u16 len, nlen;
+
+ if (*name)
+ return 0;
av_for_each_entry(ses, av) {
len = AV_LEN(av);
- if (AV_TYPE(av) == NTLMSSP_AV_NB_DOMAIN_NAME &&
- len < CIFS_MAX_DOMAINNAME_LEN && !ses->domainName) {
- ses->domainName = kmalloc(len + 1, GFP_KERNEL);
- if (!ses->domainName)
+ if (AV_TYPE(av) != type)
+ continue;
+ if (!IS_ALIGNED(len, sizeof(__le16))) {
+ cifs_dbg(VFS | ONCE, "%s: bad length(%u) for type %u\n",
+ __func__, len, type);
+ continue;
+ }
+ nlen = len / sizeof(__le16);
+ if (nlen <= maxlen) {
+ ++nlen;
+ *name = kmalloc(nlen, GFP_KERNEL);
+ if (!*name)
return -ENOMEM;
- cifs_from_utf16(ses->domainName, AV_DATA_PTR(av),
- len, len, nlsc, NO_MAP_UNI_RSVD);
+ cifs_from_utf16(*name, AV_DATA_PTR(av), nlen,
+ len, nlsc, NO_MAP_UNI_RSVD);
+ break;
}
}
return 0;
@@ -546,16 +552,29 @@ setup_ntlmv2_rsp(struct cifs_ses *ses, const struct nls_table *nls_cp)
if (ses->server->negflavor == CIFS_NEGFLAVOR_EXTENDED) {
if (!ses->domainName) {
if (ses->domainAuto) {
- rc = find_domain_name(ses);
- if (rc) {
- cifs_dbg(VFS, "error %d finding domain name\n",
- rc);
+ /*
+ * Domain (workgroup) hasn't been specified in
+ * mount options, so try to find it in
+ * CHALLENGE_MESSAGE message and then use it as
+ * part of NTLMv2 authentication.
+ */
+ rc = find_av_name(ses, NTLMSSP_AV_NB_DOMAIN_NAME,
+ &ses->domainName,
+ CIFS_MAX_DOMAINNAME_LEN);
+ if (rc)
goto setup_ntlmv2_rsp_ret;
- }
} else {
ses->domainName = kstrdup("", GFP_KERNEL);
+ if (!ses->domainName) {
+ rc = -ENOMEM;
+ goto setup_ntlmv2_rsp_ret;
+ }
}
}
+ rc = find_av_name(ses, NTLMSSP_AV_DNS_DOMAIN_NAME,
+ &ses->dns_dom, CIFS_MAX_DOMAINNAME_LEN);
+ if (rc)
+ goto setup_ntlmv2_rsp_ret;
} else {
rc = build_avpair_blob(ses, nls_cp);
if (rc) {
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index 6e63abe461fd..e5982136e66f 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -1154,6 +1154,7 @@ struct cifs_ses {
/* ========= end: protected by chan_lock ======== */
struct cifs_ses *dfs_root_ses;
struct nls_table *local_nls;
+ char *dns_dom; /* FQDN of the domain */
};
static inline bool
diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c
index 4373dd64b66d..c23d5ba44cae 100644
--- a/fs/smb/client/misc.c
+++ b/fs/smb/client/misc.c
@@ -101,6 +101,7 @@ sesInfoFree(struct cifs_ses *buf_to_free)
kfree_sensitive(buf_to_free->password2);
kfree(buf_to_free->user_name);
kfree(buf_to_free->domainName);
+ kfree(buf_to_free->dns_dom);
kfree_sensitive(buf_to_free->auth_key.response);
spin_lock(&buf_to_free->iface_lock);
list_for_each_entry_safe(iface, niface, &buf_to_free->iface_list,
--
2.47.1
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 3/4] smb: client: fix DFS mount against old servers with NTLMSSP
2025-01-03 22:28 [PATCH 1/4] smb: client: introduce av_for_each_entry() helper Paulo Alcantara
2025-01-03 22:28 ` [PATCH 2/4] smb: client: parse av pair type 4 in CHALLENGE_MESSAGE Paulo Alcantara
@ 2025-01-03 22:28 ` Paulo Alcantara
2025-01-03 22:28 ` [PATCH 4/4] smb: client: parse DNS domain name from domain= option Paulo Alcantara
2 siblings, 0 replies; 5+ messages in thread
From: Paulo Alcantara @ 2025-01-03 22:28 UTC (permalink / raw)
To: smfrench; +Cc: linux-cifs, Paulo Alcantara
Old Windows servers will return not fully qualified DFS targets by
default as specified in
MS-DFSC 3.2.5.5 Receiving a Root Referral Request or Link Referral
Request
| Servers SHOULD<30> return fully qualified DNS host names of
| targets in responses to root referral requests and link referral
| requests.
| ...
| <30> Section 3.2.5.5: By default, Windows Server 2003, Windows
| Server 2008, Windows Server 2008 R2, Windows Server 2012, and
| Windows Server 2012 R2 return DNS host names that are not fully
| qualified for targets.
Fix this by converting all NetBIOS host names from DFS targets to
FQDNs and try resolving them first if DNS domain name was provided in
NTLMSSP CHALLENGE_MESSAGE message from previous SMB2_SESSION_SETUP.
This also prevents the client from translating the DFS target
hostnames to another domain depending on the network domain search
order.
Signed-off-by: Paulo Alcantara (Red Hat) <pc@manguebit.com>
---
fs/smb/client/cifsglob.h | 21 +++++++
fs/smb/client/connect.c | 5 +-
fs/smb/client/dfs.c | 13 ++--
fs/smb/client/dfs_cache.c | 3 +-
fs/smb/client/dns_resolve.c | 118 +++++++++++++++++++++---------------
fs/smb/client/dns_resolve.h | 3 +-
fs/smb/client/fs_context.c | 4 ++
fs/smb/client/fs_context.h | 1 +
fs/smb/client/misc.c | 3 +-
9 files changed, 113 insertions(+), 58 deletions(-)
diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h
index e5982136e66f..c747b6f9baca 100644
--- a/fs/smb/client/cifsglob.h
+++ b/fs/smb/client/cifsglob.h
@@ -828,6 +828,7 @@ struct TCP_Server_Info {
*/
char *leaf_fullpath;
bool dfs_conn:1;
+ char dns_dom[CIFS_MAX_DOMAINNAME_LEN + 1];
};
static inline bool is_smb1(struct TCP_Server_Info *server)
@@ -2312,4 +2313,24 @@ static inline bool cifs_ses_exiting(struct cifs_ses *ses)
return ret;
}
+static inline bool cifs_netbios_name(const char *name, size_t namelen)
+{
+ bool ret = false;
+ size_t i;
+
+ if (namelen >= 1 && namelen <= RFC1001_NAME_LEN) {
+ for (i = 0; i < namelen; i++) {
+ const unsigned char c = name[i];
+
+ if (c == '\\' || c == '/' || c == ':' || c == '*' ||
+ c == '?' || c == '"' || c == '<' || c == '>' ||
+ c == '|' || c == '.')
+ return false;
+ if (!ret && isalpha(c))
+ ret = true;
+ }
+ }
+ return ret;
+}
+
#endif /* _CIFS_GLOB_H */
diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c
index ddcc9e514a0e..d4b571d74191 100644
--- a/fs/smb/client/connect.c
+++ b/fs/smb/client/connect.c
@@ -97,7 +97,8 @@ static int reconn_set_ipaddr_from_hostname(struct TCP_Server_Info *server)
ss = server->dstaddr;
spin_unlock(&server->srv_lock);
- rc = dns_resolve_server_name_to_ip(unc, (struct sockaddr *)&ss, NULL);
+ rc = dns_resolve_server_name_to_ip(server->dns_dom, unc,
+ (struct sockaddr *)&ss, NULL);
kfree(unc);
if (rc < 0) {
@@ -1711,6 +1712,8 @@ cifs_get_tcp_session(struct smb3_fs_context *ctx,
goto out_err;
}
}
+ if (ctx->dns_dom)
+ strscpy(tcp_ses->dns_dom, ctx->dns_dom);
if (ctx->nosharesock)
tcp_ses->nosharesock = true;
diff --git a/fs/smb/client/dfs.c b/fs/smb/client/dfs.c
index 4647df9e1e3b..09d8808cd2e0 100644
--- a/fs/smb/client/dfs.c
+++ b/fs/smb/client/dfs.c
@@ -9,6 +9,8 @@
#include "fs_context.h"
#include "dfs.h"
+#define DFS_DOM(ctx) (ctx->dfs_root_ses ? ctx->dfs_root_ses->dns_dom : NULL)
+
/**
* dfs_parse_target_referral - set fs context for dfs target referral
*
@@ -46,8 +48,9 @@ int dfs_parse_target_referral(const char *full_path, const struct dfs_info3_para
if (rc)
goto out;
- rc = dns_resolve_server_name_to_ip(path, (struct sockaddr *)&ctx->dstaddr, NULL);
-
+ rc = dns_resolve_server_name_to_ip(DFS_DOM(ctx), path,
+ (struct sockaddr *)&ctx->dstaddr,
+ NULL);
out:
kfree(path);
return rc;
@@ -59,8 +62,9 @@ static int get_session(struct cifs_mount_ctx *mnt_ctx, const char *full_path)
int rc;
ctx->leaf_fullpath = (char *)full_path;
+ ctx->dns_dom = DFS_DOM(ctx);
rc = cifs_mount_get_session(mnt_ctx);
- ctx->leaf_fullpath = NULL;
+ ctx->leaf_fullpath = ctx->dns_dom = NULL;
return rc;
}
@@ -264,7 +268,8 @@ static int update_fs_context_dstaddr(struct smb3_fs_context *ctx)
int rc = 0;
if (!ctx->nodfs && ctx->dfs_automount) {
- rc = dns_resolve_server_name_to_ip(ctx->source, addr, NULL);
+ rc = dns_resolve_server_name_to_ip(NULL, ctx->source,
+ addr, NULL);
if (!rc)
cifs_set_port(addr, ctx->port);
ctx->dfs_automount = false;
diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c
index 541608b0267e..c0a167c869fb 100644
--- a/fs/smb/client/dfs_cache.c
+++ b/fs/smb/client/dfs_cache.c
@@ -1114,7 +1114,8 @@ static bool target_share_equal(struct cifs_tcon *tcon, const char *s1)
extract_unc_hostname(s1, &host, &hostlen);
scnprintf(unc, sizeof(unc), "\\\\%.*s", (int)hostlen, host);
- rc = dns_resolve_server_name_to_ip(unc, (struct sockaddr *)&ss, NULL);
+ rc = dns_resolve_server_name_to_ip(server->dns_dom, unc,
+ (struct sockaddr *)&ss, NULL);
if (rc < 0) {
cifs_dbg(FYI, "%s: could not resolve %.*s. assuming server address matches.\n",
__func__, (int)hostlen, host);
diff --git a/fs/smb/client/dns_resolve.c b/fs/smb/client/dns_resolve.c
index 8bf8978bc5d6..83db27f9c8f1 100644
--- a/fs/smb/client/dns_resolve.c
+++ b/fs/smb/client/dns_resolve.c
@@ -20,69 +20,87 @@
#include "cifsproto.h"
#include "cifs_debug.h"
-/**
- * dns_resolve_server_name_to_ip - Resolve UNC server name to ip address.
- * @unc: UNC path specifying the server (with '/' as delimiter)
- * @ip_addr: Where to return the IP address.
- * @expiry: Where to return the expiry time for the dns record.
- *
- * Returns zero success, -ve on error.
- */
-int
-dns_resolve_server_name_to_ip(const char *unc, struct sockaddr *ip_addr, time64_t *expiry)
+static int resolve_name(const char *name, size_t namelen,
+ struct sockaddr *addr, time64_t *expiry)
{
- const char *hostname, *sep;
char *ip;
- int len, rc;
+ int rc;
- if (!ip_addr || !unc)
- return -EINVAL;
-
- len = strlen(unc);
- if (len < 3) {
- cifs_dbg(FYI, "%s: unc is too short: %s\n", __func__, unc);
- return -EINVAL;
- }
-
- /* Discount leading slashes for cifs */
- len -= 2;
- hostname = unc + 2;
-
- /* Search for server name delimiter */
- sep = memchr(hostname, '/', len);
- if (sep)
- len = sep - hostname;
- else
- cifs_dbg(FYI, "%s: probably server name is whole unc: %s\n",
- __func__, unc);
-
- /* Try to interpret hostname as an IPv4 or IPv6 address */
- rc = cifs_convert_address(ip_addr, hostname, len);
- if (rc > 0) {
- cifs_dbg(FYI, "%s: unc is IP, skipping dns upcall: %*.*s\n", __func__, len, len,
- hostname);
- return 0;
- }
-
- /* Perform the upcall */
- rc = dns_query(current->nsproxy->net_ns, NULL, hostname, len,
- NULL, &ip, expiry, false);
+ rc = dns_query(current->nsproxy->net_ns, NULL, name,
+ namelen, NULL, &ip, expiry, false);
if (rc < 0) {
cifs_dbg(FYI, "%s: unable to resolve: %*.*s\n",
- __func__, len, len, hostname);
+ __func__, (int)namelen, (int)namelen, name);
} else {
cifs_dbg(FYI, "%s: resolved: %*.*s to %s expiry %llu\n",
- __func__, len, len, hostname, ip,
+ __func__, (int)namelen, (int)namelen, name, ip,
expiry ? (*expiry) : 0);
- rc = cifs_convert_address(ip_addr, ip, strlen(ip));
+ rc = cifs_convert_address(addr, ip, strlen(ip));
kfree(ip);
-
if (!rc) {
- cifs_dbg(FYI, "%s: unable to determine ip address\n", __func__);
+ cifs_dbg(FYI, "%s: unable to determine ip address\n",
+ __func__);
rc = -EHOSTUNREACH;
- } else
+ } else {
rc = 0;
+ }
}
return rc;
}
+
+/**
+ * dns_resolve_server_name_to_ip - Resolve UNC server name to ip address.
+ * @dom: optional DNS domain name
+ * @unc: UNC path specifying the server (with '/' as delimiter)
+ * @ip_addr: Where to return the IP address.
+ * @expiry: Where to return the expiry time for the dns record.
+ *
+ * Returns zero success, -ve on error.
+ */
+int dns_resolve_server_name_to_ip(const char *dom, const char *unc,
+ struct sockaddr *ip_addr, time64_t *expiry)
+{
+ const char *name;
+ size_t namelen, len;
+ char *s;
+ int rc;
+
+ if (!ip_addr || !unc)
+ return -EINVAL;
+
+ cifs_dbg(FYI, "%s: dom=%s unc=%s\n", __func__, dom, unc);
+ if (strlen(unc) < 3)
+ return -EINVAL;
+
+ extract_unc_hostname(unc, &name, &namelen);
+ if (!namelen)
+ return -EINVAL;
+
+ cifs_dbg(FYI, "%s: hostname=%.*s\n", __func__, (int)namelen, name);
+ /* Try to interpret hostname as an IPv4 or IPv6 address */
+ rc = cifs_convert_address(ip_addr, name, namelen);
+ if (rc > 0) {
+ cifs_dbg(FYI, "%s: unc is IP, skipping dns upcall: %*.*s\n",
+ __func__, (int)namelen, (int)namelen, name);
+ return 0;
+ }
+
+ /*
+ * If @name contains a NetBIOS name and @dom has been specified, then
+ * convert @name to an FQDN and try resolving it first.
+ */
+ if (dom && *dom && cifs_netbios_name(name, namelen)) {
+ len = strnlen(dom, CIFS_MAX_DOMAINNAME_LEN) + namelen + 2;
+ s = kmalloc(len, GFP_KERNEL);
+ if (!s)
+ return -ENOMEM;
+
+ scnprintf(s, len, "%.*s.%s", (int)namelen, name, dom);
+ rc = resolve_name(s, len - 1, ip_addr, expiry);
+ kfree(s);
+ if (!rc)
+ return 0;
+ }
+ return resolve_name(name, namelen, ip_addr, expiry);
+}
diff --git a/fs/smb/client/dns_resolve.h b/fs/smb/client/dns_resolve.h
index 6eb0c15a2440..64c1dd2ad39b 100644
--- a/fs/smb/client/dns_resolve.h
+++ b/fs/smb/client/dns_resolve.h
@@ -14,7 +14,8 @@
#include <linux/net.h>
#ifdef __KERNEL__
-int dns_resolve_server_name_to_ip(const char *unc, struct sockaddr *ip_addr, time64_t *expiry);
+int dns_resolve_server_name_to_ip(const char *dom, const char *unc,
+ struct sockaddr *ip_addr, time64_t *expiry);
#endif /* KERNEL */
#endif /* _DNS_RESOLVE_H */
diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c
index 49123f458d0c..5381f05420bc 100644
--- a/fs/smb/client/fs_context.c
+++ b/fs/smb/client/fs_context.c
@@ -385,6 +385,7 @@ smb3_fs_context_dup(struct smb3_fs_context *new_ctx, struct smb3_fs_context *ctx
new_ctx->source = NULL;
new_ctx->iocharset = NULL;
new_ctx->leaf_fullpath = NULL;
+ new_ctx->dns_dom = NULL;
/*
* Make sure to stay in sync with smb3_cleanup_fs_context_contents()
*/
@@ -399,6 +400,7 @@ smb3_fs_context_dup(struct smb3_fs_context *new_ctx, struct smb3_fs_context *ctx
DUP_CTX_STR(nodename);
DUP_CTX_STR(iocharset);
DUP_CTX_STR(leaf_fullpath);
+ DUP_CTX_STR(dns_dom);
return 0;
}
@@ -1863,6 +1865,8 @@ smb3_cleanup_fs_context_contents(struct smb3_fs_context *ctx)
ctx->prepath = NULL;
kfree(ctx->leaf_fullpath);
ctx->leaf_fullpath = NULL;
+ kfree(ctx->dns_dom);
+ ctx->dns_dom = NULL;
}
void
diff --git a/fs/smb/client/fs_context.h b/fs/smb/client/fs_context.h
index ac6baa774ad3..8813533345ee 100644
--- a/fs/smb/client/fs_context.h
+++ b/fs/smb/client/fs_context.h
@@ -295,6 +295,7 @@ struct smb3_fs_context {
bool dfs_automount:1; /* set for dfs automount only */
enum cifs_reparse_type reparse_type;
bool dfs_conn:1; /* set for dfs mounts */
+ char *dns_dom;
};
extern const struct fs_parameter_spec smb3_fs_parameters[];
diff --git a/fs/smb/client/misc.c b/fs/smb/client/misc.c
index c23d5ba44cae..0d483cd08354 100644
--- a/fs/smb/client/misc.c
+++ b/fs/smb/client/misc.c
@@ -1189,7 +1189,8 @@ int match_target_ip(struct TCP_Server_Info *server,
cifs_dbg(FYI, "%s: target name: %s\n", __func__, target + 2);
- rc = dns_resolve_server_name_to_ip(target, (struct sockaddr *)&ss, NULL);
+ rc = dns_resolve_server_name_to_ip(server->dns_dom, target,
+ (struct sockaddr *)&ss, NULL);
kfree(target);
if (rc < 0)
--
2.47.1
^ permalink raw reply related [flat|nested] 5+ messages in thread