Commit 327a8d76 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag '5.9-rc-smb3-fixes-part1' of git://git.samba.org/sfrench/cifs-2.6

Pull cifs updates from Steve French:
 "16 cifs/smb3 fixes, about half DFS related, two fixes for stable.

  Still working on and testing an additional set of fixes (including
  updates to mount, and some fallocate scenario improvements) for later
  in the merge window"

* tag '5.9-rc-smb3-fixes-part1' of git://git.samba.org/sfrench/cifs-2.6:
  cifs: document and cleanup dfs mount
  cifs: only update prefix path of DFS links in cifs_tree_connect()
  cifs: fix double free error on share and prefix
  cifs: handle RESP_GET_DFS_REFERRAL.PathConsumed in reconnect
  cifs: handle empty list of targets in cifs_reconnect()
  cifs: rename reconn_inval_dfs_target()
  cifs: reduce number of referral requests in DFS link lookups
  cifs: merge __{cifs,smb2}_reconnect[_tcon]() into cifs_tree_connect()
  cifs: convert to use be32_add_cpu()
  cifs: delete duplicated words in header files
  cifs: Remove the superfluous break
  cifs: smb1: Try failing back to SetFileInfo if SetPathInfo fails
  cifs`: handle ERRBaduid for SMB1
  cifs: remove unused variable 'server'
  smb3: warn on confusing error scenario with sec=krb5
  cifs: Fix leak when handling lease break for cached root fid
parents 96e3f3c1 7efd0815
...@@ -132,7 +132,7 @@ struct cifs_ace { ...@@ -132,7 +132,7 @@ struct cifs_ace {
/* /*
* The current SMB3 form of security descriptor is similar to what was used for * The current SMB3 form of security descriptor is similar to what was used for
* cifs (see above) but some fields are split, and fields in the struct below * cifs (see above) but some fields are split, and fields in the struct below
* matches names of fields to the the spec, MS-DTYP (see sections 2.4.5 and * matches names of fields to the spec, MS-DTYP (see sections 2.4.5 and
* 2.4.6). Note that "CamelCase" fields are used in this struct in order to * 2.4.6). Note that "CamelCase" fields are used in this struct in order to
* match the MS-DTYP and MS-SMB2 specs which define the wire format. * match the MS-DTYP and MS-SMB2 specs which define the wire format.
*/ */
...@@ -178,7 +178,7 @@ struct smb3_acl { ...@@ -178,7 +178,7 @@ struct smb3_acl {
/* /*
* Used to store the special 'NFS SIDs' used to persist the POSIX uid and gid * Used to store the special 'NFS SIDs' used to persist the POSIX uid and gid
* See See http://technet.microsoft.com/en-us/library/hh509017(v=ws.10).aspx * See http://technet.microsoft.com/en-us/library/hh509017(v=ws.10).aspx
*/ */
struct owner_sid { struct owner_sid {
u8 Revision; u8 Revision;
......
...@@ -1466,7 +1466,7 @@ struct cifsInodeInfo { ...@@ -1466,7 +1466,7 @@ struct cifsInodeInfo {
struct list_head llist; /* locks helb by this inode */ struct list_head llist; /* locks helb by this inode */
/* /*
* NOTE: Some code paths call down_read(lock_sem) twice, so * NOTE: Some code paths call down_read(lock_sem) twice, so
* we must always use use cifs_down_write() instead of down_write() * we must always use cifs_down_write() instead of down_write()
* for this semaphore to avoid deadlocks. * for this semaphore to avoid deadlocks.
*/ */
struct rw_semaphore lock_sem; /* protect the fields above */ struct rw_semaphore lock_sem; /* protect the fields above */
......
...@@ -154,6 +154,7 @@ extern int decode_negTokenInit(unsigned char *security_blob, int length, ...@@ -154,6 +154,7 @@ extern int decode_negTokenInit(unsigned char *security_blob, int length,
extern int cifs_convert_address(struct sockaddr *dst, const char *src, int len); extern int cifs_convert_address(struct sockaddr *dst, const char *src, int len);
extern void cifs_set_port(struct sockaddr *addr, const unsigned short int port); extern void cifs_set_port(struct sockaddr *addr, const unsigned short int port);
extern int map_smb_to_linux_error(char *buf, bool logErr); extern int map_smb_to_linux_error(char *buf, bool logErr);
extern int map_and_check_smb_error(struct mid_q_entry *mid, bool logErr);
extern void header_assemble(struct smb_hdr *, char /* command */ , extern void header_assemble(struct smb_hdr *, char /* command */ ,
const struct cifs_tcon *, int /* length of const struct cifs_tcon *, int /* length of
fixed section (word count) in two byte units */); fixed section (word count) in two byte units */);
...@@ -271,6 +272,9 @@ extern void cifs_move_llist(struct list_head *source, struct list_head *dest); ...@@ -271,6 +272,9 @@ extern void cifs_move_llist(struct list_head *source, struct list_head *dest);
extern void cifs_free_llist(struct list_head *llist); extern void cifs_free_llist(struct list_head *llist);
extern void cifs_del_lock_waiters(struct cifsLockInfo *lock); extern void cifs_del_lock_waiters(struct cifsLockInfo *lock);
extern int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon,
const struct nls_table *nlsc);
extern int cifs_negotiate_protocol(const unsigned int xid, extern int cifs_negotiate_protocol(const unsigned int xid,
struct cifs_ses *ses); struct cifs_ses *ses);
extern int cifs_setup_session(const unsigned int xid, struct cifs_ses *ses, extern int cifs_setup_session(const unsigned int xid, struct cifs_ses *ses,
...@@ -344,7 +348,7 @@ extern int CIFSSMBQFSPosixInfo(const unsigned int xid, struct cifs_tcon *tcon, ...@@ -344,7 +348,7 @@ extern int CIFSSMBQFSPosixInfo(const unsigned int xid, struct cifs_tcon *tcon,
extern int CIFSSMBSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon, extern int CIFSSMBSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon,
const char *fileName, const FILE_BASIC_INFO *data, const char *fileName, const FILE_BASIC_INFO *data,
const struct nls_table *nls_codepage, const struct nls_table *nls_codepage,
int remap_special_chars); struct cifs_sb_info *cifs_sb);
extern int CIFSSMBSetFileInfo(const unsigned int xid, struct cifs_tcon *tcon, extern int CIFSSMBSetFileInfo(const unsigned int xid, struct cifs_tcon *tcon,
const FILE_BASIC_INFO *data, __u16 fid, const FILE_BASIC_INFO *data, __u16 fid,
__u32 pid_of_opener); __u32 pid_of_opener);
...@@ -613,8 +617,7 @@ int smb2_parse_query_directory(struct cifs_tcon *tcon, struct kvec *rsp_iov, ...@@ -613,8 +617,7 @@ int smb2_parse_query_directory(struct cifs_tcon *tcon, struct kvec *rsp_iov,
struct super_block *cifs_get_tcp_super(struct TCP_Server_Info *server); struct super_block *cifs_get_tcp_super(struct TCP_Server_Info *server);
void cifs_put_tcp_super(struct super_block *sb); void cifs_put_tcp_super(struct super_block *sb);
int update_super_prepath(struct cifs_tcon *tcon, const char *prefix, int update_super_prepath(struct cifs_tcon *tcon, char *prefix);
size_t prefix_len);
#ifdef CONFIG_CIFS_DFS_UPCALL #ifdef CONFIG_CIFS_DFS_UPCALL
static inline int get_dfs_path(const unsigned int xid, struct cifs_ses *ses, static inline int get_dfs_path(const unsigned int xid, struct cifs_ses *ses,
......
...@@ -124,116 +124,6 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon) ...@@ -124,116 +124,6 @@ cifs_mark_open_files_invalid(struct cifs_tcon *tcon)
*/ */
} }
#ifdef CONFIG_CIFS_DFS_UPCALL
static int __cifs_reconnect_tcon(const struct nls_table *nlsc,
struct cifs_tcon *tcon)
{
int rc;
struct TCP_Server_Info *server = tcon->ses->server;
struct dfs_cache_tgt_list tl;
struct dfs_cache_tgt_iterator *it = NULL;
char *tree;
const char *tcp_host;
size_t tcp_host_len;
const char *dfs_host;
size_t dfs_host_len;
tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL);
if (!tree)
return -ENOMEM;
if (!tcon->dfs_path) {
if (tcon->ipc) {
scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$",
server->hostname);
rc = CIFSTCon(0, tcon->ses, tree, tcon, nlsc);
} else {
rc = CIFSTCon(0, tcon->ses, tcon->treeName, tcon, nlsc);
}
goto out;
}
rc = dfs_cache_noreq_find(tcon->dfs_path + 1, NULL, &tl);
if (rc)
goto out;
extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len);
for (it = dfs_cache_get_tgt_iterator(&tl); it;
it = dfs_cache_get_next_tgt(&tl, it)) {
const char *share, *prefix;
size_t share_len, prefix_len;
bool target_match;
rc = dfs_cache_get_tgt_share(it, &share, &share_len, &prefix,
&prefix_len);
if (rc) {
cifs_dbg(VFS, "%s: failed to parse target share %d\n",
__func__, rc);
continue;
}
extract_unc_hostname(share, &dfs_host, &dfs_host_len);
if (dfs_host_len != tcp_host_len
|| strncasecmp(dfs_host, tcp_host, dfs_host_len) != 0) {
cifs_dbg(FYI, "%s: %.*s doesn't match %.*s\n",
__func__,
(int)dfs_host_len, dfs_host,
(int)tcp_host_len, tcp_host);
rc = match_target_ip(server, dfs_host, dfs_host_len,
&target_match);
if (rc) {
cifs_dbg(VFS, "%s: failed to match target ip: %d\n",
__func__, rc);
break;
}
if (!target_match) {
cifs_dbg(FYI, "%s: skipping target\n", __func__);
continue;
}
}
if (tcon->ipc) {
scnprintf(tree, MAX_TREE_SIZE, "\\\\%.*s\\IPC$",
(int)share_len, share);
rc = CIFSTCon(0, tcon->ses, tree, tcon, nlsc);
} else {
scnprintf(tree, MAX_TREE_SIZE, "\\%.*s", (int)share_len,
share);
rc = CIFSTCon(0, tcon->ses, tree, tcon, nlsc);
if (!rc) {
rc = update_super_prepath(tcon, prefix,
prefix_len);
break;
}
}
if (rc == -EREMOTE)
break;
}
if (!rc) {
if (it)
rc = dfs_cache_noreq_update_tgthint(tcon->dfs_path + 1,
it);
else
rc = -ENOENT;
}
dfs_cache_free_tgts(&tl);
out:
kfree(tree);
return rc;
}
#else
static inline int __cifs_reconnect_tcon(const struct nls_table *nlsc,
struct cifs_tcon *tcon)
{
return CIFSTCon(0, tcon->ses, tcon->treeName, tcon, nlsc);
}
#endif
/* reconnect the socket, tcon, and smb session if needed */ /* reconnect the socket, tcon, and smb session if needed */
static int static int
cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command) cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command)
...@@ -338,7 +228,7 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command) ...@@ -338,7 +228,7 @@ cifs_reconnect_tcon(struct cifs_tcon *tcon, int smb_command)
} }
cifs_mark_open_files_invalid(tcon); cifs_mark_open_files_invalid(tcon);
rc = __cifs_reconnect_tcon(nls_codepage, tcon); rc = cifs_tree_connect(0, tcon, nls_codepage);
mutex_unlock(&ses->session_mutex); mutex_unlock(&ses->session_mutex);
cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc); cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc);
...@@ -5913,10 +5803,42 @@ CIFSSMBSetFileDisposition(const unsigned int xid, struct cifs_tcon *tcon, ...@@ -5913,10 +5803,42 @@ CIFSSMBSetFileDisposition(const unsigned int xid, struct cifs_tcon *tcon,
return rc; return rc;
} }
static int
CIFSSMBSetPathInfoFB(const unsigned int xid, struct cifs_tcon *tcon,
const char *fileName, const FILE_BASIC_INFO *data,
const struct nls_table *nls_codepage,
struct cifs_sb_info *cifs_sb)
{
int oplock = 0;
struct cifs_open_parms oparms;
struct cifs_fid fid;
int rc;
oparms.tcon = tcon;
oparms.cifs_sb = cifs_sb;
oparms.desired_access = GENERIC_WRITE;
oparms.create_options = cifs_create_options(cifs_sb, 0);
oparms.disposition = FILE_OPEN;
oparms.path = fileName;
oparms.fid = &fid;
oparms.reconnect = false;
rc = CIFS_open(xid, &oparms, &oplock, NULL);
if (rc)
goto out;
rc = CIFSSMBSetFileInfo(xid, tcon, data, fid.netfid, current->tgid);
CIFSSMBClose(xid, tcon, fid.netfid);
out:
return rc;
}
int int
CIFSSMBSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon, CIFSSMBSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon,
const char *fileName, const FILE_BASIC_INFO *data, const char *fileName, const FILE_BASIC_INFO *data,
const struct nls_table *nls_codepage, int remap) const struct nls_table *nls_codepage,
struct cifs_sb_info *cifs_sb)
{ {
TRANSACTION2_SPI_REQ *pSMB = NULL; TRANSACTION2_SPI_REQ *pSMB = NULL;
TRANSACTION2_SPI_RSP *pSMBr = NULL; TRANSACTION2_SPI_RSP *pSMBr = NULL;
...@@ -5925,6 +5847,7 @@ CIFSSMBSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon, ...@@ -5925,6 +5847,7 @@ CIFSSMBSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon,
int bytes_returned = 0; int bytes_returned = 0;
char *data_offset; char *data_offset;
__u16 params, param_offset, offset, byte_count, count; __u16 params, param_offset, offset, byte_count, count;
int remap = cifs_remap(cifs_sb);
cifs_dbg(FYI, "In SetTimes\n"); cifs_dbg(FYI, "In SetTimes\n");
...@@ -5987,6 +5910,10 @@ CIFSSMBSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon, ...@@ -5987,6 +5910,10 @@ CIFSSMBSetPathInfo(const unsigned int xid, struct cifs_tcon *tcon,
if (rc == -EAGAIN) if (rc == -EAGAIN)
goto SetTimesRetry; goto SetTimesRetry;
if (rc == -EOPNOTSUPP)
return CIFSSMBSetPathInfoFB(xid, tcon, fileName, data,
nls_codepage, cifs_sb);
return rc; return rc;
} }
......
...@@ -393,15 +393,14 @@ static inline int reconn_set_ipaddr(struct TCP_Server_Info *server) ...@@ -393,15 +393,14 @@ static inline int reconn_set_ipaddr(struct TCP_Server_Info *server)
#ifdef CONFIG_CIFS_DFS_UPCALL #ifdef CONFIG_CIFS_DFS_UPCALL
/* These functions must be called with server->srv_mutex held */ /* These functions must be called with server->srv_mutex held */
static void reconn_inval_dfs_target(struct TCP_Server_Info *server, static void reconn_set_next_dfs_target(struct TCP_Server_Info *server,
struct cifs_sb_info *cifs_sb, struct cifs_sb_info *cifs_sb,
struct dfs_cache_tgt_list *tgt_list, struct dfs_cache_tgt_list *tgt_list,
struct dfs_cache_tgt_iterator **tgt_it) struct dfs_cache_tgt_iterator **tgt_it)
{ {
const char *name; const char *name;
if (!cifs_sb || !cifs_sb->origin_fullpath || !tgt_list || if (!cifs_sb || !cifs_sb->origin_fullpath)
!server->nr_targets)
return; return;
if (!*tgt_it) { if (!*tgt_it) {
...@@ -471,11 +470,13 @@ cifs_reconnect(struct TCP_Server_Info *server) ...@@ -471,11 +470,13 @@ cifs_reconnect(struct TCP_Server_Info *server)
sb = NULL; sb = NULL;
} else { } else {
cifs_sb = CIFS_SB(sb); cifs_sb = CIFS_SB(sb);
rc = reconn_setup_dfs_targets(cifs_sb, &tgt_list); rc = reconn_setup_dfs_targets(cifs_sb, &tgt_list);
if (rc && (rc != -EOPNOTSUPP)) { if (rc) {
cifs_sb = NULL;
if (rc != -EOPNOTSUPP) {
cifs_server_dbg(VFS, "%s: no target servers for DFS failover\n", cifs_server_dbg(VFS, "%s: no target servers for DFS failover\n",
__func__); __func__);
}
} else { } else {
server->nr_targets = dfs_cache_get_nr_tgts(&tgt_list); server->nr_targets = dfs_cache_get_nr_tgts(&tgt_list);
} }
...@@ -578,7 +579,7 @@ cifs_reconnect(struct TCP_Server_Info *server) ...@@ -578,7 +579,7 @@ cifs_reconnect(struct TCP_Server_Info *server)
* feature is disabled, then we will retry last server we * feature is disabled, then we will retry last server we
* connected to before. * connected to before.
*/ */
reconn_inval_dfs_target(server, cifs_sb, &tgt_list, &tgt_it); reconn_set_next_dfs_target(server, cifs_sb, &tgt_list, &tgt_it);
#endif #endif
rc = reconn_set_ipaddr(server); rc = reconn_set_ipaddr(server);
if (rc) { if (rc) {
...@@ -4422,11 +4423,11 @@ build_unc_path_to_root(const struct smb_vol *vol, ...@@ -4422,11 +4423,11 @@ build_unc_path_to_root(const struct smb_vol *vol,
static int static int
expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses, expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses,
struct smb_vol *volume_info, struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info, struct cifs_sb_info *cifs_sb,
int check_prefix) char *ref_path)
{ {
int rc; int rc;
struct dfs_info3_param referral = {0}; struct dfs_info3_param referral = {0};
char *full_path = NULL, *ref_path = NULL, *mdata = NULL; char *full_path = NULL, *mdata = NULL;
if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS)
return -EREMOTE; return -EREMOTE;
...@@ -4435,9 +4436,6 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses, ...@@ -4435,9 +4436,6 @@ expand_dfs_referral(const unsigned int xid, struct cifs_ses *ses,
if (IS_ERR(full_path)) if (IS_ERR(full_path))
return PTR_ERR(full_path); return PTR_ERR(full_path);
/* For DFS paths, skip the first '\' of the UNC */
ref_path = check_prefix ? full_path + 1 : volume_info->UNC + 1;
rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb), rc = dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb),
ref_path, &referral, NULL); ref_path, &referral, NULL);
if (!rc) { if (!rc) {
...@@ -4500,13 +4498,10 @@ static int update_vol_info(const struct dfs_cache_tgt_iterator *tgt_it, ...@@ -4500,13 +4498,10 @@ static int update_vol_info(const struct dfs_cache_tgt_iterator *tgt_it,
return 0; return 0;
} }
static int setup_dfs_tgt_conn(const char *path, static int setup_dfs_tgt_conn(const char *path, const char *full_path,
const struct dfs_cache_tgt_iterator *tgt_it, const struct dfs_cache_tgt_iterator *tgt_it,
struct cifs_sb_info *cifs_sb, struct cifs_sb_info *cifs_sb, struct smb_vol *vol, unsigned int *xid,
struct smb_vol *vol, struct TCP_Server_Info **server, struct cifs_ses **ses,
unsigned int *xid,
struct TCP_Server_Info **server,
struct cifs_ses **ses,
struct cifs_tcon **tcon) struct cifs_tcon **tcon)
{ {
int rc; int rc;
...@@ -4520,8 +4515,7 @@ static int setup_dfs_tgt_conn(const char *path, ...@@ -4520,8 +4515,7 @@ static int setup_dfs_tgt_conn(const char *path,
if (rc) if (rc)
return rc; return rc;
mdata = cifs_compose_mount_options(cifs_sb->mountdata, path, &ref, mdata = cifs_compose_mount_options(cifs_sb->mountdata, full_path + 1, &ref, &fake_devname);
&fake_devname);
free_dfs_info_param(&ref); free_dfs_info_param(&ref);
if (IS_ERR(mdata)) { if (IS_ERR(mdata)) {
...@@ -4544,7 +4538,7 @@ static int setup_dfs_tgt_conn(const char *path, ...@@ -4544,7 +4538,7 @@ static int setup_dfs_tgt_conn(const char *path,
mount_put_conns(cifs_sb, *xid, *server, *ses, *tcon); mount_put_conns(cifs_sb, *xid, *server, *ses, *tcon);
rc = mount_get_conns(&fake_vol, cifs_sb, xid, server, ses, rc = mount_get_conns(&fake_vol, cifs_sb, xid, server, ses,
tcon); tcon);
if (!rc) { if (!rc || (*server && *ses)) {
/* /*
* We were able to connect to new target server. * We were able to connect to new target server.
* Update current volume info with new target server. * Update current volume info with new target server.
...@@ -4556,13 +4550,9 @@ static int setup_dfs_tgt_conn(const char *path, ...@@ -4556,13 +4550,9 @@ static int setup_dfs_tgt_conn(const char *path,
return rc; return rc;
} }
static int mount_do_dfs_failover(const char *path, static int do_dfs_failover(const char *path, const char *full_path, struct cifs_sb_info *cifs_sb,
struct cifs_sb_info *cifs_sb, struct smb_vol *vol, struct cifs_ses *root_ses, unsigned int *xid,
struct smb_vol *vol, struct TCP_Server_Info **server, struct cifs_ses **ses,
struct cifs_ses *root_ses,
unsigned int *xid,
struct TCP_Server_Info **server,
struct cifs_ses **ses,
struct cifs_tcon **tcon) struct cifs_tcon **tcon)
{ {
int rc; int rc;
...@@ -4582,9 +4572,9 @@ static int mount_do_dfs_failover(const char *path, ...@@ -4582,9 +4572,9 @@ static int mount_do_dfs_failover(const char *path,
if (rc) if (rc)
break; break;
/* Connect to next DFS target */ /* Connect to next DFS target */
rc = setup_dfs_tgt_conn(path, tgt_it, cifs_sb, vol, xid, server, rc = setup_dfs_tgt_conn(path, full_path, tgt_it, cifs_sb, vol, xid, server, ses,
ses, tcon); tcon);
if (!rc || rc == -EACCES || rc == -EOPNOTSUPP) if (!rc || (*server && *ses))
break; break;
} }
if (!rc) { if (!rc) {
...@@ -4754,207 +4744,210 @@ static int is_path_remote(struct cifs_sb_info *cifs_sb, struct smb_vol *vol, ...@@ -4754,207 +4744,210 @@ static int is_path_remote(struct cifs_sb_info *cifs_sb, struct smb_vol *vol,
} }
#ifdef CONFIG_CIFS_DFS_UPCALL #ifdef CONFIG_CIFS_DFS_UPCALL
static inline void set_root_tcon(struct cifs_sb_info *cifs_sb, static void set_root_ses(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
struct cifs_tcon *tcon, struct cifs_ses **root_ses)
struct cifs_tcon **root)
{ {
if (ses) {
spin_lock(&cifs_tcp_ses_lock); spin_lock(&cifs_tcp_ses_lock);
tcon->tc_count++; ses->ses_count++;
tcon->remap = cifs_remap(cifs_sb); ses->tcon_ipc->remap = cifs_remap(cifs_sb);
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
*root = tcon; }
*root_ses = ses;
}
static void put_root_ses(struct cifs_ses *ses)
{
if (ses)
cifs_put_smb_ses(ses);
}
/* Check if a path component is remote and then update @dfs_path accordingly */
static int check_dfs_prepath(struct cifs_sb_info *cifs_sb, struct smb_vol *vol,
const unsigned int xid, struct TCP_Server_Info *server,
struct cifs_tcon *tcon, char **dfs_path)
{
char *path, *s;
char sep = CIFS_DIR_SEP(cifs_sb), tmp;
char *npath;
int rc = 0;
int added_treename = tcon->Flags & SMB_SHARE_IS_IN_DFS;
int skip = added_treename;
path = cifs_build_path_to_root(vol, cifs_sb, tcon, added_treename);
if (!path)
return -ENOMEM;
/*
* Walk through the path components in @path and check if they're accessible. In case any of
* the components is -EREMOTE, then update @dfs_path with the next DFS referral request path
* (NOT including the remaining components).
*/
s = path;
do {
/* skip separators */
while (*s && *s == sep)
s++;
if (!*s)
break;
/* next separator */
while (*s && *s != sep)
s++;
/*
* if the treename is added, we then have to skip the first
* part within the separators
*/
if (skip) {
skip = 0;
continue;
}
tmp = *s;
*s = 0;
rc = server->ops->is_path_accessible(xid, tcon, cifs_sb, path);
if (rc && rc == -EREMOTE) {
struct smb_vol v = {NULL};
/* if @path contains a tree name, skip it in the prefix path */
if (added_treename) {
rc = cifs_parse_devname(path, &v);
if (rc)
break;
rc = -EREMOTE;
npath = build_unc_path_to_root(&v, cifs_sb, true);
cifs_cleanup_volume_info_contents(&v);
} else {
v.UNC = vol->UNC;
v.prepath = path + 1;
npath = build_unc_path_to_root(&v, cifs_sb, true);
}
if (IS_ERR(npath)) {
rc = PTR_ERR(npath);
break;
}
kfree(*dfs_path);
*dfs_path = npath;
}
*s = tmp;
} while (rc == 0);
kfree(path);
return rc;
} }
int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol) int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
{ {
int rc = 0; int rc = 0;
unsigned int xid; unsigned int xid;
struct cifs_ses *ses; struct TCP_Server_Info *server = NULL;
struct cifs_tcon *root_tcon = NULL; struct cifs_ses *ses = NULL, *root_ses = NULL;
struct cifs_tcon *tcon = NULL; struct cifs_tcon *tcon = NULL;
struct TCP_Server_Info *server; int count = 0;
char *root_path = NULL, *full_path = NULL; char *ref_path = NULL, *full_path = NULL;
char *old_mountdata, *origin_mountdata = NULL; char *oldmnt = NULL;
int count; char *mntdata = NULL;
rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon); rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon);
if (!rc && tcon) {
/* If not a standalone DFS root, then check if path is remote */
rc = dfs_cache_find(xid, ses, cifs_sb->local_nls,
cifs_remap(cifs_sb), vol->UNC + 1, NULL,
NULL);
if (rc) {
rc = is_path_remote(cifs_sb, vol, xid, server, tcon);
if (!rc)
goto out;
if (rc != -EREMOTE)
goto error;
}
}
/* /*
* If first DFS target server went offline and we failed to connect it, * Unconditionally try to get an DFS referral (even cached) to determine whether it is an
* server and ses pointers are NULL at this point, though we still have * DFS mount.
* chance to get a cached DFS referral in expand_dfs_referral() and
* retry next target available in it.
* *
* If a NULL ses ptr is passed to dfs_cache_find(), a lookup will be * Skip prefix path to provide support for DFS referrals from w2k8 servers which don't seem
* performed against DFS path and *no* requests will be sent to server * to respond with PATH_NOT_COVERED to requests that include the prefix.
* for any new DFS referrals. Hence it's safe to skip checking whether
* server or ses ptr is NULL.
*/ */
if (rc == -EACCES || rc == -EOPNOTSUPP) if (dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb), vol->UNC + 1, NULL,
goto error; NULL)) {
/* No DFS referral was returned. Looks like a regular share. */
root_path = build_unc_path_to_root(vol, cifs_sb, false); if (rc)
if (IS_ERR(root_path)) {
rc = PTR_ERR(root_path);
root_path = NULL;
goto error;
}
full_path = build_unc_path_to_root(vol, cifs_sb, true);
if (IS_ERR(full_path)) {
rc = PTR_ERR(full_path);
full_path = NULL;
goto error; goto error;
} /* Check if it is fully accessible and then mount it */
/* rc = is_path_remote(cifs_sb, vol, xid, server, tcon);
* Perform an unconditional check for whether there are DFS if (!rc)
* referrals for this path without prefix, to provide support goto out;
* for DFS referrals from w2k8 servers which don't seem to respond if (rc != -EREMOTE)
* with PATH_NOT_COVERED to requests that include the prefix.
* Chase the referral if found, otherwise continue normally.
*/
old_mountdata = cifs_sb->mountdata;
(void)expand_dfs_referral(xid, ses, vol, cifs_sb, false);
if (cifs_sb->mountdata == NULL) {
rc = -ENOENT;
goto error; goto error;
} }
/* Save mount options */
/* Save DFS root volume information for DFS refresh worker */ mntdata = kstrndup(cifs_sb->mountdata, strlen(cifs_sb->mountdata), GFP_KERNEL);
origin_mountdata = kstrndup(cifs_sb->mountdata, if (!mntdata) {
strlen(cifs_sb->mountdata), GFP_KERNEL);
if (!origin_mountdata) {
rc = -ENOMEM; rc = -ENOMEM;
goto error; goto error;
} }
/* Get path of DFS root */
if (cifs_sb->mountdata != old_mountdata) { ref_path = build_unc_path_to_root(vol, cifs_sb, false);
/* If we were redirected, reconnect to new target server */ if (IS_ERR(ref_path)) {
mount_put_conns(cifs_sb, xid, server, ses, tcon); rc = PTR_ERR(ref_path);
rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon); ref_path = NULL;
}
if (rc) {
if (rc == -EACCES || rc == -EOPNOTSUPP)
goto error;
/* Perform DFS failover to any other DFS targets */
rc = mount_do_dfs_failover(root_path + 1, cifs_sb, vol, NULL,
&xid, &server, &ses, &tcon);
if (rc)
goto error;
}
kfree(root_path);
root_path = build_unc_path_to_root(vol, cifs_sb, false);
if (IS_ERR(root_path)) {
rc = PTR_ERR(root_path);
root_path = NULL;
goto error; goto error;
} }
/* Cache out resolved root server */
(void)dfs_cache_find(xid, ses, cifs_sb->local_nls, cifs_remap(cifs_sb),
root_path + 1, NULL, NULL);
kfree(root_path);
root_path = NULL;
set_root_tcon(cifs_sb, tcon, &root_tcon);
for (count = 1; ;) {
if (!rc && tcon) {
rc = is_path_remote(cifs_sb, vol, xid, server, tcon);
if (!rc || rc != -EREMOTE)
break;
}
/*
* BB: when we implement proper loop detection,
* we will remove this check. But now we need it
* to prevent an indefinite loop if 'DFS tree' is
* misconfigured (i.e. has loops).
*/
if (count++ > MAX_NESTED_LINKS) {
rc = -ELOOP;
break;
}
set_root_ses(cifs_sb, ses, &root_ses);
do {
/* Save full path of last DFS path we used to resolve final target server */
kfree(full_path); kfree(full_path);
full_path = build_unc_path_to_root(vol, cifs_sb, true); full_path = build_unc_path_to_root(vol, cifs_sb, !!count);
if (IS_ERR(full_path)) { if (IS_ERR(full_path)) {
rc = PTR_ERR(full_path); rc = PTR_ERR(full_path);
full_path = NULL;
break; break;
} }
/* Chase referral */
old_mountdata = cifs_sb->mountdata; oldmnt = cifs_sb->mountdata;
rc = expand_dfs_referral(xid, root_tcon->ses, vol, cifs_sb, rc = expand_dfs_referral(xid, root_ses, vol, cifs_sb, ref_path + 1);
true);
if (rc) if (rc)
break; break;
/* Connect to new DFS target only if we were redirected */
if (cifs_sb->mountdata != old_mountdata) { if (oldmnt != cifs_sb->mountdata) {
mount_put_conns(cifs_sb, xid, server, ses, tcon); mount_put_conns(cifs_sb, xid, server, ses, tcon);
rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon);
&tcon);
/*
* Ensure that DFS referrals go through new root server.
*/
if (!rc && tcon &&
(tcon->share_flags & (SHI1005_FLAGS_DFS |
SHI1005_FLAGS_DFS_ROOT))) {
cifs_put_tcon(root_tcon);
set_root_tcon(cifs_sb, tcon, &root_tcon);
}
} }
if (rc) { if (rc && !server && !ses) {
if (rc == -EACCES || rc == -EOPNOTSUPP) /* Failed to connect. Try to connect to other targets in the referral. */
break; rc = do_dfs_failover(ref_path + 1, full_path, cifs_sb, vol, root_ses, &xid,
/* Perform DFS failover to any other DFS targets */
rc = mount_do_dfs_failover(full_path + 1, cifs_sb, vol,
root_tcon->ses, &xid,
&server, &ses, &tcon); &server, &ses, &tcon);
if (rc == -EACCES || rc == -EOPNOTSUPP || !server ||
!ses)
goto error;
}
} }
cifs_put_tcon(root_tcon); if (rc == -EACCES || rc == -EOPNOTSUPP || !server || !ses)
break;
if (!tcon)
continue;
/* Make sure that requests go through new root servers */
if (tcon->share_flags & (SHI1005_FLAGS_DFS | SHI1005_FLAGS_DFS_ROOT)) {
put_root_ses(root_ses);
set_root_ses(cifs_sb, ses, &root_ses);
}
/* Check for remaining path components and then continue chasing them (-EREMOTE) */
rc = check_dfs_prepath(cifs_sb, vol, xid, server, tcon, &ref_path);
/* Prevent recursion on broken link referrals */
if (rc == -EREMOTE && ++count > MAX_NESTED_LINKS)
rc = -ELOOP;
} while (rc == -EREMOTE);
if (rc) if (rc)
goto error; goto error;
put_root_ses(root_ses);
spin_lock(&cifs_tcp_ses_lock); root_ses = NULL;
if (!tcon->dfs_path) { kfree(ref_path);
/* Save full path in new tcon to do failover when reconnecting tcons */ ref_path = NULL;
tcon->dfs_path = full_path; /*
full_path = NULL; * Store DFS full path in both superblock and tree connect structures.
tcon->remap = cifs_remap(cifs_sb); *
} * For DFS root mounts, the prefix path (cifs_sb->prepath) is preserved during reconnect so
cifs_sb->origin_fullpath = kstrndup(tcon->dfs_path, * only the root path is set in cifs_sb->origin_fullpath and tcon->dfs_path. And for DFS
strlen(tcon->dfs_path), * links, the prefix path is included in both and may be changed during reconnect. See
GFP_ATOMIC); * cifs_tree_connect().
*/
cifs_sb->origin_fullpath = kstrndup(full_path, strlen(full_path), GFP_KERNEL);
if (!cifs_sb->origin_fullpath) { if (!cifs_sb->origin_fullpath) {
spin_unlock(&cifs_tcp_ses_lock);
rc = -ENOMEM; rc = -ENOMEM;
goto error; goto error;
} }
spin_lock(&cifs_tcp_ses_lock);
tcon->dfs_path = full_path;
full_path = NULL;
tcon->remap = cifs_remap(cifs_sb);
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
rc = dfs_cache_add_vol(origin_mountdata, vol, cifs_sb->origin_fullpath); /* Add original volume information for DFS cache to be used when refreshing referrals */
if (rc) { rc = dfs_cache_add_vol(mntdata, vol, cifs_sb->origin_fullpath);
kfree(cifs_sb->origin_fullpath); if (rc)
goto error; goto error;
}
/* /*
* After reconnecting to a different server, unique ids won't * After reconnecting to a different server, unique ids won't
* match anymore, so we disable serverino. This prevents * match anymore, so we disable serverino. This prevents
...@@ -4976,9 +4969,11 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol) ...@@ -4976,9 +4969,11 @@ int cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *vol)
return mount_setup_tlink(cifs_sb, ses, tcon); return mount_setup_tlink(cifs_sb, ses, tcon);
error: error:
kfree(ref_path);
kfree(full_path); kfree(full_path);
kfree(root_path); kfree(mntdata);
kfree(origin_mountdata); kfree(cifs_sb->origin_fullpath);
put_root_ses(root_ses);
mount_put_conns(cifs_sb, xid, server, ses, tcon); mount_put_conns(cifs_sb, xid, server, ses, tcon);
return rc; return rc;
} }
...@@ -5114,8 +5109,7 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses, ...@@ -5114,8 +5109,7 @@ CIFSTCon(const unsigned int xid, struct cifs_ses *ses,
bcc_ptr += strlen("?????"); bcc_ptr += strlen("?????");
bcc_ptr += 1; bcc_ptr += 1;
count = bcc_ptr - &pSMB->Password[0]; count = bcc_ptr - &pSMB->Password[0];
pSMB->hdr.smb_buf_length = cpu_to_be32(be32_to_cpu( be32_add_cpu(&pSMB->hdr.smb_buf_length, count);
pSMB->hdr.smb_buf_length) + count);
pSMB->ByteCount = cpu_to_le16(count); pSMB->ByteCount = cpu_to_le16(count);
rc = SendReceive(xid, ses, smb_buffer, smb_buffer_response, &length, rc = SendReceive(xid, ses, smb_buffer, smb_buffer_response, &length,
...@@ -5533,3 +5527,115 @@ cifs_prune_tlinks(struct work_struct *work) ...@@ -5533,3 +5527,115 @@ cifs_prune_tlinks(struct work_struct *work)
queue_delayed_work(cifsiod_wq, &cifs_sb->prune_tlinks, queue_delayed_work(cifsiod_wq, &cifs_sb->prune_tlinks,
TLINK_IDLE_EXPIRE); TLINK_IDLE_EXPIRE);
} }
#ifdef CONFIG_CIFS_DFS_UPCALL
int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc)
{
int rc;
struct TCP_Server_Info *server = tcon->ses->server;
const struct smb_version_operations *ops = server->ops;
struct dfs_cache_tgt_list tl;
struct dfs_cache_tgt_iterator *it = NULL;
char *tree;
const char *tcp_host;
size_t tcp_host_len;
const char *dfs_host;
size_t dfs_host_len;
char *share = NULL, *prefix = NULL;
struct dfs_info3_param ref = {0};
bool isroot;
tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL);
if (!tree)
return -ENOMEM;
if (!tcon->dfs_path) {
if (tcon->ipc) {
scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", server->hostname);
rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);
} else {
rc = ops->tree_connect(xid, tcon->ses, tcon->treeName, tcon, nlsc);
}
goto out;
}
rc = dfs_cache_noreq_find(tcon->dfs_path + 1, &ref, &tl);
if (rc)
goto out;
isroot = ref.server_type == DFS_TYPE_ROOT;
free_dfs_info_param(&ref);
extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len);
for (it = dfs_cache_get_tgt_iterator(&tl); it; it = dfs_cache_get_next_tgt(&tl, it)) {
bool target_match;
kfree(share);
kfree(prefix);
share = NULL;
prefix = NULL;
rc = dfs_cache_get_tgt_share(tcon->dfs_path + 1, it, &share, &prefix);
if (rc) {
cifs_dbg(VFS, "%s: failed to parse target share %d\n",
__func__, rc);
continue;
}
extract_unc_hostname(share, &dfs_host, &dfs_host_len);
if (dfs_host_len != tcp_host_len
|| strncasecmp(dfs_host, tcp_host, dfs_host_len) != 0) {
cifs_dbg(FYI, "%s: %.*s doesn't match %.*s\n", __func__, (int)dfs_host_len,
dfs_host, (int)tcp_host_len, tcp_host);
rc = match_target_ip(server, dfs_host, dfs_host_len, &target_match);
if (rc) {
cifs_dbg(VFS, "%s: failed to match target ip: %d\n", __func__, rc);
break;
}
if (!target_match) {
cifs_dbg(FYI, "%s: skipping target\n", __func__);
continue;
}
}
if (tcon->ipc) {
scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$", share);
rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);
} else {
scnprintf(tree, MAX_TREE_SIZE, "\\%s", share);
rc = ops->tree_connect(xid, tcon->ses, tree, tcon, nlsc);
/* Only handle prefix paths of DFS link targets */
if (!rc && !isroot) {
rc = update_super_prepath(tcon, prefix);
break;
}
}
if (rc == -EREMOTE)
break;
}
kfree(share);
kfree(prefix);
if (!rc) {
if (it)
rc = dfs_cache_noreq_update_tgthint(tcon->dfs_path + 1, it);
else
rc = -ENOENT;
}
dfs_cache_free_tgts(&tl);
out:
kfree(tree);
return rc;
}
#else
int cifs_tree_connect(const unsigned int xid, struct cifs_tcon *tcon, const struct nls_table *nlsc)
{
const struct smb_version_operations *ops = tcon->ses->server->ops;
return ops->tree_connect(xid, tcon->ses, tcon->treeName, tcon, nlsc);
}
#endif
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
struct cache_dfs_tgt { struct cache_dfs_tgt {
char *name; char *name;
int path_consumed;
struct list_head list; struct list_head list;
}; };
...@@ -350,7 +351,7 @@ static inline struct timespec64 get_expire_time(int ttl) ...@@ -350,7 +351,7 @@ static inline struct timespec64 get_expire_time(int ttl)
} }
/* Allocate a new DFS target */ /* Allocate a new DFS target */
static struct cache_dfs_tgt *alloc_target(const char *name) static struct cache_dfs_tgt *alloc_target(const char *name, int path_consumed)
{ {
struct cache_dfs_tgt *t; struct cache_dfs_tgt *t;
...@@ -362,6 +363,7 @@ static struct cache_dfs_tgt *alloc_target(const char *name) ...@@ -362,6 +363,7 @@ static struct cache_dfs_tgt *alloc_target(const char *name)
kfree(t); kfree(t);
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
} }
t->path_consumed = path_consumed;
INIT_LIST_HEAD(&t->list); INIT_LIST_HEAD(&t->list);
return t; return t;
} }
...@@ -384,7 +386,7 @@ static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs, ...@@ -384,7 +386,7 @@ static int copy_ref_data(const struct dfs_info3_param *refs, int numrefs,
for (i = 0; i < numrefs; i++) { for (i = 0; i < numrefs; i++) {
struct cache_dfs_tgt *t; struct cache_dfs_tgt *t;
t = alloc_target(refs[i].node_name); t = alloc_target(refs[i].node_name, refs[i].path_consumed);
if (IS_ERR(t)) { if (IS_ERR(t)) {
free_tgts(ce); free_tgts(ce);
return PTR_ERR(t); return PTR_ERR(t);
...@@ -490,16 +492,7 @@ static int add_cache_entry(const char *path, unsigned int hash, ...@@ -490,16 +492,7 @@ static int add_cache_entry(const char *path, unsigned int hash,
return 0; return 0;
} }
/* static struct cache_entry *__lookup_cache_entry(const char *path)
* Find a DFS cache entry in hash table and optionally check prefix path against
* @path.
* Use whole path components in the match.
* Must be called with htable_rw_lock held.
*
* Return ERR_PTR(-ENOENT) if the entry is not found.
*/
static struct cache_entry *lookup_cache_entry(const char *path,
unsigned int *hash)
{ {
struct cache_entry *ce; struct cache_entry *ce;
unsigned int h; unsigned int h;
...@@ -517,9 +510,75 @@ static struct cache_entry *lookup_cache_entry(const char *path, ...@@ -517,9 +510,75 @@ static struct cache_entry *lookup_cache_entry(const char *path,
if (!found) if (!found)
ce = ERR_PTR(-ENOENT); ce = ERR_PTR(-ENOENT);
return ce;
}
/*
* Find a DFS cache entry in hash table and optionally check prefix path against
* @path.
* Use whole path components in the match.
* Must be called with htable_rw_lock held.
*
* Return ERR_PTR(-ENOENT) if the entry is not found.
*/
static struct cache_entry *lookup_cache_entry(const char *path, unsigned int *hash)
{
struct cache_entry *ce = ERR_PTR(-ENOENT);
unsigned int h;
int cnt = 0;
char *npath;
char *s, *e;
char sep;
npath = kstrndup(path, strlen(path), GFP_KERNEL);
if (!npath)
return ERR_PTR(-ENOMEM);
s = npath;
sep = *npath;
while ((s = strchr(s, sep)) && ++cnt < 3)
s++;
if (cnt < 3) {
h = cache_entry_hash(path, strlen(path));
ce = __lookup_cache_entry(path);
goto out;
}
/*
* Handle paths that have more than two path components and are a complete prefix of the DFS
* referral request path (@path).
*
* See MS-DFSC 3.2.5.5 "Receiving a Root Referral Request or Link Referral Request".
*/
h = cache_entry_hash(npath, strlen(npath));
e = npath + strlen(npath) - 1;
while (e > s) {
char tmp;
/* skip separators */
while (e > s && *e == sep)
e--;
if (e == s)
goto out;
tmp = *(e+1);
*(e+1) = 0;
ce = __lookup_cache_entry(npath);
if (!IS_ERR(ce)) {
h = cache_entry_hash(npath, strlen(npath));
break;
}
*(e+1) = tmp;
/* backward until separator */
while (e > s && *e != sep)
e--;
}
out:
if (hash) if (hash)
*hash = h; *hash = h;
kfree(npath);
return ce; return ce;
} }
...@@ -773,6 +832,7 @@ static int get_targets(struct cache_entry *ce, struct dfs_cache_tgt_list *tl) ...@@ -773,6 +832,7 @@ static int get_targets(struct cache_entry *ce, struct dfs_cache_tgt_list *tl)
rc = -ENOMEM; rc = -ENOMEM;
goto err_free_it; goto err_free_it;
} }
it->it_path_consumed = t->path_consumed;
if (ce->tgthint == t) if (ce->tgthint == t)
list_add(&it->it_list, head); list_add(&it->it_list, head);
...@@ -1263,23 +1323,26 @@ void dfs_cache_del_vol(const char *fullpath) ...@@ -1263,23 +1323,26 @@ void dfs_cache_del_vol(const char *fullpath)
/** /**
* dfs_cache_get_tgt_share - parse a DFS target * dfs_cache_get_tgt_share - parse a DFS target
* *
* @path: DFS full path
* @it: DFS target iterator. * @it: DFS target iterator.
* @share: tree name. * @share: tree name.
* @share_len: length of tree name.
* @prefix: prefix path. * @prefix: prefix path.
* @prefix_len: length of prefix path.
* *
* Return zero if target was parsed correctly, otherwise non-zero. * Return zero if target was parsed correctly, otherwise non-zero.
*/ */
int dfs_cache_get_tgt_share(const struct dfs_cache_tgt_iterator *it, int dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it,
const char **share, size_t *share_len, char **share, char **prefix)
const char **prefix, size_t *prefix_len)
{ {
char *s, sep; char *s, sep, *p;
size_t len;
size_t plen1, plen2;
if (!it || !share || !share_len || !prefix || !prefix_len) if (!it || !path || !share || !prefix || strlen(path) < it->it_path_consumed)
return -EINVAL; return -EINVAL;
*share = NULL;
*prefix = NULL;
sep = it->it_name[0]; sep = it->it_name[0];
if (sep != '\\' && sep != '/') if (sep != '\\' && sep != '/')
return -EINVAL; return -EINVAL;
...@@ -1288,13 +1351,38 @@ int dfs_cache_get_tgt_share(const struct dfs_cache_tgt_iterator *it, ...@@ -1288,13 +1351,38 @@ int dfs_cache_get_tgt_share(const struct dfs_cache_tgt_iterator *it,
if (!s) if (!s)
return -EINVAL; return -EINVAL;
/* point to prefix in target node */
s = strchrnul(s + 1, sep); s = strchrnul(s + 1, sep);
*share = it->it_name; /* extract target share */
*share_len = s - it->it_name; *share = kstrndup(it->it_name, s - it->it_name, GFP_KERNEL);
*prefix = *s ? s + 1 : s; if (!*share)
*prefix_len = &it->it_name[strlen(it->it_name)] - *prefix; return -ENOMEM;
/* skip separator */
if (*s)
s++;
/* point to prefix in DFS path */
p = path + it->it_path_consumed;
if (*p == sep)
p++;
/* merge prefix paths from DFS path and target node */
plen1 = it->it_name + strlen(it->it_name) - s;
plen2 = path + strlen(path) - p;
if (plen1 || plen2) {
len = plen1 + plen2 + 2;
*prefix = kmalloc(len, GFP_KERNEL);
if (!*prefix) {
kfree(*share);
*share = NULL;
return -ENOMEM;
}
if (plen1)
scnprintf(*prefix, len, "%.*s%c%.*s", (int)plen1, s, sep, (int)plen2, p);
else
strscpy(*prefix, p, len);
}
return 0; return 0;
} }
......
...@@ -19,6 +19,7 @@ struct dfs_cache_tgt_list { ...@@ -19,6 +19,7 @@ struct dfs_cache_tgt_list {
struct dfs_cache_tgt_iterator { struct dfs_cache_tgt_iterator {
char *it_name; char *it_name;
int it_path_consumed;
struct list_head it_list; struct list_head it_list;
}; };
...@@ -48,10 +49,8 @@ extern int dfs_cache_add_vol(char *mntdata, struct smb_vol *vol, ...@@ -48,10 +49,8 @@ extern int dfs_cache_add_vol(char *mntdata, struct smb_vol *vol,
extern int dfs_cache_update_vol(const char *fullpath, extern int dfs_cache_update_vol(const char *fullpath,
struct TCP_Server_Info *server); struct TCP_Server_Info *server);
extern void dfs_cache_del_vol(const char *fullpath); extern void dfs_cache_del_vol(const char *fullpath);
extern int dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it,
extern int dfs_cache_get_tgt_share(const struct dfs_cache_tgt_iterator *it, char **share, char **prefix);
const char **share, size_t *share_len,
const char **prefix, size_t *prefix_len);
static inline struct dfs_cache_tgt_iterator * static inline struct dfs_cache_tgt_iterator *
dfs_cache_get_next_tgt(struct dfs_cache_tgt_list *tl, dfs_cache_get_next_tgt(struct dfs_cache_tgt_list *tl,
......
...@@ -1086,7 +1086,6 @@ smb311_posix_get_inode_info(struct inode **inode, ...@@ -1086,7 +1086,6 @@ smb311_posix_get_inode_info(struct inode **inode,
struct super_block *sb, unsigned int xid) struct super_block *sb, unsigned int xid)
{ {
struct cifs_tcon *tcon; struct cifs_tcon *tcon;
struct TCP_Server_Info *server;
struct tcon_link *tlink; struct tcon_link *tlink;
struct cifs_sb_info *cifs_sb = CIFS_SB(sb); struct cifs_sb_info *cifs_sb = CIFS_SB(sb);
bool adjust_tz = false; bool adjust_tz = false;
...@@ -1100,7 +1099,6 @@ smb311_posix_get_inode_info(struct inode **inode, ...@@ -1100,7 +1099,6 @@ smb311_posix_get_inode_info(struct inode **inode,
if (IS_ERR(tlink)) if (IS_ERR(tlink))
return PTR_ERR(tlink); return PTR_ERR(tlink);
tcon = tlink_tcon(tlink); tcon = tlink_tcon(tlink);
server = tcon->ses->server;
/* /*
* 1. Fetch file metadata * 1. Fetch file metadata
......
...@@ -1164,8 +1164,7 @@ static inline void cifs_put_tcon_super(struct super_block *sb) ...@@ -1164,8 +1164,7 @@ static inline void cifs_put_tcon_super(struct super_block *sb)
} }
#endif #endif
int update_super_prepath(struct cifs_tcon *tcon, const char *prefix, int update_super_prepath(struct cifs_tcon *tcon, char *prefix)
size_t prefix_len)
{ {
struct super_block *sb; struct super_block *sb;
struct cifs_sb_info *cifs_sb; struct cifs_sb_info *cifs_sb;
...@@ -1179,8 +1178,8 @@ int update_super_prepath(struct cifs_tcon *tcon, const char *prefix, ...@@ -1179,8 +1178,8 @@ int update_super_prepath(struct cifs_tcon *tcon, const char *prefix,
kfree(cifs_sb->prepath); kfree(cifs_sb->prepath);
if (*prefix && prefix_len) { if (prefix && *prefix) {
cifs_sb->prepath = kstrndup(prefix, prefix_len, GFP_ATOMIC); cifs_sb->prepath = kstrndup(prefix, strlen(prefix), GFP_ATOMIC);
if (!cifs_sb->prepath) { if (!cifs_sb->prepath) {
rc = -ENOMEM; rc = -ENOMEM;
goto out; goto out;
......
...@@ -881,6 +881,33 @@ map_smb_to_linux_error(char *buf, bool logErr) ...@@ -881,6 +881,33 @@ map_smb_to_linux_error(char *buf, bool logErr)
return rc; return rc;
} }
int
map_and_check_smb_error(struct mid_q_entry *mid, bool logErr)
{
int rc;
struct smb_hdr *smb = (struct smb_hdr *)mid->resp_buf;
rc = map_smb_to_linux_error((char *)smb, logErr);
if (rc == -EACCES && !(smb->Flags2 & SMBFLG2_ERR_STATUS)) {
/* possible ERRBaduid */
__u8 class = smb->Status.DosError.ErrorClass;
__u16 code = le16_to_cpu(smb->Status.DosError.Error);
/* switch can be used to handle different errors */
if (class == ERRSRV && code == ERRbaduid) {
cifs_dbg(FYI, "Server returned 0x%x, reconnecting session...\n",
code);
spin_lock(&GlobalMid_Lock);
if (mid->server->tcpStatus != CifsExiting)
mid->server->tcpStatus = CifsNeedReconnect;
spin_unlock(&GlobalMid_Lock);
}
}
return rc;
}
/* /*
* calculate the size of the SMB message based on the fixed header * calculate the size of the SMB message based on the fixed header
* portion, the number of word parameters and the data portion of the message * portion, the number of word parameters and the data portion of the message
......
...@@ -938,8 +938,7 @@ sess_sendreceive(struct sess_data *sess_data) ...@@ -938,8 +938,7 @@ sess_sendreceive(struct sess_data *sess_data)
struct kvec rsp_iov = { NULL, 0 }; struct kvec rsp_iov = { NULL, 0 };
count = sess_data->iov[1].iov_len + sess_data->iov[2].iov_len; count = sess_data->iov[1].iov_len + sess_data->iov[2].iov_len;
smb_buf->smb_buf_length = be32_add_cpu(&smb_buf->smb_buf_length, count);
cpu_to_be32(be32_to_cpu(smb_buf->smb_buf_length) + count);
put_bcc(count, smb_buf); put_bcc(count, smb_buf);
rc = SendReceive2(sess_data->xid, sess_data->ses, rc = SendReceive2(sess_data->xid, sess_data->ses,
...@@ -1705,7 +1704,6 @@ static int select_sec(struct cifs_ses *ses, struct sess_data *sess_data) ...@@ -1705,7 +1704,6 @@ static int select_sec(struct cifs_ses *ses, struct sess_data *sess_data)
#else #else
cifs_dbg(VFS, "Kerberos negotiated but upcall support disabled!\n"); cifs_dbg(VFS, "Kerberos negotiated but upcall support disabled!\n");
return -ENOSYS; return -ENOSYS;
break;
#endif /* CONFIG_CIFS_UPCALL */ #endif /* CONFIG_CIFS_UPCALL */
case RawNTLMSSP: case RawNTLMSSP:
sess_data->func = sess_auth_rawntlmssp_negotiate; sess_data->func = sess_auth_rawntlmssp_negotiate;
......
...@@ -688,7 +688,7 @@ cifs_mkdir_setinfo(struct inode *inode, const char *full_path, ...@@ -688,7 +688,7 @@ cifs_mkdir_setinfo(struct inode *inode, const char *full_path,
dosattrs = cifsInode->cifsAttrs|ATTR_READONLY; dosattrs = cifsInode->cifsAttrs|ATTR_READONLY;
info.Attributes = cpu_to_le32(dosattrs); info.Attributes = cpu_to_le32(dosattrs);
rc = CIFSSMBSetPathInfo(xid, tcon, full_path, &info, cifs_sb->local_nls, rc = CIFSSMBSetPathInfo(xid, tcon, full_path, &info, cifs_sb->local_nls,
cifs_remap(cifs_sb)); cifs_sb);
if (rc == 0) if (rc == 0)
cifsInode->cifsAttrs = dosattrs; cifsInode->cifsAttrs = dosattrs;
} }
...@@ -783,7 +783,7 @@ smb_set_file_info(struct inode *inode, const char *full_path, ...@@ -783,7 +783,7 @@ smb_set_file_info(struct inode *inode, const char *full_path,
tcon = tlink_tcon(tlink); tcon = tlink_tcon(tlink);
rc = CIFSSMBSetPathInfo(xid, tcon, full_path, buf, cifs_sb->local_nls, rc = CIFSSMBSetPathInfo(xid, tcon, full_path, buf, cifs_sb->local_nls,
cifs_remap(cifs_sb)); cifs_sb);
if (rc == 0) { if (rc == 0) {
cinode->cifsAttrs = le32_to_cpu(buf->Attributes); cinode->cifsAttrs = le32_to_cpu(buf->Attributes);
goto out; goto out;
......
...@@ -508,15 +508,31 @@ cifs_ses_oplock_break(struct work_struct *work) ...@@ -508,15 +508,31 @@ cifs_ses_oplock_break(struct work_struct *work)
kfree(lw); kfree(lw);
} }
static void
smb2_queue_pending_open_break(struct tcon_link *tlink, __u8 *lease_key,
__le32 new_lease_state)
{
struct smb2_lease_break_work *lw;
lw = kmalloc(sizeof(struct smb2_lease_break_work), GFP_KERNEL);
if (!lw) {
cifs_put_tlink(tlink);
return;
}
INIT_WORK(&lw->lease_break, cifs_ses_oplock_break);
lw->tlink = tlink;
lw->lease_state = new_lease_state;
memcpy(lw->lease_key, lease_key, SMB2_LEASE_KEY_SIZE);
queue_work(cifsiod_wq, &lw->lease_break);
}
static bool static bool
smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp, smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp)
struct smb2_lease_break_work *lw)
{ {
bool found;
__u8 lease_state; __u8 lease_state;
struct list_head *tmp; struct list_head *tmp;
struct cifsFileInfo *cfile; struct cifsFileInfo *cfile;
struct cifs_pending_open *open;
struct cifsInodeInfo *cinode; struct cifsInodeInfo *cinode;
int ack_req = le32_to_cpu(rsp->Flags & int ack_req = le32_to_cpu(rsp->Flags &
SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED); SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED);
...@@ -546,22 +562,29 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp, ...@@ -546,22 +562,29 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
cfile->oplock_level = lease_state; cfile->oplock_level = lease_state;
cifs_queue_oplock_break(cfile); cifs_queue_oplock_break(cfile);
kfree(lw);
return true; return true;
} }
found = false; return false;
}
static struct cifs_pending_open *
smb2_tcon_find_pending_open_lease(struct cifs_tcon *tcon,
struct smb2_lease_break *rsp)
{
__u8 lease_state = le32_to_cpu(rsp->NewLeaseState);
int ack_req = le32_to_cpu(rsp->Flags &
SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED);
struct cifs_pending_open *open;
struct cifs_pending_open *found = NULL;
list_for_each_entry(open, &tcon->pending_opens, olist) { list_for_each_entry(open, &tcon->pending_opens, olist) {
if (memcmp(open->lease_key, rsp->LeaseKey, if (memcmp(open->lease_key, rsp->LeaseKey,
SMB2_LEASE_KEY_SIZE)) SMB2_LEASE_KEY_SIZE))
continue; continue;
if (!found && ack_req) { if (!found && ack_req) {
found = true; found = open;
memcpy(lw->lease_key, open->lease_key,
SMB2_LEASE_KEY_SIZE);
lw->tlink = cifs_get_tlink(open->tlink);
queue_work(cifsiod_wq, &lw->lease_break);
} }
cifs_dbg(FYI, "found in the pending open list\n"); cifs_dbg(FYI, "found in the pending open list\n");
...@@ -582,14 +605,7 @@ smb2_is_valid_lease_break(char *buffer) ...@@ -582,14 +605,7 @@ smb2_is_valid_lease_break(char *buffer)
struct TCP_Server_Info *server; struct TCP_Server_Info *server;
struct cifs_ses *ses; struct cifs_ses *ses;
struct cifs_tcon *tcon; struct cifs_tcon *tcon;
struct smb2_lease_break_work *lw; struct cifs_pending_open *open;
lw = kmalloc(sizeof(struct smb2_lease_break_work), GFP_KERNEL);
if (!lw)
return false;
INIT_WORK(&lw->lease_break, cifs_ses_oplock_break);
lw->lease_state = rsp->NewLeaseState;
cifs_dbg(FYI, "Checking for lease break\n"); cifs_dbg(FYI, "Checking for lease break\n");
...@@ -607,11 +623,27 @@ smb2_is_valid_lease_break(char *buffer) ...@@ -607,11 +623,27 @@ smb2_is_valid_lease_break(char *buffer)
spin_lock(&tcon->open_file_lock); spin_lock(&tcon->open_file_lock);
cifs_stats_inc( cifs_stats_inc(
&tcon->stats.cifs_stats.num_oplock_brks); &tcon->stats.cifs_stats.num_oplock_brks);
if (smb2_tcon_has_lease(tcon, rsp, lw)) { if (smb2_tcon_has_lease(tcon, rsp)) {
spin_unlock(&tcon->open_file_lock); spin_unlock(&tcon->open_file_lock);
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
return true; return true;
} }
open = smb2_tcon_find_pending_open_lease(tcon,
rsp);
if (open) {
__u8 lease_key[SMB2_LEASE_KEY_SIZE];
struct tcon_link *tlink;
tlink = cifs_get_tlink(open->tlink);
memcpy(lease_key, open->lease_key,
SMB2_LEASE_KEY_SIZE);
spin_unlock(&tcon->open_file_lock);
spin_unlock(&cifs_tcp_ses_lock);
smb2_queue_pending_open_break(tlink,
lease_key,
rsp->NewLeaseState);
return true;
}
spin_unlock(&tcon->open_file_lock); spin_unlock(&tcon->open_file_lock);
if (tcon->crfid.is_valid && if (tcon->crfid.is_valid &&
...@@ -629,7 +661,6 @@ smb2_is_valid_lease_break(char *buffer) ...@@ -629,7 +661,6 @@ smb2_is_valid_lease_break(char *buffer)
} }
} }
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
kfree(lw);
cifs_dbg(FYI, "Can not process lease break - no lease matched\n"); cifs_dbg(FYI, "Can not process lease break - no lease matched\n");
return false; return false;
} }
......
...@@ -152,117 +152,6 @@ smb2_hdr_assemble(struct smb2_sync_hdr *shdr, __le16 smb2_cmd, ...@@ -152,117 +152,6 @@ smb2_hdr_assemble(struct smb2_sync_hdr *shdr, __le16 smb2_cmd,
return; return;
} }
#ifdef CONFIG_CIFS_DFS_UPCALL
static int __smb2_reconnect(const struct nls_table *nlsc,
struct cifs_tcon *tcon)
{
int rc;
struct TCP_Server_Info *server = tcon->ses->server;
struct dfs_cache_tgt_list tl;
struct dfs_cache_tgt_iterator *it = NULL;
char *tree;
const char *tcp_host;
size_t tcp_host_len;
const char *dfs_host;
size_t dfs_host_len;
tree = kzalloc(MAX_TREE_SIZE, GFP_KERNEL);
if (!tree)
return -ENOMEM;
if (!tcon->dfs_path) {
if (tcon->ipc) {
scnprintf(tree, MAX_TREE_SIZE, "\\\\%s\\IPC$",
server->hostname);
rc = SMB2_tcon(0, tcon->ses, tree, tcon, nlsc);
} else {
rc = SMB2_tcon(0, tcon->ses, tcon->treeName, tcon,
nlsc);
}
goto out;
}
rc = dfs_cache_noreq_find(tcon->dfs_path + 1, NULL, &tl);
if (rc)
goto out;
extract_unc_hostname(server->hostname, &tcp_host, &tcp_host_len);
for (it = dfs_cache_get_tgt_iterator(&tl); it;
it = dfs_cache_get_next_tgt(&tl, it)) {
const char *share, *prefix;
size_t share_len, prefix_len;
bool target_match;
rc = dfs_cache_get_tgt_share(it, &share, &share_len, &prefix,
&prefix_len);
if (rc) {
cifs_dbg(VFS, "%s: failed to parse target share %d\n",
__func__, rc);
continue;
}
extract_unc_hostname(share, &dfs_host, &dfs_host_len);
if (dfs_host_len != tcp_host_len
|| strncasecmp(dfs_host, tcp_host, dfs_host_len) != 0) {
cifs_dbg(FYI, "%s: %.*s doesn't match %.*s\n",
__func__,
(int)dfs_host_len, dfs_host,
(int)tcp_host_len, tcp_host);
rc = match_target_ip(server, dfs_host, dfs_host_len,
&target_match);
if (rc) {
cifs_dbg(VFS, "%s: failed to match target ip: %d\n",
__func__, rc);
break;
}
if (!target_match) {
cifs_dbg(FYI, "%s: skipping target\n", __func__);
continue;
}
}
if (tcon->ipc) {
scnprintf(tree, MAX_TREE_SIZE, "\\\\%.*s\\IPC$",
(int)share_len, share);
rc = SMB2_tcon(0, tcon->ses, tree, tcon, nlsc);
} else {
scnprintf(tree, MAX_TREE_SIZE, "\\%.*s", (int)share_len,
share);
rc = SMB2_tcon(0, tcon->ses, tree, tcon, nlsc);
if (!rc) {
rc = update_super_prepath(tcon, prefix,
prefix_len);
break;
}
}
if (rc == -EREMOTE)
break;
}
if (!rc) {
if (it)
rc = dfs_cache_noreq_update_tgthint(tcon->dfs_path + 1,
it);
else
rc = -ENOENT;
}
dfs_cache_free_tgts(&tl);
out:
kfree(tree);
return rc;
}
#else
static inline int __smb2_reconnect(const struct nls_table *nlsc,
struct cifs_tcon *tcon)
{
return SMB2_tcon(0, tcon->ses, tcon->treeName, tcon, nlsc);
}
#endif
static int static int
smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon, smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
struct TCP_Server_Info *server) struct TCP_Server_Info *server)
...@@ -409,7 +298,7 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon, ...@@ -409,7 +298,7 @@ smb2_reconnect(__le16 smb2_command, struct cifs_tcon *tcon,
if (tcon->use_persistent) if (tcon->use_persistent)
tcon->need_reopen_files = true; tcon->need_reopen_files = true;
rc = __smb2_reconnect(nls_codepage, tcon); rc = cifs_tree_connect(0, tcon, nls_codepage);
mutex_unlock(&tcon->ses->session_mutex); mutex_unlock(&tcon->ses->session_mutex);
cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc); cifs_dbg(FYI, "reconnect tcon rc = %d\n", rc);
...@@ -1387,6 +1276,8 @@ SMB2_auth_kerberos(struct SMB2_sess_data *sess_data) ...@@ -1387,6 +1276,8 @@ SMB2_auth_kerberos(struct SMB2_sess_data *sess_data)
spnego_key = cifs_get_spnego_key(ses); spnego_key = cifs_get_spnego_key(ses);
if (IS_ERR(spnego_key)) { if (IS_ERR(spnego_key)) {
rc = PTR_ERR(spnego_key); rc = PTR_ERR(spnego_key);
if (rc == -ENOKEY)
cifs_dbg(VFS, "Verify user has a krb5 ticket and keyutils is installed\n");
spnego_key = NULL; spnego_key = NULL;
goto out; goto out;
} }
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
* Note that, due to trying to use names similar to the protocol specifications, * Note that, due to trying to use names similar to the protocol specifications,
* there are many mixed case field names in the structures below. Although * there are many mixed case field names in the structures below. Although
* this does not match typical Linux kernel style, it is necessary to be * this does not match typical Linux kernel style, it is necessary to be
* be able to match against the protocol specfication. * able to match against the protocol specfication.
* *
* SMB2 commands * SMB2 commands
* Some commands have minimal (wct=0,bcc=0), or uninteresting, responses * Some commands have minimal (wct=0,bcc=0), or uninteresting, responses
......
...@@ -936,7 +936,7 @@ cifs_check_receive(struct mid_q_entry *mid, struct TCP_Server_Info *server, ...@@ -936,7 +936,7 @@ cifs_check_receive(struct mid_q_entry *mid, struct TCP_Server_Info *server,
} }
/* BB special case reconnect tid and uid here? */ /* BB special case reconnect tid and uid here? */
return map_smb_to_linux_error(mid->resp_buf, log_error); return map_and_check_smb_error(mid, log_error);
} }
struct mid_q_entry * struct mid_q_entry *
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment