fix(core): update
This commit is contained in:
191
ts/mta/mta.classes.smtpserver.ts
Normal file
191
ts/mta/mta.classes.smtpserver.ts
Normal file
@ -0,0 +1,191 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
import { Email } from './mta.classes.email.js';
|
||||
import type { MTA } from './mta.classes.mta.js';
|
||||
|
||||
export interface ISmtpServerOptions {
|
||||
port: number;
|
||||
key: string;
|
||||
cert: string;
|
||||
}
|
||||
|
||||
export class SMTPServer {
|
||||
public mtaRef: MTA;
|
||||
private smtpServerOptions: ISmtpServerOptions;
|
||||
private server: plugins.net.Server;
|
||||
private emailBufferStringMap: Map<plugins.net.Socket, string>;
|
||||
|
||||
constructor(mtaRefArg: MTA, optionsArg: ISmtpServerOptions) {
|
||||
console.log('SMTPServer instance is being created...');
|
||||
|
||||
this.mtaRef = mtaRefArg;
|
||||
this.smtpServerOptions = optionsArg;
|
||||
this.emailBufferStringMap = new Map();
|
||||
|
||||
this.server = plugins.net.createServer((socket) => {
|
||||
console.log('New connection established...');
|
||||
|
||||
socket.write('220 mta.lossless.one ESMTP Postfix\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
this.processData(socket, data);
|
||||
});
|
||||
|
||||
socket.on('end', () => {
|
||||
console.log('Socket closed. Deleting related emailBuffer...');
|
||||
socket.destroy();
|
||||
this.emailBufferStringMap.delete(socket);
|
||||
});
|
||||
|
||||
socket.on('error', () => {
|
||||
console.error('Socket error occurred. Deleting related emailBuffer...');
|
||||
socket.destroy();
|
||||
this.emailBufferStringMap.delete(socket);
|
||||
});
|
||||
|
||||
socket.on('close', () => {
|
||||
console.log('Connection was closed by the client');
|
||||
socket.destroy();
|
||||
this.emailBufferStringMap.delete(socket);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private startTLS(socket: plugins.net.Socket) {
|
||||
const secureContext = plugins.tls.createSecureContext({
|
||||
key: this.smtpServerOptions.key,
|
||||
cert: this.smtpServerOptions.cert,
|
||||
});
|
||||
|
||||
const tlsSocket = new plugins.tls.TLSSocket(socket, {
|
||||
secureContext: secureContext,
|
||||
isServer: true,
|
||||
});
|
||||
|
||||
tlsSocket.on('secure', () => {
|
||||
console.log('Connection secured.');
|
||||
this.emailBufferStringMap.set(tlsSocket, this.emailBufferStringMap.get(socket) || '');
|
||||
this.emailBufferStringMap.delete(socket);
|
||||
});
|
||||
|
||||
// Use the same handler for the 'data' event as for the unsecured socket.
|
||||
tlsSocket.on('data', (data: Buffer) => {
|
||||
this.processData(tlsSocket, Buffer.from(data));
|
||||
});
|
||||
|
||||
tlsSocket.on('end', () => {
|
||||
console.log('TLS socket closed. Deleting related emailBuffer...');
|
||||
this.emailBufferStringMap.delete(tlsSocket);
|
||||
});
|
||||
|
||||
tlsSocket.on('error', (err) => {
|
||||
console.error('TLS socket error occurred. Deleting related emailBuffer...');
|
||||
this.emailBufferStringMap.delete(tlsSocket);
|
||||
});
|
||||
}
|
||||
|
||||
private processData(socket: plugins.net.Socket | plugins.tls.TLSSocket, data: Buffer) {
|
||||
const dataString = data.toString();
|
||||
console.log(`Received data:`);
|
||||
console.log(`${dataString}`)
|
||||
|
||||
if (dataString.startsWith('EHLO')) {
|
||||
socket.write('250-mta.lossless.one Hello\r\n250 STARTTLS\r\n');
|
||||
} else if (dataString.startsWith('MAIL FROM')) {
|
||||
socket.write('250 Ok\r\n');
|
||||
} else if (dataString.startsWith('RCPT TO')) {
|
||||
socket.write('250 Ok\r\n');
|
||||
} else if (dataString.startsWith('STARTTLS')) {
|
||||
socket.write('220 Ready to start TLS\r\n');
|
||||
this.startTLS(socket);
|
||||
} else if (dataString.startsWith('DATA')) {
|
||||
socket.write('354 End data with <CR><LF>.<CR><LF>\r\n');
|
||||
let emailBuffer = this.emailBufferStringMap.get(socket);
|
||||
if (!emailBuffer) {
|
||||
this.emailBufferStringMap.set(socket, '');
|
||||
}
|
||||
} else if (dataString.startsWith('QUIT')) {
|
||||
socket.write('221 Bye\r\n');
|
||||
console.log('Received QUIT command, closing the socket...');
|
||||
socket.destroy();
|
||||
this.parseEmail(socket);
|
||||
} else {
|
||||
let emailBuffer = this.emailBufferStringMap.get(socket);
|
||||
if (typeof emailBuffer === 'string') {
|
||||
emailBuffer += dataString;
|
||||
this.emailBufferStringMap.set(socket, emailBuffer);
|
||||
}
|
||||
socket.write('250 Ok\r\n');
|
||||
}
|
||||
|
||||
if (dataString.endsWith('\r\n.\r\n') ) { // End of data
|
||||
console.log('Received end of data.');
|
||||
}
|
||||
}
|
||||
|
||||
private async parseEmail(socket: plugins.net.Socket | plugins.tls.TLSSocket) {
|
||||
let emailData = this.emailBufferStringMap.get(socket);
|
||||
// lets strip the end sequence
|
||||
emailData = emailData?.replace(/\r\n\.\r\n$/, '');
|
||||
|
||||
plugins.smartfile.fs.ensureDirSync(paths.receivedEmailsDir);
|
||||
plugins.smartfile.memory.toFsSync(emailData, plugins.path.join(paths.receivedEmailsDir, `${Date.now()}.eml`));
|
||||
|
||||
|
||||
if (!emailData) {
|
||||
console.error('No email data found for socket.');
|
||||
return;
|
||||
}
|
||||
|
||||
let mightBeSpam = false;
|
||||
|
||||
// Verifying the email with DKIM
|
||||
try {
|
||||
const isVerified = await this.mtaRef.dkimVerifier.verify(emailData);
|
||||
mightBeSpam = !isVerified;
|
||||
} catch (error) {
|
||||
console.error('Failed to verify DKIM signature:', error);
|
||||
mightBeSpam = true;
|
||||
}
|
||||
|
||||
const parsedEmail = await plugins.mailparser.simpleParser(emailData);
|
||||
console.log(parsedEmail)
|
||||
const email = new Email({
|
||||
from: parsedEmail.from?.value[0].address || '',
|
||||
to:
|
||||
parsedEmail.to instanceof Array
|
||||
? parsedEmail.to[0].value[0].address
|
||||
: parsedEmail.to?.value[0].address,
|
||||
subject: parsedEmail.subject || '',
|
||||
text: parsedEmail.html || parsedEmail.text,
|
||||
attachments:
|
||||
parsedEmail.attachments?.map((attachment) => ({
|
||||
filename: attachment.filename || '',
|
||||
content: attachment.content,
|
||||
contentType: attachment.contentType,
|
||||
})) || [],
|
||||
mightBeSpam: mightBeSpam,
|
||||
});
|
||||
|
||||
console.log('mail received!');
|
||||
console.log(email);
|
||||
|
||||
this.emailBufferStringMap.delete(socket);
|
||||
}
|
||||
|
||||
public start() {
|
||||
this.server.listen(this.smtpServerOptions.port, () => {
|
||||
console.log(`SMTP Server is now running on port ${this.smtpServerOptions.port}`);
|
||||
});
|
||||
}
|
||||
|
||||
public stop() {
|
||||
this.server.getConnections((err, count) => {
|
||||
if (err) throw err;
|
||||
console.log('Number of active connections: ', count);
|
||||
});
|
||||
this.server.close(() => {
|
||||
console.log('SMTP Server is now stopped');
|
||||
});
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user