soc-mre 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- soc_mre-1.0.0/PKG-INFO +119 -0
- soc_mre-1.0.0/README.md +104 -0
- soc_mre-1.0.0/pyproject.toml +26 -0
- soc_mre-1.0.0/setup.cfg +4 -0
- soc_mre-1.0.0/soc_mre/__init__.py +1 -0
- soc_mre-1.0.0/soc_mre/__main__.py +6 -0
- soc_mre-1.0.0/soc_mre/config.py +37 -0
- soc_mre-1.0.0/soc_mre/database.py +58 -0
- soc_mre-1.0.0/soc_mre/database_sql.py +140 -0
- soc_mre-1.0.0/soc_mre/detector.py +361 -0
- soc_mre-1.0.0/soc_mre/file_loader.py +262 -0
- soc_mre-1.0.0/soc_mre/main.py +724 -0
- soc_mre-1.0.0/soc_mre/menu.py +132 -0
- soc_mre-1.0.0/soc_mre/parser.py +142 -0
- soc_mre-1.0.0/soc_mre/utils.py +68 -0
- soc_mre-1.0.0/soc_mre.egg-info/PKG-INFO +119 -0
- soc_mre-1.0.0/soc_mre.egg-info/SOURCES.txt +23 -0
- soc_mre-1.0.0/soc_mre.egg-info/dependency_links.txt +1 -0
- soc_mre-1.0.0/soc_mre.egg-info/entry_points.txt +2 -0
- soc_mre-1.0.0/soc_mre.egg-info/requires.txt +8 -0
- soc_mre-1.0.0/soc_mre.egg-info/top_level.txt +1 -0
- soc_mre-1.0.0/tests/test_database_sql.py +117 -0
- soc_mre-1.0.0/tests/test_file_loader_csv_gz.py +274 -0
- soc_mre-1.0.0/tests/test_file_loader_slow.py +250 -0
- soc_mre-1.0.0/tests/tests_parser.py +65 -0
soc_mre-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: soc-mre
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Credential Intelligence Tool for SOC
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: rich>=13.0
|
|
8
|
+
Requires-Dist: tqdm>=4.0
|
|
9
|
+
Requires-Dist: psutil>=5.9
|
|
10
|
+
Requires-Dist: python-magic>=0.4
|
|
11
|
+
Requires-Dist: py7zr<1,>=0.20
|
|
12
|
+
Requires-Dist: rarfile>=4.0
|
|
13
|
+
Requires-Dist: pdfplumber>=0.10
|
|
14
|
+
Requires-Dist: openpyxl>=3.1
|
|
15
|
+
|
|
16
|
+
# SOC MRE
|
|
17
|
+
|
|
18
|
+
Ferramenta defensiva de análise de credenciais vazadas para equipes de SOC. Lê ficheiros na pasta de leaks, cruza com domínios monitorados (pastas sob **CTI**) e grava resultados em SQLite e ficheiros de blacklist por domínio.
|
|
19
|
+
|
|
20
|
+
## Dependências de sistema (Linux)
|
|
21
|
+
|
|
22
|
+
O pacote Python inclui bibliotecas para vários formatos; no sistema é recomendado:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
sudo apt install libmagic1 unrar
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- **libmagic1** — deteção MIME (`python-magic`).
|
|
29
|
+
- **unrar** — leitura de alguns ficheiros `.rar` via `rarfile` (sem extrair para disco).
|
|
30
|
+
|
|
31
|
+
No Windows, instale o binário **UnRAR** se precisar de `.rar`, e as DLLs necessárias para `python-magic`.
|
|
32
|
+
|
|
33
|
+
## Instalação
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd soc-mre
|
|
37
|
+
pip install .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Instalação a partir de um repositório Git
|
|
41
|
+
|
|
42
|
+
Substitua o URL pelo do seu fork ou organização:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install git+https://github.com/SEU-USUARIO/soc-mre.git
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Configuração
|
|
49
|
+
|
|
50
|
+
Gere um ficheiro JSON de exemplo e edite os caminhos (`leaks`, `cti`, `database_dir`):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
soc-mre --init-config
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Ficheiro padrão: `soc_mre.json` no diretório atual (alterável com `--config`).
|
|
57
|
+
|
|
58
|
+
## Uso interativo
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
soc-mre --leaks /caminho/leaks --cti /caminho/CTI --database-dir /caminho/database
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Ou preencha os mesmos caminhos no `soc_mre.json` e execute:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
soc-mre
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Linha de comando e modo stdin
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
soc-mre --help
|
|
74
|
+
python -m soc_mre --help
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Modo pipe (exige CTI válido para localizar a pasta do domínio e exportar blacklist):
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
rg -i "@exemplo.com" /caminho/leaks | soc-mre --stdin --domain @exemplo.com -c /caminho/CTI -d /caminho/database
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Persistência (SQLite)
|
|
84
|
+
|
|
85
|
+
- **`database.db`** — criado/atualizado dentro do diretório passado em `--database-dir` / `database_dir`. Tabela `credentials` com chave `(email, password)`, modo WAL.
|
|
86
|
+
- **Migração** — se existir `masterList.txt` nesse diretório na primeira execução, o conteúdo é importado para o SQLite e o ficheiro legado é renomeado para `masterList.txt.bak` (mensagem na consola).
|
|
87
|
+
|
|
88
|
+
## Blacklists por domínio
|
|
89
|
+
|
|
90
|
+
- Ficheiros exportados seguem o padrão lógico **`blackList-<dominio-sanitizado>.txt`**, mas **cada exportação com dados** gera um **novo ficheiro** com timestamp e microssegundos no nome, por exemplo:
|
|
91
|
+
|
|
92
|
+
`blackList-exemplo.com.br_2026-05-03_14-30-00-123456.txt`
|
|
93
|
+
|
|
94
|
+
- **Não** se sobrescrevem exportações anteriores; o histórico acumula-se no disco.
|
|
95
|
+
- Se **não** houver credenciais para aquele domínio na base, a exportação **não** cria ficheiro novo (retorno `0`).
|
|
96
|
+
- O menu continua a carregar credenciais já guardadas a partir do caminho “canónico” da pasta do domínio (`blackList-*.txt` existentes); novas execuções acrescentam ficheiros com timestamp.
|
|
97
|
+
|
|
98
|
+
## Formatos de leak suportados
|
|
99
|
+
|
|
100
|
+
| Formato | Comportamento |
|
|
101
|
+
|---------|-----------------|
|
|
102
|
+
| `.txt`, `.csv` | Leitura em fluxo (linhas em bytes). |
|
|
103
|
+
| `.gz` | Descompressão em fluxo. |
|
|
104
|
+
| `.zip` | Leitura com `zipfile` **sem** extrair para disco; vários membros e subpastas internas. |
|
|
105
|
+
| `.7z`, `.rar`, `.pdf`, `.xlsx` | Tratados como **formatos lentos**: é pedida confirmação no terminal antes de processar; leitura em memória/streaming conforme o tipo (**sem** `extractall` para disco nos arquivos compactados). |
|
|
106
|
+
|
|
107
|
+
Extensões reconhecidas na pasta de leaks estão alinhadas com o que o motor de deteção e o `rg` (quando instalado) consideram.
|
|
108
|
+
|
|
109
|
+
## Outras dependências Python
|
|
110
|
+
|
|
111
|
+
Instaladas automaticamente com `pip install .` (ver `pyproject.toml`): Rich, tqdm, psutil, python-magic, py7zr, rarfile, pdfplumber, openpyxl.
|
|
112
|
+
|
|
113
|
+
## Avisos de memória
|
|
114
|
+
|
|
115
|
+
Durante análise de ficheiros muito grandes, pode ser mostrado um aviso se a RAM do sistema ultrapassar um limiar (percentagem configurada no código); o processo **não** termina so por isso.
|
|
116
|
+
|
|
117
|
+
## Repositório
|
|
118
|
+
|
|
119
|
+
O código-fonte vive no pacote **`soc_mre/`** (nome com underscore). A pasta **`soc-mre/`** (hífen) é a raiz do projeto instalável.
|
soc_mre-1.0.0/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# SOC MRE
|
|
2
|
+
|
|
3
|
+
Ferramenta defensiva de análise de credenciais vazadas para equipes de SOC. Lê ficheiros na pasta de leaks, cruza com domínios monitorados (pastas sob **CTI**) e grava resultados em SQLite e ficheiros de blacklist por domínio.
|
|
4
|
+
|
|
5
|
+
## Dependências de sistema (Linux)
|
|
6
|
+
|
|
7
|
+
O pacote Python inclui bibliotecas para vários formatos; no sistema é recomendado:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
sudo apt install libmagic1 unrar
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- **libmagic1** — deteção MIME (`python-magic`).
|
|
14
|
+
- **unrar** — leitura de alguns ficheiros `.rar` via `rarfile` (sem extrair para disco).
|
|
15
|
+
|
|
16
|
+
No Windows, instale o binário **UnRAR** se precisar de `.rar`, e as DLLs necessárias para `python-magic`.
|
|
17
|
+
|
|
18
|
+
## Instalação
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cd soc-mre
|
|
22
|
+
pip install .
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Instalação a partir de um repositório Git
|
|
26
|
+
|
|
27
|
+
Substitua o URL pelo do seu fork ou organização:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install git+https://github.com/SEU-USUARIO/soc-mre.git
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Configuração
|
|
34
|
+
|
|
35
|
+
Gere um ficheiro JSON de exemplo e edite os caminhos (`leaks`, `cti`, `database_dir`):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
soc-mre --init-config
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Ficheiro padrão: `soc_mre.json` no diretório atual (alterável com `--config`).
|
|
42
|
+
|
|
43
|
+
## Uso interativo
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
soc-mre --leaks /caminho/leaks --cti /caminho/CTI --database-dir /caminho/database
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Ou preencha os mesmos caminhos no `soc_mre.json` e execute:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
soc-mre
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Linha de comando e modo stdin
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
soc-mre --help
|
|
59
|
+
python -m soc_mre --help
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Modo pipe (exige CTI válido para localizar a pasta do domínio e exportar blacklist):
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
rg -i "@exemplo.com" /caminho/leaks | soc-mre --stdin --domain @exemplo.com -c /caminho/CTI -d /caminho/database
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Persistência (SQLite)
|
|
69
|
+
|
|
70
|
+
- **`database.db`** — criado/atualizado dentro do diretório passado em `--database-dir` / `database_dir`. Tabela `credentials` com chave `(email, password)`, modo WAL.
|
|
71
|
+
- **Migração** — se existir `masterList.txt` nesse diretório na primeira execução, o conteúdo é importado para o SQLite e o ficheiro legado é renomeado para `masterList.txt.bak` (mensagem na consola).
|
|
72
|
+
|
|
73
|
+
## Blacklists por domínio
|
|
74
|
+
|
|
75
|
+
- Ficheiros exportados seguem o padrão lógico **`blackList-<dominio-sanitizado>.txt`**, mas **cada exportação com dados** gera um **novo ficheiro** com timestamp e microssegundos no nome, por exemplo:
|
|
76
|
+
|
|
77
|
+
`blackList-exemplo.com.br_2026-05-03_14-30-00-123456.txt`
|
|
78
|
+
|
|
79
|
+
- **Não** se sobrescrevem exportações anteriores; o histórico acumula-se no disco.
|
|
80
|
+
- Se **não** houver credenciais para aquele domínio na base, a exportação **não** cria ficheiro novo (retorno `0`).
|
|
81
|
+
- O menu continua a carregar credenciais já guardadas a partir do caminho “canónico” da pasta do domínio (`blackList-*.txt` existentes); novas execuções acrescentam ficheiros com timestamp.
|
|
82
|
+
|
|
83
|
+
## Formatos de leak suportados
|
|
84
|
+
|
|
85
|
+
| Formato | Comportamento |
|
|
86
|
+
|---------|-----------------|
|
|
87
|
+
| `.txt`, `.csv` | Leitura em fluxo (linhas em bytes). |
|
|
88
|
+
| `.gz` | Descompressão em fluxo. |
|
|
89
|
+
| `.zip` | Leitura com `zipfile` **sem** extrair para disco; vários membros e subpastas internas. |
|
|
90
|
+
| `.7z`, `.rar`, `.pdf`, `.xlsx` | Tratados como **formatos lentos**: é pedida confirmação no terminal antes de processar; leitura em memória/streaming conforme o tipo (**sem** `extractall` para disco nos arquivos compactados). |
|
|
91
|
+
|
|
92
|
+
Extensões reconhecidas na pasta de leaks estão alinhadas com o que o motor de deteção e o `rg` (quando instalado) consideram.
|
|
93
|
+
|
|
94
|
+
## Outras dependências Python
|
|
95
|
+
|
|
96
|
+
Instaladas automaticamente com `pip install .` (ver `pyproject.toml`): Rich, tqdm, psutil, python-magic, py7zr, rarfile, pdfplumber, openpyxl.
|
|
97
|
+
|
|
98
|
+
## Avisos de memória
|
|
99
|
+
|
|
100
|
+
Durante análise de ficheiros muito grandes, pode ser mostrado um aviso se a RAM do sistema ultrapassar um limiar (percentagem configurada no código); o processo **não** termina so por isso.
|
|
101
|
+
|
|
102
|
+
## Repositório
|
|
103
|
+
|
|
104
|
+
O código-fonte vive no pacote **`soc_mre/`** (nome com underscore). A pasta **`soc-mre/`** (hífen) é a raiz do projeto instalável.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "soc-mre"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Credential Intelligence Tool for SOC"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"rich>=13.0",
|
|
13
|
+
"tqdm>=4.0",
|
|
14
|
+
"psutil>=5.9",
|
|
15
|
+
"python-magic>=0.4",
|
|
16
|
+
"py7zr>=0.20,<1",
|
|
17
|
+
"rarfile>=4.0",
|
|
18
|
+
"pdfplumber>=0.10",
|
|
19
|
+
"openpyxl>=3.1",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
soc-mre = "soc_mre.main:main"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools]
|
|
26
|
+
packages = ["soc_mre"]
|
soc_mre-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""SOC MRE — pacote de análise de credenciais para SOC."""
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
DEFAULT_CONFIG_FILE = "soc_mre.json"
|
|
9
|
+
|
|
10
|
+
MASTERLIST_FILENAME = "masterList.txt"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load_config(config_path: Path) -> Dict[str, Any]:
|
|
14
|
+
if not config_path.exists():
|
|
15
|
+
return {}
|
|
16
|
+
try:
|
|
17
|
+
raw = json.loads(config_path.read_text(encoding="utf-8"))
|
|
18
|
+
except (json.JSONDecodeError, OSError):
|
|
19
|
+
return {}
|
|
20
|
+
if not isinstance(raw, dict):
|
|
21
|
+
return {}
|
|
22
|
+
return raw
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def save_default_config(config_path: Path) -> None:
|
|
26
|
+
template = {
|
|
27
|
+
"leaks": "/home/kali/Area de trabalho/leaks_23-04-2026",
|
|
28
|
+
"cti": "/home/kali/Area de trabalho/CTI",
|
|
29
|
+
"database_dir": "/home/kali/Area de trabalho/database",
|
|
30
|
+
"blacklist_pattern": "blackList-<dominio>.txt",
|
|
31
|
+
"recent_hours": 24,
|
|
32
|
+
"state_file": ".processed_state.json",
|
|
33
|
+
}
|
|
34
|
+
config_path.write_text(
|
|
35
|
+
json.dumps(template, ensure_ascii=True, indent=2),
|
|
36
|
+
encoding="utf-8",
|
|
37
|
+
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, Set
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
CredentialDB = Dict[str, Set[str]]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def load_database(db_path: Path) -> CredentialDB:
|
|
11
|
+
db: CredentialDB = {}
|
|
12
|
+
if not db_path.exists():
|
|
13
|
+
return db
|
|
14
|
+
|
|
15
|
+
with db_path.open("r", encoding="utf-8", errors="replace") as f:
|
|
16
|
+
for raw in f:
|
|
17
|
+
line = raw.strip()
|
|
18
|
+
if not line or ":" not in line:
|
|
19
|
+
continue
|
|
20
|
+
email, passwords_raw = line.split(":", 1)
|
|
21
|
+
email = email.strip().lower()
|
|
22
|
+
if not email:
|
|
23
|
+
continue
|
|
24
|
+
password_set = {
|
|
25
|
+
p.strip()
|
|
26
|
+
for p in passwords_raw.split(",")
|
|
27
|
+
if p.strip()
|
|
28
|
+
}
|
|
29
|
+
if not password_set:
|
|
30
|
+
continue
|
|
31
|
+
db.setdefault(email, set()).update(password_set)
|
|
32
|
+
return db
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def add_credential(db: CredentialDB, email: str, password: str) -> bool:
|
|
36
|
+
"""Retorna True se foi novidade na base."""
|
|
37
|
+
email = email.lower().strip()
|
|
38
|
+
password = password.strip()
|
|
39
|
+
if not email or not password:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
known = db.setdefault(email, set())
|
|
43
|
+
if password in known:
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
known.add(password)
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def save_database(db_path: Path, db: CredentialDB) -> None:
|
|
51
|
+
tmp = db_path.with_suffix(".tmp")
|
|
52
|
+
with tmp.open("w", encoding="utf-8") as f:
|
|
53
|
+
for email in sorted(db.keys()):
|
|
54
|
+
values = sorted(db[email])
|
|
55
|
+
if not values:
|
|
56
|
+
continue
|
|
57
|
+
f.write(f"{email}:{','.join(values)}\n")
|
|
58
|
+
tmp.replace(db_path)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sqlite3
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING, Optional
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .database import CredentialDB
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def init_db(db_path: Path) -> sqlite3.Connection:
|
|
13
|
+
conn = sqlite3.connect(db_path)
|
|
14
|
+
conn.execute("PRAGMA journal_mode=WAL")
|
|
15
|
+
conn.execute("PRAGMA synchronous=NORMAL")
|
|
16
|
+
conn.execute("""
|
|
17
|
+
CREATE TABLE IF NOT EXISTS credentials (
|
|
18
|
+
email TEXT NOT NULL,
|
|
19
|
+
password TEXT NOT NULL,
|
|
20
|
+
domain TEXT NOT NULL,
|
|
21
|
+
source TEXT,
|
|
22
|
+
found_at TEXT DEFAULT (datetime('now')),
|
|
23
|
+
PRIMARY KEY (email, password)
|
|
24
|
+
)
|
|
25
|
+
""")
|
|
26
|
+
conn.commit()
|
|
27
|
+
return conn
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def add_credential_sql(
|
|
31
|
+
conn: sqlite3.Connection,
|
|
32
|
+
email: str,
|
|
33
|
+
password: str,
|
|
34
|
+
domain: str,
|
|
35
|
+
source: Optional[str] = None,
|
|
36
|
+
insert_count: Optional[list] = None,
|
|
37
|
+
) -> bool:
|
|
38
|
+
cursor = conn.execute(
|
|
39
|
+
"INSERT OR IGNORE INTO credentials "
|
|
40
|
+
"(email, password, domain, source) VALUES (?,?,?,?)",
|
|
41
|
+
(email.lower(), password, domain, source),
|
|
42
|
+
)
|
|
43
|
+
inserted = cursor.rowcount > 0
|
|
44
|
+
if inserted and insert_count is not None:
|
|
45
|
+
insert_count[0] += 1
|
|
46
|
+
if insert_count[0] % 100_000 == 0:
|
|
47
|
+
conn.commit()
|
|
48
|
+
return inserted
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _get_timestamped_path(output_path: Path) -> Path:
|
|
52
|
+
"""
|
|
53
|
+
Gera um nome de arquivo único usando timestamp com microssegundos.
|
|
54
|
+
Evita colisão mesmo em execuções simultâneas.
|
|
55
|
+
"""
|
|
56
|
+
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S-%f")
|
|
57
|
+
|
|
58
|
+
base = output_path.stem
|
|
59
|
+
suffix = output_path.suffix
|
|
60
|
+
parent = output_path.parent
|
|
61
|
+
|
|
62
|
+
return parent / f"{base}_{timestamp}{suffix}"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def export_domain_txt(
|
|
66
|
+
conn: sqlite3.Connection,
|
|
67
|
+
domain: str,
|
|
68
|
+
output_path: Path,
|
|
69
|
+
) -> int:
|
|
70
|
+
rows = conn.execute(
|
|
71
|
+
"SELECT email, GROUP_CONCAT(password, ',' ORDER BY password) "
|
|
72
|
+
"FROM credentials WHERE domain=? "
|
|
73
|
+
"GROUP BY email ORDER BY email",
|
|
74
|
+
(domain,),
|
|
75
|
+
).fetchall()
|
|
76
|
+
if not rows:
|
|
77
|
+
return 0
|
|
78
|
+
target_path = _get_timestamped_path(output_path)
|
|
79
|
+
tmp = target_path.with_suffix(".tmp")
|
|
80
|
+
with tmp.open("w", encoding="utf-8") as f:
|
|
81
|
+
for email, passwords in rows:
|
|
82
|
+
f.write(f"{email}:{passwords}\n")
|
|
83
|
+
tmp.replace(target_path)
|
|
84
|
+
return len(rows)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def close_db(conn: sqlite3.Connection) -> None:
|
|
88
|
+
conn.commit()
|
|
89
|
+
conn.close()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Linhas migradas de masterList.txt sem dominio por linha.
|
|
93
|
+
_MASTERLIST_DOMAIN = "__masterlist__"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def import_masterlist_txt_to_sql(
|
|
97
|
+
conn: sqlite3.Connection,
|
|
98
|
+
masterlist_path: Path,
|
|
99
|
+
insert_count: Optional[list] = None,
|
|
100
|
+
) -> int:
|
|
101
|
+
"""Importa conteudo de masterList.txt (formato database.load_database) para o SQLite."""
|
|
102
|
+
from .database import load_database
|
|
103
|
+
|
|
104
|
+
if not masterlist_path.exists():
|
|
105
|
+
return 0
|
|
106
|
+
legacy: CredentialDB = load_database(masterlist_path)
|
|
107
|
+
n = 0
|
|
108
|
+
for email, passwords in legacy.items():
|
|
109
|
+
for password in passwords:
|
|
110
|
+
if add_credential_sql(
|
|
111
|
+
conn,
|
|
112
|
+
email,
|
|
113
|
+
password,
|
|
114
|
+
_MASTERLIST_DOMAIN,
|
|
115
|
+
"migration:masterList.txt",
|
|
116
|
+
insert_count,
|
|
117
|
+
):
|
|
118
|
+
n += 1
|
|
119
|
+
conn.commit()
|
|
120
|
+
return n
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def sync_credential_dict_to_sql(
|
|
124
|
+
conn: sqlite3.Connection,
|
|
125
|
+
domain: str,
|
|
126
|
+
cred_db: "CredentialDB",
|
|
127
|
+
insert_count: Optional[list] = None,
|
|
128
|
+
) -> None:
|
|
129
|
+
"""Garante no SQLite as credenciais ja carregadas do blackList-<dominio>.txt em RAM."""
|
|
130
|
+
for email, passwords in cred_db.items():
|
|
131
|
+
for password in passwords:
|
|
132
|
+
add_credential_sql(
|
|
133
|
+
conn,
|
|
134
|
+
email,
|
|
135
|
+
password,
|
|
136
|
+
domain,
|
|
137
|
+
"import:blackList",
|
|
138
|
+
insert_count,
|
|
139
|
+
)
|
|
140
|
+
conn.commit()
|