/****************************************************************************
**
** 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