diff options
author | Jiri Moskovcak <jmoskovc@redhat.com> | 2010-11-10 17:01:31 +0100 |
---|---|---|
committer | Jiri Moskovcak <jmoskovc@redhat.com> | 2010-11-10 18:27:05 +0100 |
commit | 3fce116492052267e5e1a634e5404b1b518f7ef3 (patch) | |
tree | 3160a2d429987573004fb8166d40542b76a7315b /src/plugins/SQLite3.cpp | |
parent | 9d2cb4518c3a8a72ccc714ddbc131aaa84506092 (diff) | |
download | abrt-3fce116492052267e5e1a634e5404b1b518f7ef3.tar.gz abrt-3fce116492052267e5e1a634e5404b1b518f7ef3.tar.xz abrt-3fce116492052267e5e1a634e5404b1b518f7ef3.zip |
move files from lib/plugins to src/plugins
Diffstat (limited to 'src/plugins/SQLite3.cpp')
-rw-r--r-- | src/plugins/SQLite3.cpp | 742 |
1 files changed, 742 insertions, 0 deletions
diff --git a/src/plugins/SQLite3.cpp b/src/plugins/SQLite3.cpp new file mode 100644 index 00000000..2ed3f9fd --- /dev/null +++ b/src/plugins/SQLite3.cpp @@ -0,0 +1,742 @@ +/* + SQLite3.cpp + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 RedHat inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include <sqlite3.h> +#include "abrtlib.h" +#include "SQLite3.h" + +using namespace std; + +#define ABRT_TABLE_VERSION 4 +#define ABRT_TABLE_VERSION_STR "4" +#define ABRT_TABLE "abrt_v"ABRT_TABLE_VERSION_STR +#define ABRT_REPRESULT_TABLE "abrt_v"ABRT_TABLE_VERSION_STR"_reportresult" +#define SQLITE3_MASTER_TABLE "sqlite_master" + +#define COL_UID "UID" +#define COL_UUID "UUID" +#define COL_INFORMALL "InformAll" +#define COL_DEBUG_DUMP_PATH "DebugDumpPath" +#define COL_COUNT "Count" +#define COL_REPORTED "Reported" +#define COL_TIME "Time" +#define COL_MESSAGE "Message" + +#define COL_REPORTER "Reporter" + +#define NUM_COL 8 + +/* Is this string safe wrt SQL injection? + * PHP's mysql_real_escape_string() treats \, ', ", \x00, \n, \r, and \x1a as special. + * We are a bit more paranoid and disallow any control chars. + */ +static bool is_string_safe(const char *str) +{ +// Apparently SQLite allows unescaped newlines. More surprisingly, +// it does not unescape escaped ones - I see lines ending with \ when I do it. +// I wonder whether this is a bug in SQLite, and whether using unescaped +// newlines is a danger with other SQL servers. +// For now, I disabled newline escaping, and also allowed double quote. + const char *p = str; + while (*p) + { + unsigned char c = *p; +// if (c == '\\' && p[1] != '\0') +// { +// p += 2; +// continue; +// } + if ((c < ' ' && c != '\n') + || strchr("\\\'", c) //was: "\\\"\'" + ) { + error_msg("Probable SQL injection: '%s'", str); + return false; + } + p++; + } + return true; +} + +#ifdef UNUSED_FOR_NOW +/* Escape \n */ +static string sql_escape(const char *str) +{ + const char *s = str; + unsigned len = 0; + do + { + if (*s == '\n') + len++; + len++; + } while (*s++); + + char buf[len]; + s = str; + char *d = buf; + do + { + if (*s == '\n') + *d++ = '\\'; + *d++ = *s; + } while (*s++); + + return buf; +} +#endif + +/* Note: + * expects "SELECT * FROM ...", not "SELECT <only some fields> FROM ..." + */ +static GList *vget_table(sqlite3 *db, const char *fmt, va_list p) +{ + char *sql = xvasprintf(fmt, p); + + char **table; + int ncol, nrow; + char *err = NULL; + int ret = sqlite3_get_table(db, sql, &table, &nrow, &ncol, &err); + if (ret != SQLITE_OK) + { + error_msg("Error in SQL:'%s' error: %s", sql, err); + free(sql); + sqlite3_free(err); + return (GList*)ERR_PTR; + } + VERB2 log("%s: %d rows returned by SQL:%s", __func__, nrow, sql); + free(sql); + + if (nrow > 0 && ncol < NUM_COL) + error_msg_and_die("Unexpected number of columns: %d", ncol); + + GList *rows = NULL; + int ii; + for (ii = 0; ii < nrow; ii++) + { + int jj; + struct db_row *row = (struct db_row*)xzalloc(sizeof(struct db_row)); + for (jj = 0; jj < ncol; jj++) + { + char *val = table[jj + (ncol*ii) + ncol]; + switch (jj) + { + case 0: row->db_uuid = xstrdup(val); break; + case 1: row->db_uid = xstrdup(val); break; + case 2: row->db_inform_all = xstrdup(val); break; + case 3: row->db_dump_dir = xstrdup(val); break; + case 4: row->db_count = xstrdup(val); break; + case 5: row->db_reported = xstrdup(val); break; + case 6: row->db_time = xstrdup(val); break; + case 7: row->db_message = xstrdup(val); break; + } + } + + VERB3 log("%s: row->db_uuid = '%s'", __func__, row->db_uuid); + VERB3 log("%s: row->db_uid = '%s'", __func__, row->db_uid); + VERB3 log("%s: row->db_inform_all = '%s'", __func__, row->db_inform_all); + VERB3 log("%s: row->db_dump_dir = '%s'", __func__, row->db_dump_dir); + VERB3 log("%s: row->db_count = '%s'", __func__, row->db_count); + VERB3 log("%s: row->db_reported = '%s'", __func__, row->db_reported); + VERB3 log("%s: row->db_time = '%s'", __func__, row->db_time); + VERB3 log("%s: row->db_message = '%s'", __func__, row->db_message); + rows = g_list_append(rows, row); + + } + sqlite3_free_table(table); + + return rows; +} + +static GList *get_table_or_die(sqlite3 *db, const char *fmt, ...) +{ + va_list p; + va_start(p, fmt); + GList *table = vget_table(db, fmt, p); + va_end(p); + + if (table == (GList*)ERR_PTR) + xfunc_die(); + + return table; +} + +static int vexecute_sql(sqlite3 *db, const char *fmt, va_list p) +{ + char *sql = xvasprintf(fmt, p); + + char *err = NULL; + int ret = sqlite3_exec(db, sql, /*callback:*/ NULL, /*callback param:*/ NULL, &err); + if (ret != SQLITE_OK) + { + error_msg("Error in SQL:'%s' error: %s", sql, err); + free(sql); + sqlite3_free(err); + return -1; + } + int affected = sqlite3_changes(db); + VERB2 log("%d rows affected by SQL:%s", affected, sql); + free(sql); + + return affected; +} + +static int execute_sql_or_die(sqlite3 *db, const char *fmt, ...) +{ + va_list p; + va_start(p, fmt); + int ret = vexecute_sql(db, fmt, p); + va_end(p); + + if (ret < 0) + xfunc_die(); + + return ret; +} + +static bool exists_uuid_uid(sqlite3 *db, const char *UUID, const char *UID) +{ + GList *table = get_table_or_die(db, "SELECT * FROM "ABRT_TABLE + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + UUID, UID + ); + + if (!table) + return false; + + db_list_free(table); + + return true; +} + +static void update_from_old_ver(sqlite3 *db, int old_version) +{ + static const char *const update_sql_commands[] = { + // v0 -> v1 + NULL, + // v1 -> v2 + "BEGIN TRANSACTION;" + "CREATE TABLE abrt_v2 (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_DEBUG_DUMP_PATH" VARCHAR NOT NULL," + COL_COUNT" INT NOT NULL DEFAULT 1," + COL_REPORTED" INT NOT NULL DEFAULT 0," + COL_TIME" VARCHAR NOT NULL DEFAULT 0," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID"));" + "INSERT INTO abrt_v2 " + "SELECT "COL_UUID"," + COL_UID"," + COL_DEBUG_DUMP_PATH"," + COL_COUNT"," + COL_REPORTED"," + COL_TIME"," + COL_MESSAGE + " FROM abrt;" + "DROP TABLE abrt;" + "COMMIT;", + // v2 -> v3 + "BEGIN TRANSACTION;" + "CREATE TABLE abrt_v3 (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_DEBUG_DUMP_PATH" VARCHAR NOT NULL," + COL_COUNT" INT NOT NULL DEFAULT 1," + COL_REPORTED" INT NOT NULL DEFAULT 0," + COL_TIME" VARCHAR NOT NULL DEFAULT 0," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID"));" + "INSERT INTO abrt_v3 " + "SELECT "COL_UUID"," + COL_UID"," + COL_DEBUG_DUMP_PATH"," + COL_COUNT"," + COL_REPORTED"," + COL_TIME"," + COL_MESSAGE + " FROM abrt_v2;" + "DROP TABLE abrt_v2;" + "CREATE TABLE abrt_v3_reportresult (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_REPORTER" VARCHAR NOT NULL," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID","COL_REPORTER"));" + "COMMIT;", + // v3-> v4 + "BEGIN TRANSACTION;" + "CREATE TABLE abrt_v4(" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_INFORMALL" INT NOT NULL DEFAULT 0," + COL_DEBUG_DUMP_PATH" VARCHAR NOT NULL," + COL_COUNT" INT NOT NULL DEFAULT 1," + COL_REPORTED" INT NOT NULL DEFAULT 0," + COL_TIME" VARCHAR NOT NULL DEFAULT 0," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID"));" + "INSERT INTO abrt_v4 " + "SELECT "COL_UUID"," + COL_UID"," + "0," /* COL_INFORMALL */ + COL_DEBUG_DUMP_PATH"," + COL_COUNT"," + COL_REPORTED"," + COL_TIME"," + COL_MESSAGE + " FROM abrt_v3;" + "DROP TABLE abrt_v3;" + "UPDATE abrt_v4" + " SET "COL_UID"='0', "COL_INFORMALL"=1" + " WHERE "COL_UID"='-1';" + "CREATE TABLE abrt_v4_reportresult (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_REPORTER" VARCHAR NOT NULL," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID","COL_REPORTER"));" + "INSERT INTO abrt_v4_reportresult " + "SELECT * FROM abrt_v3_reportresult;" + "DROP TABLE abrt_v3_reportresult;" + "COMMIT;", + }; + + while (old_version < ABRT_TABLE_VERSION) + { + execute_sql_or_die(db, update_sql_commands[old_version]); + old_version++; + } +} + +static bool check_table(sqlite3 *db) +{ + const char *command = "SELECT NAME FROM "SQLITE3_MASTER_TABLE" " + "WHERE TYPE='table' AND NAME like 'abrt_v%';"; + char **table; + int ncol, nrow; + char *err; + int ret = sqlite3_get_table(db, command, &table, &nrow, &ncol, &err); + if (ret != SQLITE_OK) + { + /* Should never happen */ + error_msg_and_die("SQLite3 database is corrupted"); + } + if (!nrow) + { + sqlite3_free_table(table); + return false; + } + + // table format: + // table[0]:"NAME" // table[1]:"SQL" <== field names from SELECT + // table[2]:"abrt_vNN" // table[3]:"sql" + char *tableName = table[0 + ncol]; + char *underscore = strchr(tableName, '_'); + if (underscore) + { + // It can be "abrt_vNN_something", thus using atoi(), not xatoi() + int tableVersion = atoi(underscore + 2); + sqlite3_free_table(table); + if (tableVersion < ABRT_TABLE_VERSION) + { + update_from_old_ver(db, tableVersion); + } + return true; + } + sqlite3_free_table(table); + update_from_old_ver(db, 1); + return true; +} + + +CSQLite3::CSQLite3() : + m_sDBPath(LOCALSTATEDIR "/spool/abrt/abrt-db"), + m_pDB(NULL) +{} + +CSQLite3::~CSQLite3() +{ + /* Paranoia. In C++, destructor will abort() if it was called while unwinding + * the stack and it throws an exception. + */ + try + { + DisConnect(); + m_sDBPath.clear(); + } + catch (...) + { + error_msg_and_die("Internal error"); + } +} + +void CSQLite3::DisConnect() +{ + if (m_pDB) + { + sqlite3_close(m_pDB); + m_pDB = NULL; + } +} + +void CSQLite3::Connect() +{ + int ret = sqlite3_open_v2(m_sDBPath.c_str(), + &m_pDB, + SQLITE_OPEN_READWRITE, + NULL + ); + + if (ret != SQLITE_OK) + { + if (ret != SQLITE_CANTOPEN) + { + error_msg_and_die("Can't open database '%s': %s", m_sDBPath.c_str(), sqlite3_errmsg(m_pDB)); + } + + ret = sqlite3_open_v2(m_sDBPath.c_str(), + &m_pDB, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + NULL + ); + if (ret != SQLITE_OK) + { + error_msg_and_die("Can't create database '%s': %s", m_sDBPath.c_str(), sqlite3_errmsg(m_pDB)); + } + } + + if (!check_table(m_pDB)) + { + execute_sql_or_die(m_pDB, + "CREATE TABLE "ABRT_TABLE" (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_INFORMALL" INT NOT NULL DEFAULT 0," + COL_DEBUG_DUMP_PATH" VARCHAR NOT NULL," + COL_COUNT" INT NOT NULL DEFAULT 1," + COL_REPORTED" INT NOT NULL DEFAULT 0," + COL_TIME" VARCHAR NOT NULL DEFAULT 0," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID"));" + ); + execute_sql_or_die(m_pDB, + "CREATE TABLE "ABRT_REPRESULT_TABLE" (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_REPORTER" VARCHAR NOT NULL," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID","COL_REPORTER"));" + ); + } +} + +void CSQLite3::Insert_or_Update(const char *crash_id, + bool inform_all_users, + const char *pDebugDumpPath, + const char *pTime) +{ + const char *UUID = strchr(crash_id, ':'); + if (!UUID + || !is_string_safe(crash_id) + || !is_string_safe(pDebugDumpPath) + || !is_string_safe(pTime) + ) { + return; + } + + /* Split crash_id into UID:UUID */ + unsigned uid_len = UUID - crash_id; + UUID++; + char UID[uid_len + 1]; + strncpy(UID, crash_id, uid_len); + UID[uid_len] = '\0'; + + if (!exists_uuid_uid(m_pDB, UUID, UID)) + { + execute_sql_or_die(m_pDB, + "INSERT INTO "ABRT_TABLE" (" + COL_UUID"," + COL_UID"," + COL_INFORMALL"," + COL_DEBUG_DUMP_PATH"," + COL_TIME + ")" + " VALUES ('%s','%s',%u,'%s','%s');", + UUID, UID, (unsigned)inform_all_users, pDebugDumpPath, pTime + ); + } + else + { + execute_sql_or_die(m_pDB, + "UPDATE "ABRT_TABLE + " SET "COL_COUNT"="COL_COUNT"+1,"COL_TIME"='%s'" + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + pTime, + UUID, UID + ); + } +} + +void CSQLite3::DeleteRow(const char *crash_id) +{ + const char *UUID = strchr(crash_id, ':'); + if (!UUID + || !is_string_safe(crash_id) + ) { + return; + } + + /* Split crash_id into UID:UUID */ + unsigned uid_len = UUID - crash_id; + UUID++; + char UID[uid_len + 1]; + strncpy(UID, crash_id, uid_len); + UID[uid_len] = '\0'; + + if (exists_uuid_uid(m_pDB, UUID, UID)) + { + execute_sql_or_die(m_pDB, "DELETE FROM "ABRT_TABLE + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + UUID, UID + ); + execute_sql_or_die(m_pDB, "DELETE FROM "ABRT_REPRESULT_TABLE + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + UUID, UID + ); + } + else + { + error_msg("crash_id %s is not found in DB", crash_id); + } +} + +void CSQLite3::DeleteRows_by_dir(const char *dump_dir) +{ + if (!is_string_safe(dump_dir)) + { + return; + } + + /* Get UID:UUID pair(s) to delete */ + GList *table = get_table_or_die(m_pDB, "SELECT * FROM "ABRT_TABLE + " WHERE "COL_DEBUG_DUMP_PATH"='%s';", + dump_dir + ); + + if (!table) + { + return; + } + + struct db_row *row = NULL; + /* Delete from both tables */ + for (GList *li = table; li != NULL; li = g_list_next(li)) + { + row = (struct db_row*)li->data; + execute_sql_or_die(m_pDB, + "DELETE FROM "ABRT_REPRESULT_TABLE + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + row->db_uuid, row->db_uid + ); + } + execute_sql_or_die(m_pDB, + "DELETE FROM "ABRT_TABLE + " WHERE "COL_DEBUG_DUMP_PATH"='%s'", + dump_dir + ); +} + +void CSQLite3::SetReported(const char *crash_id, const char *pMessage) +{ + const char *UUID = strchr(crash_id, ':'); + if (!UUID + || !is_string_safe(crash_id) + || !is_string_safe(pMessage) + ) { + return; + } + + /* Split crash_id into UID:UUID */ + unsigned uid_len = UUID - crash_id; + UUID++; + char UID[uid_len + 1]; + strncpy(UID, crash_id, uid_len); + UID[uid_len] = '\0'; + + if (exists_uuid_uid(m_pDB, UUID, UID)) + { + execute_sql_or_die(m_pDB, + "UPDATE "ABRT_TABLE + " SET "COL_REPORTED"=1" + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + UUID, UID + ); + execute_sql_or_die(m_pDB, + "UPDATE "ABRT_TABLE + " SET "COL_MESSAGE"='%s'" + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + pMessage, UUID, UID + ); + } + else + { + error_msg("crash_id %s is not found in DB", crash_id); + } +} + +void CSQLite3::SetReportedPerReporter(const char *crash_id, + const char *reporter, + const char *pMessage) +{ + const char *UUID = strchr(crash_id, ':'); + if (!UUID + || !is_string_safe(crash_id) + || !is_string_safe(reporter) + || !is_string_safe(pMessage) + ) { + return; + } + + /* Split crash_id into UID:UUID */ + unsigned uid_len = UUID - crash_id; + UUID++; + char UID[uid_len + 1]; + strncpy(UID, crash_id, uid_len); + UID[uid_len] = '\0'; + + int affected_rows = execute_sql_or_die(m_pDB, + "UPDATE "ABRT_REPRESULT_TABLE + " SET "COL_MESSAGE"='%s'" + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s' AND "COL_REPORTER"='%s'", + pMessage, + UUID, UID, reporter + ); + if (!affected_rows) + { + execute_sql_or_die(m_pDB, + "INSERT INTO "ABRT_REPRESULT_TABLE + " ("COL_UUID","COL_UID","COL_REPORTER","COL_MESSAGE")" + " VALUES ('%s','%s','%s','%s');", + UUID, UID, reporter, pMessage + ); + } +} + +GList *CSQLite3::GetUIDData(long caller_uid) +{ + GList *table = NULL; + + if (caller_uid == 0) + { + table = get_table_or_die(m_pDB, "SELECT * FROM "ABRT_TABLE";"); + } + else + { + table = get_table_or_die(m_pDB, "SELECT * FROM "ABRT_TABLE + " WHERE "COL_UID"='%ld' OR "COL_INFORMALL"=1;", + caller_uid + ); + } + return table; +} + +struct db_row *CSQLite3::GetRow(const char *crash_id) +{ + const char *UUID = strchr(crash_id, ':'); + if (!UUID + || !is_string_safe(crash_id) + ) { + return NULL; + } + + /* Split crash_id into UID:UUID */ + unsigned uid_len = UUID - crash_id; + UUID++; + char UID[uid_len + 1]; + strncpy(UID, crash_id, uid_len); + UID[uid_len] = '\0'; + + GList *table = get_table_or_die(m_pDB, "SELECT * FROM "ABRT_TABLE + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + UUID, UID + ); + + if (!table) + { + return NULL; + } + + GList *first = g_list_first(table); + struct db_row *row = db_rowcpy_from_list(first); + + db_list_free(table); + + return row; +} + +struct db_row *CSQLite3::GetRow_by_dir(const char *dir) +{ + if (!is_string_safe(dir)) + return NULL; + + GList *table = get_table_or_die(m_pDB, "SELECT * FROM "ABRT_TABLE + " WHERE "COL_DEBUG_DUMP_PATH"='%s';", + dir + ); + + if (!table) + return NULL; + + GList *first = g_list_first(table); + struct db_row *row = db_rowcpy_from_list(first); + + db_list_free(table); + + return row; +} + +void CSQLite3::SetSettings(const map_plugin_settings_t& pSettings) +{ + m_pSettings = pSettings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("DBPath"); + if (it != end) + { + m_sDBPath = it->second; + } +} + +//ok to delete? +//const map_plugin_settings_t& CSQLite3::GetSettings() +//{ +// m_pSettings["DBPath"] = m_sDBPath; +// +// return m_pSettings; +//} + +PLUGIN_INFO(DATABASE, + CSQLite3, + "SQLite3", + "0.0.2", + _("Keeps SQLite3 database about all crashes"), + "zprikryl@redhat.com,jmoskovc@redhat.com", + "https://fedorahosted.org/abrt/wiki", + ""); |