| /* |
| * fs/cifs/cifssmb.c |
| * |
| * Copyright (C) International Business Machines Corp., 2002,2009 |
| * Author(s): Steve French (sfrench@us.ibm.com) |
| * |
| * Contains the routines for constructing the SMB PDUs themselves |
| * |
| * This library is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU Lesser General Public License as published |
| * by the Free Software Foundation; either version 2.1 of the License, or |
| * (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See |
| * the GNU Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public License |
| * along with this library; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| /* SMB/CIFS PDU handling routines here - except for leftovers in connect.c */ |
| /* These are mostly routines that operate on a pathname, or on a tree id */ |
| /* (mounted volume), but there are eight handle based routines which must be */ |
| /* treated slightly differently for reconnection purposes since we never */ |
| /* want to reuse a stale file handle and only the caller knows the file info */ |
| |
| #include <linux/fs.h> |
| #include <linux/kernel.h> |
| #include <linux/vfs.h> |
| #include <linux/posix_acl_xattr.h> |
| #include <asm/uaccess.h> |
| #include "cifspdu.h" |
| #include "cifsglob.h" |
| #include "cifsacl.h" |
| #include "cifsproto.h" |
| #include "cifs_unicode.h" |
| #include "cifs_debug.h" |
| |
| #ifdef CONFIG_CIFS_POSIX |
| static struct { |
| int index; |
| char *name; |
| } protocols[] = { |
| #ifdef CONFIG_CIFS_WEAK_PW_HASH |
| {LANMAN_PROT, "\2LM1.2X002"}, |
| {LANMAN2_PROT, "\2LANMAN2.1"}, |
| #endif /* weak password hashing for legacy clients */ |
| {CIFS_PROT, "\2NT LM 0.12"}, |
| {POSIX_PROT, "\2POSIX 2"}, |
| {BAD_PROT, "\2"} |
| }; |
| #else |
| static struct { |
| int index; |
| char *name; |
| } protocols[] = { |
| #ifdef CONFIG_CIFS_WEAK_PW_HASH |
| {LANMAN_PROT, "\2LM1.2X002"}, |
| {LANMAN2_PROT, "\2LANMAN2.1"}, |
| #endif /* weak password hashing for legacy clients */ |
| {CIFS_PROT, "\2NT LM 0.12"}, |
| {BAD_PROT, "\2"} |
| }; |
| #endif |
| |
| /* define the number of elements in the cifs dialect array */ |
| #ifdef CONFIG_CIFS_POSIX |
| #ifdef CONFIG_CIFS_WEAK_PW_HASH |
| #define CIFS_NUM_PROT 4 |
| #else |
| #define CIFS_NUM_PROT 2 |
| #endif /* CIFS_WEAK_PW_HASH */ |
| #else /* not posix */ |
| #ifdef CONFIG_CIFS_WEAK_PW_HASH |
| #define CIFS_NUM_PROT 3 |
| #else |
| #define CIFS_NUM_PROT 1 |
| #endif /* CONFIG_CIFS_WEAK_PW_HASH */ |
| #endif /* CIFS_POSIX */ |
| |
| /* Mark as invalid, all open files on tree connections since they |
| were closed when session to server was lost */ |
| static void mark_open_files_invalid(struct cifsTconInfo *pTcon) |
| { |
| struct cifsFileInfo *open_file = NULL; |
| struct list_head *tmp; |
| struct list_head *tmp1; |
| |
| /* list all files open on tree connection and mark them invalid */ |
| write_lock(&GlobalSMBSeslock); |
| list_for_each_safe(tmp, tmp1, &pTcon->openFileList) { |
| open_file = list_entry(tmp, struct cifsFileInfo, tlist); |
| open_file->invalidHandle = true; |
| open_file->oplock_break_cancelled = true; |
| } |
| write_unlock(&GlobalSMBSeslock); |
| /* BB Add call to invalidate_inodes(sb) for all superblocks mounted |
| to this tcon */ |
| } |
| |
| /* reconnect the socket, tcon, and smb session if needed */ |
| static int |
| cifs_reconnect_tcon(struct cifsTconInfo *tcon, int smb_command) |
| { |
| int rc = 0; |
| struct cifsSesInfo *ses; |
| struct TCP_Server_Info *server; |
| struct nls_table *nls_codepage; |
| |
| /* |
| * SMBs NegProt, SessSetup, uLogoff do not have tcon yet so check for |
| * tcp and smb session status done differently for those three - in the |
| * calling routine |
| */ |
| if (!tcon) |
| return 0; |
| |
| ses = tcon->ses; |
| server = ses->server; |
| |
| /* |
| * only tree disconnect, open, and write, (and ulogoff which does not |
| * have tcon) are allowed as we start force umount |
| */ |
| if (tcon->tidStatus == CifsExiting) { |
| if (smb_command != SMB_COM_WRITE_ANDX && |
| smb_command != SMB_COM_OPEN_ANDX && |
| smb_command != SMB_COM_TREE_DISCONNECT) { |
| cFYI(1, ("can not send cmd %d while umounting", |
| smb_command)); |
| return -ENODEV; |
| } |
| } |
| |
| if (ses->status == CifsExiting) |
| return -EIO; |
| |
| /* |
| * Give demultiplex thread up to 10 seconds to reconnect, should be |
| * greater than cifs socket timeout which is 7 seconds |
| */ |
| while (server->tcpStatus == CifsNeedReconnect) { |
| wait_event_interruptible_timeout(server->response_q, |
| (server->tcpStatus == CifsGood), 10 * HZ); |
| |
| /* is TCP session is reestablished now ?*/ |
| if (server->tcpStatus != CifsNeedReconnect) |
| break; |
| |
| /* |
| * on "soft" mounts we wait once. Hard mounts keep |
| * retrying until process is killed or server comes |
| * back on-line |
| */ |
| if (!tcon->retry || ses->status == CifsExiting) { |
| cFYI(1, ("gave up waiting on reconnect in smb_init")); |
| return -EHOSTDOWN; |
| } |
| } |
| |
| if (!ses->need_reconnect && !tcon->need_reconnect) |
| return 0; |
| |
| nls_codepage = load_nls_default(); |
| |
| /* |
| * need to prevent multiple threads trying to simultaneously |
| * reconnect the same SMB session |
| */ |
| down(&ses->sesSem); |
| if (ses->need_reconnect) |
| rc = cifs_setup_session(0, ses, nls_codepage); |
| |
| /* do we need to reconnect tcon? */ |
| if (rc || !tcon->need_reconnect) { |
| up(&ses->sesSem); |
| goto out; |
| } |
| |
| mark_open_files_invalid(tcon); |
| rc = CIFSTCon(0, ses, tcon->treeName, tcon, nls_codepage); |
| up(&ses->sesSem); |
| cFYI(1, ("reconnect tcon rc = %d", rc)); |
| |
| if (rc) |
| goto out; |
| |
| /* |
| * FIXME: check if wsize needs updated due to negotiated smb buffer |
| * size shrinking |
| */ |
| atomic_inc(&tconInfoReconnectCount); |
| |
| /* tell server Unix caps we support */ |
| if (ses->capabilities & CAP_UNIX) |
| reset_cifs_unix_caps(0, tcon, NULL, NULL); |
| |
| /* |
| * Removed call to reopen open files here. It is safer (and faster) to |
| * reopen files one at a time as needed in read and write. |
| * |
| * FIXME: what about file locks? don't we need to reclaim them ASAP? |
| */ |
| |
| out: |
| /* |
| * Check if handle based operation so we know whether we can continue |
| * or not without returning to caller to reset file handle |
| */ |
| switch (smb_command) { |
| case SMB_COM_READ_ANDX: |
| case SMB_COM_WRITE_ANDX: |
| case SMB_COM_CLOSE: |
| case SMB_COM_FIND_CLOSE2: |
| case SMB_COM_LOCKING_ANDX: |
| rc = -EAGAIN; |
| } |
| |
| unload_nls(nls_codepage); |
| return rc; |
| } |
| |
| /* Allocate and return pointer to an SMB request buffer, and set basic |
| SMB information in the SMB header. If the return code is zero, this |
| function must have filled in request_buf pointer */ |
| static int |
| small_smb_init(int smb_command, int wct, struct cifsTconInfo *tcon, |
| void **request_buf) |
| { |
| int rc = 0; |
| |
| rc = cifs_reconnect_tcon(tcon, smb_command); |
| if (rc) |
| return rc; |
| |
| *request_buf = cifs_small_buf_get(); |
| if (*request_buf == NULL) { |
| /* BB should we add a retry in here if not a writepage? */ |
| return -ENOMEM; |
| } |
| |
| header_assemble((struct smb_hdr *) *request_buf, smb_command, |
| tcon, wct); |
| |
| if (tcon != NULL) |
| cifs_stats_inc(&tcon->num_smbs_sent); |
| |
| return rc; |
| } |
| |
| int |
| small_smb_init_no_tc(const int smb_command, const int wct, |
| struct cifsSesInfo *ses, void **request_buf) |
| { |
| int rc; |
| struct smb_hdr *buffer; |
| |
| rc = small_smb_init(smb_command, wct, NULL, request_buf); |
| if (rc) |
| return rc; |
| |
| buffer = (struct smb_hdr *)*request_buf; |
| buffer->Mid = GetNextMid(ses->server); |
| if (ses->capabilities & CAP_UNICODE) |
| buffer->Flags2 |= SMBFLG2_UNICODE; |
| if (ses->capabilities & CAP_STATUS32) |
| buffer->Flags2 |= SMBFLG2_ERR_STATUS; |
| |
| /* uid, tid can stay at zero as set in header assemble */ |
| |
| /* BB add support for turning on the signing when |
| this function is used after 1st of session setup requests */ |
| |
| return rc; |
| } |
| |
| /* If the return code is zero, this function must fill in request_buf pointer */ |
| static int |
| smb_init(int smb_command, int wct, struct cifsTconInfo *tcon, |
| void **request_buf /* returned */ , |
| void **response_buf /* returned */ ) |
| { |
| int rc = 0; |
| |
| rc = cifs_reconnect_tcon(tcon, smb_command); |
| if (rc) |
| return rc; |
| |
| *request_buf = cifs_buf_get(); |
| if (*request_buf == NULL) { |
| /* BB should we add a retry in here if not a writepage? */ |
| return -ENOMEM; |
| } |
| /* Although the original thought was we needed the response buf for */ |
| /* potential retries of smb operations it turns out we can determine */ |
| /* from the mid flags when the request buffer can be resent without */ |
| /* having to use a second distinct buffer for the response */ |
| if (response_buf) |
| *response_buf = *request_buf; |
| |
| header_assemble((struct smb_hdr *) *request_buf, smb_command, tcon, |
| wct); |
| |
| if (tcon != NULL) |
| cifs_stats_inc(&tcon->num_smbs_sent); |
| |
| return rc; |
| } |
| |
| static int validate_t2(struct smb_t2_rsp *pSMB) |
| { |
| int rc = -EINVAL; |
| int total_size; |
| char *pBCC; |
| |
| /* check for plausible wct, bcc and t2 data and parm sizes */ |
| /* check for parm and data offset going beyond end of smb */ |
| if (pSMB->hdr.WordCount >= 10) { |
| if ((le16_to_cpu(pSMB->t2_rsp.ParameterOffset) <= 1024) && |
| (le16_to_cpu(pSMB->t2_rsp.DataOffset) <= 1024)) { |
| /* check that bcc is at least as big as parms + data */ |
| /* check that bcc is less than negotiated smb buffer */ |
| total_size = le16_to_cpu(pSMB->t2_rsp.ParameterCount); |
| if (total_size < 512) { |
| total_size += |
| le16_to_cpu(pSMB->t2_rsp.DataCount); |
| /* BCC le converted in SendReceive */ |
| pBCC = (pSMB->hdr.WordCount * 2) + |
| sizeof(struct smb_hdr) + |
| (char *)pSMB; |
| if ((total_size <= (*(u16 *)pBCC)) && |
| (total_size < |
| CIFSMaxBufSize+MAX_CIFS_HDR_SIZE)) { |
| return 0; |
| } |
| } |
| } |
| } |
| cifs_dump_mem("Invalid transact2 SMB: ", (char *)pSMB, |
| sizeof(struct smb_t2_rsp) + 16); |
| return rc; |
| } |
| int |
| CIFSSMBNegotiate(unsigned int xid, struct cifsSesInfo *ses) |
| { |
| NEGOTIATE_REQ *pSMB; |
| NEGOTIATE_RSP *pSMBr; |
| int rc = 0; |
| int bytes_returned; |
| int i; |
| struct TCP_Server_Info *server; |
| u16 count; |
| unsigned int secFlags; |
| u16 dialect; |
| |
| if (ses->server) |
| server = ses->server; |
| else { |
| rc = -EIO; |
| return rc; |
| } |
| rc = smb_init(SMB_COM_NEGOTIATE, 0, NULL /* no tcon yet */ , |
| (void **) &pSMB, (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| /* if any of auth flags (ie not sign or seal) are overriden use them */ |
| if (ses->overrideSecFlg & (~(CIFSSEC_MUST_SIGN | CIFSSEC_MUST_SEAL))) |
| secFlags = ses->overrideSecFlg; /* BB FIXME fix sign flags? */ |
| else /* if override flags set only sign/seal OR them with global auth */ |
| secFlags = extended_security | ses->overrideSecFlg; |
| |
| cFYI(1, ("secFlags 0x%x", secFlags)); |
| |
| pSMB->hdr.Mid = GetNextMid(server); |
| pSMB->hdr.Flags2 |= (SMBFLG2_UNICODE | SMBFLG2_ERR_STATUS); |
| |
| if ((secFlags & CIFSSEC_MUST_KRB5) == CIFSSEC_MUST_KRB5) |
| pSMB->hdr.Flags2 |= SMBFLG2_EXT_SEC; |
| else if ((secFlags & CIFSSEC_AUTH_MASK) == CIFSSEC_MAY_KRB5) { |
| cFYI(1, ("Kerberos only mechanism, enable extended security")); |
| pSMB->hdr.Flags2 |= SMBFLG2_EXT_SEC; |
| } |
| #ifdef CONFIG_CIFS_EXPERIMENTAL |
| else if ((secFlags & CIFSSEC_MUST_NTLMSSP) == CIFSSEC_MUST_NTLMSSP) |
| pSMB->hdr.Flags2 |= SMBFLG2_EXT_SEC; |
| else if ((secFlags & CIFSSEC_AUTH_MASK) == CIFSSEC_MAY_NTLMSSP) { |
| cFYI(1, ("NTLMSSP only mechanism, enable extended security")); |
| pSMB->hdr.Flags2 |= SMBFLG2_EXT_SEC; |
| } |
| #endif |
| |
| count = 0; |
| for (i = 0; i < CIFS_NUM_PROT; i++) { |
| strncpy(pSMB->DialectsArray+count, protocols[i].name, 16); |
| count += strlen(protocols[i].name) + 1; |
| /* null at end of source and target buffers anyway */ |
| } |
| pSMB->hdr.smb_buf_length += count; |
| pSMB->ByteCount = cpu_to_le16(count); |
| |
| rc = SendReceive(xid, ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| if (rc != 0) |
| goto neg_err_exit; |
| |
| dialect = le16_to_cpu(pSMBr->DialectIndex); |
| cFYI(1, ("Dialect: %d", dialect)); |
| /* Check wct = 1 error case */ |
| if ((pSMBr->hdr.WordCount < 13) || (dialect == BAD_PROT)) { |
| /* core returns wct = 1, but we do not ask for core - otherwise |
| small wct just comes when dialect index is -1 indicating we |
| could not negotiate a common dialect */ |
| rc = -EOPNOTSUPP; |
| goto neg_err_exit; |
| #ifdef CONFIG_CIFS_WEAK_PW_HASH |
| } else if ((pSMBr->hdr.WordCount == 13) |
| && ((dialect == LANMAN_PROT) |
| || (dialect == LANMAN2_PROT))) { |
| __s16 tmp; |
| struct lanman_neg_rsp *rsp = (struct lanman_neg_rsp *)pSMBr; |
| |
| if ((secFlags & CIFSSEC_MAY_LANMAN) || |
| (secFlags & CIFSSEC_MAY_PLNTXT)) |
| server->secType = LANMAN; |
| else { |
| cERROR(1, ("mount failed weak security disabled" |
| " in /proc/fs/cifs/SecurityFlags")); |
| rc = -EOPNOTSUPP; |
| goto neg_err_exit; |
| } |
| server->secMode = (__u8)le16_to_cpu(rsp->SecurityMode); |
| server->maxReq = le16_to_cpu(rsp->MaxMpxCount); |
| server->maxBuf = min((__u32)le16_to_cpu(rsp->MaxBufSize), |
| (__u32)CIFSMaxBufSize + MAX_CIFS_HDR_SIZE); |
| server->max_vcs = le16_to_cpu(rsp->MaxNumberVcs); |
| GETU32(server->sessid) = le32_to_cpu(rsp->SessionKey); |
| /* even though we do not use raw we might as well set this |
| accurately, in case we ever find a need for it */ |
| if ((le16_to_cpu(rsp->RawMode) & RAW_ENABLE) == RAW_ENABLE) { |
| server->max_rw = 0xFF00; |
| server->capabilities = CAP_MPX_MODE | CAP_RAW_MODE; |
| } else { |
| server->max_rw = 0;/* do not need to use raw anyway */ |
| server->capabilities = CAP_MPX_MODE; |
| } |
| tmp = (__s16)le16_to_cpu(rsp->ServerTimeZone); |
| if (tmp == -1) { |
| /* OS/2 often does not set timezone therefore |
| * we must use server time to calc time zone. |
| * Could deviate slightly from the right zone. |
| * Smallest defined timezone difference is 15 minutes |
| * (i.e. Nepal). Rounding up/down is done to match |
| * this requirement. |
| */ |
| int val, seconds, remain, result; |
| struct timespec ts, utc; |
| utc = CURRENT_TIME; |
| ts = cnvrtDosUnixTm(rsp->SrvTime.Date, |
| rsp->SrvTime.Time, 0); |
| cFYI(1, ("SrvTime %d sec since 1970 (utc: %d) diff: %d", |
| (int)ts.tv_sec, (int)utc.tv_sec, |
| (int)(utc.tv_sec - ts.tv_sec))); |
| val = (int)(utc.tv_sec - ts.tv_sec); |
| seconds = abs(val); |
| result = (seconds / MIN_TZ_ADJ) * MIN_TZ_ADJ; |
| remain = seconds % MIN_TZ_ADJ; |
| if (remain >= (MIN_TZ_ADJ / 2)) |
| result += MIN_TZ_ADJ; |
| if (val < 0) |
| result = -result; |
| server->timeAdj = result; |
| } else { |
| server->timeAdj = (int)tmp; |
| server->timeAdj *= 60; /* also in seconds */ |
| } |
| cFYI(1, ("server->timeAdj: %d seconds", server->timeAdj)); |
| |
| |
| /* BB get server time for time conversions and add |
| code to use it and timezone since this is not UTC */ |
| |
| if (rsp->EncryptionKeyLength == |
| cpu_to_le16(CIFS_CRYPTO_KEY_SIZE)) { |
| memcpy(server->cryptKey, rsp->EncryptionKey, |
| CIFS_CRYPTO_KEY_SIZE); |
| } else if (server->secMode & SECMODE_PW_ENCRYPT) { |
| rc = -EIO; /* need cryptkey unless plain text */ |
| goto neg_err_exit; |
| } |
| |
| cFYI(1, ("LANMAN negotiated")); |
| /* we will not end up setting signing flags - as no signing |
| was in LANMAN and server did not return the flags on */ |
| goto signing_check; |
| #else /* weak security disabled */ |
| } else if (pSMBr->hdr.WordCount == 13) { |
| cERROR(1, ("mount failed, cifs module not built " |
| "with CIFS_WEAK_PW_HASH support")); |
| rc = -EOPNOTSUPP; |
| #endif /* WEAK_PW_HASH */ |
| goto neg_err_exit; |
| } else if (pSMBr->hdr.WordCount != 17) { |
| /* unknown wct */ |
| rc = -EOPNOTSUPP; |
| goto neg_err_exit; |
| } |
| /* else wct == 17 NTLM */ |
| server->secMode = pSMBr->SecurityMode; |
| if ((server->secMode & SECMODE_USER) == 0) |
| cFYI(1, ("share mode security")); |
| |
| if ((server->secMode & SECMODE_PW_ENCRYPT) == 0) |
| #ifdef CONFIG_CIFS_WEAK_PW_HASH |
| if ((secFlags & CIFSSEC_MAY_PLNTXT) == 0) |
| #endif /* CIFS_WEAK_PW_HASH */ |
| cERROR(1, ("Server requests plain text password" |
| " but client support disabled")); |
| |
| if ((secFlags & CIFSSEC_MUST_NTLMV2) == CIFSSEC_MUST_NTLMV2) |
| server->secType = NTLMv2; |
| else if (secFlags & CIFSSEC_MAY_NTLM) |
| server->secType = NTLM; |
| else if (secFlags & CIFSSEC_MAY_NTLMV2) |
| server->secType = NTLMv2; |
| else if (secFlags & CIFSSEC_MAY_KRB5) |
| server->secType = Kerberos; |
| else if (secFlags & CIFSSEC_MAY_NTLMSSP) |
| server->secType = RawNTLMSSP; |
| else if (secFlags & CIFSSEC_MAY_LANMAN) |
| server->secType = LANMAN; |
| /* #ifdef CONFIG_CIFS_EXPERIMENTAL |
| else if (secFlags & CIFSSEC_MAY_PLNTXT) |
| server->secType = ?? |
| #endif */ |
| else { |
| rc = -EOPNOTSUPP; |
| cERROR(1, ("Invalid security type")); |
| goto neg_err_exit; |
| } |
| /* else ... any others ...? */ |
| |
| /* one byte, so no need to convert this or EncryptionKeyLen from |
| little endian */ |
| server->maxReq = le16_to_cpu(pSMBr->MaxMpxCount); |
| /* probably no need to store and check maxvcs */ |
| server->maxBuf = min(le32_to_cpu(pSMBr->MaxBufferSize), |
| (__u32) CIFSMaxBufSize + MAX_CIFS_HDR_SIZE); |
| server->max_rw = le32_to_cpu(pSMBr->MaxRawSize); |
| cFYI(DBG2, ("Max buf = %d", ses->server->maxBuf)); |
| GETU32(ses->server->sessid) = le32_to_cpu(pSMBr->SessionKey); |
| server->capabilities = le32_to_cpu(pSMBr->Capabilities); |
| server->timeAdj = (int)(__s16)le16_to_cpu(pSMBr->ServerTimeZone); |
| server->timeAdj *= 60; |
| if (pSMBr->EncryptionKeyLength == CIFS_CRYPTO_KEY_SIZE) { |
| memcpy(server->cryptKey, pSMBr->u.EncryptionKey, |
| CIFS_CRYPTO_KEY_SIZE); |
| } else if ((pSMBr->hdr.Flags2 & SMBFLG2_EXT_SEC) |
| && (pSMBr->EncryptionKeyLength == 0)) { |
| /* decode security blob */ |
| } else if (server->secMode & SECMODE_PW_ENCRYPT) { |
| rc = -EIO; /* no crypt key only if plain text pwd */ |
| goto neg_err_exit; |
| } |
| |
| /* BB might be helpful to save off the domain of server here */ |
| |
| if ((pSMBr->hdr.Flags2 & SMBFLG2_EXT_SEC) && |
| (server->capabilities & CAP_EXTENDED_SECURITY)) { |
| count = pSMBr->ByteCount; |
| if (count < 16) { |
| rc = -EIO; |
| goto neg_err_exit; |
| } |
| read_lock(&cifs_tcp_ses_lock); |
| if (server->srv_count > 1) { |
| read_unlock(&cifs_tcp_ses_lock); |
| if (memcmp(server->server_GUID, |
| pSMBr->u.extended_response. |
| GUID, 16) != 0) { |
| cFYI(1, ("server UID changed")); |
| memcpy(server->server_GUID, |
| pSMBr->u.extended_response.GUID, |
| 16); |
| } |
| } else { |
| read_unlock(&cifs_tcp_ses_lock); |
| memcpy(server->server_GUID, |
| pSMBr->u.extended_response.GUID, 16); |
| } |
| |
| if (count == 16) { |
| server->secType = RawNTLMSSP; |
| } else { |
| rc = decode_negTokenInit(pSMBr->u.extended_response. |
| SecurityBlob, |
| count - 16, |
| &server->secType); |
| if (rc == 1) |
| rc = 0; |
| else |
| rc = -EINVAL; |
| } |
| } else |
| server->capabilities &= ~CAP_EXTENDED_SECURITY; |
| |
| #ifdef CONFIG_CIFS_WEAK_PW_HASH |
| signing_check: |
| #endif |
| if ((secFlags & CIFSSEC_MAY_SIGN) == 0) { |
| /* MUST_SIGN already includes the MAY_SIGN FLAG |
| so if this is zero it means that signing is disabled */ |
| cFYI(1, ("Signing disabled")); |
| if (server->secMode & SECMODE_SIGN_REQUIRED) { |
| cERROR(1, ("Server requires " |
| "packet signing to be enabled in " |
| "/proc/fs/cifs/SecurityFlags.")); |
| rc = -EOPNOTSUPP; |
| } |
| server->secMode &= |
| ~(SECMODE_SIGN_ENABLED | SECMODE_SIGN_REQUIRED); |
| } else if ((secFlags & CIFSSEC_MUST_SIGN) == CIFSSEC_MUST_SIGN) { |
| /* signing required */ |
| cFYI(1, ("Must sign - secFlags 0x%x", secFlags)); |
| if ((server->secMode & |
| (SECMODE_SIGN_ENABLED | SECMODE_SIGN_REQUIRED)) == 0) { |
| cERROR(1, |
| ("signing required but server lacks support")); |
| rc = -EOPNOTSUPP; |
| } else |
| server->secMode |= SECMODE_SIGN_REQUIRED; |
| } else { |
| /* signing optional ie CIFSSEC_MAY_SIGN */ |
| if ((server->secMode & SECMODE_SIGN_REQUIRED) == 0) |
| server->secMode &= |
| ~(SECMODE_SIGN_ENABLED | SECMODE_SIGN_REQUIRED); |
| } |
| |
| neg_err_exit: |
| cifs_buf_release(pSMB); |
| |
| cFYI(1, ("negprot rc %d", rc)); |
| return rc; |
| } |
| |
| int |
| CIFSSMBTDis(const int xid, struct cifsTconInfo *tcon) |
| { |
| struct smb_hdr *smb_buffer; |
| int rc = 0; |
| |
| cFYI(1, ("In tree disconnect")); |
| |
| /* BB: do we need to check this? These should never be NULL. */ |
| if ((tcon->ses == NULL) || (tcon->ses->server == NULL)) |
| return -EIO; |
| |
| /* |
| * No need to return error on this operation if tid invalidated and |
| * closed on server already e.g. due to tcp session crashing. Also, |
| * the tcon is no longer on the list, so no need to take lock before |
| * checking this. |
| */ |
| if ((tcon->need_reconnect) || (tcon->ses->need_reconnect)) |
| return 0; |
| |
| rc = small_smb_init(SMB_COM_TREE_DISCONNECT, 0, tcon, |
| (void **)&smb_buffer); |
| if (rc) |
| return rc; |
| |
| rc = SendReceiveNoRsp(xid, tcon->ses, smb_buffer, 0); |
| if (rc) |
| cFYI(1, ("Tree disconnect failed %d", rc)); |
| |
| /* No need to return error on this operation if tid invalidated and |
| closed on server already e.g. due to tcp session crashing */ |
| if (rc == -EAGAIN) |
| rc = 0; |
| |
| return rc; |
| } |
| |
| int |
| CIFSSMBLogoff(const int xid, struct cifsSesInfo *ses) |
| { |
| LOGOFF_ANDX_REQ *pSMB; |
| int rc = 0; |
| |
| cFYI(1, ("In SMBLogoff for session disconnect")); |
| |
| /* |
| * BB: do we need to check validity of ses and server? They should |
| * always be valid since we have an active reference. If not, that |
| * should probably be a BUG() |
| */ |
| if (!ses || !ses->server) |
| return -EIO; |
| |
| down(&ses->sesSem); |
| if (ses->need_reconnect) |
| goto session_already_dead; /* no need to send SMBlogoff if uid |
| already closed due to reconnect */ |
| rc = small_smb_init(SMB_COM_LOGOFF_ANDX, 2, NULL, (void **)&pSMB); |
| if (rc) { |
| up(&ses->sesSem); |
| return rc; |
| } |
| |
| pSMB->hdr.Mid = GetNextMid(ses->server); |
| |
| if (ses->server->secMode & |
| (SECMODE_SIGN_REQUIRED | SECMODE_SIGN_ENABLED)) |
| pSMB->hdr.Flags2 |= SMBFLG2_SECURITY_SIGNATURE; |
| |
| pSMB->hdr.Uid = ses->Suid; |
| |
| pSMB->AndXCommand = 0xFF; |
| rc = SendReceiveNoRsp(xid, ses, (struct smb_hdr *) pSMB, 0); |
| session_already_dead: |
| up(&ses->sesSem); |
| |
| /* if session dead then we do not need to do ulogoff, |
| since server closed smb session, no sense reporting |
| error */ |
| if (rc == -EAGAIN) |
| rc = 0; |
| return rc; |
| } |
| |
| int |
| CIFSPOSIXDelFile(const int xid, struct cifsTconInfo *tcon, const char *fileName, |
| __u16 type, const struct nls_table *nls_codepage, int remap) |
| { |
| TRANSACTION2_SPI_REQ *pSMB = NULL; |
| TRANSACTION2_SPI_RSP *pSMBr = NULL; |
| struct unlink_psx_rq *pRqD; |
| int name_len; |
| int rc = 0; |
| int bytes_returned = 0; |
| __u16 params, param_offset, offset, byte_count; |
| |
| cFYI(1, ("In POSIX delete")); |
| PsxDelete: |
| rc = smb_init(SMB_COM_TRANSACTION2, 15, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = |
| cifsConvertToUCS((__le16 *) pSMB->FileName, fileName, |
| PATH_MAX, nls_codepage, remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| } else { /* BB add path length overrun check */ |
| name_len = strnlen(fileName, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->FileName, fileName, name_len); |
| } |
| |
| params = 6 + name_len; |
| pSMB->MaxParameterCount = cpu_to_le16(2); |
| pSMB->MaxDataCount = 0; /* BB double check this with jra */ |
| pSMB->MaxSetupCount = 0; |
| pSMB->Reserved = 0; |
| pSMB->Flags = 0; |
| pSMB->Timeout = 0; |
| pSMB->Reserved2 = 0; |
| param_offset = offsetof(struct smb_com_transaction2_spi_req, |
| InformationLevel) - 4; |
| offset = param_offset + params; |
| |
| /* Setup pointer to Request Data (inode type) */ |
| pRqD = (struct unlink_psx_rq *)(((char *)&pSMB->hdr.Protocol) + offset); |
| pRqD->type = cpu_to_le16(type); |
| pSMB->ParameterOffset = cpu_to_le16(param_offset); |
| pSMB->DataOffset = cpu_to_le16(offset); |
| pSMB->SetupCount = 1; |
| pSMB->Reserved3 = 0; |
| pSMB->SubCommand = cpu_to_le16(TRANS2_SET_PATH_INFORMATION); |
| byte_count = 3 /* pad */ + params + sizeof(struct unlink_psx_rq); |
| |
| pSMB->DataCount = cpu_to_le16(sizeof(struct unlink_psx_rq)); |
| pSMB->TotalDataCount = cpu_to_le16(sizeof(struct unlink_psx_rq)); |
| pSMB->ParameterCount = cpu_to_le16(params); |
| pSMB->TotalParameterCount = pSMB->ParameterCount; |
| pSMB->InformationLevel = cpu_to_le16(SMB_POSIX_UNLINK); |
| pSMB->Reserved4 = 0; |
| pSMB->hdr.smb_buf_length += byte_count; |
| pSMB->ByteCount = cpu_to_le16(byte_count); |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| if (rc) |
| cFYI(1, ("Posix delete returned %d", rc)); |
| cifs_buf_release(pSMB); |
| |
| cifs_stats_inc(&tcon->num_deletes); |
| |
| if (rc == -EAGAIN) |
| goto PsxDelete; |
| |
| return rc; |
| } |
| |
| int |
| CIFSSMBDelFile(const int xid, struct cifsTconInfo *tcon, const char *fileName, |
| const struct nls_table *nls_codepage, int remap) |
| { |
| DELETE_FILE_REQ *pSMB = NULL; |
| DELETE_FILE_RSP *pSMBr = NULL; |
| int rc = 0; |
| int bytes_returned; |
| int name_len; |
| |
| DelFileRetry: |
| rc = smb_init(SMB_COM_DELETE, 1, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = |
| cifsConvertToUCS((__le16 *) pSMB->fileName, fileName, |
| PATH_MAX, nls_codepage, remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| } else { /* BB improve check for buffer overruns BB */ |
| name_len = strnlen(fileName, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->fileName, fileName, name_len); |
| } |
| pSMB->SearchAttributes = |
| cpu_to_le16(ATTR_READONLY | ATTR_HIDDEN | ATTR_SYSTEM); |
| pSMB->BufferFormat = 0x04; |
| pSMB->hdr.smb_buf_length += name_len + 1; |
| pSMB->ByteCount = cpu_to_le16(name_len + 1); |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| cifs_stats_inc(&tcon->num_deletes); |
| if (rc) |
| cFYI(1, ("Error in RMFile = %d", rc)); |
| |
| cifs_buf_release(pSMB); |
| if (rc == -EAGAIN) |
| goto DelFileRetry; |
| |
| return rc; |
| } |
| |
| int |
| CIFSSMBRmDir(const int xid, struct cifsTconInfo *tcon, const char *dirName, |
| const struct nls_table *nls_codepage, int remap) |
| { |
| DELETE_DIRECTORY_REQ *pSMB = NULL; |
| DELETE_DIRECTORY_RSP *pSMBr = NULL; |
| int rc = 0; |
| int bytes_returned; |
| int name_len; |
| |
| cFYI(1, ("In CIFSSMBRmDir")); |
| RmDirRetry: |
| rc = smb_init(SMB_COM_DELETE_DIRECTORY, 0, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = cifsConvertToUCS((__le16 *) pSMB->DirName, dirName, |
| PATH_MAX, nls_codepage, remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| } else { /* BB improve check for buffer overruns BB */ |
| name_len = strnlen(dirName, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->DirName, dirName, name_len); |
| } |
| |
| pSMB->BufferFormat = 0x04; |
| pSMB->hdr.smb_buf_length += name_len + 1; |
| pSMB->ByteCount = cpu_to_le16(name_len + 1); |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| cifs_stats_inc(&tcon->num_rmdirs); |
| if (rc) |
| cFYI(1, ("Error in RMDir = %d", rc)); |
| |
| cifs_buf_release(pSMB); |
| if (rc == -EAGAIN) |
| goto RmDirRetry; |
| return rc; |
| } |
| |
| int |
| CIFSSMBMkDir(const int xid, struct cifsTconInfo *tcon, |
| const char *name, const struct nls_table *nls_codepage, int remap) |
| { |
| int rc = 0; |
| CREATE_DIRECTORY_REQ *pSMB = NULL; |
| CREATE_DIRECTORY_RSP *pSMBr = NULL; |
| int bytes_returned; |
| int name_len; |
| |
| cFYI(1, ("In CIFSSMBMkDir")); |
| MkDirRetry: |
| rc = smb_init(SMB_COM_CREATE_DIRECTORY, 0, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = cifsConvertToUCS((__le16 *) pSMB->DirName, name, |
| PATH_MAX, nls_codepage, remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| } else { /* BB improve check for buffer overruns BB */ |
| name_len = strnlen(name, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->DirName, name, name_len); |
| } |
| |
| pSMB->BufferFormat = 0x04; |
| pSMB->hdr.smb_buf_length += name_len + 1; |
| pSMB->ByteCount = cpu_to_le16(name_len + 1); |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| cifs_stats_inc(&tcon->num_mkdirs); |
| if (rc) |
| cFYI(1, ("Error in Mkdir = %d", rc)); |
| |
| cifs_buf_release(pSMB); |
| if (rc == -EAGAIN) |
| goto MkDirRetry; |
| return rc; |
| } |
| |
| int |
| CIFSPOSIXCreate(const int xid, struct cifsTconInfo *tcon, __u32 posix_flags, |
| __u64 mode, __u16 *netfid, FILE_UNIX_BASIC_INFO *pRetData, |
| __u32 *pOplock, const char *name, |
| const struct nls_table *nls_codepage, int remap) |
| { |
| TRANSACTION2_SPI_REQ *pSMB = NULL; |
| TRANSACTION2_SPI_RSP *pSMBr = NULL; |
| int name_len; |
| int rc = 0; |
| int bytes_returned = 0; |
| __u16 params, param_offset, offset, byte_count, count; |
| OPEN_PSX_REQ *pdata; |
| OPEN_PSX_RSP *psx_rsp; |
| |
| cFYI(1, ("In POSIX Create")); |
| PsxCreat: |
| rc = smb_init(SMB_COM_TRANSACTION2, 15, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = |
| cifsConvertToUCS((__le16 *) pSMB->FileName, name, |
| PATH_MAX, nls_codepage, remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| } else { /* BB improve the check for buffer overruns BB */ |
| name_len = strnlen(name, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->FileName, name, name_len); |
| } |
| |
| params = 6 + name_len; |
| count = sizeof(OPEN_PSX_REQ); |
| pSMB->MaxParameterCount = cpu_to_le16(2); |
| pSMB->MaxDataCount = cpu_to_le16(1000); /* large enough */ |
| pSMB->MaxSetupCount = 0; |
| pSMB->Reserved = 0; |
| pSMB->Flags = 0; |
| pSMB->Timeout = 0; |
| pSMB->Reserved2 = 0; |
| param_offset = offsetof(struct smb_com_transaction2_spi_req, |
| InformationLevel) - 4; |
| offset = param_offset + params; |
| pdata = (OPEN_PSX_REQ *)(((char *)&pSMB->hdr.Protocol) + offset); |
| pdata->Level = cpu_to_le16(SMB_QUERY_FILE_UNIX_BASIC); |
| pdata->Permissions = cpu_to_le64(mode); |
| pdata->PosixOpenFlags = cpu_to_le32(posix_flags); |
| pdata->OpenFlags = cpu_to_le32(*pOplock); |
| pSMB->ParameterOffset = cpu_to_le16(param_offset); |
| pSMB->DataOffset = cpu_to_le16(offset); |
| pSMB->SetupCount = 1; |
| pSMB->Reserved3 = 0; |
| pSMB->SubCommand = cpu_to_le16(TRANS2_SET_PATH_INFORMATION); |
| byte_count = 3 /* pad */ + params + count; |
| |
| pSMB->DataCount = cpu_to_le16(count); |
| pSMB->ParameterCount = cpu_to_le16(params); |
| pSMB->TotalDataCount = pSMB->DataCount; |
| pSMB->TotalParameterCount = pSMB->ParameterCount; |
| pSMB->InformationLevel = cpu_to_le16(SMB_POSIX_OPEN); |
| pSMB->Reserved4 = 0; |
| pSMB->hdr.smb_buf_length += byte_count; |
| pSMB->ByteCount = cpu_to_le16(byte_count); |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| if (rc) { |
| cFYI(1, ("Posix create returned %d", rc)); |
| goto psx_create_err; |
| } |
| |
| cFYI(1, ("copying inode info")); |
| rc = validate_t2((struct smb_t2_rsp *)pSMBr); |
| |
| if (rc || (pSMBr->ByteCount < sizeof(OPEN_PSX_RSP))) { |
| rc = -EIO; /* bad smb */ |
| goto psx_create_err; |
| } |
| |
| /* copy return information to pRetData */ |
| psx_rsp = (OPEN_PSX_RSP *)((char *) &pSMBr->hdr.Protocol |
| + le16_to_cpu(pSMBr->t2.DataOffset)); |
| |
| *pOplock = le16_to_cpu(psx_rsp->OplockFlags); |
| if (netfid) |
| *netfid = psx_rsp->Fid; /* cifs fid stays in le */ |
| /* Let caller know file was created so we can set the mode. */ |
| /* Do we care about the CreateAction in any other cases? */ |
| if (cpu_to_le32(FILE_CREATE) == psx_rsp->CreateAction) |
| *pOplock |= CIFS_CREATE_ACTION; |
| /* check to make sure response data is there */ |
| if (psx_rsp->ReturnedLevel != cpu_to_le16(SMB_QUERY_FILE_UNIX_BASIC)) { |
| pRetData->Type = cpu_to_le32(-1); /* unknown */ |
| cFYI(DBG2, ("unknown type")); |
| } else { |
| if (pSMBr->ByteCount < sizeof(OPEN_PSX_RSP) |
| + sizeof(FILE_UNIX_BASIC_INFO)) { |
| cERROR(1, ("Open response data too small")); |
| pRetData->Type = cpu_to_le32(-1); |
| goto psx_create_err; |
| } |
| memcpy((char *) pRetData, |
| (char *)psx_rsp + sizeof(OPEN_PSX_RSP), |
| sizeof(FILE_UNIX_BASIC_INFO)); |
| } |
| |
| psx_create_err: |
| cifs_buf_release(pSMB); |
| |
| if (posix_flags & SMB_O_DIRECTORY) |
| cifs_stats_inc(&tcon->num_posixmkdirs); |
| else |
| cifs_stats_inc(&tcon->num_posixopens); |
| |
| if (rc == -EAGAIN) |
| goto PsxCreat; |
| |
| return rc; |
| } |
| |
| static __u16 convert_disposition(int disposition) |
| { |
| __u16 ofun = 0; |
| |
| switch (disposition) { |
| case FILE_SUPERSEDE: |
| ofun = SMBOPEN_OCREATE | SMBOPEN_OTRUNC; |
| break; |
| case FILE_OPEN: |
| ofun = SMBOPEN_OAPPEND; |
| break; |
| case FILE_CREATE: |
| ofun = SMBOPEN_OCREATE; |
| break; |
| case FILE_OPEN_IF: |
| ofun = SMBOPEN_OCREATE | SMBOPEN_OAPPEND; |
| break; |
| case FILE_OVERWRITE: |
| ofun = SMBOPEN_OTRUNC; |
| break; |
| case FILE_OVERWRITE_IF: |
| ofun = SMBOPEN_OCREATE | SMBOPEN_OTRUNC; |
| break; |
| default: |
| cFYI(1, ("unknown disposition %d", disposition)); |
| ofun = SMBOPEN_OAPPEND; /* regular open */ |
| } |
| return ofun; |
| } |
| |
| static int |
| access_flags_to_smbopen_mode(const int access_flags) |
| { |
| int masked_flags = access_flags & (GENERIC_READ | GENERIC_WRITE); |
| |
| if (masked_flags == GENERIC_READ) |
| return SMBOPEN_READ; |
| else if (masked_flags == GENERIC_WRITE) |
| return SMBOPEN_WRITE; |
| |
| /* just go for read/write */ |
| return SMBOPEN_READWRITE; |
| } |
| |
| int |
| SMBLegacyOpen(const int xid, struct cifsTconInfo *tcon, |
| const char *fileName, const int openDisposition, |
| const int access_flags, const int create_options, __u16 *netfid, |
| int *pOplock, FILE_ALL_INFO *pfile_info, |
| const struct nls_table *nls_codepage, int remap) |
| { |
| int rc = -EACCES; |
| OPENX_REQ *pSMB = NULL; |
| OPENX_RSP *pSMBr = NULL; |
| int bytes_returned; |
| int name_len; |
| __u16 count; |
| |
| OldOpenRetry: |
| rc = smb_init(SMB_COM_OPEN_ANDX, 15, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| pSMB->AndXCommand = 0xFF; /* none */ |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| count = 1; /* account for one byte pad to word boundary */ |
| name_len = |
| cifsConvertToUCS((__le16 *) (pSMB->fileName + 1), |
| fileName, PATH_MAX, nls_codepage, remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| } else { /* BB improve check for buffer overruns BB */ |
| count = 0; /* no pad */ |
| name_len = strnlen(fileName, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->fileName, fileName, name_len); |
| } |
| if (*pOplock & REQ_OPLOCK) |
| pSMB->OpenFlags = cpu_to_le16(REQ_OPLOCK); |
| else if (*pOplock & REQ_BATCHOPLOCK) |
| pSMB->OpenFlags = cpu_to_le16(REQ_BATCHOPLOCK); |
| |
| pSMB->OpenFlags |= cpu_to_le16(REQ_MORE_INFO); |
| pSMB->Mode = cpu_to_le16(access_flags_to_smbopen_mode(access_flags)); |
| pSMB->Mode |= cpu_to_le16(0x40); /* deny none */ |
| /* set file as system file if special file such |
| as fifo and server expecting SFU style and |
| no Unix extensions */ |
| |
| if (create_options & CREATE_OPTION_SPECIAL) |
| pSMB->FileAttributes = cpu_to_le16(ATTR_SYSTEM); |
| else /* BB FIXME BB */ |
| pSMB->FileAttributes = cpu_to_le16(0/*ATTR_NORMAL*/); |
| |
| if (create_options & CREATE_OPTION_READONLY) |
| pSMB->FileAttributes |= cpu_to_le16(ATTR_READONLY); |
| |
| /* BB FIXME BB */ |
| /* pSMB->CreateOptions = cpu_to_le32(create_options & |
| CREATE_OPTIONS_MASK); */ |
| /* BB FIXME END BB */ |
| |
| pSMB->Sattr = cpu_to_le16(ATTR_HIDDEN | ATTR_SYSTEM | ATTR_DIRECTORY); |
| pSMB->OpenFunction = cpu_to_le16(convert_disposition(openDisposition)); |
| count += name_len; |
| pSMB->hdr.smb_buf_length += count; |
| |
| pSMB->ByteCount = cpu_to_le16(count); |
| /* long_op set to 1 to allow for oplock break timeouts */ |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *)pSMBr, &bytes_returned, CIFS_LONG_OP); |
| cifs_stats_inc(&tcon->num_opens); |
| if (rc) { |
| cFYI(1, ("Error in Open = %d", rc)); |
| } else { |
| /* BB verify if wct == 15 */ |
| |
| /* *pOplock = pSMBr->OplockLevel; */ /* BB take from action field*/ |
| |
| *netfid = pSMBr->Fid; /* cifs fid stays in le */ |
| /* Let caller know file was created so we can set the mode. */ |
| /* Do we care about the CreateAction in any other cases? */ |
| /* BB FIXME BB */ |
| /* if (cpu_to_le32(FILE_CREATE) == pSMBr->CreateAction) |
| *pOplock |= CIFS_CREATE_ACTION; */ |
| /* BB FIXME END */ |
| |
| if (pfile_info) { |
| pfile_info->CreationTime = 0; /* BB convert CreateTime*/ |
| pfile_info->LastAccessTime = 0; /* BB fixme */ |
| pfile_info->LastWriteTime = 0; /* BB fixme */ |
| pfile_info->ChangeTime = 0; /* BB fixme */ |
| pfile_info->Attributes = |
| cpu_to_le32(le16_to_cpu(pSMBr->FileAttributes)); |
| /* the file_info buf is endian converted by caller */ |
| pfile_info->AllocationSize = |
| cpu_to_le64(le32_to_cpu(pSMBr->EndOfFile)); |
| pfile_info->EndOfFile = pfile_info->AllocationSize; |
| pfile_info->NumberOfLinks = cpu_to_le32(1); |
| pfile_info->DeletePending = 0; |
| } |
| } |
| |
| cifs_buf_release(pSMB); |
| if (rc == -EAGAIN) |
| goto OldOpenRetry; |
| return rc; |
| } |
| |
| int |
| CIFSSMBOpen(const int xid, struct cifsTconInfo *tcon, |
| const char *fileName, const int openDisposition, |
| const int access_flags, const int create_options, __u16 *netfid, |
| int *pOplock, FILE_ALL_INFO *pfile_info, |
| const struct nls_table *nls_codepage, int remap) |
| { |
| int rc = -EACCES; |
| OPEN_REQ *pSMB = NULL; |
| OPEN_RSP *pSMBr = NULL; |
| int bytes_returned; |
| int name_len; |
| __u16 count; |
| |
| openRetry: |
| rc = smb_init(SMB_COM_NT_CREATE_ANDX, 24, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| pSMB->AndXCommand = 0xFF; /* none */ |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| count = 1; /* account for one byte pad to word boundary */ |
| name_len = |
| cifsConvertToUCS((__le16 *) (pSMB->fileName + 1), |
| fileName, PATH_MAX, nls_codepage, remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| pSMB->NameLength = cpu_to_le16(name_len); |
| } else { /* BB improve check for buffer overruns BB */ |
| count = 0; /* no pad */ |
| name_len = strnlen(fileName, PATH_MAX); |
| name_len++; /* trailing null */ |
| pSMB->NameLength = cpu_to_le16(name_len); |
| strncpy(pSMB->fileName, fileName, name_len); |
| } |
| if (*pOplock & REQ_OPLOCK) |
| pSMB->OpenFlags = cpu_to_le32(REQ_OPLOCK); |
| else if (*pOplock & REQ_BATCHOPLOCK) |
| pSMB->OpenFlags = cpu_to_le32(REQ_BATCHOPLOCK); |
| pSMB->DesiredAccess = cpu_to_le32(access_flags); |
| pSMB->AllocationSize = 0; |
| /* set file as system file if special file such |
| as fifo and server expecting SFU style and |
| no Unix extensions */ |
| if (create_options & CREATE_OPTION_SPECIAL) |
| pSMB->FileAttributes = cpu_to_le32(ATTR_SYSTEM); |
| else |
| pSMB->FileAttributes = cpu_to_le32(ATTR_NORMAL); |
| |
| /* XP does not handle ATTR_POSIX_SEMANTICS */ |
| /* but it helps speed up case sensitive checks for other |
| servers such as Samba */ |
| if (tcon->ses->capabilities & CAP_UNIX) |
| pSMB->FileAttributes |= cpu_to_le32(ATTR_POSIX_SEMANTICS); |
| |
| if (create_options & CREATE_OPTION_READONLY) |
| pSMB->FileAttributes |= cpu_to_le32(ATTR_READONLY); |
| |
| pSMB->ShareAccess = cpu_to_le32(FILE_SHARE_ALL); |
| pSMB->CreateDisposition = cpu_to_le32(openDisposition); |
| pSMB->CreateOptions = cpu_to_le32(create_options & CREATE_OPTIONS_MASK); |
| /* BB Expirement with various impersonation levels and verify */ |
| pSMB->ImpersonationLevel = cpu_to_le32(SECURITY_IMPERSONATION); |
| pSMB->SecurityFlags = |
| SECURITY_CONTEXT_TRACKING | SECURITY_EFFECTIVE_ONLY; |
| |
| count += name_len; |
| pSMB->hdr.smb_buf_length += count; |
| |
| pSMB->ByteCount = cpu_to_le16(count); |
| /* long_op set to 1 to allow for oplock break timeouts */ |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *)pSMBr, &bytes_returned, CIFS_LONG_OP); |
| cifs_stats_inc(&tcon->num_opens); |
| if (rc) { |
| cFYI(1, ("Error in Open = %d", rc)); |
| } else { |
| *pOplock = pSMBr->OplockLevel; /* 1 byte no need to le_to_cpu */ |
| *netfid = pSMBr->Fid; /* cifs fid stays in le */ |
| /* Let caller know file was created so we can set the mode. */ |
| /* Do we care about the CreateAction in any other cases? */ |
| if (cpu_to_le32(FILE_CREATE) == pSMBr->CreateAction) |
| *pOplock |= CIFS_CREATE_ACTION; |
| if (pfile_info) { |
| memcpy((char *)pfile_info, (char *)&pSMBr->CreationTime, |
| 36 /* CreationTime to Attributes */); |
| /* the file_info buf is endian converted by caller */ |
| pfile_info->AllocationSize = pSMBr->AllocationSize; |
| pfile_info->EndOfFile = pSMBr->EndOfFile; |
| pfile_info->NumberOfLinks = cpu_to_le32(1); |
| pfile_info->DeletePending = 0; |
| } |
| } |
| |
| cifs_buf_release(pSMB); |
| if (rc == -EAGAIN) |
| goto openRetry; |
| return rc; |
| } |
| |
| int |
| CIFSSMBRead(const int xid, struct cifsTconInfo *tcon, const int netfid, |
| const unsigned int count, const __u64 lseek, unsigned int *nbytes, |
| char **buf, int *pbuf_type) |
| { |
| int rc = -EACCES; |
| READ_REQ *pSMB = NULL; |
| READ_RSP *pSMBr = NULL; |
| char *pReadData = NULL; |
| int wct; |
| int resp_buf_type = 0; |
| struct kvec iov[1]; |
| |
| cFYI(1, ("Reading %d bytes on fid %d", count, netfid)); |
| if (tcon->ses->capabilities & CAP_LARGE_FILES) |
| wct = 12; |
| else { |
| wct = 10; /* old style read */ |
| if ((lseek >> 32) > 0) { |
| /* can not handle this big offset for old */ |
| return -EIO; |
| } |
| } |
| |
| *nbytes = 0; |
| rc = small_smb_init(SMB_COM_READ_ANDX, wct, tcon, (void **) &pSMB); |
| if (rc) |
| return rc; |
| |
| /* tcon and ses pointer are checked in smb_init */ |
| if (tcon->ses->server == NULL) |
| return -ECONNABORTED; |
| |
| pSMB->AndXCommand = 0xFF; /* none */ |
| pSMB->Fid = netfid; |
| pSMB->OffsetLow = cpu_to_le32(lseek & 0xFFFFFFFF); |
| if (wct == 12) |
| pSMB->OffsetHigh = cpu_to_le32(lseek >> 32); |
| |
| pSMB->Remaining = 0; |
| pSMB->MaxCount = cpu_to_le16(count & 0xFFFF); |
| pSMB->MaxCountHigh = cpu_to_le32(count >> 16); |
| if (wct == 12) |
| pSMB->ByteCount = 0; /* no need to do le conversion since 0 */ |
| else { |
| /* old style read */ |
| struct smb_com_readx_req *pSMBW = |
| (struct smb_com_readx_req *)pSMB; |
| pSMBW->ByteCount = 0; |
| } |
| |
| iov[0].iov_base = (char *)pSMB; |
| iov[0].iov_len = pSMB->hdr.smb_buf_length + 4; |
| rc = SendReceive2(xid, tcon->ses, iov, 1 /* num iovecs */, |
| &resp_buf_type, CIFS_STD_OP | CIFS_LOG_ERROR); |
| cifs_stats_inc(&tcon->num_reads); |
| pSMBr = (READ_RSP *)iov[0].iov_base; |
| if (rc) { |
| cERROR(1, ("Send error in read = %d", rc)); |
| } else { |
| int data_length = le16_to_cpu(pSMBr->DataLengthHigh); |
| data_length = data_length << 16; |
| data_length += le16_to_cpu(pSMBr->DataLength); |
| *nbytes = data_length; |
| |
| /*check that DataLength would not go beyond end of SMB */ |
| if ((data_length > CIFSMaxBufSize) |
| || (data_length > count)) { |
| cFYI(1, ("bad length %d for count %d", |
| data_length, count)); |
| rc = -EIO; |
| *nbytes = 0; |
| } else { |
| pReadData = (char *) (&pSMBr->hdr.Protocol) + |
| le16_to_cpu(pSMBr->DataOffset); |
| /* if (rc = copy_to_user(buf, pReadData, data_length)) { |
| cERROR(1,("Faulting on read rc = %d",rc)); |
| rc = -EFAULT; |
| }*/ /* can not use copy_to_user when using page cache*/ |
| if (*buf) |
| memcpy(*buf, pReadData, data_length); |
| } |
| } |
| |
| /* cifs_small_buf_release(pSMB); */ /* Freed earlier now in SendReceive2 */ |
| if (*buf) { |
| if (resp_buf_type == CIFS_SMALL_BUFFER) |
| cifs_small_buf_release(iov[0].iov_base); |
| else if (resp_buf_type == CIFS_LARGE_BUFFER) |
| cifs_buf_release(iov[0].iov_base); |
| } else if (resp_buf_type != CIFS_NO_BUFFER) { |
| /* return buffer to caller to free */ |
| *buf = iov[0].iov_base; |
| if (resp_buf_type == CIFS_SMALL_BUFFER) |
| *pbuf_type = CIFS_SMALL_BUFFER; |
| else if (resp_buf_type == CIFS_LARGE_BUFFER) |
| *pbuf_type = CIFS_LARGE_BUFFER; |
| } /* else no valid buffer on return - leave as null */ |
| |
| /* Note: On -EAGAIN error only caller can retry on handle based calls |
| since file handle passed in no longer valid */ |
| return rc; |
| } |
| |
| |
| int |
| CIFSSMBWrite(const int xid, struct cifsTconInfo *tcon, |
| const int netfid, const unsigned int count, |
| const __u64 offset, unsigned int *nbytes, const char *buf, |
| const char __user *ubuf, const int long_op) |
| { |
| int rc = -EACCES; |
| WRITE_REQ *pSMB = NULL; |
| WRITE_RSP *pSMBr = NULL; |
| int bytes_returned, wct; |
| __u32 bytes_sent; |
| __u16 byte_count; |
| |
| /* cFYI(1, ("write at %lld %d bytes", offset, count));*/ |
| if (tcon->ses == NULL) |
| return -ECONNABORTED; |
| |
| if (tcon->ses->capabilities & CAP_LARGE_FILES) |
| wct = 14; |
| else { |
| wct = 12; |
| if ((offset >> 32) > 0) { |
| /* can not handle big offset for old srv */ |
| return -EIO; |
| } |
| } |
| |
| rc = smb_init(SMB_COM_WRITE_ANDX, wct, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| /* tcon and ses pointer are checked in smb_init */ |
| if (tcon->ses->server == NULL) |
| return -ECONNABORTED; |
| |
| pSMB->AndXCommand = 0xFF; /* none */ |
| pSMB->Fid = netfid; |
| pSMB->OffsetLow = cpu_to_le32(offset & 0xFFFFFFFF); |
| if (wct == 14) |
| pSMB->OffsetHigh = cpu_to_le32(offset >> 32); |
| |
| pSMB->Reserved = 0xFFFFFFFF; |
| pSMB->WriteMode = 0; |
| pSMB->Remaining = 0; |
| |
| /* Can increase buffer size if buffer is big enough in some cases ie we |
| can send more if LARGE_WRITE_X capability returned by the server and if |
| our buffer is big enough or if we convert to iovecs on socket writes |
| and eliminate the copy to the CIFS buffer */ |
| if (tcon->ses->capabilities & CAP_LARGE_WRITE_X) { |
| bytes_sent = min_t(const unsigned int, CIFSMaxBufSize, count); |
| } else { |
| bytes_sent = (tcon->ses->server->maxBuf - MAX_CIFS_HDR_SIZE) |
| & ~0xFF; |
| } |
| |
| if (bytes_sent > count) |
| bytes_sent = count; |
| pSMB->DataOffset = |
| cpu_to_le16(offsetof(struct smb_com_write_req, Data) - 4); |
| if (buf) |
| memcpy(pSMB->Data, buf, bytes_sent); |
| else if (ubuf) { |
| if (copy_from_user(pSMB->Data, ubuf, bytes_sent)) { |
| cifs_buf_release(pSMB); |
| return -EFAULT; |
| } |
| } else if (count != 0) { |
| /* No buffer */ |
| cifs_buf_release(pSMB); |
| return -EINVAL; |
| } /* else setting file size with write of zero bytes */ |
| if (wct == 14) |
| byte_count = bytes_sent + 1; /* pad */ |
| else /* wct == 12 */ |
| byte_count = bytes_sent + 5; /* bigger pad, smaller smb hdr */ |
| |
| pSMB->DataLengthLow = cpu_to_le16(bytes_sent & 0xFFFF); |
| pSMB->DataLengthHigh = cpu_to_le16(bytes_sent >> 16); |
| pSMB->hdr.smb_buf_length += byte_count; |
| |
| if (wct == 14) |
| pSMB->ByteCount = cpu_to_le16(byte_count); |
| else { /* old style write has byte count 4 bytes earlier |
| so 4 bytes pad */ |
| struct smb_com_writex_req *pSMBW = |
| (struct smb_com_writex_req *)pSMB; |
| pSMBW->ByteCount = cpu_to_le16(byte_count); |
| } |
| |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, long_op); |
| cifs_stats_inc(&tcon->num_writes); |
| if (rc) { |
| cFYI(1, ("Send error in write = %d", rc)); |
| *nbytes = 0; |
| } else { |
| *nbytes = le16_to_cpu(pSMBr->CountHigh); |
| *nbytes = (*nbytes) << 16; |
| *nbytes += le16_to_cpu(pSMBr->Count); |
| } |
| |
| cifs_buf_release(pSMB); |
| |
| /* Note: On -EAGAIN error only caller can retry on handle based calls |
| since file handle passed in no longer valid */ |
| |
| return rc; |
| } |
| |
| int |
| CIFSSMBWrite2(const int xid, struct cifsTconInfo *tcon, |
| const int netfid, const unsigned int count, |
| const __u64 offset, unsigned int *nbytes, struct kvec *iov, |
| int n_vec, const int long_op) |
| { |
| int rc = -EACCES; |
| WRITE_REQ *pSMB = NULL; |
| int wct; |
| int smb_hdr_len; |
| int resp_buf_type = 0; |
| |
| *nbytes = 0; |
| |
| cFYI(1, ("write2 at %lld %d bytes", (long long)offset, count)); |
| |
| if (tcon->ses->capabilities & CAP_LARGE_FILES) { |
| wct = 14; |
| } else { |
| wct = 12; |
| if ((offset >> 32) > 0) { |
| /* can not handle big offset for old srv */ |
| return -EIO; |
| } |
| } |
| rc = small_smb_init(SMB_COM_WRITE_ANDX, wct, tcon, (void **) &pSMB); |
| if (rc) |
| return rc; |
| /* tcon and ses pointer are checked in smb_init */ |
| if (tcon->ses->server == NULL) |
| return -ECONNABORTED; |
| |
| pSMB->AndXCommand = 0xFF; /* none */ |
| pSMB->Fid = netfid; |
| pSMB->OffsetLow = cpu_to_le32(offset & 0xFFFFFFFF); |
| if (wct == 14) |
| pSMB->OffsetHigh = cpu_to_le32(offset >> 32); |
| pSMB->Reserved = 0xFFFFFFFF; |
| pSMB->WriteMode = 0; |
| pSMB->Remaining = 0; |
| |
| pSMB->DataOffset = |
| cpu_to_le16(offsetof(struct smb_com_write_req, Data) - 4); |
| |
| pSMB->DataLengthLow = cpu_to_le16(count & 0xFFFF); |
| pSMB->DataLengthHigh = cpu_to_le16(count >> 16); |
| smb_hdr_len = pSMB->hdr.smb_buf_length + 1; /* hdr + 1 byte pad */ |
| if (wct == 14) |
| pSMB->hdr.smb_buf_length += count+1; |
| else /* wct == 12 */ |
| pSMB->hdr.smb_buf_length += count+5; /* smb data starts later */ |
| if (wct == 14) |
| pSMB->ByteCount = cpu_to_le16(count + 1); |
| else /* wct == 12 */ /* bigger pad, smaller smb hdr, keep offset ok */ { |
| struct smb_com_writex_req *pSMBW = |
| (struct smb_com_writex_req *)pSMB; |
| pSMBW->ByteCount = cpu_to_le16(count + 5); |
| } |
| iov[0].iov_base = pSMB; |
| if (wct == 14) |
| iov[0].iov_len = smb_hdr_len + 4; |
| else /* wct == 12 pad bigger by four bytes */ |
| iov[0].iov_len = smb_hdr_len + 8; |
| |
| |
| rc = SendReceive2(xid, tcon->ses, iov, n_vec + 1, &resp_buf_type, |
| long_op); |
| cifs_stats_inc(&tcon->num_writes); |
| if (rc) { |
| cFYI(1, ("Send error Write2 = %d", rc)); |
| } else if (resp_buf_type == 0) { |
| /* presumably this can not happen, but best to be safe */ |
| rc = -EIO; |
| } else { |
| WRITE_RSP *pSMBr = (WRITE_RSP *)iov[0].iov_base; |
| *nbytes = le16_to_cpu(pSMBr->CountHigh); |
| *nbytes = (*nbytes) << 16; |
| *nbytes += le16_to_cpu(pSMBr->Count); |
| } |
| |
| /* cifs_small_buf_release(pSMB); */ /* Freed earlier now in SendReceive2 */ |
| if (resp_buf_type == CIFS_SMALL_BUFFER) |
| cifs_small_buf_release(iov[0].iov_base); |
| else if (resp_buf_type == CIFS_LARGE_BUFFER) |
| cifs_buf_release(iov[0].iov_base); |
| |
| /* Note: On -EAGAIN error only caller can retry on handle based calls |
| since file handle passed in no longer valid */ |
| |
| return rc; |
| } |
| |
| |
| int |
| CIFSSMBLock(const int xid, struct cifsTconInfo *tcon, |
| const __u16 smb_file_id, const __u64 len, |
| const __u64 offset, const __u32 numUnlock, |
| const __u32 numLock, const __u8 lockType, const bool waitFlag) |
| { |
| int rc = 0; |
| LOCK_REQ *pSMB = NULL; |
| /* LOCK_RSP *pSMBr = NULL; */ /* No response data other than rc to parse */ |
| int bytes_returned; |
| int timeout = 0; |
| __u16 count; |
| |
| cFYI(1, ("CIFSSMBLock timeout %d numLock %d", (int)waitFlag, numLock)); |
| rc = small_smb_init(SMB_COM_LOCKING_ANDX, 8, tcon, (void **) &pSMB); |
| |
| if (rc) |
| return rc; |
| |
| if (lockType == LOCKING_ANDX_OPLOCK_RELEASE) { |
| timeout = CIFS_ASYNC_OP; /* no response expected */ |
| pSMB->Timeout = 0; |
| } else if (waitFlag) { |
| timeout = CIFS_BLOCKING_OP; /* blocking operation, no timeout */ |
| pSMB->Timeout = cpu_to_le32(-1);/* blocking - do not time out */ |
| } else { |
| pSMB->Timeout = 0; |
| } |
| |
| pSMB->NumberOfLocks = cpu_to_le16(numLock); |
| pSMB->NumberOfUnlocks = cpu_to_le16(numUnlock); |
| pSMB->LockType = lockType; |
| pSMB->AndXCommand = 0xFF; /* none */ |
| pSMB->Fid = smb_file_id; /* netfid stays le */ |
| |
| if ((numLock != 0) || (numUnlock != 0)) { |
| pSMB->Locks[0].Pid = cpu_to_le16(current->tgid); |
| /* BB where to store pid high? */ |
| pSMB->Locks[0].LengthLow = cpu_to_le32((u32)len); |
| pSMB->Locks[0].LengthHigh = cpu_to_le32((u32)(len>>32)); |
| pSMB->Locks[0].OffsetLow = cpu_to_le32((u32)offset); |
| pSMB->Locks[0].OffsetHigh = cpu_to_le32((u32)(offset>>32)); |
| count = sizeof(LOCKING_ANDX_RANGE); |
| } else { |
| /* oplock break */ |
| count = 0; |
| } |
| pSMB->hdr.smb_buf_length += count; |
| pSMB->ByteCount = cpu_to_le16(count); |
| |
| if (waitFlag) { |
| rc = SendReceiveBlockingLock(xid, tcon, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMB, &bytes_returned); |
| cifs_small_buf_release(pSMB); |
| } else { |
| rc = SendReceiveNoRsp(xid, tcon->ses, (struct smb_hdr *)pSMB, |
| timeout); |
| /* SMB buffer freed by function above */ |
| } |
| cifs_stats_inc(&tcon->num_locks); |
| if (rc) |
| cFYI(1, ("Send error in Lock = %d", rc)); |
| |
| /* Note: On -EAGAIN error only caller can retry on handle based calls |
| since file handle passed in no longer valid */ |
| return rc; |
| } |
| |
| int |
| CIFSSMBPosixLock(const int xid, struct cifsTconInfo *tcon, |
| const __u16 smb_file_id, const int get_flag, const __u64 len, |
| struct file_lock *pLockData, const __u16 lock_type, |
| const bool waitFlag) |
| { |
| struct smb_com_transaction2_sfi_req *pSMB = NULL; |
| struct smb_com_transaction2_sfi_rsp *pSMBr = NULL; |
| struct cifs_posix_lock *parm_data; |
| int rc = 0; |
| int timeout = 0; |
| int bytes_returned = 0; |
| int resp_buf_type = 0; |
| __u16 params, param_offset, offset, byte_count, count; |
| struct kvec iov[1]; |
| |
| cFYI(1, ("Posix Lock")); |
| |
| if (pLockData == NULL) |
| return -EINVAL; |
| |
| rc = small_smb_init(SMB_COM_TRANSACTION2, 15, tcon, (void **) &pSMB); |
| |
| if (rc) |
| return rc; |
| |
| pSMBr = (struct smb_com_transaction2_sfi_rsp *)pSMB; |
| |
| params = 6; |
| pSMB->MaxSetupCount = 0; |
| pSMB->Reserved = 0; |
| pSMB->Flags = 0; |
| pSMB->Reserved2 = 0; |
| param_offset = offsetof(struct smb_com_transaction2_sfi_req, Fid) - 4; |
| offset = param_offset + params; |
| |
| count = sizeof(struct cifs_posix_lock); |
| pSMB->MaxParameterCount = cpu_to_le16(2); |
| pSMB->MaxDataCount = cpu_to_le16(1000); /* BB find max SMB from sess */ |
| pSMB->SetupCount = 1; |
| pSMB->Reserved3 = 0; |
| if (get_flag) |
| pSMB->SubCommand = cpu_to_le16(TRANS2_QUERY_FILE_INFORMATION); |
| else |
| pSMB->SubCommand = cpu_to_le16(TRANS2_SET_FILE_INFORMATION); |
| byte_count = 3 /* pad */ + params + count; |
| pSMB->DataCount = cpu_to_le16(count); |
| pSMB->ParameterCount = cpu_to_le16(params); |
| pSMB->TotalDataCount = pSMB->DataCount; |
| pSMB->TotalParameterCount = pSMB->ParameterCount; |
| pSMB->ParameterOffset = cpu_to_le16(param_offset); |
| parm_data = (struct cifs_posix_lock *) |
| (((char *) &pSMB->hdr.Protocol) + offset); |
| |
| parm_data->lock_type = cpu_to_le16(lock_type); |
| if (waitFlag) { |
| timeout = CIFS_BLOCKING_OP; /* blocking operation, no timeout */ |
| parm_data->lock_flags = cpu_to_le16(1); |
| pSMB->Timeout = cpu_to_le32(-1); |
| } else |
| pSMB->Timeout = 0; |
| |
| parm_data->pid = cpu_to_le32(current->tgid); |
| parm_data->start = cpu_to_le64(pLockData->fl_start); |
| parm_data->length = cpu_to_le64(len); /* normalize negative numbers */ |
| |
| pSMB->DataOffset = cpu_to_le16(offset); |
| pSMB->Fid = smb_file_id; |
| pSMB->InformationLevel = cpu_to_le16(SMB_SET_POSIX_LOCK); |
| pSMB->Reserved4 = 0; |
| pSMB->hdr.smb_buf_length += byte_count; |
| pSMB->ByteCount = cpu_to_le16(byte_count); |
| if (waitFlag) { |
| rc = SendReceiveBlockingLock(xid, tcon, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned); |
| } else { |
| iov[0].iov_base = (char *)pSMB; |
| iov[0].iov_len = pSMB->hdr.smb_buf_length + 4; |
| rc = SendReceive2(xid, tcon->ses, iov, 1 /* num iovecs */, |
| &resp_buf_type, timeout); |
| pSMB = NULL; /* request buf already freed by SendReceive2. Do |
| not try to free it twice below on exit */ |
| pSMBr = (struct smb_com_transaction2_sfi_rsp *)iov[0].iov_base; |
| } |
| |
| if (rc) { |
| cFYI(1, ("Send error in Posix Lock = %d", rc)); |
| } else if (get_flag) { |
| /* lock structure can be returned on get */ |
| __u16 data_offset; |
| __u16 data_count; |
| rc = validate_t2((struct smb_t2_rsp *)pSMBr); |
| |
| if (rc || (pSMBr->ByteCount < sizeof(struct cifs_posix_lock))) { |
| rc = -EIO; /* bad smb */ |
| goto plk_err_exit; |
| } |
| data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| data_count = le16_to_cpu(pSMBr->t2.DataCount); |
| if (data_count < sizeof(struct cifs_posix_lock)) { |
| rc = -EIO; |
| goto plk_err_exit; |
| } |
| parm_data = (struct cifs_posix_lock *) |
| ((char *)&pSMBr->hdr.Protocol + data_offset); |
| if (parm_data->lock_type == cpu_to_le16(CIFS_UNLCK)) |
| pLockData->fl_type = F_UNLCK; |
| } |
| |
| plk_err_exit: |
| if (pSMB) |
| cifs_small_buf_release(pSMB); |
| |
| if (resp_buf_type == CIFS_SMALL_BUFFER) |
| cifs_small_buf_release(iov[0].iov_base); |
| else if (resp_buf_type == CIFS_LARGE_BUFFER) |
| cifs_buf_release(iov[0].iov_base); |
| |
| /* Note: On -EAGAIN error only caller can retry on handle based calls |
| since file handle passed in no longer valid */ |
| |
| return rc; |
| } |
| |
| |
| int |
| CIFSSMBClose(const int xid, struct cifsTconInfo *tcon, int smb_file_id) |
| { |
| int rc = 0; |
| CLOSE_REQ *pSMB = NULL; |
| cFYI(1, ("In CIFSSMBClose")); |
| |
| /* do not retry on dead session on close */ |
| rc = small_smb_init(SMB_COM_CLOSE, 3, tcon, (void **) &pSMB); |
| if (rc == -EAGAIN) |
| return 0; |
| if (rc) |
| return rc; |
| |
| pSMB->FileID = (__u16) smb_file_id; |
| pSMB->LastWriteTime = 0xFFFFFFFF; |
| pSMB->ByteCount = 0; |
| rc = SendReceiveNoRsp(xid, tcon->ses, (struct smb_hdr *) pSMB, 0); |
| cifs_stats_inc(&tcon->num_closes); |
| if (rc) { |
| if (rc != -EINTR) { |
| /* EINTR is expected when user ctl-c to kill app */ |
| cERROR(1, ("Send error in Close = %d", rc)); |
| } |
| } |
| |
| /* Since session is dead, file will be closed on server already */ |
| if (rc == -EAGAIN) |
| rc = 0; |
| |
| return rc; |
| } |
| |
| int |
| CIFSSMBFlush(const int xid, struct cifsTconInfo *tcon, int smb_file_id) |
| { |
| int rc = 0; |
| FLUSH_REQ *pSMB = NULL; |
| cFYI(1, ("In CIFSSMBFlush")); |
| |
| rc = small_smb_init(SMB_COM_FLUSH, 1, tcon, (void **) &pSMB); |
| if (rc) |
| return rc; |
| |
| pSMB->FileID = (__u16) smb_file_id; |
| pSMB->ByteCount = 0; |
| rc = SendReceiveNoRsp(xid, tcon->ses, (struct smb_hdr *) pSMB, 0); |
| cifs_stats_inc(&tcon->num_flushes); |
| if (rc) |
| cERROR(1, ("Send error in Flush = %d", rc)); |
| |
| return rc; |
| } |
| |
| int |
| CIFSSMBRename(const int xid, struct cifsTconInfo *tcon, |
| const char *fromName, const char *toName, |
| const struct nls_table *nls_codepage, int remap) |
| { |
| int rc = 0; |
| RENAME_REQ *pSMB = NULL; |
| RENAME_RSP *pSMBr = NULL; |
| int bytes_returned; |
| int name_len, name_len2; |
| __u16 count; |
| |
| cFYI(1, ("In CIFSSMBRename")); |
| renameRetry: |
| rc = smb_init(SMB_COM_RENAME, 1, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| pSMB->BufferFormat = 0x04; |
| pSMB->SearchAttributes = |
| cpu_to_le16(ATTR_READONLY | ATTR_HIDDEN | ATTR_SYSTEM | |
| ATTR_DIRECTORY); |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = |
| cifsConvertToUCS((__le16 *) pSMB->OldFileName, fromName, |
| PATH_MAX, nls_codepage, remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| pSMB->OldFileName[name_len] = 0x04; /* pad */ |
| /* protocol requires ASCII signature byte on Unicode string */ |
| pSMB->OldFileName[name_len + 1] = 0x00; |
| name_len2 = |
| cifsConvertToUCS((__le16 *)&pSMB->OldFileName[name_len + 2], |
| toName, PATH_MAX, nls_codepage, remap); |
| name_len2 += 1 /* trailing null */ + 1 /* Signature word */ ; |
| name_len2 *= 2; /* convert to bytes */ |
| } else { /* BB improve the check for buffer overruns BB */ |
| name_len = strnlen(fromName, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->OldFileName, fromName, name_len); |
| name_len2 = strnlen(toName, PATH_MAX); |
| name_len2++; /* trailing null */ |
| pSMB->OldFileName[name_len] = 0x04; /* 2nd buffer format */ |
| strncpy(&pSMB->OldFileName[name_len + 1], toName, name_len2); |
| name_len2++; /* trailing null */ |
| name_len2++; /* signature byte */ |
| } |
| |
| count = 1 /* 1st signature byte */ + name_len + name_len2; |
| pSMB->hdr.smb_buf_length += count; |
| pSMB->ByteCount = cpu_to_le16(count); |
| |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| cifs_stats_inc(&tcon->num_renames); |
| if (rc) |
| cFYI(1, ("Send error in rename = %d", rc)); |
| |
| cifs_buf_release(pSMB); |
| |
| if (rc == -EAGAIN) |
| goto renameRetry; |
| |
| return rc; |
| } |
| |
| int CIFSSMBRenameOpenFile(const int xid, struct cifsTconInfo *pTcon, |
| int netfid, const char *target_name, |
| const struct nls_table *nls_codepage, int remap) |
| { |
| struct smb_com_transaction2_sfi_req *pSMB = NULL; |
| struct smb_com_transaction2_sfi_rsp *pSMBr = NULL; |
| struct set_file_rename *rename_info; |
| char *data_offset; |
| char dummy_string[30]; |
| int rc = 0; |
| int bytes_returned = 0; |
| int len_of_str; |
| __u16 params, param_offset, offset, count, byte_count; |
| |
| cFYI(1, ("Rename to File by handle")); |
| rc = smb_init(SMB_COM_TRANSACTION2, 15, pTcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| params = 6; |
| pSMB->MaxSetupCount = 0; |
| pSMB->Reserved = 0; |
| pSMB->Flags = 0; |
| pSMB->Timeout = 0; |
| pSMB->Reserved2 = 0; |
| param_offset = offsetof(struct smb_com_transaction2_sfi_req, Fid) - 4; |
| offset = param_offset + params; |
| |
| data_offset = (char *) (&pSMB->hdr.Protocol) + offset; |
| rename_info = (struct set_file_rename *) data_offset; |
| pSMB->MaxParameterCount = cpu_to_le16(2); |
| pSMB->MaxDataCount = cpu_to_le16(1000); /* BB find max SMB from sess */ |
| pSMB->SetupCount = 1; |
| pSMB->Reserved3 = 0; |
| pSMB->SubCommand = cpu_to_le16(TRANS2_SET_FILE_INFORMATION); |
| byte_count = 3 /* pad */ + params; |
| pSMB->ParameterCount = cpu_to_le16(params); |
| pSMB->TotalParameterCount = pSMB->ParameterCount; |
| pSMB->ParameterOffset = cpu_to_le16(param_offset); |
| pSMB->DataOffset = cpu_to_le16(offset); |
| /* construct random name ".cifs_tmp<inodenum><mid>" */ |
| rename_info->overwrite = cpu_to_le32(1); |
| rename_info->root_fid = 0; |
| /* unicode only call */ |
| if (target_name == NULL) { |
| sprintf(dummy_string, "cifs%x", pSMB->hdr.Mid); |
| len_of_str = cifsConvertToUCS((__le16 *)rename_info->target_name, |
| dummy_string, 24, nls_codepage, remap); |
| } else { |
| len_of_str = cifsConvertToUCS((__le16 *)rename_info->target_name, |
| target_name, PATH_MAX, nls_codepage, |
| remap); |
| } |
| rename_info->target_name_len = cpu_to_le32(2 * len_of_str); |
| count = 12 /* sizeof(struct set_file_rename) */ + (2 * len_of_str); |
| byte_count += count; |
| pSMB->DataCount = cpu_to_le16(count); |
| pSMB->TotalDataCount = pSMB->DataCount; |
| pSMB->Fid = netfid; |
| pSMB->InformationLevel = |
| cpu_to_le16(SMB_SET_FILE_RENAME_INFORMATION); |
| pSMB->Reserved4 = 0; |
| pSMB->hdr.smb_buf_length += byte_count; |
| pSMB->ByteCount = cpu_to_le16(byte_count); |
| rc = SendReceive(xid, pTcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| cifs_stats_inc(&pTcon->num_t2renames); |
| if (rc) |
| cFYI(1, ("Send error in Rename (by file handle) = %d", rc)); |
| |
| cifs_buf_release(pSMB); |
| |
| /* Note: On -EAGAIN error only caller can retry on handle based calls |
| since file handle passed in no longer valid */ |
| |
| return rc; |
| } |
| |
| int |
| CIFSSMBCopy(const int xid, struct cifsTconInfo *tcon, const char *fromName, |
| const __u16 target_tid, const char *toName, const int flags, |
| const struct nls_table *nls_codepage, int remap) |
| { |
| int rc = 0; |
| COPY_REQ *pSMB = NULL; |
| COPY_RSP *pSMBr = NULL; |
| int bytes_returned; |
| int name_len, name_len2; |
| __u16 count; |
| |
| cFYI(1, ("In CIFSSMBCopy")); |
| copyRetry: |
| rc = smb_init(SMB_COM_COPY, 1, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| pSMB->BufferFormat = 0x04; |
| pSMB->Tid2 = target_tid; |
| |
| pSMB->Flags = cpu_to_le16(flags & COPY_TREE); |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = cifsConvertToUCS((__le16 *) pSMB->OldFileName, |
| fromName, PATH_MAX, nls_codepage, |
| remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| pSMB->OldFileName[name_len] = 0x04; /* pad */ |
| /* protocol requires ASCII signature byte on Unicode string */ |
| pSMB->OldFileName[name_len + 1] = 0x00; |
| name_len2 = |
| cifsConvertToUCS((__le16 *)&pSMB->OldFileName[name_len + 2], |
| toName, PATH_MAX, nls_codepage, remap); |
| name_len2 += 1 /* trailing null */ + 1 /* Signature word */ ; |
| name_len2 *= 2; /* convert to bytes */ |
| } else { /* BB improve the check for buffer overruns BB */ |
| name_len = strnlen(fromName, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->OldFileName, fromName, name_len); |
| name_len2 = strnlen(toName, PATH_MAX); |
| name_len2++; /* trailing null */ |
| pSMB->OldFileName[name_len] = 0x04; /* 2nd buffer format */ |
| strncpy(&pSMB->OldFileName[name_len + 1], toName, name_len2); |
| name_len2++; /* trailing null */ |
| name_len2++; /* signature byte */ |
| } |
| |
| count = 1 /* 1st signature byte */ + name_len + name_len2; |
| pSMB->hdr.smb_buf_length += count; |
| pSMB->ByteCount = cpu_to_le16(count); |
| |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| if (rc) { |
| cFYI(1, ("Send error in copy = %d with %d files copied", |
| rc, le16_to_cpu(pSMBr->CopyCount))); |
| } |
| cifs_buf_release(pSMB); |
| |
| if (rc == -EAGAIN) |
| goto copyRetry; |
| |
| return rc; |
| } |
| |
| int |
| CIFSUnixCreateSymLink(const int xid, struct cifsTconInfo *tcon, |
| const char *fromName, const char *toName, |
| const struct nls_table *nls_codepage) |
| { |
| TRANSACTION2_SPI_REQ *pSMB = NULL; |
| TRANSACTION2_SPI_RSP *pSMBr = NULL; |
| char *data_offset; |
| int name_len; |
| int name_len_target; |
| int rc = 0; |
| int bytes_returned = 0; |
| __u16 params, param_offset, offset, byte_count; |
| |
| cFYI(1, ("In Symlink Unix style")); |
| createSymLinkRetry: |
| rc = smb_init(SMB_COM_TRANSACTION2, 15, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = |
| cifs_strtoUCS((__le16 *) pSMB->FileName, fromName, PATH_MAX |
| /* find define for this maxpathcomponent */ |
| , nls_codepage); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| |
| } else { /* BB improve the check for buffer overruns BB */ |
| name_len = strnlen(fromName, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->FileName, fromName, name_len); |
| } |
| params = 6 + name_len; |
| pSMB->MaxSetupCount = 0; |
| pSMB->Reserved = 0; |
| pSMB->Flags = 0; |
| pSMB->Timeout = 0; |
| pSMB->Reserved2 = 0; |
| param_offset = offsetof(struct smb_com_transaction2_spi_req, |
| InformationLevel) - 4; |
| offset = param_offset + params; |
| |
| data_offset = (char *) (&pSMB->hdr.Protocol) + offset; |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len_target = |
| cifs_strtoUCS((__le16 *) data_offset, toName, PATH_MAX |
| /* find define for this maxpathcomponent */ |
| , nls_codepage); |
| name_len_target++; /* trailing null */ |
| name_len_target *= 2; |
| } else { /* BB improve the check for buffer overruns BB */ |
| name_len_target = strnlen(toName, PATH_MAX); |
| name_len_target++; /* trailing null */ |
| strncpy(data_offset, toName, name_len_target); |
| } |
| |
| pSMB->MaxParameterCount = cpu_to_le16(2); |
| /* BB find exact max on data count below from sess */ |
| pSMB->MaxDataCount = cpu_to_le16(1000); |
| pSMB->SetupCount = 1; |
| pSMB->Reserved3 = 0; |
| pSMB->SubCommand = cpu_to_le16(TRANS2_SET_PATH_INFORMATION); |
| byte_count = 3 /* pad */ + params + name_len_target; |
| pSMB->DataCount = cpu_to_le16(name_len_target); |
| pSMB->ParameterCount = cpu_to_le16(params); |
| pSMB->TotalDataCount = pSMB->DataCount; |
| pSMB->TotalParameterCount = pSMB->ParameterCount; |
| pSMB->ParameterOffset = cpu_to_le16(param_offset); |
| pSMB->DataOffset = cpu_to_le16(offset); |
| pSMB->InformationLevel = cpu_to_le16(SMB_SET_FILE_UNIX_LINK); |
| pSMB->Reserved4 = 0; |
| pSMB->hdr.smb_buf_length += byte_count; |
| pSMB->ByteCount = cpu_to_le16(byte_count); |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| cifs_stats_inc(&tcon->num_symlinks); |
| if (rc) |
| cFYI(1, ("Send error in SetPathInfo create symlink = %d", rc)); |
| |
| cifs_buf_release(pSMB); |
| |
| if (rc == -EAGAIN) |
| goto createSymLinkRetry; |
| |
| return rc; |
| } |
| |
| int |
| CIFSUnixCreateHardLink(const int xid, struct cifsTconInfo *tcon, |
| const char *fromName, const char *toName, |
| const struct nls_table *nls_codepage, int remap) |
| { |
| TRANSACTION2_SPI_REQ *pSMB = NULL; |
| TRANSACTION2_SPI_RSP *pSMBr = NULL; |
| char *data_offset; |
| int name_len; |
| int name_len_target; |
| int rc = 0; |
| int bytes_returned = 0; |
| __u16 params, param_offset, offset, byte_count; |
| |
| cFYI(1, ("In Create Hard link Unix style")); |
| createHardLinkRetry: |
| rc = smb_init(SMB_COM_TRANSACTION2, 15, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = cifsConvertToUCS((__le16 *) pSMB->FileName, toName, |
| PATH_MAX, nls_codepage, remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| |
| } else { /* BB improve the check for buffer overruns BB */ |
| name_len = strnlen(toName, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->FileName, toName, name_len); |
| } |
| params = 6 + name_len; |
| pSMB->MaxSetupCount = 0; |
| pSMB->Reserved = 0; |
| pSMB->Flags = 0; |
| pSMB->Timeout = 0; |
| pSMB->Reserved2 = 0; |
| param_offset = offsetof(struct smb_com_transaction2_spi_req, |
| InformationLevel) - 4; |
| offset = param_offset + params; |
| |
| data_offset = (char *) (&pSMB->hdr.Protocol) + offset; |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len_target = |
| cifsConvertToUCS((__le16 *) data_offset, fromName, PATH_MAX, |
| nls_codepage, remap); |
| name_len_target++; /* trailing null */ |
| name_len_target *= 2; |
| } else { /* BB improve the check for buffer overruns BB */ |
| name_len_target = strnlen(fromName, PATH_MAX); |
| name_len_target++; /* trailing null */ |
| strncpy(data_offset, fromName, name_len_target); |
| } |
| |
| pSMB->MaxParameterCount = cpu_to_le16(2); |
| /* BB find exact max on data count below from sess*/ |
| pSMB->MaxDataCount = cpu_to_le16(1000); |
| pSMB->SetupCount = 1; |
| pSMB->Reserved3 = 0; |
| pSMB->SubCommand = cpu_to_le16(TRANS2_SET_PATH_INFORMATION); |
| byte_count = 3 /* pad */ + params + name_len_target; |
| pSMB->ParameterCount = cpu_to_le16(params); |
| pSMB->TotalParameterCount = pSMB->ParameterCount; |
| pSMB->DataCount = cpu_to_le16(name_len_target); |
| pSMB->TotalDataCount = pSMB->DataCount; |
| pSMB->ParameterOffset = cpu_to_le16(param_offset); |
| pSMB->DataOffset = cpu_to_le16(offset); |
| pSMB->InformationLevel = cpu_to_le16(SMB_SET_FILE_UNIX_HLINK); |
| pSMB->Reserved4 = 0; |
| pSMB->hdr.smb_buf_length += byte_count; |
| pSMB->ByteCount = cpu_to_le16(byte_count); |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| cifs_stats_inc(&tcon->num_hardlinks); |
| if (rc) |
| cFYI(1, ("Send error in SetPathInfo (hard link) = %d", rc)); |
| |
| cifs_buf_release(pSMB); |
| if (rc == -EAGAIN) |
| goto createHardLinkRetry; |
| |
| return rc; |
| } |
| |
| int |
| CIFSCreateHardLink(const int xid, struct cifsTconInfo *tcon, |
| const char *fromName, const char *toName, |
| const struct nls_table *nls_codepage, int remap) |
| { |
| int rc = 0; |
| NT_RENAME_REQ *pSMB = NULL; |
| RENAME_RSP *pSMBr = NULL; |
| int bytes_returned; |
| int name_len, name_len2; |
| __u16 count; |
| |
| cFYI(1, ("In CIFSCreateHardLink")); |
| winCreateHardLinkRetry: |
| |
| rc = smb_init(SMB_COM_NT_RENAME, 4, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| pSMB->SearchAttributes = |
| cpu_to_le16(ATTR_READONLY | ATTR_HIDDEN | ATTR_SYSTEM | |
| ATTR_DIRECTORY); |
| pSMB->Flags = cpu_to_le16(CREATE_HARD_LINK); |
| pSMB->ClusterCount = 0; |
| |
| pSMB->BufferFormat = 0x04; |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = |
| cifsConvertToUCS((__le16 *) pSMB->OldFileName, fromName, |
| PATH_MAX, nls_codepage, remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| |
| /* protocol specifies ASCII buffer format (0x04) for unicode */ |
| pSMB->OldFileName[name_len] = 0x04; |
| pSMB->OldFileName[name_len + 1] = 0x00; /* pad */ |
| name_len2 = |
| cifsConvertToUCS((__le16 *)&pSMB->OldFileName[name_len + 2], |
| toName, PATH_MAX, nls_codepage, remap); |
| name_len2 += 1 /* trailing null */ + 1 /* Signature word */ ; |
| name_len2 *= 2; /* convert to bytes */ |
| } else { /* BB improve the check for buffer overruns BB */ |
| name_len = strnlen(fromName, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->OldFileName, fromName, name_len); |
| name_len2 = strnlen(toName, PATH_MAX); |
| name_len2++; /* trailing null */ |
| pSMB->OldFileName[name_len] = 0x04; /* 2nd buffer format */ |
| strncpy(&pSMB->OldFileName[name_len + 1], toName, name_len2); |
| name_len2++; /* trailing null */ |
| name_len2++; /* signature byte */ |
| } |
| |
| count = 1 /* string type byte */ + name_len + name_len2; |
| pSMB->hdr.smb_buf_length += count; |
| pSMB->ByteCount = cpu_to_le16(count); |
| |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| cifs_stats_inc(&tcon->num_hardlinks); |
| if (rc) |
| cFYI(1, ("Send error in hard link (NT rename) = %d", rc)); |
| |
| cifs_buf_release(pSMB); |
| if (rc == -EAGAIN) |
| goto winCreateHardLinkRetry; |
| |
| return rc; |
| } |
| |
| int |
| CIFSSMBUnixQuerySymLink(const int xid, struct cifsTconInfo *tcon, |
| const unsigned char *searchName, char **symlinkinfo, |
| const struct nls_table *nls_codepage) |
| { |
| /* SMB_QUERY_FILE_UNIX_LINK */ |
| TRANSACTION2_QPI_REQ *pSMB = NULL; |
| TRANSACTION2_QPI_RSP *pSMBr = NULL; |
| int rc = 0; |
| int bytes_returned; |
| int name_len; |
| __u16 params, byte_count; |
| char *data_start; |
| |
| cFYI(1, ("In QPathSymLinkInfo (Unix) for path %s", searchName)); |
| |
| querySymLinkRetry: |
| rc = smb_init(SMB_COM_TRANSACTION2, 15, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = |
| cifs_strtoUCS((__le16 *) pSMB->FileName, searchName, |
| PATH_MAX, nls_codepage); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| } else { /* BB improve the check for buffer overruns BB */ |
| name_len = strnlen(searchName, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->FileName, searchName, name_len); |
| } |
| |
| params = 2 /* level */ + 4 /* rsrvd */ + name_len /* incl null */ ; |
| pSMB->TotalDataCount = 0; |
| pSMB->MaxParameterCount = cpu_to_le16(2); |
| pSMB->MaxDataCount = cpu_to_le16(CIFSMaxBufSize); |
| pSMB->MaxSetupCount = 0; |
| pSMB->Reserved = 0; |
| pSMB->Flags = 0; |
| pSMB->Timeout = 0; |
| pSMB->Reserved2 = 0; |
| pSMB->ParameterOffset = cpu_to_le16(offsetof( |
| struct smb_com_transaction2_qpi_req, InformationLevel) - 4); |
| pSMB->DataCount = 0; |
| pSMB->DataOffset = 0; |
| pSMB->SetupCount = 1; |
| pSMB->Reserved3 = 0; |
| pSMB->SubCommand = cpu_to_le16(TRANS2_QUERY_PATH_INFORMATION); |
| byte_count = params + 1 /* pad */ ; |
| pSMB->TotalParameterCount = cpu_to_le16(params); |
| pSMB->ParameterCount = pSMB->TotalParameterCount; |
| pSMB->InformationLevel = cpu_to_le16(SMB_QUERY_FILE_UNIX_LINK); |
| pSMB->Reserved4 = 0; |
| pSMB->hdr.smb_buf_length += byte_count; |
| pSMB->ByteCount = cpu_to_le16(byte_count); |
| |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| if (rc) { |
| cFYI(1, ("Send error in QuerySymLinkInfo = %d", rc)); |
| } else { |
| /* decode response */ |
| |
| rc = validate_t2((struct smb_t2_rsp *)pSMBr); |
| /* BB also check enough total bytes returned */ |
| if (rc || (pSMBr->ByteCount < 2)) |
| rc = -EIO; |
| else { |
| bool is_unicode; |
| u16 count = le16_to_cpu(pSMBr->t2.DataCount); |
| |
| data_start = ((char *) &pSMBr->hdr.Protocol) + |
| le16_to_cpu(pSMBr->t2.DataOffset); |
| |
| if (pSMBr->hdr.Flags2 & SMBFLG2_UNICODE) |
| is_unicode = true; |
| else |
| is_unicode = false; |
| |
| /* BB FIXME investigate remapping reserved chars here */ |
| *symlinkinfo = cifs_strndup_from_ucs(data_start, count, |
| is_unicode, nls_codepage); |
| if (!*symlinkinfo) |
| rc = -ENOMEM; |
| } |
| } |
| cifs_buf_release(pSMB); |
| if (rc == -EAGAIN) |
| goto querySymLinkRetry; |
| return rc; |
| } |
| |
| #ifdef CONFIG_CIFS_EXPERIMENTAL |
| /* Initialize NT TRANSACT SMB into small smb request buffer. |
| This assumes that all NT TRANSACTS that we init here have |
| total parm and data under about 400 bytes (to fit in small cifs |
| buffer size), which is the case so far, it easily fits. NB: |
| Setup words themselves and ByteCount |
| MaxSetupCount (size of returned setup area) and |
| MaxParameterCount (returned parms size) must be set by caller */ |
| static int |
| smb_init_nttransact(const __u16 sub_command, const int setup_count, |
| const int parm_len, struct cifsTconInfo *tcon, |
| void **ret_buf) |
| { |
| int rc; |
| __u32 temp_offset; |
| struct smb_com_ntransact_req *pSMB; |
| |
| rc = small_smb_init(SMB_COM_NT_TRANSACT, 19 + setup_count, tcon, |
| (void **)&pSMB); |
| if (rc) |
| return rc; |
| *ret_buf = (void *)pSMB; |
| pSMB->Reserved = 0; |
| pSMB->TotalParameterCount = cpu_to_le32(parm_len); |
| pSMB->TotalDataCount = 0; |
| pSMB->MaxDataCount = cpu_to_le32((tcon->ses->server->maxBuf - |
| MAX_CIFS_HDR_SIZE) & 0xFFFFFF00); |
| pSMB->ParameterCount = pSMB->TotalParameterCount; |
| pSMB->DataCount = pSMB->TotalDataCount; |
| temp_offset = offsetof(struct smb_com_ntransact_req, Parms) + |
| (setup_count * 2) - 4 /* for rfc1001 length itself */; |
| pSMB->ParameterOffset = cpu_to_le32(temp_offset); |
| pSMB->DataOffset = cpu_to_le32(temp_offset + parm_len); |
| pSMB->SetupCount = setup_count; /* no need to le convert byte fields */ |
| pSMB->SubCommand = cpu_to_le16(sub_command); |
| return 0; |
| } |
| |
| static int |
| validate_ntransact(char *buf, char **ppparm, char **ppdata, |
| __u32 *pparmlen, __u32 *pdatalen) |
| { |
| char *end_of_smb; |
| __u32 data_count, data_offset, parm_count, parm_offset; |
| struct smb_com_ntransact_rsp *pSMBr; |
| |
| *pdatalen = 0; |
| *pparmlen = 0; |
| |
| if (buf == NULL) |
| return -EINVAL; |
| |
| pSMBr = (struct smb_com_ntransact_rsp *)buf; |
| |
| /* ByteCount was converted from little endian in SendReceive */ |
| end_of_smb = 2 /* sizeof byte count */ + pSMBr->ByteCount + |
| (char *)&pSMBr->ByteCount; |
| |
| data_offset = le32_to_cpu(pSMBr->DataOffset); |
| data_count = le32_to_cpu(pSMBr->DataCount); |
| parm_offset = le32_to_cpu(pSMBr->ParameterOffset); |
| parm_count = le32_to_cpu(pSMBr->ParameterCount); |
| |
| *ppparm = (char *)&pSMBr->hdr.Protocol + parm_offset; |
| *ppdata = (char *)&pSMBr->hdr.Protocol + data_offset; |
| |
| /* should we also check that parm and data areas do not overlap? */ |
| if (*ppparm > end_of_smb) { |
| cFYI(1, ("parms start after end of smb")); |
| return -EINVAL; |
| } else if (parm_count + *ppparm > end_of_smb) { |
| cFYI(1, ("parm end after end of smb")); |
| return -EINVAL; |
| } else if (*ppdata > end_of_smb) { |
| cFYI(1, ("data starts after end of smb")); |
| return -EINVAL; |
| } else if (data_count + *ppdata > end_of_smb) { |
| cFYI(1, ("data %p + count %d (%p) ends after end of smb %p start %p", |
| *ppdata, data_count, (data_count + *ppdata), |
| end_of_smb, pSMBr)); |
| return -EINVAL; |
| } else if (parm_count + data_count > pSMBr->ByteCount) { |
| cFYI(1, ("parm count and data count larger than SMB")); |
| return -EINVAL; |
| } |
| *pdatalen = data_count; |
| *pparmlen = parm_count; |
| return 0; |
| } |
| |
| int |
| CIFSSMBQueryReparseLinkInfo(const int xid, struct cifsTconInfo *tcon, |
| const unsigned char *searchName, |
| char *symlinkinfo, const int buflen, __u16 fid, |
| const struct nls_table *nls_codepage) |
| { |
| int rc = 0; |
| int bytes_returned; |
| struct smb_com_transaction_ioctl_req *pSMB; |
| struct smb_com_transaction_ioctl_rsp *pSMBr; |
| |
| cFYI(1, ("In Windows reparse style QueryLink for path %s", searchName)); |
| rc = smb_init(SMB_COM_NT_TRANSACT, 23, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| pSMB->TotalParameterCount = 0 ; |
| pSMB->TotalDataCount = 0; |
| pSMB->MaxParameterCount = cpu_to_le32(2); |
| /* BB find exact data count max from sess structure BB */ |
| pSMB->MaxDataCount = cpu_to_le32((tcon->ses->server->maxBuf - |
| MAX_CIFS_HDR_SIZE) & 0xFFFFFF00); |
| pSMB->MaxSetupCount = 4; |
| pSMB->Reserved = 0; |
| pSMB->ParameterOffset = 0; |
| pSMB->DataCount = 0; |
| pSMB->DataOffset = 0; |
| pSMB->SetupCount = 4; |
| pSMB->SubCommand = cpu_to_le16(NT_TRANSACT_IOCTL); |
| pSMB->ParameterCount = pSMB->TotalParameterCount; |
| pSMB->FunctionCode = cpu_to_le32(FSCTL_GET_REPARSE_POINT); |
| pSMB->IsFsctl = 1; /* FSCTL */ |
| pSMB->IsRootFlag = 0; |
| pSMB->Fid = fid; /* file handle always le */ |
| pSMB->ByteCount = 0; |
| |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 0); |
| if (rc) { |
| cFYI(1, ("Send error in QueryReparseLinkInfo = %d", rc)); |
| } else { /* decode response */ |
| __u32 data_offset = le32_to_cpu(pSMBr->DataOffset); |
| __u32 data_count = le32_to_cpu(pSMBr->DataCount); |
| if ((pSMBr->ByteCount < 2) || (data_offset > 512)) { |
| /* BB also check enough total bytes returned */ |
| rc = -EIO; /* bad smb */ |
| goto qreparse_out; |
| } |
| if (data_count && (data_count < 2048)) { |
| char *end_of_smb = 2 /* sizeof byte count */ + |
| pSMBr->ByteCount + (char *)&pSMBr->ByteCount; |
| |
| struct reparse_data *reparse_buf = |
| (struct reparse_data *) |
| ((char *)&pSMBr->hdr.Protocol |
| + data_offset); |
| if ((char *)reparse_buf >= end_of_smb) { |
| rc = -EIO; |
| goto qreparse_out; |
| } |
| if ((reparse_buf->LinkNamesBuf + |
| reparse_buf->TargetNameOffset + |
| reparse_buf->TargetNameLen) > end_of_smb) { |
| cFYI(1, ("reparse buf beyond SMB")); |
| rc = -EIO; |
| goto qreparse_out; |
| } |
| |
| if (pSMBr->hdr.Flags2 & SMBFLG2_UNICODE) { |
| cifs_from_ucs2(symlinkinfo, (__le16 *) |
| (reparse_buf->LinkNamesBuf + |
| reparse_buf->TargetNameOffset), |
| buflen, |
| reparse_buf->TargetNameLen, |
| nls_codepage, 0); |
| } else { /* ASCII names */ |
| strncpy(symlinkinfo, |
| reparse_buf->LinkNamesBuf + |
| reparse_buf->TargetNameOffset, |
| min_t(const int, buflen, |
| reparse_buf->TargetNameLen)); |
| } |
| } else { |
| rc = -EIO; |
| cFYI(1, ("Invalid return data count on " |
| "get reparse info ioctl")); |
| } |
| symlinkinfo[buflen] = 0; /* just in case so the caller |
| does not go off the end of the buffer */ |
| cFYI(1, ("readlink result - %s", symlinkinfo)); |
| } |
| |
| qreparse_out: |
| cifs_buf_release(pSMB); |
| |
| /* Note: On -EAGAIN error only caller can retry on handle based calls |
| since file handle passed in no longer valid */ |
| |
| return rc; |
| } |
| #endif /* CIFS_EXPERIMENTAL */ |
| |
| #ifdef CONFIG_CIFS_POSIX |
| |
| /*Convert an Access Control Entry from wire format to local POSIX xattr format*/ |
| static void cifs_convert_ace(posix_acl_xattr_entry *ace, |
| struct cifs_posix_ace *cifs_ace) |
| { |
| /* u8 cifs fields do not need le conversion */ |
| ace->e_perm = cpu_to_le16(cifs_ace->cifs_e_perm); |
| ace->e_tag = cpu_to_le16(cifs_ace->cifs_e_tag); |
| ace->e_id = cpu_to_le32(le64_to_cpu(cifs_ace->cifs_uid)); |
| /* cFYI(1,("perm %d tag %d id %d",ace->e_perm,ace->e_tag,ace->e_id)); */ |
| |
| return; |
| } |
| |
| /* Convert ACL from CIFS POSIX wire format to local Linux POSIX ACL xattr */ |
| static int cifs_copy_posix_acl(char *trgt, char *src, const int buflen, |
| const int acl_type, const int size_of_data_area) |
| { |
| int size = 0; |
| int i; |
| __u16 count; |
| struct cifs_posix_ace *pACE; |
| struct cifs_posix_acl *cifs_acl = (struct cifs_posix_acl *)src; |
| posix_acl_xattr_header *local_acl = (posix_acl_xattr_header *)trgt; |
| |
| if (le16_to_cpu(cifs_acl->version) != CIFS_ACL_VERSION) |
| return -EOPNOTSUPP; |
| |
| if (acl_type & ACL_TYPE_ACCESS) { |
| count = le16_to_cpu(cifs_acl->access_entry_count); |
| pACE = &cifs_acl->ace_array[0]; |
| size = sizeof(struct cifs_posix_acl); |
| size += sizeof(struct cifs_posix_ace) * count; |
| /* check if we would go beyond end of SMB */ |
| if (size_of_data_area < size) { |
| cFYI(1, ("bad CIFS POSIX ACL size %d vs. %d", |
| size_of_data_area, size)); |
| return -EINVAL; |
| } |
| } else if (acl_type & ACL_TYPE_DEFAULT) { |
| count = le16_to_cpu(cifs_acl->access_entry_count); |
| size = sizeof(struct cifs_posix_acl); |
| size += sizeof(struct cifs_posix_ace) * count; |
| /* skip past access ACEs to get to default ACEs */ |
| pACE = &cifs_acl->ace_array[count]; |
| count = le16_to_cpu(cifs_acl->default_entry_count); |
| size += sizeof(struct cifs_posix_ace) * count; |
| /* check if we would go beyond end of SMB */ |
| if (size_of_data_area < size) |
| return -EINVAL; |
| } else { |
| /* illegal type */ |
| return -EINVAL; |
| } |
| |
| size = posix_acl_xattr_size(count); |
| if ((buflen == 0) || (local_acl == NULL)) { |
| /* used to query ACL EA size */ |
| } else if (size > buflen) { |
| return -ERANGE; |
| } else /* buffer big enough */ { |
| local_acl->a_version = cpu_to_le32(POSIX_ACL_XATTR_VERSION); |
| for (i = 0; i < count ; i++) { |
| cifs_convert_ace(&local_acl->a_entries[i], pACE); |
| pACE++; |
| } |
| } |
| return size; |
| } |
| |
| static __u16 convert_ace_to_cifs_ace(struct cifs_posix_ace *cifs_ace, |
| const posix_acl_xattr_entry *local_ace) |
| { |
| __u16 rc = 0; /* 0 = ACL converted ok */ |
| |
| cifs_ace->cifs_e_perm = le16_to_cpu(local_ace->e_perm); |
| cifs_ace->cifs_e_tag = le16_to_cpu(local_ace->e_tag); |
| /* BB is there a better way to handle the large uid? */ |
| if (local_ace->e_id == cpu_to_le32(-1)) { |
| /* Probably no need to le convert -1 on any arch but can not hurt */ |
| cifs_ace->cifs_uid = cpu_to_le64(-1); |
| } else |
| cifs_ace->cifs_uid = cpu_to_le64(le32_to_cpu(local_ace->e_id)); |
| /*cFYI(1,("perm %d tag %d id %d",ace->e_perm,ace->e_tag,ace->e_id));*/ |
| return rc; |
| } |
| |
| /* Convert ACL from local Linux POSIX xattr to CIFS POSIX ACL wire format */ |
| static __u16 ACL_to_cifs_posix(char *parm_data, const char *pACL, |
| const int buflen, const int acl_type) |
| { |
| __u16 rc = 0; |
| struct cifs_posix_acl *cifs_acl = (struct cifs_posix_acl *)parm_data; |
| posix_acl_xattr_header *local_acl = (posix_acl_xattr_header *)pACL; |
| int count; |
| int i; |
| |
| if ((buflen == 0) || (pACL == NULL) || (cifs_acl == NULL)) |
| return 0; |
| |
| count = posix_acl_xattr_count((size_t)buflen); |
| cFYI(1, ("setting acl with %d entries from buf of length %d and " |
| "version of %d", |
| count, buflen, le32_to_cpu(local_acl->a_version))); |
| if (le32_to_cpu(local_acl->a_version) != 2) { |
| cFYI(1, ("unknown POSIX ACL version %d", |
| le32_to_cpu(local_acl->a_version))); |
| return 0; |
| } |
| cifs_acl->version = cpu_to_le16(1); |
| if (acl_type == ACL_TYPE_ACCESS) |
| cifs_acl->access_entry_count = cpu_to_le16(count); |
| else if (acl_type == ACL_TYPE_DEFAULT) |
| cifs_acl->default_entry_count = cpu_to_le16(count); |
| else { |
| cFYI(1, ("unknown ACL type %d", acl_type)); |
| return 0; |
| } |
| for (i = 0; i < count; i++) { |
| rc = convert_ace_to_cifs_ace(&cifs_acl->ace_array[i], |
| &local_acl->a_entries[i]); |
| if (rc != 0) { |
| /* ACE not converted */ |
| break; |
| } |
| } |
| if (rc == 0) { |
| rc = (__u16)(count * sizeof(struct cifs_posix_ace)); |
| rc += sizeof(struct cifs_posix_acl); |
| /* BB add check to make sure ACL does not overflow SMB */ |
| } |
| return rc; |
| } |
| |
| int |
| CIFSSMBGetPosixACL(const int xid, struct cifsTconInfo *tcon, |
| const unsigned char *searchName, |
| char *acl_inf, const int buflen, const int acl_type, |
| const struct nls_table *nls_codepage, int remap) |
| { |
| /* SMB_QUERY_POSIX_ACL */ |
| TRANSACTION2_QPI_REQ *pSMB = NULL; |
| TRANSACTION2_QPI_RSP *pSMBr = NULL; |
| int rc = 0; |
| int bytes_returned; |
| int name_len; |
| __u16 params, byte_count; |
| |
| cFYI(1, ("In GetPosixACL (Unix) for path %s", searchName)); |
| |
| queryAclRetry: |
| rc = smb_init(SMB_COM_TRANSACTION2, 15, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| if (pSMB->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = |
| cifsConvertToUCS((__le16 *) pSMB->FileName, searchName, |
| PATH_MAX, nls_codepage, remap); |
| name_len++; /* trailing null */ |
| name_len *= 2; |
| pSMB->FileName[name_len] = 0; |
| pSMB->FileName[name_len+1] = 0; |
| } else { /* BB improve the check for buffer overruns BB */ |
| name_len = strnlen(searchName, PATH_MAX); |
| name_len++; /* trailing null */ |
| strncpy(pSMB->FileName, searchName, name_len); |
| } |
| |
| params = 2 /* level */ + 4 /* rsrvd */ + name_len /* incl null */ ; |
| pSMB->TotalDataCount = 0; |
| pSMB->MaxParameterCount = cpu_to_le16(2); |
| /* BB find exact max data count below from sess structure BB */ |
| pSMB->MaxDataCount = cpu_to_le16(4000); |
| pSMB->MaxSetupCount = 0; |
| pSMB->Reserved = 0; |
| pSMB->Flags = 0; |
|