#include #include #include #include #include using Database = std::unique_ptr; class Query { public: Query(sqlite3* db, const char* sql) : db_(db) { sqlite3_stmt* raw = nullptr; const int result = sqlite3_prepare_v2(db, sql, -1, &raw, nullptr); statement_.reset(raw); if (result != SQLITE_OK) throw std::runtime_error(sqlite3_errmsg(db)); } void bind(int index, sqlite3_int64 value) { if (sqlite3_bind_int64(statement_.get(), index, value) != SQLITE_OK) throw std::runtime_error(sqlite3_errmsg(db_)); } bool next() { const int result = sqlite3_step(statement_.get()); if (result == SQLITE_ROW) return true; if (result == SQLITE_DONE) return false; throw std::runtime_error(sqlite3_errmsg(db_)); } sqlite3_int64 number(int column) const { return sqlite3_column_int64(statement_.get(), column); } // Quote strings and escape control characters, including terminal escapes. // NULL and an empty string remain distinguishable. std::string value(int column) const { if (sqlite3_column_type(statement_.get(), column) == SQLITE_NULL) return "NULL"; const auto* data = sqlite3_column_text(statement_.get(), column); if (!data) throw std::runtime_error("Cannot read SQLite text value"); const int size = sqlite3_column_bytes(statement_.get(), column); const char* hex = "0123456789abcdef"; std::string result = "\""; for (int i = 0; i < size; ++i) { const unsigned char c = data[i]; switch (c) { case '\\': result += "\\\\"; break; case '"': result += "\\\""; break; case '\n': result += "\\n"; break; case '\r': result += "\\r"; break; case '\t': result += "\\t"; break; default: if (c < 32 || c == 127) { result += "\\x"; result += hex[c >> 4]; result += hex[c & 15]; } else { result += static_cast(c); } } } return result + '"'; } private: sqlite3* db_; std::unique_ptr statement_{nullptr, sqlite3_finalize}; }; int main(int argc, char** argv) { if (argc != 2 || std::string(argv[1]) == "--help") { std::cerr << "Usage: " << argv[0] << " /path/to/quassel-storage.sqlite\n" << "Read-only NickServ/SASL recovery for Quassel 0.14.0.\n" << "Output includes saved passwords in clear text.\n"; return argc == 2 ? 0 : 2; } try { sqlite3* raw = nullptr; const int result = sqlite3_open_v2(argv[1], &raw, SQLITE_OPEN_READONLY, nullptr); Database db(raw, sqlite3_close); if (result != SQLITE_OK) throw std::runtime_error(raw ? sqlite3_errmsg(raw) : "Cannot allocate SQLite connection"); sqlite3_busy_timeout(db.get(), 3000); // Keep all queries in the same consistent read snapshot. Query begin(db.get(), "BEGIN"); begin.next(); Query networks(db.get(), "SELECT n.networkid, n.userid, u.username, n.networkname, n.identityid, " "i.identityname, i.realname, i.ident, n.useautoidentify, " "n.autoidentifyservice, n.autoidentifypassword, n.usesasl, " "n.saslaccount, n.saslpassword, n.perform, n.attachperform, n.detachperform " "FROM network n LEFT JOIN quasseluser u ON u.userid = n.userid " "LEFT JOIN identity i ON i.identityid = n.identityid AND i.userid = n.userid " "ORDER BY n.userid, n.networkid"); int count = 0; while (networks.next()) { ++count; std::cout << "\nNetwork " << networks.number(0) << ": " << networks.value(3) << "\n Core user: " << networks.value(2) << " (id " << networks.number(1) << ")" << "\n Identity: " << networks.value(5) << " (id " << networks.number(4) << ")" << "\n Real name: " << networks.value(6) << "\n Ident: " << networks.value(7) << '\n'; Query nicks(db.get(), "SELECT nick FROM identity_nick WHERE identityid = ? ORDER BY nickid"); nicks.bind(1, networks.number(4)); while (nicks.next()) std::cout << " Nick: " << nicks.value(0) << '\n'; Query servers(db.get(), "SELECT hostname, port, ssl FROM ircserver " "WHERE networkid = ? AND userid = ? ORDER BY serverid"); servers.bind(1, networks.number(0)); servers.bind(2, networks.number(1)); while (servers.next()) { std::cout << " Server: " << servers.value(0) << ':' << servers.number(1) << " (TLS " << (servers.number(2) ? "yes" : "no") << ")\n"; } std::cout << " NickServ auto-identify enabled: " << (networks.number(8) ? "yes" : "no") << "\n NickServ service: " << networks.value(9) << "\n NickServ password: " << networks.value(10) << "\n SASL enabled: " << (networks.number(11) ? "yes" : "no") << "\n SASL account: " << networks.value(12) << "\n SASL password: " << networks.value(13) << "\n Connect commands (perform): " << networks.value(14) << "\n Attach commands: " << networks.value(15) << "\n Detach commands: " << networks.value(16) << '\n'; } Query commit(db.get(), "COMMIT"); commit.next(); std::cout << "\nNetworks found: " << count << '\n'; std::cout.flush(); if (!std::cout) throw std::runtime_error("Cannot write output"); } catch (const std::exception& error) { std::cerr << "Error: " << error.what() << "\nExpected a Quassel 0.14.0 SQLite database.\n"; return 1; } return 0; }