Version upgrade 4.00 #45

Merged
elB4RTO merged 113 commits from devel into main 2024-02-17 16:13:26 +01:00
2 changed files with 312 additions and 430 deletions
Showing only changes of commit ae92479fca - Show all commits

View file

@ -5,10 +5,10 @@
#include "modules/dialogs.h"
#include "modules/database/database.h"
#include "utilities/io.h"
#include <QString>
#include <QSqlQuery>
#include <QSqlError>
#include <QVariant>
@ -16,6 +16,9 @@
#include <unordered_map>
struct MakeNewDatabase {};
namespace CheckSec
{
@ -24,183 +27,158 @@ namespace /*private*/
//! Checks the tables' names integrity
/*!
\param db Database object, already initialized
\param db_name Database's name, used by the dialogs if necessary
\return The result of the check: 0 if failed with an error, 1 if all the integrity checks passed, 2 if a rebuild is needed
\see checkCollectionDatabase(), checkHashesDatabase(), newCollectionDatabase(), newHashesDatabase()
\param query Query instance from the target database
\return Whether the database is valid or not
\throw LogDoctorException, MakeNewDatabase
\see checkCollectionDatabase(), checkHashesDatabase(), newCollectionDatabase(), newHashesDatabase()
*/
int checkDatabaseTablesNames( QSqlDatabase& db, const QString& db_name ) noexcept
bool checkDatabaseTablesNames( QueryWrapper query )
{
bool make_new{false}, ok{true};
QSqlQuery query{ QSqlQuery( db ) };
if ( ! query.exec("SELECT name FROM sqlite_schema WHERE type = 'table';") ) {
// error querying database
ok &= false;
DialogSec::errDatabaseFailedExecuting( db_name, query.lastQuery(), query.lastError().text() );
} else {
std::unordered_map<QString, bool> tables_checks{
{"apache", false},
{"nginx", false},
{"iis", false} };
while ( query.next() ) {
QString table_name{ query.value(0).toString() };
if ( tables_checks.find( table_name ) == tables_checks.end() ) {
// unexpected table name
if ( DialogSec::choiceDatabaseWrongTable( db_name, table_name ) ) {
// agreed to renew
make_new |= true;
} else {
// refused to renew
ok &= false;
}
break;
query( QStringLiteral("SELECT name FROM sqlite_schema WHERE type = 'table';") );
} else {
// table found
tables_checks.at( table_name ) |= true;
}
}
if ( ok && !make_new ) {
for ( const auto& [ tbl, res ] : tables_checks ) {
if ( ! res ) {
// a table has not been found
if ( DialogSec::choiceDatabaseMissingTable( db_name, tbl ) ) {
// agreed to renew
make_new |= true;
} else {
// refused to renew
ok &= false;
}
break;
}
}
}
tables_checks.clear();
}
query.finish();
if ( ok ) {
if ( make_new ) {
return 2;
std::unordered_map<QString, bool> checks{
{"apache", false},
{"nginx", false},
{"iis", false} };
while ( query->next() ) {
const QString table{ query[0].toString() };
if ( const auto tbl{ checks.find( table ) }; tbl != checks.end() ) {
tbl->second |= true;
} else {
return 1;
// unexpected table name
if ( DialogSec::choiceDatabaseWrongTable( query.dbName(), table ) ) {
// agreed to renew
throw MakeNewDatabase{};
} else {
// refused to renew
return false;
}
}
}
return 0;
for ( const auto& [tbl,res] : checks ) {
if ( ! res ) {
// a table has not been found
if ( DialogSec::choiceDatabaseMissingTable( query.dbName(), tbl ) ) {
// agreed to renew
throw MakeNewDatabase{};
} else {
// refused to renew
return false;
}
}
}
return true;
}
//! Builds a new database for the logs Collection
/*!
\param db Database object, already initialized
\param db_name Database's name, used by the dialogs if necessary
\return The result of the operation
\see checkCollectionDatabase(), checkHashesDatabase()
\param db Database object
\param db_path The database file's path
\param ws_names Database's tables names
\return The result of the operation
\see checkCollectionDatabase(), checkHashesDatabase()
*/
bool newCollectionDatabase( QSqlDatabase& db, const QString& db_name, const std::vector<QString>& ws_names ) noexcept
bool newCollectionDatabase( DatabaseWrapper db, const std::string& db_path, const std::vector<QString>& ws_names ) noexcept
{
bool successful{ true };
// create the database
if ( ! db.open() ) {
// error opening database
successful &= false;
DialogSec::errDatabaseFailedOpening( db_name, db.lastError().text() );
try {
db.open( db_path, true );
} else {
// succesfully creted database file, now create the tables
QSqlQuery query;
const QString stmt{ QStringLiteral(R"(
CREATE TABLE "%1" (
"year" SMALLINT,
"month" TINYINT,
"day" TINYINT,
"hour" TINYINT,
"minute" TINYINT,
"second" TINYINT,
"protocol" TEXT,
"method" TEXT,
"uri" TEXT,
"query" TEXT,
"response" SMALLINT,
"time_taken" INTEGER,
"bytes_sent" INTEGER,
"bytes_received" INTEGER,
"client" TEXT,
"user_agent" TEXT,
"cookie" TEXT,
"referrer" TEXT
);)")};
for ( const QString& ws_name : ws_names ) {
if ( ! successful ) { break; }
// compose the statement with the table name for the access logs
query.prepare( "\
CREATE TABLE \""+ws_name+"\" (\
\"year\" SMALLINT,\
\"month\" TINYINT,\
\"day\" TINYINT,\
\"hour\" TINYINT,\
\"minute\" TINYINT,\
\"second\" TINYINT,\
\"protocol\" TEXT,\
\"method\" TEXT,\
\"uri\" TEXT,\
\"query\" TEXT,\
\"response\" SMALLINT,\
\"time_taken\" INTEGER,\
\"bytes_sent\" INTEGER,\
\"bytes_received\" INTEGER,\
\"client\" TEXT,\
\"user_agent\" TEXT,\
\"cookie\" TEXT,\
\"referrer\" TEXT\
);");
if ( ! query.exec() ) {
QueryWrapper query{ db.getQuery() };
if ( ! query->exec( stmt.arg( ws_name ) ) ) {
// error creating table
successful &= false;
DialogSec::errDatabaseFailedExecuting(
QString( db_name ),
QString("CREATE TABLE \"%1\" (...)").arg( ws_name ),
QString( query.lastError().text() ) );
db.name(),
QStringLiteral(R"(CREATE TABLE "%1" (...))").arg( ws_name ),
query->lastError().text() );
throw LogDoctorException{};
}
query.finish();
}
// inform about creation
if ( successful ) {
DialogSec::msgDatabaseCreated( db_name );
} else {
DialogSec::errDatabaseFailedCreating( db_name );
}
} catch (...) {
DialogSec::errDatabaseFailedCreating( db.name() );
return false;
}
return successful;
DialogSec::msgDatabaseCreated( db.name() );
return true;
}
//! Builds a new database for the used log files' Hashes
/*!
\param db Database object, already initialized
\param db_name Database's name, used by the dialogs if necessary
\return The result of the operation
\see checkCollectionDatabase(), checkHashesDatabase()
\param db Database object, already initialized
\param db_path The database file's path
\param ws_names Database's tables names
\return The result of the operation
\see checkCollectionDatabase(), checkHashesDatabase()
*/
bool newHashesDatabase( QSqlDatabase& db, const QString& db_name, const std::vector<QString>& ws_names ) noexcept
bool newHashesDatabase( DatabaseWrapper db, const std::string& db_path, const std::vector<QString>& ws_names ) noexcept
{
bool successful{ true };
// create the database
if ( ! db.open() ) {
// error opening database
successful &= false;
DialogSec::errDatabaseFailedOpening( db_name, db.lastError().text() );
try {
db.open( db_path, true );
} else {
// succesfully creted database file, now create the tables
QSqlQuery query;
const QString stmt{ QStringLiteral(R"(
CREATE TABLE "%1" (
"hash" TEXT
);)")};
for ( const QString& ws_name : ws_names ) {
if ( ! successful ) { break; }
// compose the statement with the table name for the access logs
query.prepare( "\
CREATE TABLE \""+ws_name+"\" (\
\"hash\" TEXT\
);");
if ( ! query.exec() ) {
QueryWrapper query{ db.getQuery() };
if ( ! query->exec( stmt.arg( ws_name ) ) ) {
// error creating table
successful &= false;
DialogSec::errDatabaseFailedExecuting(
QString( db_name ),
QString("CREATE TABLE \"%1\" (...)").arg( ws_name ),
QString( query.lastError().text() ) );
db.name(),
QStringLiteral(R"(CREATE TABLE "%1" (...))").arg( ws_name ),
query->lastError().text() );
throw LogDoctorException{};
}
query.finish();
}
// inform about creation
if ( successful ) {
DialogSec::msgDatabaseCreated( db_name );
} else {
DialogSec::errDatabaseFailedCreating( db_name );
}
} catch (...) {
DialogSec::errDatabaseFailedCreating( db.name() );
return false;
}
return successful;
DialogSec::msgDatabaseCreated( db.name() );
return true;
}
} // namespace (private)
@ -208,314 +186,240 @@ bool newHashesDatabase( QSqlDatabase& db, const QString& db_name, const std::vec
bool checkCollectionDatabase( const std::string& db_path ) noexcept
{
bool make_new{false}, ok{true};
std::error_code err;
QString err_msg;
const QString db_name{ QString::fromStdString( db_path.substr( db_path.find_last_of( '/' ) + 1 ) ) };
const std::vector<QString> ws_names{ "apache", "nginx", "iis" };
QSqlDatabase db{ QSqlDatabase::database(DatabasesNames::data) };
db.setDatabaseName( QString::fromStdString( db_path ) );
try {
// check the existence
if ( IOutils::exists( db_path ) ) {
// check file type and permissions
if ( ! checkDatabaseFile( db_path, db_name ) ) {
ok &= false;
} else {
// database file seems ok, now try to open
if ( ! db.open() ) {
// error opening database
ok &= false;
DialogSec::errDatabaseFailedOpening( db_name, db.lastError().text() );
DatabaseWrapper db{ DatabaseHandler::get( DatabaseType::Data ) };
if ( ! IOutils::exists( db_path ) ) {
// ask to create a new one
if ( DialogSec::choiceDatabaseNotFound( db.name() ) ) {
// choosed to create it
throw MakeNewDatabase{};
} else {
// database successfully opened, now check the tables
const int check{ checkDatabaseTablesNames( db, db_name ) };
if ( check == 0 ) {
ok &= false;
} else if ( check == 2 ) {
make_new |= true;
// refused to create it, abort
return false;
}
}
// check file type and permissions
if ( ! checkDatabaseFile( db_path, db.name() ) ) {
return false;
}
// file seems ok, try to open
db.open( db_path, true );
// check the tables
if ( ! checkDatabaseTablesNames( db.getQuery() ) ) {
return false;
}
const QString stmt{ QStringLiteral("SELECT name, type FROM pragma_table_info('%1') AS tbinfo;") };
// check every WebServer table
for ( const QString& table : ws_names ) {
bool has_warning_column{ false };
// column's name:type associations
std::unordered_map<QString, std::tuple<QString, bool>> checks {
{"year", { "SMALLINT", false} },
{"month", { "TINYINT", false} },
{"day", { "TINYINT", false} },
{"hour", { "TINYINT", false} },
{"minute", { "TINYINT", false} },
{"second", { "TINYINT", false} },
{"protocol", { "TEXT", false} },
{"method", { "TEXT", false} },
{"uri", { "TEXT", false} },
{"query", { "TEXT", false} },
{"response", { "SMALLINT", false} },
{"time_taken", { "INTEGER", false} },
{"bytes_sent", { "INTEGER", false} },
{"bytes_received", { "INTEGER", false} },
{"client", { "TEXT", false} },
{"user_agent", { "TEXT", false} },
{"cookie", { "TEXT", false} },
{"referrer", { "TEXT", false} }
};
QueryWrapper query{ db.getQuery() };
query( stmt.arg( table ) );
while ( query->next() ) {
const QString col_name{ query[0].toString() };
const QString col_type{ query[1].toString() };
if ( col_name == "warning" ) {
// provide backward compatibility, this column will be removed from the table
has_warning_column |= true;
} else if ( const auto it{ checks.find( col_name ) }; it != checks.end() ) {
// column found, check the data-type
auto& type{ it->second };
if ( col_type == std::get<0>( type ) ) {
// same data-type
std::get<1>( type ) |= true;
} else {
// different data-type, ask to renew
if ( DialogSec::choiceDatabaseWrongDataType( db.name(), table, col_name, col_type ) ) {
// agreed to renew
throw MakeNewDatabase{};
} else {
// refused to renew
return false;
}
}
} else {
// unexpected column
if ( DialogSec::choiceDatabaseWrongColumn( db.name(), table, col_name ) ) {
// agreed to renew
throw MakeNewDatabase{};
} else {
// refused to renew
return false;
}
}
QSqlQuery query{ QSqlQuery( db ) };
if ( ok && !make_new ) {
}
// check every WebServer table, both access and error
for ( const QString& table : ws_names ) {
if ( has_warning_column ) {
// provide backward compatibility
query->finish();
query( QStringLiteral(R"(ALTER TABLE "%1" DROP COLUMN "warning";)").arg( table ) );
}
if ( !ok || make_new ) { break; }
bool has_warning_column{ false };
// column's name:type associations
std::unordered_map<QString, std::tuple<QString, bool>>
data_types {
{"year", { "SMALLINT", false} },
{"month", { "TINYINT", false} },
{"day", { "TINYINT", false} },
{"hour", { "TINYINT", false} },
{"minute", { "TINYINT", false} },
{"second", { "TINYINT", false} },
{"protocol", { "TEXT", false} },
{"method", { "TEXT", false} },
{"uri", { "TEXT", false} },
{"query", { "TEXT", false} },
{"response", { "SMALLINT", false} },
{"time_taken", { "INTEGER", false} },
{"bytes_sent", { "INTEGER", false} },
{"bytes_received", { "INTEGER", false} },
{"client", { "TEXT", false} },
{"user_agent", { "TEXT", false} },
{"cookie", { "TEXT", false} },
{"referrer", { "TEXT", false} }
};
// query table's columns' infoes for access logs
if ( ! query.exec( "SELECT name, type FROM pragma_table_info('"+table+"') AS tbinfo;" ) ) {
// error opening database
ok &= false;
DialogSec::errDatabaseFailedExecuting( db_name, query.lastQuery(), query.lastError().text() );
}
// iterate over results
while ( query.next() ) {
const QString col_name{ query.value(0).toString() };
const QString col_type{ query.value(1).toString() };
if ( col_name == "warning" ) {
// provide backward compatibility, this column will be removed from the table
has_warning_column |= true;
} else if ( data_types.find( col_name ) == data_types.end() ) {
// unexpected column
if ( DialogSec::choiceDatabaseWrongColumn( db_name, table, col_name ) ) {
// agreed to renew
make_new |= true;
} else {
// refused to renew
ok &= false;
}
break;
} else {
// column found, check the data-type
const QString data_type{ std::get<0>( data_types.at( col_name ) ) };
if ( col_type == data_type ) {
// same data-type
data_types.at( col_name ) = std::tuple( data_type, true );
} else {
// different data-type, ask to renew
if ( DialogSec::choiceDatabaseWrongDataType( db_name, table, col_name, col_type ) ) {
// agreed to renew
make_new |= true;
} else {
// refused to renew
ok &= false;
}
break;
}
}
}
if ( ok && !make_new ) {
if ( has_warning_column ) {
// provide backward compatibility
query.finish();
if ( ! query.exec( "ALTER TABLE \""+table+"\" DROP COLUMN \"warning\";" ) ) {
// failed to remove the column
ok &= false;
DialogSec::errDatabaseFailedExecuting( db_name, query.lastQuery(), query.lastError().text() );
break;
}
}
for ( const auto& [ col, tup ] : data_types ) {
if ( ! std::get<1>( tup ) ) {
// a column has not been found
if ( DialogSec::choiceDatabaseMissingColumn( db_name, table, col ) ) {
// agreed to renew
make_new |= true;
} else {
// refused to renew
ok &= false;
}
break;
}
}
}
query.finish();
if ( !ok || make_new ) { break; }
for ( const auto& [col,type] : checks ) {
if ( ! std::get<1>( type ) ) {
// a column has not been found
if ( DialogSec::choiceDatabaseMissingColumn( db.name(), table, col ) ) {
// agreed to renew
throw MakeNewDatabase{};
} else {
// refused to renew
return false;
}
}
}
}
} else {
// database does not exist, yet, ask to create a new one
if ( DialogSec::choiceDatabaseNotFound( QString(db_name) ) ) {
// choosed to create it
make_new |= true;
} catch (const MakeNewDatabase&) {
} else {
// refused to create it, abort
ok &= false;
}
}
if ( ok && make_new ) {
// rename the current db file as a 'copy'
if ( IOutils::exists( db_path ) ) {
// a database already exists, try rename it
std::error_code err;
if ( ! IOutils::renameAsCopy( db_path, err ) ) {
// failed to rename
ok &= false;
QString err_msg;
if ( err ) {
err_msg = QString::fromStdString( err.message() );
}
DialogSec::errRenaming( QString::fromStdString(db_path), err_msg );
}/* else {
// renamed successfully, make new one
}*/
}
if ( ok ) {
ok = newCollectionDatabase( db, db_name, ws_names );
return false;
}
}
return newCollectionDatabase( DatabaseHandler::get( DatabaseType::Data ), db_path, ws_names );
} catch (const LogDoctorException&) {
return false;
}
if ( db.isOpen() ) {
db.close();
}
return ok;
return true;
}
bool checkHashesDatabase( const std::string& db_path ) noexcept
{
bool make_new{false}, ok{true};
std::error_code err;
QString err_msg;
const QString db_name{ QString::fromStdString( db_path.substr( db_path.find_last_of( '/' ) + 1 ) ) };
const std::vector<QString> ws_names { "apache", "nginx", "iis" };
QSqlDatabase db{ QSqlDatabase::database(DatabasesNames::hashes) };
db.setDatabaseName( QString::fromStdString( db_path ) );
try {
// check the existence
if ( IOutils::exists( db_path ) ) {
// check file type and permissions
if ( ! checkDatabaseFile( db_path, db_name ) ) {
ok &= false;
} else {
// database file seems ok, now try to open
if ( ! db.open() ) {
// error opening database
ok &= false;
DialogSec::errDatabaseFailedOpening( db_name, db.lastError().text() );
DatabaseWrapper db{ DatabaseHandler::get( DatabaseType::Hashes ) };
if ( ! IOutils::exists( db_path ) ) {
// database does not exist, yet, ask to create a new one
if ( DialogSec::choiceDatabaseNotFound( db.name() ) ) {
// choosed to create it
throw MakeNewDatabase{};
} else {
// database successfully opened, now check the tables
const int check = checkDatabaseTablesNames( db, db_name );
if ( check == 0 ) {
ok &= false;
} else if ( check == 2 ) {
make_new |= true;
}
QSqlQuery query{ QSqlQuery( db ) };
if ( ok && !make_new ) {
// refused to create it, abort
return false;
}
}
// check every WebServer table, both access and error
for ( const QString& table : ws_names ) {
// check file type and permissions
if ( ! checkDatabaseFile( db_path, db.name() ) ) {
return false;
}
if ( !ok || make_new ) { break; }
// column's name:type associations
bool name_ok{ false },
type_ok{ false };
// file seems ok, try to open
db.open( db_path, true );
// query table's columns' infoes for access logs
if ( ! query.exec( "SELECT name, type FROM pragma_table_info('"+table+"') AS tbinfo;" ) ) {
// error opening database
ok &= false;
DialogSec::errDatabaseFailedExecuting( db_name, query.lastQuery(), query.lastError().text() );
}
// iterate over results
while ( query.next() ) {
const QString col_name{ query.value(0).toString() };
const QString col_type{ query.value(1).toString() };
if ( col_name != "hash" ) {
// unexpected column
if ( DialogSec::choiceDatabaseWrongColumn( db_name, table, col_name ) ) {
// agreed to renew
make_new |= true;
} else {
// refused to renew
ok &= false;
}
break;
// check the tables
if ( ! checkDatabaseTablesNames( db.getQuery() ) ) {
return false;
}
} else {
// column found, check the data-type
name_ok |= true;
if ( col_type == "TEXT" ) {
// same data-type
type_ok |= true;
} else {
// different data-type, ask to renew
if ( DialogSec::choiceDatabaseWrongDataType( db_name, table, col_name, col_type ) ) {
// agreed to renew
make_new |= true;
} else {
// refused to renew
ok &= false;
}
break;
}
}
}
if ( ok && !make_new ) {
if ( !name_ok || !type_ok ) {
ok &= false;
}
}
query.finish();
const QString stmt{ QStringLiteral("SELECT name, type FROM pragma_table_info('%1') AS tbinfo;") };
// check every WebServer table, both access and error
for ( const QString& table : ws_names ) {
QueryWrapper query{ db.getQuery() };
query( stmt.arg( table ) );
while ( query->next() ) {
const QString col_name{ query[0].toString() };
const QString col_type{ query[1].toString() };
if ( col_name != "hash" ) {
// unexpected column
if ( DialogSec::choiceDatabaseWrongColumn( db.name(), table, col_name ) ) {
// agreed to renew
throw MakeNewDatabase{};
} else {
// refused to renew
return false;
}
} else if ( col_type != "TEXT" ) {
// different data-type, ask to renew
if ( DialogSec::choiceDatabaseWrongDataType( db.name(), table, col_name, col_type ) ) {
// agreed to renew
throw MakeNewDatabase{};
} else {
// refused to renew
return false;
}
}
}
}
} else {
// database does not exist, yet, ask to create a new one
if ( DialogSec::choiceDatabaseNotFound( QString(db_name) ) ) {
// choosed to create it
make_new |= true;
} catch (const MakeNewDatabase&) {
} else {
// refused to create it, abort
ok &= false;
}
}
if ( ok && make_new ) {
// rename the current db file as a 'copy'
if ( IOutils::exists( db_path ) ) {
// a database already exists, try rename it
std::error_code err;
if ( ! IOutils::renameAsCopy( db_path, err ) ) {
// failed to rename
ok &= false;
QString err_msg;
if ( err ) {
err_msg = QString::fromStdString( err.message() );
}
DialogSec::errRenaming( QString::fromStdString(db_path), err_msg );
}/* else {
// renamed successfully, make new one
}*/
}
if ( ok ) {
ok = newHashesDatabase( db, db_name, ws_names );
return false;
}
}
return newHashesDatabase( DatabaseHandler::get( DatabaseType::Hashes ), db_path, ws_names );
} catch (const LogDoctorException&) {
return false;
}
if ( db.isOpen() ) {
db.close();
}
return ok;
return true;
}

View file

@ -92,58 +92,36 @@ QString printableTime( const unsigned seconds ) noexcept
{
const unsigned mins{ seconds / 60u };
const unsigned secs{ seconds - (mins*60u) };
return QString("%1:%2").arg(
(mins<10u)
? QString("0%1").arg( mins )
: QString::number( mins ),
(secs<10u)
? QString("0%1").arg( secs )
: QString::number( secs )
);
return QStringLiteral("%1:%2")
.arg( mins, 2, 10, QChar('0') )
.arg( secs, 2, 10, QChar('0') );
}
QString printableTime( const int hour, const int minute, const int second ) noexcept
{
return QString("%1:%2:%3").arg(
(hour<10)
? QString("0%1").arg( hour )
: QString::number( hour ),
(minute<10)
? QString("0%1").arg( minute )
: QString::number( minute ),
(second<10)
? QString("0%1").arg( second )
: QString::number( second )
);
return QStringLiteral("%1:%2:%3")
.arg( hour, 2, 10, QChar('0') )
.arg( minute, 2, 10, QChar('0') )
.arg( second, 2, 10, QChar('0') );
}
QString printableDate( const QString& year, const int month, const QString& day ) noexcept
{
return QString("%1-%2-%3").arg(
year,
(month<10)
? QString("0%1").arg( month )
: QString::number( month ),
(day.size()<2)
? QString("0%1").arg( day )
: day
);
return QStringLiteral("%1-%2-%3")
.arg( year )
.arg( month, 2, 10, QChar('0') )
.arg( day, 2, QChar('0') );
}
QString printableDate( const int year, const int month, const int day ) noexcept
{
return QString("%1-%2-%3").arg(
QString::number( year ),
(month<10)
? QString("0%1").arg( month )
: QString::number( month ),
(day<10)
? QString("0%1").arg( day )
: QString::number( day )
);
return QStringLiteral("%1-%2-%3")
.arg( year )
.arg( month, 2, 10, QChar('0') )
.arg( day, 2, 10, QChar('0') );
}