Skip to content

Commit

Permalink
Added support for ENHANCEDSTATUSCODES
Browse files Browse the repository at this point in the history
Support for ENHANCEDSTATUSCODES (rfc3463) - Issue flashmob#34
  • Loading branch information
phires committed Jan 23, 2017
1 parent 4b244cc commit f4d3bdd
Show file tree
Hide file tree
Showing 8 changed files with 326 additions and 65 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ test: *.go */*.go */*/*.go
$(GO_VARS) $(GO) test -v .
$(GO_VARS) $(GO) test -v ./tests
$(GO_VARS) $(GO) test -v ./cmd/guerrillad
$(GO_VARS) $(GO) test -v ./response
46 changes: 26 additions & 20 deletions cmd/guerrillad/serve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@ import (
"bytes"
"crypto/tls"
"encoding/json"
log "github.com/Sirupsen/logrus"
"github.com/flashmob/go-guerrilla"
"github.com/flashmob/go-guerrilla/backends"
test "github.com/flashmob/go-guerrilla/tests"
"github.com/flashmob/go-guerrilla/tests/testcert"
"github.com/spf13/cobra"
"io/ioutil"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"sync"
"testing"
"time"

log "github.com/Sirupsen/logrus"
"github.com/flashmob/go-guerrilla"
"github.com/flashmob/go-guerrilla/backends"
test "github.com/flashmob/go-guerrilla/tests"
"github.com/flashmob/go-guerrilla/tests/testcert"
"github.com/spf13/cobra"
)

var configJsonA = `
Expand Down Expand Up @@ -343,16 +345,20 @@ func TestServe(t *testing.T) {
ioutil.WriteFile("configJsonA.json", []byte(configJsonB), 0644)

// test SIGHUP via the kill command
ecmd := exec.Command("kill", "-HUP", string(data))
_, err = ecmd.Output()
if err != nil {
t.Error("could not SIGHUP", err)
t.FailNow()
}
time.Sleep(time.Second) // allow sighup to do its job
// did the pidfile change as expected?
if _, err := os.Stat("./pidfile2.pid"); os.IsNotExist(err) {
t.Error("pidfile not changed after sighup SIGHUP", err)
// Would not work on windows as kill is not available.
// TODO: Implement an alternative test for windows.
if runtime.GOOS != "windows" {
ecmd := exec.Command("kill", "-HUP", string(data))
_, err = ecmd.Output()
if err != nil {
t.Error("could not SIGHUP", err)
t.FailNow()
}
time.Sleep(time.Second) // allow sighup to do its job
// did the pidfile change as expected?
if _, err := os.Stat("./pidfile2.pid"); os.IsNotExist(err) {
t.Error("pidfile not changed after sighup SIGHUP", err)
}
}
// send kill signal and wait for exit
sigKill()
Expand Down Expand Up @@ -681,7 +687,7 @@ func TestAllowedHostsEvent(t *testing.T) {
t.Error("Expected", expect, "but got", result)
} else {
if result, err = test.Command(conn, buffin, "RCPT TO:[email protected]"); err == nil {
expect := "454 Error: Relay access denied: grr.la"
expect := "454 4.1.1 Error: Relay access denied: grr.la"
if strings.Index(result, expect) != 0 {
t.Error("Expected:", expect, "but got:", result)
}
Expand Down Expand Up @@ -714,7 +720,7 @@ func TestAllowedHostsEvent(t *testing.T) {
t.Error("Expected", expect, "but got", result)
} else {
if result, err = test.Command(conn, buffin, "RCPT TO:[email protected]"); err == nil {
expect := "250 OK"
expect := "250 2.1.5 OK"
if strings.Index(result, expect) != 0 {
t.Error("Expected:", expect, "but got:", result)
}
Expand Down Expand Up @@ -791,7 +797,7 @@ func TestTLSConfigEvent(t *testing.T) {
t.Error("Expected", expect, "but got", result)
} else {
if result, err = test.Command(conn, buffin, "STARTTLS"); err == nil {
expect := "220 Ready to start TLS"
expect := "220 2.0.0 Ready to start TLS"
if strings.Index(result, expect) != 0 {
t.Error("Expected:", expect, "but got:", result)
} else {
Expand Down Expand Up @@ -894,7 +900,7 @@ func TestBadTLS(t *testing.T) {
t.Error("Expected", expect, "but got", result)
} else {
if result, err = test.Command(conn, buffin, "STARTTLS"); err == nil {
expect := "220 Ready to start TLS"
expect := "220 2.0.0 Ready to start TLS"
if strings.Index(result, expect) != 0 {
t.Error("Expected:", expect, "but got:", result)
} else {
Expand Down
195 changes: 195 additions & 0 deletions response/enhanced.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package response

import (
"fmt"
"strconv"
)

const (
// ClassSuccess specifies that the DSN is reporting a positive delivery
// action. Detail sub-codes may provide notification of
// transformations required for delivery.
ClassSuccess = 2
// ClassTransientFailure - a persistent transient failure is one in which the message as
// sent is valid, but persistence of some temporary condition has
// caused abandonment or delay of attempts to send the message.
// If this code accompanies a delivery failure report, sending in
// the future may be successful.
ClassTransientFailure = 4
// ClassPermanentFailure - a permanent failure is one which is not likely to be resolved
// by resending the message in the current form. Some change to
// the message or the destination must be made for successful
// delivery.
ClassPermanentFailure = 5
)

// codeMap for mapping Enhanced Status Code to Basic Code
// Mapping according to https://www.iana.org/assignments/smtp-enhanced-status-codes/smtp-enhanced-status-codes.xml
// This might not be entierly useful
var codeMap = struct {
m map[string]int
}{m: map[string]int{
"2.1.5": 250,
"2.3.0": 250,
"2.5.0": 250,
"2.6.4": 250,
"2.6.8": 252,
"2.7.0": 220,
"4.1.1": 451,
"4.1.8": 451,
"4.2.4": 450,
"4.3.0": 421,
"4.3.1": 452,
"4.3.2": 453,
"4.4.1": 451,
"4.4.2": 421,
"4.4.3": 451,
"4.4.5": 451,
"4.5.0": 451,
"4.5.1": 430,
"4.5.3": 452,
"4.5.4": 451,
"4.7.0": 450,
"4.7.1": 451,
"4.7.12": 422,
"4.7.15": 450,
"4.7.24": 451,
"5.1.1": 550,
"5.1.3": 501,
"5.1.8": 501,
"5.1.10": 556,
"5.2.2": 552,
"5.2.3": 552,
"5.3.0": 550,
"5.3.4": 552,
"5.4.3": 550,
"5.5.0": 501,
"5.5.1": 500,
"5.5.2": 500,
"5.5.4": 501,
"5.5.6": 500,
"5.6.3": 554,
"5.6.6": 554,
"5.6.7": 553,
"5.6.8": 550,
"5.6.9": 550,
"5.7.0": 550,
"5.7.1": 551,
"5.7.2": 550,
"5.7.4": 504,
"5.7.8": 554,
"5.7.9": 534,
"5.7.10": 523,
"5.7.11": 524,
"5.7.13": 525,
"5.7.14": 535,
"5.7.15": 550,
"5.7.16": 552,
"5.7.17": 500,
"5.7.18": 500,
"5.7.19": 500,
"5.7.20": 550,
"5.7.21": 550,
"5.7.22": 550,
"5.7.23": 550,
"5.7.24": 550,
"5.7.25": 550,
"5.7.26": 550,
"5.7.27": 550,
}}

// DefaultMap contains defined default codes (RfC 3463)
const (
OtherStatus = ".0.0"
OtherAddressStatus = ".1.0"
BadDestinationMailboxAddress = ".1.1"
BadDestinationSystemAddress = ".1.2"
BadDestinationMailboxAddressSyntax = ".1.3"
DestinationMailboxAddressAmbiguous = ".1.4"
DestinationMailboxAddressValid = ".1.5"
MailboxHasMoved = ".1.6"
BadSendersMailboxAddressSyntax = ".1.7"
BadSendersSystemAddress = ".1.8"
OtherOrUndefinedMailboxStatus = ".2.0"
MailboxDisabled = ".2.1"
MailboxFull = ".2.2"
MessageLengthExceedsAdministrativeLimit = ".2.3"
MailingListExpansionProblem = ".2.4"
OtherOrUndefinedMailSystemStatus = ".3.0"
MailSystemFull = ".3.1"
SystemNotAcceptingNetworkMessages = ".3.2"
SystemNotCapableOfSelectedFeatures = ".3.3"
MessageTooBigForSystem = ".3.4"
OtherOrUndefinedNetworkOrRoutingStatus = ".4.0"
NoAnswerFromHost = ".4.1"
BadConnection = ".4.2"
RoutingServerFailure = ".4.3"
UnableToRoute = ".4.4"
NetworkCongestion = ".4.5"
RoutingLoopDetected = ".4.6"
DeliveryTimeExpired = ".4.7"
OtherOrUndefinedProtocolStatus = ".5.0"
InvalidCommand = ".5.1"
SyntaxError = ".5.2"
TooManyRecipients = ".5.3"
InvalidCommandArguments = ".5.4"
WrongProtocolVersion = ".5.5"
OtherOrUndefinedMediaError = ".6.0"
MediaNotSupported = ".6.1"
ConversionRequiredAndProhibited = ".6.2"
ConversionRequiredButNotSupported = ".6.3"
ConversionWithLossPerformed = ".6.4"
ConversionFailed = ".6.5"
)

// TODO: More defaults needed....
var defaultTexts = struct {
m map[string]string
}{m: map[string]string{
"2.0.0": "OK",
"2.1.0": "OK",
"2.1.5": "Recipient valid",
"2.5.0": "OK",
"4.5.3": "Too many recipients",
"4.5.4": "Relay access denied",
"5.5.1": "Invalid command",
}}

// CustomString builds an enhanced status code string using your custom string and basic code
func CustomString(enhancedCode string, basicCode, class int, comment string) string {
e := buildEnhancedResponseFromDefaultStatus(class, enhancedCode)
return fmt.Sprintf("%d %s %s", basicCode, e, comment)
}

// String builds an enhanced status code string
func String(enhancedCode string, class int) string {
e := buildEnhancedResponseFromDefaultStatus(class, enhancedCode)
basicCode := getBasicStatusCode(e)
comment := defaultTexts.m[enhancedCode]

if len(comment) == 0 {
switch class {
case 2:
comment = "OK"
case 4:
comment = "Temporary failure."
case 5:
comment = "Permanent failure."
}
}
return CustomString(enhancedCode, basicCode, class, comment)
}

func getBasicStatusCode(enhancedStatusCode string) int {
if val, ok := codeMap.m[enhancedStatusCode]; ok {
return val
}
// Fallback if code is not defined
fb, _ := strconv.Atoi(fmt.Sprintf("%c00", enhancedStatusCode[0]))
return fb
}

func buildEnhancedResponseFromDefaultStatus(c int, status string) string {
// Construct code
return fmt.Sprintf("%d%s", c, status)
}
51 changes: 51 additions & 0 deletions response/enhanced_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package response

import "testing"

func TestClass(t *testing.T) {
if ClassPermanentFailure != 5 {
t.Error("ClassPermanentFailure is not 5")
}
if ClassTransientFailure != 4 {
t.Error("ClassTransientFailure is not 4")
}
if ClassSuccess != 2 {
t.Error("ClassSuccess is not 2")
}
}

func TestGetBasicStatusCode(t *testing.T) {
// Known status code
a := getBasicStatusCode("2.5.0")
if a != 250 {
t.Errorf("getBasicStatusCode. Int \"%d\" not expected.", a)
}

// Unknown status code
b := getBasicStatusCode("2.0.0")
if b != 200 {
t.Errorf("getBasicStatusCode. Int \"%d\" not expected.", b)
}
}

// TestString for the String function
func TestCustomString(t *testing.T) {
// Basic testing
a := CustomString(OtherStatus, 200, ClassSuccess, "Test")
if a != "200 2.0.0 Test" {
t.Errorf("CustomString failed. String \"%s\" not expected.", a)
}

// Default String
b := String(OtherStatus, ClassSuccess)
if b != "200 2.0.0 OK" {
t.Errorf("String failed. String \"%s\" not expected.", b)
}
}

func TestBuildEnhancedResponseFromDefaultStatus(t *testing.T) {
a := buildEnhancedResponseFromDefaultStatus(ClassPermanentFailure, InvalidCommand)
if a != "5.5.1" {
t.Errorf("buildEnhancedResponseFromDefaultStatus failed. String \"%s\" not expected.", a)
}
}
Loading

0 comments on commit f4d3bdd

Please sign in to comment.