Files
2026-09-29 18:45:19 -04:00

149 lines
6.2 KiB
C++

#include <sqlite3.h>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
using Database = std::unique_ptr<sqlite3, decltype(&sqlite3_close)>;
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<char>(c);
}
}
}
return result + '"';
}
private:
sqlite3* db_;
std::unique_ptr<sqlite3_stmt, decltype(&sqlite3_finalize)>
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;
}