import * as net from "net"; export interface IImapServerMessage { id: string; subject: string; sender: string; recipient: string; date: Date; body: string; } export interface IImapServerInbox { name: string; messages: IImapServerMessage[]; } export interface IImapServerUser { username: string; password: string; inboxes: Map; } export class ImapServer { public users: Map; private server: net.Server; constructor() { this.users = new Map(); this.server = net.createServer(this.handleConnection.bind(this)); } // Add a user for authentication public addUser(username: string, password: string): void { if (this.users.has(username)) { throw new Error(`User "${username}" already exists.`); } this.users.set(username, { username, password, inboxes: new Map() }); } // Add an inbox for a user public createInbox(username: string, inboxName: string): void { const user = this.users.get(username); if (!user) { throw new Error(`User "${username}" does not exist.`); } if (user.inboxes.has(inboxName)) { throw new Error(`Inbox "${inboxName}" already exists for user "${username}".`); } user.inboxes.set(inboxName, { name: inboxName, messages: [] }); } // Start the server public start(port: number): void { this.server.listen(port, () => { console.log(`IMAP Server started on port ${port}`); }); } // Stop the server public stop(): void { this.server.close(() => { console.log("IMAP Server stopped."); }); } // Handle a new client connection private handleConnection(socket: net.Socket): void { let currentUser: IImapServerUser | null = null; let selectedInbox: IImapServerInbox | null = null; socket.write("* OK IMAP4rev1 Service Ready\r\n"); socket.on("data", (data) => { const command = data.toString().trim(); console.log(`Received command: ${command}`); const [tag, keyword, ...args] = command.split(" "); let response = ""; try { switch (keyword.toUpperCase()) { case "LOGIN": { const [username, password] = args; const user = this.users.get(username); if (user && user.password === password) { currentUser = user; response = `${tag} OK LOGIN completed`; } else { response = `${tag} NO LOGIN failed`; } break; } case "LIST": { if (!currentUser) { response = `${tag} NO Not authenticated`; break; } const inboxNames = Array.from(currentUser.inboxes.keys()).map((inbox) => `* LIST () "/" ${inbox}`); response = `${inboxNames.join("\r\n")}\r\n${tag} OK LIST completed`; break; } case "SELECT": { if (!currentUser) { response = `${tag} NO Not authenticated`; break; } const inboxName = args[0]; const inbox = currentUser.inboxes.get(inboxName); if (inbox) { selectedInbox = inbox; response = `* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n* EXISTS ${inbox.messages.length}\r\n${tag} OK [READ-WRITE] SELECT completed`; } else { response = `${tag} NO SELECT failed: No such mailbox`; } break; } case "FETCH": { if (!selectedInbox) { response = `${tag} NO No mailbox selected`; break; } const [id] = args; const message = selectedInbox.messages.find((msg) => msg.id === id); if (message) { response = `* ${id} FETCH (BODY[TEXT] {${message.body.length}}\r\n${message.body}\r\n)\r\n${tag} OK FETCH completed`; } else { response = `${tag} NO FETCH failed: No such message`; } break; } case "LOGOUT": { response = `* BYE IMAP4rev1 Server logging out\r\n${tag} OK LOGOUT completed`; socket.write(response + "\r\n"); socket.end(); return; } default: { response = `${tag} BAD Unknown command`; break; } } } catch (error) { response = `${tag} BAD Error: ${error.message}`; } socket.write(response + "\r\n"); }); socket.on("close", () => { console.log("Client disconnected."); }); socket.on("error", (err) => { console.error("Socket error:", err); }); } }