From 6c6325975d50a0678cd148960d8a83449f31ff83 Mon Sep 17 00:00:00 2001 From: Cfp Date: Fri, 23 Feb 2024 15:49:46 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=A9=E2=80=87SMTP=20Upstream?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.ts | 17 ++++++- src/smtp/SMTPClient.ts | 107 ++++++++++++++++++++++++++++++++++++----- src/smtp/SMTPServer.ts | 10 ++++ 3 files changed, 120 insertions(+), 14 deletions(-) diff --git a/src/main.ts b/src/main.ts index 8bd1e1a..e09d0c4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,6 +9,7 @@ import getConfig from "./config.js" // import { writeFile } from "node:fs/promises" import POPUpstream from "./upstreams/POPUpstream.js" import { readFile } from "node:fs/promises" +import SMTPClient from "./smtp/SMTPClient.js" // import IMAPClient from "./imap/IMAPClient.js" // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -83,6 +84,18 @@ if (getConfig("pop3.enabled", true)) new POP3Server(getConfig("pop3.port", 110), // const mails = await Promise.all(mailPromises) // for (const mail of mails) writeFile(`mails/${mailIds[mails.indexOf(mail)]}.txt`, mail) -const up = new POPUpstream({}) +// const up = new POPUpstream({}) -await up.fetchNewEmails() +// await up.fetchNewEmails() + +const down = new SMTPClient("smtp.ionos.de", 25, false) + +await new Promise(resolve => setTimeout(resolve, 1000)) +await down.ehlo("127.0.0.1") +await down.startTLS() +await down.ehlo("127.0.0.1") +await down.login("*@picoscratch.de", "Passwort#0000") +await down.from("jannik@picoscratch.de") +await down.to("jannik.eckhardt2009@gmail.com") +await down.data(await readFile("mails/35baf742-6aab-4e39-899b-63330c40f6f9.eml", "utf-8")) +await down.quit() diff --git a/src/smtp/SMTPClient.ts b/src/smtp/SMTPClient.ts index 875a6e0..b31413d 100644 --- a/src/smtp/SMTPClient.ts +++ b/src/smtp/SMTPClient.ts @@ -7,22 +7,105 @@ const logger = new Logger("SMTPClient", "TEAL") export default class SMTPClient { - static sendMessage(host: string, port: number, from: string, to: string, content: string, useTLS: boolean) { - // const sock = net.createConnection(port, host) - const sock = useTLS ? tls.connect(port, host) : net.createConnection(port, host) + socket: net.Socket - sock.on("data", (data: Buffer) => { + constructor(host: string, port: number, useTLS: boolean) { + this.socket = useTLS ? tls.connect(port, host) : net.createConnection(port, host) + + this.socket.on("data", (data: Buffer) => { logger.log(`Received data: ${data.toString()}`) }) - sock.on("connect", () => { - logger.log("Connected to server") - sock.write(`EHLO ${getConfig("host", "localhost")}\r\n`) - sock.write(`MAIL FROM:<${from}>\r\n`) - sock.write(`RCPT TO:<${to}>\r\n`) - sock.write("DATA\r\n") - sock.write(`${content}\r\n.\r\n`) - sock.write("QUIT\r\n") + } + + writeAndWaitForResponse(data: string) { + logger.log(`Sending data: ${data}`) + + return new Promise(resolve => { + const { socket } = this + + socket.once("data", (recv: Buffer) => { + const text = recv.toString() + + logger.log(`Received data: ${text}`) + + resolve(text) + }) + + socket.write(data) }) } + async ehlo(host: string) { + const response = await this.writeAndWaitForResponse(`EHLO ${host}\r\n`) + + return response + } + + async from(from: string) { + const response = await this.writeAndWaitForResponse(`MAIL FROM:<${from}>\r\n`) + + return response + } + + async to(to: string) { + const response = await this.writeAndWaitForResponse(`RCPT TO:<${to}>\r\n`) + + return response + } + + async data(content: string) { + const response = await this.writeAndWaitForResponse("DATA\r\n") + const contentResponse = await this.writeAndWaitForResponse(`${content}\r\n.\r\n`) + + return { response, contentResponse } + } + + async quit() { + const response = await this.writeAndWaitForResponse("QUIT\r\n") + + return response + } + + async login(username: string, password: string) { + const resp = await this.writeAndWaitForResponse(`AUTH LOGIN\r\n`) + const userResp = await this.writeAndWaitForResponse(`${Buffer.from(username).toString("base64")}\r\n`) + const passResp = await this.writeAndWaitForResponse(`${Buffer.from(password).toString("base64")}\r\n`) + + return { resp, userResp, passResp } + } + + async startTLS() { + const response = await this.writeAndWaitForResponse("STARTTLS\r\n") + + if (response.startsWith("220")) { + this.socket.removeAllListeners() + this.socket = tls.connect({ socket: this.socket }) + this.socket.on("data", (data: Buffer) => { + logger.log(`Received TLS data: ${data.toString()}`) + }) + + return true + } + + return false + } + + // static sendMessage(host: string, port: number, from: string, to: string, content: string, useTLS: boolean) { + // // const sock = net.createConnection(port, host) + // const sock = useTLS ? tls.connect(port, host) : net.createConnection(port, host) + + // sock.on("data", (data: Buffer) => { + // logger.log(`Received data: ${data.toString()}`) + // }) + // sock.on("connect", () => { + // logger.log("Connected to server") + // sock.write(`EHLO ${getConfig("host", "localhost")}\r\n`) + // sock.write(`MAIL FROM:<${from}>\r\n`) + // sock.write(`RCPT TO:<${to}>\r\n`) + // sock.write("DATA\r\n") + // sock.write(`${content}\r\n.\r\n`) + // sock.write("QUIT\r\n") + // }) + // } + } diff --git a/src/smtp/SMTPServer.ts b/src/smtp/SMTPServer.ts index 1f0bd68..007579b 100644 --- a/src/smtp/SMTPServer.ts +++ b/src/smtp/SMTPServer.ts @@ -89,6 +89,16 @@ export default class SMTPServer { const email = msg.split(":")[1].split(">")[0].replace("<", "") const [username, domain] = email.split("@") + if (info.from.endsWith(`@${getConfig("host")}`)) { + // This is an outgoing mail, we need to forward it to another server + logger.log("Mail is outgoing.") + info.to.push(email) + logger.log(`RCPT TO: ${email}`) + status(250) + + return + } + if (domain != getConfig("host")) { // The spec says we MAY forward the message ourselves, // but simply returning 550 is fine, and the client should handle it