]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
smb: client: fix DFS mount against old servers with NTLMSSP
authorPaulo Alcantara <pc@manguebit.com>
Fri, 3 Jan 2025 19:58:12 +0000 (16:58 -0300)
committerSteve French <stfrench@microsoft.com>
Mon, 20 Jan 2025 01:34:00 +0000 (19:34 -0600)
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>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/client/cifsglob.h
fs/smb/client/connect.c
fs/smb/client/dfs.c
fs/smb/client/dfs_cache.c
fs/smb/client/dns_resolve.c
fs/smb/client/dns_resolve.h
fs/smb/client/fs_context.c
fs/smb/client/fs_context.h
fs/smb/client/misc.c

index e5982136e66f0c96f0a7686681304358e122bc89..c747b6f9baca23fc976f3617d80c33d167ee8848 100644 (file)
@@ -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 */
index eaa6be4456d0eeb16e724c36a8a1b3c6e54e08dd..9e16edf5c8833a73823d3cdfcba75140309c3c6f 100644 (file)
@@ -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) {
@@ -1710,6 +1711,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;
index 4647df9e1e3bfcdc258ad77eb18951d841e5fe89..09d8808cd2e040331359ac04f87b6ef36d8e6cbc 100644 (file)
@@ -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;
index 541608b0267efff53ac4d60eac4559a52539f7d8..c0a167c869fbee4561c2fe78e3c0d019a1a26484 100644 (file)
@@ -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);
index 8bf8978bc5d66c8c963ec151ced1eb2ef5b28781..83db27f9c8f1ea7e01fa38a9c6d357170ae0af4c 100644 (file)
 #include "cifsproto.h"
 #include "cifs_debug.h"
 
+static int resolve_name(const char *name, size_t namelen,
+                       struct sockaddr *addr, time64_t *expiry)
+{
+       char *ip;
+       int rc;
+
+       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__, (int)namelen, (int)namelen, name);
+       } else {
+               cifs_dbg(FYI, "%s: resolved: %*.*s to %s expiry %llu\n",
+                        __func__, (int)namelen, (int)namelen, name, ip,
+                        expiry ? (*expiry) : 0);
+
+               rc = cifs_convert_address(addr, ip, strlen(ip));
+               kfree(ip);
+               if (!rc) {
+                       cifs_dbg(FYI, "%s: unable to determine ip address\n",
+                                __func__);
+                       rc = -EHOSTUNREACH;
+               } 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 *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)
 {
-       const char *hostname, *sep;
-       char *ip;
-       int len, rc;
+       const char *name;
+       size_t namelen, len;
+       char *s;
+       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);
+       cifs_dbg(FYI, "%s: dom=%s unc=%s\n", __func__, dom, unc);
+       if (strlen(unc) < 3)
                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);
+       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, hostname, len);
+       rc = cifs_convert_address(ip_addr, name, namelen);
        if (rc > 0) {
-               cifs_dbg(FYI, "%s: unc is IP, skipping dns upcall: %*.*s\n", __func__, len, len,
-                        hostname);
+               cifs_dbg(FYI, "%s: unc is IP, skipping dns upcall: %*.*s\n",
+                        __func__, (int)namelen, (int)namelen, name);
                return 0;
        }
 
-       /* Perform the upcall */
-       rc = dns_query(current->nsproxy->net_ns, NULL, hostname, len,
-                      NULL, &ip, expiry, false);
-       if (rc < 0) {
-               cifs_dbg(FYI, "%s: unable to resolve: %*.*s\n",
-                        __func__, len, len, hostname);
-       } else {
-               cifs_dbg(FYI, "%s: resolved: %*.*s to %s expiry %llu\n",
-                        __func__, len, len, hostname, ip,
-                        expiry ? (*expiry) : 0);
-
-               rc = cifs_convert_address(ip_addr, ip, strlen(ip));
-               kfree(ip);
+       /*
+        * 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;
 
-               if (!rc) {
-                       cifs_dbg(FYI, "%s: unable to determine ip address\n", __func__);
-                       rc = -EHOSTUNREACH;
-               } else
-                       rc = 0;
+               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 rc;
+       return resolve_name(name, namelen, ip_addr, expiry);
 }
index 6eb0c15a244066e83bff7960aecba2258056490e..64c1dd2ad39b723d01d66efad7b46e3f879dd47a 100644 (file)
@@ -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 */
index 49123f458d0c72e6e0a77223dfcc9e01180fbbc3..5381f05420bc240d6c6b8bf82cd65f2c52664a30 100644 (file)
@@ -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
index ac6baa774ad3a9c5f26f244be74b983a69001e92..8813533345ee72c8554629063ed9ff6b74efbde6 100644 (file)
@@ -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[];
index c23d5ba44caebaf9b3e4397afb6060cc51455ada..0d483cd083542bb6753c8a7f95bc083a3f5f8c34 100644 (file)
@@ -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)