/** * SMTP Helper Functions * Provides utility functions for SMTP server implementation */ import * as plugins from '../../../../plugins.js'; import { SMTP_DEFAULTS } from '../constants.js'; import type { ISmtpSession, ISmtpServerOptions } from '../../interfaces.js'; /** * Formats a multi-line SMTP response according to RFC 5321 * @param code - Response code * @param lines - Response lines * @returns Formatted SMTP response */ export function formatMultilineResponse(code: number, lines: string[]): string { if (!lines || lines.length === 0) { return `${code} `; } if (lines.length === 1) { return `${code} ${lines[0]}`; } let response = ''; for (let i = 0; i < lines.length - 1; i++) { response += `${code}-${lines[i]}${SMTP_DEFAULTS.CRLF}`; } response += `${code} ${lines[lines.length - 1]}`; return response; } /** * Generates a unique session ID * @returns Unique session ID */ export function generateSessionId(): string { return `${Date.now()}-${Math.floor(Math.random() * 10000)}`; } /** * Safely parses an integer from string with a default value * @param value - String value to parse * @param defaultValue - Default value if parsing fails * @returns Parsed integer or default value */ export function safeParseInt(value: string | undefined, defaultValue: number): number { if (!value) { return defaultValue; } const parsed = parseInt(value, 10); return isNaN(parsed) ? defaultValue : parsed; } /** * Safely gets the socket details * @param socket - Socket to get details from * @returns Socket details object */ export function getSocketDetails(socket: plugins.net.Socket | plugins.tls.TLSSocket): { remoteAddress: string; remotePort: number; remoteFamily: string; localAddress: string; localPort: number; encrypted: boolean; } { return { remoteAddress: socket.remoteAddress || 'unknown', remotePort: socket.remotePort || 0, remoteFamily: socket.remoteFamily || 'unknown', localAddress: socket.localAddress || 'unknown', localPort: socket.localPort || 0, encrypted: socket instanceof plugins.tls.TLSSocket }; } /** * Gets TLS details if socket is TLS * @param socket - Socket to get TLS details from * @returns TLS details or undefined if not TLS */ export function getTlsDetails(socket: plugins.net.Socket | plugins.tls.TLSSocket): { protocol?: string; cipher?: string; authorized?: boolean; } | undefined { if (!(socket instanceof plugins.tls.TLSSocket)) { return undefined; } return { protocol: socket.getProtocol(), cipher: socket.getCipher()?.name, authorized: socket.authorized }; } /** * Merges default options with provided options * @param options - User provided options * @returns Merged options with defaults */ export function mergeWithDefaults(options: Partial): ISmtpServerOptions { return { port: options.port || SMTP_DEFAULTS.SMTP_PORT, key: options.key || '', cert: options.cert || '', hostname: options.hostname || SMTP_DEFAULTS.HOSTNAME, host: options.host, securePort: options.securePort, ca: options.ca, maxSize: options.size || SMTP_DEFAULTS.MAX_MESSAGE_SIZE, maxConnections: options.maxConnections || SMTP_DEFAULTS.MAX_CONNECTIONS, socketTimeout: options.socketTimeout || SMTP_DEFAULTS.SOCKET_TIMEOUT, connectionTimeout: options.connectionTimeout || SMTP_DEFAULTS.CONNECTION_TIMEOUT, cleanupInterval: options.cleanupInterval || SMTP_DEFAULTS.CLEANUP_INTERVAL, maxRecipients: options.maxRecipients || SMTP_DEFAULTS.MAX_RECIPIENTS, size: options.size || SMTP_DEFAULTS.MAX_MESSAGE_SIZE, dataTimeout: options.dataTimeout || SMTP_DEFAULTS.DATA_TIMEOUT, auth: options.auth, }; } /** * Creates a text response formatter for the SMTP server * @param socket - Socket to send responses to * @returns Function to send formatted response */ export function createResponseFormatter(socket: plugins.net.Socket | plugins.tls.TLSSocket): (response: string) => void { return (response: string): void => { try { socket.write(`${response}${SMTP_DEFAULTS.CRLF}`); console.log(`→ ${response}`); } catch (error) { console.error(`Error sending response: ${error instanceof Error ? error.message : String(error)}`); socket.destroy(); } }; } /** * Extracts SMTP command name from a command line * @param commandLine - Full command line * @returns Command name in uppercase */ export function extractCommandName(commandLine: string): string { const parts = commandLine.trim().split(' '); return parts[0].toUpperCase(); } /** * Extracts SMTP command arguments from a command line * @param commandLine - Full command line * @returns Arguments string */ export function extractCommandArgs(commandLine: string): string { const firstSpace = commandLine.indexOf(' '); if (firstSpace === -1) { return ''; } return commandLine.substring(firstSpace + 1).trim(); } /** * Sanitizes data for logging (hides sensitive info) * @param data - Data to sanitize * @returns Sanitized data */ export function sanitizeForLogging(data: any): any { if (!data) { return data; } if (typeof data !== 'object') { return data; } const result: any = Array.isArray(data) ? [] : {}; for (const key in data) { if (Object.prototype.hasOwnProperty.call(data, key)) { // Sanitize sensitive fields if (key.toLowerCase().includes('password') || key.toLowerCase().includes('token') || key.toLowerCase().includes('secret') || key.toLowerCase().includes('credential')) { result[key] = '********'; } else if (typeof data[key] === 'object' && data[key] !== null) { result[key] = sanitizeForLogging(data[key]); } else { result[key] = data[key]; } } } return result; }