diff options
Diffstat (limited to 'fs/cifs/connect.c')
-rw-r--r-- | fs/cifs/connect.c | 941 |
1 files changed, 569 insertions, 372 deletions
diff --git a/fs/cifs/connect.c b/fs/cifs/connect.c index 277262a8e82..7f540df5252 100644 --- a/fs/cifs/connect.c +++ b/fs/cifs/connect.c @@ -57,61 +57,6 @@ extern mempool_t *cifs_req_poolp; -struct smb_vol { - char *username; - char *password; - char *domainname; - char *UNC; - char *UNCip; - char *iocharset; /* local code page for mapping to and from Unicode */ - char source_rfc1001_name[RFC1001_NAME_LEN_WITH_NULL]; /* clnt nb name */ - char target_rfc1001_name[RFC1001_NAME_LEN_WITH_NULL]; /* srvr nb name */ - uid_t cred_uid; - uid_t linux_uid; - gid_t linux_gid; - mode_t file_mode; - mode_t dir_mode; - unsigned secFlg; - bool retry:1; - bool intr:1; - bool setuids:1; - bool override_uid:1; - bool override_gid:1; - bool dynperm:1; - bool noperm:1; - bool no_psx_acl:1; /* set if posix acl support should be disabled */ - bool cifs_acl:1; - bool no_xattr:1; /* set if xattr (EA) support should be disabled*/ - bool server_ino:1; /* use inode numbers from server ie UniqueId */ - bool direct_io:1; - bool strict_io:1; /* strict cache behavior */ - bool remap:1; /* set to remap seven reserved chars in filenames */ - bool posix_paths:1; /* unset to not ask for posix pathnames. */ - bool no_linux_ext:1; - bool sfu_emul:1; - bool nullauth:1; /* attempt to authenticate with null user */ - bool nocase:1; /* request case insensitive filenames */ - bool nobrl:1; /* disable sending byte range locks to srv */ - bool mand_lock:1; /* send mandatory not posix byte range lock reqs */ - bool seal:1; /* request transport encryption on share */ - bool nodfs:1; /* Do not request DFS, even if available */ - bool local_lease:1; /* check leases only on local system, not remote */ - bool noblocksnd:1; - 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) @@ -134,9 +79,10 @@ cifs_reconnect(struct TCP_Server_Info *server) { int rc = 0; struct list_head *tmp, *tmp2; - struct cifsSesInfo *ses; - struct cifsTconInfo *tcon; + struct cifs_ses *ses; + struct cifs_tcon *tcon; struct mid_q_entry *mid_entry; + struct list_head retry_list; spin_lock(&GlobalMid_Lock); if (server->tcpStatus == CifsExiting) { @@ -156,11 +102,11 @@ cifs_reconnect(struct TCP_Server_Info *server) cFYI(1, "%s: marking sessions and tcons for reconnect", __func__); spin_lock(&cifs_tcp_ses_lock); list_for_each(tmp, &server->smb_ses_list) { - ses = list_entry(tmp, struct cifsSesInfo, smb_ses_list); + ses = list_entry(tmp, struct cifs_ses, smb_ses_list); ses->need_reconnect = true; ses->ipc_tid = 0; list_for_each(tmp2, &ses->tcon_list) { - tcon = list_entry(tmp2, struct cifsTconInfo, tcon_list); + tcon = list_entry(tmp2, struct cifs_tcon, tcon_list); tcon->need_reconnect = true; } } @@ -188,18 +134,25 @@ cifs_reconnect(struct TCP_Server_Info *server) mutex_unlock(&server->srv_mutex); /* mark submitted MIDs for retry and issue callback */ - cFYI(1, "%s: issuing mid callbacks", __func__); + INIT_LIST_HEAD(&retry_list); + cFYI(1, "%s: moving mids to private list", __func__); spin_lock(&GlobalMid_Lock); list_for_each_safe(tmp, tmp2, &server->pending_mid_q) { mid_entry = list_entry(tmp, struct mid_q_entry, qhead); if (mid_entry->midState == MID_REQUEST_SUBMITTED) mid_entry->midState = MID_RETRY_NEEDED; + list_move(&mid_entry->qhead, &retry_list); + } + spin_unlock(&GlobalMid_Lock); + + cFYI(1, "%s: issuing mid callbacks", __func__); + list_for_each_safe(tmp, tmp2, &retry_list) { + mid_entry = list_entry(tmp, struct mid_q_entry, qhead); list_del_init(&mid_entry->qhead); mid_entry->callback(mid_entry); } - spin_unlock(&GlobalMid_Lock); - while (server->tcpStatus == CifsNeedReconnect) { + do { try_to_freeze(); /* we should try only the port we connected to before */ @@ -214,7 +167,7 @@ cifs_reconnect(struct TCP_Server_Info *server) server->tcpStatus = CifsNeedNegotiate; spin_unlock(&GlobalMid_Lock); } - } + } while (server->tcpStatus == CifsNeedReconnect); return rc; } @@ -316,19 +269,19 @@ static int coalesce_t2(struct smb_hdr *psecond, struct smb_hdr *pTargetSMB) put_unaligned_le16(total_in_buf, &pSMBt->t2_rsp.DataCount); /* fix up the BCC */ - byte_count = get_bcc_le(pTargetSMB); + byte_count = get_bcc(pTargetSMB); byte_count += total_in_buf2; /* is the result too big for the field? */ if (byte_count > USHRT_MAX) return -EPROTO; - put_bcc_le(byte_count, pTargetSMB); + put_bcc(byte_count, pTargetSMB); - byte_count = pTargetSMB->smb_buf_length; + byte_count = be32_to_cpu(pTargetSMB->smb_buf_length); byte_count += total_in_buf2; /* don't allow buffer to overflow */ if (byte_count > CIFSMaxBufSize) return -ENOBUFS; - pTargetSMB->smb_buf_length = byte_count; + pTargetSMB->smb_buf_length = cpu_to_be32(byte_count); memcpy(data_area_of_target, data_area_of_buf2, total_in_buf2); @@ -495,8 +448,7 @@ incomplete_rcv: /* Note that FC 1001 length is big endian on the wire, but we convert it here so it is always manipulated as host byte order */ - pdu_length = be32_to_cpu((__force __be32)smb_buffer->smb_buf_length); - smb_buffer->smb_buf_length = pdu_length; + pdu_length = be32_to_cpu(smb_buffer->smb_buf_length); cFYI(1, "rfc1002 length 0x%x", pdu_length+4); @@ -672,12 +624,12 @@ multi_t2_fnd: mid_entry->when_received = jiffies; #endif list_del_init(&mid_entry->qhead); - mid_entry->callback(mid_entry); break; } spin_unlock(&GlobalMid_Lock); if (mid_entry != NULL) { + mid_entry->callback(mid_entry); /* Was previous buf put in mpx struct for multi-rsp? */ if (!isMultiRsp) { /* smb buffer will be freed by user thread */ @@ -735,21 +687,31 @@ multi_t2_fnd: sock_release(csocket); server->ssocket = NULL; } - /* buffer usuallly freed in free_mid - need to free it here on exit */ + /* buffer usually freed in free_mid - need to free it here on exit */ cifs_buf_release(bigbuf); if (smallbuf) /* no sense logging a debug message if NULL */ cifs_small_buf_release(smallbuf); if (!list_empty(&server->pending_mid_q)) { + struct list_head dispose_list; + + INIT_LIST_HEAD(&dispose_list); spin_lock(&GlobalMid_Lock); list_for_each_safe(tmp, tmp2, &server->pending_mid_q) { mid_entry = list_entry(tmp, struct mid_q_entry, qhead); - cFYI(1, "Clearing Mid 0x%x - issuing callback", - mid_entry->mid); + cFYI(1, "Clearing mid 0x%x", mid_entry->mid); + mid_entry->midState = MID_SHUTDOWN; + list_move(&mid_entry->qhead, &dispose_list); + } + spin_unlock(&GlobalMid_Lock); + + /* now walk dispose list and issue callbacks */ + list_for_each_safe(tmp, tmp2, &dispose_list) { + mid_entry = list_entry(tmp, struct mid_q_entry, qhead); + cFYI(1, "Callback mid 0x%x", mid_entry->mid); list_del_init(&mid_entry->qhead); mid_entry->callback(mid_entry); } - spin_unlock(&GlobalMid_Lock); /* 1/8th of sec is more than enough time for them to exit */ msleep(125); } @@ -818,10 +780,11 @@ extract_hostname(const char *unc) } static int -cifs_parse_mount_options(char *options, const char *devname, +cifs_parse_mount_options(const char *mountdata, const char *devname, struct smb_vol *vol) { char *value, *data, *end; + char *mountdata_copy = NULL, *options; unsigned int temp_len, i, j; char separator[2]; short int override_uid = -1; @@ -861,9 +824,14 @@ cifs_parse_mount_options(char *options, const char *devname, vol->actimeo = CIFS_DEF_ACTIMEO; - if (!options) - return 1; + if (!mountdata) + goto cifs_parse_mount_err; + mountdata_copy = kstrndup(mountdata, PAGE_SIZE, GFP_KERNEL); + if (!mountdata_copy) + goto cifs_parse_mount_err; + + options = mountdata_copy; end = options + strlen(options); if (strncmp(options, "sep=", 4) == 0) { if (options[4] != 0) { @@ -889,17 +857,22 @@ cifs_parse_mount_options(char *options, const char *devname, if (!value) { printk(KERN_WARNING "CIFS: invalid or missing username\n"); - return 1; /* needs_arg; */ + goto cifs_parse_mount_err; } else if (!*value) { /* null user, ie anonymous, authentication */ vol->nullauth = 1; } if (strnlen(value, MAX_USERNAME_SIZE) < MAX_USERNAME_SIZE) { - vol->username = value; + vol->username = kstrdup(value, GFP_KERNEL); + if (!vol->username) { + printk(KERN_WARNING "CIFS: no memory " + "for username\n"); + goto cifs_parse_mount_err; + } } else { printk(KERN_WARNING "CIFS: username too long\n"); - return 1; + goto cifs_parse_mount_err; } } else if (strnicmp(data, "pass", 4) == 0) { if (!value) { @@ -963,7 +936,7 @@ cifs_parse_mount_options(char *options, const char *devname, if (vol->password == NULL) { printk(KERN_WARNING "CIFS: no memory " "for password\n"); - return 1; + goto cifs_parse_mount_err; } for (i = 0, j = 0; i < temp_len; i++, j++) { vol->password[j] = value[i]; @@ -979,7 +952,7 @@ cifs_parse_mount_options(char *options, const char *devname, if (vol->password == NULL) { printk(KERN_WARNING "CIFS: no memory " "for password\n"); - return 1; + goto cifs_parse_mount_err; } strcpy(vol->password, value); } @@ -989,11 +962,16 @@ cifs_parse_mount_options(char *options, const char *devname, vol->UNCip = NULL; } else if (strnlen(value, INET6_ADDRSTRLEN) < INET6_ADDRSTRLEN) { - vol->UNCip = value; + vol->UNCip = kstrdup(value, GFP_KERNEL); + if (!vol->UNCip) { + printk(KERN_WARNING "CIFS: no memory " + "for UNC IP\n"); + goto cifs_parse_mount_err; + } } else { printk(KERN_WARNING "CIFS: ip address " "too long\n"); - return 1; + goto cifs_parse_mount_err; } } else if (strnicmp(data, "sec", 3) == 0) { if (!value || !*value) { @@ -1006,7 +984,7 @@ cifs_parse_mount_options(char *options, const char *devname, /* vol->secFlg |= CIFSSEC_MUST_SEAL | CIFSSEC_MAY_KRB5; */ cERROR(1, "Krb5 cifs privacy not supported"); - return 1; + goto cifs_parse_mount_err; } else if (strnicmp(value, "krb5", 4) == 0) { vol->secFlg |= CIFSSEC_MAY_KRB5; } else if (strnicmp(value, "ntlmsspi", 8) == 0) { @@ -1036,7 +1014,16 @@ cifs_parse_mount_options(char *options, const char *devname, vol->nullauth = 1; } else { cERROR(1, "bad security option: %s", value); - return 1; + goto cifs_parse_mount_err; + } + } else if (strnicmp(data, "vers", 3) == 0) { + if (!value || !*value) { + cERROR(1, "no protocol version specified" + " after vers= mount option"); + } else if ((strnicmp(value, "cifs", 4) == 0) || + (strnicmp(value, "1", 1) == 0)) { + /* this is the default */ + continue; } } else if ((strnicmp(data, "unc", 3) == 0) || (strnicmp(data, "target", 6) == 0) @@ -1044,12 +1031,12 @@ cifs_parse_mount_options(char *options, const char *devname, if (!value || !*value) { printk(KERN_WARNING "CIFS: invalid path to " "network resource\n"); - return 1; /* needs_arg; */ + goto cifs_parse_mount_err; } if ((temp_len = strnlen(value, 300)) < 300) { vol->UNC = kmalloc(temp_len+1, GFP_KERNEL); if (vol->UNC == NULL) - return 1; + goto cifs_parse_mount_err; strcpy(vol->UNC, value); if (strncmp(vol->UNC, "//", 2) == 0) { vol->UNC[0] = '\\'; @@ -1058,27 +1045,32 @@ cifs_parse_mount_options(char *options, const char *devname, printk(KERN_WARNING "CIFS: UNC Path does not begin " "with // or \\\\ \n"); - return 1; + goto cifs_parse_mount_err; } } else { printk(KERN_WARNING "CIFS: UNC name too long\n"); - return 1; + goto cifs_parse_mount_err; } } else if ((strnicmp(data, "domain", 3) == 0) || (strnicmp(data, "workgroup", 5) == 0)) { if (!value || !*value) { printk(KERN_WARNING "CIFS: invalid domain name\n"); - return 1; /* needs_arg; */ + goto cifs_parse_mount_err; } /* BB are there cases in which a comma can be valid in a domain name and need special handling? */ if (strnlen(value, 256) < 256) { - vol->domainname = value; + vol->domainname = kstrdup(value, GFP_KERNEL); + if (!vol->domainname) { + printk(KERN_WARNING "CIFS: no memory " + "for domainname\n"); + goto cifs_parse_mount_err; + } cFYI(1, "Domain name set"); } else { printk(KERN_WARNING "CIFS: domain name too " "long\n"); - return 1; + goto cifs_parse_mount_err; } } else if (strnicmp(data, "srcaddr", 7) == 0) { vol->srcaddr.ss_family = AF_UNSPEC; @@ -1086,7 +1078,7 @@ cifs_parse_mount_options(char *options, const char *devname, if (!value || !*value) { printk(KERN_WARNING "CIFS: srcaddr value" " not specified.\n"); - return 1; /* needs_arg; */ + goto cifs_parse_mount_err; } i = cifs_convert_address((struct sockaddr *)&vol->srcaddr, value, strlen(value)); @@ -1094,20 +1086,20 @@ cifs_parse_mount_options(char *options, const char *devname, printk(KERN_WARNING "CIFS: Could not parse" " srcaddr: %s\n", value); - return 1; + goto cifs_parse_mount_err; } } else if (strnicmp(data, "prefixpath", 10) == 0) { if (!value || !*value) { printk(KERN_WARNING "CIFS: invalid path prefix\n"); - return 1; /* needs_argument */ + goto cifs_parse_mount_err; } if ((temp_len = strnlen(value, 1024)) < 1024) { if (value[0] != '/') temp_len++; /* missing leading slash */ vol->prepath = kmalloc(temp_len+1, GFP_KERNEL); if (vol->prepath == NULL) - return 1; + goto cifs_parse_mount_err; if (value[0] != '/') { vol->prepath[0] = '/'; strcpy(vol->prepath+1, value); @@ -1116,24 +1108,33 @@ cifs_parse_mount_options(char *options, const char *devname, cFYI(1, "prefix path %s", vol->prepath); } else { printk(KERN_WARNING "CIFS: prefix too long\n"); - return 1; + goto cifs_parse_mount_err; } } else if (strnicmp(data, "iocharset", 9) == 0) { if (!value || !*value) { printk(KERN_WARNING "CIFS: invalid iocharset " "specified\n"); - return 1; /* needs_arg; */ + goto cifs_parse_mount_err; } if (strnlen(value, 65) < 65) { - if (strnicmp(value, "default", 7)) - vol->iocharset = value; + if (strnicmp(value, "default", 7)) { + vol->iocharset = kstrdup(value, + GFP_KERNEL); + + if (!vol->iocharset) { + printk(KERN_WARNING "CIFS: no " + "memory for" + "charset\n"); + goto cifs_parse_mount_err; + } + } /* if iocharset not set then load_nls_default is used by caller */ cFYI(1, "iocharset set to %s", value); } else { printk(KERN_WARNING "CIFS: iocharset name " "too long.\n"); - return 1; + goto cifs_parse_mount_err; } } else if (!strnicmp(data, "uid", 3) && value && *value) { vol->linux_uid = simple_strtoul(value, &value, 0); @@ -1246,7 +1247,7 @@ cifs_parse_mount_options(char *options, const char *devname, if (vol->actimeo > CIFS_MAX_ACTIMEO) { cERROR(1, "CIFS: attribute cache" "timeout too large"); - return 1; + goto cifs_parse_mount_err; } } } else if (strnicmp(data, "credentials", 4) == 0) { @@ -1358,6 +1359,8 @@ cifs_parse_mount_options(char *options, const char *devname, vol->server_ino = 1; } else if (strnicmp(data, "noserverino", 9) == 0) { vol->server_ino = 0; + } else if (strnicmp(data, "rwpidforward", 4) == 0) { + vol->rwpidforward = 1; } else if (strnicmp(data, "cifsacl", 7) == 0) { vol->cifs_acl = 1; } else if (strnicmp(data, "nocifsacl", 9) == 0) { @@ -1388,9 +1391,9 @@ cifs_parse_mount_options(char *options, const char *devname, "/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" + cERROR(1, "FS-Cache support needs CONFIG_CIFS_FSCACHE " "kernel config option set"); - return 1; + goto cifs_parse_mount_err; #endif vol->fsc = true; } else if (strnicmp(data, "mfsymlinks", 10) == 0) { @@ -1405,12 +1408,12 @@ cifs_parse_mount_options(char *options, const char *devname, if (devname == NULL) { printk(KERN_WARNING "CIFS: Missing UNC name for mount " "target\n"); - return 1; + goto cifs_parse_mount_err; } if ((temp_len = strnlen(devname, 300)) < 300) { vol->UNC = kmalloc(temp_len+1, GFP_KERNEL); if (vol->UNC == NULL) - return 1; + goto cifs_parse_mount_err; strcpy(vol->UNC, devname); if (strncmp(vol->UNC, "//", 2) == 0) { vol->UNC[0] = '\\'; @@ -1418,21 +1421,21 @@ cifs_parse_mount_options(char *options, const char *devname, } else if (strncmp(vol->UNC, "\\\\", 2) != 0) { printk(KERN_WARNING "CIFS: UNC Path does not " "begin with // or \\\\ \n"); - return 1; + goto cifs_parse_mount_err; } value = strpbrk(vol->UNC+2, "/\\"); if (value) *value = '\\'; } else { printk(KERN_WARNING "CIFS: UNC name too long\n"); - return 1; + goto cifs_parse_mount_err; } } if (vol->multiuser && !(vol->secFlg & CIFSSEC_MAY_KRB5)) { cERROR(1, "Multiuser mounts currently require krb5 " "authentication!"); - return 1; + goto cifs_parse_mount_err; } if (vol->UNCip == NULL) @@ -1450,7 +1453,12 @@ cifs_parse_mount_options(char *options, const char *devname, printk(KERN_NOTICE "CIFS: ignoring forcegid mount option " "specified with no gid= option.\n"); + kfree(mountdata_copy); return 0; + +cifs_parse_mount_err: + kfree(mountdata_copy); + return 1; } /** Returns true if srcaddr isn't specified and rhs isn't @@ -1589,16 +1597,35 @@ match_security(struct TCP_Server_Info *server, struct smb_vol *vol) /* now check if signing mode is acceptable */ if ((secFlags & CIFSSEC_MAY_SIGN) == 0 && - (server->secMode & SECMODE_SIGN_REQUIRED)) + (server->sec_mode & SECMODE_SIGN_REQUIRED)) return false; else if (((secFlags & CIFSSEC_MUST_SIGN) == CIFSSEC_MUST_SIGN) && - (server->secMode & + (server->sec_mode & (SECMODE_SIGN_ENABLED|SECMODE_SIGN_REQUIRED)) == 0) return false; return true; } +static int match_server(struct TCP_Server_Info *server, struct sockaddr *addr, + struct smb_vol *vol) +{ + if (!net_eq(cifs_net_ns(server), current->nsproxy->net_ns)) + return 0; + + if (!match_address(server, addr, + (struct sockaddr *)&vol->srcaddr)) + return 0; + + if (!match_port(server, addr)) + return 0; + + if (!match_security(server, vol)) + return 0; + + return 1; +} + static struct TCP_Server_Info * cifs_find_tcp_session(struct sockaddr *addr, struct smb_vol *vol) { @@ -1606,17 +1633,7 @@ cifs_find_tcp_session(struct sockaddr *addr, struct smb_vol *vol) spin_lock(&cifs_tcp_ses_lock); list_for_each_entry(server, &cifs_tcp_ses_list, tcp_ses_list) { - if (!net_eq(cifs_net_ns(server), current->nsproxy->net_ns)) - continue; - - if (!match_address(server, addr, - (struct sockaddr *)&vol->srcaddr)) - continue; - - if (!match_port(server, addr)) - continue; - - if (!match_security(server, vol)) + if (!match_server(server, addr, vol)) continue; ++server->srv_count; @@ -1810,32 +1827,39 @@ out_err: return ERR_PTR(rc); } -static struct cifsSesInfo * +static int match_session(struct cifs_ses *ses, struct smb_vol *vol) +{ + switch (ses->server->secType) { + case Kerberos: + if (vol->cred_uid != ses->cred_uid) + return 0; + break; + default: + /* anything else takes username/password */ + if (ses->user_name == NULL) + return 0; + if (strncmp(ses->user_name, vol->username, + MAX_USERNAME_SIZE)) + return 0; + if (strlen(vol->username) != 0 && + ses->password != NULL && + strncmp(ses->password, + vol->password ? vol->password : "", + MAX_PASSWORD_SIZE)) + return 0; + } + return 1; +} + +static struct cifs_ses * cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb_vol *vol) { - struct cifsSesInfo *ses; + struct cifs_ses *ses; spin_lock(&cifs_tcp_ses_lock); list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { - switch (server->secType) { - case Kerberos: - if (vol->cred_uid != ses->cred_uid) - continue; - break; - default: - /* anything else takes username/password */ - if (ses->user_name == NULL) - continue; - if (strncmp(ses->user_name, vol->username, - MAX_USERNAME_SIZE)) - continue; - if (strlen(vol->username) != 0 && - ses->password != NULL && - strncmp(ses->password, - vol->password ? vol->password : "", - MAX_PASSWORD_SIZE)) - continue; - } + if (!match_session(ses, vol)) + continue; ++ses->ses_count; spin_unlock(&cifs_tcp_ses_lock); return ses; @@ -1845,7 +1869,7 @@ cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb_vol *vol) } static void -cifs_put_smb_ses(struct cifsSesInfo *ses) +cifs_put_smb_ses(struct cifs_ses *ses) { int xid; struct TCP_Server_Info *server = ses->server; @@ -1871,11 +1895,11 @@ cifs_put_smb_ses(struct cifsSesInfo *ses) static bool warned_on_ntlm; /* globals init to false automatically */ -static struct cifsSesInfo * +static struct cifs_ses * cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info) { int rc = -ENOMEM, xid; - struct cifsSesInfo *ses; + struct cifs_ses *ses; struct sockaddr_in *addr = (struct sockaddr_in *)&server->dstaddr; struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&server->dstaddr; @@ -1952,7 +1976,7 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info) warned_on_ntlm = true; cERROR(1, "default security mechanism requested. The default " "security mechanism will be upgraded from ntlm to " - "ntlmv2 in kernel release 2.6.41"); + "ntlmv2 in kernel release 3.1"); } ses->overrideSecFlg = volume_info->secFlg; @@ -1978,20 +2002,26 @@ get_ses_fail: return ERR_PTR(rc); } -static struct cifsTconInfo * -cifs_find_tcon(struct cifsSesInfo *ses, const char *unc) +static int match_tcon(struct cifs_tcon *tcon, const char *unc) +{ + if (tcon->tidStatus == CifsExiting) + return 0; + if (strncmp(tcon->treeName, unc, MAX_TREE_SIZE)) + return 0; + return 1; +} + +static struct cifs_tcon * +cifs_find_tcon(struct cifs_ses *ses, const char *unc) { struct list_head *tmp; - struct cifsTconInfo *tcon; + struct cifs_tcon *tcon; 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) + tcon = list_entry(tmp, struct cifs_tcon, tcon_list); + if (!match_tcon(tcon, unc)) continue; - if (strncmp(tcon->treeName, unc, MAX_TREE_SIZE)) - continue; - ++tcon->tc_count; spin_unlock(&cifs_tcp_ses_lock); return tcon; @@ -2001,10 +2031,10 @@ cifs_find_tcon(struct cifsSesInfo *ses, const char *unc) } static void -cifs_put_tcon(struct cifsTconInfo *tcon) +cifs_put_tcon(struct cifs_tcon *tcon) { int xid; - struct cifsSesInfo *ses = tcon->ses; + struct cifs_ses *ses = tcon->ses; cFYI(1, "%s: tc_count=%d\n", __func__, tcon->tc_count); spin_lock(&cifs_tcp_ses_lock); @@ -2025,11 +2055,11 @@ cifs_put_tcon(struct cifsTconInfo *tcon) cifs_put_smb_ses(ses); } -static struct cifsTconInfo * -cifs_get_tcon(struct cifsSesInfo *ses, struct smb_vol *volume_info) +static struct cifs_tcon * +cifs_get_tcon(struct cifs_ses *ses, struct smb_vol *volume_info) { int rc, xid; - struct cifsTconInfo *tcon; + struct cifs_tcon *tcon; tcon = cifs_find_tcon(ses, volume_info->UNC); if (tcon) { @@ -2118,8 +2148,105 @@ cifs_put_tlink(struct tcon_link *tlink) return; } +static inline struct tcon_link * +cifs_sb_master_tlink(struct cifs_sb_info *cifs_sb) +{ + return cifs_sb->master_tlink; +} + +static int +compare_mount_options(struct super_block *sb, struct cifs_mnt_data *mnt_data) +{ + struct cifs_sb_info *old = CIFS_SB(sb); + struct cifs_sb_info *new = mnt_data->cifs_sb; + + if ((sb->s_flags & CIFS_MS_MASK) != (mnt_data->flags & CIFS_MS_MASK)) + return 0; + + if ((old->mnt_cifs_flags & CIFS_MOUNT_MASK) != + (new->mnt_cifs_flags & CIFS_MOUNT_MASK)) + return 0; + + if (old->rsize != new->rsize) + return 0; + + /* + * We want to share sb only if we don't specify wsize or specified wsize + * is greater or equal than existing one. + */ + if (new->wsize && new->wsize < old->wsize) + return 0; + + if (old->mnt_uid != new->mnt_uid || old->mnt_gid != new->mnt_gid) + return 0; + + if (old->mnt_file_mode != new->mnt_file_mode || + old->mnt_dir_mode != new->mnt_dir_mode) + return 0; + + if (strcmp(old->local_nls->charset, new->local_nls->charset)) + return 0; + + if (old->actimeo != new->actimeo) + return 0; + + return 1; +} + +int +cifs_match_super(struct super_block *sb, void *data) +{ + struct cifs_mnt_data *mnt_data = (struct cifs_mnt_data *)data; + struct smb_vol *volume_info; + struct cifs_sb_info *cifs_sb; + struct TCP_Server_Info *tcp_srv; + struct cifs_ses *ses; + struct cifs_tcon *tcon; + struct tcon_link *tlink; + struct sockaddr_storage addr; + int rc = 0; + + memset(&addr, 0, sizeof(struct sockaddr_storage)); + + spin_lock(&cifs_tcp_ses_lock); + cifs_sb = CIFS_SB(sb); + tlink = cifs_get_tlink(cifs_sb_master_tlink(cifs_sb)); + if (IS_ERR(tlink)) { + spin_unlock(&cifs_tcp_ses_lock); + return rc; + } + tcon = tlink_tcon(tlink); + ses = tcon->ses; + tcp_srv = ses->server; + + volume_info = mnt_data->vol; + + if (!volume_info->UNCip || !volume_info->UNC) + goto out; + + rc = cifs_fill_sockaddr((struct sockaddr *)&addr, + volume_info->UNCip, + strlen(volume_info->UNCip), + volume_info->port); + if (!rc) + goto out; + + if (!match_server(tcp_srv, (struct sockaddr *)&addr, volume_info) || + !match_session(ses, volume_info) || + !match_tcon(tcon, volume_info->UNC)) { + rc = 0; + goto out; + } + + rc = compare_mount_options(sb, mnt_data); +out: + cifs_put_tlink(tlink); + spin_unlock(&cifs_tcp_ses_lock); + return rc; +} + int -get_dfs_path(int xid, struct cifsSesInfo *pSesInfo, const char *old_path, +get_dfs_path(int xid, struct cifs_ses *pSesInfo, const char *old_path, const struct nls_table *nls_codepage, unsigned int *pnum_referrals, struct dfs_info3_param **preferrals, int remap) { @@ -2280,7 +2407,7 @@ ip_rfc1001_connect(struct TCP_Server_Info *server) smb_buf = (struct smb_hdr *)ses_init_buf; /* sizeof RFC1002_SESSION_REQUEST with no scope */ - smb_buf->smb_buf_length = 0x81000044; + smb_buf->smb_buf_length = cpu_to_be32(0x81000044); rc = smb_send(server, smb_buf, 0x44); kfree(ses_init_buf); /* @@ -2418,8 +2545,8 @@ ip_connect(struct TCP_Server_Info *server) return generic_ip_connect(server); } -void reset_cifs_unix_caps(int xid, struct cifsTconInfo *tcon, - struct super_block *sb, struct smb_vol *vol_info) +void reset_cifs_unix_caps(int xid, struct cifs_tcon *tcon, + struct cifs_sb_info *cifs_sb, struct smb_vol *vol_info) { /* if we are reconnecting then should we check to see if * any requested capabilities changed locally e.g. via @@ -2447,7 +2574,7 @@ void reset_cifs_unix_caps(int xid, struct cifsTconInfo *tcon, if (!CIFSSMBQFSUnixInfo(xid, tcon)) { __u64 cap = le64_to_cpu(tcon->fsUnixInfo.Capability); - + cFYI(1, "unix caps which server supports %lld", cap); /* check for reconnect case in which we do not want to change the mount behavior if we can avoid it */ if (vol_info == NULL) { @@ -2465,33 +2592,31 @@ void reset_cifs_unix_caps(int xid, struct cifsTconInfo *tcon, } } + if (cap & CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP) + cERROR(1, "per-share encryption not supported yet"); + cap &= CIFS_UNIX_CAP_MASK; if (vol_info && vol_info->no_psx_acl) cap &= ~CIFS_UNIX_POSIX_ACL_CAP; else if (CIFS_UNIX_POSIX_ACL_CAP & cap) { cFYI(1, "negotiated posix acl support"); - if (sb) - sb->s_flags |= MS_POSIXACL; + if (cifs_sb) + cifs_sb->mnt_cifs_flags |= + CIFS_MOUNT_POSIXACL; } if (vol_info && vol_info->posix_paths == 0) cap &= ~CIFS_UNIX_POSIX_PATHNAMES_CAP; else if (cap & CIFS_UNIX_POSIX_PATHNAMES_CAP) { cFYI(1, "negotiate posix pathnames"); - if (sb) - CIFS_SB(sb)->mnt_cifs_flags |= + if (cifs_sb) + cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_POSIX_PATHS; } - /* We might be setting the path sep back to a different - form if we are reconnecting and the server switched its - posix path capability for this share */ - if (sb && (CIFS_SB(sb)->prepathlen > 0)) - CIFS_SB(sb)->prepath[0] = CIFS_DIR_SEP(CIFS_SB(sb)); - - if (sb && (CIFS_SB(sb)->rsize > 127 * 1024)) { + if (cifs_sb && (cifs_sb->rsize > 127 * 1024)) { if ((cap & CIFS_UNIX_LARGE_READ_CAP) == 0) { - CIFS_SB(sb)->rsize = 127 * 1024; + cifs_sb->rsize = 127 * 1024; cFYI(DBG2, "larger reads not supported by srv"); } } @@ -2513,6 +2638,10 @@ void reset_cifs_unix_caps(int xid, struct cifsTconInfo *tcon, cFYI(1, "very large read cap"); if (cap & CIFS_UNIX_LARGE_WRITE_CAP) cFYI(1, "very large write cap"); + if (cap & CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP) + cFYI(1, "transport encryption cap"); + if (cap & CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP) + cFYI(1, "mandatory transport encryption cap"); #endif /* CIFS_DEBUG2 */ if (CIFSSMBSetFSUnixInfo(xid, tcon, cap)) { if (vol_info == NULL) { @@ -2529,31 +2658,14 @@ void reset_cifs_unix_caps(int xid, struct cifsTconInfo *tcon, } } -static void -convert_delimiter(char *path, char delim) -{ - int i; - char old_delim; - - if (path == NULL) - return; - - if (delim == '/') - old_delim = '\\'; - else - old_delim = '/'; - - for (i = 0; path[i] != '\0'; i++) { - if (path[i] == old_delim) - path[i] = delim; - } -} - -static void setup_cifs_sb(struct smb_vol *pvolume_info, - struct cifs_sb_info *cifs_sb) +void cifs_setup_cifs_sb(struct smb_vol *pvolume_info, + struct cifs_sb_info *cifs_sb) { INIT_DELAYED_WORK(&cifs_sb->prune_tlinks, cifs_prune_tlinks); + spin_lock_init(&cifs_sb->tlink_tree_lock); + cifs_sb->tlink_tree = RB_ROOT; + if (pvolume_info->rsize > CIFSMaxBufSize) { cERROR(1, "rsize %d too large, using MaxBufSize", pvolume_info->rsize); @@ -2564,40 +2676,19 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info, else /* default */ cifs_sb->rsize = CIFSMaxBufSize; - if (pvolume_info->wsize > PAGEVEC_SIZE * PAGE_CACHE_SIZE) { - cERROR(1, "wsize %d too large, using 4096 instead", - pvolume_info->wsize); - cifs_sb->wsize = 4096; - } else if (pvolume_info->wsize) - cifs_sb->wsize = pvolume_info->wsize; - else - cifs_sb->wsize = min_t(const int, - PAGEVEC_SIZE * PAGE_CACHE_SIZE, - 127*1024); - /* old default of CIFSMaxBufSize was too small now - that SMB Write2 can send multiple pages in kvec. - RFC1001 does not describe what happens when frame - bigger than 128K is sent so use that as max in - conjunction with 52K kvec constraint on arch with 4K - page size */ - if (cifs_sb->rsize < 2048) { cifs_sb->rsize = 2048; /* Windows ME may prefer this */ cFYI(1, "readsize set to minimum: 2048"); } - /* calculate prepath */ - cifs_sb->prepath = pvolume_info->prepath; - if (cifs_sb->prepath) { - cifs_sb->prepathlen = strlen(cifs_sb->prepath); - /* we can not convert the / to \ in the path - separators in the prefixpath yet because we do not - know (until reset_cifs_unix_caps is called later) - whether POSIX PATH CAP is available. We normalize - the / to \ after reset_cifs_unix_caps is called */ - pvolume_info->prepath = NULL; - } else - cifs_sb->prepathlen = 0; + + /* + * Temporarily set wsize for matching superblock. If we end up using + * new sb then cifs_negotiate_wsize will later negotiate it downward + * if needed. + */ + cifs_sb->wsize = pvolume_info->wsize; + cifs_sb->mnt_uid = pvolume_info->linux_uid; cifs_sb->mnt_gid = pvolume_info->linux_gid; cifs_sb->mnt_file_mode = pvolume_info->file_mode; @@ -2606,6 +2697,7 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info, cifs_sb->mnt_file_mode, cifs_sb->mnt_dir_mode); cifs_sb->actimeo = pvolume_info->actimeo; + cifs_sb->local_nls = pvolume_info->local_nls; if (pvolume_info->noperm) cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NO_PERM; @@ -2625,6 +2717,8 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info, cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NOSSYNC; if (pvolume_info->mand_lock) cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_NOPOSIXBRL; + if (pvolume_info->rwpidforward) + cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_RWPIDFORWARD; if (pvolume_info->cifs_acl) cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_CIFS_ACL; if (pvolume_info->override_uid) @@ -2658,8 +2752,62 @@ static void setup_cifs_sb(struct smb_vol *pvolume_info, "mount option supported"); } +/* + * When the server supports very large writes via POSIX extensions, we can + * allow up to 2^24-1, minus the size of a WRITE_AND_X header, not including + * the RFC1001 length. + * + * Note that this might make for "interesting" allocation problems during + * writeback however as we have to allocate an array of pointers for the + * pages. A 16M write means ~32kb page array with PAGE_CACHE_SIZE == 4096. + */ +#define CIFS_MAX_WSIZE ((1<<24) - 1 - sizeof(WRITE_REQ) + 4) + +/* + * When the server doesn't allow large posix writes, only allow a wsize of + * 128k minus the size of the WRITE_AND_X header. That allows for a write up + * to the maximum size described by RFC1002. + */ +#define CIFS_MAX_RFC1002_WSIZE (128 * 1024 - sizeof(WRITE_REQ) + 4) + +/* + * The default wsize is 1M. find_get_pages seems to return a maximum of 256 + * pages in a single call. With PAGE_CACHE_SIZE == 4k, this means we can fill + * a single wsize request with a single call. + */ +#define CIFS_DEFAULT_WSIZE (1024 * 1024) + +static unsigned int +cifs_negotiate_wsize(struct cifs_tcon *tcon, struct smb_vol *pvolume_info) +{ + __u64 unix_cap = le64_to_cpu(tcon->fsUnixInfo.Capability); + struct TCP_Server_Info *server = tcon->ses->server; + unsigned int wsize = pvolume_info->wsize ? pvolume_info->wsize : + CIFS_DEFAULT_WSIZE; + + /* can server support 24-bit write sizes? (via UNIX extensions) */ + if (!tcon->unix_ext || !(unix_cap & CIFS_UNIX_LARGE_WRITE_CAP)) + wsize = min_t(unsigned int, wsize, CIFS_MAX_RFC1002_WSIZE); + + /* + * no CAP_LARGE_WRITE_X or is signing enabled without CAP_UNIX set? + * Limit it to max buffer offered by the server, minus the size of the + * WRITEX header, not including the 4 byte RFC1001 length. + */ + if (!(server->capabilities & CAP_LARGE_WRITE_X) || + (!(server->capabilities & CAP_UNIX) && + (server->sec_mode & (SECMODE_SIGN_ENABLED|SECMODE_SIGN_REQUIRED)))) + wsize = min_t(unsigned int, wsize, + server->maxBuf - sizeof(WRITE_REQ) + 4); + + /* hard limit of CIFS_MAX_WSIZE */ + wsize = min_t(unsigned int, wsize, CIFS_MAX_WSIZE); + + return wsize; +} + static int -is_path_accessible(int xid, struct cifsTconInfo *tcon, +is_path_accessible(int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, const char *full_path) { int rc; @@ -2682,8 +2830,8 @@ is_path_accessible(int xid, struct cifsTconInfo *tcon, return rc; } -static void -cleanup_volume_info(struct smb_vol **pvolume_info) +void +cifs_cleanup_volume_info(struct smb_vol **pvolume_info) { struct smb_vol *volume_info; @@ -2691,8 +2839,12 @@ cleanup_volume_info(struct smb_vol **pvolume_info) return; volume_info = *pvolume_info; + kfree(volume_info->username); kzfree(volume_info->password); kfree(volume_info->UNC); + kfree(volume_info->UNCip); + kfree(volume_info->domainname); + kfree(volume_info->iocharset); kfree(volume_info->prepath); kfree(volume_info); *pvolume_info = NULL; @@ -2709,55 +2861,78 @@ build_unc_path_to_root(const struct smb_vol *volume_info, char *full_path; int unc_len = strnlen(volume_info->UNC, MAX_TREE_SIZE + 1); - full_path = kmalloc(unc_len + cifs_sb->prepathlen + 1, GFP_KERNEL); + full_path = kmalloc(unc_len + 1, GFP_KERNEL); if (full_path == NULL) return ERR_PTR(-ENOMEM); strncpy(full_path, volume_info->UNC, unc_len); - if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) { - int i; - for (i = 0; i < unc_len; i++) { - if (full_path[i] == '\\') - full_path[i] = '/'; - } - } - - if (cifs_sb->prepathlen) - strncpy(full_path + unc_len, cifs_sb->prepath, - cifs_sb->prepathlen); - - full_path[unc_len + cifs_sb->prepathlen] = 0; /* add trailing null */ + full_path[unc_len] = 0; /* add trailing null */ + convert_delimiter(full_path, CIFS_DIR_SEP(cifs_sb)); return full_path; } -#endif -int -cifs_mount(struct super_block *sb, struct cifs_sb_info *cifs_sb, - char *mount_data_global, const char *devname) +/* + * Perform a dfs referral query for a share and (optionally) prefix + * + * If a referral is found, cifs_sb->mountdata will be (re-)allocated + * to a string containing updated options for the submount. Otherwise it + * will be left untouched. + * + * Returns the rc from get_dfs_path to the caller, which can be used to + * determine whether there were referrals. + */ +static int +expand_dfs_referral(int xid, struct cifs_ses *pSesInfo, + struct smb_vol *volume_info, struct cifs_sb_info *cifs_sb, + int check_prefix) { int rc; - int xid; - struct smb_vol *volume_info; - struct cifsSesInfo *pSesInfo; - struct cifsTconInfo *tcon; - 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; - int referral_walks_count = 0; -try_mount_again: + struct dfs_info3_param *referrals = NULL; + char *full_path = NULL, *ref_path = NULL, *mdata = NULL; + + full_path = build_unc_path_to_root(volume_info, cifs_sb); + if (IS_ERR(full_path)) + return PTR_ERR(full_path); + + /* For DFS paths, skip the first '\' of the UNC */ + ref_path = check_prefix ? full_path + 1 : volume_info->UNC + 1; + + rc = get_dfs_path(xid, pSesInfo , ref_path, cifs_sb->local_nls, + &num_referrals, &referrals, + cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR); + + if (!rc && num_referrals > 0) { + char *fake_devname = NULL; + + mdata = cifs_compose_mount_options(cifs_sb->mountdata, + full_path + 1, referrals, + &fake_devname); + + free_dfs_info_array(referrals, num_referrals); + kfree(fake_devname); + + if (cifs_sb->mountdata != NULL) + kfree(cifs_sb->mountdata); + + if (IS_ERR(mdata)) { + rc = PTR_ERR(mdata); + mdata = NULL; + } + cifs_sb->mountdata = mdata; + } + kfree(full_path); + return rc; +} #endif - rc = 0; - tcon = NULL; - pSesInfo = NULL; - srvTcp = NULL; - full_path = NULL; - tlink = NULL; - xid = GetXid(); +int cifs_setup_volume_info(struct smb_vol **pvolume_info, char *mount_data, + const char *devname) +{ + struct smb_vol *volume_info; + int rc = 0; + + *pvolume_info = NULL; volume_info = kzalloc(sizeof(struct smb_vol), GFP_KERNEL); if (!volume_info) { @@ -2765,14 +2940,19 @@ try_mount_again: goto out; } - if (cifs_parse_mount_options(mount_data, devname, volume_info)) { + if (cifs_parse_mount_options(mount_data, devname, + volume_info)) { rc = -EINVAL; goto out; } if (volume_info->nullauth) { cFYI(1, "null user"); - volume_info->username = ""; + volume_info->username = kzalloc(1, GFP_KERNEL); + if (volume_info->username == NULL) { + rc = -ENOMEM; + goto out; + } } else if (volume_info->username) { /* BB fixme parse for domain name here */ cFYI(1, "Username: %s", volume_info->username); @@ -2797,12 +2977,58 @@ try_mount_again: goto out; } } - cifs_sb->local_nls = volume_info->local_nls; + + *pvolume_info = volume_info; + return rc; +out: + cifs_cleanup_volume_info(&volume_info); + return rc; +} + +int +cifs_mount(struct cifs_sb_info *cifs_sb, struct smb_vol *volume_info) +{ + int rc = 0; + int xid; + struct cifs_ses *pSesInfo; + struct cifs_tcon *tcon; + struct TCP_Server_Info *srvTcp; + char *full_path; + struct tcon_link *tlink; +#ifdef CONFIG_CIFS_DFS_UPCALL + int referral_walks_count = 0; + + rc = bdi_setup_and_register(&cifs_sb->bdi, "cifs", BDI_CAP_MAP_COPY); + if (rc) + return rc; + + cifs_sb->bdi.ra_pages = default_backing_dev_info.ra_pages; + +try_mount_again: + /* cleanup activities if we're chasing a referral */ + if (referral_walks_count) { + if (tcon) + cifs_put_tcon(tcon); + else if (pSesInfo) + cifs_put_smb_ses(pSesInfo); + + cifs_cleanup_volume_info(&volume_info); + FreeXid(xid); + } +#endif + tcon = NULL; + pSesInfo = NULL; + srvTcp = NULL; + full_path = NULL; + tlink = NULL; + + xid = GetXid(); /* get a reference to a tcp session */ srvTcp = cifs_get_tcp_session(volume_info); if (IS_ERR(srvTcp)) { rc = PTR_ERR(srvTcp); + bdi_destroy(&cifs_sb->bdi); goto out; } @@ -2814,15 +3040,6 @@ try_mount_again: goto mount_fail_check; } - setup_cifs_sb(volume_info, cifs_sb); - if (pSesInfo->capabilities & CAP_LARGE_FILES) - sb->s_maxbytes = MAX_LFS_FILESIZE; - else - sb->s_maxbytes = MAX_NON_LFS; - - /* BB FIXME fix time_gran to be larger for LANMAN sessions */ - sb->s_time_gran = 100; - /* search for existing tcon to this server share */ tcon = cifs_get_tcon(pSesInfo, volume_info); if (IS_ERR(tcon)) { @@ -2831,40 +3048,59 @@ try_mount_again: goto remote_path_check; } - /* do not care if following two calls succeed - informational */ - if (!tcon->ipc) { - CIFSSMBQFSDeviceInfo(xid, tcon); - CIFSSMBQFSAttributeInfo(xid, tcon); - } - /* tell server which Unix caps we support */ - if (tcon->ses->capabilities & CAP_UNIX) + if (tcon->ses->capabilities & CAP_UNIX) { /* reset of caps checks mount to see if unix extensions disabled for just this mount */ - reset_cifs_unix_caps(xid, tcon, sb, volume_info); - else + reset_cifs_unix_caps(xid, tcon, cifs_sb, volume_info); + if ((tcon->ses->server->tcpStatus == CifsNeedReconnect) && + (le64_to_cpu(tcon->fsUnixInfo.Capability) & + CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP)) { + rc = -EACCES; + goto mount_fail_check; + } + } else tcon->unix_ext = 0; /* server does not support them */ - /* convert forward to back slashes in prepath here if needed */ - if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) == 0) - convert_delimiter(cifs_sb->prepath, CIFS_DIR_SEP(cifs_sb)); + /* do not care if following two calls succeed - informational */ + if (!tcon->ipc) { + CIFSSMBQFSDeviceInfo(xid, tcon); + CIFSSMBQFSAttributeInfo(xid, tcon); + } if ((tcon->unix_ext == 0) && (cifs_sb->rsize > (1024 * 127))) { cifs_sb->rsize = 1024 * 127; cFYI(DBG2, "no very large read support, rsize now 127K"); } - if (!(tcon->ses->capabilities & CAP_LARGE_WRITE_X)) - cifs_sb->wsize = min(cifs_sb->wsize, - (tcon->ses->server->maxBuf - MAX_CIFS_HDR_SIZE)); if (!(tcon->ses->capabilities & CAP_LARGE_READ_X)) cifs_sb->rsize = min(cifs_sb->rsize, (tcon->ses->server->maxBuf - MAX_CIFS_HDR_SIZE)); + cifs_sb->wsize = cifs_negotiate_wsize(tcon, volume_info); + remote_path_check: - /* check if a whole path (including prepath) is not remote */ +#ifdef CONFIG_CIFS_DFS_UPCALL + /* + * Perform an unconditional check for whether there are DFS + * referrals for this path without prefix, to provide support + * for DFS referrals from w2k8 servers which don't seem to respond + * with PATH_NOT_COVERED to requests that include the prefix. + * Chase the referral if found, otherwise continue normally. + */ + if (referral_walks_count == 0) { + int refrc = expand_dfs_referral(xid, pSesInfo, volume_info, + cifs_sb, false); + if (!refrc) { + referral_walks_count++; + goto try_mount_again; + } + } +#endif + + /* check if a whole path is not remote */ if (!rc && tcon) { /* build_path_to_root works only when we have a valid tcon */ - full_path = cifs_build_path_to_root(cifs_sb, tcon); + full_path = cifs_build_path_to_root(volume_info, cifs_sb, tcon); if (full_path == NULL) { rc = -ENOMEM; goto mount_fail_check; @@ -2890,50 +3126,15 @@ remote_path_check: rc = -ELOOP; goto mount_fail_check; } - /* convert forward to back slashes in prepath here if needed */ - if ((cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) == 0) - convert_delimiter(cifs_sb->prepath, - CIFS_DIR_SEP(cifs_sb)); - full_path = build_unc_path_to_root(volume_info, cifs_sb); - if (IS_ERR(full_path)) { - rc = PTR_ERR(full_path); - goto mount_fail_check; - } - - cFYI(1, "Getting referral for: %s", full_path); - rc = get_dfs_path(xid, pSesInfo , full_path + 1, - cifs_sb->local_nls, &num_referrals, &referrals, - cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR); - if (!rc && num_referrals > 0) { - char *fake_devname = NULL; - - if (mount_data != mount_data_global) - kfree(mount_data); - - mount_data = cifs_compose_mount_options( - cifs_sb->mountdata, full_path + 1, - referrals, &fake_devname); - free_dfs_info_array(referrals, num_referrals); - kfree(fake_devname); - kfree(full_path); - - if (IS_ERR(mount_data)) { - rc = PTR_ERR(mount_data); - mount_data = NULL; - goto mount_fail_check; - } + rc = expand_dfs_referral(xid, pSesInfo, volume_info, cifs_sb, + true); - if (tcon) - cifs_put_tcon(tcon); - else if (pSesInfo) - cifs_put_smb_ses(pSesInfo); - - cleanup_volume_info(&volume_info); + if (!rc) { referral_walks_count++; - FreeXid(xid); goto try_mount_again; } + goto mount_fail_check; #else /* No DFS support, return error on mount */ rc = -EOPNOTSUPP; #endif @@ -2966,8 +3167,6 @@ remote_path_check: mount_fail_check: /* on error free sesinfo and tcon struct if needed */ if (rc) { - if (mount_data != mount_data_global) - kfree(mount_data); /* If find_unc succeeded then rc == 0 so we can not end */ /* up accidentally freeing someone elses tcon struct */ if (tcon) @@ -2976,6 +3175,7 @@ mount_fail_check: cifs_put_smb_ses(pSesInfo); else cifs_put_tcp_session(srvTcp); + bdi_destroy(&cifs_sb->bdi); goto out; } @@ -2985,14 +3185,17 @@ mount_fail_check: password will be freed at unmount time) */ out: /* zero out password before freeing */ - cleanup_volume_info(&volume_info); FreeXid(xid); return rc; } +/* + * Issue a TREE_CONNECT request. Note that for IPC$ shares, that the tcon + * pointer may be NULL. + */ int -CIFSTCon(unsigned int xid, struct cifsSesInfo *ses, - const char *tree, struct cifsTconInfo *tcon, +CIFSTCon(unsigned int xid, struct cifs_ses *ses, + const char *tree, struct cifs_tcon *tcon, const struct nls_table *nls_codepage) { struct smb_hdr *smb_buffer; @@ -3024,7 +3227,7 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses, pSMB->AndXCommand = 0xFF; pSMB->Flags = cpu_to_le16(TCON_EXTENDED_SECINFO); bcc_ptr = &pSMB->Password[0]; - if ((ses->server->secMode) & SECMODE_USER) { + if (!tcon || (ses->server->sec_mode & SECMODE_USER)) { pSMB->PasswordLength = cpu_to_le16(1); /* minimum */ *bcc_ptr = 0; /* password is null byte */ bcc_ptr++; /* skip password */ @@ -3041,7 +3244,7 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses, if ((global_secflags & CIFSSEC_MAY_LANMAN) && (ses->server->secType == LANMAN)) calc_lanman_hash(tcon->password, ses->server->cryptkey, - ses->server->secMode & + ses->server->sec_mode & SECMODE_PW_ENCRYPT ? true : false, bcc_ptr); else @@ -3057,7 +3260,7 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses, } } - if (ses->server->secMode & + if (ses->server->sec_mode & (SECMODE_SIGN_REQUIRED | SECMODE_SIGN_ENABLED)) smb_buffer->Flags2 |= SMBFLG2_SECURITY_SIGNATURE; @@ -3083,7 +3286,8 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses, bcc_ptr += strlen("?????"); bcc_ptr += 1; count = bcc_ptr - &pSMB->Password[0]; - pSMB->hdr.smb_buf_length += count; + pSMB->hdr.smb_buf_length = cpu_to_be32(be32_to_cpu( + pSMB->hdr.smb_buf_length) + count); pSMB->ByteCount = cpu_to_le16(count); rc = SendReceive(xid, ses, smb_buffer, smb_buffer_response, &length, @@ -3146,13 +3350,12 @@ CIFSTCon(unsigned int xid, struct cifsSesInfo *ses, return rc; } -int -cifs_umount(struct super_block *sb, struct cifs_sb_info *cifs_sb) +void +cifs_umount(struct cifs_sb_info *cifs_sb) { struct rb_root *root = &cifs_sb->tlink_tree; struct rb_node *node; struct tcon_link *tlink; - char *tmp; cancel_delayed_work_sync(&cifs_sb->prune_tlinks); @@ -3169,15 +3372,13 @@ cifs_umount(struct super_block *sb, struct cifs_sb_info *cifs_sb) } spin_unlock(&cifs_sb->tlink_tree_lock); - tmp = cifs_sb->prepath; - cifs_sb->prepathlen = 0; - cifs_sb->prepath = NULL; - kfree(tmp); - - return 0; + bdi_destroy(&cifs_sb->bdi); + kfree(cifs_sb->mountdata); + unload_nls(cifs_sb->local_nls); + kfree(cifs_sb); } -int cifs_negotiate_protocol(unsigned int xid, struct cifsSesInfo *ses) +int cifs_negotiate_protocol(unsigned int xid, struct cifs_ses *ses) { int rc = 0; struct TCP_Server_Info *server = ses->server; @@ -3195,7 +3396,7 @@ int cifs_negotiate_protocol(unsigned int xid, struct cifsSesInfo *ses) } if (rc == 0) { spin_lock(&GlobalMid_Lock); - if (server->tcpStatus != CifsExiting) + if (server->tcpStatus == CifsNeedNegotiate) server->tcpStatus = CifsGood; else rc = -EHOSTDOWN; @@ -3207,7 +3408,7 @@ int cifs_negotiate_protocol(unsigned int xid, struct cifsSesInfo *ses) } -int cifs_setup_session(unsigned int xid, struct cifsSesInfo *ses, +int cifs_setup_session(unsigned int xid, struct cifs_ses *ses, struct nls_table *nls_info) { int rc = 0; @@ -3219,7 +3420,7 @@ int cifs_setup_session(unsigned int xid, struct cifsSesInfo *ses, ses->capabilities &= (~CAP_UNIX); cFYI(1, "Security Mode: 0x%x Capabilities: 0x%x TimeAdjust: %d", - server->secMode, server->capabilities, server->timeAdj); + server->sec_mode, server->capabilities, server->timeAdj); rc = CIFS_SessSetup(xid, ses, nls_info); if (rc) { @@ -3251,14 +3452,16 @@ int cifs_setup_session(unsigned int xid, struct cifsSesInfo *ses, return rc; } -static struct cifsTconInfo * +static struct cifs_tcon * 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 cifs_tcon *master_tcon = cifs_sb_master_tcon(cifs_sb); + struct cifs_ses *ses; + struct cifs_tcon *tcon = NULL; struct smb_vol *vol_info; - char username[MAX_USERNAME_SIZE + 1]; + char username[28]; /* big enough for "krb50x" + hex of ULONG_MAX 6+16 */ + /* We used to have this as MAX_USERNAME which is */ + /* way too big now (256 instead of 32) */ vol_info = kzalloc(sizeof(*vol_info), GFP_KERNEL); if (vol_info == NULL) { @@ -3287,7 +3490,7 @@ cifs_construct_tcon(struct cifs_sb_info *cifs_sb, uid_t fsuid) ses = cifs_get_smb_ses(master_tcon->ses->server, vol_info); if (IS_ERR(ses)) { - tcon = (struct cifsTconInfo *)ses; + tcon = (struct cifs_tcon *)ses; cifs_put_tcp_session(master_tcon->ses->server); goto out; } @@ -3306,13 +3509,7 @@ out: return tcon; } -static inline struct tcon_link * -cifs_sb_master_tlink(struct cifs_sb_info *cifs_sb) -{ - return cifs_sb->master_tlink; -} - -struct cifsTconInfo * +struct cifs_tcon * cifs_sb_master_tcon(struct cifs_sb_info *cifs_sb) { return tlink_tcon(cifs_sb_master_tlink(cifs_sb)); |