dcrouter/ts/mail/delivery/classes.smtpserver.ts
2025-05-21 14:28:33 +00:00

293 lines
9.7 KiB
TypeScript

import * as plugins from '../../plugins.js';
import * as paths from '../../paths.js';
import { Email } from '../core/classes.email.js';
import type { UnifiedEmailServer } from '../routing/classes.unified.email.server.js';
import { logger } from '../../logger.js';
import {
SecurityLogger,
SecurityLogLevel,
SecurityEventType,
IPReputationChecker,
ReputationThreshold
} from '../../security/index.js';
import type {
ISmtpServerOptions,
ISmtpSession,
EmailProcessingMode
} from './interfaces.js';
import { SmtpState } from './interfaces.js';
// Import refactored SMTP server components
import {
SmtpServer,
createSmtpServer,
type ISmtpServer
} from './smtpserver/index.js';
/**
* Legacy SMTP Server implementation that uses the refactored modular version
* Maintains the original API for backward compatibility
*/
export class SMTPServer {
// Public properties used by existing code
public emailServerRef: UnifiedEmailServer;
// Protected properties for test access
protected server: plugins.net.Server;
protected secureServer?: plugins.tls.Server;
// Original properties maintained for compatibility
private smtpServerOptions: ISmtpServerOptions;
private sessions: Map<plugins.net.Socket | plugins.tls.TLSSocket, ISmtpSession>;
private sessionTimeouts: Map<string, NodeJS.Timeout>;
private hostname: string;
private sessionIdCounter: number = 0;
private connectionCount: number = 0;
private maxConnections: number = 100;
private cleanupInterval?: NodeJS.Timeout;
// New refactored server implementation
private smtpServerImpl: ISmtpServer;
constructor(emailServerRefArg: UnifiedEmailServer, optionsArg: ISmtpServerOptions) {
console.log('SMTPServer instance is being created (using refactored implementation)...');
// Store original arguments and properties for backward compatibility
this.emailServerRef = emailServerRefArg;
this.smtpServerOptions = optionsArg;
this.sessions = new Map();
this.sessionTimeouts = new Map();
this.hostname = optionsArg.hostname || 'mail.lossless.one';
this.maxConnections = optionsArg.maxConnections || 100;
// Log enhanced server configuration
const socketTimeout = optionsArg.socketTimeout || 300000;
const connectionTimeout = optionsArg.connectionTimeout || 30000;
const cleanupFrequency = optionsArg.cleanupInterval || 5000;
logger.log('info', 'SMTP server configuration', {
hostname: this.hostname,
maxConnections: this.maxConnections,
socketTimeout: socketTimeout,
connectionTimeout: connectionTimeout,
cleanupInterval: cleanupFrequency,
tlsEnabled: !!(optionsArg.key && optionsArg.cert),
starttlsEnabled: !!(optionsArg.key && optionsArg.cert),
securePort: optionsArg.securePort
});
// Create the refactored SMTP server implementation
this.smtpServerImpl = createSmtpServer(emailServerRefArg, optionsArg);
// Initialize server properties to support existing test code
// These will be properly set during the listen() call
this.server = new plugins.net.Server();
if (optionsArg.key && optionsArg.cert) {
try {
// Convert certificates to Buffer format for Node.js TLS
// This helps prevent ASN.1 encoding issues when Node parses the certificates
const key = Buffer.from(optionsArg.key.trim());
const cert = Buffer.from(optionsArg.cert.trim());
const ca = optionsArg.ca ? Buffer.from(optionsArg.ca.trim()) : undefined;
logger.log('debug', 'Creating TLS server with certificates', {
keyBufferLength: key.length,
certBufferLength: cert.length,
caBufferLength: ca ? ca.length : 0
});
// TLS configuration for secure connections
const tlsOptions: plugins.tls.TlsOptions = {
key: key,
cert: cert,
ca: ca,
// Recommended security options
minVersion: 'TLSv1.2',
honorCipherOrder: true,
// Allow self-signed certificates for test environments
rejectUnauthorized: false,
// Enable session reuse for better performance
sessionTimeout: 300,
// Add cipher suites for better compatibility with clients
ciphers: 'HIGH:!aNULL:!MD5:!RC4',
// Allow client-initiated renegotiation for SMTP
allowRenegotiation: true
};
this.secureServer = plugins.tls.createServer(tlsOptions);
logger.log('info', 'TLS server created successfully');
} catch (error) {
logger.log('error', `Failed to create secure server: ${error instanceof Error ? error.message : String(error)}`, {
error: error instanceof Error ? error.stack : String(error)
});
}
}
// Set up session events to maintain legacy behavior
const sessionManager = this.smtpServerImpl.getSessionManager();
// Track sessions for backward compatibility
sessionManager.on('created', (session, socket) => {
this.sessions.set(socket, session);
this.connectionCount++;
});
sessionManager.on('completed', (session, socket) => {
this.sessions.delete(socket);
this.connectionCount--;
});
}
/**
* Start the SMTP server and listen on the specified port
* @returns A promise that resolves when the server is listening
*/
public listen(): Promise<void> {
return new Promise<void>((resolve, reject) => {
this.smtpServerImpl.listen()
.then(() => {
// Get created servers for test compatibility
// Get the actual server instances for backward compatibility
const netServer = (this.smtpServerImpl as any).server;
if (netServer) {
this.server = netServer;
}
const tlsServer = (this.smtpServerImpl as any).secureServer;
if (tlsServer) {
this.secureServer = tlsServer;
}
resolve();
})
.catch(err => {
logger.log('error', `Failed to start SMTP server: ${err.message}`, {
stack: err.stack
});
reject(err);
});
});
}
/**
* Stop the SMTP server
* @returns A promise that resolves when the server has stopped
*/
public close(): Promise<void> {
return new Promise<void>((resolve, reject) => {
this.smtpServerImpl.close()
.then(() => {
// Clean up legacy resources
this.sessions.clear();
for (const timeoutId of this.sessionTimeouts.values()) {
clearTimeout(timeoutId);
}
this.sessionTimeouts.clear();
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
this.cleanupInterval = undefined;
}
resolve();
})
.catch(err => {
logger.log('error', `Failed to stop SMTP server: ${err.message}`, {
stack: err.stack
});
reject(err);
});
});
}
/**
* @deprecated Use the refactored implementation directly
* Maintained for backward compatibility
*/
private handleNewConnection(socket: plugins.net.Socket): void {
logger.log('warn', 'Using deprecated handleNewConnection method');
}
/**
* @deprecated Use the refactored implementation directly
* Maintained for backward compatibility
*/
private handleNewSecureConnection(socket: plugins.tls.TLSSocket): void {
logger.log('warn', 'Using deprecated handleNewSecureConnection method');
}
/**
* @deprecated Use the refactored implementation directly
* Maintained for backward compatibility
*/
private cleanupIdleSessions(): void {
// This is now handled by the session manager in the refactored implementation
}
/**
* @deprecated Use the refactored implementation directly
* Maintained for backward compatibility
*/
private generateSessionId(): string {
return `${Date.now()}-${++this.sessionIdCounter}`;
}
/**
* @deprecated Use the refactored implementation directly
* Maintained for backward compatibility
*/
private removeSession(socket: plugins.net.Socket | plugins.tls.TLSSocket): void {
// This is now handled by the session manager in the refactored implementation
}
/**
* @deprecated Use the refactored implementation directly
* Maintained for backward compatibility
*/
private processCommand(socket: plugins.net.Socket | plugins.tls.TLSSocket, line: string): void {
// This is now handled by the command handler in the refactored implementation
}
/**
* @deprecated Use the refactored implementation directly
* Maintained for backward compatibility
*/
private handleDataChunk(socket: plugins.net.Socket | plugins.tls.TLSSocket, chunk: string): void {
// This is now handled by the data handler in the refactored implementation
}
/**
* @deprecated Use the refactored implementation directly
* Maintained for backward compatibility
*/
private sendResponse(socket: plugins.net.Socket | plugins.tls.TLSSocket, response: string): void {
try {
socket.write(`${response}\r\n`);
} catch (error) {
logger.log('error', `Error sending response: ${error instanceof Error ? error.message : 'Unknown error'}`, {
response,
remoteAddress: socket.remoteAddress,
remotePort: socket.remotePort
});
}
}
/**
* Get the current active connection count
* @returns Number of active connections
*/
public getConnectionCount(): number {
return this.connectionCount;
}
/**
* Get the refactored SMTP server implementation
* This provides access to the new implementation for future use
*/
public getSmtpServerImpl(): ISmtpServer {
return this.smtpServerImpl;
}
}