Documentation
¶
Overview ¶
Package pop3 implements a POP3 server engine, RFC 1939, with zero external dependencies (standard library only).
You supply a Backend that authenticates users and exposes each user's maildrop as a slice of neutral Message values; the Server speaks the wire protocol — USER/PASS, CAPA, STLS, STAT, LIST, UIDL, RETR, TOP, DELE, RSET, NOOP and QUIT — and calls back into the Backend to fetch, mark-seen and delete messages. The engine holds no assumptions about where mail is stored: a Backend can be a database, a filesystem, or a remote API.
The message body served by RETR and TOP is whatever Mailbox.Retrieve returns, with its line endings canonicalized to CRLF and RFC 1939 dot-stuffing applied on the wire; the reported octet count is that of the canonical form. Messages the client DELEtes are only removed on QUIT, matching RFC 1939 update-state semantics.
Backend ¶
A caller implements three interfaces. Backend validates credentials and returns a Mailbox; Mailbox enumerates, retrieves, marks-seen and deletes messages; each maildrop entry is a Message whose slice position (+1) is the POP3 message number for the session. The engine calls Mailbox.Messages once per session immediately after login, then serves the resulting snapshot.
Server ¶
NewServer builds a Server that listens on the plaintext and implicit-TLS ports named by Ports; Server.ListenAndServe opens the listeners and Server.Shutdown drains them gracefully. To drive a single already-accepted connection yourself — behind your own listener or proxy — construct a Session with NewSession and call Session.Handle.
TLS and authentication ¶
A non-nil *tls.Config enables both the STLS command (STARTTLS for POP3, RFC 2595) on the plaintext port and implicit TLS on the secure port. When a TLS config is present the engine refuses USER on a plaintext connection, so credentials are never sent in the clear; with no TLS config USER is accepted as-is. CAPA advertises the supported extensions (RFC 2449): USER, STLS, TOP, UIDL, RESP-CODES and PIPELINING.
Rate limiting ¶
NewServer and NewSession accept a Limiter, a small structural interface the engine consults for per-IP connection caps and authentication-failure bans. Pass nil or NopLimiter to impose no limits, or wire in your own; any type with the required methods satisfies it.
Example ¶
Example drives a full POP3 conversation against an in-memory backend. It runs a pop3.Session over an in-process net.Pipe instead of a TCP listener, so the round trip is self-contained; in production you would call pop3.NewServer and pop3.Server.ListenAndServe with real ports (see the package README).
package main
import (
"bufio"
"fmt"
"net"
"strings"
pop3 "github.com/rest-mail/go-pop3"
)
// memBackend is a trivial in-memory [pop3.Backend]: it accepts one hard-coded
// user and hands out a maildrop holding a single message. A real backend would
// look credentials up in a store and scope the returned [pop3.Mailbox] to that
// user.
type memBackend struct{}
func (memBackend) Authenticate(user, pass string) (pop3.Mailbox, error) {
if user != "me@example.com" || pass != "s3cret" {
return nil, fmt.Errorf("bad credentials")
}
return &memMailbox{}, nil
}
// memMailbox serves one fixed message from memory.
type memMailbox struct{}
var exampleRaw = []byte("From: me@example.com\r\n" +
"Subject: hello\r\n" +
"\r\n" +
"Hi there!\r\n")
func (m *memMailbox) Messages() ([]pop3.Message, error) {
return []pop3.Message{{UID: "1001", Size: len(exampleRaw)}}, nil
}
func (m *memMailbox) Retrieve(uid string) ([]byte, error) { return exampleRaw, nil }
func (m *memMailbox) MarkSeen(uid string) error { return nil }
func (m *memMailbox) Delete(uid string) error { return nil }
// Example drives a full POP3 conversation against an in-memory backend. It runs
// a [pop3.Session] over an in-process net.Pipe instead of a TCP listener, so the
// round trip is self-contained; in production you would call [pop3.NewServer]
// and [pop3.Server.ListenAndServe] with real ports (see the package README).
func main() {
client, server := net.Pipe()
// Serve the connection in the background. A nil *tls.Config disables STLS,
// and a nil Limiter defaults to pop3.NopLimiter (no per-IP limits).
go pop3.NewSession(server, memBackend{}, nil, nil).Handle()
r := bufio.NewReader(client)
w := bufio.NewWriter(client)
// send writes one command line and prints the server's status reply.
send := func(cmd string) {
if cmd != "" {
_, _ = fmt.Fprintf(w, "%s\r\n", cmd)
_ = w.Flush()
}
line, _ := r.ReadString('\n')
fmt.Println(strings.TrimRight(line, "\r\n"))
}
send("") // read the greeting
send("USER me@example.com") // identify the user
send("PASS s3cret") // authenticate
send("STAT") // count and total size
send("QUIT") // commit deletes and disconnect
}
Output: +OK POP3 server ready +OK +OK Authentication successful +OK 1 51 +OK POP3 server signing off
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Backend ¶
type Backend interface {
// Authenticate validates the USER/PASS credentials. Returning a non-nil error
// rejects the login (the client sees "-ERR ...") and counts as an auth
// failure against the [Limiter]. On success it returns the [Mailbox] the
// session operates on.
Authenticate(user, pass string) (Mailbox, error)
}
Backend authenticates POP3 users. A Server calls Authenticate once per session, after USER and PASS have both been received.
type Limiter ¶
type Limiter interface {
// Accept reports whether a new connection from ip may proceed, incrementing
// the in-use count when it returns true.
Accept(ip string) bool
// Release decrements the in-use count for ip when a connection ends.
Release(ip string)
// RecordAuthFail records an authentication failure from ip.
RecordAuthFail(ip string)
// IsBanned reports whether ip is currently banned.
IsBanned(ip string) bool
// ResetAuth clears the recorded auth-failure history for ip after a success.
ResetAuth(ip string)
}
Limiter is the per-IP connection and authentication guard the Server consults. It is a structural interface: any type with these methods satisfies it. Pass NopLimiter (or nil) to impose no limits.
type Mailbox ¶
type Mailbox interface {
// Messages returns the maildrop contents oldest-first. It is called once,
// immediately after authentication.
Messages() ([]Message, error)
// Retrieve returns the full RFC 5322 bytes of the message with the given UID.
// RETR and TOP serve these bytes with line endings canonicalized to CRLF and
// RFC 1939 dot-stuffing applied; a bare LF in the returned bytes is treated
// as a line boundary and normalized to CRLF on the wire.
Retrieve(uid string) ([]byte, error)
// MarkSeen flags a message read after a successful RETR. POP3 has no read
// state of its own; implementations without one may return nil.
MarkSeen(uid string) error
// Delete permanently removes a message. The Server calls it on QUIT, once for
// each message the client DELEted during the session.
Delete(uid string) error
}
Mailbox is a single authenticated POP3 maildrop. Every method is scoped to the user that Backend.Authenticate accepted; a Mailbox is used by one session.
type Message ¶
type Message struct {
// UID is the persistent unique identifier a client sees via UIDL. The Server
// also passes it back to [Mailbox.Retrieve], [Mailbox.MarkSeen] and
// [Mailbox.Delete] to name this message. It must be unique and stable within
// the maildrop for the session's lifetime.
UID string
// Size is the octet count reported by STAT and LIST (the RFC 1939 maildrop
// listing size). It need not equal the exact length Retrieve returns.
Size int
// Seen reports whether the message is already marked read. RETR issues a
// MarkSeen only for a message that was previously unseen; TOP never does.
Seen bool
}
Message is one message in a POP3 maildrop, presented oldest-first. Its slice position (+1) is the POP3 message number the client uses for the session.
type NopLimiter ¶
type NopLimiter struct{}
NopLimiter is a Limiter that imposes no limits: it accepts every connection and never bans. It is the default when a nil Limiter is passed to NewServer or NewSession.
func (NopLimiter) Accept ¶
func (NopLimiter) Accept(string) bool
func (NopLimiter) IsBanned ¶
func (NopLimiter) IsBanned(string) bool
func (NopLimiter) RecordAuthFail ¶
func (NopLimiter) RecordAuthFail(string)
func (NopLimiter) Release ¶
func (NopLimiter) Release(string)
func (NopLimiter) ResetAuth ¶
func (NopLimiter) ResetAuth(string)
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server listens for POP3 connections and spawns a session handler per client.
func NewServer ¶
NewServer creates a POP3 Server backed by the given Backend. A non-nil tlsConfig enables STLS (STARTTLS) and implicit-TLS listeners. A nil limiter defaults to NopLimiter.
func (*Server) ListenAndServe ¶
ListenAndServe starts POP3 listeners on the specified ports. A zero port is skipped. It returns once the listeners are open; connections are served in the background until Server.Shutdown.
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session represents a single POP3 conversation with a client.
func NewSession ¶
NewSession creates a POP3 session over conn, authenticating against backend. A nil limiter defaults to NopLimiter. Call Session.Handle to run it.
func (*Session) Handle ¶
func (s *Session) Handle()
Handle runs the POP3 state machine until the client disconnects or QUITs.
A panic in any command handler or Backend/Mailbox call is recovered here so a single misbehaving session is isolated — logged and answered with -ERR before the connection is closed — rather than unwinding the per-connection goroutine and crashing the whole process along with every concurrent session.