Skip to content

Commit

Permalink
💪 Receive mail from pop upstream
Browse files Browse the repository at this point in the history
  • Loading branch information
cfpwastaken committed Feb 23, 2024
1 parent b080815 commit ce7717d
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 38 deletions.
74 changes: 39 additions & 35 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
// import { Dialect } from "sequelize"
// import Mail from "./models/Mail.js"
// import POP3Server from "./pop3/POP3Server.js"
// import SMTPServer from "./smtp/SMTPServer.js"
// import { Sequelize } from "sequelize-typescript"
// import User from "./models/User.js"
import { Dialect } from "sequelize"
import Mail from "./models/Mail.js"
import POP3Server from "./pop3/POP3Server.js"
import SMTPServer from "./smtp/SMTPServer.js"
import { Sequelize } from "sequelize-typescript"
import User from "./models/User.js"
import POP3Client from "./pop3/POP3Client.js"

Check warning on line 7 in src/main.ts

View workflow job for this annotation

GitHub Actions / build (16.x)

Imports should be sorted alphabetically

Check warning on line 7 in src/main.ts

View workflow job for this annotation

GitHub Actions / build (16.x)

'POP3Client' is defined but never used

Check warning on line 7 in src/main.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Imports should be sorted alphabetically

Check warning on line 7 in src/main.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

'POP3Client' is defined but never used

Check warning on line 7 in src/main.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Imports should be sorted alphabetically

Check warning on line 7 in src/main.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

'POP3Client' is defined but never used

Check warning

Code scanning / ESLint

Enforce sorted import declarations within modules Warning

Imports should be sorted alphabetically.

Check warning

Code scanning / ESLint

Disallow unused variables Warning

'POP3Client' is defined but never used.
import getConfig from "./config.js"
import { writeFile } from "node:fs/promises"

// import { readFile } from "node:fs/promises"
// import { writeFile } from "node:fs/promises"

Check warning on line 9 in src/main.ts

View workflow job for this annotation

GitHub Actions / build (16.x)

Expected line before comment

Check warning on line 9 in src/main.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Expected line before comment

Check warning on line 9 in src/main.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Expected line before comment

Check warning

Code scanning / ESLint

Require empty lines around comments Warning

Expected line before comment.
import POPUpstream from "./upstreams/POPUpstream.js"

Check warning on line 10 in src/main.ts

View workflow job for this annotation

GitHub Actions / build (16.x)

Imports should be sorted alphabetically

Check warning on line 10 in src/main.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Imports should be sorted alphabetically

Check warning on line 10 in src/main.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Imports should be sorted alphabetically

Check warning

Code scanning / ESLint

Enforce sorted import declarations within modules Warning

Imports should be sorted alphabetically.
import { readFile } from "node:fs/promises"
// import IMAPClient from "./imap/IMAPClient.js"

Check warning

Code scanning / ESLint

Require empty lines around comments Warning

Expected line before comment.

// eslint-disable-next-line @typescript-eslint/no-explicit-any
global.debug = getConfig("debug", false) as any

// export const sql = new Sequelize({
// database: getConfig<string>("db.database"),
// dialect: getConfig<Dialect>("db.dialect"),
// username: getConfig<string>("db.username"),
// password: getConfig<string>("db.password"),
// models: [User, Mail]
// })
export const sql = new Sequelize({
database: getConfig<string>("db.database"),
dialect: getConfig<Dialect>("db.dialect"),
username: getConfig<string>("db.username"),
password: getConfig<string>("db.password"),
models: [User, Mail]
})

// await sql.sync({ alter: true })

Expand All @@ -30,22 +30,22 @@ global.debug = getConfig("debug", false) as any
// password: "1234"
// })

// secure: if (getConfig("pop3s.enabled", false) || getConfig("smtps.enabled", false)) {
// let tlsCert: Buffer, tlsKey: Buffer
secure: if (getConfig("pop3s.enabled", false) || getConfig("smtps.enabled", false)) {
let tlsCert: Buffer, tlsKey: Buffer

// try {
// tlsKey = await readFile(getConfig("tls.key", "cert/privkey.pem"))
// tlsCert = await readFile(getConfig("tls.cert", "cert/fullchain.pem"))
// } catch (ignore) {
// break secure
// }
try {
tlsKey = await readFile(getConfig("tls.key", "cert/privkey.pem"))
tlsCert = await readFile(getConfig("tls.cert", "cert/fullchain.pem"))
} catch (ignore) {
break secure
}

// if (getConfig("pop3s.enabled", false)) new POP3Server(getConfig("pop3s.port", 995), true, tlsKey, tlsCert) // Port 110 for regular POP3, 995 for POP3S
// if (getConfig("smtps.enabled", false)) new SMTPServer(getConfig("smtps.port", 465), true, tlsKey, tlsCert) // Port 25 for regular SMTP, 465 for SMTPS
// }
if (getConfig("pop3s.enabled", false)) new POP3Server(getConfig("pop3s.port", 995), true, tlsKey, tlsCert) // Port 110 for regular POP3, 995 for POP3S
if (getConfig("smtps.enabled", false)) new SMTPServer(getConfig("smtps.port", 465), true, tlsKey, tlsCert) // Port 25 for regular SMTP, 465 for SMTPS
}

// if (getConfig("smtp.enabled", true)) new SMTPServer(getConfig("smtp.port", 25), false) // Port 25 for regular SMTP, 465 for SMTPS
// if (getConfig("pop3.enabled", true)) new POP3Server(getConfig("pop3.port", 110), false) // Port 110 for regular POP3, 995 for POP3S
if (getConfig("smtp.enabled", true)) new SMTPServer(getConfig("smtp.port", 25), false) // Port 25 for regular SMTP, 465 for SMTPS
if (getConfig("pop3.enabled", true)) new POP3Server(getConfig("pop3.port", 110), false) // Port 110 for regular POP3, 995 for POP3S

// repl.start()
// const client = new IMAPClient("imap.ionos.de", 993, true)
Expand All @@ -67,18 +67,22 @@ global.debug = getConfig("debug", false) as any

// for (const message of messages) console.log(message)

const client = new POP3Client("pop.ionos.de", 995, true)
// const client = new POP3Client("pop.ionos.de", 995, true)

await client.login("<REDACTED>", "<REDACTED>")
await new Promise(resolve => setTimeout(resolve, 1000))
// await client.login("<REDACTED>", "<REDACTED>")
// await new Promise(resolve => setTimeout(resolve, 1000))

writeFile("mails/1.eml", (await client.retrieveMail("1")).split("\r\n").slice(1, -2).join("\r\n"))
await new Promise(resolve => setTimeout(resolve, 1000))
writeFile("mails/2.eml", (await client.retrieveMail("2")).split("\r\n").slice(1, -2).join("\r\n"))
// writeFile("mails/1.eml", (await client.retrieveMail("1")).split("\r\n").slice(1, -2).join("\r\n"))
// await new Promise(resolve => setTimeout(resolve, 1000))
// writeFile("mails/2.eml", (await client.retrieveMail("2")).split("\r\n").slice(1, -2).join("\r\n"))

// const list = (await client.list()).split("\r\n").slice(1, -2) // Remove the first and last line
// const mailIds = list.map(l => l.split(" ")[0])
// const mailPromises = []
// for (const id of mailIds) mailPromises.push(client.retrieveMail(id))
// const mails = await Promise.all(mailPromises)
// for (const mail of mails) writeFile(`mails/${mailIds[mails.indexOf(mail)]}.txt`, mail)

const up = new POPUpstream({})

Check failure on line 86 in src/main.ts

View workflow job for this annotation

GitHub Actions / build (16.x)

Argument of type '{}' is not assignable to parameter of type 'POPUpstreamOptions'.

Check failure on line 86 in src/main.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

Argument of type '{}' is not assignable to parameter of type 'POPUpstreamOptions'.

Check failure on line 86 in src/main.ts

View workflow job for this annotation

GitHub Actions / build (20.x)

Argument of type '{}' is not assignable to parameter of type 'POPUpstreamOptions'.

await up.fetchNewEmails()
15 changes: 13 additions & 2 deletions src/pop3/POP3Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,26 @@ export default class POP3Client {

logger.log(`LIST: ${resp}`)

return resp
const list = resp.split("\r\n").slice(1, -2) // Remove the first and last line
const mailIds = list.map(l => l.split(" ")[0])

return mailIds
}

async retrieveMail(id: string) {
const resp = await this.writeAndWaitForResponse(`RETR ${id}\r\n`, true)

logger.log(`RETR: ${resp}`)

return resp
const content = resp.split("\r\n").slice(1, -2).join("\r\n")

return content
}

async deleteMail(id: string) {
const resp = await this.writeAndWaitForResponse(`DELE ${id}\r\n`)

logger.log(`DELE: ${resp}`)
}

async logout() {
Expand Down
3 changes: 2 additions & 1 deletion src/pop3/POP3Server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export default class POP3Server {
logger.log(`Received data: ${msg}`)
const args = msg.split(" ").slice(1)

const command = commands.find(c => c.command == msg.substring(0, msg.indexOf(" ")))
const sentCmd = msg.includes(" ") ? msg.substring(0, msg.indexOf(" ")) : msg.trim()
const command = commands.find(c => c.command == sentCmd)

if (!command) {
sock.write("-ERR Unknown command\r\n")
Expand Down
1 change: 1 addition & 0 deletions src/smtp/SMTP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const logger = new Logger("SMTP", "GREEN")
export default class SMTP {

static async handleNewMail(info: { from: string, to: string[], content: string }) {
logger.log(`Received mail from ${info.from} to ${info.to.join(", ")}`)
const id = crypto.randomUUID()

await mkdir(`mails/`, { recursive: true })
Expand Down
68 changes: 68 additions & 0 deletions src/upstreams/POPUpstream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import Logger from "../Logger.js";

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

Check failure

Code scanning / ESLint

Enforce location of semicolons Error

Expected this semicolon to be at the beginning of the next line.
import POP3Client from "../pop3/POP3Client.js";

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

Check failure

Code scanning / ESLint

Enforce location of semicolons Error

Expected this semicolon to be at the beginning of the next line.
import SMTP from "../smtp/SMTP.js";

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

Check failure

Code scanning / ESLint

Enforce location of semicolons Error

Expected this semicolon to be at the beginning of the next line.

type POPUpstreamOptions = {
host: string;
port: number;
useTLS: boolean;
username: string;
password: string;
}

function sleep(ms: number) {
return new Promise(resolve => {
setTimeout(resolve, ms)
})
}

const logger = new Logger("POP Upstream", "TEAL")

export default class POPUpstream {

popclient: POP3Client | null; options: POPUpstreamOptions

constructor(options: POPUpstreamOptions) {
this.options = options
this.popclient = null
}

async fetchNewEmails() {
if (this.popclient === null) {
this.popclient = new POP3Client(this.options.host, this.options.port, this.options.useTLS)
await sleep(300)
await this.popclient.login(this.options.username, this.options.password)
}

const mailIds = await this.popclient.list()

if (mailIds.length === 0) return
if (mailIds.length > 1) setTimeout(() => this.fetchNewEmails(), 1000)

const content = await this.popclient.retrieveMail(mailIds[0])

// Fetch the mail inside the <> brackets in the From: header
let fromAddress = content.match(/^From: (.+?)\r\n/m)?.[1]

fromAddress = fromAddress?.match(/<(.+?)>/)?.[1] || fromAddress

let toAddress = content.match(/^To: (.+?)\r\n/m)?.[1]

toAddress = toAddress?.match(/<(.+?)>/)?.[1] || toAddress

if (!fromAddress || !toAddress) throw new Error("Invalid email")

await this.popclient.deleteMail(mailIds[0])

if (toAddress.startsWith("*@")) {
logger.log(`Mail is to upstream mail. Ignoring.`)

return
}

SMTP.handleNewMail({ from: fromAddress, to: [toAddress], content })

if (mailIds.length == 1) await this.popclient.logout()
}

}

0 comments on commit ce7717d

Please sign in to comment.