smtp

package module
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 2020 License: MIT Imports: 19 Imported by: 0

README

go-smtp

GoDoc codecov

This is a forked version of go-smtp, changed API to function options. Added NewDefaultBackend and DefaultSession for easier usage. Client has a own package. Added XForward support

Features

  • ESMTP client & server implementing RFC 5321
  • Support for SMTP AUTH and PIPELINING
  • UTF-8 support for subject and message
  • LMTP support
  • XFORWARD support
  • Since v1.1.2: Keep \r\n in Data Reader (textproto DotReader replaces it to \n)
SMTP Server
package main

import (
	"io"
	"io/ioutil"
	"log"
	"time"

	"github.com/mschneider82/go-smtp"
)

// A Session is returned after successful login.
type Session struct{
	smtp.DefaultSession
}


func (s *Session) Data(r io.Reader, d smtp.DataContext) error {
	if b, err := ioutil.ReadAll(r); err != nil {
		return err
	} else {
		log.Println("Data:", string(b))
	}
	return nil
}

func main() {
	err := smtp.NewServer(
		smtp.NewDefaultBackend(&Session{}),
		smtp.Addr(":1025"),
		smtp.Domain("localhost"),
		smtp.WriteTimeout(10*time.Second),
		smtp.ReadTimeout(10*time.Second),
		smtp.MaxMessageBytes(1024*1024),
		smtp.MaxRecipients(50),
		smtp.AllowInsecureAuth(),
		smtp.DisableAuth(),
	).ListenAndServe()

	if err != nil {
		log.Fatal(err)
	}
}

You can use the server manually with telnet:

$ telnet localhost 1025
EHLO localhost
AUTH PLAIN
AHVzZXJuYW1lAHBhc3N3b3Jk
MAIL FROM:<root@nsa.gov>
RCPT TO:<root@gchq.gov.uk>
DATA
Hey <3
.
LMTP Server
package main

import (
	"context"
	"io"
	"io/ioutil"
	"log"
	"time"
	"fmt"

	"github.com/mschneider82/go-smtp"
)

// Session is returned for every connection
type Session struct{
	smtp.DefaultSession
}

func (s *Session) Data(r io.Reader, dataContext smtp.DataContext) error {
	mailBytes, err := ioutil.ReadAll(r)
	if err != nil {
		return err
	} 
	
	globalCtx := context.Background()

	for i, rcpt := range s.Rcpts {
		rcptCtx, _ := context.WithTimeout(globalCtx, 2*time.Second)
		// we have to assing i and rcpt new to access them in the go() routine
		rcpt := rcpt
		i := i

		dataContext.StartDelivery(rcptCtx, rcpt)
		go func() {
			// normaly we would deliver the mailBytes to our Maildir/HTTP backend
			// in this case we just do a sleep 
			time.Sleep(time.Duration(2+i) * time.Second)
			fmt.Println(string(mailBytes))
            // Lets finish with OK (if the request wasn't canceled because of the ctx timeout)
			dataContext.SetStatus(rcpt, &smtp.SMTPError{
				Code: 250,
				EnhancedCode:  smtp.EnhancedCode{2, 0, 0},
				Message: "Finished",
			})
		}()

	}
	// we always return nil in LMTP because every rcpt return code was set with dataContext.SetStatus()
	return nil
}

func main() {
	err := smtp.NewServer(
		smtp.NewDefaultBackend(&Session{}),
		smtp.Addr(":1025"),
		smtp.Domain("localhost"),
		smtp.WriteTimeout(10*time.Second),
		smtp.ReadTimeout(10*time.Second),
		smtp.MaxMessageBytes(1024*1024),
		smtp.MaxRecipients(50),
		smtp.AllowInsecureAuth(),
		smtp.DisableAuth(),
		smtp.LMTP(),
	).ListenAndServe()

	if err != nil {
		log.Fatal(err)
	}
}

You can use the server manually with telnet:

$ telnet localhost 1025
LHLO localhost
MAIL FROM:<from@example.com>
RCPT TO:<rcpt1@example.com>
RCPT TO:<rcpt2@example.com>
RCPT TO:<rcpt3@example.com>
DATA
Hey <3
.
250 2.0.0 <rcpt1@example.com> Finished
420 4.4.7 <rcpt2@example.com> Error: timeout reached
420 4.4.7 <rcpt3@example.com> Error: timeout reached

Licence

MIT

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrAuthRequired    = errors.New("Please authenticate first")
	ErrAuthUnsupported = errors.New("Authentication not supported")
)
View Source
var EnhancedCodeNotSet = EnhancedCode{0, 0, 0}

EnhancedCodeNotSet is a nil value of EnhancedCode field in SMTPError, used to indicate that backend failed to provide enhanced status code. X.0.0 will be used (X is derived from error code).

View Source
var ErrDataTooLarge = &SMTPError{
	Code:         552,
	EnhancedCode: EnhancedCode{5, 3, 4},
	Message:      "Maximum message size exceeded",
}
View Source
var NoEnhancedCode = EnhancedCode{-1, -1, -1}

NoEnhancedCode is used to indicate that enhanced error code should not be included in response.

Note that RFC 2034 requires an enhanced code to be included in all 2xx, 4xx and 5xx responses. This constant is exported for use by extensions, you should probably use EnhancedCodeNotSet instead.

Functions

func CanonicalMIMEHeaderKey

func CanonicalMIMEHeaderKey(s string) string

CanonicalMIMEHeaderKey returns the canonical format of the MIME header key s. The canonicalization converts the first letter and any letter following a hyphen to upper case; the rest are converted to lowercase. For example, the canonical key for "accept-encoding" is "Accept-Encoding". MIME header keys are assumed to be ASCII only. If s contains a space or invalid header field bytes, it is returned without modifications.

Types

type Backend

type Backend interface {
	// Authenticate a user. Return smtp.ErrAuthUnsupported if you don't want to
	// support this.
	Login(state *ConnectionState, username, password string) (Session, error)

	// Called if the client attempts to send mail without logging in first.
	// Return smtp.ErrAuthRequired if you don't want to support this.
	AnonymousLogin(state *ConnectionState) (Session, error)
}

A SMTP server backend.

func NewDefaultBackend

func NewDefaultBackend(s SessionFactory) Backend

NewDefaultBackend creates a backend without Authentication

type Conn

type Conn struct {
	XForward *XForward
	// contains filtered or unexported fields
}

func (*Conn) Close

func (c *Conn) Close() error

func (*Conn) ReadLine

func (c *Conn) ReadLine() (string, error)

Reads a line of input

func (*Conn) Reject

func (c *Conn) Reject()

func (*Conn) Server

func (c *Conn) Server() *Server

func (*Conn) Session

func (c *Conn) Session() Session

func (*Conn) SetSession

func (c *Conn) SetSession(session Session)

Setting the user resets any message being generated

func (*Conn) State

func (c *Conn) State() ConnectionState

func (*Conn) TLSConnectionState

func (c *Conn) TLSConnectionState() (state tls.ConnectionState, ok bool)

TLSConnectionState returns the connection's TLS connection state. Zero values are returned if the connection doesn't use TLS.

func (*Conn) WriteResponse

func (c *Conn) WriteResponse(code int, enhCode EnhancedCode, text ...string)

type ConnectionState

type ConnectionState struct {
	Hostname   string
	RemoteAddr net.Addr
	TLS        tls.ConnectionState
}

type DataContext

type DataContext interface {
	// SetStatus is used for LMTP only to set the answer for an Recipient
	SetStatus(rcpt string, status *SMTPError)
	// SetSMTPResponse can be used to overwrite default SMTP Accept Message after DATA finished (not for LMTP)
	SetSMTPResponse(response *SMTPError)
	StartDelivery(ctx context.Context, rcpt string)
	GetXForward() XForward
	GetHelo() string
}

type DefaultBackend

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

The DefaultBackend

func (*DefaultBackend) AnonymousLogin

func (be *DefaultBackend) AnonymousLogin(state *ConnectionState) (Session, error)

AnonymousLogin is not implemented in default backend

func (*DefaultBackend) Login

func (be *DefaultBackend) Login(state *ConnectionState, username, password string) (Session, error)

Login returns a session

type DefaultSession

type DefaultSession struct {
	From  string
	Rcpts []string
}

A DefaultSession can be used as a basic implementation for Session Interface It has already a From and Rcpts[] field, mostly used to embett in your own Session struct.

func (*DefaultSession) Data

func (s *DefaultSession) Data(r io.Reader, sc DataContext) error

func (*DefaultSession) Logout

func (s *DefaultSession) Logout() error

func (*DefaultSession) Mail

func (s *DefaultSession) Mail(from string) error

func (*DefaultSession) Rcpt

func (s *DefaultSession) Rcpt(to string) error

func (*DefaultSession) Reset

func (s *DefaultSession) Reset()

type EnhancedCode

type EnhancedCode [3]int

type Logger

type Logger interface {
	Printf(format string, v ...interface{})
	Println(v ...interface{})
}

Logger interface is used by Server to report unexpected internal errors.

type Option

type Option interface {
	// contains filtered or unexported methods
}

An Option configures a Server using functional options

func Addr

func Addr(addr string) Option

func AllowInsecureAuth

func AllowInsecureAuth() Option

func AllowXForward

func AllowXForward() Option

func DebugToWriter

func DebugToWriter(i io.Writer) Option

func DisableAuth

func DisableAuth() Option

func Domain

func Domain(domain string) Option

func ErrorLogger

func ErrorLogger(l Logger) Option

func LMTP

func LMTP() Option

func MaxMessageBytes

func MaxMessageBytes(maxMsgBytes int) Option

func MaxRecipients

func MaxRecipients(maxRcpts int) Option

func ReadTimeout

func ReadTimeout(t time.Duration) Option

func StrictMode

func StrictMode() Option

func TLSConfig

func TLSConfig(tlsconfig *tls.Config) Option

func UnixSocket

func UnixSocket() Option

func WriteTimeout

func WriteTimeout(t time.Duration) Option

type Reader

type Reader struct {
	R *bufio.Reader
	// contains filtered or unexported fields
}

A Reader implements convenience methods for reading requests or responses from a text protocol network connection.

func NewReader

func NewReader(r *bufio.Reader) *Reader

NewReader returns a new Reader reading from r.

To avoid denial of service attacks, the provided bufio.Reader should be reading from an io.LimitReader or similar Reader to bound the size of responses.

func (*Reader) DotReader2 added in v1.2.0

func (r *Reader) DotReader2() io.Reader

DotReader returns a new Reader that satisfies Reads using the decoded text of a dot-encoded block read from r. The returned Reader is only valid until the next call to a method on r.

Dot encoding is a common framing used for data blocks in text protocols such as SMTP. The data consists of a sequence of lines, each of which ends in "\r\n". The sequence itself ends at a line containing just a dot: ".\r\n". Lines beginning with a dot are escaped with an additional dot to avoid looking like the end of the sequence.

The decoded form returned by the Reader's Read method rewrites the "\r\n" line endings into the simpler "\n", removes leading dot escapes if present, and stops with error io.EOF after consuming (and discarding) the end-of-sequence line.

func (*Reader) ReadCodeLine

func (r *Reader) ReadCodeLine(expectCode int) (code int, message string, err error)

ReadCodeLine reads a response code line of the form

code message

where code is a three-digit status code and the message extends to the rest of the line. An example of such a line is:

220 plan9.bell-labs.com ESMTP

If the prefix of the status does not match the digits in expectCode, ReadCodeLine returns with err set to &Error{code, message}. For example, if expectCode is 31, an error will be returned if the status is not in the range [310,319].

If the response is multi-line, ReadCodeLine returns an error.

An expectCode <= 0 disables the check of the status code.

func (*Reader) ReadContinuedLine

func (r *Reader) ReadContinuedLine() (string, error)

ReadContinuedLine reads a possibly continued line from r, eliding the final trailing ASCII white space. Lines after the first are considered continuations if they begin with a space or tab character. In the returned data, continuation lines are separated from the previous line only by a single space: the newline and leading white space are removed.

For example, consider this input:

Line 1
  continued...
Line 2

The first call to ReadContinuedLine will return "Line 1 continued..." and the second will return "Line 2".

A line consisting of only white space is never continued.

func (*Reader) ReadContinuedLineBytes

func (r *Reader) ReadContinuedLineBytes() ([]byte, error)

ReadContinuedLineBytes is like ReadContinuedLine but returns a []byte instead of a string.

func (*Reader) ReadDotBytes

func (r *Reader) ReadDotBytes() ([]byte, error)

ReadDotBytes reads a dot-encoding and returns the decoded data.

See the documentation for the DotReader method for details about dot-encoding.

func (*Reader) ReadDotLines

func (r *Reader) ReadDotLines() ([]string, error)

ReadDotLines reads a dot-encoding and returns a slice containing the decoded lines, with the final \r\n or \n elided from each.

See the documentation for the DotReader method for details about dot-encoding.

func (*Reader) ReadLine

func (r *Reader) ReadLine() (string, error)

ReadLine reads a single line from r, eliding the final \n or \r\n from the returned string.

func (*Reader) ReadLineBytes

func (r *Reader) ReadLineBytes() ([]byte, error)

ReadLineBytes is like ReadLine but returns a []byte instead of a string.

func (*Reader) ReadMIMEHeader

func (r *Reader) ReadMIMEHeader() (textproto.MIMEHeader, error)

ReadMIMEHeader reads a MIME-style header from r. The header is a sequence of possibly continued Key: Value lines ending in a blank line. The returned map m maps CanonicalMIMEHeaderKey(key) to a sequence of values in the same order encountered in the input.

For example, consider this input:

My-Key: Value 1
Long-Key: Even
       Longer Value
My-Key: Value 2

Given that input, ReadMIMEHeader returns the map:

map[string][]string{
	"My-Key": {"Value 1", "Value 2"},
	"Long-Key": {"Even Longer Value"},
}

func (*Reader) ReadResponse

func (r *Reader) ReadResponse(expectCode int) (code int, message string, err error)

ReadResponse reads a multi-line response of the form:

code-message line 1
code-message line 2
...
code message line n

where code is a three-digit status code. The first line starts with the code and a hyphen. The response is terminated by a line that starts with the same code followed by a space. Each line in message is separated by a newline (\n).

See page 36 of RFC 959 (https://www.ietf.org/rfc/rfc959.txt) for details of another form of response accepted:

code-message line 1
message line 2
...
code message line n

If the prefix of the status does not match the digits in expectCode, ReadResponse returns with err set to &Error{code, message}. For example, if expectCode is 31, an error will be returned if the status is not in the range [310,319].

An expectCode <= 0 disables the check of the status code.

type SMTPError

type SMTPError struct {
	Code         int
	EnhancedCode EnhancedCode
	Message      string
}

SMTPError specifies the error code and message that needs to be returned to the client

func (*SMTPError) Error

func (err *SMTPError) Error() string

type SaslServerFactory

type SaslServerFactory func(conn *Conn) sasl.Server

A function that creates SASL servers.

type Server

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

A SMTP server.

func NewServer

func NewServer(be Backend, opts ...Option) *Server

New generates a new Server

func (*Server) Close

func (s *Server) Close()

Close stops the server.

func (*Server) EnableAuth

func (s *Server) EnableAuth(name string, f SaslServerFactory)

EnableAuth enables an authentication mechanism on this server.

This function should not be called directly, it must only be used by libraries implementing extensions of the SMTP protocol.

func (*Server) ForEachConn

func (s *Server) ForEachConn(f func(*Conn))

ForEachConn iterates through all opened connections.

func (*Server) ListenAndServe

func (s *Server) ListenAndServe() error

ListenAndServe listens on the network address s.Addr and then calls Serve to handle requests on incoming connections.

If s.Addr is blank ":smtp" is used.

func (*Server) ListenAndServeTLS

func (s *Server) ListenAndServeTLS() error

ListenAndServeTLS listens on the TCP network address s.Addr and then calls Serve to handle requests on incoming TLS connections.

If s.Addr is blank, ":smtps" is used.

func (*Server) Serve

func (s *Server) Serve(l net.Listener) error

Serve accepts incoming connections on the Listener l.

type Session

type Session interface {
	// Discard currently processed message.
	Reset()

	// Free all resources associated with session.
	Logout() error

	// Set return path for currently processed message.
	Mail(from string) error
	// Add recipient for currently processed message.
	Rcpt(to string) error
	// Set currently processed message contents and send it.
	Data(r io.Reader, d DataContext) error
}

type SessionFactory added in v1.2.0

type SessionFactory interface {
	New() Session
}

SessionFactory Creates a New session for each Connection

type TextConn

type TextConn struct {
	Reader
	textproto.Writer
	textproto.Pipeline
	// contains filtered or unexported fields
}

A Conn represents a textual network protocol connection. It consists of a Reader and Writer to manage I/O and a Pipeline to sequence concurrent requests on the connection. These embedded types carry methods with them; see the documentation of those types for details.

func NewTextConn

func NewTextConn(conn io.ReadWriteCloser) *TextConn

NewConn returns a new Conn using conn for I/O.

func (*TextConn) Close

func (c *TextConn) Close() error

Close closes the connection.

type XForward

type XForward struct {
	Name, Addr, Proto, Helo string
}

Directories

Path Synopsis
Package smtpclient implements the Simple Mail Transfer Protocol as defined in RFC 5321.
Package smtpclient implements the Simple Mail Transfer Protocol as defined in RFC 5321.

Jump to

Keyboard shortcuts

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