00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include <config-kleopatra.h>
00023
00024 #include "command.h"
00025 #include "command_p.h"
00026
00027 #include <QtGlobal>
00028
00029 #ifdef Q_OS_WIN // HACK: AllowSetForegroundWindow needs _WIN32_WINDOWS >= 0x0490 set
00030 # ifndef _WIN32_WINDOWS
00031 # define _WIN32_WINDOWS 0x0500
00032 # define _WIN32_WINNT 0x0500 // good enough for Vista too
00033 # endif
00034 # include <utils/gnupg-registry.h>
00035 # include <windows.h>
00036 #endif
00037
00038 #include <QMutexLocker>
00039 #include <QFile>
00040 #include <QDebug>
00041 #include <QDir>
00042
00043 #include <assuan.h>
00044 #include <gpg-error.h>
00045
00046 #include <boost/shared_ptr.hpp>
00047 #include <boost/type_traits/remove_pointer.hpp>
00048
00049 #include <algorithm>
00050 #include <string>
00051
00052 using namespace KLEOPATRACLIENT_NAMESPACE;
00053 using namespace boost;
00054
00055
00056 static std::string hexencode( const std::string & in ) {
00057 std::string result;
00058 result.reserve( 3 * in.size() );
00059
00060 static const char hex[] = "0123456789ABCDEF";
00061
00062 for ( std::string::const_iterator it = in.begin(), end = in.end() ; it != end ; ++it )
00063 switch ( const unsigned char ch = *it ) {
00064 default:
00065 if ( ch >= '!' && ch <= '~' || ch > 0xA0 ) {
00066 result += ch;
00067 break;
00068 }
00069
00070 case ' ':
00071 result += '+';
00072 break;
00073 case '"':
00074 case '#':
00075 case '$':
00076 case '%':
00077 case '\'':
00078 case '+':
00079 case '=':
00080 result += '%';
00081 result += hex[ (ch & 0xF0) >> 4 ];
00082 result += hex[ (ch & 0x0F) ];
00083 break;
00084 }
00085
00086 return result;
00087 }
00088
00089 #ifdef UNUSED
00090 static std::string hexencode( const char * in ) {
00091 if ( !in )
00092 return std::string();
00093 return hexencode( std::string( in ) );
00094 }
00095 #endif
00096
00097 static QByteArray hexencode( const QByteArray & in ) {
00098 if ( in.isNull() )
00099 return QByteArray();
00100 const std::string result = hexencode( std::string( in.constData() ) );
00101 return QByteArray( result.data(), result.size() );
00102 }
00103
00104
00105 Command::Command( QObject * p )
00106 : QObject( p ), d( new Private( this ) )
00107 {
00108 d->init();
00109 }
00110
00111 Command::Command( Private * pp, QObject * p )
00112 : QObject( p ), d( pp )
00113 {
00114 d->init();
00115 }
00116
00117 Command::~Command() {
00118 delete d; d = 0;
00119 }
00120
00121 void Command::Private::init() {
00122 connect( this, SIGNAL(started()), q, SIGNAL(started()) );
00123 connect( this, SIGNAL(finished()), q, SIGNAL(finished()) );
00124 }
00125
00126 void Command::setParentWId( WId wid ) {
00127 const QMutexLocker locker( &d->mutex );
00128 d->inputs.parentWId = wid;
00129 }
00130
00131 WId Command::parentWId() const {
00132 const QMutexLocker locker( &d->mutex );
00133 return d->inputs.parentWId;
00134 }
00135
00136
00137 void Command::setServerLocation( const QString & location ) {
00138 const QMutexLocker locker( &d->mutex );
00139 d->outputs.serverLocation = location;
00140 }
00141
00142 QString Command::serverLocation() const {
00143 const QMutexLocker locker( &d->mutex );
00144 return d->outputs.serverLocation;
00145 }
00146
00147
00148 bool Command::waitForFinished() {
00149 return d->wait();
00150 }
00151
00152 bool Command::waitForFinished( unsigned long ms ) {
00153 return d->wait( ms );
00154 }
00155
00156
00157 bool Command::error() const {
00158 const QMutexLocker locker( &d->mutex );
00159 return !d->outputs.errorString.isEmpty();
00160 }
00161
00162 bool Command::wasCanceled() const {
00163 const QMutexLocker locker( &d->mutex );
00164 return d->outputs.canceled;
00165 }
00166
00167 QString Command::errorString() const {
00168 const QMutexLocker locker( &d->mutex );
00169 return d->outputs.errorString;
00170 }
00171
00172
00173 qint64 Command::serverPid() const {
00174 const QMutexLocker locker( &d->mutex );
00175 return d->outputs.serverPid;
00176 }
00177
00178
00179 void Command::start() {
00180 d->start();
00181 }
00182
00183 void Command::cancel() {
00184 qDebug( "Sorry, not implemented: KleopatraClient::Command::Cancel" );
00185 }
00186
00187
00188 void Command::setOptionValue( const char * name, const QVariant & value, bool critical ) {
00189 if ( !name || !*name )
00190 return;
00191 const QMutexLocker locker( &d->mutex );
00192 const Private::Option opt = {
00193 value,
00194 true,
00195 critical
00196 };
00197 d->inputs.options[name] = opt;
00198 }
00199
00200 QVariant Command::optionValue( const char * name ) const {
00201 if ( !name || !*name )
00202 return QVariant();
00203 const QMutexLocker locker( &d->mutex );
00204
00205 const std::map<std::string,Private::Option>::const_iterator it = d->inputs.options.find( name );
00206 if ( it == d->inputs.options.end() )
00207 return QVariant();
00208 else
00209 return it->second.value;
00210 }
00211
00212
00213 void Command::setOption( const char * name, bool critical ) {
00214 if ( !name || !*name )
00215 return;
00216 const QMutexLocker locker( &d->mutex );
00217
00218 if ( isOptionSet( name ) )
00219 unsetOption( name );
00220
00221 const Private::Option opt = {
00222 QVariant(),
00223 false,
00224 critical
00225 };
00226
00227 d->inputs.options[name] = opt;
00228 }
00229
00230 void Command::unsetOption( const char * name ) {
00231 if ( !name || !*name )
00232 return;
00233 const QMutexLocker locker( &d->mutex );
00234 d->inputs.options.erase( name );
00235 }
00236
00237 bool Command::isOptionSet( const char * name ) const {
00238 if ( !name || !*name )
00239 return false;
00240 const QMutexLocker locker( &d->mutex );
00241 return d->inputs.options.count( name );
00242 }
00243
00244 bool Command::isOptionCritical( const char * name ) const {
00245 if ( !name || !*name )
00246 return false;
00247 const QMutexLocker locker( &d->mutex );
00248 const std::map<std::string,Private::Option>::const_iterator it = d->inputs.options.find( name );
00249 return it != d->inputs.options.end() && it->second.isCritical;
00250 }
00251
00252 void Command::setFilePaths( const QStringList & filePaths ) {
00253 const QMutexLocker locker( &d->mutex );
00254 d->inputs.filePaths = filePaths;
00255 }
00256
00257 QStringList Command::filePaths() const {
00258 const QMutexLocker locker( &d->mutex );
00259 return d->inputs.filePaths;
00260 }
00261
00262 QByteArray Command::receivedData() const {
00263 const QMutexLocker locker( &d->mutex );
00264 return d->outputs.data;
00265 }
00266
00267
00268 void Command::setCommand( const char * command ) {
00269 const QMutexLocker locker( &d->mutex );
00270 d->inputs.command = command;
00271 }
00272
00273 QByteArray Command::command() const {
00274 const QMutexLocker locker( &d->mutex );
00275 return d->inputs.command;
00276 }
00277
00278
00279
00280
00281
00282 typedef shared_ptr< remove_pointer<assuan_context_t>::type > AssuanContextBase;
00283 namespace {
00284 struct AssuanClientContext : AssuanContextBase {
00285 AssuanClientContext() : AssuanContextBase() {}
00286 explicit AssuanClientContext( assuan_context_t ctx ) : AssuanContextBase( ctx, &assuan_disconnect ) {}
00287 void reset( assuan_context_t ctx=0 ) { AssuanContextBase::reset( ctx, &assuan_disconnect ); }
00288 };
00289 }
00290
00291 static assuan_error_t
00292 my_assuan_transact( const AssuanClientContext & ctx,
00293 const char *command,
00294 int (*data_cb)( void *, const void *, size_t )=0,
00295 void * data_cb_arg=0,
00296 int (*inquire_cb)( void *, const char * )=0,
00297 void * inquire_cb_arg=0,
00298 int (*status_cb)( void *, const char * )=0,
00299 void * status_cb_arg=0)
00300 {
00301 return assuan_transact( ctx.get(), command, data_cb, data_cb_arg, inquire_cb, inquire_cb_arg, status_cb, status_cb_arg );
00302 }
00303
00304 static QString to_error_string( int err ) {
00305 char buffer[1024];
00306 gpg_strerror_r( static_cast<gpg_error_t>(err),
00307 buffer, sizeof buffer );
00308 buffer[sizeof buffer - 1] = '\0';
00309 return QString::fromLocal8Bit( buffer );
00310 }
00311
00312 static QString gnupg_home_directory() {
00313 #ifdef Q_OS_WIN
00314 return QFile::decodeName( default_homedir() );
00315 #else
00316 const QByteArray gnupgHome = qgetenv( "GNUPGHOME" );
00317 if ( !gnupgHome.isEmpty() )
00318 return QFile::decodeName( gnupgHome );
00319 else
00320 return QDir::homePath() + QLatin1String( "/.gnupg" );
00321 #endif
00322 }
00323
00324 static QString get_default_socket_name() {
00325 const QString homeDir = gnupg_home_directory();
00326 if ( homeDir.isEmpty() )
00327 return QString();
00328 return QDir( homeDir ).absoluteFilePath( QLatin1String( "S.uiserver" ) );
00329 }
00330
00331 static QString default_socket_name() {
00332 static QString name = get_default_socket_name();
00333 return name;
00334 }
00335
00336 static QString start_uiserver() {
00337 return Command::tr("start_uiserver: not yet implemented");
00338 }
00339
00340 static int getinfo_pid_cb( void * opaque, const void * buffer, size_t length ) {
00341 qint64 & pid = *static_cast<qint64*>( opaque );
00342 pid = QByteArray( static_cast<const char*>( buffer ), length ).toLongLong();
00343 return 0;
00344 }
00345
00346 static int command_data_cb( void * opaque, const void * buffer, size_t length ) {
00347 QByteArray & ba = *static_cast<QByteArray*>( opaque );
00348 ba.append( QByteArray( static_cast<const char*>(buffer), length ) );
00349 return 0;
00350 }
00351
00352 static int send_option( const AssuanClientContext & ctx, const char * name, const QVariant & value ) {
00353 if ( value.isValid() )
00354 return my_assuan_transact( ctx, QString().sprintf( "OPTION %s=%s", name, value.toString().toUtf8().constData() ).toUtf8().constData() );
00355 else
00356 return my_assuan_transact( ctx, QString().sprintf( "OPTION %s", name ).toUtf8().constData() );
00357 }
00358
00359 static int send_file( const AssuanClientContext & ctx, const QString & file ) {
00360 return my_assuan_transact( ctx, QString().sprintf( "FILE %s", hexencode( QFile::encodeName( file ) ).constData() ).toUtf8().constData() );
00361 }
00362
00363 void Command::Private::run() {
00364
00365
00366 Inputs in;
00367 Outputs out;
00368 {
00369 const QMutexLocker locker( &mutex );
00370 in = inputs;
00371 outputs = out;
00372 }
00373
00374 out.canceled = false;
00375
00376 int err = 0;
00377
00378 assuan_context_t naked_ctx = 0;
00379 AssuanClientContext ctx;
00380
00381 if ( out.serverLocation.isEmpty() )
00382 out.serverLocation = default_socket_name();
00383
00384 const QString socketName = out.serverLocation;
00385 if ( socketName.isEmpty() ) {
00386 out.errorString = tr("Invalid socket name!");
00387 goto leave;
00388 }
00389
00390 err = assuan_socket_connect( &naked_ctx, QFile::encodeName( socketName ).constData(), -1 );
00391 if ( err ) {
00392 qDebug( "UI server not running, starting it" );
00393
00394 const QString errorString = start_uiserver();
00395 if ( !errorString.isEmpty() ) {
00396 out.errorString = errorString;
00397 goto leave;
00398 }
00399
00400
00401 for ( int i = 0 ; err && i < 20 ; ++i ) {
00402 msleep( 500 );
00403 err = assuan_socket_connect( &naked_ctx, QFile::encodeName( socketName ).constData(), -1 );
00404 }
00405 }
00406
00407 if ( err ) {
00408 out.errorString = tr( "Could not connect to Kleopatra UI server at %1: %2" )
00409 .arg( socketName, to_error_string( err ) );
00410 goto leave;
00411 }
00412
00413 ctx.reset( naked_ctx );
00414 naked_ctx = 0;
00415
00416 out.serverPid = -1;
00417 err = my_assuan_transact( ctx, "GETINFO pid", &getinfo_pid_cb, &out.serverPid );
00418 if ( err || out.serverPid <= 0 ) {
00419 out.errorString = tr( "Could not get the process-id of the Kleopatra UI server at %1: %2" )
00420 .arg( socketName, to_error_string( err ) );
00421 goto leave;
00422 }
00423
00424 qDebug() << "Server PID =" << out.serverPid;
00425
00426 #ifdef Q_OS_WIN
00427 if ( !AllowSetForegroundWindow( (pid_t)out.serverPid ) )
00428 qDebug() << "AllowSetForegroundWindow(" << out.serverPid << ") failed: " << GetLastError();
00429 #endif
00430
00431 if ( in.command.isEmpty() )
00432 goto leave;
00433
00434 if ( in.parentWId ) {
00435 #ifdef Q_OS_WIN32
00436 err = send_option( ctx, "window-id", QString().sprintf( "%lx", reinterpret_cast<unsigned long>( in.parentWId ) ) );
00437 #else
00438 err = send_option( ctx, "window-id", QString().sprintf( "%lx", static_cast<unsigned long>( in.parentWId ) ) );
00439 #endif
00440 if ( err )
00441 qDebug( "sending option window-id failed - ignoring" );
00442 }
00443
00444 for ( std::map<std::string,Option>::const_iterator it = in.options.begin(), end = in.options.end() ; it != end ; ++it )
00445 if ( ( err = send_option( ctx, it->first.c_str(), it->second.hasValue ? it->second.value.toString() : QVariant() ) ) )
00446 if ( it->second.isCritical ) {
00447 out.errorString = tr("Failed to send critical option %1: %2")
00448 .arg( QString::fromLatin1( it->first.c_str() ), to_error_string( err ) );
00449 goto leave;
00450 } else {
00451 qDebug() << "Failed to send non-critical option" << it->first.c_str() << ":" << to_error_string( err );
00452 }
00453
00454 Q_FOREACH( const QString & filePath, in.filePaths )
00455 if ( ( err = send_file( ctx, filePath ) ) ) {
00456 out.errorString = tr("Failed to send file path %1: %2")
00457 .arg( filePath, to_error_string( err ) );
00458 goto leave;
00459 }
00460
00461 #if 0
00462 setup I/O;
00463 #endif
00464
00465 err = my_assuan_transact( ctx, in.command.constData(), &command_data_cb, &out.data );
00466 if ( err ) {
00467 if ( gpg_err_code( err ) == GPG_ERR_CANCELED )
00468 out.canceled = true;
00469 else
00470 out.errorString = tr( "Command (%1) failed: %2" )
00471 .arg( QString::fromLatin1( in.command.constData() ) ).arg( to_error_string( err ) );
00472 goto leave;
00473 }
00474
00475
00476 leave:
00477 const QMutexLocker locker( &mutex );
00478
00479 outputs = out;
00480 }
00481
00482 #include "moc_command_p.cpp"
00483 #include "moc_command.cpp"