package main import ( "bufio" "fmt" "context" "crypto/tls" "io" "net" "strings" "github.com/jackc/pgx/v5/pgxpool" "go.lindenii.runxiyu.org/lindenii-common/clog" ) type imap_server_state_t uint const ( imap_server_state_begin imap_server_state_t = iota imap_server_state_helo imap_server_state_mail imap_server_state_rcpt ) type imap_recv_session struct { buf_conn *bufio.ReadWriter net_conn net.Conn tls_conn *tls.Conn my_server_name string db *pgxpool.Pool } func (session *imap_recv_session) handle(ctx context.Context) error { session.buf_conn = bufio.NewReadWriter(bufio.NewReader(session.net_conn), bufio.NewWriter(session.net_conn)) session.buf_conn.WriteString("* OK [CAPABILITY IMAP4rev2 AUTH=PLAIN] " + VERSION + "\r\n") session.buf_conn.Flush() for { var tag, cmd, param string { line, err := session.buf_conn.ReadString('\n') if err != nil { if err == io.EOF { return err_connection_handler_eof } return err } line = strings.TrimSuffix(line, "\n") line = strings.TrimSuffix(line, "\r") tag_end := strings.IndexByte(line, ' ') if tag_end == -1 { session.buf_conn.WriteString(line+" BAD Error in IMAP command received by server.\r\n") session.buf_conn.Flush() continue } tag = line[:tag_end] rest := line[tag_end+1:] cmd_end := strings.IndexByte(rest, ' ') var param_start int if cmd_end == -1 { cmd_end = len(rest) param_start = len(rest) } else { param_start = cmd_end + 1 } cmd = strings.ToUpper(rest[:cmd_end]) param = rest[param_start:] } fmt.Printf("received tag=%s, cmd=%s, param=%s\n", tag, cmd, param) } } func imap_new_session(ctx context.Context, net_conn net.Conn) error { session := imap_recv_session{ net_conn: net_conn, } return session.handle(ctx) } func serve_imap() { var imap_net, imap_addr string config_consistent_run(func() { imap_net = config.IMAP.Net imap_addr = config.IMAP.Addr }) listener, err := net.Listen(imap_net, imap_addr) if err != nil { clog.Fatal(1, "IMAP: Cannot listen: "+err.Error()) } defer listener.Close() clog.Info("IMAP: Listening via " + imap_net + " on " + imap_addr) for { conn, err := listener.Accept() if err != nil { clog.Error("IMAP: Cannot accept connection: " + err.Error()) } clog.Info("IMAP: Accepted connection from " + conn.RemoteAddr().String()) go func() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() err := imap_new_session(ctx, conn) if err != nil { if err == err_connection_handler_eof { clog.Info("IMAP: Connection for " + conn.RemoteAddr().String() + " closed with EOF") } else { clog.Error("IMAP: Connection handler for " + conn.RemoteAddr().String() + " returned error: " + err.Error()) } } else { clog.Info("IMAP: Connection for " + conn.RemoteAddr().String() + " closed gracefully") } }() } }