2017-11-07 22:55:28 +00:00
/*************************************************************************
2018-03-22 16:41:49 +00:00
* Copyright ( C ) 2017 - 2018 by Andrius Š tikonas < andrius @ stikonas . eu > *
2017-11-07 22:55:28 +00:00
* *
* 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 3 of *
* the License , or ( at your option ) any later version . *
* *
* 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/>.*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include "externalcommandhelper.h"
2018-03-19 10:33:20 +00:00
# include <QtDBus>
2017-11-07 22:55:28 +00:00
# include <QDebug>
2018-02-03 15:57:47 +00:00
# include <QFile>
2018-01-24 15:22:42 +00:00
# include <QString>
2018-01-27 18:54:48 +00:00
# include <QTime>
# include <QVariant>
# include <KLocalizedString>
2018-03-19 10:33:20 +00:00
/** Initialize ExternalCommandHelper Daemon and prepare DBus interface
2018-07-15 00:09:39 +01:00
*
* KAuth helper runs in the background until application exits .
* To avoid forever running helper in case of application crash
* ExternalCommand class opens DBus interface that we ping .
* If helper is not busy than it exits when ping fails . Otherwise ,
* we wait for the current job to finish before exiting , so even in case
* of main application crash , we do not leave partially moved data .
*
* This helper also starts another DBus interface where it listens to
* command execution requests from the application that started the helper .
* These requests are validated using public key cryptography , to prevent
* other unprivileged applications from gaining root privileges .
2018-03-19 10:33:20 +00:00
*/
ActionReply ExternalCommandHelper : : init ( const QVariantMap & args )
{
ActionReply reply ;
if ( ! QDBusConnection : : systemBus ( ) . isConnected ( ) ) {
2018-07-14 21:36:13 +01:00
qWarning ( ) < < " Could not connect to DBus system bus " ;
2018-03-19 10:33:20 +00:00
reply . addData ( QStringLiteral ( " success " ) , false ) ;
return reply ;
}
if ( ! QDBusConnection : : systemBus ( ) . registerService ( QStringLiteral ( " org.kde.kpmcore.helperinterface " ) ) ) {
qWarning ( ) < < QDBusConnection : : systemBus ( ) . lastError ( ) . message ( ) ;
reply . addData ( QStringLiteral ( " success " ) , false ) ;
return reply ;
}
2018-07-15 00:09:39 +01:00
if ( ! QDBusConnection : : systemBus ( ) . registerObject ( QStringLiteral ( " /Helper " ) , this , QDBusConnection : : ExportAllSlots ) ) {
qWarning ( ) < < QDBusConnection : : systemBus ( ) . lastError ( ) . message ( ) ;
reply . addData ( QStringLiteral ( " success " ) , false ) ;
return reply ;
}
m_publicKey = QCA : : PublicKey : : fromDER ( args [ QStringLiteral ( " pubkey " ) ] . toByteArray ( ) ) ;
m_Counter = 0 ;
2018-03-19 10:33:20 +00:00
2018-03-23 22:08:23 +00:00
HelperSupport : : progressStep ( QVariantMap ( ) ) ;
2018-07-15 00:09:39 +01:00
auto timeout = [ this ] ( ) {
QDBusInterface iface ( QStringLiteral ( " org.kde.kpmcore.applicationinterface " ) ,
QStringLiteral ( " /Application " ) ,
QStringLiteral ( " org.kde.kpmcore.ping " ) ,
QDBusConnection : : systemBus ( ) ) ;
2018-07-15 01:13:54 +01:00
iface . setTimeout ( 2000 ) ; // 2 seconds;
2018-07-15 00:09:39 +01:00
auto pcall = iface . asyncCall ( QStringLiteral ( " ping " ) ) ;
QDBusPendingCallWatcher * watcher = new QDBusPendingCallWatcher ( pcall , this ) ;
auto exitLoop = [ & ] ( QDBusPendingCallWatcher * watcher ) {
if ( watcher - > isError ( ) ) {
qWarning ( ) < < watcher - > error ( ) ;
m_loop . exit ( ) ;
}
} ;
connect ( watcher , & QDBusPendingCallWatcher : : finished , exitLoop ) ;
} ;
QTimer * timer = new QTimer ( this ) ;
connect ( timer , & QTimer : : timeout , this , timeout ) ;
timer - > start ( 5000 ) ; // 5 seconds
2018-03-23 22:08:23 +00:00
m_loop . exec ( ) ;
reply . addData ( QStringLiteral ( " success " ) , true ) ;
2018-03-19 10:33:20 +00:00
return reply ;
}
2018-02-06 16:48:02 +00:00
/** Reads the given number of bytes from the sourceDevice into the given buffer.
@ param sourceDevice device or file to read from
@ param buffer buffer to store the bytes read in
@ param offset offset where to begin reading
@ param size the number of bytes to read
@ return true on success
*/
2018-03-21 17:01:40 +00:00
bool ExternalCommandHelper : : readData ( const QString & sourceDevice , QByteArray & buffer , qint64 offset , qint64 size )
2018-01-24 15:22:42 +00:00
{
2018-02-05 12:58:37 +00:00
QFile device ( sourceDevice ) ;
2018-01-24 15:22:42 +00:00
2018-02-05 12:58:37 +00:00
if ( ! device . open ( QIODevice : : ReadOnly | QIODevice : : Unbuffered ) ) {
qCritical ( ) < < xi18n ( " Could not open device <filename>%1</filename> for reading. " , sourceDevice ) ;
return false ;
}
2018-01-24 15:22:42 +00:00
2018-02-05 12:58:37 +00:00
if ( ! device . seek ( offset ) ) {
qCritical ( ) < < xi18n ( " Could not seek position %1 on device <filename>%1</filename>. " , sourceDevice ) ;
return false ;
2018-01-24 15:22:42 +00:00
}
2018-02-05 12:58:37 +00:00
buffer = device . read ( size ) ;
if ( size ! = buffer . size ( ) ) {
qCritical ( ) < < xi18n ( " Could not read from device <filename>%1</filename>. " , sourceDevice ) ;
return false ;
}
return true ;
2018-01-24 15:22:42 +00:00
}
2018-02-06 16:48:02 +00:00
/** Writes the data from buffer to a given device or file.
@ param targetDevice device or file to write to
@ param buffer the data that we write
@ param offset offset where to begin writing
@ return true on success
*/
2018-03-21 17:01:40 +00:00
bool ExternalCommandHelper : : writeData ( const QString & targetDevice , const QByteArray & buffer , qint64 offset )
2018-01-24 15:22:42 +00:00
{
2018-02-03 15:57:47 +00:00
QFile device ( targetDevice ) ;
2018-02-05 12:46:10 +00:00
if ( ! device . open ( QIODevice : : WriteOnly | QIODevice : : Append | QIODevice : : Unbuffered ) ) {
2018-02-03 15:57:47 +00:00
qCritical ( ) < < xi18n ( " Could not open device <filename>%1</filename> for writing. " , targetDevice ) ;
return false ;
}
2018-01-24 15:22:42 +00:00
2018-02-03 15:57:47 +00:00
if ( ! device . seek ( offset ) ) {
qCritical ( ) < < xi18n ( " Could not seek position %1 on device <filename>%1</filename>. " , targetDevice ) ;
return false ;
2018-01-24 15:22:42 +00:00
}
2018-02-03 15:57:47 +00:00
if ( device . write ( buffer ) ! = buffer . size ( ) ) {
qCritical ( ) < < xi18n ( " Could not write to device <filename>%1</filename>. " , targetDevice ) ;
return false ;
}
return true ;
2018-01-24 15:22:42 +00:00
}
2018-04-13 12:24:05 +01:00
bool ExternalCommandHelper : : copyblocks ( const QByteArray & signature , const QString & sourceDevice , const qint64 sourceFirstByte , const qint64 sourceLength , const QString & targetDevice , const qint64 targetFirstByte , const qint64 blockSize )
2018-01-24 15:22:42 +00:00
{
2018-04-13 12:24:05 +01:00
QByteArray request ;
request . setNum ( + + m_Counter ) ;
request . append ( sourceDevice . toUtf8 ( ) ) ;
request . append ( QByteArray : : number ( sourceFirstByte ) ) ;
request . append ( QByteArray : : number ( sourceLength ) ) ;
request . append ( targetDevice . toUtf8 ( ) ) ;
request . append ( QByteArray : : number ( targetFirstByte ) ) ;
request . append ( QByteArray : : number ( blockSize ) ) ;
QByteArray hash = QCryptographicHash : : hash ( request , QCryptographicHash : : Sha512 ) ;
if ( ! m_publicKey . verifyMessage ( hash , signature , QCA : : EMSA3_Raw ) ) {
qCritical ( ) < < xi18n ( " Invalid cryptographic signature " ) ;
2018-03-22 16:41:49 +00:00
return false ;
2018-04-13 12:24:05 +01:00
}
2018-01-24 15:22:42 +00:00
2018-03-21 17:01:40 +00:00
const qint64 blocksToCopy = sourceLength / blockSize ;
qint64 readOffset = sourceFirstByte ;
qint64 writeOffset = targetFirstByte ;
qint32 copyDirection = 1 ;
2018-01-24 15:22:42 +00:00
2018-03-21 17:01:40 +00:00
if ( targetFirstByte > sourceFirstByte ) {
readOffset = sourceFirstByte + sourceLength - blockSize ;
writeOffset = targetFirstByte + sourceLength - blockSize ;
copyDirection = - 1 ;
}
const qint64 lastBlock = sourceLength % blockSize ;
2018-01-24 15:22:42 +00:00
2018-01-27 18:54:48 +00:00
qint64 bytesWritten = 0 ;
2018-01-24 15:22:42 +00:00
qint64 blocksCopied = 0 ;
2018-01-24 15:35:11 +00:00
QByteArray buffer ;
2018-01-24 15:22:42 +00:00
int percent = 0 ;
2018-01-27 18:54:48 +00:00
QTime t ;
t . start ( ) ;
QVariantMap report ;
report [ QStringLiteral ( " report " ) ] = xi18nc ( " @info:progress " , " Copying %1 blocks (%2 bytes) from %3 to %4, direction: %5. " , blocksToCopy ,
sourceLength , readOffset , writeOffset , copyDirection = = 1 ? i18nc ( " direction: left " , " left " )
: i18nc ( " direction: right " , " right " ) ) ;
HelperSupport : : progressStep ( report ) ;
2018-01-24 15:22:42 +00:00
bool rval = true ;
2018-01-27 18:54:48 +00:00
2018-01-24 15:22:42 +00:00
while ( blocksCopied < blocksToCopy ) {
2018-02-03 15:57:47 +00:00
if ( ! ( rval = readData ( sourceDevice , buffer , readOffset + blockSize * blocksCopied * copyDirection , blockSize ) ) )
2018-01-24 15:22:42 +00:00
break ;
2018-02-03 15:57:47 +00:00
if ( ! ( rval = writeData ( targetDevice , buffer , writeOffset + blockSize * blocksCopied * copyDirection ) ) )
2018-01-24 15:22:42 +00:00
break ;
2018-01-27 18:54:48 +00:00
bytesWritten + = buffer . size ( ) ;
2018-01-24 15:22:42 +00:00
if ( + + blocksCopied * 100 / blocksToCopy ! = percent ) {
percent = blocksCopied * 100 / blocksToCopy ;
2018-01-27 18:54:48 +00:00
if ( percent % 5 = = 0 & & t . elapsed ( ) > 1000 ) {
const qint64 mibsPerSec = ( blocksCopied * blockSize / 1024 / 1024 ) / ( t . elapsed ( ) / 1000 ) ;
const qint64 estSecsLeft = ( 100 - percent ) * t . elapsed ( ) / percent / 1000 ;
report [ QStringLiteral ( " report " ) ] = xi18nc ( " @info:progress " , " Copying %1 MiB/second, estimated time left: %2 " , mibsPerSec , QTime ( 0 , 0 ) . addSecs ( estSecsLeft ) . toString ( ) ) ;
HelperSupport : : progressStep ( report ) ;
}
2018-01-24 15:22:42 +00:00
HelperSupport : : progressStep ( percent ) ;
}
}
// copy the remainder
if ( rval & & lastBlock > 0 ) {
Q_ASSERT ( lastBlock < blockSize ) ;
const qint64 lastBlockReadOffset = copyDirection > 0 ? readOffset + blockSize * blocksCopied : sourceFirstByte ;
const qint64 lastBlockWriteOffset = copyDirection > 0 ? writeOffset + blockSize * blocksCopied : targetFirstByte ;
2018-01-27 18:54:48 +00:00
report [ QStringLiteral ( " report " ) ] = xi18nc ( " @info:progress " , " Copying remainder of block size %1 from %2 to %3. " , lastBlock , lastBlockReadOffset , lastBlockWriteOffset ) ;
HelperSupport : : progressStep ( report ) ;
2018-02-03 15:57:47 +00:00
rval = readData ( sourceDevice , buffer , lastBlockReadOffset , lastBlock ) ;
2018-01-24 15:22:42 +00:00
if ( rval )
2018-02-03 15:57:47 +00:00
rval = writeData ( targetDevice , buffer , lastBlockWriteOffset ) ;
2018-01-24 15:22:42 +00:00
2018-01-27 18:54:48 +00:00
if ( rval ) {
HelperSupport : : progressStep ( 100 ) ;
bytesWritten + = buffer . size ( ) ;
}
2018-01-24 15:22:42 +00:00
}
2018-01-27 18:54:48 +00:00
report [ QStringLiteral ( " report " ) ] = xi18ncp ( " @info:progress argument 2 is a string such as 7 bytes (localized accordingly) " , " Copying 1 block (%2) finished. " , " Copying %1 blocks (%2) finished. " , blocksCopied , i18np ( " 1 byte " , " %1 bytes " , bytesWritten ) ) ;
HelperSupport : : progressStep ( report ) ;
2018-01-24 15:22:42 +00:00
2018-03-21 20:51:30 +00:00
return rval ;
2018-01-24 15:22:42 +00:00
}
2017-11-07 22:55:28 +00:00
2018-04-13 00:44:12 +01:00
QVariantMap ExternalCommandHelper : : start ( const QByteArray & signature , const QString & command , const QStringList & arguments , const QByteArray & input )
2017-11-07 22:55:28 +00:00
{
2018-04-13 00:44:12 +01:00
QTextCodec : : setCodecForLocale ( QTextCodec : : codecForName ( " UTF-8 " ) ) ;
2018-03-19 10:33:20 +00:00
QVariantMap reply ;
2018-04-12 22:43:12 +01:00
QByteArray request ;
request . setNum ( + + m_Counter ) ;
2018-04-13 12:24:05 +01:00
request . append ( command . toUtf8 ( ) ) ;
2018-04-12 22:43:12 +01:00
for ( const auto & argument : arguments )
request . append ( argument . toUtf8 ( ) ) ;
request . append ( input ) ;
QByteArray hash = QCryptographicHash : : hash ( request , QCryptographicHash : : Sha512 ) ;
if ( ! m_publicKey . verifyMessage ( hash , signature , QCA : : EMSA3_Raw ) ) {
qCritical ( ) < < xi18n ( " Invalid cryptographic signature " ) ;
2018-03-22 16:41:49 +00:00
reply [ QStringLiteral ( " success " ) ] = false ;
return reply ;
}
2017-11-07 22:55:28 +00:00
// connect(&cmd, &QProcess::readyReadStandardOutput, this, &ExternalCommandHelper::onReadOutput);
2018-04-13 00:44:12 +01:00
m_cmd . setEnvironment ( { QStringLiteral ( " LVM_SUPPRESS_FD_WARNINGS=1 " ) } ) ;
2018-03-19 10:33:20 +00:00
m_cmd . start ( command , arguments ) ;
m_cmd . write ( input ) ;
m_cmd . closeWriteChannel ( ) ;
m_cmd . waitForFinished ( - 1 ) ;
QByteArray output = m_cmd . readAllStandardOutput ( ) ;
reply [ QStringLiteral ( " output " ) ] = output ;
reply [ QStringLiteral ( " exitCode " ) ] = m_cmd . exitCode ( ) ;
2017-11-07 22:55:28 +00:00
return reply ;
}
2018-04-13 12:24:05 +01:00
void ExternalCommandHelper : : exit ( const QByteArray & signature )
2018-03-21 17:01:40 +00:00
{
2018-04-13 12:24:05 +01:00
QByteArray request ;
request . setNum ( + + m_Counter ) ;
QByteArray hash = QCryptographicHash : : hash ( request , QCryptographicHash : : Sha512 ) ;
if ( ! m_publicKey . verifyMessage ( hash , signature , QCA : : EMSA3_Raw ) ) {
2018-04-13 12:26:44 +01:00
qCritical ( ) < < xi18n ( " Invalid cryptographic signature " ) ;
2018-04-13 12:24:05 +01:00
return ;
2018-03-21 17:01:40 +00:00
}
2018-03-19 10:33:20 +00:00
m_loop . exit ( ) ;
2018-03-22 05:32:59 +00:00
QDBusConnection : : systemBus ( ) . unregisterObject ( QStringLiteral ( " /Helper " ) ) ;
2018-07-15 00:35:55 +01:00
QDBusConnection : : systemBus ( ) . unregisterService ( QStringLiteral ( " org.kde.kpmcore.helperinterface " ) ) ;
2018-03-19 10:33:20 +00:00
}
2017-11-07 22:55:28 +00:00
void ExternalCommandHelper : : onReadOutput ( )
{
// const QByteArray s = cmd.readAllStandardOutput();
// if(output.length() > 10*1024*1024) { // prevent memory overflow for badly corrupted file systems
// if (report())
// report()->line() << xi18nc("@info:status", "(Command is printing too much output)");
// return;
// }
// output += s;
// if (report())
// *report() << QString::fromLocal8Bit(s);
}
KAUTH_HELPER_MAIN ( " org.kde.kpmcore.externalcommand " , ExternalCommandHelper )