From 813aceb854949a863e095930423d8d49793e4be8 Mon Sep 17 00:00:00 2001 From: tkmorris Date: Mon, 29 Jul 2013 17:26:37 -0300 Subject: [PATCH 1/6] Add PBKDF2 support to Murmur. --- scripts/murmur.ini | 8 + src/murmur/Meta.cpp | 6 + src/murmur/Meta.h | 5 + src/murmur/ServerDB.cpp | 741 ++++++++++++++++++++++++---------------- src/murmur/ServerDB.h | 6 +- src/murmur/murmur_pch.h | 1 + 6 files changed, 475 insertions(+), 292 deletions(-) diff --git a/scripts/murmur.ini b/scripts/murmur.ini index 98ccc43a311..77cb13a6257 100644 --- a/scripts/murmur.ini +++ b/scripts/murmur.ini @@ -129,6 +129,14 @@ users=100 # Allow clients to use HTML in messages, user comments and channel descriptions? #allowhtml=true +# This sets password hash storage to legacy mode (1.2.4 and before) +# (Note that this is insecure and should not be changed this unless absolutely needed) +#legacyPasswordwHash=false + +# This overrides the global default for kdf iterations, if used for password authentications +# (Note that you should only change this value if know both what you want and what you are doing) +#kdfIterations=0 + # Murmur retains the per-server log entries in an internal database which # allows it to be accessed over D-Bus/ICE. # How many days should such entries be kept? diff --git a/src/murmur/Meta.cpp b/src/murmur/Meta.cpp index 4bd0bbbe4af..3035115f1ce 100644 --- a/src/murmur/Meta.cpp +++ b/src/murmur/Meta.cpp @@ -54,6 +54,8 @@ MetaParams::MetaParams() { iMaxUsersPerChannel = 0; iMaxTextMessageLength = 5000; iMaxImageMessageLength = 131072; + bPlainPasswordHash = false; + iKdfIterations = 0; bAllowHTML = true; iDefaultChan = 0; bRememberChan = true; @@ -262,6 +264,8 @@ void MetaParams::read(QString fname) { iTimeout = typeCheckedFromSettings("timeout", iTimeout); iMaxTextMessageLength = typeCheckedFromSettings("textmessagelength", iMaxTextMessageLength); iMaxImageMessageLength = typeCheckedFromSettings("imagemessagelength", iMaxImageMessageLength); + bPlainPasswordHash = typeCheckedFromSettings("legacypasswordwhash", bPlainPasswordHash); + iKdfIterations = typeCheckedFromSettings("kdfiterations", iKdfIterations); bAllowHTML = typeCheckedFromSettings("allowhtml", bAllowHTML); iMaxBandwidth = typeCheckedFromSettings("bandwidth", iMaxBandwidth); iDefaultChan = typeCheckedFromSettings("defaultchannel", iDefaultChan); @@ -458,6 +462,8 @@ void MetaParams::read(QString fname) { qmConfig.insert(QLatin1String("port"),QString::number(usPort)); qmConfig.insert(QLatin1String("timeout"),QString::number(iTimeout)); qmConfig.insert(QLatin1String("textmessagelength"), QString::number(iMaxTextMessageLength)); + qmConfig.insert(QLatin1String("legacypasswordwhash"), bPlainPasswordHash ? QLatin1String("true") : QLatin1String("false")); + qmConfig.insert(QLatin1String("kdfiterations"), QString::number(iKdfIterations)); qmConfig.insert(QLatin1String("allowhtml"), bAllowHTML ? QLatin1String("true") : QLatin1String("false")); qmConfig.insert(QLatin1String("bandwidth"),QString::number(iMaxBandwidth)); qmConfig.insert(QLatin1String("users"),QString::number(iMaxUsers)); diff --git a/src/murmur/Meta.h b/src/murmur/Meta.h index 19bcf2029a3..df226ca655e 100644 --- a/src/murmur/Meta.h +++ b/src/murmur/Meta.h @@ -63,6 +63,8 @@ class MetaParams { int iMaxImageMessageLength; int iOpusThreshold; int iChannelNestingLimit; + bool bPlainPasswordHash; + int iKdfIterations; bool bAllowHTML; QString qsPassword; QString qsWelcomeText; @@ -123,6 +125,9 @@ class MetaParams { QSettings *qsSettings; + // db meta + size_t kdfIterations; + MetaParams(); ~MetaParams(); void read(QString fname = QString("murmur.ini")); diff --git a/src/murmur/ServerDB.cpp b/src/murmur/ServerDB.cpp index 67720d3ae29..fcf9d64d5c0 100644 --- a/src/murmur/ServerDB.cpp +++ b/src/murmur/ServerDB.cpp @@ -49,6 +49,11 @@ #define SQLEXECBATCH() ServerDB::execBatch(query) #define SOFTEXEC() ServerDB::exec(query, QString(), false) +// kdf (pbkdf2) constants +static const int derivedKeyLen = 48; // octets +static const int saltLen = 8; // octets +static const int iterationTime = 10; // milliseconds + class TransactionHolder { public: QSqlQuery *qsqQuery; @@ -152,317 +157,351 @@ ServerDB::ServerDB() { SQLDO("CREATE TABLE IF NOT EXISTS `%1meta`(`keystring` varchar(255) PRIMARY KEY, `value` varchar(255)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); SQLDO("SELECT `value` FROM `%1meta` WHERE `keystring` = 'version'"); + if (query.next()) version = query.value(0).toInt(); - - if (version < 5) { - if (version > 0) { - qWarning("Renaming old tables..."); - SQLDO("ALTER TABLE `%1servers` RENAME TO `%1servers%2`"); - if (version < 2) - SQLMAY("ALTER TABLE `%1log` RENAME TO `%1slog`"); - SQLDO("ALTER TABLE `%1slog` RENAME TO `%1slog%2`"); - SQLDO("ALTER TABLE `%1config` RENAME TO `%1config%2`"); - SQLDO("ALTER TABLE `%1channels` RENAME TO `%1channels%2`"); - if (version < 4) - SQLDO("ALTER TABLE `%1players` RENAME TO `%1players%2`"); - else - SQLDO("ALTER TABLE `%1users` RENAME TO `%1users%2`"); - SQLDO("ALTER TABLE `%1groups` RENAME TO `%1groups%2`"); - SQLDO("ALTER TABLE `%1group_members` RENAME TO `%1group_members%2`"); - SQLDO("ALTER TABLE `%1acl` RENAME TO `%1acl%2`"); - SQLDO("ALTER TABLE `%1channel_links` RENAME TO `%1channel_links%2`"); - SQLDO("ALTER TABLE `%1bans` RENAME TO `%1bans%2`"); - - if (version >= 4) { - SQLDO("ALTER TABLE `%1user_info` RENAME TO `%1user_info%2`"); - SQLDO("ALTER TABLE `%1channel_info` RENAME TO `%1channel_info%2`"); + if (! Meta::mp.bPlainPasswordHash) { + if (Meta::mp.iKdfIterations > 0) + Meta::mp.kdfIterations = static_cast(Meta::mp.iKdfIterations); + else { + SQLDO("SELECT `value` FROM `%1meta` WHERE `keystring` = 'pbkdf2_iterations'"); + if (query.next()) { + Meta::mp.kdfIterations = static_cast(query.value(0).toInt()); + } else { + size_t iterations = ServerDB::measurePbkdf2(); + SQLPREP("INSERT INTO `%1meta` (`keystring`, `value`) VALUES('pbkdf2_iterations',?)"); + query.addBindValue(static_cast(iterations)); + SQLEXEC(); + if (iterations < 1000) + qWarning("ServerDB: kdf iteration count may be insecure."); + Meta::mp.kdfIterations = iterations; } } + } - qWarning("Generating new tables..."); - if (Meta::mp.qsDBDriver == "QSQLITE") { + if (version < 6) { + if (version < 5) { if (version > 0) { - SQLDO("DROP TRIGGER IF EXISTS `%1log_timestamp`"); - SQLDO("DROP TRIGGER IF EXISTS `%1log_server_del`"); - SQLDO("DROP TRIGGER IF EXISTS `%1slog_timestamp`"); - SQLDO("DROP TRIGGER IF EXISTS `%1slog_server_del`"); - SQLDO("DROP TRIGGER IF EXISTS `%1config_server_del`"); - SQLDO("DROP TRIGGER IF EXISTS `%1channels_parent_del`"); - SQLDO("DROP TRIGGER IF EXISTS `%1channels_server_del`"); - SQLDO("DROP TRIGGER IF EXISTS `%1channel_info_del_channel`"); - SQLDO("DROP TRIGGER IF EXISTS `%1players_server_del`"); - SQLDO("DROP TRIGGER IF EXISTS `%1players_update_timestamp`"); - SQLDO("DROP TRIGGER IF EXISTS `%1users_server_del`"); - SQLDO("DROP TRIGGER IF EXISTS `%1users_update_timestamp`"); - SQLDO("DROP TRIGGER IF EXISTS `%1user_info_del_user`"); - SQLDO("DROP TRIGGER IF EXISTS `%1groups_del_channel`"); - SQLDO("DROP TRIGGER IF EXISTS `%1groups_members_del_group`"); - SQLDO("DROP TRIGGER IF EXISTS `%1groups_members_del_player`"); - SQLDO("DROP TRIGGER IF EXISTS `%1groups_members_del_user`"); - SQLDO("DROP TRIGGER IF EXISTS `%1acl_del_channel`"); - SQLDO("DROP TRIGGER IF EXISTS `%1acl_del_player`"); - SQLDO("DROP TRIGGER IF EXISTS `%1acl_del_user`"); - SQLDO("DROP TRIGGER IF EXISTS `%1channel_links_del_channel`"); - SQLDO("DROP TRIGGER IF EXISTS `%1bans_del_server`"); - - SQLDO("DROP INDEX IF EXISTS `%1log_time`"); - SQLDO("DROP INDEX IF EXISTS `%1slog_time`"); - SQLDO("DROP INDEX IF EXISTS `%1config_key`"); - SQLDO("DROP INDEX IF EXISTS `%1channel_id`"); - SQLDO("DROP INDEX IF EXISTS `%1channel_info_id`"); - SQLDO("DROP INDEX IF EXISTS `%1players_name`"); - SQLDO("DROP INDEX IF EXISTS `%1players_id`"); - SQLDO("DROP INDEX IF EXISTS `%1users_name`"); - SQLDO("DROP INDEX IF EXISTS `%1users_id`"); - SQLDO("DROP INDEX IF EXISTS `%1user_info_id`"); - SQLDO("DROP INDEX IF EXISTS `%1groups_name_channels`"); - SQLDO("DROP INDEX IF EXISTS `%1acl_channel_pri`"); + qWarning("Renaming old tables..."); + SQLDO("ALTER TABLE `%1servers` RENAME TO `%1servers%2`"); + if (version < 2) + SQLMAY("ALTER TABLE `%1log` RENAME TO `%1slog`"); + SQLDO("ALTER TABLE `%1slog` RENAME TO `%1slog%2`"); + SQLDO("ALTER TABLE `%1config` RENAME TO `%1config%2`"); + SQLDO("ALTER TABLE `%1channels` RENAME TO `%1channels%2`"); + if (version < 4) + SQLDO("ALTER TABLE `%1players` RENAME TO `%1players%2`"); + else + SQLDO("ALTER TABLE `%1users` RENAME TO `%1users%2`"); + SQLDO("ALTER TABLE `%1groups` RENAME TO `%1groups%2`"); + SQLDO("ALTER TABLE `%1group_members` RENAME TO `%1group_members%2`"); + SQLDO("ALTER TABLE `%1acl` RENAME TO `%1acl%2`"); + SQLDO("ALTER TABLE `%1channel_links` RENAME TO `%1channel_links%2`"); + SQLDO("ALTER TABLE `%1bans` RENAME TO `%1bans%2`"); + + if (version >= 4) { + SQLDO("ALTER TABLE `%1user_info` RENAME TO `%1user_info%2`"); + SQLDO("ALTER TABLE `%1channel_info` RENAME TO `%1channel_info%2`"); + } } - SQLDO("CREATE TABLE `%1servers` (`server_id` INTEGER PRIMARY KEY AUTOINCREMENT)"); + qWarning("Generating new tables..."); + if (Meta::mp.qsDBDriver == "QSQLITE") { + if (version > 0) { + SQLDO("DROP TRIGGER IF EXISTS `%1log_timestamp`"); + SQLDO("DROP TRIGGER IF EXISTS `%1log_server_del`"); + SQLDO("DROP TRIGGER IF EXISTS `%1slog_timestamp`"); + SQLDO("DROP TRIGGER IF EXISTS `%1slog_server_del`"); + SQLDO("DROP TRIGGER IF EXISTS `%1config_server_del`"); + SQLDO("DROP TRIGGER IF EXISTS `%1channels_parent_del`"); + SQLDO("DROP TRIGGER IF EXISTS `%1channels_server_del`"); + SQLDO("DROP TRIGGER IF EXISTS `%1channel_info_del_channel`"); + SQLDO("DROP TRIGGER IF EXISTS `%1players_server_del`"); + SQLDO("DROP TRIGGER IF EXISTS `%1players_update_timestamp`"); + SQLDO("DROP TRIGGER IF EXISTS `%1users_server_del`"); + SQLDO("DROP TRIGGER IF EXISTS `%1users_update_timestamp`"); + SQLDO("DROP TRIGGER IF EXISTS `%1user_info_del_user`"); + SQLDO("DROP TRIGGER IF EXISTS `%1groups_del_channel`"); + SQLDO("DROP TRIGGER IF EXISTS `%1groups_members_del_group`"); + SQLDO("DROP TRIGGER IF EXISTS `%1groups_members_del_player`"); + SQLDO("DROP TRIGGER IF EXISTS `%1groups_members_del_user`"); + SQLDO("DROP TRIGGER IF EXISTS `%1acl_del_channel`"); + SQLDO("DROP TRIGGER IF EXISTS `%1acl_del_player`"); + SQLDO("DROP TRIGGER IF EXISTS `%1acl_del_user`"); + SQLDO("DROP TRIGGER IF EXISTS `%1channel_links_del_channel`"); + SQLDO("DROP TRIGGER IF EXISTS `%1bans_del_server`"); + + SQLDO("DROP INDEX IF EXISTS `%1log_time`"); + SQLDO("DROP INDEX IF EXISTS `%1slog_time`"); + SQLDO("DROP INDEX IF EXISTS `%1config_key`"); + SQLDO("DROP INDEX IF EXISTS `%1channel_id`"); + SQLDO("DROP INDEX IF EXISTS `%1channel_info_id`"); + SQLDO("DROP INDEX IF EXISTS `%1players_name`"); + SQLDO("DROP INDEX IF EXISTS `%1players_id`"); + SQLDO("DROP INDEX IF EXISTS `%1users_name`"); + SQLDO("DROP INDEX IF EXISTS `%1users_id`"); + SQLDO("DROP INDEX IF EXISTS `%1user_info_id`"); + SQLDO("DROP INDEX IF EXISTS `%1groups_name_channels`"); + SQLDO("DROP INDEX IF EXISTS `%1acl_channel_pri`"); + } - SQLDO("CREATE TABLE `%1slog`(`server_id` INTEGER NOT NULL, `msg` TEXT, `msgtime` DATE)"); - SQLDO("CREATE INDEX `%1slog_time` ON `%1slog`(`msgtime`)"); - SQLDO("CREATE TRIGGER `%1slog_timestamp` AFTER INSERT ON `%1slog` FOR EACH ROW BEGIN UPDATE `%1slog` SET `msgtime` = datetime('now') WHERE rowid = new.rowid; END;"); - SQLDO("CREATE TRIGGER `%1slog_server_del` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1slog` WHERE `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1servers` (`server_id` INTEGER PRIMARY KEY AUTOINCREMENT)"); - SQLDO("CREATE TABLE `%1config` (`server_id` INTEGER NOT NULL, `key` TEXT, `value` TEXT)"); - SQLDO("CREATE UNIQUE INDEX `%1config_key` ON `%1config`(`server_id`, `key`)"); - SQLDO("CREATE TRIGGER `%1config_server_del` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1config` WHERE `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1slog`(`server_id` INTEGER NOT NULL, `msg` TEXT, `msgtime` DATE)"); + SQLDO("CREATE INDEX `%1slog_time` ON `%1slog`(`msgtime`)"); + SQLDO("CREATE TRIGGER `%1slog_timestamp` AFTER INSERT ON `%1slog` FOR EACH ROW BEGIN UPDATE `%1slog` SET `msgtime` = datetime('now') WHERE rowid = new.rowid; END;"); + SQLDO("CREATE TRIGGER `%1slog_server_del` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1slog` WHERE `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1channels` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `parent_id` INTEGER, `name` TEXT, `inheritacl` INTEGER)"); - SQLDO("CREATE UNIQUE INDEX `%1channel_id` ON `%1channels`(`server_id`, `channel_id`)"); - SQLDO("CREATE TRIGGER `%1channels_parent_del` AFTER DELETE ON `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1channels` WHERE `parent_id` = old.`channel_id` AND `server_id` = old.`server_id`; UPDATE `%1users` SET `lastchannel`=0 WHERE `lastchannel` = old.`channel_id` AND `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TRIGGER `%1channels_server_del` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1channels` WHERE `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1config` (`server_id` INTEGER NOT NULL, `key` TEXT, `value` TEXT)"); + SQLDO("CREATE UNIQUE INDEX `%1config_key` ON `%1config`(`server_id`, `key`)"); + SQLDO("CREATE TRIGGER `%1config_server_del` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1config` WHERE `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1channel_info` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `key` INTEGER, `value` TEXT)"); - SQLDO("CREATE UNIQUE INDEX `%1channel_info_id` ON `%1channel_info`(`server_id`, `channel_id`, `key`)"); - SQLDO("CREATE TRIGGER `%1channel_info_del_channel` AFTER DELETE on `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1channel_info` WHERE `channel_id` = old.`channel_id` AND `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1channels` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `parent_id` INTEGER, `name` TEXT, `inheritacl` INTEGER)"); + SQLDO("CREATE UNIQUE INDEX `%1channel_id` ON `%1channels`(`server_id`, `channel_id`)"); + SQLDO("CREATE TRIGGER `%1channels_parent_del` AFTER DELETE ON `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1channels` WHERE `parent_id` = old.`channel_id` AND `server_id` = old.`server_id`; UPDATE `%1users` SET `lastchannel`=0 WHERE `lastchannel` = old.`channel_id` AND `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TRIGGER `%1channels_server_del` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1channels` WHERE `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1users` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `pw` TEXT, `lastchannel` INTEGER, `texture` BLOB, `last_active` DATE)"); - SQLDO("CREATE UNIQUE INDEX `%1users_name` ON `%1users` (`server_id`,`name`)"); - SQLDO("CREATE UNIQUE INDEX `%1users_id` ON `%1users` (`server_id`, `user_id`)"); - SQLDO("CREATE TRIGGER `%1users_server_del` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1users` WHERE `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TRIGGER `%1users_update_timestamp` AFTER UPDATE OF `lastchannel` ON `%1users` FOR EACH ROW BEGIN UPDATE `%1users` SET `last_active` = datetime('now') WHERE `user_id` = old.`user_id` AND `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1channel_info` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `key` INTEGER, `value` TEXT)"); + SQLDO("CREATE UNIQUE INDEX `%1channel_info_id` ON `%1channel_info`(`server_id`, `channel_id`, `key`)"); + SQLDO("CREATE TRIGGER `%1channel_info_del_channel` AFTER DELETE on `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1channel_info` WHERE `channel_id` = old.`channel_id` AND `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1user_info` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `key` INTEGER, `value` TEXT)"); - SQLDO("CREATE UNIQUE INDEX `%1user_info_id` ON `%1user_info`(`server_id`, `user_id`, `key`)"); - SQLDO("CREATE TRIGGER `%1user_info_del_user` AFTER DELETE on `%1users` FOR EACH ROW BEGIN DELETE FROM `%1user_info` WHERE `user_id` = old.`user_id` AND `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1users` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `pw` TEXT, `salt` TEXT, `kdfmeter` INTEGER, `lastchannel` INTEGER, `texture` BLOB, `last_active` DATE)"); + SQLDO("CREATE UNIQUE INDEX `%1users_name` ON `%1users` (`server_id`,`name`)"); + SQLDO("CREATE UNIQUE INDEX `%1users_id` ON `%1users` (`server_id`, `user_id`)"); + SQLDO("CREATE TRIGGER `%1users_server_del` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1users` WHERE `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TRIGGER `%1users_update_timestamp` AFTER UPDATE OF `lastchannel` ON `%1users` FOR EACH ROW BEGIN UPDATE `%1users` SET `last_active` = datetime('now') WHERE `user_id` = old.`user_id` AND `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1groups` (`group_id` INTEGER PRIMARY KEY AUTOINCREMENT, `server_id` INTEGER NOT NULL, `name` TEXT, `channel_id` INTEGER NOT NULL, `inherit` INTEGER, `inheritable` INTEGER)"); - SQLDO("CREATE UNIQUE INDEX `%1groups_name_channels` ON `%1groups`(`server_id`, `channel_id`, `name`)"); - SQLDO("CREATE TRIGGER `%1groups_del_channel` AFTER DELETE ON `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1groups` WHERE `channel_id` = old.`channel_id` AND `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1user_info` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `key` INTEGER, `value` TEXT)"); + SQLDO("CREATE UNIQUE INDEX `%1user_info_id` ON `%1user_info`(`server_id`, `user_id`, `key`)"); + SQLDO("CREATE TRIGGER `%1user_info_del_user` AFTER DELETE on `%1users` FOR EACH ROW BEGIN DELETE FROM `%1user_info` WHERE `user_id` = old.`user_id` AND `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1group_members` (`group_id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `addit` INTEGER)"); - SQLDO("CREATE TRIGGER `%1groups_members_del_group` AFTER DELETE ON `%1groups` FOR EACH ROW BEGIN DELETE FROM `%1group_members` WHERE `group_id` = old.`group_id`; END;"); - SQLDO("CREATE TRIGGER `%1groups_members_del_user` AFTER DELETE on `%1users` FOR EACH ROW BEGIN DELETE FROM `%1group_members` WHERE `user_id` = old.`user_id` AND `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1groups` (`group_id` INTEGER PRIMARY KEY AUTOINCREMENT, `server_id` INTEGER NOT NULL, `name` TEXT, `channel_id` INTEGER NOT NULL, `inherit` INTEGER, `inheritable` INTEGER)"); + SQLDO("CREATE UNIQUE INDEX `%1groups_name_channels` ON `%1groups`(`server_id`, `channel_id`, `name`)"); + SQLDO("CREATE TRIGGER `%1groups_del_channel` AFTER DELETE ON `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1groups` WHERE `channel_id` = old.`channel_id` AND `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1acl` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `priority` INTEGER, `user_id` INTEGER, `group_name` TEXT, `apply_here` INTEGER, `apply_sub` INTEGER, `grantpriv` INTEGER, `revokepriv` INTEGER)"); - SQLDO("CREATE UNIQUE INDEX `%1acl_channel_pri` ON `%1acl`(`server_id`, `channel_id`, `priority`)"); - SQLDO("CREATE TRIGGER `%1acl_del_channel` AFTER DELETE ON `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1acl` WHERE `channel_id` = old.`channel_id` AND `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TRIGGER `%1acl_del_user` AFTER DELETE ON `%1users` FOR EACH ROW BEGIN DELETE FROM `%1acl` WHERE `user_id` = old.`user_id` AND `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1group_members` (`group_id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `addit` INTEGER)"); + SQLDO("CREATE TRIGGER `%1groups_members_del_group` AFTER DELETE ON `%1groups` FOR EACH ROW BEGIN DELETE FROM `%1group_members` WHERE `group_id` = old.`group_id`; END;"); + SQLDO("CREATE TRIGGER `%1groups_members_del_user` AFTER DELETE on `%1users` FOR EACH ROW BEGIN DELETE FROM `%1group_members` WHERE `user_id` = old.`user_id` AND `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1channel_links` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `link_id` INTEGER NOT NULL)"); - SQLDO("CREATE TRIGGER `%1channel_links_del_channel` AFTER DELETE ON `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1channel_links` WHERE `server_id` = old.`server_id` AND (`channel_id` = old.`channel_id` OR `link_id` = old.`channel_id`); END;"); - SQLDO("DELETE FROM `%1channel_links`"); + SQLDO("CREATE TABLE `%1acl` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `priority` INTEGER, `user_id` INTEGER, `group_name` TEXT, `apply_here` INTEGER, `apply_sub` INTEGER, `grantpriv` INTEGER, `revokepriv` INTEGER)"); + SQLDO("CREATE UNIQUE INDEX `%1acl_channel_pri` ON `%1acl`(`server_id`, `channel_id`, `priority`)"); + SQLDO("CREATE TRIGGER `%1acl_del_channel` AFTER DELETE ON `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1acl` WHERE `channel_id` = old.`channel_id` AND `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TRIGGER `%1acl_del_user` AFTER DELETE ON `%1users` FOR EACH ROW BEGIN DELETE FROM `%1acl` WHERE `user_id` = old.`user_id` AND `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1bans` (`server_id` INTEGER NOT NULL, `base` BLOB, `mask` INTEGER, `name` TEXT, `hash` TEXT, `reason` TEXT, `start` DATE, `duration` INTEGER)"); - SQLDO("CREATE TRIGGER `%1bans_del_server` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1bans` WHERE `server_id` = old.`server_id`; END;"); - } else { - if (version > 0) { - typedef QPair qsp; - QList qlForeignKeys; - QList qlIndexes; + SQLDO("CREATE TABLE `%1channel_links` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `link_id` INTEGER NOT NULL)"); + SQLDO("CREATE TRIGGER `%1channel_links_del_channel` AFTER DELETE ON `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1channel_links` WHERE `server_id` = old.`server_id` AND (`channel_id` = old.`channel_id` OR `link_id` = old.`channel_id`); END;"); + SQLDO("DELETE FROM `%1channel_links`"); - SQLPREP("SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA=? AND CONSTRAINT_TYPE='FOREIGN KEY'"); - query.addBindValue(Meta::mp.qsDatabase); - SQLEXEC(); - while (query.next()) - qlForeignKeys << qsp(query.value(0).toString(), query.value(1).toString()); + SQLDO("CREATE TABLE `%1bans` (`server_id` INTEGER NOT NULL, `base` BLOB, `mask` INTEGER, `name` TEXT, `hash` TEXT, `reason` TEXT, `start` DATE, `duration` INTEGER)"); + SQLDO("CREATE TRIGGER `%1bans_del_server` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1bans` WHERE `server_id` = old.`server_id`; END;"); + } else { + if (version > 0) { + typedef QPair qsp; + QList qlForeignKeys; + QList qlIndexes; - foreach(const qsp &key, qlForeignKeys) { - if (key.first.startsWith(Meta::mp.qsDBPrefix)) - ServerDB::exec(query, QString::fromLatin1("ALTER TABLE `%1` DROP FOREIGN KEY `%2`").arg(key.first).arg(key.second), true); - } + SQLPREP("SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA=? AND CONSTRAINT_TYPE='FOREIGN KEY'"); + query.addBindValue(Meta::mp.qsDatabase); + SQLEXEC(); + while (query.next()) + qlForeignKeys << qsp(query.value(0).toString(), query.value(1).toString()); + foreach(const qsp &key, qlForeignKeys) { + if (key.first.startsWith(Meta::mp.qsDBPrefix)) + ServerDB::exec(query, QString::fromLatin1("ALTER TABLE `%1` DROP FOREIGN KEY `%2`").arg(key.first).arg(key.second), true); + } - SQLPREP("SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA=? AND CONSTRAINT_TYPE='UNIQUE'"); - query.addBindValue(Meta::mp.qsDatabase); - SQLEXEC(); - while (query.next()) - qlIndexes << qsp(query.value(0).toString(), query.value(1).toString()); - foreach(const qsp &key, qlIndexes) { - if (key.first.startsWith(Meta::mp.qsDBPrefix)) - ServerDB::exec(query, QString::fromLatin1("ALTER TABLE `%1` DROP INDEX `%2`").arg(key.first).arg(key.second), true); - } + SQLPREP("SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA=? AND CONSTRAINT_TYPE='UNIQUE'"); + query.addBindValue(Meta::mp.qsDatabase); + SQLEXEC(); + while (query.next()) + qlIndexes << qsp(query.value(0).toString(), query.value(1).toString()); - qlIndexes.clear(); + foreach(const qsp &key, qlIndexes) { + if (key.first.startsWith(Meta::mp.qsDBPrefix)) + ServerDB::exec(query, QString::fromLatin1("ALTER TABLE `%1` DROP INDEX `%2`").arg(key.first).arg(key.second), true); + } - SQLPREP("SELECT DISTINCT TABLE_NAME, INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA=? AND INDEX_NAME != 'PRIMARY';"); - query.addBindValue(Meta::mp.qsDatabase); - SQLEXEC(); - while (query.next()) - qlIndexes << qsp(query.value(0).toString(), query.value(1).toString()); + qlIndexes.clear(); - foreach(const qsp &key, qlIndexes) { - if (key.first.startsWith(Meta::mp.qsDBPrefix)) - ServerDB::exec(query, QString::fromLatin1("ALTER TABLE `%1` DROP INDEX `%2`").arg(key.first).arg(key.second), true); + SQLPREP("SELECT DISTINCT TABLE_NAME, INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA=? AND INDEX_NAME != 'PRIMARY';"); + query.addBindValue(Meta::mp.qsDatabase); + SQLEXEC(); + while (query.next()) + qlIndexes << qsp(query.value(0).toString(), query.value(1).toString()); + + foreach(const qsp &key, qlIndexes) { + if (key.first.startsWith(Meta::mp.qsDBPrefix)) + ServerDB::exec(query, QString::fromLatin1("ALTER TABLE `%1` DROP INDEX `%2`").arg(key.first).arg(key.second), true); + } } + SQLDO("CREATE TABLE `%1servers`(`server_id` INTEGER PRIMARY KEY AUTO_INCREMENT) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + + SQLDO("CREATE TABLE `%1slog`(`server_id` INTEGER NOT NULL, `msg` TEXT, `msgtime` TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE INDEX `%1slog_time` ON `%1slog`(`msgtime`)"); + SQLDO("ALTER TABLE `%1slog` ADD CONSTRAINT `%1slog_server_del` FOREIGN KEY (`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1config` (`server_id` INTEGER NOT NULL, `key` varchar(255), `value` TEXT) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE UNIQUE INDEX `%1config_key` ON `%1config`(`server_id`, `key`)"); + SQLDO("ALTER TABLE `%1config` ADD CONSTRAINT `%1config_server_del` FOREIGN KEY (`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1channels` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `parent_id` INTEGER, `name` varchar(255), `inheritacl` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE UNIQUE INDEX `%1channel_id` ON `%1channels`(`server_id`, `channel_id`)"); + SQLDO("ALTER TABLE `%1channels` ADD CONSTRAINT `%1channels_parent_del` FOREIGN KEY (`server_id`, `parent_id`) REFERENCES `%1channels`(`server_id`,`channel_id`) ON DELETE CASCADE"); + SQLDO("ALTER TABLE `%1channels` ADD CONSTRAINT `%1channels_server_del` FOREIGN KEY (`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1channel_info` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `key` INTEGER, `value` LONGTEXT) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE UNIQUE INDEX `%1channel_info_id` ON `%1channel_info`(`server_id`, `channel_id`, `key`)"); + SQLDO("ALTER TABLE `%1channel_info` ADD CONSTRAINT `%1channel_info_del_channel` FOREIGN KEY (`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`,`channel_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1users` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `name` varchar(255), `pw` varchar(128), `salt` varchar(128), `kdfmeter` INTEGER, `lastchannel` INTEGER, `texture` LONGBLOB, `last_active` TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE INDEX `%1users_channel` ON `%1users`(`server_id`, `lastchannel`)"); + SQLDO("CREATE UNIQUE INDEX `%1users_name` ON `%1users` (`server_id`,`name`)"); + SQLDO("CREATE UNIQUE INDEX `%1users_id` ON `%1users` (`server_id`, `user_id`)"); + SQLDO("ALTER TABLE `%1users` ADD CONSTRAINT `%1users_server_del` FOREIGN KEY (`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1user_info` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `key` INTEGER, `value` LONGTEXT) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE UNIQUE INDEX `%1user_info_id` ON `%1user_info`(`server_id`, `user_id`, `key`)"); + SQLDO("ALTER TABLE `%1user_info` ADD CONSTRAINT `%1user_info_del_user` FOREIGN KEY (`server_id`, `user_id`) REFERENCES `%1users`(`server_id`,`user_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1groups` (`group_id` INTEGER PRIMARY KEY AUTO_INCREMENT, `server_id` INTEGER NOT NULL, `name` varchar(255), `channel_id` INTEGER NOT NULL, `inherit` INTEGER, `inheritable` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE UNIQUE INDEX `%1groups_name_channels` ON `%1groups`(`server_id`, `channel_id`, `name`)"); + SQLDO("ALTER TABLE `%1groups` ADD CONSTRAINT `%1groups_del_channel` FOREIGN KEY (`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`, `channel_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1group_members` (`group_id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `addit` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE INDEX `%1group_members_users` ON `%1group_members`(`server_id`, `user_id`)"); + SQLDO("ALTER TABLE `%1group_members` ADD CONSTRAINT `%1group_members_del_group` FOREIGN KEY (`group_id`) REFERENCES `%1groups`(`group_id`) ON DELETE CASCADE"); + SQLDO("ALTER TABLE `%1group_members` ADD CONSTRAINT `%1group_members_del_user` FOREIGN KEY (`server_id`, `user_id`) REFERENCES `%1users`(`server_id`,`user_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1acl` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `priority` INTEGER, `user_id` INTEGER, `group_name` varchar(255), `apply_here` INTEGER, `apply_sub` INTEGER, `grantpriv` INTEGER, `revokepriv` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE UNIQUE INDEX `%1acl_channel_pri` ON `%1acl`(`server_id`, `channel_id`, `priority`)"); + SQLDO("CREATE INDEX `%1acl_user` ON `%1acl`(`server_id`, `user_id`)"); + SQLDO("ALTER TABLE `%1acl` ADD CONSTRAINT `%1acl_del_channel` FOREIGN KEY (`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`, `channel_id`) ON DELETE CASCADE"); + SQLDO("ALTER TABLE `%1acl` ADD CONSTRAINT `%1acl_del_user` FOREIGN KEY (`server_id`, `user_id`) REFERENCES `%1users`(`server_id`, `user_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1channel_links` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `link_id` INTEGER NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("ALTER TABLE `%1channel_links` ADD CONSTRAINT `%1channel_links_del_channel` FOREIGN KEY(`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`, `channel_id`) ON DELETE CASCADE"); + SQLDO("DELETE FROM `%1channel_links`"); + + SQLDO("CREATE TABLE `%1bans` (`server_id` INTEGER NOT NULL, `base` BINARY(16), `mask` INTEGER, `name` varchar(255), `hash` CHAR(40), `reason` TEXT, `start` DATETIME, `duration` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("ALTER TABLE `%1bans` ADD CONSTRAINT `%1bans_del_server` FOREIGN KEY(`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); } - SQLDO("CREATE TABLE `%1servers`(`server_id` INTEGER PRIMARY KEY AUTO_INCREMENT) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - - SQLDO("CREATE TABLE `%1slog`(`server_id` INTEGER NOT NULL, `msg` TEXT, `msgtime` TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE INDEX `%1slog_time` ON `%1slog`(`msgtime`)"); - SQLDO("ALTER TABLE `%1slog` ADD CONSTRAINT `%1slog_server_del` FOREIGN KEY (`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1config` (`server_id` INTEGER NOT NULL, `key` varchar(255), `value` TEXT) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE UNIQUE INDEX `%1config_key` ON `%1config`(`server_id`, `key`)"); - SQLDO("ALTER TABLE `%1config` ADD CONSTRAINT `%1config_server_del` FOREIGN KEY (`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1channels` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `parent_id` INTEGER, `name` varchar(255), `inheritacl` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE UNIQUE INDEX `%1channel_id` ON `%1channels`(`server_id`, `channel_id`)"); - SQLDO("ALTER TABLE `%1channels` ADD CONSTRAINT `%1channels_parent_del` FOREIGN KEY (`server_id`, `parent_id`) REFERENCES `%1channels`(`server_id`,`channel_id`) ON DELETE CASCADE"); - SQLDO("ALTER TABLE `%1channels` ADD CONSTRAINT `%1channels_server_del` FOREIGN KEY (`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1channel_info` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `key` INTEGER, `value` LONGTEXT) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE UNIQUE INDEX `%1channel_info_id` ON `%1channel_info`(`server_id`, `channel_id`, `key`)"); - SQLDO("ALTER TABLE `%1channel_info` ADD CONSTRAINT `%1channel_info_del_channel` FOREIGN KEY (`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`,`channel_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1users` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `name` varchar(255), `pw` varchar(128), `lastchannel` INTEGER, `texture` LONGBLOB, `last_active` TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE INDEX `%1users_channel` ON `%1users`(`server_id`, `lastchannel`)"); - SQLDO("CREATE UNIQUE INDEX `%1users_name` ON `%1users` (`server_id`,`name`)"); - SQLDO("CREATE UNIQUE INDEX `%1users_id` ON `%1users` (`server_id`, `user_id`)"); - SQLDO("ALTER TABLE `%1users` ADD CONSTRAINT `%1users_server_del` FOREIGN KEY (`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1user_info` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `key` INTEGER, `value` LONGTEXT) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE UNIQUE INDEX `%1user_info_id` ON `%1user_info`(`server_id`, `user_id`, `key`)"); - SQLDO("ALTER TABLE `%1user_info` ADD CONSTRAINT `%1user_info_del_user` FOREIGN KEY (`server_id`, `user_id`) REFERENCES `%1users`(`server_id`,`user_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1groups` (`group_id` INTEGER PRIMARY KEY AUTO_INCREMENT, `server_id` INTEGER NOT NULL, `name` varchar(255), `channel_id` INTEGER NOT NULL, `inherit` INTEGER, `inheritable` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE UNIQUE INDEX `%1groups_name_channels` ON `%1groups`(`server_id`, `channel_id`, `name`)"); - SQLDO("ALTER TABLE `%1groups` ADD CONSTRAINT `%1groups_del_channel` FOREIGN KEY (`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`, `channel_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1group_members` (`group_id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `addit` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE INDEX `%1group_members_users` ON `%1group_members`(`server_id`, `user_id`)"); - SQLDO("ALTER TABLE `%1group_members` ADD CONSTRAINT `%1group_members_del_group` FOREIGN KEY (`group_id`) REFERENCES `%1groups`(`group_id`) ON DELETE CASCADE"); - SQLDO("ALTER TABLE `%1group_members` ADD CONSTRAINT `%1group_members_del_user` FOREIGN KEY (`server_id`, `user_id`) REFERENCES `%1users`(`server_id`,`user_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1acl` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `priority` INTEGER, `user_id` INTEGER, `group_name` varchar(255), `apply_here` INTEGER, `apply_sub` INTEGER, `grantpriv` INTEGER, `revokepriv` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE UNIQUE INDEX `%1acl_channel_pri` ON `%1acl`(`server_id`, `channel_id`, `priority`)"); - SQLDO("CREATE INDEX `%1acl_user` ON `%1acl`(`server_id`, `user_id`)"); - SQLDO("ALTER TABLE `%1acl` ADD CONSTRAINT `%1acl_del_channel` FOREIGN KEY (`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`, `channel_id`) ON DELETE CASCADE"); - SQLDO("ALTER TABLE `%1acl` ADD CONSTRAINT `%1acl_del_user` FOREIGN KEY (`server_id`, `user_id`) REFERENCES `%1users`(`server_id`, `user_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1channel_links` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `link_id` INTEGER NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("ALTER TABLE `%1channel_links` ADD CONSTRAINT `%1channel_links_del_channel` FOREIGN KEY(`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`, `channel_id`) ON DELETE CASCADE"); - SQLDO("DELETE FROM `%1channel_links`"); - - SQLDO("CREATE TABLE `%1bans` (`server_id` INTEGER NOT NULL, `base` BINARY(16), `mask` INTEGER, `name` varchar(255), `hash` CHAR(40), `reason` TEXT, `start` DATETIME, `duration` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("ALTER TABLE `%1bans` ADD CONSTRAINT `%1bans_del_server` FOREIGN KEY(`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); - } - if (version == 0) { - SQLDO("INSERT INTO `%1servers` (`server_id`) VALUES(1)"); - SQLDO("INSERT INTO `%1meta` (`keystring`, `value`) VALUES('version','5')"); - } else { - qWarning("Importing old data..."); + if (version == 0) { + SQLDO("INSERT INTO `%1servers` (`server_id`) VALUES(1)"); + SQLDO("INSERT INTO `%1meta` (`keystring`, `value`) VALUES('version','6')"); + } else { + qWarning("Importing old data..."); - if (Meta::mp.qsDBDriver != "QSQLITE") - SQLDO("SET FOREIGN_KEY_CHECKS = 0;"); - SQLDO("INSERT INTO `%1servers` (`server_id`) SELECT `server_id` FROM `%1servers%2`"); - SQLDO("INSERT INTO `%1slog` (`server_id`, `msg`, `msgtime`) SELECT `server_id`, `msg`, `msgtime` FROM `%1slog%2`"); + if (Meta::mp.qsDBDriver != "QSQLITE") + SQLDO("SET FOREIGN_KEY_CHECKS = 0;"); + SQLDO("INSERT INTO `%1servers` (`server_id`) SELECT `server_id` FROM `%1servers%2`"); + SQLDO("INSERT INTO `%1slog` (`server_id`, `msg`, `msgtime`) SELECT `server_id`, `msg`, `msgtime` FROM `%1slog%2`"); - if (version < 4) - SQLDO("INSERT INTO `%1config` (`server_id`, `key`, `value`) SELECT `server_id`, `keystring`, `value` FROM `%1config%2`"); - else - SQLDO("INSERT INTO `%1config` (`server_id`, `key`, `value`) SELECT `server_id`, `key`, `value` FROM `%1config%2`"); + if (version < 4) + SQLDO("INSERT INTO `%1config` (`server_id`, `key`, `value`) SELECT `server_id`, `keystring`, `value` FROM `%1config%2`"); + else + SQLDO("INSERT INTO `%1config` (`server_id`, `key`, `value`) SELECT `server_id`, `key`, `value` FROM `%1config%2`"); - SQLDO("INSERT INTO `%1channels` (`server_id`, `channel_id`, `parent_id`, `name`, `inheritacl`) SELECT `server_id`, `channel_id`, `parent_id`, `name`, `inheritacl` FROM `%1channels%2` ORDER BY `parent_id`, `channel_id`"); + SQLDO("INSERT INTO `%1channels` (`server_id`, `channel_id`, `parent_id`, `name`, `inheritacl`) SELECT `server_id`, `channel_id`, `parent_id`, `name`, `inheritacl` FROM `%1channels%2` ORDER BY `parent_id`, `channel_id`"); - if (version < 4) - SQLDO("INSERT INTO `%1users` (`server_id`, `user_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active`) SELECT `server_id`, `player_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active` FROM `%1players%2`"); - else - SQLDO("INSERT INTO `%1users` (`server_id`, `user_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active`) SELECT `server_id`, `user_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active` FROM `%1users%2`"); + if (version < 4) + SQLDO("INSERT INTO `%1users` (`server_id`, `user_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active`) SELECT `server_id`, `player_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active` FROM `%1players%2`"); + else + SQLDO("INSERT INTO `%1users` (`server_id`, `user_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active`) SELECT `server_id`, `user_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active` FROM `%1users%2`"); - SQLDO("INSERT INTO `%1groups` (`group_id`, `server_id`, `name`, `channel_id`, `inherit`, `inheritable`) SELECT `group_id`, `server_id`, `name`, `channel_id`, `inherit`, `inheritable` FROM `%1groups%2`"); + SQLDO("INSERT INTO `%1groups` (`group_id`, `server_id`, `name`, `channel_id`, `inherit`, `inheritable`) SELECT `group_id`, `server_id`, `name`, `channel_id`, `inherit`, `inheritable` FROM `%1groups%2`"); - if (version < 4) - SQLDO("INSERT INTO `%1group_members` (`group_id`, `server_id`, `user_id`, `addit`) SELECT `group_id`, `server_id`, `player_id`, `addit` FROM `%1group_members%2`"); - else - SQLDO("INSERT INTO `%1group_members` (`group_id`, `server_id`, `user_id`, `addit`) SELECT `group_id`, `server_id`, `user_id`, `addit` FROM `%1group_members%2`"); + if (version < 4) + SQLDO("INSERT INTO `%1group_members` (`group_id`, `server_id`, `user_id`, `addit`) SELECT `group_id`, `server_id`, `player_id`, `addit` FROM `%1group_members%2`"); + else + SQLDO("INSERT INTO `%1group_members` (`group_id`, `server_id`, `user_id`, `addit`) SELECT `group_id`, `server_id`, `user_id`, `addit` FROM `%1group_members%2`"); - if (version < 4) - SQLDO("INSERT INTO `%1acl` (`server_id`, `channel_id`, `priority`, `user_id`, `group_name`, `apply_here`, `apply_sub`, `grantpriv`, `revokepriv`) SELECT `server_id`, `channel_id`, `priority`, `player_id`, `group_name`, `apply_here`, `apply_sub`, `grantpriv`, `revokepriv` FROM `%1acl%2`"); - else - SQLDO("INSERT INTO `%1acl` (`server_id`, `channel_id`, `priority`, `user_id`, `group_name`, `apply_here`, `apply_sub`, `grantpriv`, `revokepriv`) SELECT `server_id`, `channel_id`, `priority`, `user_id`, `group_name`, `apply_here`, `apply_sub`, `grantpriv`, `revokepriv` FROM `%1acl%2`"); + if (version < 4) + SQLDO("INSERT INTO `%1acl` (`server_id`, `channel_id`, `priority`, `user_id`, `group_name`, `apply_here`, `apply_sub`, `grantpriv`, `revokepriv`) SELECT `server_id`, `channel_id`, `priority`, `player_id`, `group_name`, `apply_here`, `apply_sub`, `grantpriv`, `revokepriv` FROM `%1acl%2`"); + else + SQLDO("INSERT INTO `%1acl` (`server_id`, `channel_id`, `priority`, `user_id`, `group_name`, `apply_here`, `apply_sub`, `grantpriv`, `revokepriv`) SELECT `server_id`, `channel_id`, `priority`, `user_id`, `group_name`, `apply_here`, `apply_sub`, `grantpriv`, `revokepriv` FROM `%1acl%2`"); - SQLDO("INSERT INTO `%1channel_links` (`server_id`, `channel_id`, `link_id`) SELECT `server_id`, `channel_id`, `link_id` FROM `%1channel_links%2`"); - if (version < 4) { - QList > ql; - SQLPREP("SELECT `server_id`, `base`, `mask` FROM `%1bans%2`"); - SQLEXEC(); - while (query.next()) { - QList l; - l << query.value(0); - l << query.value(1); - l << query.value(2); - ql << l; - } - SQLPREP("INSERT INTO `%1bans` (`server_id`, `base`, `mask`) VALUES (?, ?, ?)"); - foreach(const QList &l, ql) { - - quint32 addr = htonl(l.at(1).toUInt()); - const char *ptr = reinterpret_cast(&addr); - - QByteArray qba(16, 0); - qba[10] = static_cast(-1); - qba[11] = static_cast(-1); - qba[12] = ptr[0]; - qba[13] = ptr[1]; - qba[14] = ptr[2]; - qba[15] = ptr[3]; - - query.addBindValue(l.at(0)); - query.addBindValue(qba); - query.addBindValue(l.at(2).toInt() + 96); + SQLDO("INSERT INTO `%1channel_links` (`server_id`, `channel_id`, `link_id`) SELECT `server_id`, `channel_id`, `link_id` FROM `%1channel_links%2`"); + if (version < 4) { + QList > ql; + SQLPREP("SELECT `server_id`, `base`, `mask` FROM `%1bans%2`"); SQLEXEC(); + while (query.next()) { + QList l; + l << query.value(0); + l << query.value(1); + l << query.value(2); + ql << l; + } + SQLPREP("INSERT INTO `%1bans` (`server_id`, `base`, `mask`) VALUES (?, ?, ?)"); + foreach(const QList &l, ql) { + + quint32 addr = htonl(l.at(1).toUInt()); + const char *ptr = reinterpret_cast(&addr); + + QByteArray qba(16, 0); + qba[10] = static_cast(-1); + qba[11] = static_cast(-1); + qba[12] = ptr[0]; + qba[13] = ptr[1]; + qba[14] = ptr[2]; + qba[15] = ptr[3]; + + query.addBindValue(l.at(0)); + query.addBindValue(qba); + query.addBindValue(l.at(2).toInt() + 96); + SQLEXEC(); + } + } else { + SQLDO("INSERT INTO `%1bans` (`server_id`, `base`, `mask`) SELECT `server_id`, `base`, `mask` FROM `%1bans%2`"); } - } else { - SQLDO("INSERT INTO `%1bans` (`server_id`, `base`, `mask`) SELECT `server_id`, `base`, `mask` FROM `%1bans%2`"); - } - if (version < 4) - SQLDO("INSERT INTO `%1user_info` SELECT `server_id`,`player_id`,1,`email` FROM `%1players%2` WHERE `email` IS NOT NULL"); + if (version < 4) + SQLDO("INSERT INTO `%1user_info` SELECT `server_id`,`player_id`,1,`email` FROM `%1players%2` WHERE `email` IS NOT NULL"); - if (version == 3) { - SQLDO("INSERT INTO `%1channel_info` SELECT `server_id`,`channel_id`,0,`description` FROM `%1channels%2` WHERE `description` IS NOT NULL"); - } + if (version == 3) { + SQLDO("INSERT INTO `%1channel_info` SELECT `server_id`,`channel_id`,0,`description` FROM `%1channels%2` WHERE `description` IS NOT NULL"); + } - if (version >= 4) { - SQLDO("INSERT INTO `%1user_info` SELECT * FROM `%1user_info%2`"); - SQLDO("INSERT INTO `%1channel_info` SELECT * FROM `%1channel_info%2`"); - } + if (version >= 4) { + SQLDO("INSERT INTO `%1user_info` SELECT * FROM `%1user_info%2`"); + SQLDO("INSERT INTO `%1channel_info` SELECT * FROM `%1channel_info%2`"); + } - if (Meta::mp.qsDBDriver != "QSQLITE") - SQLDO("SET FOREIGN_KEY_CHECKS = 1;"); - - qWarning("Removing old tables..."); - SQLDO("DROP TABLE IF EXISTS `%1slog%2`"); - SQLDO("DROP TABLE IF EXISTS `%1config%2`"); - SQLDO("DROP TABLE IF EXISTS `%1channel_info%2`"); - SQLDO("DROP TABLE IF EXISTS `%1channels%2`"); - SQLDO("DROP TABLE IF EXISTS `%1user_info%2`"); - SQLDO("DROP TABLE IF EXISTS `%1users%2`"); - SQLDO("DROP TABLE IF EXISTS `%1players%2`"); - SQLDO("DROP TABLE IF EXISTS `%1groups%2`"); - SQLDO("DROP TABLE IF EXISTS `%1group_members%2`"); - SQLDO("DROP TABLE IF EXISTS `%1acl%2`"); - SQLDO("DROP TABLE IF EXISTS `%1channel_links%2`"); - SQLDO("DROP TABLE IF EXISTS `%1bans%2`"); - SQLDO("DROP TABLE IF EXISTS `%1servers%2`"); - - SQLDO("UPDATE `%1meta` SET `value` = '5' WHERE `keystring` = 'version'"); + if (Meta::mp.qsDBDriver != "QSQLITE") + SQLDO("SET FOREIGN_KEY_CHECKS = 1;"); + + qWarning("Removing old tables..."); + SQLDO("DROP TABLE IF EXISTS `%1slog%2`"); + SQLDO("DROP TABLE IF EXISTS `%1config%2`"); + SQLDO("DROP TABLE IF EXISTS `%1channel_info%2`"); + SQLDO("DROP TABLE IF EXISTS `%1channels%2`"); + SQLDO("DROP TABLE IF EXISTS `%1user_info%2`"); + SQLDO("DROP TABLE IF EXISTS `%1users%2`"); + SQLDO("DROP TABLE IF EXISTS `%1players%2`"); + SQLDO("DROP TABLE IF EXISTS `%1groups%2`"); + SQLDO("DROP TABLE IF EXISTS `%1group_members%2`"); + SQLDO("DROP TABLE IF EXISTS `%1acl%2`"); + SQLDO("DROP TABLE IF EXISTS `%1channel_links%2`"); + SQLDO("DROP TABLE IF EXISTS `%1bans%2`"); + SQLDO("DROP TABLE IF EXISTS `%1servers%2`"); + + SQLDO("UPDATE `%1meta` SET `value` = '6' WHERE `keystring` = 'version'"); + } + } + else { + if (Meta::mp.qsDBDriver == "QSQLITE") { + qWarning("Generating new tables..."); + SQLDO("ALTER TABLE `%1users%2` RENAME TO `%1__olduserstable%2`"); + SQLDO("CREATE TABLE `%1users` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `pw` TEXT, `salt` TEXT, `kdfmeter` INTEGER, `lastchannel` INTEGER, `texture` BLOB, `last_active` DATE)"); + SQLDO("INSERT INTO `%1users%s` (`server_id`, `user_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active`) SELECT `server_id`, `user_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active` FROM `%1__olduserstable%2`"); + SQLDO("DROP TABLE `%1__olduserstable%2`"); + } else { + qWarning("Generating new tables..."); + SQLDO("ALTER TABLE `%1users%s` ADD salt VARCHAR(128), kdfmeter INTEGER AFTER pw"); + } + SQLDO("UPDATE `%1meta` SET `value` = '6' WHERE `keystring` = 'version'"); } } query.clear(); @@ -874,20 +913,59 @@ int Server::authenticate(QString &name, const QString &pw, int sessionId, const TransactionHolder th; QSqlQuery &query = *th.qsqQuery; - SQLPREP("SELECT `user_id`,`name`,`pw` FROM `%1users` WHERE `server_id` = ? AND LOWER(`name`) = LOWER(?)"); - query.addBindValue(iServerNum); - query.addBindValue(name); - SQLEXEC(); - if (query.next()) { - res = -1; - QString storedpw = query.value(2).toString(); - QString hashedpw = QString::fromLatin1(sha1(pw).toHex()); - - if (! storedpw.isEmpty() && (storedpw == hashedpw)) { - name = query.value(1).toString(); - res = query.value(0).toInt(); - } else if (query.value(0).toInt() == 0) { - return -1; + if (! pw.isEmpty()) { + SQLPREP("SELECT `user_id`,`name`,`pw`, `salt`, `kdfmeter` FROM `%1users` WHERE `server_id` = ? AND LOWER(`name`) = LOWER(?)"); + query.addBindValue(iServerNum); + query.addBindValue(name); + SQLEXEC(); + if (query.next()) { + QString storedpw = query.value(2).toString(); + QString storedsalt = query.value(3).toString(); + size_t storedmeter = static_cast(query.value(4).toInt()); + res = -1; + + if (storedmeter < 1) { + if (ServerDB::getHashPlain(pw) == storedpw) + { + name = query.value(1).toString(); + res = query.value(0).toInt(); + if (! Meta::mp.bPlainPasswordHash) + { + QMap info; + info.insert(ServerDB::User_Password, pw); + info.insert(ServerDB::User_KdfMeter, QString::number(static_cast(Meta::mp.kdfIterations))); + int userId = query.value(0).toInt(); + if(! setInfo(userId, info)) + qWarning("ServerDB: Failed to upgrade user account to kdf."); + } + } + } else { + if (ServerDB::getHashPbkdf2(storedsalt, pw, storedmeter) == storedpw) + { + name = query.value(1).toString(); + res = query.value(0).toInt(); + if(Meta::mp.bPlainPasswordHash) + { + QMap info; + info.insert(ServerDB::User_Password, pw); + int userId = query.value(0).toInt(); + if(! setInfo(userId, info)) + qWarning("ServerDB: Failed to downgrade user account to legacy hash."); + } + else if (storedmeter < Meta::mp.kdfIterations || storedmeter < Meta::mp.iKdfIterations) { + // user kdfmeter lower than global a/or override, update it + QMap info; + info.insert(ServerDB::User_Password, pw); + info.insert(ServerDB::User_KdfMeter, QString::number(static_cast(Meta::mp.kdfIterations))); + int userId = query.value(0).toInt(); + if(! setInfo(userId, info)) + qWarning("ServerDB: Failed to update user kdf shadow."); + } + } + } + if (query.value(0).toInt() == 0 && res == -1) { + return -1; + } } } @@ -976,12 +1054,27 @@ bool Server::setInfo(int id, const QMap &setinfo) { info.remove(ServerDB::User_LastActive); } if (info.contains(ServerDB::User_Password)) { - const QString &pw = info.value(ServerDB::User_Password); - QCryptographicHash hash(QCryptographicHash::Sha1); - hash.addData(pw.toUtf8()); + const QString pw = info.value(ServerDB::User_Password); + QString pwHash, hashSalt; + size_t kdfMeter; - SQLPREP("UPDATE `%1users` SET `pw`=? WHERE `server_id` = ? AND `user_id`=?"); - query.addBindValue(pw.isEmpty() ? QVariant() : QString::fromLatin1(hash.result().toHex())); + if(! pw.isEmpty()) { + if(Meta::mp.bPlainPasswordHash) { + pwHash = ServerDB::getHashPlain(pw); + } else { + kdfMeter = Meta::mp.kdfIterations; + if (info.contains(ServerDB::User_KdfMeter)) + kdfMeter = info.value(ServerDB::User_KdfMeter).toInt(); + + hashSalt = ServerDB::getHashSalt(); + pwHash = ServerDB::getHashPbkdf2(hashSalt, pw, kdfMeter); + } + } + + SQLPREP("UPDATE `%1users` SET `pw`=?, `salt`=?, `kdfmeter`=? WHERE `server_id` = ? AND `user_id`=?"); + query.addBindValue(pwHash); + query.addBindValue(hashSalt); + query.addBindValue(static_cast(kdfMeter)); query.addBindValue(iServerNum); query.addBindValue(id); SQLEXEC(); @@ -1052,9 +1145,14 @@ bool Server::setTexture(int id, const QByteArray &texture) { void ServerDB::setSUPW(int srvnum, const QString &pw) { TransactionHolder th; + QString pwHash, saltHash; - QCryptographicHash hash(QCryptographicHash::Sha1); - hash.addData(pw.toUtf8()); + if(! Meta::mp.bPlainPasswordHash) { + saltHash = ServerDB::getHashSalt(); + pwHash = getHashPbkdf2(saltHash, pw, Meta::mp.kdfIterations); + } + else + pwHash = getHashPlain(pw); QSqlQuery &query = *th.qsqQuery; @@ -1070,13 +1168,74 @@ void ServerDB::setSUPW(int srvnum, const QString &pw) { SQLEXEC(); } - SQLPREP("UPDATE `%1users` SET `pw`=? WHERE `server_id` = ? AND `user_id`=?"); - query.addBindValue(QString::fromLatin1(hash.result().toHex())); + SQLPREP("UPDATE `%1users` SET `pw`=?, `salt`=?, `kdfmeter`=? WHERE `server_id` = ? AND `user_id`=?"); + query.addBindValue(pwHash); + query.addBindValue(saltHash); + query.addBindValue(static_cast(Meta::mp.kdfIterations)); query.addBindValue(srvnum); query.addBindValue(0); SQLEXEC(); } +size_t ServerDB::measurePbkdf2() { + QElapsedTimer timer; + qint64 nanoSec; + int stillOverAveragePwLen = 9; + std::vector saltArray(saltLen); + std::vector pwArray(stillOverAveragePwLen); + std::vector out(derivedKeyLen); + int measuredIterations = 10000; + + if (! RAND_bytes(&saltArray[0], saltLen) || ! RAND_bytes(reinterpret_cast(&pwArray[0]), stillOverAveragePwLen)) + qWarning("ServerDB: RAND_bytes for kdf cost measure failed: %s", ERR_error_string(ERR_get_error(), NULL)); + + timer.start(); + if( PKCS5_PBKDF2_HMAC(&pwArray[0], stillOverAveragePwLen, &saltArray[0], saltLen, measuredIterations, EVP_sha384(), derivedKeyLen, &out[0]) == 0 ) + qFatal("ServerDB: kdf iteration cost measurement failed: %s", ERR_error_string(ERR_get_error(), NULL)); + + nanoSec = timer.nsecsElapsed(); + size_t returnCount = (iterationTime * 1000000) / (nanoSec / measuredIterations); + if (returnCount < 1) + qFatal("ServerDB: wrong kdf iteration cost measurement"); + + return returnCount; +} + +QString ServerDB::getHashPbkdf2(const QString saltHex, const QString pw, size_t iterationCount) { + QString pwHex; + + std::vector pwArray(derivedKeyLen); + QByteArray pwBuff = pw.toUtf8(); + std::vector pwInput(pwBuff.data(), pwBuff.data() + pwBuff.size()); + QByteArray saltBuff = QByteArray::fromHex(saltHex.toAscii().constData()); + std::vector saltArray(saltBuff.data(), saltBuff.data() + saltBuff.size()); + + if( PKCS5_PBKDF2_HMAC(&pwInput[0], static_cast(pwInput.size()), &saltArray[0], static_cast(saltArray.size()), static_cast(iterationCount), EVP_sha384(), derivedKeyLen, &pwArray[0]) == 0 ) + qFatal("ServerDB: PKCS5_PBKDF2_HMAC failed: %s", ERR_error_string(ERR_get_error(), NULL)); + + for(int i=0;i(pwArray[i]),2,16,QLatin1Char('0')); + + return pwHex; +} + +QString ServerDB::getHashPlain(const QString pw) { + QByteArray hash = QCryptographicHash::hash(pw.toUtf8(), QCryptographicHash::Sha1); + return QString::fromLatin1(hash.toHex()); +} + +QString ServerDB::getHashSalt() { + std::vector saltArray(saltLen); + if (! RAND_bytes(&saltArray[0], saltLen)) + qWarning("ServerDB: RAND_bytes for salt failed: %s", ERR_error_string(ERR_get_error(), NULL)); + + QString saltHex; + for(int i=0;i(saltArray[i]),2,16,QLatin1Char('0')); + + return saltHex; +} + QString Server::getUserName(int id) { if (qhUserNameCache.contains(id)) return qhUserNameCache.value(id); diff --git a/src/murmur/ServerDB.h b/src/murmur/ServerDB.h index 77e80643196..ea7c5396d62 100644 --- a/src/murmur/ServerDB.h +++ b/src/murmur/ServerDB.h @@ -44,7 +44,7 @@ class QSqlQuery; class ServerDB { public: enum ChannelInfo { Channel_Description, Channel_Position }; - enum UserInfo { User_Name, User_Email, User_Comment, User_Hash, User_Password, User_LastActive }; + enum UserInfo { User_Name, User_Email, User_Comment, User_Hash, User_Password, User_KdfMeter, User_LastActive }; ServerDB(); ~ServerDB(); typedef QPair LogRecord; @@ -61,6 +61,10 @@ class ServerDB { static QVariant getConf(int server_id, const QString &key, QVariant def = QVariant()); static void setConf(int server_id, const QString &key, const QVariant &value = QVariant()); static QList getLog(int server_id, unsigned int offs_min, unsigned int offs_max); + static size_t measurePbkdf2(); + static QString getHashPbkdf2(const QString saltHex, const QString pwInput, size_t iterationCount); + static QString getHashPlain(const QString pw); + static QString getHashSalt(); static int getLogLen(int server_id); static void wipeLogs(); static bool prepare(QSqlQuery &, const QString &, bool fatal = true, bool warn = true); diff --git a/src/murmur/murmur_pch.h b/src/murmur/murmur_pch.h index 0eeaee20516..642a42df093 100644 --- a/src/murmur/murmur_pch.h +++ b/src/murmur/murmur_pch.h @@ -45,6 +45,7 @@ extern "C" { #include #include #include +#include /* OpenSSL defines set_key. This breaks our protobuf-generated setters. */ #undef set_key From 5131d9e3036bb7de94967dd026f2c7f4ec91645d Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Fri, 3 Oct 2014 01:19:29 +0200 Subject: [PATCH 2/6] Review and refactor of PBKDF2 support patch. * Adjusted to coding guidelines * Pulled out PBKDF2 functionality into own class * Make benchmark a best of N approach with guaranteed minimum * Fixed broken database migration code. Don't try to alter tables and instead rely on them being re-created with the new fields. * Fixed some typos in ini. Also move to the setting to the end so ppl. don't get the idea they have to change this. * Chose a scarier name for the plain hash function * Use int instead of size_t for iteration counts as it is the datatype used in the OpenSSL API. Otherwise we just have to much pain with constantly converting and might expose ourselves to size issues in the future. * Moved new UserInfo enum entry to the end as to preserve the order --- scripts/murmur.ini | 17 +- src/murmur/Meta.cpp | 6 +- src/murmur/Meta.h | 10 +- src/murmur/PBKDF2.cpp | 92 +++++ src/murmur/PBKDF2.h | 85 +++++ src/murmur/ServerDB.cpp | 801 +++++++++++++++++++--------------------- src/murmur/ServerDB.h | 10 +- src/murmur/murmur.pro | 4 +- 8 files changed, 581 insertions(+), 444 deletions(-) create mode 100644 src/murmur/PBKDF2.cpp create mode 100644 src/murmur/PBKDF2.h diff --git a/scripts/murmur.ini b/scripts/murmur.ini index 77cb13a6257..cef4f17d9a0 100644 --- a/scripts/murmur.ini +++ b/scripts/murmur.ini @@ -129,14 +129,6 @@ users=100 # Allow clients to use HTML in messages, user comments and channel descriptions? #allowhtml=true -# This sets password hash storage to legacy mode (1.2.4 and before) -# (Note that this is insecure and should not be changed this unless absolutely needed) -#legacyPasswordwHash=false - -# This overrides the global default for kdf iterations, if used for password authentications -# (Note that you should only change this value if know both what you want and what you are doing) -#kdfIterations=0 - # Murmur retains the per-server log entries in an internal database which # allows it to be accessed over D-Bus/ICE. # How many days should such entries be kept? @@ -180,6 +172,15 @@ users=100 # system. #sendversion=True +# This sets password hash storage to legacy mode (1.2.4 and before) +# (Note that setting this to true is insecure and should not be used unless absolutely necessary) +#legacyPasswordwHash=false + +# By default a strong amount of PBKDF2 iterations are chosen automatically. If >0 this setting +# overrides the automatic benchmark and forces a specific number of iterations. +# (Note that you should only change this value if you know what you are doing) +#kdfIterations=-1 + # You can configure any of the configuration options for Ice here. We recommend # leave the defaults as they are. # Please note that this section has to be last in the configuration file. diff --git a/src/murmur/Meta.cpp b/src/murmur/Meta.cpp index 3035115f1ce..06e20c541ae 100644 --- a/src/murmur/Meta.cpp +++ b/src/murmur/Meta.cpp @@ -55,7 +55,7 @@ MetaParams::MetaParams() { iMaxTextMessageLength = 5000; iMaxImageMessageLength = 131072; bPlainPasswordHash = false; - iKdfIterations = 0; + kdfIterations = -1; bAllowHTML = true; iDefaultChan = 0; bRememberChan = true; @@ -265,7 +265,7 @@ void MetaParams::read(QString fname) { iMaxTextMessageLength = typeCheckedFromSettings("textmessagelength", iMaxTextMessageLength); iMaxImageMessageLength = typeCheckedFromSettings("imagemessagelength", iMaxImageMessageLength); bPlainPasswordHash = typeCheckedFromSettings("legacypasswordwhash", bPlainPasswordHash); - iKdfIterations = typeCheckedFromSettings("kdfiterations", iKdfIterations); + kdfIterations = typeCheckedFromSettings("kdfiterations", -1); bAllowHTML = typeCheckedFromSettings("allowhtml", bAllowHTML); iMaxBandwidth = typeCheckedFromSettings("bandwidth", iMaxBandwidth); iDefaultChan = typeCheckedFromSettings("defaultchannel", iDefaultChan); @@ -463,7 +463,7 @@ void MetaParams::read(QString fname) { qmConfig.insert(QLatin1String("timeout"),QString::number(iTimeout)); qmConfig.insert(QLatin1String("textmessagelength"), QString::number(iMaxTextMessageLength)); qmConfig.insert(QLatin1String("legacypasswordwhash"), bPlainPasswordHash ? QLatin1String("true") : QLatin1String("false")); - qmConfig.insert(QLatin1String("kdfiterations"), QString::number(iKdfIterations)); + qmConfig.insert(QLatin1String("kdfiterations"), QString::number(kdfIterations)); qmConfig.insert(QLatin1String("allowhtml"), bAllowHTML ? QLatin1String("true") : QLatin1String("false")); qmConfig.insert(QLatin1String("bandwidth"),QString::number(iMaxBandwidth)); qmConfig.insert(QLatin1String("users"),QString::number(iMaxUsers)); diff --git a/src/murmur/Meta.h b/src/murmur/Meta.h index df226ca655e..c00d7e73f69 100644 --- a/src/murmur/Meta.h +++ b/src/murmur/Meta.h @@ -63,8 +63,13 @@ class MetaParams { int iMaxImageMessageLength; int iOpusThreshold; int iChannelNestingLimit; + /// If true the old SHA1 password hashing is used instead of PBKDF2 bool bPlainPasswordHash; - int iKdfIterations; + /// Contains the default number of PBKDF2 iterations to use + /// when hashing passwords. If the value loaded from config + /// is <= 0 the value is loaded from the database and if not + /// available there yet found by a benchmark. + int kdfIterations; bool bAllowHTML; QString qsPassword; QString qsWelcomeText; @@ -125,9 +130,6 @@ class MetaParams { QSettings *qsSettings; - // db meta - size_t kdfIterations; - MetaParams(); ~MetaParams(); void read(QString fname = QString("murmur.ini")); diff --git a/src/murmur/PBKDF2.cpp b/src/murmur/PBKDF2.cpp new file mode 100644 index 00000000000..0afe4546a50 --- /dev/null +++ b/src/murmur/PBKDF2.cpp @@ -0,0 +1,92 @@ +/* Copyright (C) 2013, tkmorris + Copyright (C) 2014, Stefan Hacker + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + - Neither the name of the Mumble Developers nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include "murmur_pch.h" + +#include "PBKDF2.h" + +int PBKDF2::benchmark() { + const QString pseudopass(QLatin1String("aboutAvg")); + const QString hexSalt = getSalt(); // Could tolerate not getting a salt here, will likely only make it harder. + + int maxIterations = -1; + + QElapsedTimer timer; + timer.start(); + + for (size_t i = 0; i < BENCHMARK_N; ++i) { + int iterations = BENCHMARK_MINIMUM_ITERATION_COUNT / 2; + + timer.restart(); + do { + iterations *= 2; + + if(getHash(hexSalt, pseudopass, iterations).isNull()) { + // Something went wrong here, don't get us stuck in a loop. + break; + } + } while (timer.restart() < BENCHMARK_DURATION_TARGET_IN_MS && (iterations / 2) < std::numeric_limits::max()); + + if (iterations > maxIterations) { + maxIterations = iterations; + } + } + return maxIterations; +} + +QString PBKDF2::getHash(const QString& hexSalt, const QString& password, int iterationCount) { + QByteArray hash(DERIVED_KEY_LENGTH, 0); + + const QByteArray utf8Password = password.toUtf8(); + const QByteArray salt = QByteArray::fromHex(hexSalt.toLatin1()); + + if( PKCS5_PBKDF2_HMAC(utf8Password.constData(), utf8Password.size(), + reinterpret_cast(salt.constData()), salt.size(), + iterationCount, + EVP_sha384(), + DERIVED_KEY_LENGTH, reinterpret_cast(hash.data())) == 0 ) { + qFatal("PBKDF2: PKCS5_PBKDF2_HMAC failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return QString(); + } + + return hash.toHex(); +} + + +QString PBKDF2::getSalt() { + QByteArray salt(SALT_LENGTH, 0); + + if (! RAND_bytes(reinterpret_cast(salt.data()), salt.size())) { + qFatal("PBKDF2: RAND_bytes for salt failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return QString(); + } + + return salt.toHex(); +} diff --git a/src/murmur/PBKDF2.h b/src/murmur/PBKDF2.h new file mode 100644 index 00000000000..068779ceb40 --- /dev/null +++ b/src/murmur/PBKDF2.h @@ -0,0 +1,85 @@ +/* Copyright (C) 2013, tkmorris + Copyright (C) 2014, Stefan Hacker + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + - Neither the name of the Mumble Developers nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#ifndef MUMBLE_MURMUR_PBKDF2_H_ +#define MUMBLE_MURMUR_PBKDF2_H_ + +#include + +class QString; + +/// +/// Fully static wrapper class for PBKF2 password hashing functionality used in Murmur. +/// +/// @note Using int all over the place because OpenSSL uses them in its C interface +/// and we want to make sure not to parameterize it in unexpected ways. +/// +class PBKDF2 { + public: + /// + /// @return Upper bound on iterations possible in + /// BENCHMARK_DURATION_TARGET_IN_MS on this machine. + /// + static int benchmark(); + + /// Performs a PBKDF2 hash operation using EVP_sha384. + /// + /// @param hexSalt Hex encoded salt to use in operation. + /// @param password Password to hash. + /// @param iterationCount Number of PBKDF2 iterations to apply. + /// @return Hex encoded password hash of DERIVED_KEY_LENGTH octets. + /// Null string if hashing failed. + /// + static QString getHash(const QString& hexSalt, + const QString& password, + int iterationCount); + + /// + /// @return SALT_LENGTH octets of hex encoded random salt. + /// Null string if not enough entropy was available. + /// + static QString getSalt(); + + /// Length of hash in octets + static const int DERIVED_KEY_LENGTH = 48; + /// Length salt in octests + static const int SALT_LENGTH = 8; + + /// Duration for hash operation the benchmark function should target + static const int BENCHMARK_DURATION_TARGET_IN_MS = 10; + /// Benchmark returns highest iteration number of N benchmark attemps + static const size_t BENCHMARK_N = 40; + /// Lower bound of iteration count returned by benchmark + /// regardless of duration (should be divisible by 2) + static const int BENCHMARK_MINIMUM_ITERATION_COUNT = 1000; + +}; + +#endif // MUMBLE_MURMUR_PBKDF2_H_ diff --git a/src/murmur/ServerDB.cpp b/src/murmur/ServerDB.cpp index fcf9d64d5c0..5619fcdb7b2 100644 --- a/src/murmur/ServerDB.cpp +++ b/src/murmur/ServerDB.cpp @@ -41,6 +41,7 @@ #include "Server.h" #include "ServerUser.h" #include "User.h" +#include "PBKDF2.h" #define SQLDO(x) ServerDB::exec(query, QLatin1String(x), true) #define SQLMAY(x) ServerDB::exec(query, QLatin1String(x), false, false) @@ -49,10 +50,6 @@ #define SQLEXECBATCH() ServerDB::execBatch(query) #define SOFTEXEC() ServerDB::exec(query, QString(), false) -// kdf (pbkdf2) constants -static const int derivedKeyLen = 48; // octets -static const int saltLen = 8; // octets -static const int iterationTime = 10; // milliseconds class TransactionHolder { public: @@ -77,6 +74,34 @@ QSqlDatabase *ServerDB::db = NULL; Timer ServerDB::tLogClean; QString ServerDB::qsUpgradeSuffix; +void ServerDB::loadOrSetupMetaPKBDF2IterationsCount(QSqlQuery &query) { + if (!Meta::mp.bPlainPasswordHash) { + if (Meta::mp.kdfIterations <= 0) { + // Configuration doesn't specify an override, load from db + + SQLDO("SELECT `value` FROM `%1meta` WHERE `keystring` = 'pbkdf2_iterations'"); + if (query.next()) { + Meta::mp.kdfIterations = query.value(0).toInt(); + } + + if (Meta::mp.kdfIterations <= 0) { + // Didn't get a valid iteration count from DB, overwrite + Meta::mp.kdfIterations = PBKDF2::benchmark(); + + qWarning() << "Performed initial PBKDF2 benchmark. Will use " << Meta::mp.kdfIterations << " iterations as default"; + + SQLPREP("INSERT INTO `%1meta` (`keystring`, `value`) VALUES('pbkdf2_iterations',?)"); + query.addBindValue(Meta::mp.kdfIterations); + SQLEXEC(); + } + } + + if (Meta::mp.kdfIterations < PBKDF2::BENCHMARK_MINIMUM_ITERATION_COUNT) { + qWarning() << "Configured default PBKDF2 iteration count of " << Meta::mp.kdfIterations << " is below minimum recommended value of " << PBKDF2::BENCHMARK_MINIMUM_ITERATION_COUNT << " and could be insecure."; + } + } +} + ServerDB::ServerDB() { if (! QSqlDatabase::isDriverAvailable(Meta::mp.qsDBDriver)) { qFatal("ServerDB: Database driver %s not available", qPrintable(Meta::mp.qsDBDriver)); @@ -160,347 +185,316 @@ ServerDB::ServerDB() { if (query.next()) version = query.value(0).toInt(); - if (! Meta::mp.bPlainPasswordHash) { - if (Meta::mp.iKdfIterations > 0) - Meta::mp.kdfIterations = static_cast(Meta::mp.iKdfIterations); - else { - SQLDO("SELECT `value` FROM `%1meta` WHERE `keystring` = 'pbkdf2_iterations'"); - if (query.next()) { - Meta::mp.kdfIterations = static_cast(query.value(0).toInt()); - } else { - size_t iterations = ServerDB::measurePbkdf2(); - SQLPREP("INSERT INTO `%1meta` (`keystring`, `value`) VALUES('pbkdf2_iterations',?)"); - query.addBindValue(static_cast(iterations)); - SQLEXEC(); - if (iterations < 1000) - qWarning("ServerDB: kdf iteration count may be insecure."); - Meta::mp.kdfIterations = iterations; + + loadOrSetupMetaPKBDF2IterationsCount(query); + + if (version < 6) { + if (version > 0) { + qWarning("Renaming old tables..."); + SQLDO("ALTER TABLE `%1servers` RENAME TO `%1servers%2`"); + if (version < 2) + SQLMAY("ALTER TABLE `%1log` RENAME TO `%1slog`"); + SQLDO("ALTER TABLE `%1slog` RENAME TO `%1slog%2`"); + SQLDO("ALTER TABLE `%1config` RENAME TO `%1config%2`"); + SQLDO("ALTER TABLE `%1channels` RENAME TO `%1channels%2`"); + if (version < 4) + SQLDO("ALTER TABLE `%1players` RENAME TO `%1players%2`"); + else + SQLDO("ALTER TABLE `%1users` RENAME TO `%1users%2`"); + SQLDO("ALTER TABLE `%1groups` RENAME TO `%1groups%2`"); + SQLDO("ALTER TABLE `%1group_members` RENAME TO `%1group_members%2`"); + SQLDO("ALTER TABLE `%1acl` RENAME TO `%1acl%2`"); + SQLDO("ALTER TABLE `%1channel_links` RENAME TO `%1channel_links%2`"); + SQLDO("ALTER TABLE `%1bans` RENAME TO `%1bans%2`"); + + if (version >= 4) { + SQLDO("ALTER TABLE `%1user_info` RENAME TO `%1user_info%2`"); + SQLDO("ALTER TABLE `%1channel_info` RENAME TO `%1channel_info%2`"); } } - } - if (version < 6) { - if (version < 5) { + qWarning("Generating new tables..."); + if (Meta::mp.qsDBDriver == "QSQLITE") { if (version > 0) { - qWarning("Renaming old tables..."); - SQLDO("ALTER TABLE `%1servers` RENAME TO `%1servers%2`"); - if (version < 2) - SQLMAY("ALTER TABLE `%1log` RENAME TO `%1slog`"); - SQLDO("ALTER TABLE `%1slog` RENAME TO `%1slog%2`"); - SQLDO("ALTER TABLE `%1config` RENAME TO `%1config%2`"); - SQLDO("ALTER TABLE `%1channels` RENAME TO `%1channels%2`"); - if (version < 4) - SQLDO("ALTER TABLE `%1players` RENAME TO `%1players%2`"); - else - SQLDO("ALTER TABLE `%1users` RENAME TO `%1users%2`"); - SQLDO("ALTER TABLE `%1groups` RENAME TO `%1groups%2`"); - SQLDO("ALTER TABLE `%1group_members` RENAME TO `%1group_members%2`"); - SQLDO("ALTER TABLE `%1acl` RENAME TO `%1acl%2`"); - SQLDO("ALTER TABLE `%1channel_links` RENAME TO `%1channel_links%2`"); - SQLDO("ALTER TABLE `%1bans` RENAME TO `%1bans%2`"); - - if (version >= 4) { - SQLDO("ALTER TABLE `%1user_info` RENAME TO `%1user_info%2`"); - SQLDO("ALTER TABLE `%1channel_info` RENAME TO `%1channel_info%2`"); - } + SQLDO("DROP TRIGGER IF EXISTS `%1log_timestamp`"); + SQLDO("DROP TRIGGER IF EXISTS `%1log_server_del`"); + SQLDO("DROP TRIGGER IF EXISTS `%1slog_timestamp`"); + SQLDO("DROP TRIGGER IF EXISTS `%1slog_server_del`"); + SQLDO("DROP TRIGGER IF EXISTS `%1config_server_del`"); + SQLDO("DROP TRIGGER IF EXISTS `%1channels_parent_del`"); + SQLDO("DROP TRIGGER IF EXISTS `%1channels_server_del`"); + SQLDO("DROP TRIGGER IF EXISTS `%1channel_info_del_channel`"); + SQLDO("DROP TRIGGER IF EXISTS `%1players_server_del`"); + SQLDO("DROP TRIGGER IF EXISTS `%1players_update_timestamp`"); + SQLDO("DROP TRIGGER IF EXISTS `%1users_server_del`"); + SQLDO("DROP TRIGGER IF EXISTS `%1users_update_timestamp`"); + SQLDO("DROP TRIGGER IF EXISTS `%1user_info_del_user`"); + SQLDO("DROP TRIGGER IF EXISTS `%1groups_del_channel`"); + SQLDO("DROP TRIGGER IF EXISTS `%1groups_members_del_group`"); + SQLDO("DROP TRIGGER IF EXISTS `%1groups_members_del_player`"); + SQLDO("DROP TRIGGER IF EXISTS `%1groups_members_del_user`"); + SQLDO("DROP TRIGGER IF EXISTS `%1acl_del_channel`"); + SQLDO("DROP TRIGGER IF EXISTS `%1acl_del_player`"); + SQLDO("DROP TRIGGER IF EXISTS `%1acl_del_user`"); + SQLDO("DROP TRIGGER IF EXISTS `%1channel_links_del_channel`"); + SQLDO("DROP TRIGGER IF EXISTS `%1bans_del_server`"); + + SQLDO("DROP INDEX IF EXISTS `%1log_time`"); + SQLDO("DROP INDEX IF EXISTS `%1slog_time`"); + SQLDO("DROP INDEX IF EXISTS `%1config_key`"); + SQLDO("DROP INDEX IF EXISTS `%1channel_id`"); + SQLDO("DROP INDEX IF EXISTS `%1channel_info_id`"); + SQLDO("DROP INDEX IF EXISTS `%1players_name`"); + SQLDO("DROP INDEX IF EXISTS `%1players_id`"); + SQLDO("DROP INDEX IF EXISTS `%1users_name`"); + SQLDO("DROP INDEX IF EXISTS `%1users_id`"); + SQLDO("DROP INDEX IF EXISTS `%1user_info_id`"); + SQLDO("DROP INDEX IF EXISTS `%1groups_name_channels`"); + SQLDO("DROP INDEX IF EXISTS `%1acl_channel_pri`"); } - qWarning("Generating new tables..."); - if (Meta::mp.qsDBDriver == "QSQLITE") { - if (version > 0) { - SQLDO("DROP TRIGGER IF EXISTS `%1log_timestamp`"); - SQLDO("DROP TRIGGER IF EXISTS `%1log_server_del`"); - SQLDO("DROP TRIGGER IF EXISTS `%1slog_timestamp`"); - SQLDO("DROP TRIGGER IF EXISTS `%1slog_server_del`"); - SQLDO("DROP TRIGGER IF EXISTS `%1config_server_del`"); - SQLDO("DROP TRIGGER IF EXISTS `%1channels_parent_del`"); - SQLDO("DROP TRIGGER IF EXISTS `%1channels_server_del`"); - SQLDO("DROP TRIGGER IF EXISTS `%1channel_info_del_channel`"); - SQLDO("DROP TRIGGER IF EXISTS `%1players_server_del`"); - SQLDO("DROP TRIGGER IF EXISTS `%1players_update_timestamp`"); - SQLDO("DROP TRIGGER IF EXISTS `%1users_server_del`"); - SQLDO("DROP TRIGGER IF EXISTS `%1users_update_timestamp`"); - SQLDO("DROP TRIGGER IF EXISTS `%1user_info_del_user`"); - SQLDO("DROP TRIGGER IF EXISTS `%1groups_del_channel`"); - SQLDO("DROP TRIGGER IF EXISTS `%1groups_members_del_group`"); - SQLDO("DROP TRIGGER IF EXISTS `%1groups_members_del_player`"); - SQLDO("DROP TRIGGER IF EXISTS `%1groups_members_del_user`"); - SQLDO("DROP TRIGGER IF EXISTS `%1acl_del_channel`"); - SQLDO("DROP TRIGGER IF EXISTS `%1acl_del_player`"); - SQLDO("DROP TRIGGER IF EXISTS `%1acl_del_user`"); - SQLDO("DROP TRIGGER IF EXISTS `%1channel_links_del_channel`"); - SQLDO("DROP TRIGGER IF EXISTS `%1bans_del_server`"); - - SQLDO("DROP INDEX IF EXISTS `%1log_time`"); - SQLDO("DROP INDEX IF EXISTS `%1slog_time`"); - SQLDO("DROP INDEX IF EXISTS `%1config_key`"); - SQLDO("DROP INDEX IF EXISTS `%1channel_id`"); - SQLDO("DROP INDEX IF EXISTS `%1channel_info_id`"); - SQLDO("DROP INDEX IF EXISTS `%1players_name`"); - SQLDO("DROP INDEX IF EXISTS `%1players_id`"); - SQLDO("DROP INDEX IF EXISTS `%1users_name`"); - SQLDO("DROP INDEX IF EXISTS `%1users_id`"); - SQLDO("DROP INDEX IF EXISTS `%1user_info_id`"); - SQLDO("DROP INDEX IF EXISTS `%1groups_name_channels`"); - SQLDO("DROP INDEX IF EXISTS `%1acl_channel_pri`"); - } - - SQLDO("CREATE TABLE `%1servers` (`server_id` INTEGER PRIMARY KEY AUTOINCREMENT)"); + SQLDO("CREATE TABLE `%1servers` (`server_id` INTEGER PRIMARY KEY AUTOINCREMENT)"); - SQLDO("CREATE TABLE `%1slog`(`server_id` INTEGER NOT NULL, `msg` TEXT, `msgtime` DATE)"); - SQLDO("CREATE INDEX `%1slog_time` ON `%1slog`(`msgtime`)"); - SQLDO("CREATE TRIGGER `%1slog_timestamp` AFTER INSERT ON `%1slog` FOR EACH ROW BEGIN UPDATE `%1slog` SET `msgtime` = datetime('now') WHERE rowid = new.rowid; END;"); - SQLDO("CREATE TRIGGER `%1slog_server_del` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1slog` WHERE `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1slog`(`server_id` INTEGER NOT NULL, `msg` TEXT, `msgtime` DATE)"); + SQLDO("CREATE INDEX `%1slog_time` ON `%1slog`(`msgtime`)"); + SQLDO("CREATE TRIGGER `%1slog_timestamp` AFTER INSERT ON `%1slog` FOR EACH ROW BEGIN UPDATE `%1slog` SET `msgtime` = datetime('now') WHERE rowid = new.rowid; END;"); + SQLDO("CREATE TRIGGER `%1slog_server_del` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1slog` WHERE `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1config` (`server_id` INTEGER NOT NULL, `key` TEXT, `value` TEXT)"); - SQLDO("CREATE UNIQUE INDEX `%1config_key` ON `%1config`(`server_id`, `key`)"); - SQLDO("CREATE TRIGGER `%1config_server_del` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1config` WHERE `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1config` (`server_id` INTEGER NOT NULL, `key` TEXT, `value` TEXT)"); + SQLDO("CREATE UNIQUE INDEX `%1config_key` ON `%1config`(`server_id`, `key`)"); + SQLDO("CREATE TRIGGER `%1config_server_del` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1config` WHERE `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1channels` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `parent_id` INTEGER, `name` TEXT, `inheritacl` INTEGER)"); - SQLDO("CREATE UNIQUE INDEX `%1channel_id` ON `%1channels`(`server_id`, `channel_id`)"); - SQLDO("CREATE TRIGGER `%1channels_parent_del` AFTER DELETE ON `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1channels` WHERE `parent_id` = old.`channel_id` AND `server_id` = old.`server_id`; UPDATE `%1users` SET `lastchannel`=0 WHERE `lastchannel` = old.`channel_id` AND `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TRIGGER `%1channels_server_del` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1channels` WHERE `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1channels` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `parent_id` INTEGER, `name` TEXT, `inheritacl` INTEGER)"); + SQLDO("CREATE UNIQUE INDEX `%1channel_id` ON `%1channels`(`server_id`, `channel_id`)"); + SQLDO("CREATE TRIGGER `%1channels_parent_del` AFTER DELETE ON `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1channels` WHERE `parent_id` = old.`channel_id` AND `server_id` = old.`server_id`; UPDATE `%1users` SET `lastchannel`=0 WHERE `lastchannel` = old.`channel_id` AND `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TRIGGER `%1channels_server_del` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1channels` WHERE `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1channel_info` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `key` INTEGER, `value` TEXT)"); - SQLDO("CREATE UNIQUE INDEX `%1channel_info_id` ON `%1channel_info`(`server_id`, `channel_id`, `key`)"); - SQLDO("CREATE TRIGGER `%1channel_info_del_channel` AFTER DELETE on `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1channel_info` WHERE `channel_id` = old.`channel_id` AND `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1channel_info` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `key` INTEGER, `value` TEXT)"); + SQLDO("CREATE UNIQUE INDEX `%1channel_info_id` ON `%1channel_info`(`server_id`, `channel_id`, `key`)"); + SQLDO("CREATE TRIGGER `%1channel_info_del_channel` AFTER DELETE on `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1channel_info` WHERE `channel_id` = old.`channel_id` AND `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1users` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `pw` TEXT, `salt` TEXT, `kdfmeter` INTEGER, `lastchannel` INTEGER, `texture` BLOB, `last_active` DATE)"); - SQLDO("CREATE UNIQUE INDEX `%1users_name` ON `%1users` (`server_id`,`name`)"); - SQLDO("CREATE UNIQUE INDEX `%1users_id` ON `%1users` (`server_id`, `user_id`)"); - SQLDO("CREATE TRIGGER `%1users_server_del` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1users` WHERE `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TRIGGER `%1users_update_timestamp` AFTER UPDATE OF `lastchannel` ON `%1users` FOR EACH ROW BEGIN UPDATE `%1users` SET `last_active` = datetime('now') WHERE `user_id` = old.`user_id` AND `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1users` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `pw` TEXT, `salt` TEXT, `kdfmeter` INTEGER, `lastchannel` INTEGER, `texture` BLOB, `last_active` DATE)"); + SQLDO("CREATE UNIQUE INDEX `%1users_name` ON `%1users` (`server_id`,`name`)"); + SQLDO("CREATE UNIQUE INDEX `%1users_id` ON `%1users` (`server_id`, `user_id`)"); + SQLDO("CREATE TRIGGER `%1users_server_del` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1users` WHERE `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TRIGGER `%1users_update_timestamp` AFTER UPDATE OF `lastchannel` ON `%1users` FOR EACH ROW BEGIN UPDATE `%1users` SET `last_active` = datetime('now') WHERE `user_id` = old.`user_id` AND `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1user_info` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `key` INTEGER, `value` TEXT)"); - SQLDO("CREATE UNIQUE INDEX `%1user_info_id` ON `%1user_info`(`server_id`, `user_id`, `key`)"); - SQLDO("CREATE TRIGGER `%1user_info_del_user` AFTER DELETE on `%1users` FOR EACH ROW BEGIN DELETE FROM `%1user_info` WHERE `user_id` = old.`user_id` AND `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1user_info` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `key` INTEGER, `value` TEXT)"); + SQLDO("CREATE UNIQUE INDEX `%1user_info_id` ON `%1user_info`(`server_id`, `user_id`, `key`)"); + SQLDO("CREATE TRIGGER `%1user_info_del_user` AFTER DELETE on `%1users` FOR EACH ROW BEGIN DELETE FROM `%1user_info` WHERE `user_id` = old.`user_id` AND `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1groups` (`group_id` INTEGER PRIMARY KEY AUTOINCREMENT, `server_id` INTEGER NOT NULL, `name` TEXT, `channel_id` INTEGER NOT NULL, `inherit` INTEGER, `inheritable` INTEGER)"); - SQLDO("CREATE UNIQUE INDEX `%1groups_name_channels` ON `%1groups`(`server_id`, `channel_id`, `name`)"); - SQLDO("CREATE TRIGGER `%1groups_del_channel` AFTER DELETE ON `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1groups` WHERE `channel_id` = old.`channel_id` AND `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1groups` (`group_id` INTEGER PRIMARY KEY AUTOINCREMENT, `server_id` INTEGER NOT NULL, `name` TEXT, `channel_id` INTEGER NOT NULL, `inherit` INTEGER, `inheritable` INTEGER)"); + SQLDO("CREATE UNIQUE INDEX `%1groups_name_channels` ON `%1groups`(`server_id`, `channel_id`, `name`)"); + SQLDO("CREATE TRIGGER `%1groups_del_channel` AFTER DELETE ON `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1groups` WHERE `channel_id` = old.`channel_id` AND `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1group_members` (`group_id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `addit` INTEGER)"); - SQLDO("CREATE TRIGGER `%1groups_members_del_group` AFTER DELETE ON `%1groups` FOR EACH ROW BEGIN DELETE FROM `%1group_members` WHERE `group_id` = old.`group_id`; END;"); - SQLDO("CREATE TRIGGER `%1groups_members_del_user` AFTER DELETE on `%1users` FOR EACH ROW BEGIN DELETE FROM `%1group_members` WHERE `user_id` = old.`user_id` AND `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1group_members` (`group_id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `addit` INTEGER)"); + SQLDO("CREATE TRIGGER `%1groups_members_del_group` AFTER DELETE ON `%1groups` FOR EACH ROW BEGIN DELETE FROM `%1group_members` WHERE `group_id` = old.`group_id`; END;"); + SQLDO("CREATE TRIGGER `%1groups_members_del_user` AFTER DELETE on `%1users` FOR EACH ROW BEGIN DELETE FROM `%1group_members` WHERE `user_id` = old.`user_id` AND `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1acl` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `priority` INTEGER, `user_id` INTEGER, `group_name` TEXT, `apply_here` INTEGER, `apply_sub` INTEGER, `grantpriv` INTEGER, `revokepriv` INTEGER)"); - SQLDO("CREATE UNIQUE INDEX `%1acl_channel_pri` ON `%1acl`(`server_id`, `channel_id`, `priority`)"); - SQLDO("CREATE TRIGGER `%1acl_del_channel` AFTER DELETE ON `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1acl` WHERE `channel_id` = old.`channel_id` AND `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TRIGGER `%1acl_del_user` AFTER DELETE ON `%1users` FOR EACH ROW BEGIN DELETE FROM `%1acl` WHERE `user_id` = old.`user_id` AND `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TABLE `%1acl` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `priority` INTEGER, `user_id` INTEGER, `group_name` TEXT, `apply_here` INTEGER, `apply_sub` INTEGER, `grantpriv` INTEGER, `revokepriv` INTEGER)"); + SQLDO("CREATE UNIQUE INDEX `%1acl_channel_pri` ON `%1acl`(`server_id`, `channel_id`, `priority`)"); + SQLDO("CREATE TRIGGER `%1acl_del_channel` AFTER DELETE ON `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1acl` WHERE `channel_id` = old.`channel_id` AND `server_id` = old.`server_id`; END;"); + SQLDO("CREATE TRIGGER `%1acl_del_user` AFTER DELETE ON `%1users` FOR EACH ROW BEGIN DELETE FROM `%1acl` WHERE `user_id` = old.`user_id` AND `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1channel_links` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `link_id` INTEGER NOT NULL)"); - SQLDO("CREATE TRIGGER `%1channel_links_del_channel` AFTER DELETE ON `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1channel_links` WHERE `server_id` = old.`server_id` AND (`channel_id` = old.`channel_id` OR `link_id` = old.`channel_id`); END;"); - SQLDO("DELETE FROM `%1channel_links`"); + SQLDO("CREATE TABLE `%1channel_links` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `link_id` INTEGER NOT NULL)"); + SQLDO("CREATE TRIGGER `%1channel_links_del_channel` AFTER DELETE ON `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1channel_links` WHERE `server_id` = old.`server_id` AND (`channel_id` = old.`channel_id` OR `link_id` = old.`channel_id`); END;"); + SQLDO("DELETE FROM `%1channel_links`"); - SQLDO("CREATE TABLE `%1bans` (`server_id` INTEGER NOT NULL, `base` BLOB, `mask` INTEGER, `name` TEXT, `hash` TEXT, `reason` TEXT, `start` DATE, `duration` INTEGER)"); - SQLDO("CREATE TRIGGER `%1bans_del_server` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1bans` WHERE `server_id` = old.`server_id`; END;"); - } else { - if (version > 0) { - typedef QPair qsp; - QList qlForeignKeys; - QList qlIndexes; + SQLDO("CREATE TABLE `%1bans` (`server_id` INTEGER NOT NULL, `base` BLOB, `mask` INTEGER, `name` TEXT, `hash` TEXT, `reason` TEXT, `start` DATE, `duration` INTEGER)"); + SQLDO("CREATE TRIGGER `%1bans_del_server` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1bans` WHERE `server_id` = old.`server_id`; END;"); + } else { + if (version > 0) { + typedef QPair qsp; + QList qlForeignKeys; + QList qlIndexes; - SQLPREP("SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA=? AND CONSTRAINT_TYPE='FOREIGN KEY'"); - query.addBindValue(Meta::mp.qsDatabase); - SQLEXEC(); - while (query.next()) - qlForeignKeys << qsp(query.value(0).toString(), query.value(1).toString()); + SQLPREP("SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA=? AND CONSTRAINT_TYPE='FOREIGN KEY'"); + query.addBindValue(Meta::mp.qsDatabase); + SQLEXEC(); + while (query.next()) + qlForeignKeys << qsp(query.value(0).toString(), query.value(1).toString()); - foreach(const qsp &key, qlForeignKeys) { - if (key.first.startsWith(Meta::mp.qsDBPrefix)) - ServerDB::exec(query, QString::fromLatin1("ALTER TABLE `%1` DROP FOREIGN KEY `%2`").arg(key.first).arg(key.second), true); - } + foreach(const qsp &key, qlForeignKeys) { + if (key.first.startsWith(Meta::mp.qsDBPrefix)) + ServerDB::exec(query, QString::fromLatin1("ALTER TABLE `%1` DROP FOREIGN KEY `%2`").arg(key.first).arg(key.second), true); + } - SQLPREP("SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA=? AND CONSTRAINT_TYPE='UNIQUE'"); - query.addBindValue(Meta::mp.qsDatabase); - SQLEXEC(); - while (query.next()) - qlIndexes << qsp(query.value(0).toString(), query.value(1).toString()); + SQLPREP("SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA=? AND CONSTRAINT_TYPE='UNIQUE'"); + query.addBindValue(Meta::mp.qsDatabase); + SQLEXEC(); + while (query.next()) + qlIndexes << qsp(query.value(0).toString(), query.value(1).toString()); - foreach(const qsp &key, qlIndexes) { - if (key.first.startsWith(Meta::mp.qsDBPrefix)) - ServerDB::exec(query, QString::fromLatin1("ALTER TABLE `%1` DROP INDEX `%2`").arg(key.first).arg(key.second), true); - } + foreach(const qsp &key, qlIndexes) { + if (key.first.startsWith(Meta::mp.qsDBPrefix)) + ServerDB::exec(query, QString::fromLatin1("ALTER TABLE `%1` DROP INDEX `%2`").arg(key.first).arg(key.second), true); + } - qlIndexes.clear(); + qlIndexes.clear(); - SQLPREP("SELECT DISTINCT TABLE_NAME, INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA=? AND INDEX_NAME != 'PRIMARY';"); - query.addBindValue(Meta::mp.qsDatabase); - SQLEXEC(); - while (query.next()) - qlIndexes << qsp(query.value(0).toString(), query.value(1).toString()); + SQLPREP("SELECT DISTINCT TABLE_NAME, INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA=? AND INDEX_NAME != 'PRIMARY';"); + query.addBindValue(Meta::mp.qsDatabase); + SQLEXEC(); + while (query.next()) + qlIndexes << qsp(query.value(0).toString(), query.value(1).toString()); - foreach(const qsp &key, qlIndexes) { - if (key.first.startsWith(Meta::mp.qsDBPrefix)) - ServerDB::exec(query, QString::fromLatin1("ALTER TABLE `%1` DROP INDEX `%2`").arg(key.first).arg(key.second), true); - } + foreach(const qsp &key, qlIndexes) { + if (key.first.startsWith(Meta::mp.qsDBPrefix)) + ServerDB::exec(query, QString::fromLatin1("ALTER TABLE `%1` DROP INDEX `%2`").arg(key.first).arg(key.second), true); } - SQLDO("CREATE TABLE `%1servers`(`server_id` INTEGER PRIMARY KEY AUTO_INCREMENT) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - - SQLDO("CREATE TABLE `%1slog`(`server_id` INTEGER NOT NULL, `msg` TEXT, `msgtime` TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE INDEX `%1slog_time` ON `%1slog`(`msgtime`)"); - SQLDO("ALTER TABLE `%1slog` ADD CONSTRAINT `%1slog_server_del` FOREIGN KEY (`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1config` (`server_id` INTEGER NOT NULL, `key` varchar(255), `value` TEXT) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE UNIQUE INDEX `%1config_key` ON `%1config`(`server_id`, `key`)"); - SQLDO("ALTER TABLE `%1config` ADD CONSTRAINT `%1config_server_del` FOREIGN KEY (`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1channels` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `parent_id` INTEGER, `name` varchar(255), `inheritacl` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE UNIQUE INDEX `%1channel_id` ON `%1channels`(`server_id`, `channel_id`)"); - SQLDO("ALTER TABLE `%1channels` ADD CONSTRAINT `%1channels_parent_del` FOREIGN KEY (`server_id`, `parent_id`) REFERENCES `%1channels`(`server_id`,`channel_id`) ON DELETE CASCADE"); - SQLDO("ALTER TABLE `%1channels` ADD CONSTRAINT `%1channels_server_del` FOREIGN KEY (`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1channel_info` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `key` INTEGER, `value` LONGTEXT) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE UNIQUE INDEX `%1channel_info_id` ON `%1channel_info`(`server_id`, `channel_id`, `key`)"); - SQLDO("ALTER TABLE `%1channel_info` ADD CONSTRAINT `%1channel_info_del_channel` FOREIGN KEY (`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`,`channel_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1users` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `name` varchar(255), `pw` varchar(128), `salt` varchar(128), `kdfmeter` INTEGER, `lastchannel` INTEGER, `texture` LONGBLOB, `last_active` TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE INDEX `%1users_channel` ON `%1users`(`server_id`, `lastchannel`)"); - SQLDO("CREATE UNIQUE INDEX `%1users_name` ON `%1users` (`server_id`,`name`)"); - SQLDO("CREATE UNIQUE INDEX `%1users_id` ON `%1users` (`server_id`, `user_id`)"); - SQLDO("ALTER TABLE `%1users` ADD CONSTRAINT `%1users_server_del` FOREIGN KEY (`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1user_info` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `key` INTEGER, `value` LONGTEXT) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE UNIQUE INDEX `%1user_info_id` ON `%1user_info`(`server_id`, `user_id`, `key`)"); - SQLDO("ALTER TABLE `%1user_info` ADD CONSTRAINT `%1user_info_del_user` FOREIGN KEY (`server_id`, `user_id`) REFERENCES `%1users`(`server_id`,`user_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1groups` (`group_id` INTEGER PRIMARY KEY AUTO_INCREMENT, `server_id` INTEGER NOT NULL, `name` varchar(255), `channel_id` INTEGER NOT NULL, `inherit` INTEGER, `inheritable` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE UNIQUE INDEX `%1groups_name_channels` ON `%1groups`(`server_id`, `channel_id`, `name`)"); - SQLDO("ALTER TABLE `%1groups` ADD CONSTRAINT `%1groups_del_channel` FOREIGN KEY (`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`, `channel_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1group_members` (`group_id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `addit` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE INDEX `%1group_members_users` ON `%1group_members`(`server_id`, `user_id`)"); - SQLDO("ALTER TABLE `%1group_members` ADD CONSTRAINT `%1group_members_del_group` FOREIGN KEY (`group_id`) REFERENCES `%1groups`(`group_id`) ON DELETE CASCADE"); - SQLDO("ALTER TABLE `%1group_members` ADD CONSTRAINT `%1group_members_del_user` FOREIGN KEY (`server_id`, `user_id`) REFERENCES `%1users`(`server_id`,`user_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1acl` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `priority` INTEGER, `user_id` INTEGER, `group_name` varchar(255), `apply_here` INTEGER, `apply_sub` INTEGER, `grantpriv` INTEGER, `revokepriv` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("CREATE UNIQUE INDEX `%1acl_channel_pri` ON `%1acl`(`server_id`, `channel_id`, `priority`)"); - SQLDO("CREATE INDEX `%1acl_user` ON `%1acl`(`server_id`, `user_id`)"); - SQLDO("ALTER TABLE `%1acl` ADD CONSTRAINT `%1acl_del_channel` FOREIGN KEY (`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`, `channel_id`) ON DELETE CASCADE"); - SQLDO("ALTER TABLE `%1acl` ADD CONSTRAINT `%1acl_del_user` FOREIGN KEY (`server_id`, `user_id`) REFERENCES `%1users`(`server_id`, `user_id`) ON DELETE CASCADE"); - - SQLDO("CREATE TABLE `%1channel_links` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `link_id` INTEGER NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("ALTER TABLE `%1channel_links` ADD CONSTRAINT `%1channel_links_del_channel` FOREIGN KEY(`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`, `channel_id`) ON DELETE CASCADE"); - SQLDO("DELETE FROM `%1channel_links`"); - - SQLDO("CREATE TABLE `%1bans` (`server_id` INTEGER NOT NULL, `base` BINARY(16), `mask` INTEGER, `name` varchar(255), `hash` CHAR(40), `reason` TEXT, `start` DATETIME, `duration` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); - SQLDO("ALTER TABLE `%1bans` ADD CONSTRAINT `%1bans_del_server` FOREIGN KEY(`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); } - if (version == 0) { - SQLDO("INSERT INTO `%1servers` (`server_id`) VALUES(1)"); - SQLDO("INSERT INTO `%1meta` (`keystring`, `value`) VALUES('version','6')"); - } else { - qWarning("Importing old data..."); + SQLDO("CREATE TABLE `%1servers`(`server_id` INTEGER PRIMARY KEY AUTO_INCREMENT) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + + SQLDO("CREATE TABLE `%1slog`(`server_id` INTEGER NOT NULL, `msg` TEXT, `msgtime` TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE INDEX `%1slog_time` ON `%1slog`(`msgtime`)"); + SQLDO("ALTER TABLE `%1slog` ADD CONSTRAINT `%1slog_server_del` FOREIGN KEY (`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1config` (`server_id` INTEGER NOT NULL, `key` varchar(255), `value` TEXT) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE UNIQUE INDEX `%1config_key` ON `%1config`(`server_id`, `key`)"); + SQLDO("ALTER TABLE `%1config` ADD CONSTRAINT `%1config_server_del` FOREIGN KEY (`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1channels` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `parent_id` INTEGER, `name` varchar(255), `inheritacl` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE UNIQUE INDEX `%1channel_id` ON `%1channels`(`server_id`, `channel_id`)"); + SQLDO("ALTER TABLE `%1channels` ADD CONSTRAINT `%1channels_parent_del` FOREIGN KEY (`server_id`, `parent_id`) REFERENCES `%1channels`(`server_id`,`channel_id`) ON DELETE CASCADE"); + SQLDO("ALTER TABLE `%1channels` ADD CONSTRAINT `%1channels_server_del` FOREIGN KEY (`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1channel_info` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `key` INTEGER, `value` LONGTEXT) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE UNIQUE INDEX `%1channel_info_id` ON `%1channel_info`(`server_id`, `channel_id`, `key`)"); + SQLDO("ALTER TABLE `%1channel_info` ADD CONSTRAINT `%1channel_info_del_channel` FOREIGN KEY (`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`,`channel_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1users` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `name` varchar(255), `pw` varchar(128), `salt` varchar(128), `kdfmeter` INTEGER, `lastchannel` INTEGER, `texture` LONGBLOB, `last_active` TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE INDEX `%1users_channel` ON `%1users`(`server_id`, `lastchannel`)"); + SQLDO("CREATE UNIQUE INDEX `%1users_name` ON `%1users` (`server_id`,`name`)"); + SQLDO("CREATE UNIQUE INDEX `%1users_id` ON `%1users` (`server_id`, `user_id`)"); + SQLDO("ALTER TABLE `%1users` ADD CONSTRAINT `%1users_server_del` FOREIGN KEY (`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1user_info` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `key` INTEGER, `value` LONGTEXT) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE UNIQUE INDEX `%1user_info_id` ON `%1user_info`(`server_id`, `user_id`, `key`)"); + SQLDO("ALTER TABLE `%1user_info` ADD CONSTRAINT `%1user_info_del_user` FOREIGN KEY (`server_id`, `user_id`) REFERENCES `%1users`(`server_id`,`user_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1groups` (`group_id` INTEGER PRIMARY KEY AUTO_INCREMENT, `server_id` INTEGER NOT NULL, `name` varchar(255), `channel_id` INTEGER NOT NULL, `inherit` INTEGER, `inheritable` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE UNIQUE INDEX `%1groups_name_channels` ON `%1groups`(`server_id`, `channel_id`, `name`)"); + SQLDO("ALTER TABLE `%1groups` ADD CONSTRAINT `%1groups_del_channel` FOREIGN KEY (`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`, `channel_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1group_members` (`group_id` INTEGER NOT NULL, `server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `addit` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE INDEX `%1group_members_users` ON `%1group_members`(`server_id`, `user_id`)"); + SQLDO("ALTER TABLE `%1group_members` ADD CONSTRAINT `%1group_members_del_group` FOREIGN KEY (`group_id`) REFERENCES `%1groups`(`group_id`) ON DELETE CASCADE"); + SQLDO("ALTER TABLE `%1group_members` ADD CONSTRAINT `%1group_members_del_user` FOREIGN KEY (`server_id`, `user_id`) REFERENCES `%1users`(`server_id`,`user_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1acl` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `priority` INTEGER, `user_id` INTEGER, `group_name` varchar(255), `apply_here` INTEGER, `apply_sub` INTEGER, `grantpriv` INTEGER, `revokepriv` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE UNIQUE INDEX `%1acl_channel_pri` ON `%1acl`(`server_id`, `channel_id`, `priority`)"); + SQLDO("CREATE INDEX `%1acl_user` ON `%1acl`(`server_id`, `user_id`)"); + SQLDO("ALTER TABLE `%1acl` ADD CONSTRAINT `%1acl_del_channel` FOREIGN KEY (`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`, `channel_id`) ON DELETE CASCADE"); + SQLDO("ALTER TABLE `%1acl` ADD CONSTRAINT `%1acl_del_user` FOREIGN KEY (`server_id`, `user_id`) REFERENCES `%1users`(`server_id`, `user_id`) ON DELETE CASCADE"); + + SQLDO("CREATE TABLE `%1channel_links` (`server_id` INTEGER NOT NULL, `channel_id` INTEGER NOT NULL, `link_id` INTEGER NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("ALTER TABLE `%1channel_links` ADD CONSTRAINT `%1channel_links_del_channel` FOREIGN KEY(`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`, `channel_id`) ON DELETE CASCADE"); + SQLDO("DELETE FROM `%1channel_links`"); + + SQLDO("CREATE TABLE `%1bans` (`server_id` INTEGER NOT NULL, `base` BINARY(16), `mask` INTEGER, `name` varchar(255), `hash` CHAR(40), `reason` TEXT, `start` DATETIME, `duration` INTEGER) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("ALTER TABLE `%1bans` ADD CONSTRAINT `%1bans_del_server` FOREIGN KEY(`server_id`) REFERENCES `%1servers`(`server_id`) ON DELETE CASCADE"); + } + if (version == 0) { + SQLDO("INSERT INTO `%1servers` (`server_id`) VALUES(1)"); + SQLDO("INSERT INTO `%1meta` (`keystring`, `value`) VALUES('version','6')"); + } else { + qWarning("Importing old data..."); - if (Meta::mp.qsDBDriver != "QSQLITE") - SQLDO("SET FOREIGN_KEY_CHECKS = 0;"); - SQLDO("INSERT INTO `%1servers` (`server_id`) SELECT `server_id` FROM `%1servers%2`"); - SQLDO("INSERT INTO `%1slog` (`server_id`, `msg`, `msgtime`) SELECT `server_id`, `msg`, `msgtime` FROM `%1slog%2`"); + if (Meta::mp.qsDBDriver != "QSQLITE") + SQLDO("SET FOREIGN_KEY_CHECKS = 0;"); + SQLDO("INSERT INTO `%1servers` (`server_id`) SELECT `server_id` FROM `%1servers%2`"); + SQLDO("INSERT INTO `%1slog` (`server_id`, `msg`, `msgtime`) SELECT `server_id`, `msg`, `msgtime` FROM `%1slog%2`"); - if (version < 4) - SQLDO("INSERT INTO `%1config` (`server_id`, `key`, `value`) SELECT `server_id`, `keystring`, `value` FROM `%1config%2`"); - else - SQLDO("INSERT INTO `%1config` (`server_id`, `key`, `value`) SELECT `server_id`, `key`, `value` FROM `%1config%2`"); + if (version < 4) + SQLDO("INSERT INTO `%1config` (`server_id`, `key`, `value`) SELECT `server_id`, `keystring`, `value` FROM `%1config%2`"); + else + SQLDO("INSERT INTO `%1config` (`server_id`, `key`, `value`) SELECT `server_id`, `key`, `value` FROM `%1config%2`"); - SQLDO("INSERT INTO `%1channels` (`server_id`, `channel_id`, `parent_id`, `name`, `inheritacl`) SELECT `server_id`, `channel_id`, `parent_id`, `name`, `inheritacl` FROM `%1channels%2` ORDER BY `parent_id`, `channel_id`"); + SQLDO("INSERT INTO `%1channels` (`server_id`, `channel_id`, `parent_id`, `name`, `inheritacl`) SELECT `server_id`, `channel_id`, `parent_id`, `name`, `inheritacl` FROM `%1channels%2` ORDER BY `parent_id`, `channel_id`"); - if (version < 4) - SQLDO("INSERT INTO `%1users` (`server_id`, `user_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active`) SELECT `server_id`, `player_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active` FROM `%1players%2`"); - else - SQLDO("INSERT INTO `%1users` (`server_id`, `user_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active`) SELECT `server_id`, `user_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active` FROM `%1users%2`"); + if (version < 4) + SQLDO("INSERT INTO `%1users` (`server_id`, `user_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active`) SELECT `server_id`, `player_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active` FROM `%1players%2`"); + else + SQLDO("INSERT INTO `%1users` (`server_id`, `user_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active`) SELECT `server_id`, `user_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active` FROM `%1users%2`"); - SQLDO("INSERT INTO `%1groups` (`group_id`, `server_id`, `name`, `channel_id`, `inherit`, `inheritable`) SELECT `group_id`, `server_id`, `name`, `channel_id`, `inherit`, `inheritable` FROM `%1groups%2`"); + SQLDO("INSERT INTO `%1groups` (`group_id`, `server_id`, `name`, `channel_id`, `inherit`, `inheritable`) SELECT `group_id`, `server_id`, `name`, `channel_id`, `inherit`, `inheritable` FROM `%1groups%2`"); - if (version < 4) - SQLDO("INSERT INTO `%1group_members` (`group_id`, `server_id`, `user_id`, `addit`) SELECT `group_id`, `server_id`, `player_id`, `addit` FROM `%1group_members%2`"); - else - SQLDO("INSERT INTO `%1group_members` (`group_id`, `server_id`, `user_id`, `addit`) SELECT `group_id`, `server_id`, `user_id`, `addit` FROM `%1group_members%2`"); + if (version < 4) + SQLDO("INSERT INTO `%1group_members` (`group_id`, `server_id`, `user_id`, `addit`) SELECT `group_id`, `server_id`, `player_id`, `addit` FROM `%1group_members%2`"); + else + SQLDO("INSERT INTO `%1group_members` (`group_id`, `server_id`, `user_id`, `addit`) SELECT `group_id`, `server_id`, `user_id`, `addit` FROM `%1group_members%2`"); - if (version < 4) - SQLDO("INSERT INTO `%1acl` (`server_id`, `channel_id`, `priority`, `user_id`, `group_name`, `apply_here`, `apply_sub`, `grantpriv`, `revokepriv`) SELECT `server_id`, `channel_id`, `priority`, `player_id`, `group_name`, `apply_here`, `apply_sub`, `grantpriv`, `revokepriv` FROM `%1acl%2`"); - else - SQLDO("INSERT INTO `%1acl` (`server_id`, `channel_id`, `priority`, `user_id`, `group_name`, `apply_here`, `apply_sub`, `grantpriv`, `revokepriv`) SELECT `server_id`, `channel_id`, `priority`, `user_id`, `group_name`, `apply_here`, `apply_sub`, `grantpriv`, `revokepriv` FROM `%1acl%2`"); + if (version < 4) + SQLDO("INSERT INTO `%1acl` (`server_id`, `channel_id`, `priority`, `user_id`, `group_name`, `apply_here`, `apply_sub`, `grantpriv`, `revokepriv`) SELECT `server_id`, `channel_id`, `priority`, `player_id`, `group_name`, `apply_here`, `apply_sub`, `grantpriv`, `revokepriv` FROM `%1acl%2`"); + else + SQLDO("INSERT INTO `%1acl` (`server_id`, `channel_id`, `priority`, `user_id`, `group_name`, `apply_here`, `apply_sub`, `grantpriv`, `revokepriv`) SELECT `server_id`, `channel_id`, `priority`, `user_id`, `group_name`, `apply_here`, `apply_sub`, `grantpriv`, `revokepriv` FROM `%1acl%2`"); - SQLDO("INSERT INTO `%1channel_links` (`server_id`, `channel_id`, `link_id`) SELECT `server_id`, `channel_id`, `link_id` FROM `%1channel_links%2`"); - if (version < 4) { - QList > ql; - SQLPREP("SELECT `server_id`, `base`, `mask` FROM `%1bans%2`"); - SQLEXEC(); - while (query.next()) { - QList l; - l << query.value(0); - l << query.value(1); - l << query.value(2); - ql << l; - } - SQLPREP("INSERT INTO `%1bans` (`server_id`, `base`, `mask`) VALUES (?, ?, ?)"); - foreach(const QList &l, ql) { - - quint32 addr = htonl(l.at(1).toUInt()); - const char *ptr = reinterpret_cast(&addr); - - QByteArray qba(16, 0); - qba[10] = static_cast(-1); - qba[11] = static_cast(-1); - qba[12] = ptr[0]; - qba[13] = ptr[1]; - qba[14] = ptr[2]; - qba[15] = ptr[3]; - - query.addBindValue(l.at(0)); - query.addBindValue(qba); - query.addBindValue(l.at(2).toInt() + 96); - SQLEXEC(); - } - } else { - SQLDO("INSERT INTO `%1bans` (`server_id`, `base`, `mask`) SELECT `server_id`, `base`, `mask` FROM `%1bans%2`"); + SQLDO("INSERT INTO `%1channel_links` (`server_id`, `channel_id`, `link_id`) SELECT `server_id`, `channel_id`, `link_id` FROM `%1channel_links%2`"); + if (version < 4) { + QList > ql; + SQLPREP("SELECT `server_id`, `base`, `mask` FROM `%1bans%2`"); + SQLEXEC(); + while (query.next()) { + QList l; + l << query.value(0); + l << query.value(1); + l << query.value(2); + ql << l; } - - if (version < 4) - SQLDO("INSERT INTO `%1user_info` SELECT `server_id`,`player_id`,1,`email` FROM `%1players%2` WHERE `email` IS NOT NULL"); - - if (version == 3) { - SQLDO("INSERT INTO `%1channel_info` SELECT `server_id`,`channel_id`,0,`description` FROM `%1channels%2` WHERE `description` IS NOT NULL"); + SQLPREP("INSERT INTO `%1bans` (`server_id`, `base`, `mask`) VALUES (?, ?, ?)"); + foreach(const QList &l, ql) { + + quint32 addr = htonl(l.at(1).toUInt()); + const char *ptr = reinterpret_cast(&addr); + + QByteArray qba(16, 0); + qba[10] = static_cast(-1); + qba[11] = static_cast(-1); + qba[12] = ptr[0]; + qba[13] = ptr[1]; + qba[14] = ptr[2]; + qba[15] = ptr[3]; + + query.addBindValue(l.at(0)); + query.addBindValue(qba); + query.addBindValue(l.at(2).toInt() + 96); + SQLEXEC(); } + } else { + SQLDO("INSERT INTO `%1bans` (`server_id`, `base`, `mask`) SELECT `server_id`, `base`, `mask` FROM `%1bans%2`"); + } - if (version >= 4) { - SQLDO("INSERT INTO `%1user_info` SELECT * FROM `%1user_info%2`"); - SQLDO("INSERT INTO `%1channel_info` SELECT * FROM `%1channel_info%2`"); - } + if (version < 4) + SQLDO("INSERT INTO `%1user_info` SELECT `server_id`,`player_id`,1,`email` FROM `%1players%2` WHERE `email` IS NOT NULL"); - if (Meta::mp.qsDBDriver != "QSQLITE") - SQLDO("SET FOREIGN_KEY_CHECKS = 1;"); - - qWarning("Removing old tables..."); - SQLDO("DROP TABLE IF EXISTS `%1slog%2`"); - SQLDO("DROP TABLE IF EXISTS `%1config%2`"); - SQLDO("DROP TABLE IF EXISTS `%1channel_info%2`"); - SQLDO("DROP TABLE IF EXISTS `%1channels%2`"); - SQLDO("DROP TABLE IF EXISTS `%1user_info%2`"); - SQLDO("DROP TABLE IF EXISTS `%1users%2`"); - SQLDO("DROP TABLE IF EXISTS `%1players%2`"); - SQLDO("DROP TABLE IF EXISTS `%1groups%2`"); - SQLDO("DROP TABLE IF EXISTS `%1group_members%2`"); - SQLDO("DROP TABLE IF EXISTS `%1acl%2`"); - SQLDO("DROP TABLE IF EXISTS `%1channel_links%2`"); - SQLDO("DROP TABLE IF EXISTS `%1bans%2`"); - SQLDO("DROP TABLE IF EXISTS `%1servers%2`"); - - SQLDO("UPDATE `%1meta` SET `value` = '6' WHERE `keystring` = 'version'"); + if (version == 3) { + SQLDO("INSERT INTO `%1channel_info` SELECT `server_id`,`channel_id`,0,`description` FROM `%1channels%2` WHERE `description` IS NOT NULL"); } - } - else { - if (Meta::mp.qsDBDriver == "QSQLITE") { - qWarning("Generating new tables..."); - SQLDO("ALTER TABLE `%1users%2` RENAME TO `%1__olduserstable%2`"); - SQLDO("CREATE TABLE `%1users` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `pw` TEXT, `salt` TEXT, `kdfmeter` INTEGER, `lastchannel` INTEGER, `texture` BLOB, `last_active` DATE)"); - SQLDO("INSERT INTO `%1users%s` (`server_id`, `user_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active`) SELECT `server_id`, `user_id`, `name`, `pw`, `lastchannel`, `texture`, `last_active` FROM `%1__olduserstable%2`"); - SQLDO("DROP TABLE `%1__olduserstable%2`"); - } else { - qWarning("Generating new tables..."); - SQLDO("ALTER TABLE `%1users%s` ADD salt VARCHAR(128), kdfmeter INTEGER AFTER pw"); + + if (version >= 4) { + SQLDO("INSERT INTO `%1user_info` SELECT * FROM `%1user_info%2`"); + SQLDO("INSERT INTO `%1channel_info` SELECT * FROM `%1channel_info%2`"); } + + if (Meta::mp.qsDBDriver != "QSQLITE") + SQLDO("SET FOREIGN_KEY_CHECKS = 1;"); + + qWarning("Removing old tables..."); + SQLDO("DROP TABLE IF EXISTS `%1slog%2`"); + SQLDO("DROP TABLE IF EXISTS `%1config%2`"); + SQLDO("DROP TABLE IF EXISTS `%1channel_info%2`"); + SQLDO("DROP TABLE IF EXISTS `%1channels%2`"); + SQLDO("DROP TABLE IF EXISTS `%1user_info%2`"); + SQLDO("DROP TABLE IF EXISTS `%1users%2`"); + SQLDO("DROP TABLE IF EXISTS `%1players%2`"); + SQLDO("DROP TABLE IF EXISTS `%1groups%2`"); + SQLDO("DROP TABLE IF EXISTS `%1group_members%2`"); + SQLDO("DROP TABLE IF EXISTS `%1acl%2`"); + SQLDO("DROP TABLE IF EXISTS `%1channel_links%2`"); + SQLDO("DROP TABLE IF EXISTS `%1bans%2`"); + SQLDO("DROP TABLE IF EXISTS `%1servers%2`"); + SQLDO("UPDATE `%1meta` SET `value` = '6' WHERE `keystring` = 'version'"); } } @@ -881,10 +875,10 @@ QMap Server::getRegistration(int id) { /// @return UserID of authenticated user, -1 for authentication failures, -2 for unknown user (fallthrough), /// -3 for authentication failures where the data could (temporarily) not be verified. -int Server::authenticate(QString &name, const QString &pw, int sessionId, const QStringList &emails, const QString &certhash, bool bStrongCert, const QList &certs) { +int Server::authenticate(QString &name, const QString &password, int sessionId, const QStringList &emails, const QString &certhash, bool bStrongCert, const QList &certs) { int res = bForceExternalAuth ? -3 : -2; - emit authenticateSig(res, name, sessionId, certs, certhash, bStrongCert, pw); + emit authenticateSig(res, name, sessionId, certs, certhash, bStrongCert, password); if (res != -2) { // External authentication handled it. Ignore certificate completely. @@ -913,59 +907,73 @@ int Server::authenticate(QString &name, const QString &pw, int sessionId, const TransactionHolder th; QSqlQuery &query = *th.qsqQuery; - if (! pw.isEmpty()) { - SQLPREP("SELECT `user_id`,`name`,`pw`, `salt`, `kdfmeter` FROM `%1users` WHERE `server_id` = ? AND LOWER(`name`) = LOWER(?)"); - query.addBindValue(iServerNum); - query.addBindValue(name); - SQLEXEC(); - if (query.next()) { - QString storedpw = query.value(2).toString(); - QString storedsalt = query.value(3).toString(); - size_t storedmeter = static_cast(query.value(4).toInt()); - res = -1; - - if (storedmeter < 1) { - if (ServerDB::getHashPlain(pw) == storedpw) - { + SQLPREP("SELECT `user_id`,`name`,`pw`, `salt`, `kdfmeter` FROM `%1users` WHERE `server_id` = ? AND LOWER(`name`) = LOWER(?)"); + query.addBindValue(iServerNum); + query.addBindValue(name); + SQLEXEC(); + if (query.next()) { + const int userId = query.value(0).toInt(); + QString storedPasswordHash = query.value(2).toString(); + QString storedSalt = query.value(3).toString(); + const int storedKdfIterations = query.value(4).toInt(); + res = -1; + + if (!storedPasswordHash.isEmpty()) { + // A user has password authentication enabled if there is a password hash. + + if (storedKdfIterations <= 0) { + // If storedmeter is <=0 this means this is an old-style SHA1 hash + // that hasn't been converted yet. Or we are operating in legacy mode. + if (ServerDB::getLegacySHA1Hash(password) == storedPasswordHash) { name = query.value(1).toString(); res = query.value(0).toInt(); - if (! Meta::mp.bPlainPasswordHash) - { + + if (! Meta::mp.bPlainPasswordHash) { + // Unless disabled upgrade the user password hash QMap info; - info.insert(ServerDB::User_Password, pw); - info.insert(ServerDB::User_KdfMeter, QString::number(static_cast(Meta::mp.kdfIterations))); - int userId = query.value(0).toInt(); - if(! setInfo(userId, info)) - qWarning("ServerDB: Failed to upgrade user account to kdf."); + info.insert(ServerDB::User_Password, password); + info.insert(ServerDB::User_KDFIterations, QString::number(Meta::mp.kdfIterations)); + + if(! setInfo(userId, info)) { + qWarning("ServerDB: Failed to upgrade user account to PBKDF2 hash, rejecting login."); + return -1; + } } } } else { - if (ServerDB::getHashPbkdf2(storedsalt, pw, storedmeter) == storedpw) - { + if (PBKDF2::getHash(storedSalt, password, storedKdfIterations) == storedPasswordHash) { name = query.value(1).toString(); res = query.value(0).toInt(); - if(Meta::mp.bPlainPasswordHash) - { + + if(Meta::mp.bPlainPasswordHash) { + // Downgrade the password to the legacy hash QMap info; - info.insert(ServerDB::User_Password, pw); - int userId = query.value(0).toInt(); - if(! setInfo(userId, info)) - qWarning("ServerDB: Failed to downgrade user account to legacy hash."); - } - else if (storedmeter < Meta::mp.kdfIterations || storedmeter < Meta::mp.iKdfIterations) { - // user kdfmeter lower than global a/or override, update it + info.insert(ServerDB::User_Password, password); + + if(! setInfo(userId, info)) { + qWarning("ServerDB: Failed to downgrade user account to legacy hash, rejecting login."); + return -1; + } + } else if (storedKdfIterations != Meta::mp.kdfIterations) { + // User kdfmeter not equal to the global one. Update it. QMap info; - info.insert(ServerDB::User_Password, pw); - info.insert(ServerDB::User_KdfMeter, QString::number(static_cast(Meta::mp.kdfIterations))); - int userId = query.value(0).toInt(); - if(! setInfo(userId, info)) - qWarning("ServerDB: Failed to update user kdf shadow."); + info.insert(ServerDB::User_Password, password); + info.insert(ServerDB::User_KDFIterations, QString::number(Meta::mp.kdfIterations)); + + if(! setInfo(userId, info)) { + qWarning() << "ServerDB: Failed to update user PBKDF2 to new iteration count" << Meta::mp.kdfIterations << ", rejecting login."; + return -1; + } } } } - if (query.value(0).toInt() == 0 && res == -1) { - return -1; - } + } + + if (userId == 0 && res < 0) { + // For SuperUser only password based authentication is allowed. + // If we couldn't verify the password don't proceed to cert auth + // and instead reject the login attempt. + return -1; } } @@ -1054,27 +1062,29 @@ bool Server::setInfo(int id, const QMap &setinfo) { info.remove(ServerDB::User_LastActive); } if (info.contains(ServerDB::User_Password)) { - const QString pw = info.value(ServerDB::User_Password); - QString pwHash, hashSalt; - size_t kdfMeter; - - if(! pw.isEmpty()) { - if(Meta::mp.bPlainPasswordHash) { - pwHash = ServerDB::getHashPlain(pw); - } else { - kdfMeter = Meta::mp.kdfIterations; - if (info.contains(ServerDB::User_KdfMeter)) - kdfMeter = info.value(ServerDB::User_KdfMeter).toInt(); + const QString password = info.value(ServerDB::User_Password); + QString passwordHash, salt; + int kdfIterations = -1; - hashSalt = ServerDB::getHashSalt(); - pwHash = ServerDB::getHashPbkdf2(hashSalt, pw, kdfMeter); + if(Meta::mp.bPlainPasswordHash) { + passwordHash = ServerDB::getLegacySHA1Hash(password); + } else { + kdfIterations = Meta::mp.kdfIterations; + if (info.contains(ServerDB::User_KDFIterations)) { + const int targetIterations = info.value(ServerDB::User_KDFIterations).toInt(); + if (targetIterations > 0) { + kdfIterations = targetIterations; + } } + + salt = PBKDF2::getSalt(); + passwordHash = PBKDF2::getHash(salt, password, kdfIterations); } SQLPREP("UPDATE `%1users` SET `pw`=?, `salt`=?, `kdfmeter`=? WHERE `server_id` = ? AND `user_id`=?"); - query.addBindValue(pwHash); - query.addBindValue(hashSalt); - query.addBindValue(static_cast(kdfMeter)); + query.addBindValue(passwordHash); + query.addBindValue(salt); + query.addBindValue(kdfIterations); query.addBindValue(iServerNum); query.addBindValue(id); SQLEXEC(); @@ -1148,11 +1158,12 @@ void ServerDB::setSUPW(int srvnum, const QString &pw) { QString pwHash, saltHash; if(! Meta::mp.bPlainPasswordHash) { - saltHash = ServerDB::getHashSalt(); - pwHash = getHashPbkdf2(saltHash, pw, Meta::mp.kdfIterations); + saltHash = PBKDF2::getSalt(); + pwHash = PBKDF2::getHash(saltHash, pw, Meta::mp.kdfIterations); + } + else { + pwHash = getLegacySHA1Hash(pw); } - else - pwHash = getHashPlain(pw); QSqlQuery &query = *th.qsqQuery; @@ -1171,71 +1182,17 @@ void ServerDB::setSUPW(int srvnum, const QString &pw) { SQLPREP("UPDATE `%1users` SET `pw`=?, `salt`=?, `kdfmeter`=? WHERE `server_id` = ? AND `user_id`=?"); query.addBindValue(pwHash); query.addBindValue(saltHash); - query.addBindValue(static_cast(Meta::mp.kdfIterations)); + query.addBindValue(Meta::mp.kdfIterations); query.addBindValue(srvnum); query.addBindValue(0); SQLEXEC(); } -size_t ServerDB::measurePbkdf2() { - QElapsedTimer timer; - qint64 nanoSec; - int stillOverAveragePwLen = 9; - std::vector saltArray(saltLen); - std::vector pwArray(stillOverAveragePwLen); - std::vector out(derivedKeyLen); - int measuredIterations = 10000; - - if (! RAND_bytes(&saltArray[0], saltLen) || ! RAND_bytes(reinterpret_cast(&pwArray[0]), stillOverAveragePwLen)) - qWarning("ServerDB: RAND_bytes for kdf cost measure failed: %s", ERR_error_string(ERR_get_error(), NULL)); - - timer.start(); - if( PKCS5_PBKDF2_HMAC(&pwArray[0], stillOverAveragePwLen, &saltArray[0], saltLen, measuredIterations, EVP_sha384(), derivedKeyLen, &out[0]) == 0 ) - qFatal("ServerDB: kdf iteration cost measurement failed: %s", ERR_error_string(ERR_get_error(), NULL)); - - nanoSec = timer.nsecsElapsed(); - size_t returnCount = (iterationTime * 1000000) / (nanoSec / measuredIterations); - if (returnCount < 1) - qFatal("ServerDB: wrong kdf iteration cost measurement"); - - return returnCount; -} - -QString ServerDB::getHashPbkdf2(const QString saltHex, const QString pw, size_t iterationCount) { - QString pwHex; - - std::vector pwArray(derivedKeyLen); - QByteArray pwBuff = pw.toUtf8(); - std::vector pwInput(pwBuff.data(), pwBuff.data() + pwBuff.size()); - QByteArray saltBuff = QByteArray::fromHex(saltHex.toAscii().constData()); - std::vector saltArray(saltBuff.data(), saltBuff.data() + saltBuff.size()); - - if( PKCS5_PBKDF2_HMAC(&pwInput[0], static_cast(pwInput.size()), &saltArray[0], static_cast(saltArray.size()), static_cast(iterationCount), EVP_sha384(), derivedKeyLen, &pwArray[0]) == 0 ) - qFatal("ServerDB: PKCS5_PBKDF2_HMAC failed: %s", ERR_error_string(ERR_get_error(), NULL)); - - for(int i=0;i(pwArray[i]),2,16,QLatin1Char('0')); - - return pwHex; -} - -QString ServerDB::getHashPlain(const QString pw) { +QString ServerDB::getLegacySHA1Hash(const QString pw) { QByteArray hash = QCryptographicHash::hash(pw.toUtf8(), QCryptographicHash::Sha1); return QString::fromLatin1(hash.toHex()); } -QString ServerDB::getHashSalt() { - std::vector saltArray(saltLen); - if (! RAND_bytes(&saltArray[0], saltLen)) - qWarning("ServerDB: RAND_bytes for salt failed: %s", ERR_error_string(ERR_get_error(), NULL)); - - QString saltHex; - for(int i=0;i(saltArray[i]),2,16,QLatin1Char('0')); - - return saltHex; -} - QString Server::getUserName(int id) { if (qhUserNameCache.contains(id)) return qhUserNameCache.value(id); diff --git a/src/murmur/ServerDB.h b/src/murmur/ServerDB.h index ea7c5396d62..ccd5b22f000 100644 --- a/src/murmur/ServerDB.h +++ b/src/murmur/ServerDB.h @@ -44,7 +44,7 @@ class QSqlQuery; class ServerDB { public: enum ChannelInfo { Channel_Description, Channel_Position }; - enum UserInfo { User_Name, User_Email, User_Comment, User_Hash, User_Password, User_KdfMeter, User_LastActive }; + enum UserInfo { User_Name, User_Email, User_Comment, User_Hash, User_Password, User_LastActive, User_KDFIterations }; ServerDB(); ~ServerDB(); typedef QPair LogRecord; @@ -61,10 +61,7 @@ class ServerDB { static QVariant getConf(int server_id, const QString &key, QVariant def = QVariant()); static void setConf(int server_id, const QString &key, const QVariant &value = QVariant()); static QList getLog(int server_id, unsigned int offs_min, unsigned int offs_max); - static size_t measurePbkdf2(); - static QString getHashPbkdf2(const QString saltHex, const QString pwInput, size_t iterationCount); - static QString getHashPlain(const QString pw); - static QString getHashSalt(); + static QString getLegacySHA1Hash(const QString pw); static int getLogLen(int server_id); static void wipeLogs(); static bool prepare(QSqlQuery &, const QString &, bool fatal = true, bool warn = true); @@ -72,6 +69,9 @@ class ServerDB { static bool execBatch(QSqlQuery &, const QString &str = QString(), bool fatal= true); // No copy; private declaration without implementation ServerDB(const ServerDB &); + + private: + static void loadOrSetupMetaPKBDF2IterationsCount(QSqlQuery &query); }; #endif diff --git a/src/murmur/murmur.pro b/src/murmur/murmur.pro index 91f0c6968f1..194ae31ce16 100644 --- a/src/murmur/murmur.pro +++ b/src/murmur/murmur.pro @@ -16,8 +16,8 @@ TARGET = murmur DBFILE = murmur.db LANGUAGE = C++ FORMS = -HEADERS *= Server.h ServerUser.h Meta.h -SOURCES *= main.cpp Server.cpp ServerUser.cpp ServerDB.cpp Register.cpp Cert.cpp Messages.cpp Meta.cpp RPC.cpp +HEADERS *= Server.h ServerUser.h Meta.h PBKDF2.h +SOURCES *= main.cpp Server.cpp ServerUser.cpp ServerDB.cpp Register.cpp Cert.cpp Messages.cpp Meta.cpp RPC.cpp PBKDF2.cpp DIST = DBus.h ServerDB.h ../../icons/murmur.ico Murmur.ice MurmurI.h MurmurIceWrapper.cpp murmur.plist PRECOMPILED_HEADER = murmur_pch.h From 8aa125fbc72718ef521cfa94bb7e1e872fd97807 Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Mon, 20 Oct 2014 23:31:23 +0200 Subject: [PATCH 3/6] Fix issues found in review of PR #1422 --- scripts/murmur.ini | 2 +- src/murmur/Meta.cpp | 6 +++--- src/murmur/Meta.h | 2 +- src/murmur/PBKDF2.cpp | 17 ++++++++++------- src/murmur/PBKDF2.h | 4 ++-- src/murmur/ServerDB.cpp | 32 ++++++++++++++++---------------- src/murmur/ServerDB.h | 2 +- 7 files changed, 34 insertions(+), 31 deletions(-) diff --git a/scripts/murmur.ini b/scripts/murmur.ini index cef4f17d9a0..d103a0aa8d9 100644 --- a/scripts/murmur.ini +++ b/scripts/murmur.ini @@ -174,7 +174,7 @@ users=100 # This sets password hash storage to legacy mode (1.2.4 and before) # (Note that setting this to true is insecure and should not be used unless absolutely necessary) -#legacyPasswordwHash=false +#legacyPasswordHash=false # By default a strong amount of PBKDF2 iterations are chosen automatically. If >0 this setting # overrides the automatic benchmark and forces a specific number of iterations. diff --git a/src/murmur/Meta.cpp b/src/murmur/Meta.cpp index 06e20c541ae..fbc25d2e449 100644 --- a/src/murmur/Meta.cpp +++ b/src/murmur/Meta.cpp @@ -54,7 +54,7 @@ MetaParams::MetaParams() { iMaxUsersPerChannel = 0; iMaxTextMessageLength = 5000; iMaxImageMessageLength = 131072; - bPlainPasswordHash = false; + legacyPasswordHash = false; kdfIterations = -1; bAllowHTML = true; iDefaultChan = 0; @@ -264,7 +264,7 @@ void MetaParams::read(QString fname) { iTimeout = typeCheckedFromSettings("timeout", iTimeout); iMaxTextMessageLength = typeCheckedFromSettings("textmessagelength", iMaxTextMessageLength); iMaxImageMessageLength = typeCheckedFromSettings("imagemessagelength", iMaxImageMessageLength); - bPlainPasswordHash = typeCheckedFromSettings("legacypasswordwhash", bPlainPasswordHash); + legacyPasswordHash = typeCheckedFromSettings("legacypasswordhash", legacyPasswordHash); kdfIterations = typeCheckedFromSettings("kdfiterations", -1); bAllowHTML = typeCheckedFromSettings("allowhtml", bAllowHTML); iMaxBandwidth = typeCheckedFromSettings("bandwidth", iMaxBandwidth); @@ -462,7 +462,7 @@ void MetaParams::read(QString fname) { qmConfig.insert(QLatin1String("port"),QString::number(usPort)); qmConfig.insert(QLatin1String("timeout"),QString::number(iTimeout)); qmConfig.insert(QLatin1String("textmessagelength"), QString::number(iMaxTextMessageLength)); - qmConfig.insert(QLatin1String("legacypasswordwhash"), bPlainPasswordHash ? QLatin1String("true") : QLatin1String("false")); + qmConfig.insert(QLatin1String("legacypasswordhash"), legacyPasswordHash ? QLatin1String("true") : QLatin1String("false")); qmConfig.insert(QLatin1String("kdfiterations"), QString::number(kdfIterations)); qmConfig.insert(QLatin1String("allowhtml"), bAllowHTML ? QLatin1String("true") : QLatin1String("false")); qmConfig.insert(QLatin1String("bandwidth"),QString::number(iMaxBandwidth)); diff --git a/src/murmur/Meta.h b/src/murmur/Meta.h index c00d7e73f69..08816cd4237 100644 --- a/src/murmur/Meta.h +++ b/src/murmur/Meta.h @@ -64,7 +64,7 @@ class MetaParams { int iOpusThreshold; int iChannelNestingLimit; /// If true the old SHA1 password hashing is used instead of PBKDF2 - bool bPlainPasswordHash; + bool legacyPasswordHash; /// Contains the default number of PBKDF2 iterations to use /// when hashing passwords. If the value loaded from config /// is <= 0 the value is loaded from the database and if not diff --git a/src/murmur/PBKDF2.cpp b/src/murmur/PBKDF2.cpp index 0afe4546a50..242fc912ed8 100644 --- a/src/murmur/PBKDF2.cpp +++ b/src/murmur/PBKDF2.cpp @@ -48,10 +48,13 @@ int PBKDF2::benchmark() { do { iterations *= 2; - if(getHash(hexSalt, pseudopass, iterations).isNull()) { - // Something went wrong here, don't get us stuck in a loop. - break; - } + // Store return value in a volatile to prevent optimizer + // from ever removing these side-effect-free calls. I don't + // think the compiler can prove they have no side-effects but + // better safe than sorry. + volatile QString result = getHash(hexSalt, pseudopass, iterations); + Q_UNUSED(result); + } while (timer.restart() < BENCHMARK_DURATION_TARGET_IN_MS && (iterations / 2) < std::numeric_limits::max()); if (iterations > maxIterations) { @@ -67,11 +70,11 @@ QString PBKDF2::getHash(const QString& hexSalt, const QString& password, int ite const QByteArray utf8Password = password.toUtf8(); const QByteArray salt = QByteArray::fromHex(hexSalt.toLatin1()); - if( PKCS5_PBKDF2_HMAC(utf8Password.constData(), utf8Password.size(), + if (PKCS5_PBKDF2_HMAC(utf8Password.constData(), utf8Password.size(), reinterpret_cast(salt.constData()), salt.size(), iterationCount, EVP_sha384(), - DERIVED_KEY_LENGTH, reinterpret_cast(hash.data())) == 0 ) { + DERIVED_KEY_LENGTH, reinterpret_cast(hash.data())) == 0) { qFatal("PBKDF2: PKCS5_PBKDF2_HMAC failed: %s", ERR_error_string(ERR_get_error(), NULL)); return QString(); } @@ -83,7 +86,7 @@ QString PBKDF2::getHash(const QString& hexSalt, const QString& password, int ite QString PBKDF2::getSalt() { QByteArray salt(SALT_LENGTH, 0); - if (! RAND_bytes(reinterpret_cast(salt.data()), salt.size())) { + if (RAND_bytes(reinterpret_cast(salt.data()), salt.size()) == 0) { qFatal("PBKDF2: RAND_bytes for salt failed: %s", ERR_error_string(ERR_get_error(), NULL)); return QString(); } diff --git a/src/murmur/PBKDF2.h b/src/murmur/PBKDF2.h index 068779ceb40..259b6fed5ca 100644 --- a/src/murmur/PBKDF2.h +++ b/src/murmur/PBKDF2.h @@ -40,6 +40,8 @@ class QString; /// /// @note Using int all over the place because OpenSSL uses them in its C interface /// and we want to make sure not to parameterize it in unexpected ways. +/// @warning Operations in this class that experience internal failure will abort +/// program execution using qFatal. /// class PBKDF2 { public: @@ -55,7 +57,6 @@ class PBKDF2 { /// @param password Password to hash. /// @param iterationCount Number of PBKDF2 iterations to apply. /// @return Hex encoded password hash of DERIVED_KEY_LENGTH octets. - /// Null string if hashing failed. /// static QString getHash(const QString& hexSalt, const QString& password, @@ -63,7 +64,6 @@ class PBKDF2 { /// /// @return SALT_LENGTH octets of hex encoded random salt. - /// Null string if not enough entropy was available. /// static QString getSalt(); diff --git a/src/murmur/ServerDB.cpp b/src/murmur/ServerDB.cpp index 5619fcdb7b2..bed6fb85b19 100644 --- a/src/murmur/ServerDB.cpp +++ b/src/murmur/ServerDB.cpp @@ -75,7 +75,7 @@ Timer ServerDB::tLogClean; QString ServerDB::qsUpgradeSuffix; void ServerDB::loadOrSetupMetaPKBDF2IterationsCount(QSqlQuery &query) { - if (!Meta::mp.bPlainPasswordHash) { + if (!Meta::mp.legacyPasswordHash) { if (Meta::mp.kdfIterations <= 0) { // Configuration doesn't specify an override, load from db @@ -273,7 +273,7 @@ ServerDB::ServerDB() { SQLDO("CREATE UNIQUE INDEX `%1channel_info_id` ON `%1channel_info`(`server_id`, `channel_id`, `key`)"); SQLDO("CREATE TRIGGER `%1channel_info_del_channel` AFTER DELETE on `%1channels` FOR EACH ROW BEGIN DELETE FROM `%1channel_info` WHERE `channel_id` = old.`channel_id` AND `server_id` = old.`server_id`; END;"); - SQLDO("CREATE TABLE `%1users` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `pw` TEXT, `salt` TEXT, `kdfmeter` INTEGER, `lastchannel` INTEGER, `texture` BLOB, `last_active` DATE)"); + SQLDO("CREATE TABLE `%1users` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `name` TEXT NOT NULL, `pw` TEXT, `salt` TEXT, `kdfiterations` INTEGER, `lastchannel` INTEGER, `texture` BLOB, `last_active` DATE)"); SQLDO("CREATE UNIQUE INDEX `%1users_name` ON `%1users` (`server_id`,`name`)"); SQLDO("CREATE UNIQUE INDEX `%1users_id` ON `%1users` (`server_id`, `user_id`)"); SQLDO("CREATE TRIGGER `%1users_server_del` AFTER DELETE ON `%1servers` FOR EACH ROW BEGIN DELETE FROM `%1users` WHERE `server_id` = old.`server_id`; END;"); @@ -363,7 +363,7 @@ ServerDB::ServerDB() { SQLDO("CREATE UNIQUE INDEX `%1channel_info_id` ON `%1channel_info`(`server_id`, `channel_id`, `key`)"); SQLDO("ALTER TABLE `%1channel_info` ADD CONSTRAINT `%1channel_info_del_channel` FOREIGN KEY (`server_id`, `channel_id`) REFERENCES `%1channels`(`server_id`,`channel_id`) ON DELETE CASCADE"); - SQLDO("CREATE TABLE `%1users` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `name` varchar(255), `pw` varchar(128), `salt` varchar(128), `kdfmeter` INTEGER, `lastchannel` INTEGER, `texture` LONGBLOB, `last_active` TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); + SQLDO("CREATE TABLE `%1users` (`server_id` INTEGER NOT NULL, `user_id` INTEGER NOT NULL, `name` varchar(255), `pw` varchar(128), `salt` varchar(128), `kdfiterations` INTEGER, `lastchannel` INTEGER, `texture` LONGBLOB, `last_active` TIMESTAMP) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin"); SQLDO("CREATE INDEX `%1users_channel` ON `%1users`(`server_id`, `lastchannel`)"); SQLDO("CREATE UNIQUE INDEX `%1users_name` ON `%1users` (`server_id`,`name`)"); SQLDO("CREATE UNIQUE INDEX `%1users_id` ON `%1users` (`server_id`, `user_id`)"); @@ -907,14 +907,14 @@ int Server::authenticate(QString &name, const QString &password, int sessionId, TransactionHolder th; QSqlQuery &query = *th.qsqQuery; - SQLPREP("SELECT `user_id`,`name`,`pw`, `salt`, `kdfmeter` FROM `%1users` WHERE `server_id` = ? AND LOWER(`name`) = LOWER(?)"); + SQLPREP("SELECT `user_id`,`name`,`pw`, `salt`, `kdfiterations` FROM `%1users` WHERE `server_id` = ? AND LOWER(`name`) = LOWER(?)"); query.addBindValue(iServerNum); query.addBindValue(name); SQLEXEC(); if (query.next()) { const int userId = query.value(0).toInt(); - QString storedPasswordHash = query.value(2).toString(); - QString storedSalt = query.value(3).toString(); + const QString storedPasswordHash = query.value(2).toString(); + const QString storedSalt = query.value(3).toString(); const int storedKdfIterations = query.value(4).toInt(); res = -1; @@ -922,13 +922,13 @@ int Server::authenticate(QString &name, const QString &password, int sessionId, // A user has password authentication enabled if there is a password hash. if (storedKdfIterations <= 0) { - // If storedmeter is <=0 this means this is an old-style SHA1 hash + // If storedKdfIterations is <=0 this means this is an old-style SHA1 hash // that hasn't been converted yet. Or we are operating in legacy mode. if (ServerDB::getLegacySHA1Hash(password) == storedPasswordHash) { name = query.value(1).toString(); res = query.value(0).toInt(); - if (! Meta::mp.bPlainPasswordHash) { + if (! Meta::mp.legacyPasswordHash) { // Unless disabled upgrade the user password hash QMap info; info.insert(ServerDB::User_Password, password); @@ -945,7 +945,7 @@ int Server::authenticate(QString &name, const QString &password, int sessionId, name = query.value(1).toString(); res = query.value(0).toInt(); - if(Meta::mp.bPlainPasswordHash) { + if(Meta::mp.legacyPasswordHash) { // Downgrade the password to the legacy hash QMap info; info.insert(ServerDB::User_Password, password); @@ -955,7 +955,7 @@ int Server::authenticate(QString &name, const QString &password, int sessionId, return -1; } } else if (storedKdfIterations != Meta::mp.kdfIterations) { - // User kdfmeter not equal to the global one. Update it. + // User kdfiterations not equal to the global one. Update it. QMap info; info.insert(ServerDB::User_Password, password); info.insert(ServerDB::User_KDFIterations, QString::number(Meta::mp.kdfIterations)); @@ -1066,7 +1066,7 @@ bool Server::setInfo(int id, const QMap &setinfo) { QString passwordHash, salt; int kdfIterations = -1; - if(Meta::mp.bPlainPasswordHash) { + if (Meta::mp.legacyPasswordHash) { passwordHash = ServerDB::getLegacySHA1Hash(password); } else { kdfIterations = Meta::mp.kdfIterations; @@ -1081,7 +1081,7 @@ bool Server::setInfo(int id, const QMap &setinfo) { passwordHash = PBKDF2::getHash(salt, password, kdfIterations); } - SQLPREP("UPDATE `%1users` SET `pw`=?, `salt`=?, `kdfmeter`=? WHERE `server_id` = ? AND `user_id`=?"); + SQLPREP("UPDATE `%1users` SET `pw`=?, `salt`=?, `kdfiterations`=? WHERE `server_id` = ? AND `user_id`=?"); query.addBindValue(passwordHash); query.addBindValue(salt); query.addBindValue(kdfIterations); @@ -1157,7 +1157,7 @@ void ServerDB::setSUPW(int srvnum, const QString &pw) { TransactionHolder th; QString pwHash, saltHash; - if(! Meta::mp.bPlainPasswordHash) { + if(! Meta::mp.legacyPasswordHash) { saltHash = PBKDF2::getSalt(); pwHash = PBKDF2::getHash(saltHash, pw, Meta::mp.kdfIterations); } @@ -1179,7 +1179,7 @@ void ServerDB::setSUPW(int srvnum, const QString &pw) { SQLEXEC(); } - SQLPREP("UPDATE `%1users` SET `pw`=?, `salt`=?, `kdfmeter`=? WHERE `server_id` = ? AND `user_id`=?"); + SQLPREP("UPDATE `%1users` SET `pw`=?, `salt`=?, `kdfiterations`=? WHERE `server_id` = ? AND `user_id`=?"); query.addBindValue(pwHash); query.addBindValue(saltHash); query.addBindValue(Meta::mp.kdfIterations); @@ -1188,8 +1188,8 @@ void ServerDB::setSUPW(int srvnum, const QString &pw) { SQLEXEC(); } -QString ServerDB::getLegacySHA1Hash(const QString pw) { - QByteArray hash = QCryptographicHash::hash(pw.toUtf8(), QCryptographicHash::Sha1); +QString ServerDB::getLegacySHA1Hash(const QString& password) { + QByteArray hash = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha1); return QString::fromLatin1(hash.toHex()); } diff --git a/src/murmur/ServerDB.h b/src/murmur/ServerDB.h index ccd5b22f000..0fdd8bdd13c 100644 --- a/src/murmur/ServerDB.h +++ b/src/murmur/ServerDB.h @@ -61,7 +61,7 @@ class ServerDB { static QVariant getConf(int server_id, const QString &key, QVariant def = QVariant()); static void setConf(int server_id, const QString &key, const QVariant &value = QVariant()); static QList getLog(int server_id, unsigned int offs_min, unsigned int offs_max); - static QString getLegacySHA1Hash(const QString pw); + static QString getLegacySHA1Hash(const QString& password); static int getLogLen(int server_id); static void wipeLogs(); static bool prepare(QSqlQuery &, const QString &, bool fatal = true, bool warn = true); From cebcbd30048e48ffafa607ddc0920e4f9924a2e4 Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Tue, 21 Oct 2014 22:30:26 +0200 Subject: [PATCH 4/6] Fix RAND_bytes return value checking. That function can actually return -1 if it isn't supported. --- src/murmur/PBKDF2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/murmur/PBKDF2.cpp b/src/murmur/PBKDF2.cpp index 242fc912ed8..acce8605827 100644 --- a/src/murmur/PBKDF2.cpp +++ b/src/murmur/PBKDF2.cpp @@ -86,7 +86,7 @@ QString PBKDF2::getHash(const QString& hexSalt, const QString& password, int ite QString PBKDF2::getSalt() { QByteArray salt(SALT_LENGTH, 0); - if (RAND_bytes(reinterpret_cast(salt.data()), salt.size()) == 0) { + if (RAND_bytes(reinterpret_cast(salt.data()), salt.size()) != 1) { qFatal("PBKDF2: RAND_bytes for salt failed: %s", ERR_error_string(ERR_get_error(), NULL)); return QString(); } From c664b98943aed13e3c0bbc2843391b5064a356a2 Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Tue, 21 Oct 2014 23:44:33 +0200 Subject: [PATCH 5/6] Fix more coding guideline violations. --- src/murmur/PBKDF2.cpp | 2 +- src/murmur/PBKDF2.h | 4 ++-- src/murmur/ServerDB.cpp | 15 +++++++-------- src/murmur/ServerDB.h | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/murmur/PBKDF2.cpp b/src/murmur/PBKDF2.cpp index acce8605827..bde7dacca0e 100644 --- a/src/murmur/PBKDF2.cpp +++ b/src/murmur/PBKDF2.cpp @@ -64,7 +64,7 @@ int PBKDF2::benchmark() { return maxIterations; } -QString PBKDF2::getHash(const QString& hexSalt, const QString& password, int iterationCount) { +QString PBKDF2::getHash(const QString &hexSalt, const QString &password, int iterationCount) { QByteArray hash(DERIVED_KEY_LENGTH, 0); const QByteArray utf8Password = password.toUtf8(); diff --git a/src/murmur/PBKDF2.h b/src/murmur/PBKDF2.h index 259b6fed5ca..481901762c9 100644 --- a/src/murmur/PBKDF2.h +++ b/src/murmur/PBKDF2.h @@ -58,8 +58,8 @@ class PBKDF2 { /// @param iterationCount Number of PBKDF2 iterations to apply. /// @return Hex encoded password hash of DERIVED_KEY_LENGTH octets. /// - static QString getHash(const QString& hexSalt, - const QString& password, + static QString getHash(const QString &hexSalt, + const QString &password, int iterationCount); /// diff --git a/src/murmur/ServerDB.cpp b/src/murmur/ServerDB.cpp index bed6fb85b19..6411fcc9c79 100644 --- a/src/murmur/ServerDB.cpp +++ b/src/murmur/ServerDB.cpp @@ -934,7 +934,7 @@ int Server::authenticate(QString &name, const QString &password, int sessionId, info.insert(ServerDB::User_Password, password); info.insert(ServerDB::User_KDFIterations, QString::number(Meta::mp.kdfIterations)); - if(! setInfo(userId, info)) { + if (!setInfo(userId, info)) { qWarning("ServerDB: Failed to upgrade user account to PBKDF2 hash, rejecting login."); return -1; } @@ -945,12 +945,12 @@ int Server::authenticate(QString &name, const QString &password, int sessionId, name = query.value(1).toString(); res = query.value(0).toInt(); - if(Meta::mp.legacyPasswordHash) { + if (Meta::mp.legacyPasswordHash) { // Downgrade the password to the legacy hash QMap info; info.insert(ServerDB::User_Password, password); - if(! setInfo(userId, info)) { + if (!setInfo(userId, info)) { qWarning("ServerDB: Failed to downgrade user account to legacy hash, rejecting login."); return -1; } @@ -960,7 +960,7 @@ int Server::authenticate(QString &name, const QString &password, int sessionId, info.insert(ServerDB::User_Password, password); info.insert(ServerDB::User_KDFIterations, QString::number(Meta::mp.kdfIterations)); - if(! setInfo(userId, info)) { + if (!setInfo(userId, info)) { qWarning() << "ServerDB: Failed to update user PBKDF2 to new iteration count" << Meta::mp.kdfIterations << ", rejecting login."; return -1; } @@ -1157,11 +1157,10 @@ void ServerDB::setSUPW(int srvnum, const QString &pw) { TransactionHolder th; QString pwHash, saltHash; - if(! Meta::mp.legacyPasswordHash) { + if (!Meta::mp.legacyPasswordHash) { saltHash = PBKDF2::getSalt(); pwHash = PBKDF2::getHash(saltHash, pw, Meta::mp.kdfIterations); - } - else { + } else { pwHash = getLegacySHA1Hash(pw); } @@ -1188,7 +1187,7 @@ void ServerDB::setSUPW(int srvnum, const QString &pw) { SQLEXEC(); } -QString ServerDB::getLegacySHA1Hash(const QString& password) { +QString ServerDB::getLegacySHA1Hash(const QString &password) { QByteArray hash = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha1); return QString::fromLatin1(hash.toHex()); } diff --git a/src/murmur/ServerDB.h b/src/murmur/ServerDB.h index 0fdd8bdd13c..d9c789064be 100644 --- a/src/murmur/ServerDB.h +++ b/src/murmur/ServerDB.h @@ -61,7 +61,7 @@ class ServerDB { static QVariant getConf(int server_id, const QString &key, QVariant def = QVariant()); static void setConf(int server_id, const QString &key, const QVariant &value = QVariant()); static QList getLog(int server_id, unsigned int offs_min, unsigned int offs_max); - static QString getLegacySHA1Hash(const QString& password); + static QString getLegacySHA1Hash(const QString &password); static int getLogLen(int server_id); static void wipeLogs(); static bool prepare(QSqlQuery &, const QString &, bool fatal = true, bool warn = true); From b923e067f0c56145583e7120e883bef758d0424b Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Sun, 26 Oct 2014 17:46:49 +0100 Subject: [PATCH 6/6] Use real-name in copyright line. --- src/murmur/PBKDF2.cpp | 2 +- src/murmur/PBKDF2.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/murmur/PBKDF2.cpp b/src/murmur/PBKDF2.cpp index bde7dacca0e..3e7b1922527 100644 --- a/src/murmur/PBKDF2.cpp +++ b/src/murmur/PBKDF2.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2013, tkmorris +/* Copyright (C) 2013, Morris Moraes Copyright (C) 2014, Stefan Hacker All rights reserved. diff --git a/src/murmur/PBKDF2.h b/src/murmur/PBKDF2.h index 481901762c9..6e8d0bc4445 100644 --- a/src/murmur/PBKDF2.h +++ b/src/murmur/PBKDF2.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2013, tkmorris +/* Copyright (C) 2013, Morris Moraes Copyright (C) 2014, Stefan Hacker All rights reserved.