update
This commit is contained in:
@ -6,12 +6,12 @@
|
||||
|
||||
import * as plugins from '../../../plugins.js';
|
||||
import type { ISmtpSession, ISmtpAuth } from './interfaces.js';
|
||||
import type { ISecurityHandler } from './interfaces.js';
|
||||
import type { ISecurityHandler, ISmtpServer } from './interfaces.js';
|
||||
import { SmtpLogger } from './utils/logging.js';
|
||||
import { SecurityEventType, SecurityLogLevel } from './constants.js';
|
||||
import { isValidEmail } from './utils/validation.js';
|
||||
import { getSocketDetails, getTlsDetails } from './utils/helpers.js';
|
||||
import { UnifiedEmailServer } from '../../routing/classes.unified.email.server.js';
|
||||
import { IPReputationChecker } from '../../../security/classes.ipreputationchecker.js';
|
||||
|
||||
/**
|
||||
* Interface for IP denylist entry
|
||||
@ -27,23 +27,14 @@ interface IIpDenylistEntry {
|
||||
*/
|
||||
export class SecurityHandler implements ISecurityHandler {
|
||||
/**
|
||||
* Email server reference
|
||||
* Reference to the SMTP server instance
|
||||
*/
|
||||
private emailServer: UnifiedEmailServer;
|
||||
private smtpServer: ISmtpServer;
|
||||
|
||||
/**
|
||||
* IP reputation service
|
||||
* IP reputation checker service
|
||||
*/
|
||||
private ipReputationService?: any;
|
||||
|
||||
/**
|
||||
* Authentication options
|
||||
*/
|
||||
private authOptions?: {
|
||||
required: boolean;
|
||||
methods: ('PLAIN' | 'LOGIN' | 'OAUTH2')[];
|
||||
validateUser?: (username: string, password: string) => Promise<boolean>;
|
||||
};
|
||||
private ipReputationService: IPReputationChecker;
|
||||
|
||||
/**
|
||||
* Simple in-memory IP denylist
|
||||
@ -51,26 +42,22 @@ export class SecurityHandler implements ISecurityHandler {
|
||||
private ipDenylist: IIpDenylistEntry[] = [];
|
||||
|
||||
/**
|
||||
* Creates a new security handler
|
||||
* @param emailServer - Email server reference
|
||||
* @param ipReputationService - Optional IP reputation service
|
||||
* @param authOptions - Authentication options
|
||||
* Cleanup interval timer
|
||||
*/
|
||||
constructor(
|
||||
emailServer: UnifiedEmailServer,
|
||||
ipReputationService?: any,
|
||||
authOptions?: {
|
||||
required: boolean;
|
||||
methods: ('PLAIN' | 'LOGIN' | 'OAUTH2')[];
|
||||
validateUser?: (username: string, password: string) => Promise<boolean>;
|
||||
}
|
||||
) {
|
||||
this.emailServer = emailServer;
|
||||
this.ipReputationService = ipReputationService;
|
||||
this.authOptions = authOptions;
|
||||
private cleanupInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
/**
|
||||
* Creates a new security handler
|
||||
* @param smtpServer - SMTP server instance
|
||||
*/
|
||||
constructor(smtpServer: ISmtpServer) {
|
||||
this.smtpServer = smtpServer;
|
||||
|
||||
// Initialize IP reputation checker
|
||||
this.ipReputationService = new IPReputationChecker();
|
||||
|
||||
// Clean expired denylist entries periodically
|
||||
setInterval(() => this.cleanExpiredDenylistEntries(), 60000); // Every minute
|
||||
this.cleanupInterval = setInterval(() => this.cleanExpiredDenylistEntries(), 60000); // Every minute
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,18 +82,28 @@ export class SecurityHandler implements ISecurityHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If no reputation service, allow by default
|
||||
// Check with IP reputation service
|
||||
if (!this.ipReputationService) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// Check with IP reputation service
|
||||
const reputationResult = await this.ipReputationService.checkIp(ip);
|
||||
const reputationResult = await this.ipReputationService.checkReputation(ip);
|
||||
|
||||
if (!reputationResult.allowed) {
|
||||
// Block if score is below HIGH_RISK threshold (20) or if it's spam/proxy/tor/vpn
|
||||
const isBlocked = reputationResult.score < 20 ||
|
||||
reputationResult.isSpam ||
|
||||
reputationResult.isTor ||
|
||||
reputationResult.isProxy;
|
||||
|
||||
if (isBlocked) {
|
||||
// Add to local denylist temporarily
|
||||
this.addToDenylist(ip, reputationResult.reason, 3600000); // 1 hour
|
||||
const reason = reputationResult.isSpam ? 'spam' :
|
||||
reputationResult.isTor ? 'tor' :
|
||||
reputationResult.isProxy ? 'proxy' :
|
||||
`low reputation score: ${reputationResult.score}`;
|
||||
this.addToDenylist(ip, reason, 3600000); // 1 hour
|
||||
|
||||
// Log the blocked connection
|
||||
this.logSecurityEvent(
|
||||
@ -114,9 +111,12 @@ export class SecurityHandler implements ISecurityHandler {
|
||||
SecurityLogLevel.WARN,
|
||||
`Connection blocked by reputation service: ${ip}`,
|
||||
{
|
||||
reason: reputationResult.reason,
|
||||
reason,
|
||||
score: reputationResult.score,
|
||||
categories: reputationResult.categories
|
||||
isSpam: reputationResult.isSpam,
|
||||
isTor: reputationResult.isTor,
|
||||
isProxy: reputationResult.isProxy,
|
||||
isVPN: reputationResult.isVPN
|
||||
}
|
||||
);
|
||||
|
||||
@ -130,7 +130,8 @@ export class SecurityHandler implements ISecurityHandler {
|
||||
`IP reputation check passed: ${ip}`,
|
||||
{
|
||||
score: reputationResult.score,
|
||||
categories: reputationResult.categories
|
||||
country: reputationResult.country,
|
||||
org: reputationResult.org
|
||||
}
|
||||
);
|
||||
|
||||
@ -165,8 +166,12 @@ export class SecurityHandler implements ISecurityHandler {
|
||||
* @returns Promise that resolves to true if authenticated
|
||||
*/
|
||||
public async authenticate(session: ISmtpSession, username: string, password: string, method: string): Promise<boolean> {
|
||||
// Get auth options from server
|
||||
const options = this.smtpServer.getOptions();
|
||||
const authOptions = options.auth;
|
||||
|
||||
// Check if authentication is enabled
|
||||
if (!this.authOptions) {
|
||||
if (!authOptions) {
|
||||
this.logSecurityEvent(
|
||||
SecurityEventType.AUTHENTICATION,
|
||||
SecurityLogLevel.WARN,
|
||||
@ -178,7 +183,7 @@ export class SecurityHandler implements ISecurityHandler {
|
||||
}
|
||||
|
||||
// Check if method is supported
|
||||
if (!this.authOptions.methods.includes(method as any)) {
|
||||
if (!authOptions.methods.includes(method as any)) {
|
||||
this.logSecurityEvent(
|
||||
SecurityEventType.AUTHENTICATION,
|
||||
SecurityLogLevel.WARN,
|
||||
@ -205,8 +210,8 @@ export class SecurityHandler implements ISecurityHandler {
|
||||
let authenticated = false;
|
||||
|
||||
// Use custom validation function if provided
|
||||
if (this.authOptions.validateUser) {
|
||||
authenticated = await this.authOptions.validateUser(username, password);
|
||||
if ((authOptions as any).validateUser) {
|
||||
authenticated = await (authOptions as any).validateUser(username, password);
|
||||
} else {
|
||||
// Default behavior - no authentication
|
||||
authenticated = false;
|
||||
@ -339,4 +344,25 @@ export class SecurityHandler implements ISecurityHandler {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources
|
||||
*/
|
||||
public destroy(): void {
|
||||
// Clear the cleanup interval
|
||||
if (this.cleanupInterval) {
|
||||
clearInterval(this.cleanupInterval);
|
||||
this.cleanupInterval = null;
|
||||
}
|
||||
|
||||
// Clear the denylist
|
||||
this.ipDenylist = [];
|
||||
|
||||
// Clean up IP reputation service if it has a destroy method
|
||||
if (this.ipReputationService && typeof (this.ipReputationService as any).destroy === 'function') {
|
||||
(this.ipReputationService as any).destroy();
|
||||
}
|
||||
|
||||
SmtpLogger.debug('SecurityHandler destroyed');
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user