#!/usr/bin/env python3
import re
import sys
import argparse
def parse_logs(file_path):
unique_ips = set()
unique_users = set()
unique_passwords = set()
unique_commands = set()
unique_pubkeys = set()
# Regex patterns adjusted for your specific log format
# Extracts everything inside the first set of brackets after the timestamp
ip_bracket_pattern = re.compile(r'^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[([^\]]+)\]')
# Auth patterns
password_pattern = re.compile(r'authentication for user "([^"]+)" with password "([^"]*)"')
no_cred_pattern = re.compile(r'authentication for user "([^"]+)" without credentials')
pubkey_pattern = re.compile(r'authentication for user "([^"]+)" with (?:public )?key "([^"]+)"')
# Command pattern
command_pattern = re.compile(r'command "([^"]+)" requested')
try:
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
line = line.strip()
# 1. Extract IP from the bracket header [IP:Port]
ip_match = ip_bracket_pattern.match(line)
if ip_match:
host_port = ip_match.group(1)
# Split from the rightmost colon to isolate the port
if ':' in host_port:
ip = host_port.rsplit(':', 1)[0].strip('[]')
unique_ips.add(ip)
# 2. Extract Username & Password
pwd_match = password_pattern.search(line)
if pwd_match:
unique_users.add(pwd_match.group(1))
unique_passwords.add(pwd_match.group(2))
continue
# 3. Extract Username from "without credentials" attempts
no_cred_match = no_cred_pattern.search(line)
if no_cred_match:
unique_users.add(no_cred_match.group(1))
continue
# 4. Extract Public Keys (if any exist in other parts of the log)
key_match = pubkey_pattern.search(line)
if key_match:
unique_users.add(key_match.group(1))
unique_pubkeys.add(key_match.group(2))
continue
# 5. Extract Commands
cmd_match = command_pattern.search(line)
if cmd_match:
unique_commands.add(cmd_match.group(1))
continue
except FileNotFoundError:
print(f"Error: The file '{file_path}' could not be found.", file=sys.stderr)
sys.exit(1)
except PermissionError:
print(f"Error: Missing permissions to read '{file_path}'.", file=sys.stderr)
sys.exit(1)
# Output Results
print_section("Unique Attacker IPs", unique_ips)
print_section("Targeted Usernames", unique_users)
print_section("Attempted Passwords", unique_passwords)
print_section("Public Keys Used", unique_pubkeys)
print_section("Attempted Commands / Payloads", unique_commands)
def print_section(title, data_set):
print(f"=== {title} ({len(data_set)}) ===")
if not data_set:
print(" (None found)")
else:
for item in sorted(data_set):
print(f" {item}")
print("\n")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Parse unique intelligence from your sshesame log format.")
parser.add_argument("logfile", help="Path to the sshesame text log file")
args = parser.parse_args()
parse_logs(args.logfile)
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>EML Reader & Viewer</title>
<!-- Tailwind CSS (Play CDN) -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Alpine.js (reaktywność) - ładowany z defer -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body class="bg-gray-50 h-screen flex flex-col font-sans overflow-hidden" x-data="emlApp()">
<!-- TOPBAR: Adres URL i przycisk pobierania -->
<header class="bg-white border-b border-gray-200 px-6 py-4 flex flex-col sm:flex-row items-center justify-between gap-4 shrink-0">
<div class="flex items-center gap-3">
<span class="text-2xl">✉️</span>
<h1 class="text-xl font-bold text-gray-800">EML Viewer</h1>
</div>
<div class="flex w-full sm:w-auto max-w-xl gap-2 grow justify-end">
<input
type="url"
x-model="targetUrl"
placeholder="Wpisz URL do listy plików EML (np. http://localhost:8000/)"
class="border border-gray-300 rounded px-3 py-1.5 text-sm w-full focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
@click="fetchEmlList()"
:disabled="isLoadingList"
class="bg-blue-600 hover:bg-blue-700 text-white font-medium text-sm px-4 py-1.5 rounded transition disabled:opacity-50 whitespace-nowrap"
>
<span x-show="!isLoadingList">Pobierz listę</span>
<span x-show="isLoadingList">Ładowanie...</span>
</button>
</div>
</header>
<!-- GŁÓWNY PANEL: Układ dwukolumnowy -->
<main class="flex flex-1 overflow-hidden">
<!-- LEWA KOLUMNA: Lista plików -->
<section class="w-80 border-r border-gray-200 bg-white flex flex-col shrink-0">
<div class="p-4 border-b border-gray-100 bg-gray-50">
<span class="text-xs font-bold uppercase tracking-wider text-gray-500">Wykryte pliki EML</span>
<span class="bg-blue-100 text-blue-800 text-xs px-2 py-0.5 rounded-full ml-2 font-semibold" x-text="emlFiles.length">0</span>
</div>
<div class="flex-1 overflow-y-auto divide-y divide-gray-100">
<!-- Loader listy -->
<template x-if="isLoadingList">
<div class="p-6 text-center text-sm text-gray-500">Skanowanie strony w poszukiwaniu plików...</div>
</template>
<!-- Brak wyników -->
<template x-if="!isLoadingList && emlFiles.length === 0">
<div class="p-6 text-center text-sm text-gray-400">Brak plików EML. Wpisz URL i kliknij pobieranie.</div>
</template>
<!-- Elementy listy -->
<template x-for="fileUrl in emlFiles" :key="fileUrl">
<button
@click="selectEml(fileUrl)"
class="w-full text-left p-4 hover:bg-blue-50/50 transition flex flex-col gap-1 focus:outline-none focus:bg-blue-50"
:class="selectedEmlUrl === fileUrl ? 'bg-blue-50 border-l-4 border-blue-600 pl-3' : ''"
>
<span class="text-sm font-medium text-gray-700 truncate w-full" x-text="getFileName(fileUrl)"></span>
<span class="text-xs text-gray-400 truncate w-full" x-text="fileUrl"></span>
</button>
</template>
</div>
</section>
<!-- PRAWA KOLUMNA: Podgląd maila -->
<section class="flex-1 bg-white flex flex-col overflow-hidden">
<!-- Loader szczegółów maila -->
<template x-if="isLoadingMail">
<div class="flex-1 flex flex-col items-center justify-center text-gray-500">
<div class="animate-spin rounded-full h-10 w-10 border-b-2 border-blue-600 mb-4"></div>
<p>Pobieranie i dekodowanie wiadomości...</p>
</div>
</template>
<!-- Stan początkowy (gdy nic nie wybrano) -->
<template x-if="!isLoadingMail && !selectedEmlContent">
<div class="flex-1 flex flex-col items-center justify-center text-gray-400 p-8 text-center">
<span class="text-5xl mb-4">📂</span>
<p class="text-lg font-medium">Nie wybrano wiadomości</p>
<p class="text-sm max-w-xs mt-1">Wybierz plik EML z listy po lewej stronie, aby wyświetlić jego zawartość.</p>
</div>
</template>
<!-- Widok wiadomości -->
<template x-if="!isLoadingMail && selectedEmlContent">
<div class="flex flex-1 flex-col overflow-hidden">
<!-- Nagłówki wiadomości -->
<div class="p-6 border-b border-gray-200 bg-gray-50/50 shrink-0">
<h2 class="text-xl font-bold text-gray-900 mb-3" x-text="selectedEmlContent.subject || '(Bez tematu)'"></h2>
<div class="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1.5 text-sm">
<span class="text-gray-400 font-medium">Od:</span>
<span class="text-gray-800" x-text="formatAddress(selectedEmlContent.from)"></span>
<span class="text-gray-400 font-medium">Do:</span>
<span class="text-gray-800" x-text="formatAddress(selectedEmlContent.to)"></span>
<span class="text-gray-400 font-medium">Data:</span>
<span class="text-gray-800" x-text="selectedEmlContent.date ? new Date(selectedEmlContent.date).toLocaleString() : 'Brak daty'"></span>
</div>
<!-- Załączniki (jeśli istnieją) -->
<template x-if="selectedEmlContent.attachments && selectedEmlContent.attachments.length > 0">
<div class="mt-4 pt-4 border-t border-gray-200">
<span class="text-xs font-bold text-gray-500 uppercase tracking-wider block mb-2">Załączniki</span>
<div class="flex flex-wrap gap-2">
<template x-for="att in selectedEmlContent.attachments" :key="att.filename">
<button
@click="downloadAttachment(att)"
class="bg-white border border-gray-200 hover:border-blue-400 rounded px-3 py-1.5 text-xs font-medium text-gray-700 flex items-center gap-1.5 shadow-sm transition"
>
📎 <span x-text="att.filename || 'Załącznik bez nazwy'"></span>
</button>
</template>
</div>
</div>
</template>
</div>
<!-- Treść wiadomości renderowana w izolowanym iframe -->
<div class="flex-1 bg-white p-4 overflow-hidden flex">
<iframe
class="w-full h-full border-0"
:srcdoc="getEmailBody(selectedEmlContent)"
sandbox="allow-popups allow-popups-to-escape-sandbox"
></iframe>
</div>
</div>
</template>
</section>
</main>
<!-- Powiadomienie o błędzie -->
<template x-if="errorMessage">
<div class="fixed bottom-4 right-4 bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded shadow-lg max-w-md z-50 flex items-start gap-2">
<div class="flex-1">
<p class="font-bold text-sm">Wystąpił błąd</p>
<p class="text-xs mt-1" x-text="errorMessage"></p>
</div>
<button @click="errorMessage = null" class="text-red-500 hover:text-red-700 font-bold text-lg leading-none">×</button>
</div>
</template>
<!-- Standardowy skrypt gwarantujący poprawne wpięcie w cykl życia Alpine.js -->
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('emlApp', () => ({
targetUrl: 'http://localhost:8000/',
emlFiles: [],
selectedEmlUrl: null,
selectedEmlContent: null,
isLoadingList: false,
isLoadingMail: false,
errorMessage: null,
async fetchEmlList() {
this.isLoadingList = true;
this.errorMessage = null;
this.emlFiles = [];
try {
const response = await fetch(this.targetUrl);
if (!response.ok) {
throw new Error(`Serwer odpowiedział kodem ${response.status}: ${response.statusText}`);
}
const htmlText = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(htmlText, 'text/html');
const links = Array.from(doc.querySelectorAll('a'));
this.emlFiles = links
.map(a => a.getAttribute('href'))
.filter(href => href && href.toLowerCase().endsWith('.eml'))
.map(href => {
return new URL(href, this.targetUrl).href;
});
if (this.emlFiles.length === 0) {
this.errorMessage = "Nie znaleziono żadnych plików .eml na tej stronie. Upewnij się, że serwer zwraca poprawną listę plików w tagach <a href='...'>";
}
} catch (err) {
this.errorMessage = `Błąd podczas pobierania listy: ${err.message}. Zweryfikuj CORS oraz czy URL jest poprawny.`;
console.error(err);
} finally {
this.isLoadingList = false;
}
},
async selectEml(url) {
this.selectedEmlUrl = url;
this.isLoadingMail = true;
this.selectedEmlContent = null;
this.errorMessage = null;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Błąd pobierania pliku: ${response.status}`);
}
const arrayBuffer = await response.arrayBuffer();
// Dynamiczny, asynchroniczny import modułu PostalMime w locie
const { default: PostalMime } = await import('https://cdn.jsdelivr.net/npm/postal-mime@2.2.0/+esm');
const parser = new PostalMime();
const parsedEmail = await parser.parse(arrayBuffer);
this.selectedEmlContent = parsedEmail;
} catch (err) {
this.errorMessage = `Nie udało się załadować wiadomości: ${err.message}`;
console.error(err);
} finally {
this.isLoadingMail = false;
}
},
getFileName(url) {
try {
const decoded = decodeURIComponent(url);
return decoded.substring(decoded.lastIndexOf('/') + 1);
} catch {
return url;
}
},
formatAddress(addressObj) {
if (!addressObj) return '(Brak danych)';
const list = Array.isArray(addressObj) ? addressObj : [addressObj];
return list.map(addr => {
if (addr.name) {
return `${addr.name} <${addr.address}>`;
}
return addr.address;
}).join(', ');
},
getEmailBody(email) {
if (!email) return '';
if (email.html) {
return `<meta http-equiv="Content-Security-Policy" content="script-src 'none';">` + email.html;
}
const escapedText = email.text
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
return `<pre style="font-family: sans-serif; white-space: pre-wrap; word-break: break-all;">${escapedText}</pre>`;
},
downloadAttachment(attachment) {
try {
const blob = new Blob([attachment.content], { type: attachment.mimeType });
const blobUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = attachment.filename || 'download';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(blobUrl);
} catch (err) {
this.errorMessage = `Nie udało się pobrać załącznika: ${err.message}`;
}
}
}));
});
</script>
</body>
</html>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
public class BigFileMergeSort
{
private static void SplitAndSort(string inputFile, string outputFile)
{
var lines = new List<string>(100_000);
var part = 0;
var size = 0;
Task blocker = Task.CompletedTask;
using (var reader = new StreamReader(inputFile))
{
while (!reader.EndOfStream)
{
string line = reader.ReadLine()!;
lines.Add(line);
size += line.Length;
if (size > 128_000_000)
{
size = 0;
var fname = $"{outputFile}part{part++}.txt";
var flines = lines.ToList();
Task.WaitAll(new[] { blocker });
blocker = Task.Run(() => Jazda(flines, fname));
lines.Clear();
}
}
Task.WaitAll(new[] { blocker });
if (lines.Count > 0)
{
Jazda(lines, $"{outputFile}part{part++}.txt");
}
}
static void Jazda(List<string> flines, string fname)
{
flines.Sort();
File.WriteAllLines(fname, flines);
}
}
public class FilePart
{
public required StreamReader S { get; set; }
public required string? L { get; set; }
public void Go() => L = S.ReadLine();
public bool GameOver => L is null;
}
private static void Merge(string outputFile)
{
var parts = new List<FilePart>();
var flush = new List<string>();
var size = 0;
foreach (var file in Directory.GetFiles("C:\\temp\\sort", $"*part*.txt"))
{
parts.Add(new FilePart() { S = new StreamReader(file), L = null });
}
parts.ForEach(x => x.Go());
Task blocker = Task.CompletedTask;
while (parts.Count > 0)
{
var kd = parts.OrderBy(x => x.L).First();
flush.Add(kd.L!);
size += kd.L!.Length;
if (size > 128_000_000)
{
Task.WaitAll(new[] { blocker });
size = 0;
var tmp = flush;
blocker = Task.Run(() =>
{
File.AppendAllLines(outputFile, tmp);
tmp.Clear();
});
flush = new List<string>();
}
kd.Go();
if (kd.GameOver)
{
parts.Remove(kd);
}
}
Task.WaitAll(new[] { blocker });
if (flush.Count > 0)
{
File.AppendAllLines(outputFile, flush);
}
}
public static void Main()
{
string inputFile = "C:\\temp\\sort\\testfile.txt";
string outputFile = "C:\\temp\\sort\\output";
var sw = Stopwatch.StartNew();
SplitAndSort(inputFile, outputFile);
Merge(outputFile);
Console.WriteLine(sw.Elapsed.TotalSeconds);
}
}