181 lines
6.0 KiB
TypeScript
181 lines
6.0 KiB
TypeScript
/**
|
|
* SMTP Server Constants
|
|
* This file contains all constants and enums used by the SMTP server
|
|
*/
|
|
|
|
import { SmtpState } from '../interfaces.js';
|
|
|
|
// Re-export SmtpState enum from the main interfaces file
|
|
export { SmtpState };
|
|
|
|
/**
|
|
* SMTP Response Codes
|
|
* Based on RFC 5321 and common SMTP practice
|
|
*/
|
|
export enum SmtpResponseCode {
|
|
// Success codes (2xx)
|
|
SUCCESS = 250, // Requested mail action okay, completed
|
|
SYSTEM_STATUS = 211, // System status, or system help reply
|
|
HELP_MESSAGE = 214, // Help message
|
|
SERVICE_READY = 220, // <domain> Service ready
|
|
SERVICE_CLOSING = 221, // <domain> Service closing transmission channel
|
|
AUTHENTICATION_SUCCESSFUL = 235, // Authentication successful
|
|
OK = 250, // Requested mail action okay, completed
|
|
FORWARD = 251, // User not local; will forward to <forward-path>
|
|
CANNOT_VRFY = 252, // Cannot VRFY user, but will accept message and attempt delivery
|
|
|
|
// Intermediate codes (3xx)
|
|
MORE_INFO_NEEDED = 334, // Server challenge for authentication
|
|
START_MAIL_INPUT = 354, // Start mail input; end with <CRLF>.<CRLF>
|
|
|
|
// Temporary error codes (4xx)
|
|
SERVICE_NOT_AVAILABLE = 421, // <domain> Service not available, closing transmission channel
|
|
MAILBOX_TEMPORARILY_UNAVAILABLE = 450, // Requested mail action not taken: mailbox unavailable
|
|
LOCAL_ERROR = 451, // Requested action aborted: local error in processing
|
|
INSUFFICIENT_STORAGE = 452, // Requested action not taken: insufficient system storage
|
|
TLS_UNAVAILABLE_TEMP = 454, // TLS not available due to temporary reason
|
|
|
|
// Permanent error codes (5xx)
|
|
SYNTAX_ERROR = 500, // Syntax error, command unrecognized
|
|
SYNTAX_ERROR_PARAMETERS = 501, // Syntax error in parameters or arguments
|
|
COMMAND_NOT_IMPLEMENTED = 502, // Command not implemented
|
|
BAD_SEQUENCE = 503, // Bad sequence of commands
|
|
COMMAND_PARAMETER_NOT_IMPLEMENTED = 504, // Command parameter not implemented
|
|
AUTH_REQUIRED = 530, // Authentication required
|
|
AUTH_FAILED = 535, // Authentication credentials invalid
|
|
MAILBOX_UNAVAILABLE = 550, // Requested action not taken: mailbox unavailable
|
|
USER_NOT_LOCAL = 551, // User not local; please try <forward-path>
|
|
EXCEEDED_STORAGE = 552, // Requested mail action aborted: exceeded storage allocation
|
|
MAILBOX_NAME_INVALID = 553, // Requested action not taken: mailbox name not allowed
|
|
TRANSACTION_FAILED = 554, // Transaction failed
|
|
MAIL_RCPT_PARAMETERS_INVALID = 555, // MAIL FROM/RCPT TO parameters not recognized or not implemented
|
|
}
|
|
|
|
/**
|
|
* SMTP Command Types
|
|
*/
|
|
export enum SmtpCommand {
|
|
HELO = 'HELO',
|
|
EHLO = 'EHLO',
|
|
MAIL_FROM = 'MAIL',
|
|
RCPT_TO = 'RCPT',
|
|
DATA = 'DATA',
|
|
RSET = 'RSET',
|
|
NOOP = 'NOOP',
|
|
QUIT = 'QUIT',
|
|
STARTTLS = 'STARTTLS',
|
|
AUTH = 'AUTH',
|
|
HELP = 'HELP',
|
|
VRFY = 'VRFY',
|
|
EXPN = 'EXPN',
|
|
}
|
|
|
|
/**
|
|
* Security log event types
|
|
*/
|
|
export enum SecurityEventType {
|
|
CONNECTION = 'connection',
|
|
AUTHENTICATION = 'authentication',
|
|
COMMAND = 'command',
|
|
DATA = 'data',
|
|
IP_REPUTATION = 'ip_reputation',
|
|
TLS_NEGOTIATION = 'tls_negotiation',
|
|
DKIM = 'dkim',
|
|
SPF = 'spf',
|
|
DMARC = 'dmarc',
|
|
EMAIL_VALIDATION = 'email_validation',
|
|
SPAM = 'spam',
|
|
ACCESS_CONTROL = 'access_control',
|
|
}
|
|
|
|
/**
|
|
* Security log levels
|
|
*/
|
|
export enum SecurityLogLevel {
|
|
DEBUG = 'debug',
|
|
INFO = 'info',
|
|
WARN = 'warn',
|
|
ERROR = 'error',
|
|
}
|
|
|
|
/**
|
|
* SMTP Server Defaults
|
|
*/
|
|
export const SMTP_DEFAULTS = {
|
|
// Default timeouts in milliseconds
|
|
CONNECTION_TIMEOUT: 30000, // 30 seconds
|
|
SOCKET_TIMEOUT: 300000, // 5 minutes
|
|
DATA_TIMEOUT: 60000, // 1 minute
|
|
CLEANUP_INTERVAL: 5000, // 5 seconds
|
|
|
|
// Default limits
|
|
MAX_CONNECTIONS: 100,
|
|
MAX_RECIPIENTS: 100,
|
|
MAX_MESSAGE_SIZE: 10485760, // 10MB
|
|
|
|
// Default ports
|
|
SMTP_PORT: 25,
|
|
SUBMISSION_PORT: 587,
|
|
SECURE_PORT: 465,
|
|
|
|
// Default hostname
|
|
HOSTNAME: 'mail.lossless.one',
|
|
|
|
// CRLF line ending required by SMTP protocol
|
|
CRLF: '\r\n',
|
|
};
|
|
|
|
/**
|
|
* SMTP Command Patterns
|
|
* Regular expressions for parsing SMTP commands
|
|
*/
|
|
export const SMTP_PATTERNS = {
|
|
// Match EHLO/HELO command: "EHLO example.com"
|
|
// Made very permissive to handle various client implementations
|
|
EHLO: /^(?:EHLO|HELO)\s+(.+)$/i,
|
|
|
|
// Match MAIL FROM command: "MAIL FROM:<user@example.com> [PARAM=VALUE]"
|
|
// Made more permissive with whitespace and parameter formats
|
|
MAIL_FROM: /^MAIL\s+FROM\s*:\s*<([^>]*)>((?:\s+[a-zA-Z0-9][a-zA-Z0-9\-]*(?:=[^\s]+)?)*)$/i,
|
|
|
|
// Match RCPT TO command: "RCPT TO:<user@example.com> [PARAM=VALUE]"
|
|
// Made more permissive with whitespace and parameter formats
|
|
RCPT_TO: /^RCPT\s+TO\s*:\s*<([^>]*)>((?:\s+[a-zA-Z0-9][a-zA-Z0-9\-]*(?:=[^\s]+)?)*)$/i,
|
|
|
|
// Match parameter format: "PARAM=VALUE"
|
|
PARAM: /\s+([A-Za-z0-9][A-Za-z0-9\-]*)(?:=([^\s]+))?/g,
|
|
|
|
// Match email address format - made much more permissive
|
|
// This only does very basic format validation to avoid rejecting valid addresses
|
|
// According to RFC 5321, we should accept a wide variety of sender formats
|
|
EMAIL: /^[^\s@]+(@[^\s@]+\.[^\s@]+)?$/,
|
|
|
|
// Match end of DATA marker: \r\n.\r\n or just .\r\n at the start of a line (to handle various client implementations)
|
|
END_DATA: /(\r\n\.\r\n$)|(\n\.\r\n$)|(\r\n\.\n$)|(\n\.\n$)|^\.(\r\n|\n)$/,
|
|
};
|
|
|
|
/**
|
|
* SMTP Extension List
|
|
* These extensions are advertised in the EHLO response
|
|
*/
|
|
export const SMTP_EXTENSIONS = {
|
|
// Basic extensions (RFC 1869)
|
|
PIPELINING: 'PIPELINING',
|
|
SIZE: 'SIZE',
|
|
EIGHTBITMIME: '8BITMIME',
|
|
|
|
// Security extensions
|
|
STARTTLS: 'STARTTLS',
|
|
AUTH: 'AUTH',
|
|
|
|
// Additional extensions
|
|
ENHANCEDSTATUSCODES: 'ENHANCEDSTATUSCODES',
|
|
HELP: 'HELP',
|
|
CHUNKING: 'CHUNKING',
|
|
DSN: 'DSN',
|
|
|
|
// Format an extension with a parameter
|
|
formatExtension(name: string, parameter?: string | number): string {
|
|
return parameter !== undefined ? `${name} ${parameter}` : name;
|
|
}
|
|
}; |