246 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			246 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | /** | ||
|  |  * SMTP Logging Utilities | ||
|  |  * Provides structured logging for SMTP server components | ||
|  |  */ | ||
|  | 
 | ||
|  | import * as plugins from '../../../../plugins.ts'; | ||
|  | import { logger } from '../../../../logger.ts'; | ||
|  | import { SecurityLogLevel, SecurityEventType } from '../constants.ts'; | ||
|  | import type { ISmtpSession } from '../interfaces.ts'; | ||
|  | 
 | ||
|  | /** | ||
|  |  * SMTP connection metadata to include in logs | ||
|  |  */ | ||
|  | export interface IConnectionMetadata { | ||
|  |   remoteAddress?: string; | ||
|  |   remotePort?: number; | ||
|  |   socketId?: string; | ||
|  |   secure?: boolean; | ||
|  |   sessionId?: string; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Log levels for SMTP server | ||
|  |  */ | ||
|  | export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; | ||
|  | 
 | ||
|  | /** | ||
|  |  * Options for SMTP log | ||
|  |  */ | ||
|  | export interface ISmtpLogOptions { | ||
|  |   level?: LogLevel; | ||
|  |   sessionId?: string; | ||
|  |   sessionState?: string; | ||
|  |   remoteAddress?: string; | ||
|  |   remotePort?: number; | ||
|  |   command?: string; | ||
|  |   error?: Error; | ||
|  |   [key: string]: any; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * SMTP logger - provides structured logging for SMTP server | ||
|  |  */ | ||
|  | export class SmtpLogger { | ||
|  |   /** | ||
|  |    * Log a message with context | ||
|  |    * @param level - Log level | ||
|  |    * @param message - Log message | ||
|  |    * @param options - Additional log options | ||
|  |    */ | ||
|  |   public static log(level: LogLevel, message: string, options: ISmtpLogOptions = {}): void { | ||
|  |     // Extract error information if provided
 | ||
|  |     const errorInfo = options.error ? { | ||
|  |       errorMessage: options.error.message, | ||
|  |       errorStack: options.error.stack, | ||
|  |       errorName: options.error.name | ||
|  |     } : {}; | ||
|  |      | ||
|  |     // Structure log data
 | ||
|  |     const logData = { | ||
|  |       component: 'smtp-server', | ||
|  |       ...options, | ||
|  |       ...errorInfo | ||
|  |     }; | ||
|  |      | ||
|  |     // Remove error from log data to avoid duplication
 | ||
|  |     if (logData.error) { | ||
|  |       delete logData.error; | ||
|  |     } | ||
|  |      | ||
|  |     // Log through the main logger
 | ||
|  |     logger.log(level, message, logData); | ||
|  |      | ||
|  |     // Also console log for immediate visibility during development
 | ||
|  |     if (level === 'error' || level === 'warn') { | ||
|  |       console[level](`[SMTP] ${message}`, logData); | ||
|  |     } | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Log debug level message | ||
|  |    * @param message - Log message | ||
|  |    * @param options - Additional log options | ||
|  |    */ | ||
|  |   public static debug(message: string, options: ISmtpLogOptions = {}): void { | ||
|  |     this.log('debug', message, options); | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Log info level message | ||
|  |    * @param message - Log message | ||
|  |    * @param options - Additional log options | ||
|  |    */ | ||
|  |   public static info(message: string, options: ISmtpLogOptions = {}): void { | ||
|  |     this.log('info', message, options); | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Log warning level message | ||
|  |    * @param message - Log message | ||
|  |    * @param options - Additional log options | ||
|  |    */ | ||
|  |   public static warn(message: string, options: ISmtpLogOptions = {}): void { | ||
|  |     this.log('warn', message, options); | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Log error level message | ||
|  |    * @param message - Log message | ||
|  |    * @param options - Additional log options | ||
|  |    */ | ||
|  |   public static error(message: string, options: ISmtpLogOptions = {}): void { | ||
|  |     this.log('error', message, options); | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Log command received from client | ||
|  |    * @param command - The command string | ||
|  |    * @param socket - The client socket | ||
|  |    * @param session - The SMTP session | ||
|  |    */ | ||
|  |   public static logCommand(command: string, socket: plugins.net.Socket | plugins.tls.TLSSocket, session?: ISmtpSession): void { | ||
|  |     const clientInfo = { | ||
|  |       remoteAddress: socket.remoteAddress, | ||
|  |       remotePort: socket.remotePort, | ||
|  |       secure: socket instanceof plugins.tls.TLSSocket, | ||
|  |       sessionId: session?.id, | ||
|  |       sessionState: session?.state | ||
|  |     }; | ||
|  |      | ||
|  |     this.info(`Command received: ${command}`, { | ||
|  |       ...clientInfo, | ||
|  |       command: command.split(' ')[0]?.toUpperCase() | ||
|  |     }); | ||
|  |      | ||
|  |     // Also log to console for easy debugging
 | ||
|  |     console.log(`← ${command}`); | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Log response sent to client | ||
|  |    * @param response - The response string | ||
|  |    * @param socket - The client socket | ||
|  |    */ | ||
|  |   public static logResponse(response: string, socket: plugins.net.Socket | plugins.tls.TLSSocket): void { | ||
|  |     const clientInfo = { | ||
|  |       remoteAddress: socket.remoteAddress, | ||
|  |       remotePort: socket.remotePort, | ||
|  |       secure: socket instanceof plugins.tls.TLSSocket | ||
|  |     }; | ||
|  |      | ||
|  |     // Get the response code from the beginning of the response
 | ||
|  |     const responseCode = response.substring(0, 3); | ||
|  |      | ||
|  |     // Log different levels based on response code
 | ||
|  |     if (responseCode.startsWith('2') || responseCode.startsWith('3')) { | ||
|  |       this.debug(`Response sent: ${response}`, clientInfo); | ||
|  |     } else if (responseCode.startsWith('4')) { | ||
|  |       this.warn(`Temporary error response: ${response}`, clientInfo); | ||
|  |     } else if (responseCode.startsWith('5')) { | ||
|  |       this.error(`Permanent error response: ${response}`, clientInfo); | ||
|  |     } | ||
|  |      | ||
|  |     // Also log to console for easy debugging
 | ||
|  |     console.log(`→ ${response}`); | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Log client connection event | ||
|  |    * @param socket - The client socket | ||
|  |    * @param eventType - Type of connection event (connect, close, error) | ||
|  |    * @param session - The SMTP session | ||
|  |    * @param error - Optional error object for error events | ||
|  |    */ | ||
|  |   public static logConnection( | ||
|  |     socket: plugins.net.Socket | plugins.tls.TLSSocket, | ||
|  |     eventType: 'connect' | 'close' | 'error', | ||
|  |     session?: ISmtpSession, | ||
|  |     error?: Error | ||
|  |   ): void { | ||
|  |     const clientInfo = { | ||
|  |       remoteAddress: socket.remoteAddress, | ||
|  |       remotePort: socket.remotePort, | ||
|  |       secure: socket instanceof plugins.tls.TLSSocket, | ||
|  |       sessionId: session?.id, | ||
|  |       sessionState: session?.state | ||
|  |     }; | ||
|  |      | ||
|  |     switch (eventType) { | ||
|  |       case 'connect': | ||
|  |         this.info(`New ${clientInfo.secure ? 'secure ' : ''}connection from ${clientInfo.remoteAddress}:${clientInfo.remotePort}`, clientInfo); | ||
|  |         break; | ||
|  |          | ||
|  |       case 'close': | ||
|  |         this.info(`Connection closed from ${clientInfo.remoteAddress}:${clientInfo.remotePort}`, clientInfo); | ||
|  |         break; | ||
|  |          | ||
|  |       case 'error': | ||
|  |         this.error(`Connection error from ${clientInfo.remoteAddress}:${clientInfo.remotePort}`, { | ||
|  |           ...clientInfo, | ||
|  |           error | ||
|  |         }); | ||
|  |         break; | ||
|  |     } | ||
|  |   } | ||
|  |    | ||
|  |   /** | ||
|  |    * Log security event | ||
|  |    * @param level - Security log level | ||
|  |    * @param type - Security event type | ||
|  |    * @param message - Log message | ||
|  |    * @param details - Event details | ||
|  |    * @param ipAddress - Client IP address | ||
|  |    * @param domain - Optional domain involved | ||
|  |    * @param success - Whether the security check was successful | ||
|  |    */ | ||
|  |   public static logSecurityEvent( | ||
|  |     level: SecurityLogLevel, | ||
|  |     type: SecurityEventType, | ||
|  |     message: string, | ||
|  |     details: Record<string, any>, | ||
|  |     ipAddress?: string, | ||
|  |     domain?: string, | ||
|  |     success?: boolean | ||
|  |   ): void { | ||
|  |     // Map security log level to system log level
 | ||
|  |     const logLevel: LogLevel = level === SecurityLogLevel.DEBUG ? 'debug' : | ||
|  |                                level === SecurityLogLevel.INFO ? 'info' : | ||
|  |                                level === SecurityLogLevel.WARN ? 'warn' : 'error'; | ||
|  |      | ||
|  |     // Log the security event
 | ||
|  |     this.log(logLevel, message, { | ||
|  |       component: 'smtp-security', | ||
|  |       eventType: type, | ||
|  |       success, | ||
|  |       ipAddress, | ||
|  |       domain, | ||
|  |       ...details | ||
|  |     }); | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  |  * Default instance for backward compatibility | ||
|  |  */ | ||
|  | export const smtpLogger = SmtpLogger; |