// (c) 2004 Mark Kretschmann // (c) 2004 Christian Muehlhaeuser // (c) 2004 Sami Nieminen // (c) 2005 Ian Monroe // See COPYING file for licensing information. #include "config.h" #include "klamavconfig.h" #include "collectiondb.h" #include "klamav.h" #include "activityviewer.h" #include "sqlite/sqlite3.h" #include #include #include #include #include #include //setupCoverFetcher() #include #include //setupCoverFetcher() #include #include #include #include #include #include #include //DbConnection::sqlite_power() #include //query() #include //usleep() ////////////////////////////////////////////////////////////////////////////////////////// // CLASS CollectionDB ////////////////////////////////////////////////////////////////////////////////////////// CollectionDB* CollectionDB::instance() { static CollectionDB db; return &db; } CollectionDB::CollectionDB( bool temporary ) : m_isTemporary( temporary ) { // initialize(); // } CollectionDB::~CollectionDB() { destroy(); } ////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC ////////////////////////////////////////////////////////////////////////////////////////// DbConnection *CollectionDB::getStaticDbConnection() { return m_dbConnPool->getDbConnection(); } void CollectionDB::returnStaticDbConnection( DbConnection *conn ) { m_dbConnPool->putDbConnection( conn ); } /** * Executes a SQL query on the already opened database * @param statement SQL program to execute. Only one SQL statement is allowed. * @return The queried data, or TQStringList() on error. */ TQStringList CollectionDB::query( const TQString& statement, DbConnection *conn ) { clock_t start; if ( DEBUGSQL ) { kdDebug() << "Query-start: " << statement << endl; start = clock(); } DbConnection *dbConn; if ( conn != NULL ) { dbConn = conn; } else { dbConn = m_dbConnPool->getDbConnection(); } //kdDebug() << statement << endl; TQStringList values = dbConn->query( statement ); //kdDebug() << values << endl; if ( conn == NULL ) { m_dbConnPool->putDbConnection( dbConn ); } if ( DEBUGSQL ) { clock_t finish = clock(); const double duration = (double) (finish - start) / CLOCKS_PER_SEC; kdDebug() << "SQL-query (" << duration << "s): " << statement << endl; } return values; } /** * Executes a SQL insert on the already opened database * @param statement SQL statement to execute. Only one SQL statement is allowed. * @return The rowid of the inserted item. */ int CollectionDB::insert( const TQString& statement, const TQString& table, DbConnection *conn ) { clock_t start; if ( DEBUGSQL ) { kdDebug() << "insert-start: " << statement << endl; start = clock(); } DbConnection *dbConn; if ( conn != NULL ) { dbConn = conn; } else { dbConn = m_dbConnPool->getDbConnection(); } //kdDebug() << statement << endl; int id = dbConn->insert( statement, table ); //kdDebug() << id << endl; if ( conn == NULL ) { m_dbConnPool->putDbConnection( dbConn ); } if ( DEBUGSQL ) { clock_t finish = clock(); const double duration = (double) (finish - start) / CLOCKS_PER_SEC; kdDebug() << "SQL-insert (" << duration << "s): " << statement << endl; } return id; } bool CollectionDB::isEmpty() { TQStringList values; values = query( "SELECT COUNT( type ) FROM klamav_activity LIMIT 0, 1;" ); return values.isEmpty() ? true : values.first() == "0"; } bool CollectionDB::isValid(const TQString &column, const TQString &table) { TQStringList values1; TQStringList values2; values1 = query( TQString("SELECT COUNT( %1 ) FROM %2 LIMIT 0, 1;").arg(column).arg(table) ); //TODO? this returns true if value1 or value2 is not empty. Shouldn't this be and (&&)??? return !values1.isEmpty(); } void CollectionDB::createTables( DbConnection */*conn*/ ) { //create tag table createActivityTable(); //createMetaDBTable(); //create indexes /* query( TQString( "CREATE INDEX date_idx%1 ON klamav_activity%2( date );" ) .arg( conn ? "_temp" : "" ).arg( conn ? "_temp" : "" ), conn );*/ } void CollectionDB::createActivityTable( DbConnection *conn ) { query( "CREATE TABLE klamav_activity ( date TEXT, type TEXT, event TEXT, file TEXT);", conn ); } void CollectionDB::createMetaDBTable( DbConnection *conn ) { query( "CREATE TABLE klamav_metadb ( id INTEGER PRIMARY KEY, date TEXT, submission INTEGER, creator TEXT, virus TEXT, alias TEXT, sender TEXT);", conn ); loadMetaDBTable(conn); } void CollectionDB::loadMetaDBTable( DbConnection */*conn*/ ) { TQString location = locate("data", "klamav/about/metadb.txt"); /* query( TQString( ".import %1 klamav_activity;" ).arg(location), conn ); return;*/ TQFile file( location ); if ( file.open( IO_ReadOnly ) ) { TQTextStream stream( &file ); TQString line; while ( !stream.atEnd() ) { line = stream.readLine(); // line of text excluding '\n' TQStringList columns = TQStringList::split("\t",line.remove("\"")); if (columns.count() >= 5) insertMetaDBEntry(columns[0],columns[1],columns[2],columns[3],columns[4],columns[5]); } file.close(); } } void CollectionDB::dropTables( DbConnection *conn ) { query( TQString( "DROP TABLE klamav_activity%1;" ).arg( conn ? "_temp" : "" ), conn ); query( TQString( "DROP TABLE klamav_metadb%1;" ).arg( conn ? "_temp" : "" ), conn ); } void CollectionDB::clearTables( DbConnection *conn ) { TQString clearCommand = "DELETE FROM"; query( TQString( "%1 klamav_activity%2;" ).arg( clearCommand ).arg( conn ? "_temp" : "" ), conn ); query( TQString( "%1 klamav_metadb%2;" ).arg( clearCommand ).arg( conn ? "_temp" : "" ), conn ); } void CollectionDB::moveTempTables( DbConnection *conn ) { insert( "INSERT INTO klamav_activity SELECT * FROM klamav_activity_temp;", NULL, conn ); insert( "INSERT INTO klamav_metadb SELECT * FROM klamav_activity_temp;", NULL, conn ); } uint CollectionDB::IDFromValue( TQString name, TQString value, bool autocreate, const bool temporary, const bool updateSpelling, DbConnection *conn ) { if ( temporary ) name.append( "_temp" ); else conn = NULL; TQStringList values = query( TQString( "SELECT id, name FROM %1 WHERE name LIKE '%2';" ) .arg( name ) .arg( CollectionDB::instance()->escapeString( value ) ), conn ); if ( updateSpelling && !values.isEmpty() && ( values[1] != value ) ) { query( TQString( "UPDATE %1 SET id = %2, name = '%3' WHERE id = %4;" ) .arg( name ) .arg( values.first() ) .arg( CollectionDB::instance()->escapeString( value ) ) .arg( values.first() ), conn ); } //check if item exists. if not, should we autocreate it? uint id; if ( values.isEmpty() && autocreate ) { id = insert( TQString( "INSERT INTO %1 ( name ) VALUES ( '%2' );" ) .arg( name ) .arg( CollectionDB::instance()->escapeString( value ) ), name, conn ); return id; } return values.isEmpty() ? 0 : values.first().toUInt(); } TQString CollectionDB::valueFromID( TQString table, uint id ) { TQStringList values = query( TQString( "SELECT name FROM %1 WHERE id=%2;" ) .arg( table ) .arg( id ) ); return values.isEmpty() ? 0 : values.first(); } TQString CollectionDB::typeCount( const TQString &type_id ) { TQStringList values = query( TQString( "SELECT COUNT( type ) FROM klamav_activity WHERE type = %1;" ) .arg( type_id ) ); return values.first(); } TQStringList CollectionDB::messagesForType( const TQString &type_id, const bool isValue ) { if ( isValue) { return query( TQString( "SELECT * FROM klamav_activity " "WHERE (type = \"%1\" ) ;" ) .arg( type_id ) ); } return ""; } ////////////////////////////////////////////////////////////////////////////////////////// // PRIVATE SLOTS ////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////// // PRIVATE ////////////////////////////////////////////////////////////////////////////////////////// void CollectionDB::initialize() { m_dbConnPool = new DbConnectionPool( m_isTemporary ); DbConnection *dbConn = m_dbConnPool->getDbConnection(); m_dbConnPool->putDbConnection( dbConn ); // KConfig* config = amaroK::config( "Collection Browser" ); if(!dbConn->isConnected()) kdDebug() << "db not connected" << endl; //amaroK::MessageQueue::instance()->addMessage(dbConn->lastError()); if ( !dbConn->isInitialized() ) { createTables(); } // if (!isValid("id","klamav_metadb")) // createMetaDBTable(); if (!isValid("type","klamav_activity")) createActivityTable(); m_dbConnPool->createDbConnections(); } void CollectionDB::destroy() { delete m_dbConnPool; } ////////////////////////////////////////////////////////////////////////////////////////// // CLASS DbConnection ////////////////////////////////////////////////////////////////////////////////////////// DbConnection::DbConnection( DbConfig* config ) : m_config( config ) {} DbConnection::~DbConnection() {} ////////////////////////////////////////////////////////////////////////////////////////// // CLASS SqliteConnection ////////////////////////////////////////////////////////////////////////////////////////// SqliteConnection::SqliteConnection( SqliteConfig* config ) : DbConnection( config ) { TQString homepath = getenv("HOME"); const TQCString path = (homepath+"/.klamav/activity.db").local8Bit(); // Open database file and check for correctness m_initialized = false; TQFile file( path ); if ( file.open( IO_ReadOnly ) ) { TQString format; file.readLine( format, 50 ); if ( !format.startsWith( "SQLite format 3" ) ) { kdDebug() << "Database versions incompatible. Removing and rebuilding database.\n"; } else if ( sqlite3_open( path, &m_db ) != SQLITE_OK ) { kdDebug() << "Database file corrupt. Removing and rebuilding database.\n"; sqlite3_close( m_db ); } else m_initialized = true; } if ( !m_initialized ) { // Remove old db file; create new TQFile::remove( path ); if ( sqlite3_open( path, &m_db ) == SQLITE_OK ) { m_initialized = true; } } if ( m_initialized ) { if( sqlite3_create_function(m_db, "rand", 0, SQLITE_UTF8, NULL, sqlite_rand, NULL, NULL) != SQLITE_OK ) m_initialized = false; if( sqlite3_create_function(m_db, "power", 2, SQLITE_UTF8, NULL, sqlite_power, NULL, NULL) != SQLITE_OK ) m_initialized = false; } //optimization for speeding up SQLite query( "PRAGMA default_synchronous = OFF;" ); } SqliteConnection::~SqliteConnection() { if ( m_db ) sqlite3_close( m_db ); } TQStringList SqliteConnection::query( const TQString& statement ) { TQStringList values; int error; const char* tail; sqlite3_stmt* stmt; //compile SQL program to virtual machine error = sqlite3_prepare( m_db, statement.utf8(), statement.length(), &stmt, &tail ); if ( error != SQLITE_OK ) { kdDebug() << k_funcinfo << " sqlite3_compile error:" << endl; kdDebug() << sqlite3_errmsg( m_db ) << endl; kdDebug() << "on query: " << statement << endl; values = TQStringList(); } else { int busyCnt = 0; int number = sqlite3_column_count( stmt ); //execute virtual machine by iterating over rows while ( true ) { error = sqlite3_step( stmt ); if ( error == SQLITE_BUSY ) { if ( busyCnt++ > 20 ) { kdDebug() << "Busy-counter has reached maximum. Aborting this sql statement!\n"; break; } ::usleep( 100000 ); // Sleep 100 msec kdDebug() << "sqlite3_step: BUSY counter: " << busyCnt << endl; } if ( error == SQLITE_MISUSE ) kdDebug() << "sqlite3_step: MISUSE" << endl; if ( error == SQLITE_DONE || error == SQLITE_ERROR ) break; //iterate over columns for ( int i = 0; i < number; i++ ) { values << TQString::fromUtf8( (const char*) sqlite3_column_text( stmt, i ) ); } } //deallocate vm ressources sqlite3_finalize( stmt ); if ( error != SQLITE_DONE ) { kdDebug() << k_funcinfo << "sqlite_step error.\n"; kdDebug() << sqlite3_errmsg( m_db ) << endl; kdDebug() << "on query: " << statement << endl; values = TQStringList(); } } return values; } int SqliteConnection::insert( const TQString& statement, const TQString& /* table */ ) { int error; const char* tail; sqlite3_stmt* stmt; //compile SQL program to virtual machine error = sqlite3_prepare( m_db, statement.utf8(), statement.length(), &stmt, &tail ); if ( error != SQLITE_OK ) { kdDebug() << k_funcinfo << " sqlite3_compile error:" << endl; kdDebug() << sqlite3_errmsg( m_db ) << endl; kdDebug() << "on insert: " << statement << endl; } else { int busyCnt = 0; //execute virtual machine by iterating over rows while ( true ) { error = sqlite3_step( stmt ); if ( error == SQLITE_BUSY ) { if ( busyCnt++ > 20 ) { kdDebug() << "Busy-counter has reached maximum. Aborting this sql statement!\n"; break; } ::usleep( 100000 ); // Sleep 100 msec kdDebug() << "sqlite3_step: BUSY counter: " << busyCnt << endl; } if ( error == SQLITE_MISUSE ) kdDebug() << "sqlite3_step: MISUSE" << endl; if ( error == SQLITE_DONE || error == SQLITE_ERROR ) break; } //deallocate vm ressources sqlite3_finalize( stmt ); if ( error != SQLITE_DONE ) { kdDebug() << k_funcinfo << "sqlite_step error.\n"; kdDebug() << sqlite3_errmsg( m_db ) << endl; kdDebug() << "on insert: " << statement << endl; } } return sqlite3_last_insert_rowid( m_db ); } // this implements a RAND() function compatible with the MySQL RAND() (0-param-form without seed) void SqliteConnection::sqlite_rand(sqlite3_context *context, int /*argc*/, sqlite3_value ** /*argv*/) { sqlite3_result_double( context, static_cast(KApplication::random()) / (RAND_MAX+1.0) ); } // this implements a POWER() function compatible with the MySQL POWER() void SqliteConnection::sqlite_power(sqlite3_context *context, int argc, sqlite3_value **argv) { Q_ASSERT( argc==2 ); if( sqlite3_value_type(argv[0])==SQLITE_NULL || sqlite3_value_type(argv[1])==SQLITE_NULL ) { sqlite3_result_null(context); return; } double a = sqlite3_value_double(argv[0]); double b = sqlite3_value_double(argv[1]); sqlite3_result_double( context, pow(a,b) ); } ////////////////////////////////////////////////////////////////////////////////////////// // CLASS SqliteConfig ////////////////////////////////////////////////////////////////////////////////////////// SqliteConfig::SqliteConfig( const TQString& dbfile ) : m_dbfile( dbfile ) { } ////////////////////////////////////////////////////////////////////////////////////////// // CLASS DbConnectionPool ////////////////////////////////////////////////////////////////////////////////////////// DbConnectionPool::DbConnectionPool( bool temporary ) : m_isTemporary( temporary ) , m_semaphore( POOL_SIZE ) { m_dbConnType = DbConnection::sqlite; m_semaphore += POOL_SIZE; DbConnection *dbConn; m_dbConfig = new SqliteConfig( "activity.db" ); dbConn = new SqliteConnection( static_cast ( m_dbConfig ) ); enqueue( dbConn ); m_semaphore--; kdDebug() << "Available db connections: " << m_semaphore.available() << endl; } DbConnectionPool::~DbConnectionPool() { m_semaphore += POOL_SIZE; DbConnection *conn; bool vacuum = !m_isTemporary; while ( ( conn = dequeue() ) != 0 ) { if ( m_dbConnType == DbConnection::sqlite && vacuum ) { vacuum = false; kdDebug() << "Running VACUUM" << endl; conn->query( "VACUUM; "); } delete conn; } delete m_dbConfig; } void DbConnectionPool::createDbConnections() { for ( int i = 0; i < POOL_SIZE - 1; i++ ) { DbConnection *dbConn; dbConn = new SqliteConnection( static_cast ( m_dbConfig ) ); enqueue( dbConn ); m_semaphore--; } kdDebug() << "Available db connections: " << m_semaphore.available() << endl; } DbConnection *DbConnectionPool::getDbConnection() { m_semaphore++; return dequeue(); } void DbConnectionPool::putDbConnection( const DbConnection *conn ) { enqueue( conn ); m_semaphore--; } void CollectionDB::expireActivity(const TQString &days ) { int intdays = days.toInt(); if (intdays > 0) intdays--; query( TQString( "DELETE FROM klamav_activity WHERE date < datetime('now','localtime','-%2 days', 'start of day');" ).arg(intdays) ); } TQStringList CollectionDB::allActivity( ) { return query( TQString( "SELECT * FROM klamav_activity" ) ); } TQStringList CollectionDB::allActivityOfType(const TQString &type,const TQString &days ) { int intdays = days.toInt(); intdays--; if (type == "All Types") return query( TQString( "SELECT * FROM klamav_activity WHERE date > datetime('now','localtime','-%2 days', 'start of day');" ).arg(intdays) ); return query( TQString( "SELECT * FROM klamav_activity where type = '%1'" " and date > datetime('now','localtime','-%2 days', 'start of day');" ).arg(type).arg(intdays) ); } void CollectionDB::insertEvent(const TQString &type, const TQString &event, const TQString &file, DbConnection *conn) { if (((!(KlamavConfig::launchShutdown())) && (type == "Launch")) || ((!(KlamavConfig::softwareUpdates())) && (type == "Updates")) || ((!(KlamavConfig::dBUpdates())) && (type == "Updates")) || ((!(KlamavConfig::quarantined())) && (type == "Quarantine")) || ((!(KlamavConfig::virusFound())) && (type == "Virus Found")) || ((!(KlamavConfig::error())) && (type == "Error Found")) || ((!(KlamavConfig::startedStoppedCancelled())) && (type == "Manual Scan")) || ((!(KlamavConfig::startedStoppedCancelled())) && (type == "Auto-Scan"))) return; TQString date = query( TQString( "select datetime('now','localtime')" ) ).first(); insert( TQString( "INSERT INTO klamav_activity ( type, date, event, file )" " VALUES ( '%1', '%2', '%3', '%4' );" ) .arg( type ) .arg( date ) .arg( event ) .arg( file ) , "klamav_activity", conn); kmain->activityviewer->insertItem(date,type,event,file); } void CollectionDB::insertMetaDBEntry(const TQString &date, const TQString &submission, const TQString &creator,const TQString &virus,const TQString &alias, const TQString &sender,DbConnection *conn) { insert( TQString( "INSERT INTO klamav_metadb ( id, date, submission, creator, virus, alias, sender )" " VALUES ( NULL, \"%1\", \"%2\", \"%3\", \"%4\", \"%5\", \"%6\");" ) .arg( date ) .arg( submission.toInt() ) .arg( creator ) .arg( virus ) .arg( alias ) .arg( sender ) , "klamav_metadb", conn); } TQString CollectionDB::latestMetaDBDate( ) { TQStringList highest = query( TQString( "SELECT MAX(date) FROM klamav_metadb;" )); return highest.first(); } #include "collectiondb.moc"