Files
herolib/lib/servers/imap/server.v
2025-02-16 06:44:43 +03:00

123 lines
2.9 KiB
V

module imap
import net
import strings
import freeflowuniverse.herolib.ui.console
import time
import io
// Run starts the server on port 143 and accepts client connections.
fn (mut server IMAPServer) run() ! {
addr := '0.0.0.0:143'
mut listener := net.listen_tcp(.ip, addr, dualstack:true) or {
return error('Failed to listen on $addr: $err')
}
println('IMAP Server listening on $addr')
// Set TCP options for better reliability
// listener.set_option_bool(.reuse_addr, true)
for {
mut conn := listener.accept() or {
eprintln('Failed to accept connection: $err')
continue
}
// Set connection options
// conn.set_option_int(.tcp_keepalive, 60)!
conn.set_read_timeout(30 * time.second)
conn.set_write_timeout(30 * time.second)
// Handle each connection concurrently
spawn handle_connection(mut conn, mut server)
}
}
// handle_connection processes commands from a connected client.
fn handle_connection(mut conn net.TcpConn, mut server IMAPServer) ! {
// Send greeting per IMAP protocol.
defer {
conn.close() or { panic(err) }
}
conn.write('* OK [CAPABILITY IMAP4rev1 AUTH=PLAIN STARTTLS LOGIN] IMAP server ready\r\n'.bytes())!
// Initially no mailbox is selected.
mut selected_mailbox_name := ''
mut res := false
client_addr := conn.peer_addr()!
console.print_debug('> new client: ${client_addr}')
mut reader := io.new_buffered_reader(reader: conn)
defer {
unsafe {
reader.free()
}
}
mut session:= Session{
server:&server
mailbox:""
conn:conn
reader:reader
}
for {
// Read a line (command) from the client.
line := reader.read_line() or {
match err.msg() {
'closed' {
console.print_debug('Client disconnected normally')
return error('client disconnected')
}
'EOF' {
console.print_debug('Client connection ended (EOF)')
return error('connection ended')
}
else {
eprintln('Connection read error: $err')
return error('connection error: $err')
}
}
}
console.print_debug(line)
trimmed := line.trim_space()
if trimmed.len == 0 {
continue
}
// Commands come with a tag followed by the command and parameters.
parts := trimmed.split(' ')
if parts.len < 2 {
conn.write('${parts[0]} BAD Invalid command\r\n'.bytes())!
continue
}
tag := parts[0]
cmd := parts[1].to_upper()
match cmd {
'LOGIN' {
session.handle_login(tag, parts )!
}
'AUTHENTICATE' {
session.handle_authenticate(tag, parts)!
}
'SELECT' {
session.selected_mailbox_name = handle_select(mut conn, tag, parts, mut server) !
}
'FETCH' {
session.handle_fetch(mut conn, tag, parts) !
}
'STORE' {
session.handle_store(mut conn, tag, parts) !
}
'CAPABILITY' {
session.handle_capability(mut conn, tag) !
}
'LOGOUT' {
handle_logout(mut conn, tag) !
return
}
else {
conn.write('${tag} BAD Unknown command\r\n'.bytes())!
}
}
}
}