You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
klamav/src/klamonacc.cpp

387 lines
11 KiB

/*
* KlamOnAcc class -- the non-graphical class which manages clamonacc.
*
* Copyright (C) 2021 Mavridis Philippe <mavridisf@gmail.com>
*
* Portions taken from freshklam.cpp and scanviewer.cpp
*/
/* TODO:
- Implement a separate start/stop daemon process so that we don't need to
ask for root privgileges every time we start or kill clamonacc
- processOutput: [Initializing] and [Scanner ready] notifications
*/
#include "klamonacc.h"
#include "klamonacc_alert.h"
#include "klamav.h"
#include "klamavconfig.h"
#include "collectiondb.h"
#include "directorylist.h"
#include <tdeglobal.h>
#include <tdelocale.h>
#include <tdeconfig.h>
#include <tdetempfile.h>
#include <kprocess.h>
#include <tdemessagebox.h>
#include <ksystemtray.h>
#include <knotifyclient.h>
/* Required by quarantine() function */
#include <kiconloader.h>
#include <tdeio/netaccess.h>
KlamOnAcc::KlamOnAcc( TQWidget *parent, const char *name )
: TQObject( parent, name )
{
config = TDEGlobal::config();
config->setGroup("OnAccess");
// Initial state
toggle( config->readBoolEntry("EnableOnAccess", false) );
}
KlamOnAcc::~KlamOnAcc()
{
}
TQString KlamOnAcc::tdesu(TQString command, TQString caption, bool terminal)
{
TQString sucommand;
if(terminal)
sucommand = TQString("tdesu --caption \"%1\" --ignorebutton -t ").arg( caption );
else
sucommand = TQString("tdesu --caption \"%1\" --ignorebutton ").arg( caption );
sucommand += "\"" + command + "\"";
return sucommand;
}
TQString KlamOnAcc::startPrepare()
{
// Determine and write configuration
TQString daemonOpts;
TQStringList daemonConfig;
// Create a config file based on the default one
TQFile defaultConfigFile( "/etc/clamav/clamd.conf" );
if( defaultConfigFile.open(IO_ReadOnly) )
{
TQTextStream in_stream( &defaultConfigFile );
TQString in_line;
int in_line_n = 0;
while(! in_stream.atEnd() )
{
daemonConfig += in_stream.readLine();
++in_line_n;
}
defaultConfigFile.close();
}
// Set up ClamOnAcc's config
daemonConfig += "OnAccessPrevention yes";
config->setGroup("OnAccess");
if ( config->readBoolEntry("ExtraScanning", false) )
daemonConfig += "OnAccessExtraScanning yes";
daemonConfig += TQString("OnAccessMaxFileSize %1M").arg(
config->readNumEntry("OnAccessMaxFile", 5)
);
daemonConfig += "OnAccessExcludeUname clamav";
// Specify directories to watch
TQStringList dirs = CollectionSetup::pruneSelectedDirs( config->readListEntry("Watchlist") );
if (! dirs.count() ) {
fatalError( i18n("Please select the directories you want to watch from the Options dialog.") );
return TQString::null;
}
for ( TQStringList::Iterator it = dirs.begin(); it != dirs.end(); it++ )
daemonConfig += TQString("OnAccessIncludePath %1").arg(*it);
/* BUG: DOES NOT WORK (why do they have this option then?)
"ERROR: ClamInotif: can't exclude '/home/user/.trinity'" */
// if ( config->readBoolEntry("ExcludeConfDir", true) )
// daemonConfig += TQString("OnAccessExcludePath %1/.trinity").arg(getenv("HOME"));
// Write the config
KTempFile tf;
if ( tf.status() != 0 ) {
tf.close();
fatalError( i18n("Could not create temporary configuration file for ClamOnAcc!") );
return TQString::null;
}
TQString tempFileName = tf.name();
TQTextStream &ts = *(tf.textStream());
for ( TQStringList::Iterator it = daemonConfig.begin(); it != daemonConfig.end(); it++ )
ts << (*it) << endl;
tf.close();
// Set up ClamOnAcc's command-line options
daemonOpts += " --fdpass -v --stdout --foreground";
daemonOpts += TQString(" --config-file=%1").arg(tempFileName);
// Make the start command
TQString command = "clamonacc";
command += daemonOpts;
return command;
}
void KlamOnAcc::startProcess( TQString command )
{
childproc = new KShellProcess();
*childproc << command;
childproc->start(TDEProcess::NotifyOnExit, TDEProcess::Stdout);
connect( childproc, SIGNAL(receivedStdout(TDEProcess*, char*, int)), SLOT(processOutput(TDEProcess*, char*, int)) );
connect( childproc, SIGNAL(processExited(TDEProcess*)), SLOT(childExited()) );
emit stateUpdated();
}
void KlamOnAcc::start()
{
if( active || !enabled ) return;
active = true;
crashed = false;
// Log this event
CollectionDB::instance()->insertEvent("On-Access Scanner","Starting On-Access Scanner",0);
startProcess( tdesu(startPrepare(), i18n("Start On-Access Scanner"), true) );
}
TQString KlamOnAcc::stopPrepare()
{
disconnect( childproc, 0, 0, 0 );
// It's like this until a proper start/stop daemon is implemented
return TQString("killall -9 clamonacc");
}
void KlamOnAcc::stopProcess( TQString command )
{
if( childproc->isRunning() ) {
TDEProcess *terminator = new KShellProcess();
*terminator << command;
terminator->start();
}
emit stateUpdated();
}
void KlamOnAcc::stop()
{
if( !active || !enabled ) return;
active = false;
// Log this event
CollectionDB::instance()->insertEvent("On-Access Scanner","Stopping On-Access Scanner",0);
stopProcess( tdesu(stopPrepare(), i18n("Stop On-Access Scanner")) );
}
void KlamOnAcc::restart()
{
kdDebug() << "restart()" << endl;
if( isActive() )
{
active = false;
// We combine two commands here
TQString command = stopPrepare() + TQString("; ") + startPrepare();
kdDebug() << "restart(): " << command << endl;
startProcess( tdesu(command, i18n("Restart On-Access Scanner"), true) );
active = true;
}
}
void KlamOnAcc::toggle( bool on ) {
kdDebug() << "toggle()" << endl;
if ( !on && isEnabled() )
disable();
else if ( on && !isEnabled() )
enable();
}
void KlamOnAcc::childExited() {
if(active) // died too early
fatalError( i18n("ClamOnAcc has died unexpectedly. If you did not kill it yourself, please check your ClamAV installation.") );
}
void KlamOnAcc::fatalError(TQString descr)
{
if( crashed ) return; // do not display further errors
active = false;
crashed = true;
CollectionDB::instance()->insertEvent("On-Access Scanner","On-Access Scanner has died!",0);
disable();
KMessageBox::sorry(
0,
descr,
i18n("Fatal Error")
);
}
void KlamOnAcc::processOutput(TDEProcess*, char* buffer, int buffSize)
{
TQString buff( buffer );
buff = buff.mid( 0, buff.find("\n") ).stripWhiteSpace();
kdDebug() << "KLAMONACC " << buff << endl;
int pos;
if( buff.find("Could not connect to clamd") != -1 )
{
fatalError( i18n("The ClamAV daemon is unavailable! Please ensure that it is running and try again.") );
return;
}
else if( buff.find("ClamInotif: watching") != -1 )
{
// TODO: "initialization complete" notification
}
else if( (pos = buff.find("FOUND")) != -1 )
{
fname = buff.mid( 0, buff.find(":") );
vname = buff.mid( buff.find(":")+2, pos );
if( shownAlerts.find(fname) != shownAlerts.end() )
return; // alert already shown for this file
TQListViewItem *virusItem;
alert = new KlamOnAccAlert();
alert->setModal(false);
alert->setActiveWindow();
TQListViewItem *virus = new TQListViewItem( alert->VirusList, fname, vname, i18n("Loose") );
virus->setPixmap( 0, SmallIcon("klamav_virus") );
shownAlerts << fname;
alert->exec();
if( alert->result() == TQDialog::Accepted )
quarantine();
}
}
void KlamOnAcc::enable()
{
kdDebug() << "% ENABLE()" << endl;
config->setGroup("OnAccess");
config->writeEntry("EnableOnAccess", true);
config->sync();
enabled = true;
if(! isActive() ) start();
emit stateUpdated();
}
void KlamOnAcc::disable()
{
kdDebug() << "% DISABLE()" << endl;
if( isActive() ) stop();
config->setGroup("OnAccess");
config->writeEntry("EnableOnAccess", false);
config->sync();
enabled = false;
emit stateUpdated();
}
void KlamOnAcc::quarantine()
{
TQDate today = TQDate::currentDate();
TQTime now = TQTime::currentTime();
TQString suffix = TQString(":%1 %2")
.arg(today.toString("ddd MMMM d yyyy"))
.arg(now.toString("hh-mm-ss-zzz ap"));
TQStringList QuarantineList;
QuarantineList.append(fname+":"+vname+suffix);
/* The following has been taken nearly verbatim from scanviewer.cpp */
bool allQuarantined=TRUE;
config->setGroup("Kuarantine");
TQStringList lastQuarLocations = config->readListEntry("KuarantineLocations");
tdemain->_tray->setPixmap(KSystemTray::loadIcon("klamav_quarantining"));
TQString quarloc;
for (TQStringList::Iterator it = lastQuarLocations.begin(); it == lastQuarLocations.begin() ; it++){
quarloc = *it;
}
TQStringList lastQuarItems = config->readListEntry(TQString("Items %1").arg(quarloc));
for (TQStringList::Iterator it = QuarantineList.begin(); it != QuarantineList.end(); it++ ){
if (lastQuarItems.contains(*it) != 0) {
lastQuarItems.remove(*it);
}
TQString item2 = (*it).stripWhiteSpace();
int fnameStartPoint = 0;
int dtStartPoint = item2.findRev(":");
int fnameEndPoint = item2.findRev(":", (signed int)-((item2.length() - dtStartPoint)+1));
TQString fname = item2.mid(fnameStartPoint,(fnameEndPoint - fnameStartPoint));
TQString itemName = item2.mid((fnameEndPoint+1),((dtStartPoint+1) - (fnameEndPoint+2)));
TQString when = item2.mid((dtStartPoint+1),(item2.length() - (dtStartPoint+1)));
if (!(fname.isEmpty())){
TQStringList tokens = TQStringList::split ( "/", fname, FALSE );
TQString qname = tokens.last();
qname.prepend("/");
qname.prepend(quarloc);
qname.append(":"+when);
if (TDEIO::NetAccess::file_move(fname,qname)){
if (lastQuarItems.contains(item2))
lastQuarItems.remove(item2);
lastQuarItems.prepend(item2);
(alert->VirusList->findItem(fname,0))->setText(2,"Quarantined");
(alert->VirusList->findItem(fname,0))->setPixmap( 0, SmallIcon("klamav") );
chmod(qname.ascii(),0400);
CollectionDB::instance()->insertEvent("Quarantine",TQString("Quarantined"),fname);
}else{
KMessageBox::information (tdemain, i18n("<p>There was a problem quarantining <b>%1</b>. Check your diskspace, the permissions on your quarantine location and whether a file with the same name already exists in the quarantine. </p>").arg(fname));
}
}
}
emit stateUpdated();
config->writeEntry(TQString("Items %1").arg(quarloc), lastQuarItems);
config->sync();
}
#include "klamonacc.moc"