#!/usr/bin/env python3 """ IMAP Log Parser - Extracts and groups IMAP transactions from log files """ import re import json from collections import defaultdict from datetime import datetime import argparse def unescape_imap_content(content): """ Unescape IMAP content by converting escape sequences back to their original characters """ # Remove surrounding quotes if present if content.startswith('"') and content.endswith('"'): content = content[1:-1] # Common escape sequences in IMAP logs replacements = { '\\r\\n': '\r\n', '\\n': '\n', '\\r': '\r', '\\t': '\t', '\\"': '"', '\\\\': '\\' } for escaped, unescaped in replacements.items(): content = content.replace(escaped, unescaped) return content def parse_imap_log_line(line): """ Parse a single IMAP log line and extract relevant information """ # Pattern to match the log format pattern = r'(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)\s+TRACE\s+Raw IMAP\s+(input received|output sent)\s+.*?remoteIp\s*=\s*([^,]+),\s*remotePort\s*=\s*(\d+).*?contents\s*=\s*(.+)$' match = re.search(pattern, line) if not match: return None timestamp, direction, remote_ip, remote_port, contents = match.groups() return { 'timestamp': timestamp, 'direction': direction, 'remote_ip': remote_ip.strip(), 'remote_port': int(remote_port), 'contents': unescape_imap_content(contents.strip()), 'raw_line': line.strip() } def group_by_connection(log_entries): """ Group log entries by IP and port combination """ connections = defaultdict(list) for entry in log_entries: if entry: # Skip None entries key = f"{entry['remote_ip']}:{entry['remote_port']}" connections[key].append(entry) # Sort entries within each connection by timestamp for key in connections: connections[key].sort(key=lambda x: x['timestamp']) return dict(connections) def format_imap_transaction(entries): """ Format IMAP transaction entries into a readable format """ transaction = [] for entry in entries: direction_symbol = "C: " if "input received" in entry['direction'] else "S: " timestamp = entry['timestamp'] content = entry['contents'] # Clean up the content display if content.endswith('\\r\\n') or content.endswith('\r\n'): content = content.rstrip('\\r\\n\r\n') transaction.append(f"[{timestamp}] {direction_symbol}{content}") return transaction def write_output_file(connections, output_file): """ Write the grouped transactions to an output file """ with open(output_file, 'w', encoding='utf-8') as f: f.write("IMAP Transaction Log Analysis\n") f.write("=" * 50 + "\n\n") for connection_key, entries in connections.items(): f.write(f"Connection: {connection_key}\n") f.write("-" * 30 + "\n") f.write(f"Total messages: {len(entries)}\n") f.write(f"Duration: {entries[0]['timestamp']} to {entries[-1]['timestamp']}\n\n") transaction = format_imap_transaction(entries) for line in transaction: f.write(line + "\n") f.write("\n" + "=" * 50 + "\n\n") def main(): parser = argparse.ArgumentParser(description='Parse IMAP log files and group transactions by connection') parser.add_argument('input_file', help='Input log file path') parser.add_argument('-o', '--output', default='imap_transactions.txt', help='Output file path (default: imap_transactions.txt)') parser.add_argument('-j', '--json', action='store_true', help='Also output raw data as JSON') parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose output') args = parser.parse_args() if args.verbose: print(f"Reading log file: {args.input_file}") # Parse the log file log_entries = [] imap_line_count = 0 try: with open(args.input_file, 'r', encoding='utf-8') as f: for line_num, line in enumerate(f, 1): if 'Raw IMAP' in line: imap_line_count += 1 parsed_entry = parse_imap_log_line(line) if parsed_entry: log_entries.append(parsed_entry) elif args.verbose: print(f"Warning: Could not parse line {line_num}: {line.strip()}") except FileNotFoundError: print(f"Error: File '{args.input_file}' not found") return 1 except Exception as e: print(f"Error reading file: {e}") return 1 if args.verbose: print(f"Found {imap_line_count} Raw IMAP lines") print(f"Successfully parsed {len(log_entries)} entries") # Group by connection connections = group_by_connection(log_entries) if args.verbose: print(f"Found {len(connections)} unique connections:") for conn_key, entries in connections.items(): print(f" {conn_key}: {len(entries)} messages") # Write output try: write_output_file(connections, args.output) print(f"IMAP transactions written to: {args.output}") # Optionally write JSON output if args.json: json_file = args.output.rsplit('.', 1)[0] + '.json' with open(json_file, 'w', encoding='utf-8') as f: json.dump(connections, f, indent=2, ensure_ascii=False) print(f"Raw data written to: {json_file}") except Exception as e: print(f"Error writing output: {e}") return 1 return 0 if __name__ == "__main__": exit(main())