/****************************************************************************
**
** Copyright (C) 2001-2006 Frank Hemer <frank@hemer.org>
**
**
**----------------------------------------------------------------------------
**
** This class implements the cvs pserver auth protocol as nonblocking
** and sspi login
**
**----------------------------------------------------------------------------
**
** CrossVC is available under two different licenses:
**
** If CrossVC is linked against the GPLed version of Qt 
** CrossVC is released under the terms of GPL also.
**
** If CrossVC is linked against a nonGPLed version of Qt 
** CrossVC is released under the terms of the 
** CrossVC License for non-Unix platforms (CLNU)
**
**
** CrossVC License for non-Unix platforms (CLNU):
**
** Redistribution and use in binary form, without modification, 
** are permitted provided that the following conditions are met:
**
** 1. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 2. It is not permitted to distribute the binary package under a name
**    different than CrossVC.
** 3. The name of the authors may not be used to endorse or promote
**    products derived from this software without specific prior written
**    permission.
** 4. The source code is the creative property of the authors.
**    Extensions and development under the terms of the Gnu Public License
**    are limited to the Unix platform. Any distribution or compilation of 
**    the source code against libraries licensed other than gpl requires 
**    the written permission of the authors.
**
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR 
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
** GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
**
**
** CrossVC License for Unix platforms:
**
** 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, version 2 of the License.
** 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 version 2 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
*****************************************************************************/

#include "config.h"

#include <qglobal.h>		//needed for define Q_WS_WIN
#include <qregexp.h>

#include "globals.h"
#include "login.h"
#include "LinCVSLog.h"

static const unsigned char shifts[] = {
   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
   16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
   114,120, 53, 79, 96,109, 72,108, 70, 64, 76, 67,116, 74, 68, 87,
   111, 52, 75,119, 49, 34, 82, 81, 95, 65,112, 86,118,110,122,105,
   41, 57, 83, 43, 46,102, 40, 89, 38,103, 45, 50, 42,123, 91, 35,
   125, 55, 54, 66,124,126, 59, 47, 92, 71,115, 78, 88,107,106, 56,
   36,121,117,104,101,100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48,
   58,113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85,223,
   225,216,187,166,229,189,222,188,141,249,148,200,184,136,248,190,
   199,170,181,204,138,232,218,183,255,234,220,247,213,203,226,193,
   174,172,228,252,217,201,131,230,197,211,145,238,161,179,160,212,
   207,221,254,173,202,146,224,151,140,196,205,130,135,133,143,246,
   192,159,244,239,185,168,215,144,139,165,180,157,147,186,214,176,
   227,231,219,169,175,156,206,198,129,164,150,210,154,177,134,127,
   182,128,158,208,162,132,167,209,149,241,153,251,237,236,171,195,
   243,233,253,240,194,250,191,155,142,137,245,235,163,242,178,152
};

//--------------------------------------------------------------------------------

Login::Login( CCvsOutput *messages, QString method, QString user, QString host, int port, QString dir)
   : QSocket(),
     pMessages(messages),
     m_method(method),
     m_user(user),
     m_host(host),
     m_port(port),
     m_dir(dir)
{
   loginDone = false;
   loginState = false;
   connect (this,SIGNAL(error(int)),this,SLOT(error(int)));
   connect (this,SIGNAL(connected()),this,SLOT(connected()));
   connect (this,SIGNAL(readyRead()),this,SLOT(readyRead()));
}

Login::~Login() {

}

//--------------------------------------------------------------------------------

bool Login::doLogin(QString pwd) {
   pMessages->setMode(CVS_LOGINANDOUT_CMD);
   pMessages->setText("");
   pMessages->setCursorPosition(0, 0);
   pMessages->append("trying to login: :"+m_method+":"+m_user+"@"+m_host+":"+QString::number(m_port)+m_dir+"\n");
   m_pwd = scramble(pwd);

   if (m_method == "sspi"){
     QString userCopy = m_user;
     userCopy.replace(QChar('\\'), "\\\\");
     pMessages->append("cmd (pwd scrambled):  \""+CVSPATH+"\" -d "+"\":"+m_method+":"+userCopy+"@"+m_host+":"+m_dir+"\" login -p \""+m_pwd+"\"\n");
     if (system("\""+CVSPATH+"\" -d "+"\":"+m_method+":"+userCopy+"@"+m_host+":"+m_dir+"\" login -p \""+pwd+"\"")) {
     }
   } else {
     //---------------- check for proxy ------------------------------
     proxyString = getProxy(m_user+"@"+m_host);
     int CVS_proxy_port = -1;

     if (!proxyString.isEmpty()) {
        int splitPos = proxyString.find(":");

        if (splitPos>0) {
           proxyPortString =  proxyString.mid(splitPos+1);
           if ( (CVS_proxy_port = proxyPortString.toInt()) > 0) {
              CVS_proxy = proxyString.left(splitPos);
           }
        }
     }

     if (!CVS_proxy.isEmpty()) {
        mode = 0;
	  
#ifdef Q_WS_WIN
        wconnect(CVS_proxy, CVS_proxy_port);
#else    
        connectToHost(CVS_proxy, CVS_proxy_port);
#endif
     } else {
        mode = 2;

#ifdef Q_WS_WIN
        wconnect(m_host, m_port);
#else    
        connectToHost(m_host, m_port);
#endif
     }

     //event loop for only returning after login procedure is done  
     //this is ugly, but avoids having to change the lincvs.cpp
     //command interface
     while ( (!loginDone) && (!KILLED) && (!globalStopAction)) {//loginDone, exit(), stopped
        qApp->processEvents();
     }

     if ( globalStopAction) {
        pMessages->append("\nAction interrupted by user\n");
     }
   
     if (loginState) {
        appendCvsPassEntry( &m_pwd);
     }
     m_pwd = "";
     close();
   }

   pMessages->setMode(-1);
   return loginState;
}

//--------------------------------------------------------------------------------

/* Return a  scrambled version of STR. */
QString Login::scramble (QString pwd) {
   QString scr_pwd = "A";
   for (int i=0; i<(int)pwd.length();i++) scr_pwd += shifts[(QChar)pwd[i]];
   return scr_pwd;
}

//--------------------------------------------------------------------------------

void Login::connected() {

   if (mode == 0) {//we have a proxy defined
      
      /* Send a "CONNECT" command to proxy: */
      QString connectString = "CONNECT " + m_host + ":" + QString::number(m_port) + " HTTP/1.0\r\n\r\n";
      if (writeBlock( connectString.latin1(), connectString.length()) == -1) {
         error( -1);
      } else {
         pMessages->append("(HTTP tunneling through "+CVS_proxy+":"+proxyPortString+")");
      }
   } else {//no proxy, run auth protocol
      if (!requestAuth()) {
         error( -1);
      }
   }
}

//--------------------------------------------------------------------------------

void Login::readyRead() {
   
   while (bytesAvailable()) {
      QChar ch = getch();
      // pMessages->append("received: "+ QString::number(ch));
      if ( ch == '\12') {
         lineReceived();
      } else {
         receive_buf += ch;
      }
   }
}

//--------------------------------------------------------------------------------

void Login::lineReceived() {

   switch (mode)
   {
   case 0: {//proxy connect
//       pMessages->append("mode 0 read");
//       pMessages->append("line received: "+receive_buf);

      if ( receive_buf.startsWith("HTTP/1.0") || receive_buf.startsWith("HTTP/1.1")) {
	
         QString hlp = receive_buf.mid(8).replace( QRegExp("\\D"),"");
         bool ok;
         int resultCode = hlp.toInt(&ok);
         if ( (!ok) || (resultCode != 200)) {//not a number or error code
            pMessages->append("Proxy server: " +CVS_proxy + ":"+proxyPortString + 
                              " does not support http tunneling to: " + m_host + 
                              ":" + QString::number(m_port) + "\n" + 
                              "The answer is: ");
            pMessages->append( receive_buf+"\n");
            receive_buf = "";
            mode = 9;
         } else {//Ok
            receive_buf = "";
            mode = 1;
         }
      } else {
         pMessages->append("Proxy answered with unsupported HTTP version number: " + receive_buf + "\n");
         error( -1);
      }
      break;
   }
   case 1: {//idle through mime header
//       pMessages->append("mode 1 read");
//       pMessages->append("line received: "+receive_buf);
      if (receive_buf.at(0) == '\r') {//till end of message
         receive_buf = "";
         mode = 2;
         if (!requestAuth()) {
            error( -1);
         }
      } else {//maybe more to ignore
         receive_buf = "";
      }
      break;
   }
   case 2: {//auth reply
//       pMessages->append("mode 2 read");
//       pMessages->append("line received: "+receive_buf);
      if ( readReply()) {//login succeeded
         loginState = true;
         loginDone = true;
      }
      break;
   }
   case 9: {//read error reply till end
      if (receive_buf.at(0) == '\r') {
         receive_buf = "";
         error( -1);//stop here, most proxy's give more info, but we
         //cut at this point for not having to parse proxy output
      } else {
         pMessages->append( receive_buf);
         receive_buf = "";
      }
      break;
   }
   }
}

//------------------------- send auth request --------------------------
bool Login::requestAuth() {

   char *begin = NULL;
   char *end   = NULL;
  
   begin = "BEGIN VERIFICATION REQUEST\012";
   end   = "END VERIFICATION REQUEST\012";
  
   /* Announce that we're starting the authorization protocol. */
   if (writeBlock( begin, strlen (begin)) < 0) {
      pMessages->append("cannot send: ");
      return false;
   }
   /* Send the data the server needs. */
   if (writeBlock( m_dir.latin1(), m_dir.length()) < 0) {//changed dir!!!
      pMessages->append("cannot send: ");
      return false;
   }
   if (writeBlock( "\012", 1) < 0) {
      pMessages->append("cannot send: ");
      return false;
   }
   if (writeBlock( m_user.latin1(), m_user.length()) < 0) {
      pMessages->append("cannot send: ");
      return false;
   }
   if (writeBlock( "\012", 1) < 0) {
      pMessages->append("cannot send: ");
      return false;
   }
   if (writeBlock( m_pwd.latin1(), m_pwd.length()) < 0) {
      pMessages->append("cannot send: ");
      return false;
   }
   if (writeBlock( "\012", 1) < 0) {
      pMessages->append("cannot send: ");
      return false;
   }
   /* Announce that we're ending the authorization protocol. */
   if (writeBlock( end, strlen (end)) < 0) {
      pMessages->append("cannot send: ");
      return false;
   }
   return true;//authRequest properly sent
}

//---------------------- read auth reply -------------------------------------

bool Login::readReply() {

   QString retval;

   if (receive_buf.find("I HATE YOU") == 0) {
      retval += "\nauthentication failed:\n";
      retval += "cvs server: "+receive_buf;
   }

   else if (receive_buf.find("E ") == 0) {
      retval += "\n"+receive_buf.mid(2);
      pMessages->append(retval);
      return false;//Continue with the authentication protocol.
   }

   else if (receive_buf.find("error ") == 0) {
    
      /* First skip the code.  */
      receive_buf = receive_buf.mid(6);
      int i = 0;
      while (receive_buf[i] != ' ' && receive_buf[i] != '\0') i++;
      if (receive_buf[i] == ' ') i++;
      receive_buf = receive_buf.mid(i);
    
      /* Now output the text.  */
      retval += "\n"+receive_buf;
   }

   else if (receive_buf.find("I LOVE YOU") == 0) {
      retval += "\nlogin succeeded:\n";
      retval += "cvs server: " + receive_buf;
      pMessages->append(retval);
      return true;
   }

   else {
      /* Unrecognized response from server. */
      retval = "\nunrecognized auth response from "+m_host+": "+receive_buf;
   }
   pMessages->append(retval);
   error( -1);
   return false;
}

//--------------------------------------------------------------------------------

void Login::error( int err) {
   switch (err)
   {
   case QSocket::ErrConnectionRefused: {
      pMessages->append("\nLogin ERROR: connection refused");
      break;
   }
   case QSocket::ErrHostNotFound: {
      pMessages->append("\nLogin ERROR: can't resolve host <"+m_host+">");
      break;
   }
   case QSocket::ErrSocketRead: {
      pMessages->append("\nLogin ERROR: can't read from socket");
      break;
   }
   }

   loginState = false;
   loginDone = true;
   close();//don't go any further
}

//--------------------------------------------------------------------------------

void Login::appendCvsPassEntry(QString *pwd) {
   QString cvsroot;
   QString cvsPassPath;
   if (CVSVERSION == "cvsnt") {
      cvsroot = ":pserver:"+m_user+"@"+m_host+":"+QString::number(m_port)+":"+m_dir;
      cvsPassPath = QDir::homeDirPath()+"/.cvs/cvspass";
   } else {
      cvsroot = ":pserver:"+m_user+"@"+m_host+":"+QString::number(m_port)+m_dir;
      cvsPassPath = CVSPASSPATH;
   }
   bool res = writeToCvsPassFile(cvsroot,*pwd);
   if (!res) {
      pMessages->append ("Couldn't append: "+cvsroot+" to "+cvsPassPath+"\n");
      Debug::g_pLog->log (Debug::LL_INFO, "Couldn't append: "+cvsroot+" to "+cvsPassPath);
      pMessages->append("Check if you have required rw permissions (0600 is required on unix systems)\n");
   } else {
      pMessages->append ("Added: "+cvsroot+" to registered logins\n");
      Debug::g_pLog->log (Debug::LL_INFO, "Added: "+cvsroot+" to registered logins");
      //appending of entry with port number succeeded...
      if (m_port == 2401) {//add an entry without port number for old cvs versions
         cvsroot = ":pserver:"+m_user+"@"+m_host+":"+m_dir;
         bool res = writeToCvsPassFile(cvsroot,*pwd);
         if (!res) {//adding failed --> and now? At the moment a log entry is enough
            Debug::g_pLog->log(Debug::LL_INFO, "Couldn't append: "+cvsroot+" to "+cvsPassPath);
         }
      }
   }
}

//--------------------------------------------------------------------------------

bool Login::removeCvsPassEntry() {
   QString cvsroot = ":"+m_method+":"+m_user+"@"+m_host+":"+QString::number(m_port)+m_dir;
   bool res = removeFromCvsPassFile(cvsroot);
   pMessages->setMode(CVS_LOGINANDOUT_CMD);
   if (!res) {
      pMessages->append("Login: "+cvsroot+" not found\n");
      Debug::g_pLog->log(Debug::LL_INFO, "Login: "+cvsroot+" not found");
   } else {
      pMessages->append("Login: "+cvsroot+" removed\n");
      Debug::g_pLog->log(Debug::LL_INFO, "Login: "+cvsroot+" removed");
   }
   pMessages->setMode(-1);

   if (m_port == 2401) {//remove the portless entry
      cvsroot = ":"+m_method+":"+m_user+"@"+m_host+":"+m_dir;
      bool resNoPort = removeFromCvsPassFile(cvsroot);
      if (!resNoPort) {
         Debug::g_pLog->log(Debug::LL_INFO, "Login: "+cvsroot+" not found");
      }
   }

   return res;
}

//--------------------------------------------------------------------------------



syntax highlighted by Code2HTML, v. 0.9.1