2025-05-21 13:42:12 +00:00

405 lines
11 KiB
TypeScript

/**
* SMTP Server
* Core implementation for the refactored SMTP server
*/
import * as plugins from '../../../plugins.js';
import { SmtpState } from './interfaces.js';
import type { ISmtpServerOptions } from './interfaces.js';
import type { ISmtpServer, ISmtpServerConfig, ISessionManager, IConnectionManager, ICommandHandler, IDataHandler, ITlsHandler, ISecurityHandler } from './interfaces.js';
import { SessionManager } from './session-manager.js';
import { ConnectionManager } from './connection-manager.js';
import { CommandHandler } from './command-handler.js';
import { DataHandler } from './data-handler.js';
import { TlsHandler } from './tls-handler.js';
import { SecurityHandler } from './security-handler.js';
import { SMTP_DEFAULTS } from './constants.js';
import { mergeWithDefaults } from './utils/helpers.js';
import { SmtpLogger } from './utils/logging.js';
import { UnifiedEmailServer } from '../../routing/classes.unified.email.server.js';
/**
* SMTP Server implementation
* The main server class that coordinates all components
*/
export class SmtpServer implements ISmtpServer {
/**
* Email server reference
*/
private emailServer: UnifiedEmailServer;
/**
* Session manager
*/
private sessionManager: ISessionManager;
/**
* Connection manager
*/
private connectionManager: IConnectionManager;
/**
* Command handler
*/
private commandHandler: ICommandHandler;
/**
* Data handler
*/
private dataHandler: IDataHandler;
/**
* TLS handler
*/
private tlsHandler: ITlsHandler;
/**
* Security handler
*/
private securityHandler: ISecurityHandler;
/**
* SMTP server options
*/
private options: ISmtpServerOptions;
/**
* Net server instance
*/
private server: plugins.net.Server | null = null;
/**
* Secure server instance
*/
private secureServer: plugins.tls.Server | null = null;
/**
* Whether the server is running
*/
private running = false;
/**
* Creates a new SMTP server
* @param config - Server configuration
*/
constructor(config: ISmtpServerConfig) {
this.emailServer = config.emailServer;
this.options = mergeWithDefaults(config.options);
// Create components or use provided ones
this.sessionManager = config.sessionManager || new SessionManager({
socketTimeout: this.options.socketTimeout,
connectionTimeout: this.options.connectionTimeout,
cleanupInterval: this.options.cleanupInterval
});
this.securityHandler = config.securityHandler || new SecurityHandler(
this.emailServer,
undefined, // IP reputation service
this.options.auth
);
this.tlsHandler = config.tlsHandler || new TlsHandler(
this.sessionManager,
{
key: this.options.key,
cert: this.options.cert,
ca: this.options.ca
}
);
this.dataHandler = config.dataHandler || new DataHandler(
this.sessionManager,
this.emailServer,
{
size: this.options.size
}
);
this.commandHandler = config.commandHandler || new CommandHandler(
this.sessionManager,
{
hostname: this.options.hostname,
size: this.options.size,
maxRecipients: this.options.maxRecipients,
auth: this.options.auth
},
this.dataHandler,
this.tlsHandler,
this.securityHandler
);
this.connectionManager = config.connectionManager || new ConnectionManager(
this.sessionManager,
(socket, line) => this.commandHandler.processCommand(socket, line),
{
hostname: this.options.hostname,
maxConnections: this.options.maxConnections,
socketTimeout: this.options.socketTimeout
}
);
}
/**
* Start the SMTP server
* @returns Promise that resolves when server is started
*/
public async listen(): Promise<void> {
if (this.running) {
throw new Error('SMTP server is already running');
}
try {
// Create the server
this.server = plugins.net.createServer((socket) => {
// Check IP reputation before handling connection
this.securityHandler.checkIpReputation(socket)
.then(allowed => {
if (allowed) {
this.connectionManager.handleNewConnection(socket);
} else {
// Close connection if IP is not allowed
socket.destroy();
}
})
.catch(error => {
SmtpLogger.error(`IP reputation check error: ${error instanceof Error ? error.message : String(error)}`, {
remoteAddress: socket.remoteAddress,
error: error instanceof Error ? error : new Error(String(error))
});
// Allow connection on error (fail open)
this.connectionManager.handleNewConnection(socket);
});
});
// Set up error handling
this.server.on('error', (err) => {
SmtpLogger.error(`SMTP server error: ${err.message}`, { error: err });
});
// Start listening
await new Promise<void>((resolve, reject) => {
if (!this.server) {
reject(new Error('Server not initialized'));
return;
}
this.server.listen(this.options.port, this.options.host, () => {
SmtpLogger.info(`SMTP server listening on ${this.options.host || '0.0.0.0'}:${this.options.port}`);
resolve();
});
this.server.on('error', reject);
});
// Start secure server if configured
if (this.options.securePort && this.tlsHandler.isTlsEnabled()) {
this.secureServer = this.tlsHandler.createSecureServer();
if (this.secureServer) {
this.secureServer.on('secureConnection', (socket) => {
// Check IP reputation before handling connection
this.securityHandler.checkIpReputation(socket)
.then(allowed => {
if (allowed) {
this.connectionManager.handleNewSecureConnection(socket);
} else {
// Close connection if IP is not allowed
socket.destroy();
}
})
.catch(error => {
SmtpLogger.error(`IP reputation check error: ${error instanceof Error ? error.message : String(error)}`, {
remoteAddress: socket.remoteAddress,
error: error instanceof Error ? error : new Error(String(error))
});
// Allow connection on error (fail open)
this.connectionManager.handleNewSecureConnection(socket);
});
});
this.secureServer.on('error', (err) => {
SmtpLogger.error(`SMTP secure server error: ${err.message}`, { error: err });
});
// Start listening on secure port
await new Promise<void>((resolve, reject) => {
if (!this.secureServer) {
reject(new Error('Secure server not initialized'));
return;
}
this.secureServer.listen(this.options.securePort, this.options.host, () => {
SmtpLogger.info(`SMTP secure server listening on ${this.options.host || '0.0.0.0'}:${this.options.securePort}`);
resolve();
});
this.secureServer.on('error', reject);
});
} else {
SmtpLogger.warn('Failed to create secure server, TLS may not be properly configured');
}
}
this.running = true;
} catch (error) {
SmtpLogger.error(`Failed to start SMTP server: ${error instanceof Error ? error.message : String(error)}`, {
error: error instanceof Error ? error : new Error(String(error))
});
// Clean up on error
this.close();
throw error;
}
}
/**
* Stop the SMTP server
* @returns Promise that resolves when server is stopped
*/
public async close(): Promise<void> {
if (!this.running) {
return;
}
SmtpLogger.info('Stopping SMTP server');
try {
// Close all active connections
this.connectionManager.closeAllConnections();
// Clear all sessions
this.sessionManager.clearAllSessions();
// Close servers
const closePromises: Promise<void>[] = [];
if (this.server) {
closePromises.push(
new Promise<void>((resolve, reject) => {
if (!this.server) {
resolve();
return;
}
this.server.close((err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
})
);
}
if (this.secureServer) {
closePromises.push(
new Promise<void>((resolve, reject) => {
if (!this.secureServer) {
resolve();
return;
}
this.secureServer.close((err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
})
);
}
await Promise.all(closePromises);
this.server = null;
this.secureServer = null;
this.running = false;
SmtpLogger.info('SMTP server stopped');
} catch (error) {
SmtpLogger.error(`Error stopping SMTP server: ${error instanceof Error ? error.message : String(error)}`, {
error: error instanceof Error ? error : new Error(String(error))
});
throw error;
}
}
/**
* Get the session manager
* @returns Session manager instance
*/
public getSessionManager(): ISessionManager {
return this.sessionManager;
}
/**
* Get the connection manager
* @returns Connection manager instance
*/
public getConnectionManager(): IConnectionManager {
return this.connectionManager;
}
/**
* Get the command handler
* @returns Command handler instance
*/
public getCommandHandler(): ICommandHandler {
return this.commandHandler;
}
/**
* Get the data handler
* @returns Data handler instance
*/
public getDataHandler(): IDataHandler {
return this.dataHandler;
}
/**
* Get the TLS handler
* @returns TLS handler instance
*/
public getTlsHandler(): ITlsHandler {
return this.tlsHandler;
}
/**
* Get the security handler
* @returns Security handler instance
*/
public getSecurityHandler(): ISecurityHandler {
return this.securityHandler;
}
/**
* Get the server options
* @returns SMTP server options
*/
public getOptions(): ISmtpServerOptions {
return this.options;
}
/**
* Get the email server reference
* @returns Email server instance
*/
public getEmailServer(): UnifiedEmailServer {
return this.emailServer;
}
/**
* Check if the server is running
* @returns Whether the server is running
*/
public isRunning(): boolean {
return this.running;
}
}