]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - fs/cifs/connect.c
Merge tag 'v2.6.37' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[mv-sheeva.git] / fs / cifs / connect.c
index 88c84a38bccb182c313316a172bbae39ba2ee38f..cc1a8604a790b46767e066a59ef897f314144163 100644 (file)
@@ -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,26 @@ 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;
+       unsigned long actimeo; /* attribute cache timeout (jiffies) */
        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 tlink_rb_insert(struct rb_root *root, struct tcon_link *new_tlink);
+static void cifs_prune_tlinks(struct work_struct *work);
 
 /*
  * cifs tcp session reconnection
@@ -143,7 +152,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 +162,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 +175,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 +212,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 +642,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 +682,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 +692,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 +715,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 +738,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);
@@ -794,23 +807,20 @@ cifs_parse_mount_options(char *options, const char *devname,
        short int override_gid = -1;
        bool uid_specified = false;
        bool gid_specified = false;
+       char *nodename = utsname()->nodename;
 
        separator[0] = ',';
        separator[1] = 0;
 
-       if (Local_System_Name[0] != 0)
-               memcpy(vol->source_rfc1001_name, Local_System_Name, 15);
-       else {
-               char *nodename = utsname()->nodename;
-               int n = strnlen(nodename, 15);
-               memset(vol->source_rfc1001_name, 0x20, 15);
-               for (i = 0; i < n; i++) {
-                       /* does not have to be perfect mapping since field is
-                       informational, only used for servers that do not support
-                       port 445 and it can be overridden at mount time */
-                       vol->source_rfc1001_name[i] = toupper(nodename[i]);
-               }
-       }
+       /*
+        * does not have to be perfect mapping since field is
+        * informational, only used for servers that do not support
+        * port 445 and it can be overridden at mount time
+        */
+       memset(vol->source_rfc1001_name, 0x20, 15);
+       for (i = 0; i < strnlen(nodename, 15); i++)
+               vol->source_rfc1001_name[i] = toupper(nodename[i]);
+
        vol->source_rfc1001_name[15] = 0;
        /* null target name indicates to use *SMBSERVR default called name
           if we end up sending RFC1001 session initialize */
@@ -828,6 +838,8 @@ cifs_parse_mount_options(char *options, const char *devname,
        /* default to using server inode numbers where available */
        vol->server_ino = 1;
 
+       vol->actimeo = CIFS_DEF_ACTIMEO;
+
        if (!options)
                return 1;
 
@@ -1046,6 +1058,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
@@ -1186,6 +1214,16 @@ cifs_parse_mount_options(char *options, const char *devname,
                                        printk(KERN_WARNING "CIFS: server net"
                                        "biosname longer than 15 truncated.\n");
                        }
+               } else if (strnicmp(data, "actimeo", 7) == 0) {
+                       if (value && *value) {
+                               vol->actimeo = HZ * simple_strtoul(value,
+                                                                  &value, 0);
+                               if (vol->actimeo > CIFS_MAX_ACTIMEO) {
+                                       cERROR(1, "CIFS: attribute cache"
+                                                       "timeout too large");
+                                       return 1;
+                               }
+                       }
                } else if (strnicmp(data, "credentials", 4) == 0) {
                        /* ignore */
                } else if (strnicmp(data, "version", 3) == 0) {
@@ -1324,7 +1362,16 @@ cifs_parse_mount_options(char *options, const char *devname,
                                "supported. Instead set "
                                "/proc/fs/cifs/LookupCacheEnabled to 0\n");
                } else if (strnicmp(data, "fsc", 3) == 0) {
+#ifndef CONFIG_CIFS_FSCACHE
+                       cERROR(1, "FS-Cache support needs CONFIG_CIFS_FSCACHE"
+                                 "kernel config option set");
+                       return 1;
+#endif
                        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 +1403,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 +1428,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 +1484,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 +1543,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 +1566,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 +1638,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 +1662,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 +1673,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 +1691,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 +1705,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 +1736,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 +1756,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 +1770,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 +1854,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 +1871,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 +1890,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 +1899,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 +1913,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 +1993,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 +2006,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 +2107,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 +2159,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 +2325,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 +2528,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);
@@ -2434,6 +2581,8 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info,
        cFYI(1, "file mode: 0x%x  dir mode: 0x%x",
                cifs_sb->mnt_file_mode, cifs_sb->mnt_dir_mode);
 
+       cifs_sb->actimeo = pvolume_info->actimeo;
+
        if (pvolume_info->noperm)
                cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NO_PERM;
        if (pvolume_info->setuids)
@@ -2462,10 +2611,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 +2712,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 +2724,7 @@ try_mount_again:
        pSesInfo = NULL;
        srvTcp = NULL;
        full_path = NULL;
+       tlink = NULL;
 
        xid = GetXid();
 
@@ -2638,8 +2800,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);
@@ -2673,13 +2833,13 @@ remote_path_check:
        /* check if a whole path (including prepath) is not remote */
        if (!rc && cifs_sb->prepathlen && tcon) {
                /* build_path_to_root works only when we have a valid tcon */
-               full_path = cifs_build_path_to_root(cifs_sb);
+               full_path = cifs_build_path_to_root(cifs_sb, tcon);
                if (full_path == NULL) {
                        rc = -ENOMEM;
                        goto mount_fail_check;
                }
                rc = is_path_accessible(xid, tcon, cifs_sb, full_path);
-               if (rc != -EREMOTE) {
+               if (rc != 0 && rc != -EREMOTE) {
                        kfree(full_path);
                        goto mount_fail_check;
                }
@@ -2748,6 +2908,30 @@ 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_uid = 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);
+
+       cifs_sb->master_tlink = tlink;
+       spin_lock(&cifs_sb->tlink_tree_lock);
+       tlink_rb_insert(&cifs_sb->tlink_tree, tlink);
+       spin_unlock(&cifs_sb->tlink_tree_lock);
+
+       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 +3009,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 +3117,32 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses,
 int
 cifs_umount(struct super_block *sb, struct cifs_sb_info *cifs_sb)
 {
-       int rc = 0;
+       struct rb_root *root = &cifs_sb->tlink_tree;
+       struct rb_node *node;
+       struct tcon_link *tlink;
        char *tmp;
 
-       if (cifs_sb->tcon)
-               cifs_put_tcon(cifs_sb->tcon);
+       cancel_delayed_work_sync(&cifs_sb->prune_tlinks);
+
+       spin_lock(&cifs_sb->tlink_tree_lock);
+       while ((node = rb_first(root))) {
+               tlink = rb_entry(node, struct tcon_link, tl_rbnode);
+               cifs_get_tlink(tlink);
+               clear_bit(TCON_LINK_IN_TREE, &tlink->tl_flags);
+               rb_erase(node, root);
+
+               spin_unlock(&cifs_sb->tlink_tree_lock);
+               cifs_put_tlink(tlink);
+               spin_lock(&cifs_sb->tlink_tree_lock);
+       }
+       spin_unlock(&cifs_sb->tlink_tree_lock);
 
-       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 +3193,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 +3210,263 @@ 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 inline struct tcon_link *
+cifs_sb_master_tlink(struct cifs_sb_info *cifs_sb)
+{
+       return cifs_sb->master_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 and return a tlink with given uid */
+static struct tcon_link *
+tlink_rb_search(struct rb_root *root, uid_t uid)
+{
+       struct rb_node *node = root->rb_node;
+       struct tcon_link *tlink;
+
+       while (node) {
+               tlink = rb_entry(node, struct tcon_link, tl_rbnode);
+
+               if (tlink->tl_uid > uid)
+                       node = node->rb_left;
+               else if (tlink->tl_uid < uid)
+                       node = node->rb_right;
+               else
+                       return tlink;
+       }
+       return NULL;
+}
+
+/* insert a tcon_link into the tree */
+static void
+tlink_rb_insert(struct rb_root *root, struct tcon_link *new_tlink)
+{
+       struct rb_node **new = &(root->rb_node), *parent = NULL;
+       struct tcon_link *tlink;
+
+       while (*new) {
+               tlink = rb_entry(*new, struct tcon_link, tl_rbnode);
+               parent = *new;
+
+               if (tlink->tl_uid > new_tlink->tl_uid)
+                       new = &((*new)->rb_left);
+               else
+                       new = &((*new)->rb_right);
+       }
+
+       rb_link_node(&new_tlink->tl_rbnode, parent, new);
+       rb_insert_color(&new_tlink->tl_rbnode, root);
+}
+
+/*
+ * 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 rbtree 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;
+       uid_t fsuid = 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 = tlink_rb_search(&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_uid = 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);
+
+               spin_lock(&cifs_sb->tlink_tree_lock);
+               /* was one inserted after previous search? */
+               tlink = tlink_rb_search(&cifs_sb->tlink_tree, fsuid);
+               if (tlink) {
+                       cifs_get_tlink(tlink);
+                       spin_unlock(&cifs_sb->tlink_tree_lock);
+                       kfree(newtlink);
+                       goto wait_for_construction;
+               }
+               tlink = newtlink;
+               tlink_rb_insert(&cifs_sb->tlink_tree, tlink);
+               spin_unlock(&cifs_sb->tlink_tree_lock);
+       } 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 rb_root *root = &cifs_sb->tlink_tree;
+       struct rb_node *node = rb_first(root);
+       struct rb_node *tmp;
+       struct tcon_link *tlink;
+
+       /*
+        * Because we drop the spinlock in the loop in order to put the tlink
+        * it's not guarded against removal of links from the tree. The only
+        * places that remove entries from the tree are this function and
+        * umounts. Because this function is non-reentrant and is canceled
+        * before umount can proceed, this is safe.
+        */
+       spin_lock(&cifs_sb->tlink_tree_lock);
+       node = rb_first(root);
+       while (node != NULL) {
+               tmp = node;
+               node = rb_next(tmp);
+               tlink = rb_entry(tmp, struct tcon_link, tl_rbnode);
+
+               if (test_bit(TCON_LINK_MASTER, &tlink->tl_flags) ||
+                   atomic_read(&tlink->tl_count) != 0 ||
+                   time_after(tlink->tl_time + TLINK_IDLE_EXPIRE, jiffies))
+                       continue;
+
+               cifs_get_tlink(tlink);
+               clear_bit(TCON_LINK_IN_TREE, &tlink->tl_flags);
+               rb_erase(tmp, root);
+
+               spin_unlock(&cifs_sb->tlink_tree_lock);
+               cifs_put_tlink(tlink);
+               spin_lock(&cifs_sb->tlink_tree_lock);
+       }
+       spin_unlock(&cifs_sb->tlink_tree_lock);
+
+       queue_delayed_work(system_nrt_wq, &cifs_sb->prune_tlinks,
+                               TLINK_IDLE_EXPIRE);
+}