Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #3

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ Simple smtp server library for GO. Each received message will call the handler,
package main

import (
"github.com/dutchcoders/smtpd"
"os"
"context"
"fmt"
"log"
"os"

"github.com/dutchcoders/smtpd"
)

func main() {
Expand All @@ -19,8 +22,18 @@ func main() {
return nil
})

addr := fmt.Sprintf(":%s", os.Getenv("PORT"))
smtpd.ListenAndServe(addr)
listener := smtpd.NewListener(
smtpd.ListenWithPort(os.Getenv("PORT")),
)

server, err := smtpd.New(
smtpd.WithListener(listener),
)
if err != nil {
panic(err)
}

log.Fatal(server.ListenAndServe(context.Background()))
}
```

Expand Down
36 changes: 27 additions & 9 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,41 @@ package smtpd

import (
"fmt"
"strconv"
)

type Config struct {
Listeners []Listener
}

func WithListener(l Listener) func(*Config) error {
//WithListener takes 1 or more Listener structs to serve on.
func WithListener(ll ...Listener) func(*Config) error {
return func(cfg *Config) error {
switch {
case l.Address == "":
return fmt.Errorf("Required field Listener.Address is empty!")
case l.Port == "":
return fmt.Errorf("Required field Listener.Port is empty!")
case l.Mode == "":
l.Mode = "plain"
if len(ll) == 0 {
return fmt.Errorf("Got no listeners to configure.")
}
cfg.Listeners = append(cfg.Listeners, l)

for n, l := range ll {
if l.ID == "" {
l.ID = strconv.Itoa(n)
}

if l.Mode == "" {
l.Mode = "plain"
}

if l.Port == "" {
return fmt.Errorf("[%s] Required field \"Port\" is empty!", l.ID)
}

if l.Mode == "tls" || l.Mode == "starttls" {
if l.TLSConfig == nil {
return fmt.Errorf("[%s] Mode 'tls/starttls' requires a tls config.", l.ID)
}
}
}

cfg.Listeners = append(cfg.Listeners, ll...)
return nil
}
}
80 changes: 71 additions & 9 deletions config_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,66 @@
package smtpd

import "testing"
import (
"crypto/tls"
"testing"
)

func TestWithListenerTLSNoConfig(t *testing.T) {
l := Listener{
Port: "8025",
Mode: "tls",
}

err := WithListener(l)(&Config{})
if err == nil {
t.Error("Mode 'tls' without tls config did not return an error.")
}

l.Mode = "starttls"

err = WithListener(l)(&Config{})
if err == nil {
t.Error("Mode 'starttls' without tls config did not return an error.")
}
}

func TestWithListenerTLSAndConfig(t *testing.T) {
l := Listener{
Port: "8025",
TLSConfig: &tls.Config{},
Mode: "tls",
}

err := WithListener(l)(&Config{})
if err != nil {
t.Errorf("Mode 'tls' with tls config gives error: %v", err)
}

l.Mode = "starttls"

err = WithListener(l)(&Config{})
if err != nil {
t.Errorf("Mode 'starttls' with tls config gives error: %v", err)
}
}

func TestWithListener(t *testing.T) {
l := Listener{
Address: "127.0.0.1",
Port: "8025",
Mode: "tls",
ID: "test",
Address: "127.0.0.1",
Port: "8025",
Mode: "tls",
Banner: func() string { return "test" },
TLSConfig: &tls.Config{},
Handler: DefaultServeMux,
}

cfg := &Config{}

_ = WithListener(l)(cfg)
err := WithListener(l)(cfg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

got := cfg.Listeners

Expand All @@ -30,6 +79,22 @@ func TestWithListener(t *testing.T) {
if got[0].Mode != l.Mode {
t.Errorf("Listener.Address got %s, want %s", got[0].Mode, l.Mode)
}

if got[0].ID != l.ID {
t.Errorf("Listener.ID got %s, want %s", got[0].ID, l.ID)
}

if s := got[0].Banner(); s != "test" {
t.Errorf("Listener.Banner got %s, want %s", s, "test")
}

if got[0].TLSConfig == nil {
t.Error("Listener.TLSConfig got <nil>")
}

if got[0].Handler != DefaultServeMux {
t.Error("Listener.Handler is not DefaultServeMux")
}
}

func TestWithListenerError(t *testing.T) {
Expand All @@ -45,10 +110,7 @@ func TestWithListenerError(t *testing.T) {
}

func TestWithListenerDefaultMode(t *testing.T) {
l := Listener{
Address: "127.0.0.1",
Port: "8025",
}
l := NewListener(ListenWithPort("25"))

cfg := &Config{}

Expand Down
36 changes: 26 additions & 10 deletions listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,41 @@ package smtpd
import "crypto/tls"

type Listener struct {
ID string
Address string
Port string
Mode string //smtp modes: 'plain (25)', 'tls (465)' or 'starttls (587)'
Banner func() string
//ID optional text to identify the listener in the logs.
ID string

//Address optional network address to listen on. Default: localhost
Address string

//Port required port to listen on.
Port string

//Mode optional smtp mode to use.
//smtp modes: 'plain (25)', 'tls (465)' or 'starttls (587)'
Mode string

//Banner optional function returning the banner text shown to clients.
Banner func() string

//TLSConfig optional tls configuration.
//note: mode 'tls' and 'starttls' require one,
// in mode 'plain' STARTTLS command will not be available without a config.
TLSConfig *tls.Config
Handler Handler

//Handler optional handler(s) for this listener. Default: DefaultHandler
Handler Handler
}

func NewListener(options ...func(*Listener)) {
l := &Listener{
ID: "-",
func NewListener(options ...func(*Listener)) Listener {
l := Listener{
Mode: "plain",
Banner: func() string { return "DutchCoders SMTPd" },
}

for _, opt := range options {
opt(l)
opt(&l)
}
return l
}

func ListenWithID(id string) func(*Listener) {
Expand Down
2 changes: 2 additions & 0 deletions smtpd.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ type smtpServer struct {

var ErrServerClosed = errors.New("SMTPd Closed.")

//ListenAndServe starts serving smtp on the configured listeners.
//Always returns an error.
func (s *Server) ListenAndServe(ctx context.Context) error {
//lctx, cancel := context.WithCancel(ctx)
//defer cancel()
Expand Down