diff options
Diffstat (limited to 'fs/cifs/connect.c')
-rw-r--r-- | fs/cifs/connect.c | 569 |
1 files changed, 506 insertions, 63 deletions
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 88c84a38bccb..9eb327defa1d 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -47,7 +47,6 @@ #include "ntlmssp.h" #include "nterr.h" #include "rfc1002pdu.h" -#include "cn_cifs.h" #include "fscache.h" #define CIFS_PORT 445 @@ -100,16 +99,24 @@ struct smb_vol { bool noautotune:1; bool nostrictsync:1; /* do not force expensive SMBflush on every sync */ bool fsc:1; /* enable fscache */ + bool mfsymlinks:1; /* use Minshall+French Symlinks */ + bool multiuser:1; unsigned int rsize; unsigned int wsize; bool sockopt_tcp_nodelay:1; unsigned short int port; char *prepath; + struct sockaddr_storage srcaddr; /* allow binding to a local IP */ struct nls_table *local_nls; }; +/* FIXME: should these be tunable? */ +#define TLINK_ERROR_EXPIRE (1 * HZ) +#define TLINK_IDLE_EXPIRE (600 * HZ) + static int ipv4_connect(struct TCP_Server_Info *server); static int ipv6_connect(struct TCP_Server_Info *server); +static void cifs_prune_tlinks(struct work_struct *work); /* * cifs tcp session reconnection @@ -143,7 +150,7 @@ cifs_reconnect(struct TCP_Server_Info *server) /* before reconnecting the tcp session, mark the smb session (uid) and the tid bad so they are not used until reconnected */ - read_lock(&cifs_tcp_ses_lock); + spin_lock(&cifs_tcp_ses_lock); list_for_each(tmp, &server->smb_ses_list) { ses = list_entry(tmp, struct cifsSesInfo, smb_ses_list); ses->need_reconnect = true; @@ -153,7 +160,7 @@ cifs_reconnect(struct TCP_Server_Info *server) tcon->need_reconnect = true; } } - read_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); /* do not want to be sending data on a socket we are freeing */ mutex_lock(&server->srv_mutex); if (server->ssocket) { @@ -166,6 +173,11 @@ cifs_reconnect(struct TCP_Server_Info *server) sock_release(server->ssocket); server->ssocket = NULL; } + server->sequence_number = 0; + server->session_estab = false; + kfree(server->session_key.response); + server->session_key.response = NULL; + server->session_key.len = 0; spin_lock(&GlobalMid_Lock); list_for_each(tmp, &server->pending_mid_q) { @@ -198,7 +210,6 @@ cifs_reconnect(struct TCP_Server_Info *server) spin_lock(&GlobalMid_Lock); if (server->tcpStatus != CifsExiting) server->tcpStatus = CifsGood; - server->sequence_number = 0; spin_unlock(&GlobalMid_Lock); /* atomic_set(&server->inFlight,0);*/ wake_up(&server->response_q); @@ -629,9 +640,9 @@ multi_t2_fnd: } /* end while !EXITING */ /* take it off the list, if it's not already */ - write_lock(&cifs_tcp_ses_lock); + spin_lock(&cifs_tcp_ses_lock); list_del_init(&server->tcp_ses_list); - write_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); spin_lock(&GlobalMid_Lock); server->tcpStatus = CifsExiting; @@ -669,7 +680,7 @@ multi_t2_fnd: * BB: we shouldn't have to do any of this. It shouldn't be * possible to exit from the thread with active SMB sessions */ - read_lock(&cifs_tcp_ses_lock); + spin_lock(&cifs_tcp_ses_lock); if (list_empty(&server->pending_mid_q)) { /* loop through server session structures attached to this and mark them dead */ @@ -679,7 +690,7 @@ multi_t2_fnd: ses->status = CifsExiting; ses->server = NULL; } - read_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); } else { /* although we can not zero the server struct pointer yet, since there are active requests which may depnd on them, @@ -702,7 +713,7 @@ multi_t2_fnd: } } spin_unlock(&GlobalMid_Lock); - read_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); /* 1/8th of sec is more than enough time for them to exit */ msleep(125); } @@ -725,12 +736,12 @@ multi_t2_fnd: if a crazy root user tried to kill cifsd kernel thread explicitly this might happen) */ /* BB: This shouldn't be necessary, see above */ - read_lock(&cifs_tcp_ses_lock); + spin_lock(&cifs_tcp_ses_lock); list_for_each(tmp, &server->smb_ses_list) { ses = list_entry(tmp, struct cifsSesInfo, smb_ses_list); ses->server = NULL; } - read_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); kfree(server->hostname); task_to_wake = xchg(&server->tsk, NULL); @@ -1046,6 +1057,22 @@ cifs_parse_mount_options(char *options, const char *devname, "long\n"); return 1; } + } else if (strnicmp(data, "srcaddr", 7) == 0) { + vol->srcaddr.ss_family = AF_UNSPEC; + + if (!value || !*value) { + printk(KERN_WARNING "CIFS: srcaddr value" + " not specified.\n"); + return 1; /* needs_arg; */ + } + i = cifs_convert_address((struct sockaddr *)&vol->srcaddr, + value, strlen(value)); + if (i == 0) { + printk(KERN_WARNING "CIFS: Could not parse" + " srcaddr: %s\n", + value); + return 1; + } } else if (strnicmp(data, "prefixpath", 10) == 0) { if (!value || !*value) { printk(KERN_WARNING @@ -1325,6 +1352,10 @@ cifs_parse_mount_options(char *options, const char *devname, "/proc/fs/cifs/LookupCacheEnabled to 0\n"); } else if (strnicmp(data, "fsc", 3) == 0) { vol->fsc = true; + } else if (strnicmp(data, "mfsymlinks", 10) == 0) { + vol->mfsymlinks = true; + } else if (strnicmp(data, "multiuser", 8) == 0) { + vol->multiuser = true; } else printk(KERN_WARNING "CIFS: Unknown mount option %s\n", data); @@ -1356,6 +1387,13 @@ cifs_parse_mount_options(char *options, const char *devname, return 1; } } + + if (vol->multiuser && !(vol->secFlg & CIFSSEC_MAY_KRB5)) { + cERROR(1, "Multiuser mounts currently require krb5 " + "authentication!"); + return 1; + } + if (vol->UNCip == NULL) vol->UNCip = &vol->UNC[2]; @@ -1374,8 +1412,36 @@ cifs_parse_mount_options(char *options, const char *devname, return 0; } +/** Returns true if srcaddr isn't specified and rhs isn't + * specified, or if srcaddr is specified and + * matches the IP address of the rhs argument. + */ static bool -match_address(struct TCP_Server_Info *server, struct sockaddr *addr) +srcip_matches(struct sockaddr *srcaddr, struct sockaddr *rhs) +{ + switch (srcaddr->sa_family) { + case AF_UNSPEC: + return (rhs->sa_family == AF_UNSPEC); + case AF_INET: { + struct sockaddr_in *saddr4 = (struct sockaddr_in *)srcaddr; + struct sockaddr_in *vaddr4 = (struct sockaddr_in *)rhs; + return (saddr4->sin_addr.s_addr == vaddr4->sin_addr.s_addr); + } + case AF_INET6: { + struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)srcaddr; + struct sockaddr_in6 *vaddr6 = (struct sockaddr_in6 *)&rhs; + return ipv6_addr_equal(&saddr6->sin6_addr, &vaddr6->sin6_addr); + } + default: + WARN_ON(1); + return false; /* don't expect to be here */ + } +} + + +static bool +match_address(struct TCP_Server_Info *server, struct sockaddr *addr, + struct sockaddr *srcaddr) { struct sockaddr_in *addr4 = (struct sockaddr_in *)addr; struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr; @@ -1402,6 +1468,9 @@ match_address(struct TCP_Server_Info *server, struct sockaddr *addr) break; } + if (!srcip_matches(srcaddr, (struct sockaddr *)&server->srcaddr)) + return false; + return true; } @@ -1458,29 +1527,21 @@ cifs_find_tcp_session(struct sockaddr *addr, struct smb_vol *vol) { struct TCP_Server_Info *server; - write_lock(&cifs_tcp_ses_lock); + spin_lock(&cifs_tcp_ses_lock); list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) { - /* - * the demux thread can exit on its own while still in CifsNew - * so don't accept any sockets in that state. Since the - * tcpStatus never changes back to CifsNew it's safe to check - * for this without a lock. - */ - if (server->tcpStatus == CifsNew) - continue; - - if (!match_address(server, addr)) + if (!match_address(server, addr, + (struct sockaddr *)&vol->srcaddr)) continue; if (!match_security(server, vol)) continue; ++server->srv_count; - write_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); cFYI(1, "Existing tcp session with server found"); return server; } - write_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); return NULL; } @@ -1489,21 +1550,26 @@ cifs_put_tcp_session(struct TCP_Server_Info *server) { struct task_struct *task; - write_lock(&cifs_tcp_ses_lock); + spin_lock(&cifs_tcp_ses_lock); if (--server->srv_count > 0) { - write_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); return; } list_del_init(&server->tcp_ses_list); - write_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); spin_lock(&GlobalMid_Lock); server->tcpStatus = CifsExiting; spin_unlock(&GlobalMid_Lock); + cifs_crypto_shash_release(server); cifs_fscache_release_client_cookie(server); + kfree(server->session_key.response); + server->session_key.response = NULL; + server->session_key.len = 0; + task = xchg(&server->tsk, NULL); if (task) force_sig(SIGKILL, task); @@ -1556,10 +1622,16 @@ cifs_get_tcp_session(struct smb_vol *volume_info) goto out_err; } + rc = cifs_crypto_shash_allocate(tcp_ses); + if (rc) { + cERROR(1, "could not setup hash structures rc %d", rc); + goto out_err; + } + tcp_ses->hostname = extract_hostname(volume_info->UNC); if (IS_ERR(tcp_ses->hostname)) { rc = PTR_ERR(tcp_ses->hostname); - goto out_err; + goto out_err_crypto_release; } tcp_ses->noblocksnd = volume_info->noblocksnd; @@ -1574,6 +1646,7 @@ cifs_get_tcp_session(struct smb_vol *volume_info) volume_info->source_rfc1001_name, RFC1001_NAME_LEN_WITH_NULL); memcpy(tcp_ses->server_RFC1001_name, volume_info->target_rfc1001_name, RFC1001_NAME_LEN_WITH_NULL); + tcp_ses->session_estab = false; tcp_ses->sequence_number = 0; INIT_LIST_HEAD(&tcp_ses->tcp_ses_list); INIT_LIST_HEAD(&tcp_ses->smb_ses_list); @@ -1584,6 +1657,8 @@ cifs_get_tcp_session(struct smb_vol *volume_info) * no need to spinlock this init of tcpStatus or srv_count */ tcp_ses->tcpStatus = CifsNew; + memcpy(&tcp_ses->srcaddr, &volume_info->srcaddr, + sizeof(tcp_ses->srcaddr)); ++tcp_ses->srv_count; if (addr.ss_family == AF_INET6) { @@ -1600,7 +1675,7 @@ cifs_get_tcp_session(struct smb_vol *volume_info) } if (rc < 0) { cERROR(1, "Error connecting to socket. Aborting operation"); - goto out_err; + goto out_err_crypto_release; } /* @@ -1614,18 +1689,21 @@ cifs_get_tcp_session(struct smb_vol *volume_info) rc = PTR_ERR(tcp_ses->tsk); cERROR(1, "error %d create cifsd thread", rc); module_put(THIS_MODULE); - goto out_err; + goto out_err_crypto_release; } /* thread spawned, put it on the list */ - write_lock(&cifs_tcp_ses_lock); + spin_lock(&cifs_tcp_ses_lock); list_add(&tcp_ses->tcp_ses_list, &cifs_tcp_ses_list); - write_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); cifs_fscache_get_client_cookie(tcp_ses); return tcp_ses; +out_err_crypto_release: + cifs_crypto_shash_release(tcp_ses); + out_err: if (tcp_ses) { if (!IS_ERR(tcp_ses->hostname)) @@ -1642,7 +1720,7 @@ cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb_vol *vol) { struct cifsSesInfo *ses; - write_lock(&cifs_tcp_ses_lock); + spin_lock(&cifs_tcp_ses_lock); list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { switch (server->secType) { case Kerberos: @@ -1662,10 +1740,10 @@ cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb_vol *vol) continue; } ++ses->ses_count; - write_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); return ses; } - write_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); return NULL; } @@ -1676,14 +1754,14 @@ cifs_put_smb_ses(struct cifsSesInfo *ses) struct TCP_Server_Info *server = ses->server; cFYI(1, "%s: ses_count=%d\n", __func__, ses->ses_count); - write_lock(&cifs_tcp_ses_lock); + spin_lock(&cifs_tcp_ses_lock); if (--ses->ses_count > 0) { - write_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); return; } list_del_init(&ses->smb_ses_list); - write_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); if (ses->status == CifsGood) { xid = GetXid(); @@ -1760,10 +1838,9 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info) goto get_ses_fail; } if (volume_info->domainname) { - int len = strlen(volume_info->domainname); - ses->domainName = kmalloc(len + 1, GFP_KERNEL); - if (ses->domainName) - strcpy(ses->domainName, volume_info->domainname); + ses->domainName = kstrdup(volume_info->domainname, GFP_KERNEL); + if (!ses->domainName) + goto get_ses_fail; } ses->cred_uid = volume_info->cred_uid; ses->linux_uid = volume_info->linux_uid; @@ -1778,9 +1855,9 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info) goto get_ses_fail; /* success, put it on the list */ - write_lock(&cifs_tcp_ses_lock); + spin_lock(&cifs_tcp_ses_lock); list_add(&ses->smb_ses_list, &server->smb_ses_list); - write_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); FreeXid(xid); return ses; @@ -1797,7 +1874,7 @@ cifs_find_tcon(struct cifsSesInfo *ses, const char *unc) struct list_head *tmp; struct cifsTconInfo *tcon; - write_lock(&cifs_tcp_ses_lock); + spin_lock(&cifs_tcp_ses_lock); list_for_each(tmp, &ses->tcon_list) { tcon = list_entry(tmp, struct cifsTconInfo, tcon_list); if (tcon->tidStatus == CifsExiting) @@ -1806,10 +1883,10 @@ cifs_find_tcon(struct cifsSesInfo *ses, const char *unc) continue; ++tcon->tc_count; - write_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); return tcon; } - write_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); return NULL; } @@ -1820,14 +1897,14 @@ cifs_put_tcon(struct cifsTconInfo *tcon) struct cifsSesInfo *ses = tcon->ses; cFYI(1, "%s: tc_count=%d\n", __func__, tcon->tc_count); - write_lock(&cifs_tcp_ses_lock); + spin_lock(&cifs_tcp_ses_lock); if (--tcon->tc_count > 0) { - write_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); return; } list_del_init(&tcon->tcon_list); - write_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); xid = GetXid(); CIFSSMBTDis(xid, tcon); @@ -1900,9 +1977,9 @@ cifs_get_tcon(struct cifsSesInfo *ses, struct smb_vol *volume_info) tcon->nocase = volume_info->nocase; tcon->local_lease = volume_info->local_lease; - write_lock(&cifs_tcp_ses_lock); + spin_lock(&cifs_tcp_ses_lock); list_add(&tcon->tcon_list, &ses->tcon_list); - write_unlock(&cifs_tcp_ses_lock); + spin_unlock(&cifs_tcp_ses_lock); cifs_fscache_get_super_cookie(tcon); @@ -1913,6 +1990,23 @@ out_fail: return ERR_PTR(rc); } +void +cifs_put_tlink(struct tcon_link *tlink) +{ + if (!tlink || IS_ERR(tlink)) + return; + + if (!atomic_dec_and_test(&tlink->tl_count) || + test_bit(TCON_LINK_IN_TREE, &tlink->tl_flags)) { + tlink->tl_time = jiffies; + return; + } + + if (!IS_ERR(tlink_tcon(tlink))) + cifs_put_tcon(tlink_tcon(tlink)); + kfree(tlink); + return; +} int get_dfs_path(int xid, struct cifsSesInfo *pSesInfo, const char *old_path, @@ -1997,6 +2091,33 @@ static void rfc1002mangle(char *target, char *source, unsigned int length) } +static int +bind_socket(struct TCP_Server_Info *server) +{ + int rc = 0; + if (server->srcaddr.ss_family != AF_UNSPEC) { + /* Bind to the specified local IP address */ + struct socket *socket = server->ssocket; + rc = socket->ops->bind(socket, + (struct sockaddr *) &server->srcaddr, + sizeof(server->srcaddr)); + if (rc < 0) { + struct sockaddr_in *saddr4; + struct sockaddr_in6 *saddr6; + saddr4 = (struct sockaddr_in *)&server->srcaddr; + saddr6 = (struct sockaddr_in6 *)&server->srcaddr; + if (saddr6->sin6_family == AF_INET6) + cERROR(1, "cifs: " + "Failed to bind to: %pI6c, error: %d\n", + &saddr6->sin6_addr, rc); + else + cERROR(1, "cifs: " + "Failed to bind to: %pI4, error: %d\n", + &saddr4->sin_addr.s_addr, rc); + } + } + return rc; +} static int ipv4_connect(struct TCP_Server_Info *server) @@ -2022,6 +2143,10 @@ ipv4_connect(struct TCP_Server_Info *server) cifs_reclassify_socket4(socket); } + rc = bind_socket(server); + if (rc < 0) + return rc; + /* user overrode default port */ if (server->addr.sockAddr.sin_port) { rc = socket->ops->connect(socket, (struct sockaddr *) @@ -2184,6 +2309,10 @@ ipv6_connect(struct TCP_Server_Info *server) cifs_reclassify_socket6(socket); } + rc = bind_socket(server); + if (rc < 0) + return rc; + /* user overrode default port */ if (server->addr.sockAddr6.sin6_port) { rc = socket->ops->connect(socket, @@ -2383,6 +2512,8 @@ convert_delimiter(char *path, char delim) static void setup_cifs_sb(struct smb_vol *pvolume_info, struct cifs_sb_info *cifs_sb) { + INIT_DELAYED_WORK(&cifs_sb->prune_tlinks, cifs_prune_tlinks); + if (pvolume_info->rsize > CIFSMaxBufSize) { cERROR(1, "rsize %d too large, using MaxBufSize", pvolume_info->rsize); @@ -2462,10 +2593,21 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info, cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DYNPERM; if (pvolume_info->fsc) cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_FSCACHE; + if (pvolume_info->multiuser) + cifs_sb->mnt_cifs_flags |= (CIFS_MOUNT_MULTIUSER | + CIFS_MOUNT_NO_PERM); if (pvolume_info->direct_io) { cFYI(1, "mounting share using direct i/o"); cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_DIRECT_IO; } + if (pvolume_info->mfsymlinks) { + if (pvolume_info->sfu_emul) { + cERROR(1, "mount option mfsymlinks ignored if sfu " + "mount option is used"); + } else { + cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_MF_SYMLINKS; + } + } if ((pvolume_info->cifs_acl) && (pvolume_info->dynperm)) cERROR(1, "mount option dynperm ignored if cifsacl " @@ -2552,6 +2694,7 @@ cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb, struct TCP_Server_Info *srvTcp; char *full_path; char *mount_data = mount_data_global; + struct tcon_link *tlink; #ifdef CONFIG_CIFS_DFS_UPCALL struct dfs_info3_param *referrals = NULL; unsigned int num_referrals = 0; @@ -2563,6 +2706,7 @@ try_mount_again: pSesInfo = NULL; srvTcp = NULL; full_path = NULL; + tlink = NULL; xid = GetXid(); @@ -2638,8 +2782,6 @@ try_mount_again: goto remote_path_check; } - cifs_sb->tcon = tcon; - /* do not care if following two calls succeed - informational */ if (!tcon->ipc) { CIFSSMBQFSDeviceInfo(xid, tcon); @@ -2748,6 +2890,38 @@ remote_path_check: #endif } + if (rc) + goto mount_fail_check; + + /* now, hang the tcon off of the superblock */ + tlink = kzalloc(sizeof *tlink, GFP_KERNEL); + if (tlink == NULL) { + rc = -ENOMEM; + goto mount_fail_check; + } + + tlink->tl_index = pSesInfo->linux_uid; + tlink->tl_tcon = tcon; + tlink->tl_time = jiffies; + set_bit(TCON_LINK_MASTER, &tlink->tl_flags); + set_bit(TCON_LINK_IN_TREE, &tlink->tl_flags); + + rc = radix_tree_preload(GFP_KERNEL); + if (rc == -ENOMEM) { + kfree(tlink); + goto mount_fail_check; + } + + spin_lock(&cifs_sb->tlink_tree_lock); + radix_tree_insert(&cifs_sb->tlink_tree, pSesInfo->linux_uid, tlink); + radix_tree_tag_set(&cifs_sb->tlink_tree, pSesInfo->linux_uid, + CIFS_TLINK_MASTER_TAG); + spin_unlock(&cifs_sb->tlink_tree_lock); + radix_tree_preload_end(); + + queue_delayed_work(system_nrt_wq, &cifs_sb->prune_tlinks, + TLINK_IDLE_EXPIRE); + mount_fail_check: /* on error free sesinfo and tcon struct if needed */ if (rc) { @@ -2825,14 +2999,13 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses, #ifdef CONFIG_CIFS_WEAK_PW_HASH if ((global_secflags & CIFSSEC_MAY_LANMAN) && (ses->server->secType == LANMAN)) - calc_lanman_hash(tcon->password, ses->server->cryptKey, + calc_lanman_hash(tcon->password, ses->server->cryptkey, ses->server->secMode & SECMODE_PW_ENCRYPT ? true : false, bcc_ptr); else #endif /* CIFS_WEAK_PW_HASH */ - SMBNTencrypt(tcon->password, ses->server->cryptKey, - bcc_ptr); + SMBNTencrypt(tcon->password, ses->server->cryptkey, bcc_ptr); bcc_ptr += CIFS_SESS_KEY_SIZE; if (ses->capabilities & CAP_UNICODE) { @@ -2934,19 +3107,39 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses, int cifs_umount(struct super_block *sb, struct cifs_sb_info *cifs_sb) { - int rc = 0; + int i, ret; char *tmp; + struct tcon_link *tlink[8]; + unsigned long index = 0; + + cancel_delayed_work_sync(&cifs_sb->prune_tlinks); + + do { + spin_lock(&cifs_sb->tlink_tree_lock); + ret = radix_tree_gang_lookup(&cifs_sb->tlink_tree, + (void **)tlink, index, + ARRAY_SIZE(tlink)); + /* increment index for next pass */ + if (ret > 0) + index = tlink[ret - 1]->tl_index + 1; + for (i = 0; i < ret; i++) { + cifs_get_tlink(tlink[i]); + clear_bit(TCON_LINK_IN_TREE, &tlink[i]->tl_flags); + radix_tree_delete(&cifs_sb->tlink_tree, + tlink[i]->tl_index); + } + spin_unlock(&cifs_sb->tlink_tree_lock); - if (cifs_sb->tcon) - cifs_put_tcon(cifs_sb->tcon); + for (i = 0; i < ret; i++) + cifs_put_tlink(tlink[i]); + } while (ret != 0); - cifs_sb->tcon = NULL; tmp = cifs_sb->prepath; cifs_sb->prepathlen = 0; cifs_sb->prepath = NULL; kfree(tmp); - return rc; + return 0; } int cifs_negotiate_protocol(unsigned int xid, struct cifsSesInfo *ses) @@ -2997,6 +3190,16 @@ int cifs_setup_session(unsigned int xid, struct cifsSesInfo *ses, if (rc) { cERROR(1, "Send error in SessSetup = %d", rc); } else { + mutex_lock(&ses->server->srv_mutex); + if (!server->session_estab) { + server->session_key.response = ses->auth_key.response; + server->session_key.len = ses->auth_key.len; + server->sequence_number = 0x2; + server->session_estab = true; + ses->auth_key.response = NULL; + } + mutex_unlock(&server->srv_mutex); + cFYI(1, "CIFS Session Established successfully"); spin_lock(&GlobalMid_Lock); ses->status = CifsGood; @@ -3004,6 +3207,246 @@ int cifs_setup_session(unsigned int xid, struct cifsSesInfo *ses, spin_unlock(&GlobalMid_Lock); } + kfree(ses->auth_key.response); + ses->auth_key.response = NULL; + ses->auth_key.len = 0; + kfree(ses->ntlmssp); + ses->ntlmssp = NULL; + return rc; } +static struct cifsTconInfo * +cifs_construct_tcon(struct cifs_sb_info *cifs_sb, uid_t fsuid) +{ + struct cifsTconInfo *master_tcon = cifs_sb_master_tcon(cifs_sb); + struct cifsSesInfo *ses; + struct cifsTconInfo *tcon = NULL; + struct smb_vol *vol_info; + char username[MAX_USERNAME_SIZE + 1]; + + vol_info = kzalloc(sizeof(*vol_info), GFP_KERNEL); + if (vol_info == NULL) { + tcon = ERR_PTR(-ENOMEM); + goto out; + } + + snprintf(username, MAX_USERNAME_SIZE, "krb50x%x", fsuid); + vol_info->username = username; + vol_info->local_nls = cifs_sb->local_nls; + vol_info->linux_uid = fsuid; + vol_info->cred_uid = fsuid; + vol_info->UNC = master_tcon->treeName; + vol_info->retry = master_tcon->retry; + vol_info->nocase = master_tcon->nocase; + vol_info->local_lease = master_tcon->local_lease; + vol_info->no_linux_ext = !master_tcon->unix_ext; + + /* FIXME: allow for other secFlg settings */ + vol_info->secFlg = CIFSSEC_MUST_KRB5; + + /* get a reference for the same TCP session */ + spin_lock(&cifs_tcp_ses_lock); + ++master_tcon->ses->server->srv_count; + spin_unlock(&cifs_tcp_ses_lock); + + ses = cifs_get_smb_ses(master_tcon->ses->server, vol_info); + if (IS_ERR(ses)) { + tcon = (struct cifsTconInfo *)ses; + cifs_put_tcp_session(master_tcon->ses->server); + goto out; + } + + tcon = cifs_get_tcon(ses, vol_info); + if (IS_ERR(tcon)) { + cifs_put_smb_ses(ses); + goto out; + } + + if (ses->capabilities & CAP_UNIX) + reset_cifs_unix_caps(0, tcon, NULL, vol_info); +out: + kfree(vol_info); + + return tcon; +} + +static struct tcon_link * +cifs_sb_master_tlink(struct cifs_sb_info *cifs_sb) +{ + struct tcon_link *tlink; + unsigned int ret; + + spin_lock(&cifs_sb->tlink_tree_lock); + ret = radix_tree_gang_lookup_tag(&cifs_sb->tlink_tree, (void **)&tlink, + 0, 1, CIFS_TLINK_MASTER_TAG); + spin_unlock(&cifs_sb->tlink_tree_lock); + + /* the master tcon should always be present */ + if (ret == 0) + BUG(); + + return tlink; +} + +struct cifsTconInfo * +cifs_sb_master_tcon(struct cifs_sb_info *cifs_sb) +{ + return tlink_tcon(cifs_sb_master_tlink(cifs_sb)); +} + +static int +cifs_sb_tcon_pending_wait(void *unused) +{ + schedule(); + return signal_pending(current) ? -ERESTARTSYS : 0; +} + +/* + * Find or construct an appropriate tcon given a cifs_sb and the fsuid of the + * current task. + * + * If the superblock doesn't refer to a multiuser mount, then just return + * the master tcon for the mount. + * + * First, search the radix tree for an existing tcon for this fsuid. If one + * exists, then check to see if it's pending construction. If it is then wait + * for construction to complete. Once it's no longer pending, check to see if + * it failed and either return an error or retry construction, depending on + * the timeout. + * + * If one doesn't exist then insert a new tcon_link struct into the tree and + * try to construct a new one. + */ +struct tcon_link * +cifs_sb_tlink(struct cifs_sb_info *cifs_sb) +{ + int ret; + unsigned long fsuid = (unsigned long) current_fsuid(); + struct tcon_link *tlink, *newtlink; + + if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MULTIUSER)) + return cifs_get_tlink(cifs_sb_master_tlink(cifs_sb)); + + spin_lock(&cifs_sb->tlink_tree_lock); + tlink = radix_tree_lookup(&cifs_sb->tlink_tree, fsuid); + if (tlink) + cifs_get_tlink(tlink); + spin_unlock(&cifs_sb->tlink_tree_lock); + + if (tlink == NULL) { + newtlink = kzalloc(sizeof(*tlink), GFP_KERNEL); + if (newtlink == NULL) + return ERR_PTR(-ENOMEM); + newtlink->tl_index = fsuid; + newtlink->tl_tcon = ERR_PTR(-EACCES); + set_bit(TCON_LINK_PENDING, &newtlink->tl_flags); + set_bit(TCON_LINK_IN_TREE, &newtlink->tl_flags); + cifs_get_tlink(newtlink); + + ret = radix_tree_preload(GFP_KERNEL); + if (ret != 0) { + kfree(newtlink); + return ERR_PTR(ret); + } + + spin_lock(&cifs_sb->tlink_tree_lock); + /* was one inserted after previous search? */ + tlink = radix_tree_lookup(&cifs_sb->tlink_tree, fsuid); + if (tlink) { + cifs_get_tlink(tlink); + spin_unlock(&cifs_sb->tlink_tree_lock); + radix_tree_preload_end(); + kfree(newtlink); + goto wait_for_construction; + } + ret = radix_tree_insert(&cifs_sb->tlink_tree, fsuid, newtlink); + spin_unlock(&cifs_sb->tlink_tree_lock); + radix_tree_preload_end(); + if (ret) { + kfree(newtlink); + return ERR_PTR(ret); + } + tlink = newtlink; + } else { +wait_for_construction: + ret = wait_on_bit(&tlink->tl_flags, TCON_LINK_PENDING, + cifs_sb_tcon_pending_wait, + TASK_INTERRUPTIBLE); + if (ret) { + cifs_put_tlink(tlink); + return ERR_PTR(ret); + } + + /* if it's good, return it */ + if (!IS_ERR(tlink->tl_tcon)) + return tlink; + + /* return error if we tried this already recently */ + if (time_before(jiffies, tlink->tl_time + TLINK_ERROR_EXPIRE)) { + cifs_put_tlink(tlink); + return ERR_PTR(-EACCES); + } + + if (test_and_set_bit(TCON_LINK_PENDING, &tlink->tl_flags)) + goto wait_for_construction; + } + + tlink->tl_tcon = cifs_construct_tcon(cifs_sb, fsuid); + clear_bit(TCON_LINK_PENDING, &tlink->tl_flags); + wake_up_bit(&tlink->tl_flags, TCON_LINK_PENDING); + + if (IS_ERR(tlink->tl_tcon)) { + cifs_put_tlink(tlink); + return ERR_PTR(-EACCES); + } + + return tlink; +} + +/* + * periodic workqueue job that scans tcon_tree for a superblock and closes + * out tcons. + */ +static void +cifs_prune_tlinks(struct work_struct *work) +{ + struct cifs_sb_info *cifs_sb = container_of(work, struct cifs_sb_info, + prune_tlinks.work); + struct tcon_link *tlink[8]; + unsigned long now = jiffies; + unsigned long index = 0; + int i, ret; + + do { + spin_lock(&cifs_sb->tlink_tree_lock); + ret = radix_tree_gang_lookup(&cifs_sb->tlink_tree, + (void **)tlink, index, + ARRAY_SIZE(tlink)); + /* increment index for next pass */ + if (ret > 0) + index = tlink[ret - 1]->tl_index + 1; + for (i = 0; i < ret; i++) { + if (test_bit(TCON_LINK_MASTER, &tlink[i]->tl_flags) || + atomic_read(&tlink[i]->tl_count) != 0 || + time_after(tlink[i]->tl_time + TLINK_IDLE_EXPIRE, + now)) { + tlink[i] = NULL; + continue; + } + cifs_get_tlink(tlink[i]); + clear_bit(TCON_LINK_IN_TREE, &tlink[i]->tl_flags); + radix_tree_delete(&cifs_sb->tlink_tree, + tlink[i]->tl_index); + } + spin_unlock(&cifs_sb->tlink_tree_lock); + + for (i = 0; i < ret; i++) { + if (tlink[i] != NULL) + cifs_put_tlink(tlink[i]); + } + } while (ret != 0); + + queue_delayed_work(system_nrt_wq, &cifs_sb->prune_tlinks, + TLINK_IDLE_EXPIRE); +} |