dcrouter/ts/mail/delivery/classes.smtpserver.ts

293 lines
9.7 KiB
TypeScript
Raw Normal View History

2025-05-08 01:13:54 +00:00
import * as plugins from '../../plugins.js';
import * as paths from '../../paths.js';
import { Email } from '../core/classes.email.js';
2025-05-21 00:12:49 +00:00
import type { UnifiedEmailServer } from '../routing/classes.unified.email.server.js';
2025-05-08 01:13:54 +00:00
import { logger } from '../../logger.js';
2025-05-07 20:20:17 +00:00
import {
SecurityLogger,
SecurityLogLevel,
SecurityEventType,
IPReputationChecker,
ReputationThreshold
2025-05-08 01:13:54 +00:00
} from '../../security/index.js';
2024-02-16 13:28:40 +01:00
2025-05-21 02:17:18 +00:00
import type {
ISmtpServerOptions,
ISmtpSession,
EmailProcessingMode
} from './interfaces.js';
import { SmtpState } from './interfaces.js';
2024-02-16 13:28:40 +01:00
2025-05-21 13:42:12 +00:00
// 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
*/
2024-02-16 13:28:40 +01:00
export class SMTPServer {
2025-05-21 13:42:12 +00:00
// Public properties used by existing code
2025-05-21 00:12:49 +00:00
public emailServerRef: UnifiedEmailServer;
2025-05-21 13:42:12 +00:00
// Protected properties for test access
2025-05-21 10:38:22 +00:00
protected server: plugins.net.Server;
2025-05-21 12:52:24 +00:00
protected secureServer?: plugins.tls.Server;
2025-05-21 13:42:12 +00:00
// Original properties maintained for compatibility
private smtpServerOptions: ISmtpServerOptions;
2025-05-21 02:17:18 +00:00
private sessions: Map<plugins.net.Socket | plugins.tls.TLSSocket, ISmtpSession>;
2025-05-21 10:00:06 +00:00
private sessionTimeouts: Map<string, NodeJS.Timeout>;
private hostname: string;
2025-05-21 10:00:06 +00:00
private sessionIdCounter: number = 0;
private connectionCount: number = 0;
2025-05-21 13:42:12 +00:00
private maxConnections: number = 100;
private cleanupInterval?: NodeJS.Timeout;
// New refactored server implementation
private smtpServerImpl: ISmtpServer;
2024-02-16 13:28:40 +01:00
2025-05-21 00:12:49 +00:00
constructor(emailServerRefArg: UnifiedEmailServer, optionsArg: ISmtpServerOptions) {
2025-05-21 13:42:12 +00:00
console.log('SMTPServer instance is being created (using refactored implementation)...');
2024-02-16 13:28:40 +01:00
2025-05-21 13:42:12 +00:00
// Store original arguments and properties for backward compatibility
2025-05-21 00:12:49 +00:00
this.emailServerRef = emailServerRefArg;
2024-02-16 13:28:40 +01:00
this.smtpServerOptions = optionsArg;
this.sessions = new Map();
2025-05-21 10:00:06 +00:00
this.sessionTimeouts = new Map();
2025-05-21 00:12:49 +00:00
this.hostname = optionsArg.hostname || 'mail.lossless.one';
2025-05-21 13:42:12 +00:00
this.maxConnections = optionsArg.maxConnections || 100;
2025-05-21 12:52:24 +00:00
// 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
});
2025-05-21 13:42:12 +00:00
// Create the refactored SMTP server implementation
this.smtpServerImpl = createSmtpServer(emailServerRefArg, optionsArg);
2025-05-21 12:52:24 +00:00
2025-05-21 13:42:12 +00:00
// Initialize server properties to support existing test code
// These will be properly set during the listen() call
this.server = new plugins.net.Server();
2025-05-21 12:52:24 +00:00
if (optionsArg.key && optionsArg.cert) {
2025-05-21 14:28:33 +00:00
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)
});
}
2025-05-21 12:52:24 +00:00
}
2025-05-21 13:42:12 +00:00
// 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--;
});
2025-05-21 10:38:22 +00:00
}
/**
* 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) => {
2025-05-21 13:42:12 +00:00
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;
}
2025-05-21 12:52:24 +00:00
2025-05-21 13:42:12 +00:00
const tlsServer = (this.smtpServerImpl as any).secureServer;
if (tlsServer) {
this.secureServer = tlsServer;
2025-05-21 12:52:24 +00:00
}
2025-05-21 13:42:12 +00:00
resolve();
})
.catch(err => {
logger.log('error', `Failed to start SMTP server: ${err.message}`, {
stack: err.stack
});
reject(err);
2025-05-21 12:52:24 +00:00
});
2025-05-21 10:38:22 +00:00
});
}
/**
* Stop the SMTP server
* @returns A promise that resolves when the server has stopped
*/
public close(): Promise<void> {
return new Promise<void>((resolve, reject) => {
2025-05-21 13:42:12 +00:00
this.smtpServerImpl.close()
2025-05-21 12:52:24 +00:00
.then(() => {
2025-05-21 13:42:12 +00:00
// Clean up legacy resources
this.sessions.clear();
for (const timeoutId of this.sessionTimeouts.values()) {
clearTimeout(timeoutId);
2025-05-21 12:52:24 +00:00
}
2025-05-21 13:42:12 +00:00
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);
2025-05-21 12:52:24 +00:00
});
2025-05-21 10:38:22 +00:00
});
2024-02-16 13:28:40 +01:00
}
2025-05-21 10:00:06 +00:00
/**
2025-05-21 13:42:12 +00:00
* @deprecated Use the refactored implementation directly
* Maintained for backward compatibility
2025-05-21 10:00:06 +00:00
*/
2025-05-21 13:42:12 +00:00
private handleNewConnection(socket: plugins.net.Socket): void {
logger.log('warn', 'Using deprecated handleNewConnection method');
2025-05-21 10:00:06 +00:00
}
/**
2025-05-21 13:42:12 +00:00
* @deprecated Use the refactored implementation directly
* Maintained for backward compatibility
2025-05-21 10:00:06 +00:00
*/
2025-05-21 13:42:12 +00:00
private handleNewSecureConnection(socket: plugins.tls.TLSSocket): void {
logger.log('warn', 'Using deprecated handleNewSecureConnection method');
2025-05-21 10:00:06 +00:00
}
/**
2025-05-21 13:42:12 +00:00
* @deprecated Use the refactored implementation directly
* Maintained for backward compatibility
2025-05-21 10:00:06 +00:00
*/
2025-05-21 13:42:12 +00:00
private cleanupIdleSessions(): void {
// This is now handled by the session manager in the refactored implementation
2025-05-21 10:00:06 +00:00
}
2025-05-21 12:52:24 +00:00
/**
2025-05-21 13:42:12 +00:00
* @deprecated Use the refactored implementation directly
* Maintained for backward compatibility
2025-05-21 12:52:24 +00:00
*/
2025-05-21 13:42:12 +00:00
private generateSessionId(): string {
return `${Date.now()}-${++this.sessionIdCounter}`;
2025-05-21 12:52:24 +00:00
}
2025-05-21 10:00:06 +00:00
/**
2025-05-21 13:42:12 +00:00
* @deprecated Use the refactored implementation directly
* Maintained for backward compatibility
2025-05-21 10:00:06 +00:00
*/
2025-05-21 13:42:12 +00:00
private removeSession(socket: plugins.net.Socket | plugins.tls.TLSSocket): void {
// This is now handled by the session manager in the refactored implementation
2025-05-21 10:00:06 +00:00
}
2025-05-21 13:42:12 +00:00
2025-05-21 12:52:24 +00:00
/**
2025-05-21 13:42:12 +00:00
* @deprecated Use the refactored implementation directly
* Maintained for backward compatibility
2025-05-21 12:52:24 +00:00
*/
2025-05-21 13:42:12 +00:00
private processCommand(socket: plugins.net.Socket | plugins.tls.TLSSocket, line: string): void {
// This is now handled by the command handler in the refactored implementation
2025-05-21 12:52:24 +00:00
}
/**
2025-05-21 13:42:12 +00:00
* @deprecated Use the refactored implementation directly
* Maintained for backward compatibility
2025-05-21 12:52:24 +00:00
*/
2025-05-21 13:42:12 +00:00
private handleDataChunk(socket: plugins.net.Socket | plugins.tls.TLSSocket, chunk: string): void {
// This is now handled by the data handler in the refactored implementation
2025-05-21 12:52:24 +00:00
}
/**
2025-05-21 13:42:12 +00:00
* @deprecated Use the refactored implementation directly
* Maintained for backward compatibility
2025-05-21 12:52:24 +00:00
*/
private sendResponse(socket: plugins.net.Socket | plugins.tls.TLSSocket, response: string): void {
try {
socket.write(`${response}\r\n`);
} catch (error) {
2025-05-21 13:42:12 +00:00
logger.log('error', `Error sending response: ${error instanceof Error ? error.message : 'Unknown error'}`, {
response,
remoteAddress: socket.remoteAddress,
remotePort: socket.remotePort
});
}
}
2025-05-21 13:42:12 +00:00
2025-05-21 12:52:24 +00:00
/**
2025-05-21 13:42:12 +00:00
* Get the current active connection count
* @returns Number of active connections
2025-05-21 12:52:24 +00:00
*/
2025-05-21 13:42:12 +00:00
public getConnectionCount(): number {
return this.connectionCount;
}
2025-05-21 13:42:12 +00:00
2025-05-21 12:52:24 +00:00
/**
2025-05-21 13:42:12 +00:00
* Get the refactored SMTP server implementation
* This provides access to the new implementation for future use
2025-05-21 12:52:24 +00:00
*/
2025-05-21 13:42:12 +00:00
public getSmtpServerImpl(): ISmtpServer {
return this.smtpServerImpl;
}
}