149 lines
6.2 KiB
C++
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;
|
|
}
|