|
@@ -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,8 @@ cifs_reconnect(struct TCP_Server_Info *server)
|
|
|
sock_release(server->ssocket);
|
|
|
server->ssocket = NULL;
|
|
|
}
|
|
|
+ server->sequence_number = 0;
|
|
|
+ server->session_estab = false;
|
|
|
|
|
|
spin_lock(&GlobalMid_Lock);
|
|
|
list_for_each(tmp, &server->pending_mid_q) {
|
|
@@ -198,7 +207,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 +637,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 +677,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 +687,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 +710,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 +733,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 +1054,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 +1349,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 +1384,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 +1409,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
|
|
|
+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)
|
|
|
+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 +1465,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 +1524,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,14 +1547,14 @@ 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;
|
|
@@ -1574,6 +1632,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 +1643,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) {
|
|
@@ -1618,9 +1679,9 @@ cifs_get_tcp_session(struct smb_vol *volume_info)
|
|
|
}
|
|
|
|
|
|
/* 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);
|
|
|
|
|
@@ -1642,7 +1703,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 +1723,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 +1737,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();
|
|
@@ -1740,6 +1801,8 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info)
|
|
|
if (ses == NULL)
|
|
|
goto get_ses_fail;
|
|
|
|
|
|
+ ses->tilen = 0;
|
|
|
+ ses->tiblob = NULL;
|
|
|
/* new SMB session uses our server ref */
|
|
|
ses->server = server;
|
|
|
if (server->addr.sockAddr6.sin6_family == AF_INET6)
|
|
@@ -1778,9 +1841,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 +1860,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 +1869,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 +1883,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 +1963,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 +1976,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 +2077,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 +2129,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 +2295,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 +2498,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 +2579,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 +2680,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 +2692,7 @@ try_mount_again:
|
|
|
pSesInfo = NULL;
|
|
|
srvTcp = NULL;
|
|
|
full_path = NULL;
|
|
|
+ tlink = NULL;
|
|
|
|
|
|
xid = GetXid();
|
|
|
|
|
@@ -2638,8 +2768,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 +2876,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 +2985,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->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->cryptKey, bcc_ptr);
|
|
|
|
|
|
bcc_ptr += CIFS_SESS_KEY_SIZE;
|
|
|
if (ses->capabilities & CAP_UNICODE) {
|
|
@@ -2934,19 +3093,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 +3176,15 @@ 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) {
|
|
|
+ memcpy(&server->session_key.data,
|
|
|
+ &ses->auth_key.data, ses->auth_key.len);
|
|
|
+ server->session_key.len = ses->auth_key.len;
|
|
|
+ ses->server->session_estab = true;
|
|
|
+ }
|
|
|
+ mutex_unlock(&server->srv_mutex);
|
|
|
+
|
|
|
cFYI(1, "CIFS Session Established successfully");
|
|
|
spin_lock(&GlobalMid_Lock);
|
|
|
ses->status = CifsGood;
|
|
@@ -3007,3 +3195,237 @@ int cifs_setup_session(unsigned int xid, struct cifsSesInfo *ses,
|
|
|
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);
|
|
|
+}
|