/*/////////////////////////////////////////////////////////////////////////////
Name: Bo Bayles
Assignment: CS 285, FTP Project
File: client.cpp
Compile: g++ -o client client.cpp
/*/////////////////////////////////////////////////////////////////////////////

/*/////////////////////////////////////////////////////////////////////////////
    Compiler Directives
/////////////////////////////////////////////////////////////////////////////*/
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <netinet/in.h>
#include <netdb.h>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <time.h>
using namespace std;

#define SERVER_PORT 2221

/*/////////////////////////////////////////////////////////////////////////////
    Data type definitions
/////////////////////////////////////////////////////////////////////////////*/


/*/////////////////////////////////////////////////////////////////////////////
    Function declarations
/////////////////////////////////////////////////////////////////////////////*/
int createDataConnection( int &dataPort,
                          int &dataSocket,
                          struct sockaddr_in &clientAddress );
void decryptFile(string &localDir, string &fileName, string &password);
void encryptPassword( string &username,
                      string &password,
                      string &encryptedPassword );
int receiveFile( int &dataSocket,
                 string &fileName,
                 string &localDir,
                 string &password );

/*/////////////////////////////////////////////////////////////////////////////
    Function definitions
/////////////////////////////////////////////////////////////////////////////*/
int main( int argc, char* argv[] )
{
  if( argc != 5 )
  {
    cout << "Usage: " << argv[0] << "server_hostname server_port local_dir"
         << " remote_dir" << endl;
    exit( 1 );
  }
  //Check usage

  string serverHostname = argv[1];
  int serverPort = atoi( argv[2] );
  string localDir = (string)"./" + argv[3];
  string remoteDir = (string)"./" + argv[4];
  //Get arguments into useful form
  
  srand( time(NULL) ); //Seed the randomizer for the port generation

  bool firstTime = true;
  bool finished = false;
  bool skip = false;
  char buf[512];
  int controlSocket;
  int dataSocket = -1;
  int dataPort = 0;
  string fileName, username, password, encryptedPassword, tempString;
  struct sockaddr_in serverAddress = { AF_INET, htons(serverPort) };
  struct sockaddr_in clientAddress = {AF_INET};
  struct hostent *hostPath;
  //Declare variables for connection and communication

  hostPath = gethostbyname( serverHostname.c_str() ); //Get server information
  if( hostPath == NULL )
  {
    cout << "bbFTP: " << argv[0] << " " << argv[1] << " unknown host." << endl;
    exit(1);
  }
  bcopy( hostPath->h_addr_list[0],
         (char*)&serverAddress.sin_addr,
         hostPath->h_length );
  //Get host information into useful form

  controlSocket = socket(AF_INET, SOCK_STREAM, 0);
  if( controlSocket == -1 )
  {
    cout << "bbFTP: Socket creation failed." << endl;
    exit(1);
  }
  cout << "bbFTP: Socket " << controlSocket << " was created successfully." << endl;
  //Create a socket

  if( connect( controlSocket, (struct sockaddr*)&serverAddress,
      sizeof(serverAddress) ) == -1 )
  {
    cout << "bbFTP: Connection failed." << endl;
    close(controlSocket);
    exit(1);
  }
  cout << "bbFTP: Connected to server." << endl;
  //Connect to the server

  while(!finished)
  {
    while(firstTime)
    {
      cout << "\nbbFTP: Enter your user name." << endl << "bbFTP> ";;
      cin.getline( buf, sizeof(buf), '\n' );
      username = buf;
      tempString = "USER " + username;
      write( controlSocket, tempString.c_str(), tempString.size()+1 );
      read( controlSocket, buf, sizeof(buf) );

      cout << "\nbbFTP: Enter your password:" << endl << "bbFTP> ";
      cin.getline(buf, sizeof(buf));
      password = buf;
      encryptPassword(username, password, encryptedPassword);
      tempString = "PASS " + encryptedPassword;
      write( controlSocket, tempString.c_str(), tempString.size()+1 );
      read( controlSocket, buf, sizeof(buf) );

      if( !strncmp(buf, "200", 3) )
      {
        cout << "bbFTP: Login successful." << endl;
        firstTime = false;
      }
      else
      {
        cout << "Login unsuccessful." << endl;
      }
    }
    
    skip = false;
    cout << "bbFTP> ";
    cin.getline( buf, sizeof(buf), '\n' );
    
    if( !strncmp(buf, "USER", 4) || !strncmp(buf, "PASS", 4) )
    {
      skip = true;
      cout << "bbFTP: You are already logged in as " << username << endl;
    }
    else if( !strncmp(buf, "PORT", 4) )
    {
      cout << "bbFTP: " << buf << " was specified, but bbFTP does not "
           << "allow you to specify a port with the PORT command. An "
           << " ephemeral port will be used." << endl;

      tempString = buf;
      tempString.erase(0,5);
      dataPort =  (rand() % 65536) + 1024; //Get a random port number
      //Could call bind() with a port of 0 to get a random port, but
      //  that would require listen()ing to get the port number with
      //  getsockname(), which would mess up this whole sequence.
      
      cout << "bbFTP: Using port " << dataPort << endl;
      sprintf(buf, "PORT %u", dataPort);

      if( createDataConnection( dataPort, dataSocket, clientAddress ) == 1 )
      {
        sprintf(buf, "NULL");
        cout << "bbFTP: Data socket creation failed." << endl;
      }
      //If the data socket creation failed, don't send a bad port number to
      //  the server and try to fail gracefully.
    }
    else if( !strncmp(buf, "RETR", 4) )
    {
      tempString = buf;
      fileName = tempString.erase(0,5);
      tempString = "RETR " + remoteDir + (string)"/" + fileName;
      sprintf( buf, "%s", tempString.c_str() );
    }
    
    if(!skip)
      write( controlSocket, buf, sizeof(buf) ); //Send command to server

    if( !strncmp(buf, "RETR", 4) )
    {
      read( controlSocket, buf, sizeof(buf) );
      cout << "Server: " << buf << endl;
      if( !strncmp(buf, "200", 3) )
      {
        cout << "bbFTP: File transfer to begin." << endl;
        if ( dataSocket != -1) //Make sure we sent PORT first
          receiveFile(dataSocket, fileName, localDir, password);
      }
      else
        skip = true;      
      dataSocket = -1;
    }
    else if( !strncmp(buf, "QUIT", 4) )
    {
      finished = true;
    }
    
    if(!skip)
    {
      read( controlSocket, buf, sizeof(buf) );
      cout << "Server: " << buf << endl;
    }
    
    if ( !strncmp(buf, "666", 3) ) //Server shut down
      finished = true;
  }

  if ( shutdown(controlSocket, 2) != -1 )
    cout << "bbFTP: Shutdown successful." << endl;

  close(controlSocket); //Close the connection

  return 0;
}

void decryptFile(string &localDir, string &fileName, string &password)
{
  string argument = (string)"openssl des3 -d -in client_cipher.tmp -out \"" +
                    localDir + (string)"/" + fileName + (string)"\" -k " +
                    password;
  cout << "SYSTEM: " << argument << endl;
  system( argument.c_str() );
  
  system("rm -rf client_cipher.tmp");

  return;
}


void encryptPassword(string &username, string &password, string &encryptedPassword)
{
  ofstream tempPlain;
  ifstream tempCipher;
  char buffer[256];

  
  tempPlain.open("client_plain.tmp", ios::trunc);
  tempPlain << password;
  //cout << "PLAINTEXT PASSWORD: " << password << endl;
  tempPlain.close();
  
  string argument = "openssl des3 -in client_plain.tmp -out client_cipher.tmp -k " + username;
  system( argument.c_str() );
  
  tempCipher.open("client_cipher.tmp");
  tempCipher.getline( buffer, 256 );
  string tempString ( buffer, tempCipher.gcount() );
  encryptedPassword = tempString;
  //cout << "ENCRYPTED PASSWORD: " << buffer << " , " << encryptedPassword << endl;
  tempCipher.close();
  
  system("rm -rf client_cipher.tmp");
  system("rm -rf client_plain.tmp");
  
  return;
}

int createDataConnection( int &dataPort, int &dataSocket, struct sockaddr_in &clientAddress )
{

  clientAddress.sin_family = AF_INET;
  clientAddress.sin_port = htons(dataPort);
  //Set up connection parameters with Berkeley Sockets API

  dataSocket = socket( AF_INET, SOCK_STREAM, 0 );
  if( dataSocket == -1 )
  {
    cout << "bbFTP: socket() failed"  << endl
    << "Exiting..." << endl;
    return 1;
  }
  //Create a stream socket with socket()
  cout << "bbFTP: Socket " << dataSocket << " was created." << endl;

  if( bind( dataSocket,
            (struct sockaddr*)&clientAddress,
            sizeof(clientAddress) ) == -1 )
  {
    cout << "bbFTP: bind() failed"  << endl
    << "Exiting..." << endl;
    return 1;
  }
  //Bind the socket to an internet port with bind()
  cout << "bbFTP: bind() was successful with port " << dataPort << "." << endl;

  return 0;
}

int receiveFile( int &dataSocket,
                 string &fileName,
                 string &localDir,
                 string &password )
{
  char buffer[50];
  int txSocket;
  string message;
  struct sockaddr_in serverAddress = {AF_INET};
  
  int server_len = sizeof(serverAddress);

  if( listen( dataSocket, 1 ) == -1 )
  {
    cout << "bbFTP: listen() failed"  << endl;
    return 1;
  }
  //Listen for clients with listen()
  cout << "bbFTP: Listening for clients..." << endl;
  
  txSocket = accept( dataSocket,
                     (struct sockaddr*)&serverAddress,
                     (socklen_t*)&server_len );
  if(txSocket < 0)
  {
    cout << "accept() failed" << endl;
    return 1;
  }
  else
    cout << "bbFTP: Server connected to data socket" << endl;

/*/////////////////////////////////////////////////////////////////////////////
    File transfer
/////////////////////////////////////////////////////////////////////////////*/
  int k;
  ofstream outFile;
  outFile.open("client_cipher.tmp", ios::trunc);
  while(1)
  {
    k = read( txSocket, buffer, sizeof(buffer) );
    buffer[k] = (char)EOF;
    //cout << "RECEIVED '" << buffer << "' SIZE:" << k << endl;
    if (k == 0)
      break;
    outFile.write( buffer, k );
  }
  outFile.close();
  decryptFile(localDir, fileName, password);
///////////////////////////////////////////////////////////////////////////////

  if ( shutdown(dataSocket, 2) != -1)
    cout << "bbFTP: Shutting down dataSocket connection..." << endl;
  close(dataSocket);

  if ( shutdown(dataSocket, 2) != -1)
    cout << "bbFTP: Shutting down txSocket connection..." << endl;
  close(txSocket);

  cout << "bbFTP: File transfer over." << endl;

  return 0;
}

