imapsql

package module
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Aug 23, 2020 License: MIT Imports: 38 Imported by: 1

README

go-imap-sql Travis CI CodeCov Reference stability-unstable

SQL-based storage backend for go-imap library.

Building

Go 1.13 is required due to use of Go 1.13 error inspection features.

RDBMS support

go-imap-sql is known to work with (and constantly being tested against) following RDBMS:

  • SQLite 3.25.0
  • PostgreSQL 9.6

Following RDBMS was actively supported in the past, it's unknown whether they still work with go-imap-sql:

  • MariaDB 10.2

IMAP Extensions Supported

Authentication

go-imap-sql does not implement any authentication. "password" argument of Login method is not checked and the user account is created if it does not exist. You are supposed to wrap it to implement your own authentication the way you need it.

Usernames case-insensitivity

Usernames are always converted to lower-case before doing anything. This means that if you type imapsql-ctl ... users create FOXCPP. Account with username foxcpp will be created. Also this means that you can use any case in account settings in your IMAP client.

secure_delete

You may want to overwrite deleted messages and theirs meta-data with zeroes for security/privacy reasons. For MySQL, PostgreSQL - consult documentation (AFAIK, there is no such option).

For SQLite3, you should build go-imap-sql with sqlite_secure_delete build tag. It will enable corresponding SQLite3 feature by default for all databases.

If you want to enable it per-database - you can use file:PATH?_secure_delete=ON in DSN.

UIDVALIDITY

go-imap-sql never invalidates UIDs in an existing mailbox. If mailbox is DELETE'd then UIDVALIDITY value changes.

Unlike many popular IMAP server implementations, go-imap-sql uses randomly generated UIDVALIDITY values instead of timestamps.

This makes several things easier to implement with less edge cases. And answer to the question you are already probably asked: To make go-imap-sql malfunction you need to get Go's PRNG to generate two equal integers in range of [1, 2^32-1] just at right moment (seems unlikely enough to ignore it). Even then, it will not cause much problems due to the way most client implementations work.

go-imap-sql uses separate math/rand.Rand instance and seeds it with system time on initialization (in New).

You can provide custom pre-seeded struct implementing math/rand.Source in Opts struct (PRNG field).

Maddy

You can use go-imap-sql as part of the maddy mail server.

imapsql-ctl

For direct access to database you can use imapsql-ctl console utility. See more information in separate README here.

go install github.com/foxcpp/go-imap-sql/cmd/imapsql-ctl

Documentation

Index

Constants

View Source
const MailboxPathSep = "."
View Source
const SchemaVersion = 5

SchemaVersion is incremented each time DB schema changes.

View Source
const VersionStr = "0.4.0"

VersionStr is a string value representing go-imap-sql version.

Meant for debug logs, you may want to know which go-imap-sql version users have.

Variables

View Source
var (
	ErrUserAlreadyExists = errors.New("imap: user already exists")
	ErrUserDoesntExists  = errors.New("imap: user doesn't exists")
)
View Source
var ErrDeliveryInterrupted = errors.New("sql: delivery transaction interrupted, try again later")
View Source
var ErrUnsupportedSpecialAttr = errors.New("imap: special attribute is not supported")

Functions

func RegisterCompressionAlgo added in v0.4.0

func RegisterCompressionAlgo(name string, algo CompressionAlgo)

RegisterCompressionAlgo adds a new compression algorithm to the registry so it can be used in Opts.CompressionAlgo.

Types

type Backend added in v0.2.0

type Backend struct {

	// Opts structure used to construct this Backend object.
	//
	// For most cases it is safe to change options while backend is serving
	// requests.
	// Options that should NOT be changed while backend is processing commands:
	// - PRNG
	// - CompressAlgoParams
	// Changes for the following options have no effect after backend initialization:
	// - CompressAlgo
	// - ExclusiveLock
	// - CacheSize
	// - NoWAL
	// - UpdatesChan
	Opts Opts

	// database/sql.DB object created by New.
	DB *sql.DB
	// contains filtered or unexported fields
}

func New added in v0.2.0

func New(driver, dsn string, extStore ExternalStore, opts Opts) (*Backend, error)

New creates new Backend instance using provided configuration.

driver and dsn arguments are passed directly to sql.Open.

Note that it is not safe to create multiple Backend instances working with the single database as they need to keep some state synchronized and there is no measures for this implemented in go-imap-sql.

func (*Backend) Close added in v0.2.0

func (b *Backend) Close() error

func (*Backend) CreateMessageLimit added in v0.2.0

func (b *Backend) CreateMessageLimit() *uint32

func (*Backend) CreateUser added in v0.2.0

func (b *Backend) CreateUser(username string) error

CreateUser creates user account.

func (*Backend) DeleteUser added in v0.2.0

func (b *Backend) DeleteUser(username string) error

DeleteUser deleted user account with specified username.

It is error to delete account that doesn't exist, ErrUserDoesntExists will be returned in this case.

func (*Backend) EnableChildrenExt added in v0.2.0

func (b *Backend) EnableChildrenExt() bool

EnableChildrenExt enables generation of /HasChildren and /HasNoChildren attributes for mailboxes. It should be used only if server advertises CHILDREN extension support (see children subpackage).

func (*Backend) EnableSpecialUseExt added in v0.4.0

func (b *Backend) EnableSpecialUseExt() bool

EnableSpecialUseExt enables generation of special-use attributes for mailboxes. It should be used only if server advertises SPECIAL-USE extension support (see go-imap-specialuse).

func (*Backend) GetOrCreateUser added in v0.2.0

func (b *Backend) GetOrCreateUser(username string) (backend.User, error)

GetOrCreateUser is a convenience wrapper for GetUser and CreateUser.

All database operations are executed within one transaction so this method is atomic as defined by used RDBMS.

func (*Backend) GetUser added in v0.2.0

func (b *Backend) GetUser(username string) (backend.User, error)

GetUser creates backend.User object for the user credentials.

func (*Backend) ListUsers added in v0.2.0

func (b *Backend) ListUsers() ([]string, error)

ListUsers returns list of existing usernames.

It may return nil slice if no users are registered.

func (*Backend) Login added in v0.2.0

func (b *Backend) Login(_ *imap.ConnInfo, username, password string) (backend.User, error)

func (*Backend) NewDelivery added in v0.4.0

func (b *Backend) NewDelivery() Delivery

NewDelivery creates a new state object for atomic delivery session.

Messages added to the storage using that interface are added either to all recipients mailboxes or none or them.

Also use of this interface is more efficient than separate GetUser/GetMailbox/CreateMessage calls.

Note that for performance reasons, the DB is not locked while the Delivery object exists, but only when BodyRaw/BodyParsed is called and until Abort/Commit is called. This means that the recipient mailbox can be deleted between AddRcpt and Body* calls. In that case, either Body* or Commit will return ErrDeliveryInterrupt. Sender should retry delivery after a short delay.

func (*Backend) SetMessageLimit added in v0.2.0

func (b *Backend) SetMessageLimit(val *uint32) error

Change global APPEND limit, Opts.MaxMsgBytes.

Provided to implement interfaces used by go-imap-backend-tests.

func (*Backend) SupportedThreadAlgorithms added in v0.5.0

func (b *Backend) SupportedThreadAlgorithms() []sortthread.ThreadAlgorithm

func (*Backend) Updates added in v0.2.0

func (b *Backend) Updates() <-chan backend.Update

type Buffer added in v0.4.0

type Buffer interface {
	Open() (io.ReadCloser, error)
}

Buffer is the temporary storage for the message body.

type BufferedReadCloser added in v0.4.0

type BufferedReadCloser struct {
	*bufio.Reader
	io.Closer
}

type CompressionAlgo added in v0.4.0

type CompressionAlgo interface {
	// WrapCompress wraps writer such that any data written to it
	// will be compressed using a certain compression algorithms.
	//
	// Close on returned writer should not close original writer, but
	// should flush any buffers if necessary.
	//
	// Algorithm settings can be customized by passing
	// implementation-defined params argument. Most algorithms
	// will include compression level here as a string. More complex
	// algorithms can use JSON to store complex settings. Empty string
	// means that the default parameters should be used.
	WrapCompress(w io.Writer, params string) (io.WriteCloser, error)

	// WrapDecompress wraps writer such that underlying stream should be decompressed
	// using a certain compression algorithms.
	WrapDecompress(r io.Reader) (io.Reader, error)
}

type Delivery added in v0.4.0

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

func (*Delivery) Abort added in v0.4.0

func (d *Delivery) Abort() error

func (*Delivery) AddRcpt added in v0.4.0

func (d *Delivery) AddRcpt(username string, userHeader textproto.Header) error

AddRcpt adds the recipient username/mailbox pair to the delivery.

If this function returns an error - further calls will still work correctly and there is no need to restart the delivery.

The specified user account and mailbox should exist at the time AddRcpt is called, but it can disappear before Body* call, in which case Delivery will be terminated with ErrDeliveryInterrupted error. See Backend.StartDelivery method documentation for details.

Fields from userHeader, if any, will be prepended to the message header *only* for that recipient. Use this to add Received and Delivered-To fields with recipient-specific information (e.g. its address).

func (*Delivery) BodyParsed added in v0.4.0

func (d *Delivery) BodyParsed(header textproto.Header, bodyLen int, body Buffer) error

func (*Delivery) BodyRaw added in v0.4.0

func (d *Delivery) BodyRaw(message io.Reader) error

BodyRaw is convenience wrapper for BodyParsed. Use it only for most simple cases (e.g. for tests).

You want to use BodyParsed in most cases. It is much more efficient. BodyRaw reads the entire message into memory.

func (*Delivery) Commit added in v0.4.0

func (d *Delivery) Commit() error

Commit finishes the delivery.

If this function returns no error - the message is successfully added to the mailbox of *all* recipients.

After Commit or Abort is called, Delivery object can be reused as if it was just created.

func (*Delivery) Mailbox added in v0.4.0

func (d *Delivery) Mailbox(name string) error

Mailbox command changes the target mailbox for all recipients. It should be called before BodyParsed/BodyRaw.

If it is not called, it defaults to INBOX. If mailbox doesn't exist for some users - it will created.

func (*Delivery) SpecialMailbox added in v0.4.0

func (d *Delivery) SpecialMailbox(attribute, fallbackName string) error

SpecialMailbox is similar to Mailbox method but instead of looking up mailboxes by name it looks it up by the SPECIAL-USE attribute.

If no such mailbox exists for some user, it will be created with fallbackName and requested SPECIAL-USE attribute set.

The main use-case of this function is to reroute messages into Junk directory during multi-recipient delivery.

func (*Delivery) UserMailbox added in v0.5.0

func (d *Delivery) UserMailbox(username, mailbox string, flags []string)

type DummyLogger added in v0.4.0

type DummyLogger struct{}

func (DummyLogger) Debugf added in v0.4.0

func (DummyLogger) Debugf(format string, v ...interface{})

func (DummyLogger) Debugln added in v0.4.0

func (DummyLogger) Debugln(v ...interface{})

func (DummyLogger) Printf added in v0.4.0

func (DummyLogger) Printf(format string, v ...interface{})

func (DummyLogger) Println added in v0.4.0

func (DummyLogger) Println(v ...interface{})

type ExtStoreObj added in v0.4.0

type ExtStoreObj interface {
	Sync() error
	io.Reader
	io.Writer
	io.Closer
}

type ExternalError added in v0.4.0

type ExternalError struct {
	// true if error was caused by an attempt to access non-existent key.
	NonExistent bool

	Key string
	Err error
}

func (ExternalError) Cause added in v0.4.0

func (err ExternalError) Cause() error

Cause implements Cause() for pkg/errors.

func (ExternalError) Error added in v0.4.0

func (err ExternalError) Error() string

func (ExternalError) Unwrap added in v0.4.0

func (err ExternalError) Unwrap() error

Unwrap implements Unwrap() for Go 1.13 'errors'.

type ExternalStore added in v0.2.0

type ExternalStore interface {
	Create(key string) (ExtStoreObj, error)

	// Open returns the ExtStoreObj that reads the message body specified by
	// passed key.
	//
	// If no such message exists - ExternalError with NonExistent = true is
	// returned.
	Open(key string) (ExtStoreObj, error)

	// Delete removes a set of keys from store. Non-existent keys are ignored.
	Delete(keys []string) error
}

ExternalStore is an interface used by go-imap-sql to store message bodies outside of main database.

type FSStore added in v0.4.0

type FSStore struct {
	Root string
}

FSStore struct represents directory on FS used to store message bodies.

Always use field names on initialization because new fields may be added without a major version change.

func (*FSStore) Create added in v0.4.0

func (s *FSStore) Create(key string) (ExtStoreObj, error)

func (*FSStore) Delete added in v0.4.0

func (s *FSStore) Delete(keys []string) error

func (*FSStore) Open added in v0.4.0

func (s *FSStore) Open(key string) (ExtStoreObj, error)

type Logger added in v0.4.0

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

type Mailbox added in v0.2.0

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

func (*Mailbox) Check added in v0.2.0

func (m *Mailbox) Check() error

func (*Mailbox) CopyMessages added in v0.2.0

func (m *Mailbox) CopyMessages(uid bool, seqset *imap.SeqSet, dest string) error

func (*Mailbox) CreateMessage added in v0.2.0

func (m *Mailbox) CreateMessage(flags []string, date time.Time, fullBody imap.Literal) error

func (*Mailbox) CreateMessageLimit added in v0.2.0

func (m *Mailbox) CreateMessageLimit() *uint32

func (*Mailbox) DelMessages added in v0.2.0

func (m *Mailbox) DelMessages(uid bool, seqset *imap.SeqSet) error

func (*Mailbox) Expunge added in v0.2.0

func (m *Mailbox) Expunge() error

func (*Mailbox) Info added in v0.2.0

func (m *Mailbox) Info() (*imap.MailboxInfo, error)

func (*Mailbox) ListMessages added in v0.2.0

func (m *Mailbox) ListMessages(uid bool, seqset *imap.SeqSet, items []imap.FetchItem, ch chan<- *imap.Message) error

func (*Mailbox) MoveMessages added in v0.2.0

func (m *Mailbox) MoveMessages(uid bool, seqset *imap.SeqSet, dest string) error

func (*Mailbox) Name added in v0.2.0

func (m *Mailbox) Name() string

func (*Mailbox) SearchMessages added in v0.2.0

func (m *Mailbox) SearchMessages(uid bool, criteria *imap.SearchCriteria) ([]uint32, error)

func (*Mailbox) SetMessageLimit added in v0.2.0

func (m *Mailbox) SetMessageLimit(val *uint32) error

func (*Mailbox) SetSubscribed added in v0.2.0

func (m *Mailbox) SetSubscribed(subscribed bool) error

func (*Mailbox) Sort added in v0.5.0

func (m *Mailbox) Sort(uid bool, sortCrit []sortthread.SortCriterion, searchCrit *imap.SearchCriteria) ([]uint32, error)

func (*Mailbox) Status added in v0.2.0

func (m *Mailbox) Status(items []imap.StatusItem) (*imap.MailboxStatus, error)

func (*Mailbox) Thread added in v0.5.0

func (m *Mailbox) Thread(uid bool, threading sortthread.ThreadAlgorithm, searchCrit *imap.SearchCriteria) ([]*sortthread.Thread, error)

func (*Mailbox) UpdateMessagesFlags added in v0.2.0

func (m *Mailbox) UpdateMessagesFlags(uid bool, seqset *imap.SeqSet, operation imap.FlagsOp, flags []string) error

type Opts added in v0.2.0

type Opts struct {

	// Maximum amount of bytes that backend will accept.
	// Intended for use with APPENDLIMIT extension.
	// nil value means no limit, 0 means zero limit (no new messages allowed)
	MaxMsgBytes *uint32

	// Controls when channel returned by Updates should be created.
	// If set to false - channel will be created before NewBackend returns.
	// If set to true - channel will be created upon first call to Updates.
	// Second is useful for tests that don't consume values from Updates
	// channel.
	LazyUpdatesInit bool

	// UpdatesChan allows to pass custom channel object used for unilateral
	// updates dispatching.
	//
	// You can use this to change default updates buffer size (20) or to split
	// initializaton into phases (which allows to break circular dependencies
	// if you need updates channel before database initialization).
	UpdatesChan chan backend.Update

	// Custom randomness source for UIDVALIDITY values generation.
	PRNG Rand

	// (SQLite3 only) Don't force WAL journaling mode.
	NoWAL bool

	// (SQLite3 only) Use different value for busy_timeout. Default is 50000.
	// To set to 0, use -1 (you probably don't want this).
	BusyTimeout int

	// (SQLite3 only) Use EXCLUSIVE locking mode.
	ExclusiveLock bool

	// (SQLite3 only) Change page cache size. Positive value indicates cache
	// size in pages, negative in KiB. If set 0 - SQLite default will be used.
	CacheSize int

	// (SQLite3 only) Repack database file into minimal amount of disk space on
	// Close.
	// It runs VACUUM and PRAGMA wal_checkpoint(TRUNCATE).
	// Failures of these operations are ignored and don't affect return value
	// of Close.
	MinimizeOnClose bool

	// Compression algorithm to use for new messages. Empty string means no compression.
	//
	// Algorithms should be registered before using RegisterCompressionAlgo.
	CompressAlgo string

	// CompressAlgoParams is passed directly to compression algorithm without changes.
	CompressAlgoParams string

	Log Logger
	// contains filtered or unexported fields
}

Opts structure specifies additional settings that may be set for backend.

Please use names to reference structure members on creation, fields may be reordered or added without major version increment.

type Rand added in v0.2.0

type Rand interface {
	Uint32() uint32
}

type SerializationError added in v0.4.0

type SerializationError struct {
	Err error
}

func (SerializationError) Error added in v0.4.0

func (se SerializationError) Error() string

func (SerializationError) Unwrap added in v0.4.0

func (se SerializationError) Unwrap() error

type User added in v0.2.0

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

func (*User) CreateMailbox added in v0.2.0

func (u *User) CreateMailbox(name string) error

func (*User) CreateMailboxSpecial added in v0.4.0

func (u *User) CreateMailboxSpecial(name, specialUseAttr string) error

CreateMailboxSpecial creates a mailbox with SPECIAL-USE attribute set.

func (*User) CreateMessageLimit added in v0.2.0

func (u *User) CreateMessageLimit() *uint32

func (*User) DeleteMailbox added in v0.2.0

func (u *User) DeleteMailbox(name string) error

func (*User) GetMailbox added in v0.2.0

func (u *User) GetMailbox(name string) (backend.Mailbox, error)

func (*User) ID added in v0.2.0

func (u *User) ID() uint64

func (*User) ListMailboxes added in v0.2.0

func (u *User) ListMailboxes(subscribed bool) ([]backend.Mailbox, error)

func (*User) Logout added in v0.2.0

func (u *User) Logout() error

func (*User) Namespaces added in v0.5.0

func (u *User) Namespaces() (personal, other, shared []namespace.Namespace, err error)

func (*User) RenameMailbox added in v0.2.0

func (u *User) RenameMailbox(existingName, newName string) error

func (*User) SetMessageLimit added in v0.2.0

func (u *User) SetMessageLimit(val *uint32) error

func (*User) Username added in v0.2.0

func (u *User) Username() string

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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