/* 
   Unix SMB/Netbios implementation.
   
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
   
   This program 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 General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/******************************************************************************
 *
 * RCS ID
 * $Id: coreplus,v 1.1 1999/11/23 22:00:15 david Exp $
 *
 * HISTORY
 * $Log: coreplus,v $
 * Revision 1.1  1999/11/23 22:00:15  david
 * Updates for long filename support.
 * Added files for Coreplus, Lanman1, Lanman2 and NT protorocols.
 *
 *
 *
 *****************************************************************************/

#include <time.h>


#include "includes.h"


extern fstring local_machine;
extern fstring remote_machine;
extern fstring global_myworkgroup;
extern pstring global_myname;
extern pstring myhostname;
extern pstring sesssetup_user,samlogon_user;
extern int sam_logon_in_ssb;
extern int Client;
extern time_t smb_last_time;
extern BOOL case_preserve;
extern BOOL short_case_preserve;
extern BOOL case_mangle;
extern BOOL case_sensitive;
extern BOOL use_mangled_map;
extern BOOL smbDontStripFileExt;
extern BOOL setFileType;
extern BOOL appendFileType;
extern BOOL stripExtension;
extern BOOL detectDosParts;
extern int  smb_read_error;
extern unsigned int smb_echo_count ;
extern int dontConvertName;
extern int last_message;
extern connection_struct * g_conn;
extern int global_oplock_break;
extern struct current_user current_user;

extern char *last_inbuf;
extern int Protocol; 


/****************************************************************************
   reply to a readbraw (core+ protocol)
****************************************************************************/
int reply_readbraw(tpClients aclient,
             connection_struct *conn,
             tpSmbHeader smbHeader,
             char *inbuf,
             char *outbuf, 
             int dum_size, 
             int dum_buffsize)
{
  size_t maxcount,mincount;
  size_t nread = 0;
  SMB_OFF_T startpos;
  char *header = outbuf;
  ssize_t ret=0;
  files_struct *fsp;

  /*
   * Special check if an oplock break has been issued
   * and the readraw request croses on the wire, we must
   * return a zero length response here.
   */

  if(global_oplock_break)
  {
    _smb_setlen(header,0);
    transfer_file(0,Client,(SMB_OFF_T)0,header,4,0);
    DEBUG(5,("readbraw - oplock break finished\n"));
    return -1;
  }

  fsp = file_fsp(inbuf,smb_vwv0);

  if (!FNUM_OK(fsp,conn) || !fsp->can_read) {
      /*
       * fsp could be NULL here so use the value from the packet. JRA.
       */
      DEBUG(3,("fnum %d not open in readbraw - cache prime?\n",(int)SVAL(inbuf,smb_vwv0)));
      _smb_setlen(header,0);
      transfer_file(0,Client,(SMB_OFF_T)0,header,4,0);
      return(-1);
  }

  startpos = IVAL(inbuf,smb_vwv1);
  if(CVAL(inbuf,smb_wct) == 10) {
    /*
     * This is a large offset (64 bit) read.
     */
#ifdef LARGE_SMB_OFF_T

    startpos |= (((SMB_OFF_T)IVAL(inbuf,smb_vwv8)) << 32);

#else /* !LARGE_SMB_OFF_T */

    /*
     * Ensure we haven't been sent a >32 bit offset.
     */

    if(IVAL(inbuf,smb_vwv8) != 0) {
      DEBUG(0,("readbraw - large offset (%x << 32) used and we don't support \
64 bit offsets.\n", (unsigned int)IVAL(inbuf,smb_vwv8) ));
      _smb_setlen(header,0);
      transfer_file(0,Client,(SMB_OFF_T)0,header,4,0);
      return(-1);
    }

#endif /* LARGE_SMB_OFF_T */

    if(startpos < 0) {
      DEBUG(0,("readbraw - negative 64 bit readraw offset (%.0f) !\n",
            (double)startpos ));
      _smb_setlen(header,0);
      transfer_file(0,Client,(SMB_OFF_T)0,header,4,0);
      return(-1);
    }      
  }
  maxcount = (SVAL(inbuf,smb_vwv3) & 0xFFFF);
  mincount = (SVAL(inbuf,smb_vwv4) & 0xFFFF);

  /* ensure we don't overrun the packet size */
  maxcount = MIN(65535,maxcount);
  maxcount = MAX(mincount,maxcount);

  if (!is_locked(fsp,conn,maxcount,startpos, F_RDLCK))
  {
    SMB_OFF_T size = fsp->size;
    SMB_OFF_T sizeneeded = startpos + maxcount;
        
    if (size < sizeneeded)
    {
      SMB_STRUCT_STAT st;
      if (sys_fstat(fsp->fd_ptr->fd,&st) == 0)
        size = st.st_size;
      if (!fsp->can_write) 
        fsp->size = size;
    }

    nread = MIN(maxcount,(size - startpos));      
  }

  if (nread < mincount)
    nread = 0;
  
  DEBUG( 3, ( "readbraw fnum=%d start=%.0f max=%d min=%d nread=%d\n",
          fsp->fnum, (double)startpos,
          maxcount, mincount, nread ) );
  
#if UNSAFE_READRAW
  {
    BOOL seek_fail = False;
    int predict=0;
    _smb_setlen(header,nread);

#if USE_READ_PREDICTION
    if (!fsp->can_write)
      predict = read_predict(fsp->fd_ptr->fd,startpos,header+4,NULL,nread);
#endif /* USE_READ_PREDICTION */

    if ((nread-predict) > 0) {
      if(seek_file(fsp,startpos + predict) == -1) {
        DEBUG(0,("reply_readbraw: ERROR: seek_file failed.\n"));
        ret = 0;
        seek_fail = True;
      } 
    }

    if(!seek_fail)
      ret = (ssize_t)transfer_file(fsp->fd_ptr->fd,Client,
                                   (SMB_OFF_T)(nread-predict),header,4+predict, 
                                   startpos+predict);
  }

  if (ret != nread+4)
    DEBUG(0,("ERROR: file read failure on %s at %d for %d bytes (%d)\n",
         fsp->fsp_name,startpos,nread,ret));

#else /* UNSAFE_READRAW */
  ret = read_file(fsp,header+4,startpos,nread);
  if (ret < mincount) ret = 0;

  _smb_setlen(header,ret);
  transfer_file(0,Client,0,header,4+ret,0);
#endif /* UNSAFE_READRAW */

  DEBUG(5,("readbraw finished\n"));
  return -1;
}


/****************************************************************************
  reply to a lockread (core+ protocol)
****************************************************************************/
int reply_lockread(tpClients aclient,
             connection_struct *conn,
             tpSmbHeader smbHeader,
             char *inbuf,
             char *outbuf, 
             int length, 
             int dum_buffsize)
{
  ssize_t nread = -1;
  char *data;
  int outsize = 0;
  SMB_OFF_T startpos;
  size_t numtoread;
  int eclass;
  uint32 ecode;
  files_struct *fsp = file_fsp(inbuf,smb_vwv0);

  CHECK_FSP(fsp,conn);
  CHECK_READ(fsp);
  CHECK_ERROR(fsp);

  numtoread = SVAL(inbuf,smb_vwv1);
  startpos = IVAL(inbuf,smb_vwv2);
  
  outsize = set_message(outbuf,5,3,True);
  numtoread = MIN(BUFFER_SIZE-outsize,numtoread);
  data = smb_buf(outbuf) + 3;
 
  /*
   * NB. Discovered by Menny Hamburger at Mainsoft. This is a core+
   * protocol request that predates the read/write lock concept. 
   * Thus instead of asking for a read lock here we need to ask
   * for a write lock. JRA.
   */

  if(!do_lock( fsp, conn, numtoread, startpos, F_WRLCK, &eclass, &ecode)) {
    if((ecode == ERRlock) && lp_blocking_locks(SNUM(conn))) {
      /*
       * A blocking lock was requested. Package up
       * this smb into a queued request and push it
       * onto the blocking lock queue.
       */
      if(push_blocking_lock_request(inbuf, length, -1, 0))
        return -1;
    }
    return (ERROR(eclass,ecode));
  }

  nread = read_file(fsp,data,startpos,numtoread);

  if (nread < 0)
    return(UNIXERROR(ERRDOS,ERRnoaccess));

  outsize += nread;
  SSVAL(outbuf,smb_vwv0,nread);
  SSVAL(outbuf,smb_vwv5,nread+3);
  SSVAL(smb_buf(outbuf),1,nread);

  DEBUG( 3, ( "lockread fnum=%d num=%d nread=%d\n",
            fsp->fnum, numtoread, nread ) );

  return(outsize);
}

/****************************************************************************
  reply to a writebraw (core+ or LANMAN1.0 protocol)
****************************************************************************/
int reply_writebraw(tpClients aclient,
             connection_struct *conn,
             tpSmbHeader smbHeader,
             char *inbuf,
             char *outbuf, 
             int dum_size, 
             int dum_buffsize)
{
  ssize_t nwritten=0;
  ssize_t total_written=0;
  size_t numtowrite=0;
  size_t tcount;
  SMB_OFF_T startpos;
  char *data=NULL;
  BOOL write_through;
  files_struct *fsp = file_fsp(inbuf,smb_vwv0);
  int outsize = 0;

  CHECK_FSP(fsp,conn);
  CHECK_WRITE(fsp);
  CHECK_ERROR(fsp);
  
  tcount = IVAL(inbuf,smb_vwv1);
  startpos = IVAL(inbuf,smb_vwv3);
  write_through = BITSETW(inbuf+smb_vwv7,0);

  /* We have to deal with slightly different formats depending
     on whether we are using the core+ or lanman1.0 protocol */
  if(Protocol <= PROTOCOL_COREPLUS) {
    numtowrite = SVAL(smb_buf(inbuf),-2);
    data = smb_buf(inbuf);
  } else {
    numtowrite = SVAL(inbuf,smb_vwv10);
    data = smb_base(inbuf) + SVAL(inbuf, smb_vwv11);
  }

  /* force the error type */
  CVAL(inbuf,smb_com) = SMBwritec;
  CVAL(outbuf,smb_com) = SMBwritec;

  if (is_locked(fsp,conn,tcount,startpos, F_WRLCK))
    return(ERROR(ERRDOS,ERRlock));

  if (seek_file(fsp,startpos) == -1) {
    DEBUG(0,("couldn't seek to %.0f in writebraw\n",(double)startpos));
    return(UNIXERROR(ERRDOS,ERRnoaccess));
  }

  if (numtowrite>0)
    nwritten = write_file(fsp,data,numtowrite);
  
  DEBUG(3,("writebraw1 fnum=%d start=%.0f num=%d wrote=%d sync=%d\n",
       fsp->fnum, (double)startpos, numtowrite, nwritten, write_through));

  if (nwritten < numtowrite) 
    return(UNIXERROR(ERRHRD,ERRdiskfull));

  total_written = nwritten;

  /* Return a message to the redirector to tell it
     to send more bytes */
  CVAL(outbuf,smb_com) = SMBwritebraw;
  SSVALS(outbuf,smb_vwv0,-1);
  outsize = set_message(outbuf,Protocol>PROTOCOL_COREPLUS?1:0,0,True);
  send_smb(&Client,outbuf); /* DRHB change */
  
  /* Now read the raw data into the buffer and write it */
  if (read_smb_length(Client,inbuf,SMB_SECONDARY_WAIT) == -1) {
    exit_server("secondary writebraw failed");
  }
  
  /* Even though this is not an smb message, smb_len
     returns the generic length of an smb message */
  numtowrite = smb_len(inbuf);

  if (tcount > nwritten+numtowrite) {
    DEBUG(3,("Client overestimated the write %d %d %d\n",
         tcount,nwritten,numtowrite));
  }

  nwritten = transfer_file(Client,fsp->fd_ptr->fd,(SMB_OFF_T)numtowrite,NULL,0,
               startpos+nwritten);
  total_written += nwritten;
  
  /* Set up outbuf to return the correct return */
  outsize = set_message(outbuf,1,0,True);
  CVAL(outbuf,smb_com) = SMBwritec;
  SSVAL(outbuf,smb_vwv0,total_written);

  if (nwritten < (ssize_t)numtowrite) {
    CVAL(outbuf,smb_rcls) = ERRHRD;
    SSVAL(outbuf,smb_err,ERRdiskfull);      
  }

  if (lp_syncalways(SNUM(conn)) || write_through)
    sync_file(conn,fsp);

  DEBUG(3,("writebraw2 fnum=%d start=%.0f num=%d wrote=%d\n",
       fsp->fnum, (double)startpos, numtowrite, total_written));

  /* we won't return a status if write through is not selected - this 
     follows what WfWg does */
  if (!write_through && total_written==tcount)
    return(-1);

  return(outsize);
}

/****************************************************************************
  reply to a writeunlock (core+)
****************************************************************************/
int reply_writeunlock(tpClients aclient,
             connection_struct *conn,
             tpSmbHeader smbHeader,
             char *inbuf,
             char *outbuf, 
             int dum_size, 
             int dum_buffsize)
{
  ssize_t nwritten = -1;
  size_t numtowrite;
  SMB_OFF_T startpos;
  char *data;
  int eclass;
  uint32 ecode;
  files_struct *fsp = file_fsp(inbuf,smb_vwv0);
  int outsize = 0;

  CHECK_FSP(fsp,conn);
  CHECK_WRITE(fsp);
  CHECK_ERROR(fsp);

  numtowrite = SVAL(inbuf,smb_vwv1);
  startpos = IVAL(inbuf,smb_vwv2);
  data = smb_buf(inbuf) + 3;
  
  if (is_locked(fsp,conn,numtowrite,startpos, F_WRLCK))
    return(ERROR(ERRDOS,ERRlock));

  if(seek_file(fsp,startpos) == -1)
    return(UNIXERROR(ERRDOS,ERRnoaccess));

  /* The special X/Open SMB protocol handling of
     zero length writes is *NOT* done for
     this call */
  if(numtowrite == 0)
    nwritten = 0;
  else
    nwritten = write_file(fsp,data,numtowrite);
  
  if (lp_syncalways(SNUM(conn)))
    sync_file(conn,fsp);

  if(((nwritten == 0) && (numtowrite != 0))||(nwritten < 0))
    return(UNIXERROR(ERRDOS,ERRnoaccess));

  if(!do_unlock(fsp, conn, numtowrite, startpos, &eclass, &ecode))
    return(ERROR(eclass,ecode));

  outsize = set_message(outbuf,1,0,True);
  
  SSVAL(outbuf,smb_vwv0,nwritten);
  
  DEBUG( 3, ( "writeunlock fnum=%d num=%d wrote=%d\n",
          fsp->fnum, numtowrite, nwritten ) );

  return(outsize);
}

/****************************************************************************
  reply to a writeclose (Core+ protocol)
****************************************************************************/
int reply_writeclose(tpClients aclient,
             connection_struct *conn,
             tpSmbHeader smbHeader,
             char *inbuf,
             char *outbuf, 
             int dum_size, 
             int dum_buffsize)
{
    size_t numtowrite;
    ssize_t nwritten = -1;
    int outsize = 0;
    int close_err = 0;
    SMB_OFF_T startpos;
    char *data;
    time_t mtime;
    files_struct *fsp = file_fsp(inbuf,smb_vwv0);

    CHECK_FSP(fsp,conn);
    CHECK_WRITE(fsp);
    CHECK_ERROR(fsp);

    numtowrite = SVAL(inbuf,smb_vwv1);
    startpos = IVAL(inbuf,smb_vwv2);
    mtime = make_unix_date3(inbuf+smb_vwv4);
    data = smb_buf(inbuf) + 1;
  
    if (is_locked(fsp,conn,numtowrite,startpos, F_WRLCK))
        return(ERROR(ERRDOS,ERRlock));
      
    if(seek_file(fsp,startpos) == -1)
        return(UNIXERROR(ERRDOS,ERRnoaccess));
      
    nwritten = write_file(fsp,data,numtowrite);

    set_filetime(conn, fsp->fsp_name,mtime);
  
    close_err = close_file(fsp,True);

    DEBUG(3,("writeclose fnum=%d num=%d wrote=%d (numopen=%d)\n",
         fsp->fnum, numtowrite, nwritten,
         conn->num_files_open));
  
    if (nwritten <= 0)
        return(UNIXERROR(ERRDOS,ERRnoaccess));
 
    if(close_err != 0) {
        errno = close_err;
        return(UNIXERROR(ERRHRD,ERRgeneral));
    }
 
    outsize = set_message(outbuf,1,0,True);
  
    SSVAL(outbuf,smb_vwv0,nwritten);
    return(outsize);
}


