smtpsrv

package module
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Jun 30, 2021 License: MIT Imports: 13 Imported by: 0

README

A SMTP Server Package

a simple smtp server library, forked from This repo but I refactored it to be more advanced and organized

Features

  • Very simple
  • Automated SPF Checks
  • Automated FROM validation (MX checking), via Request.Mailable
  • Supports TLS
  • Modular, as you can add more smtp command processors to extend the functionality as you need

Quick Start

go get github.com/alash3al/go-smtpsrv

package main

import (
	"fmt"

	"github.com/alash3al/go-smtpsrv"
)

func main() {
	handler := func(req *smtpsrv.Request) error {
		// ...
		return nil
	}
	srv := &smtpsrv.Server{
		Name: "mail.my.server",
		Addr:        ":25025",
		MaxBodySize: 5 * 1024,
		Handler:     handler,
	}
	fmt.Println(srv.ListenAndServe())
}

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))
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 ..
	ErrorRequestedActionAbortedLocalError = errors.New("Requested action aborted: local error in processing")
	// ErrorTransactionFailed ..
	ErrorTransactionFailed = errors.New("Transaction failed")
	// ErrorServiceNotAvailable ..
	ErrorServiceNotAvailable = errors.New("Service not available, closing transmission channel")
	// ErrorRequestedActionAbortedExceededStorage ..
	ErrorRequestedActionAbortedExceededStorage = errors.New("Requested mail action aborted: exceeded storage allocation")
)
View Source
var (
	// DefaultProcessors holds processor functions
	DefaultProcessors = map[string]Processor{
		"EHLO":     ehloProcessor,
		"HELO":     ehloProcessor,
		"STARTTLS": starttlsProcessor,
		"AUTH":     authProcessor,
		"MAIL":     mailProcessor,
		"RCPT":     rcptProcessor,
		"DATA":     dataProcessor,
		"RSET":     rsetProcessor,
		"VRFY":     vrfyProcessor,
		"EXPN":     expnProcessor,
		"HELP":     helpProcessor,
		"NOOP":     noopProcessor,
		"QUIT":     quitProcessor,
	}
)

Functions

func CanonicalizeEmail

func CanonicalizeEmail(local string) string

CanonicalizeEmail format the specified email address 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 LimitDataSize

func LimitDataSize(r io.Reader, s int64) io.Reader

LimitDataSize Limit the incoming data size of the DATA command

func ListenAndServe

func ListenAndServe(addr string, handler Handler) error

ListenAndServe start listening on the specified addr using the specified handler

func ListenAndServeTLS

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

ListenAndServeTLS start listening on the specified addr using the specified handler and tls configs

func SplitAddress

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

SplitAddress split the email@addre.ss to <user>@<domain>

Types

type Handler

type Handler func(req *Request) error

Handler is a 'net/http' like handler but for mails

type Processor

type Processor func(req *Request) error

Processor is a SMTP command processor

type Request

type Request struct {
	// the currently running server
	Server *Server

	// the underlying socket for currently connected client
	Conn net.Conn

	// an instance of go stdlib for handling the above Conn as a text-porotocol
	TextProto *textproto.Conn

	// TLS related info
	TLSState *tls.ConnectionState

	// a shortcut for Conn.RemoteAddr()
	RemoteAddr string

	// contains the hostname of the currently connected client during the EHLO/HELO command
	HelloHost string

	// whether EHLO/HELO called or not
	HelloRecieved bool

	// the login username used for login, empty means that this is an anonymous attempt
	AuthUser string

	// the user that sends the mail
	From string

	// the rctps!
	To []string

	// the body of the mail "DATA command" but parsed
	Message *mail.Message

	// whether the client called QUIT or not
	QuitSent bool

	// the spf checking result
	SPFResult spf.Result

	// whether the FROM mail is mailable or not
	Mailable bool

	// the currently processing line
	Line []string
}

Request is the incoming connection meta-data

func NewRequest

func NewRequest(conn net.Conn, srv *Server) (req *Request, err error)

NewRequest creates a new instance of the Request struct

func (*Request) Process

func (req *Request) Process() error

Process start parsing and processing the current command-line

func (*Request) Reset

func (req *Request) Reset()

Reset resets to the defaults

func (*Request) Serve

func (req *Request) Serve()

Serve start accepting incoming connections

type Server

type Server struct {
	// The name of the server used while greeting
	Name string

	// The address to listen on
	Addr string

	// The default inbox handler
	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

	// Processors is a map of current supported commands' processor for incomming
	// SMTP messages/lines.
	Processors map[string]Processor

	// Maximum size of the DATA command in bytes
	MaxBodySize int64
}

Server is our main server handler

func (*Server) ListenAndServe

func (srv *Server) ListenAndServe() error

ListenAndServe start serving the incoming data

func (*Server) ListenAndServeTLS

func (srv *Server) ListenAndServeTLS(certFile string, keyFile string) error

ListenAndServeTLS start serving the incoming tls connection

func (*Server) Serve

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

Serve start accepting the incoming connections

Jump to

Keyboard shortcuts

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