| /* |
| * fs/cifs/cifssmb.c |
| * |
| * Copyright (C) International Business Machines Corp., 2002,2006 |
| * 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 different for reconnection purposes since we never want */ |
| /* to reuse a stale file handle and the caller knows the file handle */ |
| |
| #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 "cifsproto.h" |
| #include "cifs_unicode.h" |
| #include "cifs_debug.h" |
| #include "cifsacl.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); |
| if(open_file) { |
| open_file->invalidHandle = TRUE; |
| } |
| } |
| write_unlock(&GlobalSMBSeslock); |
| /* BB Add call to invalidate_inodes(sb) for all superblocks mounted |
| to this tcon */ |
| } |
| |
| /* If the return code is zero, this function must fill in request_buf pointer */ |
| static int |
| small_smb_init(int smb_command, int wct, struct cifsTconInfo *tcon, |
| void **request_buf /* returned */) |
| { |
| int rc = 0; |
| |
| /* 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) { |
| if(tcon->tidStatus == CifsExiting) { |
| /* only tree disconnect, open, and write, |
| (and ulogoff which does not have tcon) |
| are allowed as we start force umount */ |
| 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((tcon->ses) && (tcon->ses->status != CifsExiting) && |
| (tcon->ses->server)){ |
| struct nls_table *nls_codepage; |
| /* Give Demultiplex thread up to 10 seconds to |
| reconnect, should be greater than cifs socket |
| timeout which is 7 seconds */ |
| while(tcon->ses->server->tcpStatus == CifsNeedReconnect) { |
| wait_event_interruptible_timeout(tcon->ses->server->response_q, |
| (tcon->ses->server->tcpStatus == CifsGood), 10 * HZ); |
| if(tcon->ses->server->tcpStatus == CifsNeedReconnect) { |
| /* on "soft" mounts we wait once */ |
| if((tcon->retry == FALSE) || |
| (tcon->ses->status == CifsExiting)) { |
| cFYI(1,("gave up waiting on reconnect in smb_init")); |
| return -EHOSTDOWN; |
| } /* else "hard" mount - keep retrying |
| until process is killed or server |
| comes back on-line */ |
| } else /* TCP session is reestablished now */ |
| break; |
| |
| } |
| |
| nls_codepage = load_nls_default(); |
| /* need to prevent multiple threads trying to |
| simultaneously reconnect the same SMB session */ |
| down(&tcon->ses->sesSem); |
| if(tcon->ses->status == CifsNeedReconnect) |
| rc = cifs_setup_session(0, tcon->ses, |
| nls_codepage); |
| if(!rc && (tcon->tidStatus == CifsNeedReconnect)) { |
| mark_open_files_invalid(tcon); |
| rc = CIFSTCon(0, tcon->ses, tcon->treeName, tcon |
| , nls_codepage); |
| up(&tcon->ses->sesSem); |
| /* BB FIXME add code to check if wsize needs |
| update due to negotiated smb buffer size |
| shrinking */ |
| if(rc == 0) |
| atomic_inc(&tconInfoReconnectCount); |
| |
| cFYI(1, ("reconnect tcon rc = %d", rc)); |
| /* 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 */ |
| |
| /* 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: { |
| unload_nls(nls_codepage); |
| return -EAGAIN; |
| } |
| } |
| } else { |
| up(&tcon->ses->sesSem); |
| } |
| unload_nls(nls_codepage); |
| |
| } else { |
| return -EIO; |
| } |
| } |
| 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; |
| |
| /* 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) { |
| if(tcon->tidStatus == CifsExiting) { |
| /* only tree disconnect, open, and write, |
| (and ulogoff which does not have tcon) |
| are allowed as we start force umount */ |
| 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((tcon->ses) && (tcon->ses->status != CifsExiting) && |
| (tcon->ses->server)){ |
| struct nls_table *nls_codepage; |
| /* Give Demultiplex thread up to 10 seconds to |
| reconnect, should be greater than cifs socket |
| timeout which is 7 seconds */ |
| while(tcon->ses->server->tcpStatus == CifsNeedReconnect) { |
| wait_event_interruptible_timeout(tcon->ses->server->response_q, |
| (tcon->ses->server->tcpStatus == CifsGood), 10 * HZ); |
| if(tcon->ses->server->tcpStatus == |
| CifsNeedReconnect) { |
| /* on "soft" mounts we wait once */ |
| if((tcon->retry == FALSE) || |
| (tcon->ses->status == CifsExiting)) { |
| cFYI(1,("gave up waiting on reconnect in smb_init")); |
| return -EHOSTDOWN; |
| } /* else "hard" mount - keep retrying |
| until process is killed or server |
| comes on-line */ |
| } else /* TCP session is reestablished now */ |
| break; |
| |
| } |
| |
| nls_codepage = load_nls_default(); |
| /* need to prevent multiple threads trying to |
| simultaneously reconnect the same SMB session */ |
| down(&tcon->ses->sesSem); |
| if(tcon->ses->status == CifsNeedReconnect) |
| rc = cifs_setup_session(0, tcon->ses, |
| nls_codepage); |
| if(!rc && (tcon->tidStatus == CifsNeedReconnect)) { |
| mark_open_files_invalid(tcon); |
| rc = CIFSTCon(0, tcon->ses, tcon->treeName, |
| tcon, nls_codepage); |
| up(&tcon->ses->sesSem); |
| /* BB FIXME add code to check if wsize needs |
| update due to negotiated smb buffer size |
| shrinking */ |
| if(rc == 0) |
| atomic_inc(&tconInfoReconnectCount); |
| |
| cFYI(1, ("reconnect tcon rc = %d", rc)); |
| /* 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 */ |
| |
| /* 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: { |
| unload_nls(nls_codepage); |
| return -EAGAIN; |
| } |
| } |
| } else { |
| up(&tcon->ses->sesSem); |
| } |
| unload_nls(nls_codepage); |
| |
| } else { |
| return -EIO; |
| } |
| } |
| 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 /*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; |
| 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; |
| if((secFlags & CIFSSEC_MUST_KRB5) == CIFSSEC_MUST_KRB5) |
| pSMB->hdr.Flags2 |= SMBFLG2_EXT_SEC; |
| |
| 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); |
| 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->maxRw = 0xFF00; |
| server->capabilities = CAP_MPX_MODE | CAP_RAW_MODE; |
| } else { |
| server->maxRw = 0;/* we 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(le16_to_cpu(rsp->SrvTime.Date), |
| le16_to_cpu(rsp->SrvTime.Time)); |
| 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 = val < 0 ? -val : 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 krb5 ... 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->maxRw = le32_to_cpu(pSMBr->MaxRawSize); |
| cFYI(0, ("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; |
| else if (count == 16) { |
| server->secType = RawNTLMSSP; |
| if (server->socketUseCount.counter > 1) { |
| 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 |
| memcpy(server->server_GUID, |
| pSMBr->u.extended_response.GUID, 16); |
| } else { |
| rc = decode_negTokenInit(pSMBr->u.extended_response. |
| SecurityBlob, |
| count - 16, |
| &server->secType); |
| if(rc == 1) { |
| /* BB Need to fill struct for sessetup here */ |
| rc = -EOPNOTSUPP; |
| } else { |
| rc = -EINVAL; |
| } |
| } |
| } else |
| server->capabilities &= ~CAP_EXTENDED_SECURITY; |
| |
| #ifdef CONFIG_CIFS_WEAK_PW_HASH |
| signing_check: |
| #endif |
| if(sign_CIFS_PDUs == FALSE) { |
| if(server->secMode & SECMODE_SIGN_REQUIRED) |
| cERROR(1,("Server requires " |
| "/proc/fs/cifs/PacketSigningEnabled to be on")); |
| server->secMode &= |
| ~(SECMODE_SIGN_ENABLED | SECMODE_SIGN_REQUIRED); |
| } else if(sign_CIFS_PDUs == 1) { |
| if((server->secMode & SECMODE_SIGN_REQUIRED) == 0) |
| server->secMode &= |
| ~(SECMODE_SIGN_ENABLED | SECMODE_SIGN_REQUIRED); |
| } else if(sign_CIFS_PDUs == 2) { |
| if((server->secMode & |
| (SECMODE_SIGN_ENABLED | SECMODE_SIGN_REQUIRED)) == 0) { |
| cERROR(1,("signing required but server lacks support")); |
| } |
| } |
| 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; |
| struct smb_hdr *smb_buffer_response; /* BB removeme BB */ |
| int rc = 0; |
| int length; |
| |
| cFYI(1, ("In tree disconnect")); |
| /* |
| * If last user of the connection and |
| * connection alive - disconnect it |
| * If this is the last connection on the server session disconnect it |
| * (and inside session disconnect we should check if tcp socket needs |
| * to be freed and kernel thread woken up). |
| */ |
| if (tcon) |
| down(&tcon->tconSem); |
| else |
| return -EIO; |
| |
| atomic_dec(&tcon->useCount); |
| if (atomic_read(&tcon->useCount) > 0) { |
| up(&tcon->tconSem); |
| return -EBUSY; |
| } |
| |
| /* No need to return error on this operation if tid invalidated and |
| closed on server already e.g. due to tcp session crashing */ |
| if(tcon->tidStatus == CifsNeedReconnect) { |
| up(&tcon->tconSem); |
| return 0; |
| } |
| |
| if((tcon->ses == NULL) || (tcon->ses->server == NULL)) { |
| up(&tcon->tconSem); |
| return -EIO; |
| } |
| rc = small_smb_init(SMB_COM_TREE_DISCONNECT, 0, tcon, |
| (void **)&smb_buffer); |
| if (rc) { |
| up(&tcon->tconSem); |
| return rc; |
| } else { |
| smb_buffer_response = smb_buffer; /* BB removeme BB */ |
| } |
| rc = SendReceive(xid, tcon->ses, smb_buffer, smb_buffer_response, |
| &length, 0); |
| if (rc) |
| cFYI(1, ("Tree disconnect failed %d", rc)); |
| |
| if (smb_buffer) |
| cifs_small_buf_release(smb_buffer); |
| up(&tcon->tconSem); |
| |
| /* 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) |
| { |
| struct smb_hdr *smb_buffer_response; |
| LOGOFF_ANDX_REQ *pSMB; |
| int rc = 0; |
| int length; |
| |
| cFYI(1, ("In SMBLogoff for session disconnect")); |
| if (ses) |
| down(&ses->sesSem); |
| else |
| return -EIO; |
| |
| atomic_dec(&ses->inUse); |
| if (atomic_read(&ses->inUse) > 0) { |
| up(&ses->sesSem); |
| return -EBUSY; |
| } |
| rc = small_smb_init(SMB_COM_LOGOFF_ANDX, 2, NULL, (void **)&pSMB); |
| if (rc) { |
| up(&ses->sesSem); |
| return rc; |
| } |
| |
| smb_buffer_response = (struct smb_hdr *)pSMB; /* BB removeme BB */ |
| |
| if(ses->server) { |
| 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 = SendReceive(xid, ses, (struct smb_hdr *) pSMB, |
| smb_buffer_response, &length, 0); |
| if (ses->server) { |
| atomic_dec(&ses->server->socketUseCount); |
| if (atomic_read(&ses->server->socketUseCount) == 0) { |
| spin_lock(&GlobalMid_Lock); |
| ses->server->tcpStatus = CifsExiting; |
| spin_unlock(&GlobalMid_Lock); |
| rc = -ESHUTDOWN; |
| } |
| } |
| up(&ses->sesSem); |
| cifs_small_buf_release(pSMB); |
| |
| /* 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 |
| 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; |
| } |
| |
| 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; |
| } |
| |
| 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); |
| /* BB fixme add conversion for access_flags to bits 0 - 2 of mode */ |
| /* 0 = read |
| 1 = write |
| 2 = rw |
| 3 = execute |
| */ |
| pSMB->Mode = cpu_to_le16(2); |
| 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 |
| pSMB->FileAttributes = cpu_to_le16(0/*ATTR_NORMAL*/); /* BB FIXME */ |
| |
| /* if ((omode & S_IWUGO) == 0) |
| pSMB->FileAttributes |= cpu_to_le32(ATTR_READONLY);*/ |
| /* Above line causes problems due to vfs splitting create into two |
| pieces - need to set mode after file created not while it is |
| being created */ |
| |
| /* 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, 1); |
| 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 BB */ |
| |
| *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); |
| } |
| } |
| |
| 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 ((omode & S_IWUGO) == 0) |
| pSMB->FileAttributes |= cpu_to_le32(ATTR_READONLY);*/ |
| /* Above line causes problems due to vfs splitting create into two |
| pieces - need to set mode after file created not while it is |
| being created */ |
| 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, 1); |
| 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); |
| } |
| } |
| |
| 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 */ |
| |
| *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); |
| else if((lseek >> 32) > 0) /* can not handle this big offset for old */ |
| return -EIO; |
| |
| 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, 0); |
| 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; |
| |
| 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); |
| else if((offset >> 32) > 0) /* can not handle this big offset for old */ |
| return -EIO; |
| |
| 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; |
| |
| cFYI(1,("write2 at %lld %d bytes", (long long)offset, count)); |
| |
| if(tcon->ses->capabilities & CAP_LARGE_FILES) |
| wct = 14; |
| else |
| wct = 12; |
| 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); |
| else if((offset >> 32) > 0) /* can not handle this big offset for old */ |
| return -EIO; |
| 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)); |
| *nbytes = 0; |
| } else if(resp_buf_type == 0) { |
| /* presumably this can not happen, but best to be safe */ |
| rc = -EIO; |
| *nbytes = 0; |
| } 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 int waitFlag) |
| { |
| int rc = 0; |
| LOCK_REQ *pSMB = NULL; |
| LOCK_RSP *pSMBr = NULL; |
| int bytes_returned; |
| int timeout = 0; |
| __u16 count; |
| |
| cFYI(1, ("In CIFSSMBLock - timeout %d numLock %d",waitFlag,numLock)); |
| rc = small_smb_init(SMB_COM_LOCKING_ANDX, 8, tcon, (void **) &pSMB); |
| |
| if (rc) |
| return rc; |
| |
| pSMBr = (LOCK_RSP *)pSMB; /* BB removeme BB */ |
| |
| if(lockType == LOCKING_ANDX_OPLOCK_RELEASE) { |
| timeout = -1; /* no response expected */ |
| pSMB->Timeout = 0; |
| } else if (waitFlag == TRUE) { |
| timeout = 3; /* 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 *) pSMBr, &bytes_returned); |
| } else { |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, timeout); |
| } |
| cifs_stats_inc(&tcon->num_locks); |
| if (rc) { |
| cFYI(1, ("Send error in Lock = %d", rc)); |
| } |
| cifs_small_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 |
| 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 int waitFlag) |
| { |
| struct smb_com_transaction2_sfi_req *pSMB = NULL; |
| struct smb_com_transaction2_sfi_rsp *pSMBr = NULL; |
| char *data_offset; |
| struct cifs_posix_lock *parm_data; |
| int rc = 0; |
| int timeout = 0; |
| int bytes_returned = 0; |
| __u16 params, param_offset, offset, byte_count, count; |
| |
| 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; |
| |
| data_offset = (char *) (&pSMB->hdr.Protocol) + offset; |
| |
| count = sizeof(struct cifs_posix_lock); |
| pSMB->MaxParameterCount = cpu_to_le16(2); |
| pSMB->MaxDataCount = cpu_to_le16(1000); /* BB find max SMB PDU 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 = 3; /* 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 { |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, timeout); |
| } |
| |
| 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; |
| } |
| if(pLockData == NULL) { |
| rc = -EINVAL; |
| 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); |
| |
| /* 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; |
| CLOSE_RSP *pSMBr = NULL; |
| int bytes_returned; |
| 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; |
| |
| pSMBr = (CLOSE_RSP *)pSMB; /* BB removeme BB */ |
| |
| pSMB->FileID = (__u16) smb_file_id; |
| pSMB->LastWriteTime = 0xFFFFFFFF; |
| pSMB->ByteCount = 0; |
| rc = SendReceive(xid, tcon->ses, (struct smb_hdr *) pSMB, |
| (struct smb_hdr *) pSMBr, &bytes_returned, 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)); |
| } |
| } |
| |
| cifs_small_buf_release(pSMB); |
| |
| /* Since session is dead, file will be closed on server already */ |
| if(rc == -EAGAIN) |
| rc = 0; |
| |
| 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, 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 PDU 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) + 2; |
| 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))); |
| } |
| if (pSMB) |
| 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)); |
| } |
| |
| if (pSMB) |
| 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; |
| pSMB->OldFileName[name_len] = 0; /* pad */ |
| pSMB->OldFileName[name_len + 1] = 0x04; |
| 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 int buflen, |
| 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; |
| |
| 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 |
| /* 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(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; |
| 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); |
| if (rc || (pSMBr->ByteCount < 2)) |
| /* BB also check enough total bytes returned */ |
| rc = -EIO; /* bad smb */ |
| else { |
| __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| __u16 count = le16_to_cpu(pSMBr->t2.DataCount); |
| |
| if (pSMBr->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = UniStrnlen((wchar_t *) ((char *) |
| &pSMBr->hdr.Protocol +data_offset), |
| min_t(const int, buflen,count) / 2); |
| /* BB FIXME investigate remapping reserved chars here */ |
| cifs_strfromUCS_le(symlinkinfo, |
| (__le16 *) ((char *)&pSMBr->hdr.Protocol + |
| data_offset), |
| name_len, nls_codepage); |
| } else { |
| strncpy(symlinkinfo, |
| (char *) &pSMBr->hdr.Protocol + |
| data_offset, |
| min_t(const int, buflen, count)); |
| } |
| symlinkinfo[buflen] = 0; |
| /* just in case so calling code does not go off the end of buffer */ |
| } |
| } |
| cifs_buf_release(pSMB); |
| if (rc == -EAGAIN) |
| goto querySymLinkRetry; |
| return rc; |
| } |
| |
| /* 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_ntransact(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, |
| int * pdatalen, int * pparmlen) |
| { |
| char * end_of_smb; |
| __u32 data_count, data_offset, parm_count, parm_offset; |
| struct smb_com_ntransact_rsp * pSMBr; |
| |
| 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)); /* BB FIXME */ |
| return -EINVAL; |
| } else if(parm_count + data_count > pSMBr->ByteCount) { |
| cFYI(1,("parm count and data count larger than SMB")); |
| return -EINVAL; |
| } |
| 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; |
| int name_len; |
| 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 */ |
| else { |
| 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 extended beyond SMB")); |
| rc = -EIO; |
| goto qreparse_out; |
| } |
| |
| if (pSMBr->hdr.Flags2 & SMBFLG2_UNICODE) { |
| name_len = UniStrnlen((wchar_t *) |
| (reparse_buf->LinkNamesBuf + |
| reparse_buf->TargetNameOffset), |
| min(buflen/2, reparse_buf->TargetNameLen / 2)); |
| cifs_strfromUCS_le(symlinkinfo, |
| (__le16 *) (reparse_buf->LinkNamesBuf + |
| reparse_buf->TargetNameOffset), |
| name_len, nls_codepage); |
| } 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; |
| } |
| |
| #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; |
| 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_POSIX_ACL); |
| 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_acl_get); |
| if (rc) { |
| cFYI(1, ("Send error in Query POSIX ACL = %d", rc)); |
| } else { |
| /* decode response */ |
| |
| rc = validate_t2((struct smb_t2_rsp *)pSMBr); |
| if (rc || (pSMBr->ByteCount < 2)) |
| /* BB also check enough total bytes returned */ |
| rc = -EIO; /* bad smb */ |
| else { |
| __u16 data_offset = le16_to_cpu(pSMBr->t2.DataOffset); |
| __u16 count = le16_to_cpu(pSMBr->t2.DataCount); |
| rc = cifs_copy_posix_acl(acl_inf, |
| (char *)&pSMBr->hdr.Protocol+data_offset, |
| buflen,acl_type,count); |
| } |
| } |
| cifs_buf_release(pSMB); |
| if (rc == -EAGAIN) |
| goto queryAclRetry; |
| return rc; |
| } |
| |
| int |
| CIFSSMBSetPosixACL(const int xid, struct cifsTconInfo *tcon, |
| const unsigned char *fileName, |
| const char *local_acl, const int buflen, |
| const int acl_type, |
| const struct nls_table *nls_codepage, int remap) |
| { |
| struct smb_com_transaction2_spi_req *pSMB = NULL; |
| struct smb_com_transaction2_spi_rsp *pSMBr = NULL; |
| char *parm_data; |
| int name_len; |
| int rc = 0; |
| int bytes_returned = 0; |
| __u16 params, byte_count, data_count, param_offset, offset; |
| |
| cFYI(1, ("In SetPosixACL (Unix) for path %s", fileName)); |
| setAclRetry: |
| 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 improve the check for buffer overruns BB */ |
| 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 = cpu_to_le16(1000); /* BB find max SMB size from sess */ |
| 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; |
| parm_data = ((char *) &pSMB->hdr.Protocol) + offset; |
| pSMB->ParameterOffset = cpu_to_le16(param_offset); |
| |
| /* convert to on the wire format for POSIX ACL */ |
| data_count = ACL_to_cifs_posix(parm_data,local_acl,buflen,acl_type); |
| |
| if(data_count == 0) { |
| rc = -EOPNOTSUPP; |
| goto setACLerrorExit; |
| } |
| pSMB->DataOffset = cpu_to_le16(offset); |
| pSMB->SetupCount = 1; |
| pSMB->Reserved3 = 0; |
| pSMB->SubCommand = cpu_to_le16(TRANS2_SET_PATH_INFORMATION); |
| pSMB->InformationLevel = cpu_to_le16(SMB_SET_POSIX_ACL); |
| byte_count = 3 /* pad */ + params + data_count; |
| pSMB->DataCount = cpu_to_le16(data_count); |
| pSMB->TotalDataCount = pSMB->DataCount; |
| pSMB->ParameterCount = cpu_to_le16(params); |
| pSMB->TotalParameterCount = pSMB->ParameterCount; |
| 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, ("Set POSIX ACL returned %d", rc)); |
| } |
| |
| setACLerrorExit: |
| cifs_buf_release(pSMB); |
| if (rc == -EAGAIN) |
| goto setAclRetry; |
| return rc; |
| } |
| |
| /* BB fix tabs in this function FIXME BB */ |
| int |
| CIFSGetExtAttr(const int xid, struct cifsTconInfo *tcon, |
| const int netfid, __u64 * pExtAttrBits, __u64 *pMask) |
| { |
| int rc = 0; |
| struct smb_t2_qfi_req *pSMB = NULL; |
| struct smb_t2_qfi_rsp *pSMBr = NULL; |
| int bytes_returned; |
| __u16 params, byte_count; |
| |
| cFYI(1,("In GetExtAttr")); |
| if(tcon == NULL) |
| return -ENODEV; |
| |
| GetExtAttrRetry: |
| rc = smb_init(SMB_COM_TRANSACTION2, 15, tcon, (void **) &pSMB, |
| (void **) &pSMBr); |
| if (rc) |
| return rc; |
| |
| params = 2 /* level */ +2 /* fid */; |
| pSMB->t2.TotalDataCount = 0; |
| pSMB->t2.MaxParameterCount = cpu_to_le16(4); |
| /* BB find exact max data count below from sess structure BB */ |
| pSMB->t2.MaxDataCount = cpu_to_le16(4000); |
| pSMB->t2.MaxSetupCount = 0; |
| pSMB->t2.Reserved = 0; |
| pSMB->t2.Flags = 0; |
| pSMB->t2.Timeout = 0; |
| pSMB->t2.Reserved2 = 0; |
| pSMB->t2.ParameterOffset = cpu_to_le16(offsetof(struct smb_t2_qfi_req, |
| Fid) - 4); |
| pSMB->t2.DataCount = 0; |
| pSMB->t2.DataOffset = 0; |
| pSMB->t2.SetupCount = 1; |
| pSMB->t2.Reserved3 = 0; |
| pSMB->t2.SubCommand = cpu_to_le16(TRANS2_QUERY_FILE_INFORMATION); |
| byte_count = params + 1 /* pad */ ; |
| pSMB->t2.TotalParameterCount = cpu_to_le16(params); |
| pSMB->t2.ParameterCount = pSMB->t2.TotalParameterCount; |
| pSMB->InformationLevel = cpu_to_le16(SMB_QUERY_ATTR_FLAGS); |
| pSMB->Pad = 0; |
| pSMB->Fid = netfid; |
| pSMB->hdr.smb_buf_length += byte_count; |
| pSMB->t2.ByteCount = cpu_to_le16(byte_count); |
|