kde-extraapps/kdevplatform/util/ssh_client/main.cpp
2015-07-26 14:23:17 +03:00

252 lines
8.9 KiB
C++

/***************************************************************************
* Copyright 2009 Evgeniy Ivanov <powerfox@kde.ru> *
* *
* 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) version 3 or any later version *
* accepted by the membership of KDE e.V. (or its successor approved *
* by the membership of KDE e.V.), which shall act as a proxy *
* defined in Section 14 of version 3 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 for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
***************************************************************************/
/* TODO:
- crossplatform sockets? QTcpSocket can't be used, see comments in the code
- check fingerprint using libssh2_hostkey_hash.
- support keyboard-interactive auth.
- kde wallet support
- FIXME: integrate to kdessh from kdeutils? Requires moving kdessh to kdebase, also
it has similar functionality, but I failed to make it work correctly.
*/
#include "main.h"
#include <KDE/KApplication>
#include <KDE/KAboutData>
#include <KDE/KCmdLineArgs>
#include <KDE/KMessageBox>
#include <QtNetwork/QTcpSocket>
#include <cstring>
#include <string>
#include <memory>
#include <cerrno>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <libssh2.h>
void show_error(const QString &error)
{
KMessageBox::error(0, error);
}
void shutdown_ssh(LIBSSH2_SESSION *session, const QString &error = "")
{
if (!error.isEmpty())
show_error(error);
libssh2_session_disconnect(session, "See you later, alligator!");
libssh2_session_free(session);
}
int main (int argc, char *argv[])
{
KAboutData aboutData(
"kssh_client",
0,
ki18n("Simple KDE ssh client provider"),
"0.1",
ki18n("Displays a KDE Password Dialog and establishes an ssh connection using libssh. After connecting it executes 'svnserve -t' to provide tunneling."),
KAboutData::License_GPL,
ki18n("(c) 2009"),
ki18n("Use this client instead of standard ssh when using subversion to provide a KDE Password Dialog"
" instead of a command-line ssh password prompt."),
"http://kde.org",
"submit@bugs.kde.org");
KCmdLineArgs::init( argc, argv, &aboutData );
//according to docs we should be invoked as following:
//<command> <hostname> svnserve -t
KCmdLineOptions k_options;
k_options.add("+hostname", ki18n("Hostname to connect"));
k_options.add("+subversioncommand", ki18n("Subversion command to call")); //FIXME anywhere always svnserve -t
k_options.add("t", ki18n("svnserve option"));
KCmdLineArgs::addCmdLineOptions( k_options );
KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
KApplication app;
std::string username;
std::string host;
if (args->arg(0).contains('@'))
{
username = args->arg(0).split('@')[0].toStdString();
host = args->arg(0).split('@')[1].toStdString();
}
else
host = args->arg(0).toStdString();
const unsigned int port = 22;
//QTcpSocket makes libssh2_session_startup failed with *EAGAIN
//QTcpSocket::readReady doesn't help, since socket is used multiple times inside libssh2_session_startup
int sock;
struct sockaddr_in addr;
sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
show_error(i18n("Cannot create socket, operation aborted."));
return 1;
}
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
struct hostent *he;
if (!(he = gethostbyname(host.c_str()) ) )
{
show_error(i18n("Get host by name failed, operation aborted."));
return 1;
}
addr.sin_addr = *((struct in_addr *)he->h_addr);
if(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("connect");
show_error(i18n("Cannot connect to host, operation aborted."));
return 1;
}
LIBSSH2_SESSION *session;
if (!(session = libssh2_session_init()) )
{
shutdown_ssh(session, i18n("Cannot start an ssh session, operation aborted."));
return 1;
}
if (libssh2_session_startup(session, sock)) {
shutdown_ssh(session, i18n("SSH connection failed, operation aborted."));
return 1;
}
char* auth_methods = 0;
if (!(auth_methods = libssh2_userauth_list(session, username.c_str(), username.size()+1)))
{
shutdown_ssh(session, i18n("Server does not support any authentication methods for the user, operation aborted."));
return 1;
}
kDebug() << "Supported auth methods:" << auth_methods;
unsigned int auth_mask = 0;
QStringList supported_auth_methods = QString(auth_methods).split(',');
if (supported_auth_methods.contains("password") )
auth_mask |= 1;
if (supported_auth_methods.contains("publickey") )
auth_mask |= 2;
AuthForm *form = new AuthForm(QString(username.c_str()), AuthForm::AuthType(auth_mask));
KDialog *dialog = new KDialog();
dialog->setCaption(i18n("ssh auth"));
dialog->setButtons(KDialog::Ok);
dialog->setMainWidget(form);
dialog->exec();
username = form->username->text().toStdString();
const std::string passphrase = form->password_or_passphrase->text().toStdString();
const std::string pub_key_filename = form->pub_key->text().toStdString();
const std::string private_key_filename = form->private_key->text().toStdString();
const uint auth_type = form->select_auth_type->itemData(form->select_auth_type->currentIndex()).toInt();
int auth;
switch(auth_type)
{
case AuthForm::PASSWORD:
auth = libssh2_userauth_password(session, username.c_str(), passphrase.c_str());
break;
case AuthForm::PUBKEY:
auth = libssh2_userauth_publickey_fromfile(session, username.c_str(), pub_key_filename.c_str(),
private_key_filename.c_str(), passphrase.c_str());
break;
default:
//shouldn't happen
shutdown_ssh(session, i18n("Unsupported authentication type, could possibly be a bug. Operation aborted."));
return 1;
}
if (auth)
{
shutdown_ssh(session, i18n("Authentication failed, operation aborted."));
return 1;
}
LIBSSH2_CHANNEL *channel;
if (!(channel = libssh2_channel_open_session(session))) {
shutdown_ssh(session, i18n("Cannot open ssh channel, operation aborted."));
return 1;
}
if(libssh2_channel_exec(channel, "svnserve -t") )
{
shutdown_ssh(session, i18n("Failed to launch 'svnserve -t', operation aborted."));
return 1;
}
char remotebuf[BUFSIZ];
char localbuf[BUFSIZ];
int readremote, readlocal;
//stdin shoudn't block, since svn can wait for some data from remote server
int flags = fcntl(0, F_GETFL, 0);
if (fcntl(0, F_SETFL, flags | O_NONBLOCK))
{
shutdown_ssh(session, i18n("Cannot set non-blocking mode for stdin, operation aborted."));
return 1;
}
while ((readremote = libssh2_channel_read(channel, remotebuf, BUFSIZ)) > 0 ||
readremote == LIBSSH2_ERROR_EAGAIN)
{
//Don't block after first read, since server can wait svn sent some info.
libssh2_channel_set_blocking(channel, 0);
if (readremote > 0)
{
int written = 0;
while ( (written += write(1, remotebuf, readremote)) < readremote) ;
}
readlocal = read(0,localbuf,BUFSIZ); //FIXME: Check EAGAIN and other errors?
if (readlocal > 0)
{
libssh2_channel_write(channel, localbuf, readlocal);
}
}
libssh2_channel_close(channel);
//libssh2_channel_wait_closed(channel); //doubt we need it
libssh2_channel_free(channel);
libssh2_session_disconnect(session, "Goodbye");
libssh2_session_free(session);
return 0;
}