293 lines
9.7 KiB
TypeScript
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;
|
|
}
|
|
} |