1019 lines
18 KiB
Python
1019 lines
18 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
Работа с capture_sessions.
|
||
|
||
Модуль предоставляет функции для получения
|
||
информации о поездках (захватах).
|
||
|
||
Используется:
|
||
|
||
dashboard.py
|
||
|
||
session_page.py
|
||
|
||
generator.py
|
||
"""
|
||
|
||
from pathlib import Path
|
||
|
||
from .config import (
|
||
CAPTURES_DIR,
|
||
)
|
||
|
||
from .database import (
|
||
query_all,
|
||
query_one,
|
||
query_value,
|
||
)
|
||
|
||
from datetime import datetime
|
||
|
||
from .utils import (
|
||
format_duration,
|
||
format_size,
|
||
)
|
||
|
||
|
||
# ============================================================
|
||
# Список всех поездок
|
||
# ============================================================
|
||
|
||
def get_sessions(conn):
|
||
|
||
sql = """
|
||
|
||
SELECT
|
||
|
||
id,
|
||
|
||
start_time,
|
||
|
||
end_time,
|
||
|
||
capture_file
|
||
|
||
FROM capture_sessions
|
||
|
||
ORDER BY
|
||
|
||
start_time DESC
|
||
|
||
"""
|
||
|
||
return query_all(
|
||
conn,
|
||
sql
|
||
)
|
||
|
||
|
||
# ============================================================
|
||
# Получить одну поездку
|
||
# ============================================================
|
||
|
||
def get_session(
|
||
conn,
|
||
session_id
|
||
):
|
||
|
||
sql = """
|
||
|
||
SELECT
|
||
|
||
id,
|
||
|
||
start_time,
|
||
|
||
end_time,
|
||
|
||
capture_file
|
||
|
||
FROM capture_sessions
|
||
|
||
WHERE id=?
|
||
|
||
"""
|
||
|
||
return query_one(
|
||
conn,
|
||
sql,
|
||
(
|
||
session_id,
|
||
)
|
||
)
|
||
|
||
|
||
# ============================================================
|
||
# Продолжительность
|
||
# ============================================================
|
||
|
||
def session_duration(
|
||
conn,
|
||
session_id
|
||
):
|
||
|
||
row = query_one(
|
||
conn,
|
||
"""
|
||
|
||
SELECT
|
||
|
||
start_time,
|
||
|
||
end_time
|
||
|
||
FROM capture_sessions
|
||
|
||
WHERE id=?
|
||
|
||
""",
|
||
(
|
||
session_id,
|
||
)
|
||
)
|
||
|
||
if row is None:
|
||
|
||
return None
|
||
|
||
start = row[0]
|
||
|
||
end = row[1]
|
||
|
||
return format_duration(
|
||
start,
|
||
end
|
||
)
|
||
|
||
# ============================================================
|
||
# Реальное время поездки (GPS)
|
||
# ============================================================
|
||
|
||
def trip_time(
|
||
conn,
|
||
session_id,
|
||
):
|
||
|
||
row = query_one(
|
||
conn,
|
||
"""
|
||
SELECT
|
||
|
||
MIN(timestamp),
|
||
MAX(timestamp)
|
||
|
||
FROM gps_track
|
||
|
||
WHERE session_id=?
|
||
|
||
""",
|
||
(
|
||
session_id,
|
||
)
|
||
)
|
||
|
||
if row is None:
|
||
|
||
return (
|
||
None,
|
||
None,
|
||
)
|
||
|
||
return (
|
||
row[0],
|
||
row[1],
|
||
)
|
||
|
||
# ============================================================
|
||
# Количество точек доступа в поездке
|
||
# ============================================================
|
||
|
||
def access_points_count(
|
||
conn,
|
||
session_id
|
||
):
|
||
|
||
return query_value(
|
||
conn,
|
||
"""
|
||
SELECT COUNT(DISTINCT access_point_id)
|
||
|
||
FROM access_point_observations
|
||
|
||
WHERE session_id=?
|
||
""",
|
||
(
|
||
session_id,
|
||
),
|
||
default=0
|
||
)
|
||
|
||
|
||
# ============================================================
|
||
# Handshake AP (уникальные точки)
|
||
# ============================================================
|
||
|
||
def handshakes_count(
|
||
conn,
|
||
session_id
|
||
):
|
||
|
||
return query_value(
|
||
conn,
|
||
"""
|
||
SELECT COUNT(DISTINCT access_point_id)
|
||
|
||
FROM handshakes
|
||
|
||
WHERE session_id=?
|
||
""",
|
||
(
|
||
session_id,
|
||
),
|
||
default=0
|
||
)
|
||
|
||
|
||
# ============================================================
|
||
# Handshake Records (все записи)
|
||
# ============================================================
|
||
|
||
def handshake_records_count(
|
||
conn,
|
||
session_id
|
||
):
|
||
|
||
return query_value(
|
||
conn,
|
||
"""
|
||
SELECT COUNT(*)
|
||
|
||
FROM handshakes
|
||
|
||
WHERE session_id=?
|
||
""",
|
||
(
|
||
session_id,
|
||
),
|
||
default=0
|
||
)
|
||
|
||
# ============================================================
|
||
# Найденные пароли
|
||
# ============================================================
|
||
|
||
def credentials_count(
|
||
conn,
|
||
session_id
|
||
):
|
||
|
||
return query_value(
|
||
conn,
|
||
"""
|
||
SELECT COUNT(DISTINCT c.id)
|
||
|
||
FROM credentials c
|
||
|
||
JOIN access_points ap
|
||
ON ap.id = c.access_point_id
|
||
|
||
JOIN access_point_observations obs
|
||
ON obs.access_point_id = ap.id
|
||
|
||
WHERE obs.session_id = ?
|
||
""",
|
||
(
|
||
session_id,
|
||
),
|
||
default=0
|
||
)
|
||
|
||
|
||
# ============================================================
|
||
# PMKID
|
||
# ============================================================
|
||
|
||
def pmkid_count(
|
||
conn,
|
||
session_id
|
||
):
|
||
|
||
return query_value(
|
||
conn,
|
||
"""
|
||
SELECT COUNT(DISTINCT ap.id)
|
||
|
||
FROM access_points ap
|
||
|
||
JOIN access_point_observations obs
|
||
ON obs.access_point_id = ap.id
|
||
|
||
WHERE
|
||
obs.session_id = ?
|
||
AND ap.has_pmkid = 1
|
||
""",
|
||
(
|
||
session_id,
|
||
),
|
||
default=0
|
||
)
|
||
|
||
|
||
# ============================================================
|
||
# Взломанные сети
|
||
# ============================================================
|
||
|
||
def cracked_count(
|
||
conn,
|
||
session_id
|
||
):
|
||
|
||
return query_value(
|
||
conn,
|
||
"""
|
||
SELECT COUNT(DISTINCT ap.id)
|
||
|
||
FROM access_points ap
|
||
|
||
JOIN access_point_observations obs
|
||
ON obs.access_point_id = ap.id
|
||
|
||
WHERE
|
||
obs.session_id = ?
|
||
AND ap.is_cracked = 1
|
||
""",
|
||
(
|
||
session_id,
|
||
),
|
||
default=0
|
||
)
|
||
|
||
|
||
def coordinates_restored_count(
|
||
conn,
|
||
session_id
|
||
):
|
||
|
||
return query_value(
|
||
conn,
|
||
"""
|
||
SELECT COUNT(*)
|
||
FROM
|
||
(
|
||
SELECT
|
||
current_obs.access_point_id
|
||
FROM access_point_observations AS current_obs
|
||
WHERE
|
||
current_obs.session_id = ?
|
||
AND current_obs.latitude IS NOT NULL
|
||
AND current_obs.longitude IS NOT NULL
|
||
AND current_obs.latitude != 0
|
||
AND current_obs.longitude != 0
|
||
AND EXISTS
|
||
(
|
||
SELECT 1
|
||
FROM access_point_observations AS previous_obs
|
||
WHERE
|
||
previous_obs.access_point_id =
|
||
current_obs.access_point_id
|
||
AND previous_obs.observed_at <
|
||
(
|
||
SELECT start_time
|
||
FROM capture_sessions
|
||
WHERE id=?
|
||
)
|
||
)
|
||
AND NOT EXISTS
|
||
(
|
||
SELECT 1
|
||
FROM access_point_observations AS previous_coords
|
||
WHERE
|
||
previous_coords.access_point_id =
|
||
current_obs.access_point_id
|
||
AND previous_coords.observed_at <
|
||
(
|
||
SELECT start_time
|
||
FROM capture_sessions
|
||
WHERE id=?
|
||
)
|
||
AND previous_coords.latitude IS NOT NULL
|
||
AND previous_coords.longitude IS NOT NULL
|
||
AND previous_coords.latitude != 0
|
||
AND previous_coords.longitude != 0
|
||
)
|
||
GROUP BY current_obs.access_point_id
|
||
)
|
||
""",
|
||
(
|
||
session_id,
|
||
session_id,
|
||
session_id
|
||
),
|
||
default=0
|
||
)
|
||
|
||
|
||
def ap_without_coordinates_count(
|
||
conn,
|
||
session_id
|
||
):
|
||
|
||
return query_value(
|
||
conn,
|
||
"""
|
||
SELECT COUNT(*)
|
||
FROM
|
||
(
|
||
SELECT
|
||
obs.access_point_id
|
||
FROM access_point_observations AS obs
|
||
WHERE
|
||
obs.session_id = ?
|
||
GROUP BY obs.access_point_id
|
||
HAVING
|
||
SUM(
|
||
CASE
|
||
WHEN obs.latitude IS NOT NULL
|
||
AND obs.longitude IS NOT NULL
|
||
AND obs.latitude != 0
|
||
AND obs.longitude != 0
|
||
THEN 1
|
||
ELSE 0
|
||
END
|
||
) = 0
|
||
)
|
||
""",
|
||
(
|
||
session_id,
|
||
),
|
||
default=0
|
||
)
|
||
|
||
# ============================================================
|
||
# Полная статистика поездки
|
||
# ============================================================
|
||
|
||
def session_statistics(
|
||
conn,
|
||
session_id
|
||
):
|
||
|
||
row = get_session(
|
||
conn,
|
||
session_id
|
||
)
|
||
|
||
if row is None:
|
||
|
||
return None
|
||
|
||
(
|
||
ident,
|
||
start_time,
|
||
end_time,
|
||
capture_file,
|
||
) = row
|
||
|
||
return {
|
||
|
||
"id": ident,
|
||
|
||
"start_time": start_time,
|
||
|
||
"end_time": end_time,
|
||
|
||
"duration": session_duration(
|
||
conn,
|
||
session_id
|
||
),
|
||
|
||
"access_points": access_points_count(
|
||
conn,
|
||
session_id
|
||
),
|
||
|
||
"coordinates_restored": coordinates_restored_count(
|
||
conn,
|
||
session_id
|
||
),
|
||
|
||
"ap_without_coordinates": ap_without_coordinates_count(
|
||
conn,
|
||
session_id
|
||
),
|
||
|
||
"handshakes": handshakes_count(
|
||
conn,
|
||
session_id
|
||
),
|
||
|
||
"handshake_records": handshake_records_count(
|
||
conn,
|
||
session_id
|
||
),
|
||
|
||
"credentials": credentials_count(
|
||
conn,
|
||
session_id
|
||
),
|
||
|
||
"pmkid": pmkid_count(
|
||
conn,
|
||
session_id
|
||
),
|
||
|
||
"cracked": cracked_count(
|
||
conn,
|
||
session_id
|
||
),
|
||
|
||
"capture_file": capture_file,
|
||
|
||
}
|
||
|
||
# ============================================================
|
||
# Каталог поездки
|
||
# ============================================================
|
||
|
||
def session_directory(capture_file):
|
||
|
||
if capture_file is None:
|
||
|
||
return None
|
||
|
||
capture_path = Path(capture_file)
|
||
|
||
if capture_path.parent.exists():
|
||
|
||
return capture_path.parent
|
||
|
||
return CAPTURES_DIR / capture_path.parent.name
|
||
|
||
|
||
# ============================================================
|
||
# Проверка существования файла
|
||
# ============================================================
|
||
|
||
def file_exists(path):
|
||
|
||
if path is None:
|
||
|
||
return False
|
||
|
||
return Path(path).exists()
|
||
|
||
|
||
# ============================================================
|
||
# Получить все файлы поездки
|
||
# ============================================================
|
||
|
||
def session_files(
|
||
conn,
|
||
session_id
|
||
):
|
||
|
||
row = get_session(
|
||
conn,
|
||
session_id
|
||
)
|
||
|
||
if row is None:
|
||
|
||
return None
|
||
|
||
directory = session_directory(
|
||
row[3]
|
||
)
|
||
|
||
files = {
|
||
|
||
"directory": directory,
|
||
|
||
"capture": directory / "capture.pcapng",
|
||
|
||
"gps_track": directory / "gps_track.nmea",
|
||
|
||
"parser_log": directory / "parser.log",
|
||
|
||
"access_points": directory / "access_points.csv",
|
||
|
||
"handshakes": directory / "handshakes.22000",
|
||
|
||
"essid": directory / "essid.txt",
|
||
|
||
}
|
||
|
||
return files
|
||
|
||
|
||
# ============================================================
|
||
# Файлы, реально существующие на диске
|
||
# ============================================================
|
||
|
||
def existing_files(
|
||
conn,
|
||
session_id
|
||
):
|
||
|
||
files = session_files(
|
||
conn,
|
||
session_id
|
||
)
|
||
|
||
if files is None:
|
||
|
||
return {}
|
||
|
||
result = {}
|
||
|
||
for name, path in files.items():
|
||
|
||
if name == "directory":
|
||
|
||
result[name] = path
|
||
|
||
continue
|
||
|
||
result[name] = {
|
||
|
||
"path": path,
|
||
|
||
"exists": file_exists(path),
|
||
|
||
"size": path.stat().st_size if file_exists(path) else 0,
|
||
|
||
}
|
||
|
||
return result
|
||
|
||
|
||
# ============================================================
|
||
# Размер каталога поездки
|
||
# ============================================================
|
||
|
||
def session_directory_size(
|
||
conn,
|
||
session_id
|
||
):
|
||
|
||
files = session_files(
|
||
conn,
|
||
session_id
|
||
)
|
||
|
||
if files is None:
|
||
return 0
|
||
|
||
directory = files["directory"]
|
||
|
||
if not directory.exists():
|
||
return 0
|
||
|
||
total = 0
|
||
|
||
for file in directory.iterdir():
|
||
|
||
if file.is_file():
|
||
total += file.stat().st_size
|
||
|
||
return total
|
||
|
||
|
||
# ============================================================
|
||
# Последние поездки
|
||
# ============================================================
|
||
|
||
def recent_sessions(
|
||
conn,
|
||
limit=10
|
||
):
|
||
|
||
sql = """
|
||
|
||
SELECT
|
||
|
||
id
|
||
|
||
FROM capture_sessions
|
||
|
||
ORDER BY
|
||
|
||
start_time DESC
|
||
|
||
LIMIT ?
|
||
|
||
"""
|
||
|
||
rows = conn.execute(
|
||
sql,
|
||
(
|
||
limit,
|
||
)
|
||
).fetchall()
|
||
|
||
result = []
|
||
|
||
for row in rows:
|
||
|
||
card = session_card_data(
|
||
|
||
conn,
|
||
|
||
row[0]
|
||
|
||
)
|
||
|
||
if card:
|
||
|
||
result.append(card)
|
||
|
||
return result
|
||
|
||
# ============================================================
|
||
# Самая длинная поездка
|
||
# ============================================================
|
||
|
||
def longest_session(conn):
|
||
|
||
sql = """
|
||
|
||
SELECT
|
||
|
||
id,
|
||
|
||
start_time,
|
||
|
||
end_time
|
||
|
||
FROM capture_sessions
|
||
|
||
WHERE end_time IS NOT NULL
|
||
|
||
"""
|
||
|
||
rows = conn.execute(sql).fetchall()
|
||
|
||
winner = None
|
||
|
||
seconds = -1
|
||
|
||
for ident, start, end in rows:
|
||
|
||
try:
|
||
|
||
s = datetime.fromisoformat(start)
|
||
|
||
e = datetime.fromisoformat(end)
|
||
|
||
except Exception:
|
||
|
||
continue
|
||
|
||
diff = (e - s).total_seconds()
|
||
|
||
if diff > seconds:
|
||
|
||
seconds = diff
|
||
|
||
winner = ident
|
||
|
||
if winner is None:
|
||
|
||
return None
|
||
|
||
return session_statistics(
|
||
conn,
|
||
winner
|
||
)
|
||
|
||
|
||
# ============================================================
|
||
# Самая короткая поездка
|
||
# ============================================================
|
||
|
||
def shortest_session(conn):
|
||
|
||
sql = """
|
||
|
||
SELECT
|
||
|
||
id,
|
||
|
||
start_time,
|
||
|
||
end_time
|
||
|
||
FROM capture_sessions
|
||
|
||
WHERE end_time IS NOT NULL
|
||
|
||
"""
|
||
|
||
rows = conn.execute(sql).fetchall()
|
||
|
||
winner = None
|
||
|
||
seconds = None
|
||
|
||
for ident, start, end in rows:
|
||
|
||
try:
|
||
|
||
s = datetime.fromisoformat(start)
|
||
|
||
e = datetime.fromisoformat(end)
|
||
|
||
except Exception:
|
||
|
||
continue
|
||
|
||
diff = (e - s).total_seconds()
|
||
|
||
if seconds is None or diff < seconds:
|
||
|
||
seconds = diff
|
||
|
||
winner = ident
|
||
|
||
if winner is None:
|
||
|
||
return None
|
||
|
||
return session_statistics(
|
||
conn,
|
||
winner
|
||
)
|
||
|
||
|
||
# ============================================================
|
||
# Карточка поездки
|
||
# ============================================================
|
||
|
||
def session_card_data(
|
||
conn,
|
||
session_id
|
||
):
|
||
|
||
stat = session_statistics(
|
||
conn,
|
||
session_id
|
||
)
|
||
|
||
if stat is None:
|
||
|
||
return None
|
||
|
||
files = existing_files(
|
||
conn,
|
||
session_id
|
||
)
|
||
|
||
stat["files"] = files
|
||
|
||
trip_start, trip_end = trip_time(
|
||
conn,
|
||
session_id
|
||
)
|
||
|
||
if trip_start:
|
||
stat["trip_start"] = trip_start
|
||
|
||
if trip_end:
|
||
stat["trip_end"] = trip_end
|
||
|
||
if trip_start and trip_end:
|
||
stat["duration"] = format_duration(
|
||
trip_start,
|
||
trip_end
|
||
)
|
||
|
||
stat["directory_size"] = format_size(
|
||
|
||
session_directory_size(
|
||
conn,
|
||
session_id
|
||
)
|
||
|
||
)
|
||
|
||
return stat
|
||
|
||
|
||
# ============================================================
|
||
# Карточки всех поездок
|
||
# ============================================================
|
||
|
||
def all_sessions(
|
||
conn
|
||
):
|
||
|
||
sql = """
|
||
|
||
SELECT
|
||
|
||
id
|
||
|
||
FROM capture_sessions
|
||
|
||
ORDER BY
|
||
|
||
start_time DESC
|
||
|
||
"""
|
||
|
||
rows = conn.execute(
|
||
sql
|
||
).fetchall()
|
||
|
||
result = []
|
||
|
||
for row in rows:
|
||
|
||
card = session_card_data(
|
||
conn,
|
||
row[0]
|
||
)
|
||
|
||
if card:
|
||
|
||
result.append(card)
|
||
|
||
return result
|
||
|
||
def all_session_cards(conn):
|
||
|
||
sql = """
|
||
|
||
SELECT
|
||
|
||
id
|
||
|
||
FROM capture_sessions
|
||
|
||
ORDER BY
|
||
|
||
start_time DESC
|
||
|
||
"""
|
||
|
||
rows = conn.execute(sql).fetchall()
|
||
|
||
result = []
|
||
|
||
for row in rows:
|
||
|
||
card = session_card_data(
|
||
|
||
conn,
|
||
|
||
row[0]
|
||
|
||
)
|
||
|
||
if card:
|
||
|
||
result.append(card)
|
||
|
||
return result
|
||
|
||
|
||
# ============================================================
|
||
# Поиск поездки по ID
|
||
# ============================================================
|
||
|
||
def find_session(
|
||
conn,
|
||
session_id
|
||
):
|
||
|
||
for session in all_session_cards(conn):
|
||
|
||
if session["id"] == session_id:
|
||
|
||
return session
|
||
|
||
return None
|
||
|
||
|
||
# ============================================================
|
||
# Всего поездок
|
||
# ============================================================
|
||
|
||
def sessions_count(conn):
|
||
|
||
return query_value(
|
||
|
||
conn,
|
||
|
||
"""
|
||
|
||
SELECT COUNT(*)
|
||
|
||
FROM capture_sessions
|
||
|
||
""",
|
||
|
||
default=0
|
||
|
||
) |