1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-17 09:37:02 +00:00

POP3 is working

Added pop3.domain config option (for APOP greeting)
Implemented CAPA command
Implemented APOP "encrypted" authorization
Updated all sample config files to include [pop3] section
Closes #8
This commit is contained in:
James Hillyerd
2013-09-12 22:07:24 -07:00
parent 06ce860435
commit aa7b760385
7 changed files with 100 additions and 14 deletions

View File

@@ -25,6 +25,7 @@ type SmtpConfig struct {
type Pop3Config struct { type Pop3Config struct {
Ip4address net.IP Ip4address net.IP
Ip4port int Ip4port int
Domain string
MaxIdleSeconds int MaxIdleSeconds int
} }
@@ -109,6 +110,7 @@ func LoadConfig(filename string) error {
requireOption(messages, "smtp", "store.messages") requireOption(messages, "smtp", "store.messages")
requireOption(messages, "pop3", "ip4.address") requireOption(messages, "pop3", "ip4.address")
requireOption(messages, "pop3", "ip4.port") requireOption(messages, "pop3", "ip4.port")
requireOption(messages, "pop3", "domain")
requireOption(messages, "pop3", "max.idle.seconds") requireOption(messages, "pop3", "max.idle.seconds")
requireOption(messages, "web", "ip4.address") requireOption(messages, "web", "ip4.address")
requireOption(messages, "web", "ip4.port") requireOption(messages, "web", "ip4.port")
@@ -262,6 +264,13 @@ func parsePop3Config() error {
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err) return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
} }
option = "domain"
str, err = Config.String(section, option)
if err != nil {
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
}
pop3Config.Domain = str
option = "max.idle.seconds" option = "max.idle.seconds"
pop3Config.MaxIdleSeconds, err = Config.Int(section, option) pop3Config.MaxIdleSeconds, err = Config.Int(section, option)
if err != nil { if err != nil {

View File

@@ -53,6 +53,9 @@ ip4.address=0.0.0.0
# IPv4 port to listen for POP3 connections on. # IPv4 port to listen for POP3 connections on.
ip4.port=1100 ip4.port=1100
# used in POP3 greeting
domain=inbucket.local
# How long we allow a network connection to be idle before hanging up on the # How long we allow a network connection to be idle before hanging up on the
# client, POP3 RFC requires at least 10 minutes (600 seconds). # client, POP3 RFC requires at least 10 minutes (600 seconds).
max.idle.seconds=600 max.idle.seconds=600

View File

@@ -37,13 +37,29 @@ max.recipients=100
# client, SMTP RFC recommends at least 5 minutes (300 seconds). # client, SMTP RFC recommends at least 5 minutes (300 seconds).
max.idle.seconds=300 max.idle.seconds=300
# Maximum allowable size of message body in bytes (including attachments) # Maximum allowable size of message body in bytes (including attachments)
max.message.bytes=2048000 max.message.bytes=2048000
# Should we place messages into the datastore, or just throw them away # Should we place messages into the datastore, or just throw them away
# (for load testing): true or false # (for load testing): true or false
store.messages=true store.messages=true
#############################################################################
[pop3]
# IPv4 address to listen for POP3 connections on.
ip4.address=0.0.0.0
# IPv4 port to listen for POP3 connections on.
ip4.port=1100
# used in POP3 greeting
domain=inbucket.local
# How long we allow a network connection to be idle before hanging up on the
# client, POP3 RFC requires at least 10 minutes (600 seconds).
max.idle.seconds=600
############################################################################# #############################################################################
[web] [web]

View File

@@ -37,13 +37,29 @@ max.recipients=100
# client, SMTP RFC recommends at least 5 minutes (300 seconds). # client, SMTP RFC recommends at least 5 minutes (300 seconds).
max.idle.seconds=300 max.idle.seconds=300
# Maximum allowable size of message body in bytes (including attachments) # Maximum allowable size of message body in bytes (including attachments)
max.message.bytes=2048000 max.message.bytes=2048000
# Should we place messages into the datastore, or just throw them away # Should we place messages into the datastore, or just throw them away
# (for load testing): true or false # (for load testing): true or false
store.messages=true store.messages=true
#############################################################################
[pop3]
# IPv4 address to listen for POP3 connections on.
ip4.address=0.0.0.0
# IPv4 port to listen for POP3 connections on.
ip4.port=110
# used in POP3 greeting
domain=inbucket.local
# How long we allow a network connection to be idle before hanging up on the
# client, POP3 RFC requires at least 10 minutes (600 seconds).
max.idle.seconds=600
############################################################################# #############################################################################
[web] [web]

View File

@@ -37,13 +37,29 @@ max.recipients=100
# client, SMTP RFC recommends at least 5 minutes (300 seconds). # client, SMTP RFC recommends at least 5 minutes (300 seconds).
max.idle.seconds=300 max.idle.seconds=300
# Maximum allowable size of message body in bytes (including attachments) # Maximum allowable size of message body in bytes (including attachments)
max.message.bytes=2048000 max.message.bytes=2048000
# Should we place messages into the datastore, or just throw them away # Should we place messages into the datastore, or just throw them away
# (for load testing): true or false # (for load testing): true or false
store.messages=true store.messages=true
#############################################################################
[pop3]
# IPv4 address to listen for POP3 connections on.
ip4.address=0.0.0.0
# IPv4 port to listen for POP3 connections on.
ip4.port=1100
# used in POP3 greeting
domain=inbucket.local
# How long we allow a network connection to be idle before hanging up on the
# client, POP3 RFC requires at least 10 minutes (600 seconds).
max.idle.seconds=600
############################################################################# #############################################################################
[web] [web]

View File

@@ -8,6 +8,7 @@ import (
"github.com/jhillyerd/inbucket/smtpd" "github.com/jhillyerd/inbucket/smtpd"
"io" "io"
"net" "net"
"os"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -46,6 +47,7 @@ var commands = map[string]bool{
"USER": true, "USER": true,
"PASS": true, "PASS": true,
"APOP": true, "APOP": true,
"CAPA": true,
} }
type Session struct { type Session struct {
@@ -91,7 +93,8 @@ func (s *Server) startSession(id int, conn net.Conn) {
}() }()
ses := NewSession(s, id, conn) ses := NewSession(s, id, conn)
ses.send("+OK Inbucket POP3 server ready") ses.send(fmt.Sprintf("+OK Inbucket POP3 server ready <%v.%v@%v>", os.Getpid(),
time.Now().Unix(), s.domain))
// This is our command reading loop // This is our command reading loop
for ses.state != QUIT && ses.sendError == nil { for ses.state != QUIT && ses.sendError == nil {
@@ -111,10 +114,14 @@ func (s *Server) startSession(id int, conn net.Conn) {
// Commands we handle in any state // Commands we handle in any state
switch cmd { switch cmd {
case "APOP": case "CAPA":
// These commands are not implemented in any state // List our capabilities per RFC2449
ses.send(fmt.Sprintf("-ERR %v command not implemented", cmd)) ses.send("+OK Capability list follows")
ses.warn("Command %v not implemented by Inbucket", cmd) ses.send("TOP")
ses.send("USER")
ses.send("UIDL")
ses.send("IMPLEMENTATION Inbucket")
ses.send(".")
continue continue
} }
@@ -191,6 +198,24 @@ func (ses *Session) authorizationHandler(cmd string, args []string) {
ses.send(fmt.Sprintf("+OK Found %v messages for %v", ses.msgCount, ses.user)) ses.send(fmt.Sprintf("+OK Found %v messages for %v", ses.msgCount, ses.user))
ses.enterState(TRANSACTION) ses.enterState(TRANSACTION)
} }
case "APOP":
if len(args) != 2 {
ses.warn("Expected two arguments for APOP")
ses.send("-ERR APOP requires two arguments")
return
}
ses.user = args[0]
var err error
ses.mailbox, err = ses.server.dataStore.MailboxFor(ses.user)
if err != nil {
ses.error("Failed to open mailbox for %v", ses.user)
ses.send(fmt.Sprintf("-ERR Failed to open mailbox for %v", ses.user))
ses.enterState(QUIT)
return
}
ses.loadMailbox()
ses.send(fmt.Sprintf("+OK Found %v messages for %v", ses.msgCount, ses.user))
ses.enterState(TRANSACTION)
default: default:
ses.ooSeq(cmd) ses.ooSeq(cmd)
} }

View File

@@ -12,11 +12,12 @@ import (
// Real server code starts here // Real server code starts here
type Server struct { type Server struct {
maxIdleSeconds int domain string
dataStore smtpd.DataStore maxIdleSeconds int
listener net.Listener dataStore smtpd.DataStore
shutdown bool listener net.Listener
waitgroup *sync.WaitGroup shutdown bool
waitgroup *sync.WaitGroup
} }
// Init a new Server object // Init a new Server object
@@ -24,7 +25,7 @@ func New() *Server {
// TODO is two filestores better/worse than sharing w/ smtpd? // TODO is two filestores better/worse than sharing w/ smtpd?
ds := smtpd.NewFileDataStore() ds := smtpd.NewFileDataStore()
cfg := config.GetPop3Config() cfg := config.GetPop3Config()
return &Server{dataStore: ds, maxIdleSeconds: cfg.MaxIdleSeconds, return &Server{domain: cfg.Domain, dataStore: ds, maxIdleSeconds: cfg.MaxIdleSeconds,
waitgroup: new(sync.WaitGroup)} waitgroup: new(sync.WaitGroup)}
} }