/* * Copyright (C) 2004 Robert Hogan */ #include "freshklam.h" #include "klamav.h" #include "collectiondb.h" /*#include "gmanedb.h"*/ #include "klamavconfig.h" #include "config.h" #include "../config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "version.h" #include const char *check_desc[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "48", 0 }; TQString readymessage = i18n("KlamAV ") + KLAMAV_VERSION + i18n(" - Ready"); Freshklam::Freshklam(TQWidget *parent, const char *name) : TQWidget(parent, name) { // MetaDB* updater = new MetaDB(); // updater->update(); updater = new KlamavUpdate(this); connect( updater, SIGNAL( getCurrentVersionOfClamAV() ), SLOT( getCurrentVersionOfClamAV() ) ); tdemain->firstDownload = false; checkingDirectly = false; freshklamAlive = FALSE; config = TDEGlobal::config(); config->setGroup("Freshklam"); lastDownloadPaths = config->readListEntry("lastDownloadPaths"); getCurrentVersionOfClamAV( ); if ((lastDownloadPaths.isEmpty()) || (!(TDEIO::NetAccess::exists(TQString((*lastDownloadPaths.begin())),TRUE,NULL)))){ createDBDir(); } TQString proxyIPText = config->readEntry("ProxyIP"); TQString proxyPortText = config->readEntry("ProxyPort"); TQString proxyUserText = config->readEntry("ProxyUser"); TQString proxyPassText = config->readEntry("ProxyPass"); //Data Directory Widget TQVBoxLayout *vbox = new TQVBoxLayout(this, KDialog::marginHint(), KDialog::spacingHint(), "vbox"); //Virus Database Directory TQGroupBox *group = new TQGroupBox(i18n("Virus Database Directory"), this); vbox->addWidget(group); TQGridLayout *layout = new TQGridLayout( group, 9, 2, KDialog::spacingHint(), KDialog::spacingHint(), "layout"); layout->addRowSpacing(0, group->fontMetrics().height()); layout->setColStretch(0, 1); layout->setColStretch(1, 1); TQWidget *hlp = new TQWidget( group ); layout->addMultiCellWidget(hlp, 1,1, 0,1); TQHBoxLayout *dir_layout = new TQHBoxLayout(hlp,KDialog::spacingHint() ); dir_combo = new KURLRequester( new KComboBox(true, this), hlp, "dir combo" ); dir_combo->completionObject()->setMode(KURLCompletion::DirCompletion); dir_combo->comboBox()->insertStringList(lastDownloadPaths); dir_combo->setMode( KFile::Directory|KFile::LocalOnly ); TQLabel *dir_label = new TQLabel(i18n("Directory:"), hlp); dir_label->setFixedSize(dir_label->sizeHint()); dir_layout->addWidget(dir_label); dir_layout->addWidget(dir_combo); //Proxy Widget TQGroupBox *proxy_group = new TQGroupBox(i18n("Proxy for Database Updates"), this); vbox->addWidget(proxy_group); TQGridLayout *proxy_layout = new TQGridLayout( proxy_group, 2, 5, KDialog::spacingHint(), KDialog::spacingHint(), "proxy_layout"); proxy_layout->addRowSpacing(0, proxy_group->fontMetrics().height()); proxy_layout->setColStretch(0, 1); proxy_layout->setColStretch(1, 1); //IP Address & Port TQWidget *proxy_hlp = new TQWidget( proxy_group ); proxy_layout->addMultiCellWidget(proxy_hlp, 1,2, 0,2); TQGridLayout *proxy_dir_layout = new TQGridLayout(proxy_hlp, 2,4,KDialog::spacingHint() ); TQLabel *proxy_dir_label = new TQLabel(i18n("IP Address:"), proxy_hlp); //proxy_dir_label->setFixedSize(proxy_dir_label->sizeHint()); proxy_dir_layout->addWidget(proxy_dir_label,0,0); proxyIP = new KLineEdit(proxy_hlp); proxy_dir_layout->addWidget(proxyIP,0,1); proxyIP->setText(proxyIPText); TQLabel *proxy_port_label = new TQLabel(i18n("Port:"), proxy_hlp); //proxy_port_label->setFixedSize(proxy_port_label->sizeHint()); proxy_dir_layout->addWidget(proxy_port_label,0,3); proxyPort = new KLineEdit(proxy_hlp); proxy_dir_layout->addWidget(proxyPort,0,4); proxyPort->setText(proxyPortText); TQLabel *proxy_user_label = new TQLabel(i18n("User:"), proxy_hlp); //proxy_user_label->setFixedSize(proxy_user_label->sizeHint()); proxy_dir_layout->addWidget(proxy_user_label,1,0); proxyUser = new KLineEdit(proxy_hlp); proxy_dir_layout->addWidget(proxyUser,1,1); proxyUser->setText(proxyUserText); TQLabel *proxy_pass_label = new TQLabel(i18n("Password:"), proxy_hlp); //proxy_pass_label->setFixedSize(proxy_pass_label->sizeHint()); proxy_dir_layout->addWidget(proxy_pass_label,1,3); proxyPass = new KLineEdit(proxy_hlp); proxy_dir_layout->addWidget(proxyPass,1,4); proxyPass->setText(proxyPassText); //Daemon Widget TQGroupBox *daemon_group = new TQGroupBox(i18n("Database AutoUpdate Settings"), this); vbox->addWidget(daemon_group); TQGridLayout *daemon_layout = new TQGridLayout( daemon_group, 5, 4, KDialog::spacingHint(), KDialog::spacingHint(), "daemon_layout"); daemon_layout->addRowSpacing(0, daemon_group->fontMetrics().height()); daemon_layout->addRowSpacing(1, daemon_group->fontMetrics().height()); //daemon_layout->setColStretch(0, 1); //daemon_layout->setColStretch(1, 1); TQWidget *daemonhlp = new TQWidget( daemon_group ); daemon_layout->addMultiCellWidget(daemonhlp, 1,1, 0,1); TQHBoxLayout *daemon_check_layout = new TQHBoxLayout(daemonhlp, KDialog::spacingHint() ); daemon_box = new TQCheckBox(i18n("Update Virus Database Automatically"), daemonhlp); daemon_box->setMinimumWidth(daemon_box->sizeHint().width()); daemon_check_layout->addSpacing(10); daemon_check_layout->addWidget(daemon_box); check_combo = new TQComboBox(false, daemonhlp); check_combo->insertStrList(check_desc); if (!(config->readEntry("NoOfUpdates").isEmpty())) check_combo->setCurrentText(config->readEntry("NoOfUpdates")); check_combo->adjustSize(); //check_combo->setFixedSize(check_combo->size()); daemon_check_layout->addWidget(check_combo); TQLabel *combo_label = new TQLabel(i18n("Times a Day"), daemonhlp); //combo_label->setFixedSize(combo_label->sizeHint()); daemon_check_layout->addWidget(combo_label); KButtonBox *actionbox = new KButtonBox(this, TQt::Horizontal); vbox->addWidget(actionbox, 2, 0); actionbox->addStretch(); search_button = actionbox->addButton(i18n("&Update Now")); search_button->setDefault(true); cancel_button = actionbox->addButton(i18n("Cancel")); cancel_button->setEnabled(false); actionbox->addStretch(); actionbox->layout(); TQFrame *status_frame = new TQFrame(this); status_frame->setFrameStyle(TQFrame::Panel | TQFrame::Sunken); TQBoxLayout *status_layout = new TQHBoxLayout(status_frame, 2); status_label = new TQLabel(readymessage, status_frame); status_layout->addWidget(status_label, 10); status_layout->activate(); status_frame->adjustSize(); status_frame->setMinimumSize(status_frame->size()); vbox->addWidget(status_frame); initCheckBoxes(); } Freshklam::~Freshklam() { if (daemon_box->isChecked()){ killPID(); } if (!(tempFileName.isEmpty())) TDEIO::NetAccess::del(tempFileName,NULL); } void Freshklam::processOutput() { int pos; int pos2; TQString item2; while (( (pos = buf.find('\n')) != -1) || ( (pos = buf.find(']')) != -1)) { TQString item = buf.left(pos); 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 ap : ")); if (!item.isEmpty()){ if ( (pos2 = buf.find('[')) != -1) item +=']'; status_label->setText(suffix + item); item2 += item; item2 += '\n'; } buf = buf.right(buf.length()-pos-1); } if ((pos = item2.find("ERROR")) != -1){ pos2 = item2.find('\n',pos); errorMessage = item2.mid(pos,pos2); } if ((pos = item2.find("Database updated")) != -1){ CollectionDB::instance()->insertEvent("Updates","Database Updated",dir_combo->url()); KNotifyClient::event(tdemain->_tray->winId(),"UpdatedDatabase", "Virus Database Updated."); updateMailClient(); } if ((pos = item2.find("daily.cvd is up to date")) != -1){ CollectionDB::instance()->insertEvent("Updates","Database Already Up To Date",dir_combo->url()); KNotifyClient::event(tdemain->_tray->winId(),"DatabaseUpToDate", "Virus Database Up To Date."); updateMailClient(); } if ((pos = item2.find("Recommended version:")) != -1){ tdemain->_tray->setPixmap(KSystemTray::loadIcon("klamav_update_required")); pos2 = item2.find('\n',pos); TQString version = item2.mid((pos+20),pos2 - (pos+20)).stripWhiteSpace(); KNotifyClient::event(tdemain->_tray->winId(),"ClamAVOutDated", TQString("Your copy of ClamAV is out of date! Please Upgrade to ClamAV %1!").arg(version)); } } void Freshklam::slotSearch() { search_button->setEnabled(false); cancel_button->setEnabled(true); TQString filepattern = dir_combo->url(); CollectionDB::instance()->insertEvent("Updates","Commencing DB Update", filepattern); status_label->setText(i18n("Beginning Update...")); childproc = new KShellProcess(); TQString command = "freshclam --stdout "; TQString user = KUser().loginName(); command += " --user=" + user + " "; if (!(filepattern.isEmpty())){ command += " --datadir="; command += filepattern; } if (daemon_box->isChecked()){ command += " -d -c "; command += check_combo->currentText(); KTempFile tf; if ( tf.status() != 0 ) { tf.close(); //delete tf; KMessageBox::information (this,i18n( "There was an error creating a temp file!") ); return; } pidFileName = tf.name(); command += " -p "; command += pidFileName; freshklamAlive = TRUE; } writeConf(); command += " --config-file="; command += tempFileName; disableInputs(); *childproc << command; //kdDebug() << command << endl; childproc->start(TDEProcess::NotifyOnExit, TDEProcess::Stdout); connect( childproc, SIGNAL(processExited(TDEProcess *)), SLOT(childExited()) ); connect( childproc, SIGNAL(receivedStdout(TDEProcess *, char *, int)), SLOT(receivedOutput(TDEProcess *, char *, int)) ); tdemain->EnableFreshklam->setEnabled(FALSE); tdemain->DisableFreshklam->setEnabled(TRUE); } void Freshklam::finish() { search_button->setEnabled(true); cancel_button->setEnabled(false); enableInputs(); tdemain->_tray->setPixmap(KSystemTray::loadIcon("klamav_on_acc_disabled")); buf += '\n'; processOutput(); delete childproc; childproc = 0; freshklamAlive = FALSE; if (!(tempFileName.isEmpty())) TDEIO::NetAccess::del(tempFileName,NULL); tdemain->EnableFreshklam->setEnabled(TRUE); tdemain->DisableFreshklam->setEnabled(FALSE); //updateMetaDB(); } void Freshklam::updateMetaDB() { TQDate latestDate = TQDate::fromString(CollectionDB::instance()->latestMetaDBDate(),TQt::ISODate); TQDate today = TQDate::currentDate(); kdDebug() << latestDate << " " << today << endl; if (latestDate.daysTo(today) > 31){ /* MetaDB* updater = new MetaDB(); updater->update();*/ } } void Freshklam::slotCancel() { finish(); if (daemon_box->isChecked()){ if (!(killPID())) KMessageBox::information (this,i18n( "There was a problem killing the update process!") ); } status_label->setText(i18n("Canceled")); CollectionDB::instance()->insertEvent("Updates","Database Update Cancelled", dir_combo->url()); } void Freshklam::processDied() { KMessageBox::information (this,i18n( "Update Process died unexpectedly! Did you kill it manually?" )); status_label->setText(i18n("Update Process Died Unexpectedly!")); CollectionDB::instance()->insertEvent("Updates","Database Update Process Died Unexpectedly",dir_combo->url()); } void Freshklam::childExited() { int status = childproc->exitStatus(); /* int winid = 0; if (TDEApplication::kApplication()->mainWidget()) { winid = TDEApplication::kApplication()->mainWidget()->winId(); }*/ finish(); if (daemon_box->isChecked()){ //KMessageBox::information (this,"Update Running in Background!", "Auto-Update","Don't Show Again"); processDied(); return; } if (status == 0){ //updateMailClient(); }else if (status == 1){ //KMessageBox::information (this,"No Update Required - Database already up to date!"); //updateMailClient(); }else if (status == 40) KMessageBox::information (this, i18n("Unknown option passed.")); else if (status == 50) KMessageBox::information (this, i18n("Can't change directory.")); else if (status == 51) KMessageBox::information (this, i18n("Can't check MD5 sum.")); else if (status == 52) KMessageBox::information (this, i18n("Connection (network) problem.")); else if (status == 53) KMessageBox::information (this, i18n("Can't unlink a file.")); else if (status == 54) KMessageBox::information (this, i18n("MD5 or digital signature verification error.")); else if (status == 55) KMessageBox::information (this, i18n("Error reading file.")); else if (status == 56) KMessageBox::information (this, i18n("Config file error.")); else if (status == 57) KMessageBox::information (this, i18n("Can't create a new file.")); else if (status == 58) KMessageBox::information (this, i18n("Can't read database from remote server.")); else if (status == 59) KMessageBox::information (this, i18n("Mirrors are not fully synchronized (try again later).")); else if (status == 60) KMessageBox::information (this, i18n("Can't get information about clamav user from /etc/passwd.")); else if (status == 61) KMessageBox::information (this, i18n("Can't drop privileges.")); else KMessageBox::information (this, i18n("Warning - Unknown Error!")); if (errorMessage != "") KMessageBox::information (this,errorMessage); errorMessage = ""; status_label->setText( readymessage ); //if (status != 0) //matches_label->setText(""); } void Freshklam::updateMailClient() { config->setGroup("Freshklam"); if (lastDownloadPaths.contains(dir_combo->/*currentText*/url()) == 0) { dir_combo->comboBox()->insertItem(dir_combo->/*currentText*/url(), 0); lastDownloadPaths.prepend(dir_combo->/*currentText*/url()); if (lastDownloadPaths.count() > 10) { lastDownloadPaths.remove(lastDownloadPaths.fromLast()); dir_combo->comboBox()->removeItem(dir_combo->comboBox()->count() - 1); } }else{ lastDownloadPaths.remove(dir_combo->url()); lastDownloadPaths.prepend(dir_combo->url()); } config->writeEntry("lastDownloadPaths", lastDownloadPaths); config->writeEntry("ProxyIP", proxyIP->text()); config->writeEntry("ProxyPort", proxyPort->text()); config->writeEntry("ProxyUser", proxyUser->text()); config->writeEntry("ProxyPass", proxyPass->text()); //KMessageBox::information (this,proxyIP->text()); config->sync(); TDEConfig* mailconfig = new TDEConfig("kmailrc"); mailconfig->setGroup("General"); TQVariant nooffilters = mailconfig->readEntry("filters"); int result; int numfilters = nooffilters.toInt(); nooffilters = numfilters; for (int j=0; j != numfilters; j++ ){ TQVariant numb = j; TQString filtername=TQString("Filter #%1").arg(numb.toString()); mailconfig->setGroup(filtername); TQString binary = mailconfig->readEntry("action-args-0"); if (binary.find("klammail") != -1){ TQString pathonly = binary.section(" ",2,2); if (pathonly == dir_combo->url()) break; if (binary.find(" " + dir_combo->url() + " ") == -1){ TQString path = getenv("HOME"); result = KMessageBox::warningContinueCancelList(this, i18n( "Since you have changed the database location, KlamAV needs to change the parameters used for mail scanning in KMail. The change is displayed below. If you have KMail open you will need to close it now so that the change can take effect. If you want to make the change manually just click 'Cancel'. "),TQString("New filter command: klammail -d %1").arg(dir_combo->url()),i18n( "Update KMail Filters" ),i18n( "Update" )); switch (result) { case 2 : KMessageBox::information (this,"KMail has not been updated with the new database location."); break; case 5 : mailconfig->writeEntry("action-args-0",TQString("klammail -d %1").arg(dir_combo->url())); mailconfig->sync(); break; } break; } } } //KMessageBox::information (this,"Database Update Complete!"); delete mailconfig; } void Freshklam::receivedOutput(TDEProcess */*proc*/, char *buffer, int buflen) { //kdDebug() << buffer << endl; buf += TQCString(buffer, buflen+1); processOutput(); } void Freshklam::slotClear() { finish(); resultbox->clear(); status_label->setText(i18n("Ready")); matches_label->setText(""); } void Freshklam::setDirName(TQString dir){ // dir_combo->setEditText(dir); dir_combo->setURL(dir); } void Freshklam::writeConf() { KTempFile tf; if ( tf.status() != 0 ) { tf.close(); //delete tf; KMessageBox::information (this,i18n( "KMFilterActionWithCommand: Could not create temp file!" )); return; } //tf->setAutoDelete(TRUE); tempFileName = tf.name(); TQTextStream &ts = *(tf.textStream()); ts << "DatabaseMirror database.clamav.net" << "\n"; ts << "DNSDatabaseInfo current.cvd.clamav.net" << "\n"; ts << "Foreground True" << "\n"; if ( (proxyIP->hasAcceptableInput()) && (!(proxyIP->text().isEmpty()))) { ts << TQString("HTTPProxyServer %1").arg(proxyIP->text()) << "\n"; if ( proxyPort->hasAcceptableInput() ) { ts << TQString("HTTPProxyPort %1").arg(proxyPort->text()) << "\n"; } if ( proxyUser->hasAcceptableInput() && ! proxyUser->text().isEmpty() ) { ts << TQString("HTTPProxyUsername %1").arg(proxyUser->text()) << "\n"; if ( proxyPass->hasAcceptableInput() ) { ts << TQString("HTTPProxyPassword %1").arg(proxyPass->text()) << "\n"; } } } else return; } void Freshklam::initCheckBoxes(){ if ((config->readEntry("AutoUpdate")) == "Yes"){ daemon_box->setChecked(true); check_combo->setEnabled(true); }else{ daemon_box->setChecked(false); check_combo->setEnabled(false); } connect( daemon_box, SIGNAL(toggled(bool)), SLOT(handleChecks()) ); connect( check_combo, SIGNAL(activated(int)), SLOT(handleChecks()) ); if (daemon_box->isChecked()) slotSearch(); } void Freshklam::handleChecks(){ config = TDEGlobal::config(); config->setGroup("Freshklam"); if (daemon_box->isChecked()){ config->writeEntry("AutoUpdate","Yes"); check_combo->setEnabled(true); }else{ config->writeEntry("AutoUpdate","No"); check_combo->setEnabled(false); } config->writeEntry("NoOfUpdates", check_combo->currentText()); config->sync(); } bool Freshklam::killPID(){ TQFile inf(pidFileName); if (!inf.open(IO_ReadOnly)) return false; TQTextStream is(&inf); TQString line; while (!is.eof()) line = is.readLine(); inf.close(); ////kdDebug() << line << endl; if (!(line.isEmpty())) kill(line.toInt(), SIGKILL); else return false; if (!(pidFileName.isEmpty())) TDEIO::NetAccess::del(pidFileName,NULL); return true; } void Freshklam::disableInputs(){ dir_combo->setEnabled(false); daemon_box->setEnabled(false); check_combo->setEnabled(false); proxyIP->setEnabled(false); proxyUser->setEnabled(false); proxyPort->setEnabled(false); proxyPass->setEnabled(false); } void Freshklam::enableInputs(){ dir_combo->setEnabled(true); daemon_box->setEnabled(true); if (daemon_box->isChecked()) check_combo->setEnabled(true); else check_combo->setEnabled(false); proxyIP->setEnabled(true); proxyUser->setEnabled(true); proxyPort->setEnabled(true); proxyPass->setEnabled(true); } TQString Freshklam::getCurrentDBDir(){ unsigned int ret= 0; unsigned int no = 0; struct cl_engine *engine = NULL; TQStringList lastDownloadPaths; TQString dbdir; TQString db; config = TDEGlobal::config(); config->setGroup("Freshklam"); lastDownloadPaths = config->readListEntry("lastDownloadPaths"); for (TQStringList::Iterator ita = lastDownloadPaths.begin(); ita == lastDownloadPaths.begin() ; ita++){ dbdir = *ita; } if (dbdir != dir_combo->url()){ /* load all available databases from default directory */ #ifdef SUPPORT_CLAMAV_V095 ret = cl_load((const char *)dir_combo->url(), engine, &no, CL_DB_STDOPT); #else ret = cl_load((const char *)dir_combo->url(), &engine, &no, CL_DB_STDOPT); #endif //ret = cl_loaddbdir((const char *)dir_combo->url(), &root, &no); ////kdDebug() << "ret " << ret << endl; if (no == 0){ db = dbdir; }else{ db = dir_combo->url(); lastDownloadPaths.prepend(dir_combo->url()); config->writeEntry("lastDownloadPaths", lastDownloadPaths); config->sync(); } }else db = dbdir; return db; } void Freshklam::createDBDir(){ TQString path = getenv("HOME"); bool ok = true; // directory exist? path += "/.klamav"; TQDir klamavdir(path); if (!klamavdir.exists() && !klamavdir.mkdir(path)) ok = false; path += "/database"; if (ok) { TQDir klamavqdir(path); if (!klamavqdir.exists() && !klamavqdir.mkdir(path)) ok = false; else chmod((const char *)path,0700); } if (ok){ config->setGroup("Freshklam"); config->writeEntry("lastDownloadPaths", lastDownloadPaths); config->sync(); lastDownloadPaths = config->readListEntry("lastDownloadPaths"); } KMessageBox::information (this,TQString(ok ? i18n( "Your Virus Database location has been set up as '%1'. You can change this to something else if you want to." ) : i18n( "I cannot create the directory '%1' for you. Something is wrong with your HOME or klamav directory. You have to adjust your Virus Database directory by your self." )).arg(path)); if (ok){ int result = KMessageBox::warningContinueCancel(this, i18n( "Would you like to download the latest Virus Database to your new database location now? (You can do this later manually if you want.)"),i18n( "Download Virus Database" ),i18n( "Download" )); switch (result) { case 2 : KMessageBox::information (this,i18n( "You should update the database manually at your earliest convenience.") ); break; case 5 : tdemain->firstDownload = true; break; } } } bool Freshklam::isFreshklamAlive(){ if (freshklamAlive) return true; return false; } void Freshklam::enableAutoUpdates() { config = TDEGlobal::config(); config->writeEntry("AutoUpdate","Yes"); config->sync(); daemon_box->setChecked(true); check_combo->setEnabled(true); slotSearch(); } void Freshklam::getCurrentVersionOfClamAV( ) { ////kdDebug() << "version clamav" << endl; TQString suCommand=TQString("clamscan -V"); versionproc = new KProcIO(); versionproc->setUseShell(TRUE); versionproc->setUsePty (KProcIO::Stdout,TRUE); *versionproc<start(KProcIO::NotifyOnExit); versionproc->closeWhenDone(); } void Freshklam::versionExited() { kdDebug() << "deleting versionproc" << endl; delete versionproc; } void Freshklam::readVersionLine(KProcIO *) { TQString lineout = ""; int pos; if ((pos = (versionproc->readln(lineout))) != -1) { if ((pos = (lineout.find("ClamAV"))) != -1){ lineout = lineout.stripWhiteSpace(); int StartPoint = (lineout.find("ClamAV") + 6); int EndPoint = lineout.find("/"); TQString currentClamAVVersion = lineout.mid(StartPoint,(EndPoint - StartPoint)); currentClamAVVersion = currentClamAVVersion.stripWhiteSpace(); KlamavConfig::setClamAVVersion(currentClamAVVersion); KlamavConfig::writeConfig(); ////kdDebug() << currentClamAVVersion << endl; } } versionproc->ackRead(); lineout = ""; } #include "freshklam.moc"