From aa7b7603853bce8d8406e8b8f85bb705f22837b7 Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Thu, 12 Sep 2013 22:07:24 -0700 Subject: [PATCH] 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 --- config/config.go | 9 +++++++++ etc/devel.conf | 3 +++ etc/inbucket.conf | 18 +++++++++++++++++- etc/unix-sample.conf | 18 +++++++++++++++++- etc/win-sample.conf | 18 +++++++++++++++++- pop3d/handler.go | 35 ++++++++++++++++++++++++++++++----- pop3d/listener.go | 13 +++++++------ 7 files changed, 100 insertions(+), 14 deletions(-) diff --git a/config/config.go b/config/config.go index 62c7f69..bfa1e5f 100644 --- a/config/config.go +++ b/config/config.go @@ -25,6 +25,7 @@ type SmtpConfig struct { type Pop3Config struct { Ip4address net.IP Ip4port int + Domain string MaxIdleSeconds int } @@ -109,6 +110,7 @@ func LoadConfig(filename string) error { requireOption(messages, "smtp", "store.messages") requireOption(messages, "pop3", "ip4.address") requireOption(messages, "pop3", "ip4.port") + requireOption(messages, "pop3", "domain") requireOption(messages, "pop3", "max.idle.seconds") requireOption(messages, "web", "ip4.address") requireOption(messages, "web", "ip4.port") @@ -262,6 +264,13 @@ func parsePop3Config() error { 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" pop3Config.MaxIdleSeconds, err = Config.Int(section, option) if err != nil { diff --git a/etc/devel.conf b/etc/devel.conf index bcca491..2ce5c4b 100644 --- a/etc/devel.conf +++ b/etc/devel.conf @@ -53,6 +53,9 @@ 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 diff --git a/etc/inbucket.conf b/etc/inbucket.conf index f8ca8b7..a6c9c81 100644 --- a/etc/inbucket.conf +++ b/etc/inbucket.conf @@ -37,13 +37,29 @@ max.recipients=100 # client, SMTP RFC recommends at least 5 minutes (300 seconds). 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 # Should we place messages into the datastore, or just throw them away # (for load testing): true or false 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] diff --git a/etc/unix-sample.conf b/etc/unix-sample.conf index f42c2f2..17b7b9d 100644 --- a/etc/unix-sample.conf +++ b/etc/unix-sample.conf @@ -37,13 +37,29 @@ max.recipients=100 # client, SMTP RFC recommends at least 5 minutes (300 seconds). 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 # Should we place messages into the datastore, or just throw them away # (for load testing): true or false 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] diff --git a/etc/win-sample.conf b/etc/win-sample.conf index a6ea92b..b0fcdcf 100644 --- a/etc/win-sample.conf +++ b/etc/win-sample.conf @@ -37,13 +37,29 @@ max.recipients=100 # client, SMTP RFC recommends at least 5 minutes (300 seconds). 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 # Should we place messages into the datastore, or just throw them away # (for load testing): true or false 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] diff --git a/pop3d/handler.go b/pop3d/handler.go index 01f10e5..3bacd51 100644 --- a/pop3d/handler.go +++ b/pop3d/handler.go @@ -8,6 +8,7 @@ import ( "github.com/jhillyerd/inbucket/smtpd" "io" "net" + "os" "strconv" "strings" "time" @@ -46,6 +47,7 @@ var commands = map[string]bool{ "USER": true, "PASS": true, "APOP": true, + "CAPA": true, } type Session struct { @@ -91,7 +93,8 @@ func (s *Server) startSession(id int, conn net.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 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 switch cmd { - case "APOP": - // These commands are not implemented in any state - ses.send(fmt.Sprintf("-ERR %v command not implemented", cmd)) - ses.warn("Command %v not implemented by Inbucket", cmd) + case "CAPA": + // List our capabilities per RFC2449 + ses.send("+OK Capability list follows") + ses.send("TOP") + ses.send("USER") + ses.send("UIDL") + ses.send("IMPLEMENTATION Inbucket") + ses.send(".") 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.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: ses.ooSeq(cmd) } diff --git a/pop3d/listener.go b/pop3d/listener.go index 7fc9d30..3bbfb73 100644 --- a/pop3d/listener.go +++ b/pop3d/listener.go @@ -12,11 +12,12 @@ import ( // Real server code starts here type Server struct { - maxIdleSeconds int - dataStore smtpd.DataStore - listener net.Listener - shutdown bool - waitgroup *sync.WaitGroup + domain string + maxIdleSeconds int + dataStore smtpd.DataStore + listener net.Listener + shutdown bool + waitgroup *sync.WaitGroup } // Init a new Server object @@ -24,7 +25,7 @@ func New() *Server { // TODO is two filestores better/worse than sharing w/ smtpd? ds := smtpd.NewFileDataStore() cfg := config.GetPop3Config() - return &Server{dataStore: ds, maxIdleSeconds: cfg.MaxIdleSeconds, + return &Server{domain: cfg.Domain, dataStore: ds, maxIdleSeconds: cfg.MaxIdleSeconds, waitgroup: new(sync.WaitGroup)} }