/* * KlamOnAcc class -- the non-graphical class which manages clamonacc. * * Copyright (C) 2021 Mavridis Philippe * * 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 #include #include #include #include #include #include #include /* Required by quarantine() function */ #include #include 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("

There was a problem quarantining %1. Check your diskspace, the permissions on your quarantine location and whether a file with the same name already exists in the quarantine.

").arg(fname)); } } } emit stateUpdated(); config->writeEntry(TQString("Items %1").arg(quarloc), lastQuarItems); config->sync(); } #include "klamonacc.moc"