package main import ( "bufio" "bytes" "crypto/tls" "net" "slices" "strings" "go.lindenii.runxiyu.org/lindenii-common/mailkit" ) type server_state_t uint const ( server_state_begin server_state_t = iota server_state_helo server_state_mail server_state_rcpt ) func handle_incoming_server_connection(buf_conn *bufio.ReadWriter, net_conn *net.Conn) error { var tls_conn *tls.Conn var my_server_name string var routes map[string]string var tls_config *tls.Config config_consistent_run(func() { my_server_name = config.Server_name routes = config.Routes tls_config = config._tls_config }) _, _ = buf_conn.WriteString("220 " + my_server_name + " " + VERSION + "\r\n") _ = buf_conn.Flush() server_state := server_state_begin var remote_server_name string var current_mail_from string var current_rcpt_to []string for { line, err := buf_conn.ReadString('\n') if err != nil { return err } line = strings.TrimSuffix(line, "\n") line = strings.TrimSuffix(line, "\r") cmd_end := strings.IndexByte(line, ' ') var param_start int if cmd_end == -1 { cmd_end = len(line) param_start = len(line) } else { param_start = cmd_end + 1 } cmd := strings.ToUpper(line[:cmd_end]) param := line[param_start:] switch_cmd: switch cmd { case "STARTTLS": _, _ = buf_conn.WriteString("220 2.0.0 Ready to start TLS\r\n") _ = buf_conn.Flush() tls_conn = tls.Server(*net_conn, tls_config) buf_conn = bufio.NewReadWriter(bufio.NewReader(tls_conn), bufio.NewWriter(tls_conn)) case "HELO": if param == "" { // TODO: actually validate the hostname _, _ = buf_conn.WriteString("501 Syntax: HELO hostname\r\n") _ = buf_conn.Flush() break } remote_server_name = param _ = remote_server_name // TODO server_state = server_state_helo _, _ = buf_conn.WriteString("250 " + my_server_name + "\r\n") _ = buf_conn.Flush() case "MAIL": switch server_state { case server_state_begin: _, _ = buf_conn.WriteString("503 5.5.1 Error: send HELO/EHLO first\r\n") _ = buf_conn.Flush() break switch_cmd case server_state_helo: break case server_state_mail: _, _ = buf_conn.WriteString("503 5.5.1 Error: nested MAIL command\r\n") _ = buf_conn.Flush() break switch_cmd } if len(param) <= len("FROM:") || strings.ToUpper(param[:len("FROM:")]) != "FROM:" { _, _ = buf_conn.WriteString("501 5.5.4 Syntax: MAIL FROM:
\r\n") _ = buf_conn.Flush() break } current_mail_from = param[len("FROM:"):] current_rcpt_to = []string{} server_state = server_state_mail _, _ = buf_conn.WriteString("250 2.1.0 Ok\r\n") _ = buf_conn.Flush() // TODO: Address validation case "RCPT": if server_state != server_state_mail && server_state != server_state_rcpt { _, _ = buf_conn.WriteString("503 5.5.1 Error: need MAIL command\r\n") _ = buf_conn.Flush() break } if len(param) <= len("TO:") || strings.ToUpper(param[:len("TO:")]) != "TO:" { _, _ = buf_conn.WriteString("501 5.5.4 Syntax: RCPT TO:\r\n") _ = buf_conn.Flush() break } recipient, _, _ := mailkit.Strip_angle_brackets(param[len("TO:"):]) _, ok := routes[recipient] if !ok { _, _ = buf_conn.WriteString("550 5.1.1 <" + recipient + ">: Recipient address rejected: User unknown in local recipient table\r\n") _ = buf_conn.Flush() break switch_cmd } current_rcpt_to = append(current_rcpt_to, recipient) server_state = server_state_rcpt _, _ = buf_conn.WriteString("250 2.1.5 Ok\r\n") _ = buf_conn.Flush() case "DATA": if server_state != server_state_rcpt { _, _ = buf_conn.WriteString("503 5.5.1 Error: need RCPT command\r\n") _ = buf_conn.Flush() break } _, _ = buf_conn.WriteString("354 End data with