/*
 *Copyright (c) 1996, 1998, 1999 by Oracle Corporation. All Rights Reserved. \
  $Header: sendrecv.c 24-sep-99.10:46:37 rachacos Exp $
*/

#include "tftpdef.h"
#include <sys/stat.h>
#include <ctype.h>
#include <string.h>
#ifdef VMS
# include <in.h>
#endif

extern FILE *file_open();
extern void file_close();
extern int file_read();
extern int file_write();

int err_dump();
void net_send();
#ifdef SERVER
int recv_xRQ();
void strlccpy();
char *sys_err_str();
#endif

static void send_ERROR();

#ifdef CLIENT

/*
 * Send a RRQ or WRQ to the other side. 
 * These two packets are only sent by the client to the server.
 */

void send_RQ(opcode, fname, mode)
int opcode;  /* RRQ or WRQ */
char *fname;
int mode;
{
  register int len;
  char *modestr;

  DEBUG2("sending RRQ/WRQ for %s, mode =%d", fname, mode);
  
  stshort(opcode, sendbuff);
  strcpy(sendbuff+2, fname);
  len = 2 + strlen(fname) + 1;    /* 1 for null byte at the end of fname */

  switch (mode)
  {
  case MODE_ASCII:  modestr="netascii"; break;
  case MODE_BINARY: modestr="octet"; break;
  default: err_dump("unknown mode");
  }

  strcpy(sendbuff + len, modestr);
  len +=strlen(modestr) + 1;

  sendlen = len;
  net_send(sendbuff, sendlen);
  op_sent = opcode;
}

/*
 * Error packet received in response to an RRQ or WRQ.
 * Usually means the file we are asking for on the other system
 * can't be accessed for some reason. We need to print the error
 * message that is returned.
 * called by finite state machine
 */

int recv_RQERR(ptr, nbytes)
char *ptr;   /* points just past received opcode */
int nbytes;  /* does not include received opcode */
{
  register int ecode;

  ecode  = ldshort(ptr);
  ptr += 2;
  nbytes -= 2;
  ptr[nbytes] = 0;  

  DEBUG2("ERROR received, %d bytes, error code %d", nbytes, ecode);
  fflush(stdout);
  fprintf(stderr, "Error# %d: %s\n", ecode, ptr);
  fflush(stderr);
  return(-1);
}

/*
 * Send a disconnect to the other side to inform it to disconnect
 */
void send_DISRQ()
{
  stshort(OP_DISCONNECT, sendbuff);
  sendlen = 2;

  net_send(sendbuff, sendlen);
}

#endif /* CLIENT */

/* 
 * Send data to the other side
 * The data must be stored in the "sendbuff" by the caller
 * called by recv_ACK()
 */
void send_DATA(blocknum, nbytes)
int blocknum;
int nbytes;        /* #bytes of actual data to send */
{
  DEBUG2("sending %d bytes of DATA with block# %d", nbytes, blocknum);

  stshort(OP_DATA, sendbuff);
  stshort(blocknum, sendbuff+2);
  sendlen = nbytes + 4;
  
  net_send(sendbuff, sendlen);
  op_sent = OP_DATA;

}

/*
 * Send an acknowledgment packet to the other side
 * called by recv_DATA) and recv_WRQ()
 */
void send_ACK(blocknum)
int blocknum;
{
  DEBUG1("sending ACK for block # %d", blocknum);

  stshort(OP_ACK, sendbuff);
  stshort(blocknum, sendbuff+2);

  sendlen = 4;
  net_send(sendbuff, sendlen);
  op_sent = OP_ACK;
}

/* 
 * Data packet received. Send an acknowledgment.
 * called by finite state machine.
 * This fucntion is called by both client and server
 */
int recv_DATA(ptr, nbytes)
char *ptr;     /* points just past received opcode */
int nbytes;    /* does not include received opcode */
{
  register int recvblknum;

  recvblknum = ldshort(ptr);
  ptr += 2;
  nbytes -= 2;

  DEBUG2("data received, %d bytes, block# %d", nbytes, recvblknum);

  if (nbytes > MAXDATA)
  {
    err_dump("data packet received with length = %d bytes", nbytes);
  }

  if (recvblknum == nextblknum)
  {
    /*
     * The data packet is the expected one. Increase our expected-block# for
     * the next packet.
     */
    nextblknum++;
    totnbytes += nbytes;

    if (nbytes > 0)
    {
      /*
       * The final data packet can have a data length of zero, so we only
       * write the data to the local file if there is data.
       */
      file_write(localfp, ptr, nbytes, modetype);
    }

    send_ACK(recvblknum);
#ifdef SERVER
    /*
     * if the length of the data is between 0-511, this is the last data
     * block. For the server, here is where we have to close the file. 
     * For the client, the "get" command processing will close the file.
     */
    if (nbytes < MAXDATA)
    {
      file_close(localfp);
      op_sent = 0;      /* need to reset it for next operation */
    }
#endif
  }
  else
  {
    /* something is wrong */
    err_dump("data block# and ACK block# mismatch");
  }



  /*
   * If the length of the data is between 0-511, we have just received
   * the final data packet, else there is more to come 
   */
  /* 
   * For server, it has to stay in fsm_loop() to wait for new incoming
   * request, for client, it should exit fsm_loop()
   */
#ifdef SERVER
  return (0);
#else    /* client */
  return( (nbytes == MAXDATA) ? 0 : -1);
#endif
}

  
/*
 * ACK packet received. Send some more data.
 * Called by finite state machine. Also called by recv_RRQ() to start 
 * the transmission of a file to the client.
 * This function is called by both the client and the server.
 */
int recv_ACK(ptr, nbytes)
char *ptr;         /* pointer to just past received code */
int nbytes;        /* does not include received opcode */
{
  register int recvblknum;

  recvblknum = ldshort(ptr);
  if (nbytes != 2)
    err_dump("ACK packet received with length = %d bytes", nbytes + 2);

  DEBUG1("ACK received, block# %d", recvblknum);
  if (recvblknum == nextblknum)
  {
    /*
     * The received acknowledgement is for the expected data packet that
     * we sent.
     * Fill the transmit buffer with the next block of data to send.
     * If there is not more data to send, then we might be finished.
     * Note that we must send a final data packet containing 0-511 
     * bytes of data. If the length of the last packet that we sent
     * was exactly 512 bytes, there we must send a 0-length data packet.
     */

    if (( nbytes = file_read(localfp, sendbuff+4, MAXDATA, modetype)) == 0)
    {
      if (lastsend < MAXDATA)
      {
/*
 * This is the last data packet sent on the server side, server needs to 
 * close the file. Server returns 0 so it will loop in fsm_loop() to wait
 * for new incoming request.
 */
#ifdef SERVER
        file_close(localfp);
        op_sent = 0;    /* reset op_sent for server */
        return(0);
#else  /* client */
        return(-1);  /* done */
#endif
      }
    }
    lastsend = nbytes;
    nextblknum++;
    totnbytes +=nbytes;
    send_DATA(nextblknum, nbytes);
    return(0);
  }
  else if (recvblknum == (nextblknum - 1))
  {
    /*
     * We have received a duplicate ACK. This means either
     * (1) the other side never received our last data packet
     * (2) the other side's ACK got delayed somehow.
     *
     * Ignore the ACK, do NOT retransmit the data packet
     * 
     * This should not happen in a reliable transport
     */
    return(0);
  }
  else 
  {
    err_dump("data block # and ACK block # mismatch");
  }
}

        

#ifdef SERVER

/*
 * RRQ received
 */
int recv_RRQ(ptr, nbytes)
char *ptr;
int nbytes;
{
  char ackbuff[2];
  recv_xRQ(OP_RRQ, ptr, nbytes);

  /*
   * set things up so we can just call recv_ACK() and pretend we 
   * have received an ACK, so it will send the first data block to 
   * the client.
   */

  lastsend = MAXDATA;
  stshort(0, ackbuff);  /* pretend it is an ACK of block #0 */

  recv_ACK(ackbuff, 2); /* this sends data block #1 */
  return(0);            /* the state machine takes over from here */

}

/*
 * WRQ received
 */
int recv_WRQ(ptr, nbytes)
char *ptr;
int nbytes;
{
  recv_xRQ(OP_WRQ, ptr, nbytes);

  /* 
   * call send_ACK() to acknowledge block #0, which will cause the client
   * to send data block #1.
   */
  nextblknum = 1;
  send_ACK(0);
  return(0);       /* the state machine takes over from here */
}

/*
 * Process the RRQ or WRQ received 
 */
int recv_xRQ(opcode, ptr, nbytes)
int opcode;                 /* RRQ or WRQ */
register char *ptr;         /* points just past received opcode */
int nbytes;                 /* does not include received opcode */
{
  register int i;
  register char *saveptr;
  char filename[MAXFILENAME], dirname[MAXFILENAME], mode[MAXFILENAME];
  struct stat statbuff;

  /*
   * Assume the file name and mode are present and null-terminated
   */
  saveptr = ptr;    /* points to the beginning of file name */
  for (i = 0; i < nbytes; i++)
    if (*ptr++ == '\0')
      goto FileOK;
  err_dump("Invalid filename");

 FileOK:
  strcpy(filename, saveptr);
  saveptr = ptr;

  for ( ; i < nbytes; i++)
    if (*ptr++ == '\0')
      goto ModeOK;
  err_dump("Invalid Mode");

 ModeOK:
  strlccpy(mode, saveptr);

  if (strcmp(mode, "netascii") == 0)
    modetype = MODE_ASCII;
  else if (strcmp(mode, "octet") == 0)
    modetype = MODE_BINARY;
  else
    send_ERROR(ERR_BADOP, "Mode is not netascii or octet");

  /*
   * validate file name. File must have permission to be publicly accessed
   * Filename must have its full pathname specified, i.e., it must begin
   * with a slash
   */
    
#ifdef NEVER
  if (filename[0] != '/')
    send_ERROR(ERR_ACCESS, "filename must begin with a '/'");
#endif

  if (opcode == OP_RRQ)
  {
    /*
     * read request - verify that the file exists and it has world
     * read permission
     */
    if (stat(filename, &statbuff) < 0)
      send_ERROR(ERR_ACCESS, sys_err_str());
    if ((statbuff.st_mode & (S_IREAD >> 6)) == 0 )
      send_ERROR(ERR_ACCESS, "File does not allow world read permission");
  }
  else if ( opcode == OP_WRQ)
  {
    /*
     * write request - verify that the directory that the file is being
     * written to has world write permission. 
    char *strrchr();
     */
#ifndef WIN32
    strcpy(dirname, filename);
    *(strrchr(dirname, '/') + 1) = '\0';
    if (stat(dirname, &statbuff) < 0)
      send_ERROR(ERR_ACCESS, sys_err_str());
    if((statbuff.st_mode & (S_IWRITE >> 6)) == 0)
      send_ERROR(ERR_ACCESS, "Directory does not have world write permission");
#endif /* WIN32 */
  }
  else
    err_dump("Unknown request mode");

  if( modetype == MODE_ASCII )
    localfp = file_open(filename, (opcode == OP_RRQ) ? "r" : "w",0);
  else
    localfp = file_open(filename, (opcode == OP_RRQ) ? "rb" : "wb",0);

  if (localfp == NULL)
    send_ERROR(ERR_NOFILE, sys_err_str());  
  return(0);
}

  
/*
 * Send an error packet.
 */
static void send_ERROR(ecode, str)
int ecode;          /* error code, ERR_XXX from tftpdef.h */
char *str;          /* error message */
{
  DEBUG2("sending ERROR, code = %d, string = %s", ecode, str);
  
  stshort(OP_ERROR, sendbuff);
  stshort(ecode, sendbuff+2);

  strcpy(sendbuff+4, str);
  sendlen = 4 + strlen(sendbuff+4) + 1; /* 1 for null-termination */

  net_send(sendbuff, sendlen);
}


/*
 * Copy a string and convert it to lower case
 */
void strlccpy(dest, src)
register char *dest, *src;
{
  register char c;

  while ( (c = *src++) != '\0')
  {
    if (isupper(c))
      c = tolower(c);
    *dest++ = c;
  }
  *dest = '\0';
}

#endif /* SERVER */
    
