smtp

package module
v0.0.0-...-54a7c42 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 4, 2019 License: MIT Imports: 13 Imported by: 3

README

A Go SMTP Server

Introduction

I wanted a way to bring email as a first class citizen in go. I modeled the api much like the http package. There were a few translation errors that I'm still trying to work out, things that are harder due to the fact that smtp is a stateful protocol. I'd welcome feedback on this.

A Very simple server

The smtp package works by offering a muxer that will route incoming email to registered handlers. A simple server would register an 'all' handler, and then listen on the smtp port.

import "github.com/murphysean/smtp"

smtp.HandleFunc("*@*", func(envelope *smtp.Envelope) error {
	fmt.Println("Message Received", envelope.MessageTo)
	fmt.Println("From:", envelope.MessageFrom, envelope.RemoteAddr)
	fmt.Println("To:", envelope.MessageTo)
	fn := "emails/" + time.Now().Format(time.RFC3339) + ".eml"
	ioutil.WriteFile(fn, b, os.ModePerm)
	fmt.Println("Wrote to " + fn)
	return nil
}

log.Fatal(smtp.ListenAndServe(":smtp", nil))

Now all incoming messages will be logged and saved to the emails directory.

The Handler pattern has two parts, the local and the domain. The matching algorithm:

  1. Start with the domain
  2. If there is a match 1. Move to step 2
  3. If there is not a match 1. Start at step 1 with domain = "*"
  4. Check the local portion
  5. If there is a match 1. Move to step 3
  6. If there is not a match 1. Retry step 2 with local = "*"
  7. Call the registered handler

If there is no registered handler the server will return an error to the client and disregard the email.

Additional Options

Security

The smtp server also supports the STARTTLS option, if you use the ListenAndServeTLS variant. You can also further customize the tls config as well.

server := smtp.Server{Name: "example.com", Debug: true}
config := &tls.Config{MinVersion:tls.VersionSSL30}
server.TLSConfig = config
log.Fatal(server.ListenAndServeTLS(":smtp", "cert.pem", "key.pem", nil))
Naming and Debugging

As shown in the previous snippet you can also give your server a name (default = localhost). Naming lends credibility to your server, that some clients seem to require.

Debugging is pretty verbose and dumps the entire protocol out to stderr. It is really handy for troubleshooting particularly annoying clients.

Authentication

The smtp server also supports authentication via the PLAIN method. Ideally this would be coupled with STARTTLS to ensure secrecy of passwords in transit. You can do this by creating a custom server and registering the AUTH callback. This will be called everytime someone attempts to authenticate.

server.Auth = func(username, password, remoteAddress string) error {
	if username == "user" && password == "p@$$w0rd" {
		return nil
	}
	return errors.New("Nope!")
}
Addressing and preventing open-relay

Since your callback is only called once the smtp protocol has progressed to the data point, meaning the sender and recipient have been specified, the server also offers an Addressable callback that can be used to deny unknown recipients.

server.Addressable = func(user, address string) bool {
	if user != ""{
		//Allow relay for authenticated users
		return true
	}
	if strings.HasSuffix(address, "example.com"){
		return true
	}
	return false
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrorRequestedActionAbortedLocalError      = errors.New("Requested action aborted: local error in processing")
	ErrorTransactionFailed                     = errors.New("Transaction failed")
	ErrorServiceNotAvailable                   = errors.New("Service not available, closing transmission channel")
	ErrorRequestedActionAbortedExceededStorage = errors.New("Requested mail action aborted: exceeded storage allocation")
)
View Source
var DefaultServeMux = NewServeMux()

Functions

func CanonicalizeEmail

func CanonicalizeEmail(local string) string

I'm following googles example here. Basically all '.' dont matter in the local portion. Also you can append a '+' section to the end of your email and it will still route to you. This allows categorization of emails when they are given out. The muxer will canonicalize incoming email addresses to route them to a handler. The handler will still see the original email in the to portion.

func Handle

func Handle(pattern string, handler Handler)

func HandleFunc

func HandleFunc(pattern string, handler func(envelope *Envelope) error)

func ListenAndServe

func ListenAndServe(addr string, handler Handler) error

func ListenAndServeTLS

func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error

func ListenAndServeTLSWithContext

func ListenAndServeTLSWithContext(ctx context.Context, addr, certFile, keyFile string, handler Handler) error

func ListenAndServeWithContext

func ListenAndServeWithContext(ctx context.Context, addr string, handler Handler) error

func SplitAddress

func SplitAddress(address string) (string, string, error)

Types

type Envelope

type Envelope struct {
	FromAgent   string
	RemoteAddr  string
	User        string
	MessageFrom string
	MessageTo   string
	MessageData io.Reader
}

type Handler

type Handler interface {
	ServeSMTP(envelope *Envelope) error
}

type HandlerFunc

type HandlerFunc func(envelope *Envelope) error

func (HandlerFunc) ServeSMTP

func (f HandlerFunc) ServeSMTP(envelope *Envelope) error

type ServeMux

type ServeMux struct {
	// contains filtered or unexported fields
}

func NewServeMux

func NewServeMux() *ServeMux

func (*ServeMux) Handle

func (mux *ServeMux) Handle(pattern string, handler Handler)

Handle will register the given email pattern to be handled with the given handler. The Canonacalize method will be called on the pattern to help ensure expected matches will come through. The special '*' wildcard can be used for the local portion or the domain portion to broaden the match.

func (*ServeMux) HandleFunc

func (mux *ServeMux) HandleFunc(pattern string, handler func(envelope *Envelope) error)

func (*ServeMux) ServeSMTP

func (mux *ServeMux) ServeSMTP(envelope *Envelope) error

type Server

type Server struct {
	Name    string
	Addr    string
	Handler Handler
	// If a tls config is set then this server will broadcast support
	// for the STARTTLS (RFC3207) extension.
	TLSConfig *tls.Config

	// Auth specifies an optional callback function that is called
	// when a client attempts to authenticate. If left nil (the default)
	// then the AUTH extension will not be supported.
	Auth func(username, password, remoteAddress string) error

	// Addressable specifies an optional callback function that is called
	// when a client attempts to send a message to the given address. This
	// allows the server to refuse messages that it doesn't own. If left nil
	// (the default) then the server will assume true
	Addressable func(user, address string) bool

	Debug    bool
	ErrorLog *log.Logger
}

func (*Server) ListenAndServe

func (srv *Server) ListenAndServe(ctx context.Context) error

func (*Server) ListenAndServeTLS

func (srv *Server) ListenAndServeTLS(ctx context.Context, certFile string, keyFile string) error

func (*Server) Serve

func (srv *Server) Serve(ctx context.Context, l net.Listener) error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL