summaryrefslogtreecommitdiff
path: root/src/main/kotlin/SMTPProtocol.kt
blob: e5da60132865f77ea6163b9a6197786335d3b956 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import java.net.InetAddress
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract


@OptIn(ExperimentalContracts::class)
class SMTPReceiveProtocol(val localHost: String, val inetAddress: InetAddress) : Protocol() {

    class Commands(val line: String, private val io: IO) : IO by io {
        var matched = false


        suspend inline fun command(vararg name: String, block: suspend IO.(String) -> Unit) {
            contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) }
            for (n in name) commandOnce(n, block)
        }

        suspend inline fun commandOnce(name: String, block: suspend IO.(String) -> Unit) {
            contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) }
            if (matched) return
            if (!line.startsWith(name, ignoreCase = true)) return
            matched = true
            block(line.substring(name.length).trimStart())
        }

        suspend inline fun otherwise(block: suspend IO.(String) -> Unit) {
            contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) }
            if (matched) return
            matched = true
            block(line)
        }
    }

    suspend inline fun IO.commands(line: String, block: suspend Commands.() -> Unit) {
        contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
        Commands(line, this).block()
    }

    data class Mail(val sender: String, val recipient: List<String>, val text: String)

    class MailTransaction(
        var isHelod: Boolean = false,
        var isEhlod: Boolean = false,
        var recipients: MutableList<String> = mutableListOf(),
        var sender: String? = null,
    ) {
        fun reset() {
            recipients = mutableListOf()
            sender = null
        }
    }

    override suspend fun IO.execute() {
        send("220 $localHost\r\n")
        val messages = mutableListOf<Mail>()
        val trans = MailTransaction()
        while (isOpen()) {
            commands(readLine()) {
                println(line)
                command("EHLO") {
                    send("250 hello advanced $it\r\n")
                    trans.isHelod = true
                    trans.isEhlod = true
                }
                command("HELO") {
                    send("250 Hello $it, how are you on this fine day?\r\n")
                    trans.isHelod = true
                }
                command("MAIL FROM:") {
                    send("250 Sender ok\r\n")
                    trans.sender = it
                }
                command("RCPT TO:") {
                    send("250 Receipient ok\r\n")
                    trans.recipients.add(it)
                }
                command("DATA") {
                    send("354 Enter mail, end with \".\" on a line by itself\r\n")
                    var text = readUntil("\r\n.\r\n".encodeToByteArray())
                    messages.add(Mail(trans.sender!!, trans.recipients.toList(), text.decodeToString()))
                    trans.reset()
                    send("250 Message accepted for delivery\r\n")
                }
                command("QUIT") {
                    send("221 $localHost closing connection\r\n")
                    close()
                }
                otherwise {
                    send("500 ERROR UNKNOWN CODE $it\r\n")
                }
            }
        }
        println("Got ${messages.size} messages")
        messages.forEach {
            println("Message:")
            println("MAIL FROM: ${it.sender}")
            println("RCPTS TO: ${it.recipient}")
            println("CONTENT: ${it.text}")
        }
    }
}