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")
}
}()
}
}