Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Makefile for mailrelay

# Binary name
BINARY_NAME=mailrelay

# Build directories
BUILD_DIR=build
LINUX_DIR=$(BUILD_DIR)/linux_amd64
WINDOWS_DIR=$(BUILD_DIR)/windows_amd64
OSX_DIR=$(BUILD_DIR)/osx_amd64
OPENBSD_DIR=$(BUILD_DIR)/openbsd_amd64
ARM_DIR=$(BUILD_DIR)/linux_arm64

# Default target
.PHONY: all
all: build

# Build for current architecture
.PHONY: build
build:
go build -o $(BINARY_NAME)

# Run tests
.PHONY: test
test:
go test ./...

# Build for all supported architectures
.PHONY: buildall
buildall: clean
@echo "Building for all architectures..."
@mkdir -p $(LINUX_DIR) $(WINDOWS_DIR) $(OSX_DIR) $(OPENBSD_DIR) $(ARM_DIR)
@echo "Building Linux AMD64..."
env GOOS=linux GOARCH=amd64 go build -o $(LINUX_DIR)/$(BINARY_NAME)-linux-amd64
@echo "Building Windows AMD64..."
env GOOS=windows GOARCH=amd64 go build -o $(WINDOWS_DIR)/$(BINARY_NAME)-windows-amd64.exe
@echo "Building macOS AMD64..."
env GOOS=darwin GOARCH=amd64 go build -o $(OSX_DIR)/$(BINARY_NAME)-osx-amd64
@echo "Building OpenBSD AMD64..."
env GOOS=openbsd GOARCH=amd64 go build -o $(OPENBSD_DIR)/$(BINARY_NAME)-openbsd-amd64
@echo "Building Linux ARM64..."
env GOOS=linux GOARCH=arm64 go build -o $(ARM_DIR)/$(BINARY_NAME)-linux-arm64
@echo "Build complete!"

# Clean build artifacts
.PHONY: clean
clean:
rm -rf $(BUILD_DIR)
rm -f $(BINARY_NAME)

# Run the application
.PHONY: run
run: build
./$(BINARY_NAME)

# Run with test configuration
.PHONY: test-config
test-config: build
./$(BINARY_NAME) -config=./mailrelay.json -test -sender=test@example.com -rcpt=recipient@example.com

# Check IP address
.PHONY: check-ip
check-ip: build
./$(BINARY_NAME) -config=./mailrelay.json -checkIP -ip=$(IP)

# Show help
.PHONY: help
help:
@echo "Available targets:"
@echo " build - Build for current architecture"
@echo " buildall - Build for all supported architectures"
@echo " test - Run tests"
@echo " clean - Remove build artifacts"
@echo " run - Build and run the application"
@echo " test-config - Test configuration with sample email"
@echo " check-ip - Check if IP is allowed (use IP=x.x.x.x)"
@echo " help - Show this help message"
104 changes: 104 additions & 0 deletions auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package main

import (
"net/smtp"
"testing"

"github.com/stretchr/testify/assert"
)

func TestLoginAuth(t *testing.T) {
auth := LoginAuth("testuser", "testpass")
assert.NotNil(t, auth)

// Type assertion to ensure we get the right type
loginAuth, ok := auth.(*loginAuth)
assert.True(t, ok)
assert.Equal(t, "testuser", loginAuth.username)
assert.Equal(t, "testpass", loginAuth.password)
}

func TestLoginAuthStart(t *testing.T) {
auth := &loginAuth{
username: "testuser",
password: "testpass",
}

method, resp, err := auth.Start(&smtp.ServerInfo{})

assert.NoError(t, err)
assert.Equal(t, "LOGIN", method)
assert.Empty(t, resp)
}

func TestLoginAuthNext(t *testing.T) {
auth := &loginAuth{
username: "testuser",
password: "testpass",
}

tests := []struct {
name string
serverMsg string
more bool
expected string
expectErr bool
}{
{
name: "username prompt - User Name",
serverMsg: "User Name",
more: true,
expected: "testuser",
expectErr: false,
},
{
name: "username prompt - Username:",
serverMsg: "Username:",
more: true,
expected: "testuser",
expectErr: false,
},
{
name: "password prompt - Password",
serverMsg: "Password",
more: true,
expected: "testpass",
expectErr: false,
},
{
name: "password prompt - Password:",
serverMsg: "Password:",
more: true,
expected: "testpass",
expectErr: false,
},
{
name: "unknown server response",
serverMsg: "Unknown Prompt",
more: true,
expected: "",
expectErr: true,
},
{
name: "more is false",
serverMsg: "anything",
more: false,
expected: "",
expectErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resp, err := auth.Next([]byte(tt.serverMsg), tt.more)

if tt.expectErr {
assert.Error(t, err)
assert.Contains(t, err.Error(), "unknown server response")
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expected, string(resp))
}
})
}
}
21 changes: 0 additions & 21 deletions build.sh

This file was deleted.

5 changes: 3 additions & 2 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net"
"net/smtp"
"net/textproto"
"strconv"

"github.com/flashmob/go-guerrilla/mail"
"github.com/pkg/errors"
Expand All @@ -19,7 +20,7 @@ type closeable interface {

// sendMail sends the contents of the envelope to a SMTP server.
func sendMail(e *mail.Envelope, config *relayConfig) error {
server := fmt.Sprintf("%s:%d", config.Server, config.Port)
server := net.JoinHostPort(config.Server, strconv.Itoa(config.Port))
to := getTo(e)

var msg bytes.Buffer
Expand All @@ -36,7 +37,7 @@ func sendMail(e *mail.Envelope, config *relayConfig) error {

if AllowedSendersFilter.Blocked(e.RemoteIP) {
Logger.Info("Remote IP of " + e.RemoteIP + " not allowed to send email.")
return errors.Wrap(err, "Remote IP of "+e.RemoteIP+" not allowed to send email.")
return errors.New("Remote IP of " + e.RemoteIP + " not allowed to send email.")
}

tlsconfig := &tls.Config{
Expand Down
105 changes: 105 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package main

import (
"net/textproto"
"testing"

"github.com/flashmob/go-guerrilla/mail"
"github.com/stretchr/testify/assert"
)

func TestGetTo(t *testing.T) {
tests := []struct {
name string
envelope *mail.Envelope
expected []string
}{
{
name: "single recipient",
envelope: &mail.Envelope{
RcptTo: []mail.Address{
{User: "user1", Host: "example.com"},
},
},
expected: []string{"user1@example.com"},
},
{
name: "multiple recipients",
envelope: &mail.Envelope{
RcptTo: []mail.Address{
{User: "user1", Host: "example.com"},
{User: "user2", Host: "test.com"},
{User: "admin", Host: "company.org"},
},
},
expected: []string{
"user1@example.com",
"user2@test.com",
"admin@company.org",
},
},
{
name: "no recipients",
envelope: &mail.Envelope{RcptTo: []mail.Address{}},
expected: nil,
},
{
name: "nil envelope recipients",
envelope: &mail.Envelope{RcptTo: nil},
expected: nil,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := getTo(tt.envelope)
assert.Equal(t, tt.expected, result)
})
}
}

func TestIsQuitError(t *testing.T) {
tests := []struct {
name string
err error
expected bool
}{
{
name: "nil error",
err: nil,
expected: false,
},
{
name: "SMTP 221 code (acceptable)",
err: &textproto.Error{Code: 221, Msg: "Bye"},
expected: false,
},
{
name: "SMTP 250 code (acceptable)",
err: &textproto.Error{Code: 250, Msg: "OK"},
expected: false,
},
{
name: "SMTP 550 error code",
err: &textproto.Error{Code: 550, Msg: "Mailbox not found"},
expected: true,
},
{
name: "SMTP 421 error code",
err: &textproto.Error{Code: 421, Msg: "Service not available"},
expected: true,
},
{
name: "non-textproto error",
err: assert.AnError,
expected: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isQuitError(tt.err)
assert.Equal(t, tt.expected, result)
})
}
}
Loading