Skip to content
API Documentation
BastionGuard™
LeakDatabase.cpp
Go to the documentation of this file.
1/*
2 * BastionGuard™
3 * Copyright (C) 2025–2026 Calogero Scarnà
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 *
17 * BastionGuard™ is a trademark of Calogero Scarnà.
18 * The BastionGuard™ name and branding are not licensed under the GPL.
19 */
20
21#include "LeakDatabase.hpp"
22#include <glib/gi18n.h>
23#include <sqlite3.h>
24#include <filesystem>
25#include <stdexcept>
26
27namespace fs = std::filesystem;
28
29static const char* SCHEMA_SQL = R"SQL(
30CREATE TABLE IF NOT EXISTS identity (
31 email_hash TEXT PRIMARY KEY,
32 first_seen INTEGER DEFAULT (strftime('%s','now'))
33);
34
35CREATE TABLE IF NOT EXISTS leak (
36 email_hash TEXT NOT NULL,
37 breach_name TEXT NOT NULL,
38 breach_date TEXT NOT NULL,
39 severity INTEGER NOT NULL,
40 provider TEXT,
41 description TEXT,
42 PRIMARY KEY (email_hash, breach_name, breach_date)
43);
44)SQL";
45
47: db(nullptr)
48{
49 // getenv("HOME") può restituire NULL (sandbox, setuid, ambiente pulito).
50 // std::string(nullptr) è UB: proteggiamoci con un fallback ragionevole.
51 const char* home_env = std::getenv("HOME");
52 std::string base;
53 if (home_env && *home_env) {
54 base = std::string(home_env) + "/.local/share/BastionGuard";
55 } else {
56 base = "/tmp/BastionGuard";
57 }
58
59 fs::create_directories(base);
60 dbPath = base + "/identity_leaks.db";
61
62 open();
63 initSchema();
64}
65
67{
68 std::lock_guard<std::mutex> lock(dbMutex);
69
70 sqlite3* handle = nullptr;
71 if (sqlite3_open(dbPath.c_str(), &handle) != SQLITE_OK)
72 {
73 // Anche se open fallisce, SQLite alloca un handle che va chiuso
74 // (documentato in https://sqlite.org/c3ref/open.html).
75 if (handle) sqlite3_close(handle);
76 throw std::runtime_error(_("SQLite: open failed"));
77 }
78 db = handle;
79}
80
82{
83 std::lock_guard<std::mutex> lock(dbMutex);
84
85 char* err = nullptr;
86 if (sqlite3_exec(
87 reinterpret_cast<sqlite3*>(db),
89 nullptr,
90 nullptr,
91 &err) != SQLITE_OK)
92 {
93 std::string msg = err ? err : "unknown";
94 sqlite3_free(err);
95 throw std::runtime_error(
96 "SQLite schema error: " + msg);
97 }
98}
99
100void LeakDatabase::ensureIdentity(const std::string& emailHash)
101{
102 std::lock_guard<std::mutex> lock(dbMutex);
103
104 const char* sql =
105 "INSERT OR IGNORE INTO identity(email_hash) VALUES (?);";
106
107 sqlite3_stmt* stmt = nullptr;
108 if (sqlite3_prepare_v2(
109 reinterpret_cast<sqlite3*>(db),
110 sql,
111 -1,
112 &stmt,
113 nullptr) != SQLITE_OK || !stmt)
114 {
115 // Se prepare fallisce, stmt è NULL e bind/step andrebbero in crash.
116 return;
117 }
118
119 sqlite3_bind_text(stmt, 1,
120 emailHash.c_str(), -1, SQLITE_TRANSIENT);
121
122 sqlite3_step(stmt);
123 sqlite3_finalize(stmt);
124}
125
127 const std::string& emailHash,
128 const LeakRecord& record)
129{
130 std::lock_guard<std::mutex> lock(dbMutex);
131
132 const char* sql =
133 "SELECT 1 FROM leak "
134 "WHERE email_hash=? AND breach_name=? AND breach_date=?;";
135
136 sqlite3_stmt* stmt = nullptr;
137 if (sqlite3_prepare_v2(
138 reinterpret_cast<sqlite3*>(db),
139 sql,
140 -1,
141 &stmt,
142 nullptr) != SQLITE_OK || !stmt)
143 {
144 // prepare fallita: trattiamolo come "nuovo leak" per non perdere notifiche.
145 return true;
146 }
147
148 sqlite3_bind_text(stmt, 1,
149 emailHash.c_str(), -1, SQLITE_TRANSIENT);
150 sqlite3_bind_text(stmt, 2,
151 record.breachName.c_str(), -1, SQLITE_TRANSIENT);
152 sqlite3_bind_text(stmt, 3,
153 record.breachDate.c_str(), -1, SQLITE_TRANSIENT);
154
155 int rc = sqlite3_step(stmt);
156 sqlite3_finalize(stmt);
157
158 return rc != SQLITE_ROW;
159}
160
162 const std::string& emailHash,
163 const LeakRecord& record)
164{
165 std::lock_guard<std::mutex> lock(dbMutex);
166
167 const char* sql =
168 "INSERT INTO leak "
169 "(email_hash, breach_name, breach_date, severity, provider, description) "
170 "VALUES (?, ?, ?, ?, ?, ?);";
171
172 sqlite3_stmt* stmt = nullptr;
173 if (sqlite3_prepare_v2(
174 reinterpret_cast<sqlite3*>(db),
175 sql,
176 -1,
177 &stmt,
178 nullptr) != SQLITE_OK || !stmt)
179 {
180 // prepare fallita: non possiamo scrivere, ma non crashiamo.
181 return;
182 }
183
184 sqlite3_bind_text(stmt, 1,
185 emailHash.c_str(), -1, SQLITE_TRANSIENT);
186 sqlite3_bind_text(stmt, 2,
187 record.breachName.c_str(), -1, SQLITE_TRANSIENT);
188 sqlite3_bind_text(stmt, 3,
189 record.breachDate.c_str(), -1, SQLITE_TRANSIENT);
190 sqlite3_bind_int(stmt, 4,
191 static_cast<int>(record.severity));
192 sqlite3_bind_text(stmt, 5,
193 record.provider.c_str(), -1, SQLITE_TRANSIENT);
194 sqlite3_bind_text(stmt, 6,
195 record.description.c_str(), -1, SQLITE_TRANSIENT);
196
197 sqlite3_step(stmt);
198 sqlite3_finalize(stmt);
199}
#define _(s)
static const char * SCHEMA_SQL
std::string dbPath
bool isNewLeak(const std::string &emailHash, const LeakRecord &record)
std::mutex dbMutex
void ensureIdentity(const std::string &emailHash)
void storeLeak(const std::string &emailHash, const LeakRecord &record)
std::string breachDate
Severity severity
std::string provider
std::string description
std::string breachName