mirror of
https://blitiri.com.ar/repos/chasquid
synced 2025-12-18 14:47:03 +00:00
docs: Add missing docstrings, adjust wording to match standard style
This patch adds a missing docstrings for exported identifiers, and adjust some of the existing ones to match the standard style. In some cases, the identifiers were un-exported after noticing they had no external users. Besides improving documentation, it also reduces the linter noise significantly.
This commit is contained in:
@@ -56,12 +56,12 @@ func main() {
|
||||
}
|
||||
|
||||
commands := map[string]func(){
|
||||
"user-add": UserAdd,
|
||||
"user-remove": UserRemove,
|
||||
"authenticate": Authenticate,
|
||||
"check-userdb": CheckUserDB,
|
||||
"aliases-resolve": AliasesResolve,
|
||||
"print-config": PrintConfig,
|
||||
"user-add": userAdd,
|
||||
"user-remove": userRemove,
|
||||
"authenticate": authenticate,
|
||||
"check-userdb": checkUserDB,
|
||||
"aliases-resolve": aliasesResolve,
|
||||
"print-config": printConfig,
|
||||
}
|
||||
|
||||
for cmd, f := range commands {
|
||||
@@ -71,6 +71,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Fatalf prints the given message, then exits the program with an error code.
|
||||
func Fatalf(s string, arg ...interface{}) {
|
||||
fmt.Printf(s+"\n", arg...)
|
||||
os.Exit(1)
|
||||
@@ -109,7 +110,7 @@ func userDBFromArgs(create bool) (string, string, *userdb.DB) {
|
||||
}
|
||||
|
||||
// chasquid-util check-userdb <domain>
|
||||
func CheckUserDB() {
|
||||
func checkUserDB() {
|
||||
_, err := userdb.Load(userDBForDomain(""))
|
||||
if err != nil {
|
||||
Fatalf("Error loading database: %v", err)
|
||||
@@ -119,7 +120,7 @@ func CheckUserDB() {
|
||||
}
|
||||
|
||||
// chasquid-util user-add <username> [--password=<password>]
|
||||
func UserAdd() {
|
||||
func userAdd() {
|
||||
user, _, db := userDBFromArgs(true)
|
||||
password := getPassword()
|
||||
|
||||
@@ -137,7 +138,7 @@ func UserAdd() {
|
||||
}
|
||||
|
||||
// chasquid-util authenticate <username> [--password=<password>]
|
||||
func Authenticate() {
|
||||
func authenticate() {
|
||||
user, _, db := userDBFromArgs(false)
|
||||
|
||||
password := getPassword()
|
||||
@@ -177,7 +178,7 @@ func getPassword() string {
|
||||
}
|
||||
|
||||
// chasquid-util user-remove <username>
|
||||
func UserRemove() {
|
||||
func userRemove() {
|
||||
user, _, db := userDBFromArgs(false)
|
||||
|
||||
present := db.RemoveUser(user)
|
||||
@@ -194,7 +195,7 @@ func UserRemove() {
|
||||
}
|
||||
|
||||
// chasquid-util aliases-resolve <address>
|
||||
func AliasesResolve() {
|
||||
func aliasesResolve() {
|
||||
conf, err := config.Load(configDir + "/chasquid.conf")
|
||||
if err != nil {
|
||||
Fatalf("Error reading config")
|
||||
@@ -238,7 +239,7 @@ func AliasesResolve() {
|
||||
}
|
||||
|
||||
// chasquid-util print-config
|
||||
func PrintConfig() {
|
||||
func printConfig() {
|
||||
conf, err := config.Load(configDir + "/chasquid.conf")
|
||||
if err != nil {
|
||||
Fatalf("Error reading config")
|
||||
|
||||
@@ -72,6 +72,7 @@ type Recipient struct {
|
||||
Type RType
|
||||
}
|
||||
|
||||
// RType represents a recipient type, see the contants below for valid values.
|
||||
type RType string
|
||||
|
||||
// Valid recipient types.
|
||||
@@ -81,6 +82,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrRecursionLimitExceeded is returned when the resolving lookup
|
||||
// exceeded the recursion limit. Usually caused by aliases loops.
|
||||
ErrRecursionLimitExceeded = fmt.Errorf("recursion limit exceeded")
|
||||
|
||||
// How many levels of recursions we allow during lookups.
|
||||
@@ -109,6 +112,7 @@ type Resolver struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewResolver returns a new, empty Resolver.
|
||||
func NewResolver() *Resolver {
|
||||
return &Resolver{
|
||||
files: map[string][]string{},
|
||||
@@ -117,6 +121,8 @@ func NewResolver() *Resolver {
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the given address, returning the list of corresponding recipients
|
||||
// (if any).
|
||||
func (v *Resolver) Resolve(addr string) ([]Recipient, error) {
|
||||
v.mu.Lock()
|
||||
defer v.mu.Unlock()
|
||||
@@ -184,14 +190,17 @@ func (v *Resolver) cleanIfLocal(addr string) string {
|
||||
return user + "@" + domain
|
||||
}
|
||||
|
||||
// AddDomain to the resolver, registering its existence.
|
||||
func (v *Resolver) AddDomain(domain string) {
|
||||
v.mu.Lock()
|
||||
v.domains[domain] = true
|
||||
v.mu.Unlock()
|
||||
}
|
||||
|
||||
// AddAliasesFile to the resolver. The file will be parsed, and an error
|
||||
// returned if it does not exist or parse correctly.
|
||||
func (v *Resolver) AddAliasesFile(domain, path string) error {
|
||||
// We inconditionally add the domain and file on our list.
|
||||
// We unconditionally add the domain and file on our list.
|
||||
// Even if the file does not exist now, it may later. This makes it be
|
||||
// consider when doing Reload.
|
||||
// Adding it to the domains mean that we will do drop character and suffix
|
||||
@@ -219,10 +228,13 @@ func (v *Resolver) AddAliasesFile(domain, path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddAliasForTesting adds an alias to the resolver, for testing purposes.
|
||||
// Not for use in production code.
|
||||
func (v *Resolver) AddAliasForTesting(addr, rcpt string, rType RType) {
|
||||
v.aliases[addr] = append(v.aliases[addr], Recipient{rcpt, rType})
|
||||
}
|
||||
|
||||
// Reload aliases files for all known domains.
|
||||
func (v *Resolver) Reload() error {
|
||||
newAliases := map[string][]Recipient{}
|
||||
|
||||
|
||||
@@ -13,16 +13,16 @@ import (
|
||||
"blitiri.com.ar/go/chasquid/internal/normalize"
|
||||
)
|
||||
|
||||
// Interface for authentication backends.
|
||||
// Backend is the common interface for all authentication backends.
|
||||
type Backend interface {
|
||||
Authenticate(user, password string) (bool, error)
|
||||
Exists(user string) (bool, error)
|
||||
Reload() error
|
||||
}
|
||||
|
||||
// Interface for authentication backends that don't need to emit errors.
|
||||
// This allows backends to avoid unnecessary complexity, in exchange for a bit
|
||||
// more here.
|
||||
// NoErrorBackend is the interface for authentication backends that don't need
|
||||
// to emit errors. This allows backends to avoid unnecessary complexity, in
|
||||
// exchange for a bit more here.
|
||||
// They can be converted to normal Backend using WrapNoErrorBackend (defined
|
||||
// below).
|
||||
type NoErrorBackend interface {
|
||||
@@ -31,6 +31,8 @@ type NoErrorBackend interface {
|
||||
Reload() error
|
||||
}
|
||||
|
||||
// Authenticator tracks the backends for each domain, and allows callers to
|
||||
// query them with a more practical API.
|
||||
type Authenticator struct {
|
||||
// Registered backends, map of domain (string) -> Backend.
|
||||
// Backend operations will _not_ include the domain in the username.
|
||||
@@ -48,6 +50,7 @@ type Authenticator struct {
|
||||
AuthDuration time.Duration
|
||||
}
|
||||
|
||||
// NewAuthenticator returns a new Authenticator with no backends.
|
||||
func NewAuthenticator() *Authenticator {
|
||||
return &Authenticator{
|
||||
backends: map[string]Backend{},
|
||||
@@ -55,6 +58,7 @@ func NewAuthenticator() *Authenticator {
|
||||
}
|
||||
}
|
||||
|
||||
// Register a backend to use for the given domain.
|
||||
func (a *Authenticator) Register(domain string, be Backend) {
|
||||
a.backends[domain] = be
|
||||
}
|
||||
@@ -87,6 +91,7 @@ func (a *Authenticator) Authenticate(user, domain, password string) (bool, error
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Exists checks that user@domain exists.
|
||||
func (a *Authenticator) Exists(user, domain string) (bool, error) {
|
||||
if be, ok := a.backends[domain]; ok {
|
||||
ok, err := be.Exists(user)
|
||||
|
||||
@@ -81,6 +81,7 @@ func Load(path string) (*Config, error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// LogConfig logs the given configuration, in a human-friendly way.
|
||||
func LogConfig(c *Config) {
|
||||
log.Infof("Configuration:")
|
||||
log.Infof(" Hostname: %q", c.Hostname)
|
||||
|
||||
@@ -29,6 +29,8 @@ type Procmail struct {
|
||||
Timeout time.Duration // Timeout for each invocation.
|
||||
}
|
||||
|
||||
// Deliver an email. On failures, returns an error, and whether or not it is
|
||||
// permanent.
|
||||
func (p *Procmail) Deliver(from string, to string, data []byte) (error, bool) {
|
||||
tr := trace.New("Courier.Procmail", to)
|
||||
defer tr.Finish()
|
||||
|
||||
@@ -41,6 +41,8 @@ type SMTP struct {
|
||||
Dinfo *domaininfo.DB
|
||||
}
|
||||
|
||||
// Deliver an email. On failures, returns an error, and whether or not it is
|
||||
// permanent.
|
||||
func (s *SMTP) Deliver(from string, to string, data []byte) (error, bool) {
|
||||
a := &attempt{
|
||||
courier: s,
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
// Command to generate domaininfo.pb.go.
|
||||
//go:generate protoc --go_out=. domaininfo.proto
|
||||
|
||||
// DB represents the persistent domain information database.
|
||||
type DB struct {
|
||||
// Persistent store with the list of domains we know.
|
||||
store *protoio.Store
|
||||
@@ -23,6 +24,8 @@ type DB struct {
|
||||
ev *trace.EventLog
|
||||
}
|
||||
|
||||
// New opens a domain information database on the given dir, creating it if
|
||||
// necessary. The returned database will not be loaded.
|
||||
func New(dir string) (*DB, error) {
|
||||
st, err := protoio.NewStore(dir)
|
||||
if err != nil {
|
||||
|
||||
@@ -21,12 +21,12 @@ import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Default timeout to use. We expect Dovecot to be quite fast, but don't want
|
||||
// DefaultTimeout to use. We expect Dovecot to be quite fast, but don't want
|
||||
// to hang forever if something gets stuck.
|
||||
const DefaultTimeout = 5 * time.Second
|
||||
|
||||
var (
|
||||
ErrUsernameNotSafe = errors.New("username not safe (contains spaces)")
|
||||
errUsernameNotSafe = errors.New("username not safe (contains spaces)")
|
||||
)
|
||||
|
||||
var defaultUserdbPaths = []string{
|
||||
@@ -60,6 +60,7 @@ func NewAuth(userdb, client string) *Auth {
|
||||
}
|
||||
}
|
||||
|
||||
// String representation of this Auth, for human consumption.
|
||||
func (a *Auth) String() string {
|
||||
return fmt.Sprintf("DovecotAuth(%q, %q)", a.userdbAddr, a.clientAddr)
|
||||
}
|
||||
@@ -79,10 +80,10 @@ func (a *Auth) Check() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Does user exist?
|
||||
// Exists returns true if the user exists, false otherwise.
|
||||
func (a *Auth) Exists(user string) (bool, error) {
|
||||
if !isUsernameSafe(user) {
|
||||
return false, ErrUsernameNotSafe
|
||||
return false, errUsernameNotSafe
|
||||
}
|
||||
|
||||
conn, err := a.dial("unix", a.userdbAddr)
|
||||
@@ -126,10 +127,11 @@ func (a *Auth) Exists(user string) (bool, error) {
|
||||
return false, fmt.Errorf("invalid response: %q", resp)
|
||||
}
|
||||
|
||||
// Is the password valud for the user?
|
||||
// Authenticate returns true if the password is valid for the user, false
|
||||
// otherwise.
|
||||
func (a *Auth) Authenticate(user, passwd string) (bool, error) {
|
||||
if !isUsernameSafe(user) {
|
||||
return false, ErrUsernameNotSafe
|
||||
return false, errUsernameNotSafe
|
||||
}
|
||||
|
||||
conn, err := a.dial("unix", a.clientAddr)
|
||||
@@ -182,6 +184,8 @@ func (a *Auth) Authenticate(user, passwd string) (bool, error) {
|
||||
return false, fmt.Errorf("invalid response: %q", resp)
|
||||
}
|
||||
|
||||
// Reload the authenticator. It's a no-op for dovecot, but it is needed to
|
||||
// conform with the auth.Backend interface.
|
||||
func (a *Auth) Reload() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,12 +19,12 @@ func TestUsernameNotSafe(t *testing.T) {
|
||||
"a b", " ab", "ab ", "a\tb", "a\t", " ", "\t", "\t "}
|
||||
for _, c := range cases {
|
||||
ok, err := a.Authenticate(c, "passwd")
|
||||
if ok || err != ErrUsernameNotSafe {
|
||||
if ok || err != errUsernameNotSafe {
|
||||
t.Errorf("Authenticate(%q, _): got %v, %v", c, ok, err)
|
||||
}
|
||||
|
||||
ok, err = a.Exists(c)
|
||||
if ok || err != ErrUsernameNotSafe {
|
||||
if ok || err != errUsernameNotSafe {
|
||||
t.Errorf("Exists(%q): got %v, %v", c, ok, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,16 +19,19 @@ func Split(addr string) (string, string) {
|
||||
return ps[0], ps[1]
|
||||
}
|
||||
|
||||
// UserOf user@domain returns user.
|
||||
func UserOf(addr string) string {
|
||||
user, _ := Split(addr)
|
||||
return user
|
||||
}
|
||||
|
||||
// DomainOf user@domain returns domain.
|
||||
func DomainOf(addr string) string {
|
||||
_, domain := Split(addr)
|
||||
return domain
|
||||
}
|
||||
|
||||
// DomainIn checks that the domain of the address is on the given set.
|
||||
func DomainIn(addr string, locals *set.String) bool {
|
||||
domain := DomainOf(addr)
|
||||
if domain == "" {
|
||||
@@ -38,6 +41,7 @@ func DomainIn(addr string, locals *set.String) bool {
|
||||
return locals.Has(domain)
|
||||
}
|
||||
|
||||
// AddHeader adds (prepends) a MIME header to the message.
|
||||
func AddHeader(data []byte, k, v string) []byte {
|
||||
if len(v) > 0 {
|
||||
// If the value contains newlines, indent them properly.
|
||||
|
||||
@@ -24,20 +24,26 @@ type timedWriter struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// Write the given buffer, prepending timing information.
|
||||
func (t timedWriter) Write(b []byte) (int, error) {
|
||||
fmt.Fprintf(t.w, "%s ", time.Now().Format("2006-01-02 15:04:05.000000"))
|
||||
return t.w.Write(b)
|
||||
}
|
||||
|
||||
// Logger contains a backend used to log data to, such as a file or syslog.
|
||||
// It implements various user-friendly methods for logging mail information to
|
||||
// it.
|
||||
type Logger struct {
|
||||
w io.Writer
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// New creates a new Logger which will write messages to the given writer.
|
||||
func New(w io.Writer) *Logger {
|
||||
return &Logger{w: timedWriter{w}}
|
||||
}
|
||||
|
||||
// NewSyslog creates a new Logger which will write messages to syslog.
|
||||
func NewSyslog() (*Logger, error) {
|
||||
w, err := syslog.New(syslog.LOG_INFO|syslog.LOG_MAIL, "chasquid")
|
||||
if err != nil {
|
||||
@@ -58,10 +64,12 @@ func (l *Logger) printf(format string, args ...interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// Listening logs that the daemon is listening on the given address.
|
||||
func (l *Logger) Listening(a string) {
|
||||
l.printf("daemon listening on %s\n", a)
|
||||
}
|
||||
|
||||
// Auth logs an authentication request.
|
||||
func (l *Logger) Auth(netAddr net.Addr, user string, successful bool) {
|
||||
res := "succeeded"
|
||||
if !successful {
|
||||
@@ -72,6 +80,7 @@ func (l *Logger) Auth(netAddr net.Addr, user string, successful bool) {
|
||||
authLog.Debugf(msg)
|
||||
}
|
||||
|
||||
// Rejected logs that we've rejected an email.
|
||||
func (l *Logger) Rejected(netAddr net.Addr, from string, to []string, err string) {
|
||||
if from != "" {
|
||||
from = fmt.Sprintf(" from=%s", from)
|
||||
@@ -83,10 +92,12 @@ func (l *Logger) Rejected(netAddr net.Addr, from string, to []string, err string
|
||||
l.printf("%s rejected%s%s - %v\n", netAddr, from, toStr, err)
|
||||
}
|
||||
|
||||
// Queued logs that we have queued an email.
|
||||
func (l *Logger) Queued(netAddr net.Addr, from string, to []string, id string) {
|
||||
l.printf("%s from=%s queued ip=%s to=%v\n", id, from, netAddr, to)
|
||||
}
|
||||
|
||||
// SendAttempt logs that we have attempted to send an email.
|
||||
func (l *Logger) SendAttempt(id, from, to string, err error, permanent bool) {
|
||||
if err == nil {
|
||||
l.printf("%s from=%s to=%s sent\n", id, from, to)
|
||||
@@ -99,6 +110,7 @@ func (l *Logger) SendAttempt(id, from, to string, err error, permanent bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// QueueLoop logs that we have completed a queue loop.
|
||||
func (l *Logger) QueueLoop(id, from string, nextDelay time.Duration) {
|
||||
if nextDelay > 0 {
|
||||
l.printf("%s from=%s completed loop, next in %v\n", id, from, nextDelay)
|
||||
@@ -107,29 +119,35 @@ func (l *Logger) QueueLoop(id, from string, nextDelay time.Duration) {
|
||||
}
|
||||
}
|
||||
|
||||
// The default logger used in the following top-level functions.
|
||||
var Default *Logger = New(ioutil.Discard)
|
||||
// Default logger, used in the following top-level functions.
|
||||
var Default = New(ioutil.Discard)
|
||||
|
||||
// Listening logs that the daemon is listening on the given address.
|
||||
func Listening(a string) {
|
||||
Default.Listening(a)
|
||||
}
|
||||
|
||||
// Auth logs an authentication request.
|
||||
func Auth(netAddr net.Addr, user string, successful bool) {
|
||||
Default.Auth(netAddr, user, successful)
|
||||
}
|
||||
|
||||
// Rejected logs that we've rejected an email.
|
||||
func Rejected(netAddr net.Addr, from string, to []string, err string) {
|
||||
Default.Rejected(netAddr, from, to, err)
|
||||
}
|
||||
|
||||
// Queued logs that we have queued an email.
|
||||
func Queued(netAddr net.Addr, from string, to []string, id string) {
|
||||
Default.Queued(netAddr, from, to, id)
|
||||
}
|
||||
|
||||
// SendAttempt logs that we have attempted to send an email.
|
||||
func SendAttempt(id, from, to string, err error, permanent bool) {
|
||||
Default.SendAttempt(id, from, to, err, permanent)
|
||||
}
|
||||
|
||||
// QueueLoop logs that we have completed a queue loop.
|
||||
func QueueLoop(id, from string, nextDelay time.Duration) {
|
||||
Default.QueueLoop(id, from, nextDelay)
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func Domain(domain string) (string, error) {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// Name normalizes an email address, applying User and Domain to its
|
||||
// Addr normalizes an email address, applying User and Domain to its
|
||||
// respective components.
|
||||
// On error, it will also return the original address to simplify callers.
|
||||
func Addr(addr string) (string, error) {
|
||||
@@ -60,8 +60,8 @@ func Addr(addr string) (string, error) {
|
||||
return user + "@" + domain, nil
|
||||
}
|
||||
|
||||
// Take an address with an ASCII domain, and convert it to Unicode as per
|
||||
// IDNA, including basic normalization.
|
||||
// DomainToUnicode takes an address with an ASCII domain, and convert it to
|
||||
// Unicode as per IDNA, including basic normalization.
|
||||
// The user part is unchanged.
|
||||
func DomainToUnicode(addr string) (string, error) {
|
||||
if addr == "<>" {
|
||||
|
||||
@@ -72,10 +72,12 @@ func (s *Store) idToFname(id string) string {
|
||||
return s.dir + "/" + storeIDPrefix + url.QueryEscape(id)
|
||||
}
|
||||
|
||||
// Put a message into the store.
|
||||
func (s *Store) Put(id string, m proto.Message) error {
|
||||
return WriteTextMessage(s.idToFname(id), m, 0660)
|
||||
}
|
||||
|
||||
// Get a message from the store.
|
||||
func (s *Store) Get(id string, m proto.Message) (bool, error) {
|
||||
err := ReadTextMessage(s.idToFname(id), m)
|
||||
if os.IsNotExist(err) {
|
||||
@@ -84,6 +86,7 @@ func (s *Store) Get(id string, m proto.Message) (bool, error) {
|
||||
return err == nil, err
|
||||
}
|
||||
|
||||
// ListIDs in the store.
|
||||
func (s *Store) ListIDs() ([]string, error) {
|
||||
ids := []string{}
|
||||
|
||||
|
||||
@@ -146,6 +146,7 @@ func (q *Queue) Load() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the queue.
|
||||
func (q *Queue) Len() int {
|
||||
q.mu.RLock()
|
||||
defer q.mu.RUnlock()
|
||||
@@ -253,7 +254,7 @@ func (q *Queue) DumpString() string {
|
||||
return s
|
||||
}
|
||||
|
||||
// An item in the queue.
|
||||
// An Item in the queue.
|
||||
type Item struct {
|
||||
// Base the item on the protobuf message.
|
||||
// We will use this for serialization, so any fields below are NOT
|
||||
@@ -267,6 +268,7 @@ type Item struct {
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// ItemFromFile loads an item from the given file.
|
||||
func ItemFromFile(fname string) (*Item, error) {
|
||||
item := &Item{}
|
||||
err := protoio.ReadTextMessage(fname, &item.Message)
|
||||
@@ -278,6 +280,7 @@ func ItemFromFile(fname string) (*Item, error) {
|
||||
return item, err
|
||||
}
|
||||
|
||||
// WriteTo saves an item to the given directory.
|
||||
func (item *Item) WriteTo(dir string) error {
|
||||
item.Lock()
|
||||
defer item.Unlock()
|
||||
@@ -294,6 +297,7 @@ func (item *Item) WriteTo(dir string) error {
|
||||
return protoio.WriteTextMessage(path, &item.Message, 0600)
|
||||
}
|
||||
|
||||
// SendLoop repeatedly attempts to send the item.
|
||||
func (item *Item) SendLoop(q *Queue) {
|
||||
tr := trace.New("Queue.SendLoop", item.ID)
|
||||
defer tr.Finish()
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Type FileOp represents an operation on a file (passed by its name).
|
||||
// FileOp represents an operation on a file (passed by its name).
|
||||
type FileOp func(fname string) error
|
||||
|
||||
// WriteFile writes data to a file named by filename, atomically.
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
// Package set implement sets for various types. Well, only string for now :)
|
||||
package set
|
||||
|
||||
// String set.
|
||||
type String struct {
|
||||
m map[string]struct{}
|
||||
}
|
||||
|
||||
// NewString returns a new string set, with the given values in it.
|
||||
func NewString(values ...string) *String {
|
||||
s := &String{}
|
||||
s.Add(values...)
|
||||
return s
|
||||
}
|
||||
|
||||
// Add values to the string set.
|
||||
func (s *String) Add(values ...string) {
|
||||
if s.m == nil {
|
||||
s.m = map[string]struct{}{}
|
||||
@@ -21,6 +24,7 @@ func (s *String) Add(values ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Has checks if the set has the given value.
|
||||
func (s *String) Has(value string) bool {
|
||||
// We explicitly allow s to be nil *in this function* to simplify callers'
|
||||
// code. Note that Add will not tolerate it, and will panic.
|
||||
|
||||
@@ -22,6 +22,7 @@ type Client struct {
|
||||
*smtp.Client
|
||||
}
|
||||
|
||||
// NewClient uses the given connection to create a new Client.
|
||||
func NewClient(conn net.Conn, host string) (*Client, error) {
|
||||
c, err := smtp.NewClient(conn, host)
|
||||
if err != nil {
|
||||
@@ -129,7 +130,7 @@ func isASCII(s string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ErrIsPermanent returns true if the error is permanent, and false otherwise.
|
||||
// IsPermanent returns true if the error is permanent, and false otherwise.
|
||||
// If it can't tell, it returns false.
|
||||
func IsPermanent(err error) bool {
|
||||
terr, ok := err.(*textproto.Error)
|
||||
|
||||
@@ -52,7 +52,7 @@ var (
|
||||
disableSPFForTesting = false
|
||||
)
|
||||
|
||||
// Mode for a socket (listening or connection).
|
||||
// SocketMode represents the mode for a socket (listening or connection).
|
||||
// We keep them distinct, as policies can differ between them.
|
||||
type SocketMode struct {
|
||||
// Is this mode submission?
|
||||
@@ -81,7 +81,7 @@ var (
|
||||
ModeSubmissionTLS = SocketMode{IsSubmission: true, TLS: true}
|
||||
)
|
||||
|
||||
// Incoming SMTP connection.
|
||||
// Conn represents an incoming SMTP connection.
|
||||
type Conn struct {
|
||||
// Main hostname, used for display only.
|
||||
hostname string
|
||||
@@ -146,10 +146,13 @@ type Conn struct {
|
||||
commandTimeout time.Duration
|
||||
}
|
||||
|
||||
// Close the connection.
|
||||
func (c *Conn) Close() {
|
||||
c.conn.Close()
|
||||
}
|
||||
|
||||
// Handle implements the main protocol loop (reading commands, sending
|
||||
// replies).
|
||||
func (c *Conn) Handle() {
|
||||
defer c.Close()
|
||||
|
||||
@@ -265,6 +268,7 @@ loop:
|
||||
}
|
||||
}
|
||||
|
||||
// HELO SMTP command handler.
|
||||
func (c *Conn) HELO(params string) (code int, msg string) {
|
||||
if len(strings.TrimSpace(params)) == 0 {
|
||||
return 501, "Invisible customers are not welcome!"
|
||||
@@ -282,6 +286,7 @@ func (c *Conn) HELO(params string) (code int, msg string) {
|
||||
return 250, msg
|
||||
}
|
||||
|
||||
// EHLO SMTP command handler.
|
||||
func (c *Conn) EHLO(params string) (code int, msg string) {
|
||||
if len(strings.TrimSpace(params)) == 0 {
|
||||
return 501, "Invisible customers are not welcome!"
|
||||
@@ -303,10 +308,12 @@ func (c *Conn) EHLO(params string) (code int, msg string) {
|
||||
return 250, buf.String()
|
||||
}
|
||||
|
||||
// HELP SMTP command handler.
|
||||
func (c *Conn) HELP(params string) (code int, msg string) {
|
||||
return 214, "hoy por ti, mañana por mi"
|
||||
}
|
||||
|
||||
// RSET SMTP command handler.
|
||||
func (c *Conn) RSET(params string) (code int, msg string) {
|
||||
c.resetEnvelope()
|
||||
|
||||
@@ -319,6 +326,7 @@ func (c *Conn) RSET(params string) (code int, msg string) {
|
||||
return 250, msgs[rand.Int()%len(msgs)]
|
||||
}
|
||||
|
||||
// VRFY SMTP command handler.
|
||||
func (c *Conn) VRFY(params string) (code int, msg string) {
|
||||
// 252 can be used for cases like ours, when we don't really want to
|
||||
// confirm or deny anything.
|
||||
@@ -326,6 +334,7 @@ func (c *Conn) VRFY(params string) (code int, msg string) {
|
||||
return 252, "You have a strange feeling for a moment, then it passes."
|
||||
}
|
||||
|
||||
// EXPN SMTP command handler.
|
||||
func (c *Conn) EXPN(params string) (code int, msg string) {
|
||||
// 252 can be used for cases like ours, when we don't really want to
|
||||
// confirm or deny anything.
|
||||
@@ -333,10 +342,12 @@ func (c *Conn) EXPN(params string) (code int, msg string) {
|
||||
return 252, "You feel disoriented for a moment."
|
||||
}
|
||||
|
||||
// NOOP SMTP command handler.
|
||||
func (c *Conn) NOOP(params string) (code int, msg string) {
|
||||
return 250, "You hear a faint typing noise."
|
||||
}
|
||||
|
||||
// MAIL SMTP command handler.
|
||||
func (c *Conn) MAIL(params string) (code int, msg string) {
|
||||
// params should be: "FROM:<name@host>", and possibly followed by
|
||||
// options such as "BODY=8BITMIME" (which we ignore).
|
||||
@@ -466,6 +477,7 @@ func (c *Conn) secLevelCheck(addr string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// RCPT SMTP command handler.
|
||||
func (c *Conn) RCPT(params string) (code int, msg string) {
|
||||
// params should be: "TO:<name@host>", and possibly followed by options
|
||||
// such as "NOTIFY=SUCCESS,DELAY" (which we ignore).
|
||||
@@ -531,6 +543,7 @@ func (c *Conn) RCPT(params string) (code int, msg string) {
|
||||
return 250, "You have an eerie feeling..."
|
||||
}
|
||||
|
||||
// DATA SMTP command handler.
|
||||
func (c *Conn) DATA(params string) (code int, msg string) {
|
||||
if c.ehloAddress == "" {
|
||||
return 503, "Invisible customers are not welcome!"
|
||||
@@ -796,6 +809,7 @@ func boolToStr(b bool) string {
|
||||
return "0"
|
||||
}
|
||||
|
||||
// STARTTLS SMTP command handler.
|
||||
func (c *Conn) STARTTLS(params string) (code int, msg string) {
|
||||
if c.onTLS {
|
||||
return 503, "You are already wearing that!"
|
||||
@@ -840,6 +854,7 @@ func (c *Conn) STARTTLS(params string) (code int, msg string) {
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
// AUTH SMTP command handler.
|
||||
func (c *Conn) AUTH(params string) (code int, msg string) {
|
||||
if !c.onTLS {
|
||||
return 503, "You feel vulnerable"
|
||||
|
||||
@@ -28,6 +28,7 @@ var (
|
||||
"how often to reload, ONLY FOR TESTING")
|
||||
)
|
||||
|
||||
// Server represents an SMTP server instance.
|
||||
type Server struct {
|
||||
// Main hostname, used for display only.
|
||||
Hostname string
|
||||
@@ -70,6 +71,7 @@ type Server struct {
|
||||
PostDataHook string
|
||||
}
|
||||
|
||||
// NewServer returns a new empty Server.
|
||||
func NewServer() *Server {
|
||||
return &Server{
|
||||
addrs: map[SocketMode][]string{},
|
||||
@@ -83,6 +85,7 @@ func NewServer() *Server {
|
||||
}
|
||||
}
|
||||
|
||||
// AddCerts (TLS) to the server.
|
||||
func (s *Server) AddCerts(certPath, keyPath string) error {
|
||||
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||
if err != nil {
|
||||
@@ -92,36 +95,44 @@ func (s *Server) AddCerts(certPath, keyPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddAddr adds an address for the server to listen on.
|
||||
func (s *Server) AddAddr(a string, m SocketMode) {
|
||||
s.addrs[m] = append(s.addrs[m], a)
|
||||
}
|
||||
|
||||
// AddListeners adds listeners for the server to listen on.
|
||||
func (s *Server) AddListeners(ls []net.Listener, m SocketMode) {
|
||||
s.listeners[m] = append(s.listeners[m], ls...)
|
||||
}
|
||||
|
||||
// AddDomain adds a local domain to the server.
|
||||
func (s *Server) AddDomain(d string) {
|
||||
s.localDomains.Add(d)
|
||||
s.aliasesR.AddDomain(d)
|
||||
}
|
||||
|
||||
// AddUserDB adds a userdb.DB instance as backend for the domain.
|
||||
func (s *Server) AddUserDB(domain string, db *userdb.DB) {
|
||||
s.authr.Register(domain, auth.WrapNoErrorBackend(db))
|
||||
}
|
||||
|
||||
// AddAliasesFile adds an aliases file for the given domain.
|
||||
func (s *Server) AddAliasesFile(domain, f string) error {
|
||||
return s.aliasesR.AddAliasesFile(domain, f)
|
||||
}
|
||||
|
||||
// SetAuthFallback sets the authentication backend to use as fallback.
|
||||
func (s *Server) SetAuthFallback(be auth.Backend) {
|
||||
s.authr.Fallback = be
|
||||
}
|
||||
|
||||
// SetAliasesConfig sets the aliases configuration options.
|
||||
func (s *Server) SetAliasesConfig(suffixSep, dropChars string) {
|
||||
s.aliasesR.SuffixSep = suffixSep
|
||||
s.aliasesR.DropChars = dropChars
|
||||
}
|
||||
|
||||
// InitDomainInfo initializes the domain info database.
|
||||
func (s *Server) InitDomainInfo(dir string) *domaininfo.DB {
|
||||
var err error
|
||||
s.dinfo, err = domaininfo.New(dir)
|
||||
@@ -137,6 +148,7 @@ func (s *Server) InitDomainInfo(dir string) *domaininfo.DB {
|
||||
return s.dinfo
|
||||
}
|
||||
|
||||
// InitQueue initializes the queue.
|
||||
func (s *Server) InitQueue(path string, localC, remoteC courier.Courier) {
|
||||
q := queue.New(path, s.localDomains, s.aliasesR, localC, remoteC)
|
||||
err := q.Load()
|
||||
@@ -151,7 +163,7 @@ func (s *Server) InitQueue(path string, localC, remoteC courier.Courier) {
|
||||
})
|
||||
}
|
||||
|
||||
// PeriodicallyReload some of the server's information, such as aliases and
|
||||
// periodicallyReload some of the server's information, such as aliases and
|
||||
// the user databases.
|
||||
func (s *Server) periodicallyReload() {
|
||||
for range time.Tick(*reloadEvery) {
|
||||
@@ -167,6 +179,8 @@ func (s *Server) periodicallyReload() {
|
||||
}
|
||||
}
|
||||
|
||||
// ListenAndServe on the addresses and listeners that were previously added.
|
||||
// This function will not return.
|
||||
func (s *Server) ListenAndServe() {
|
||||
if len(s.tlsConfig.Certificates) == 0 {
|
||||
// chasquid assumes there's at least one valid certificate (for things
|
||||
|
||||
@@ -10,12 +10,14 @@ import (
|
||||
nettrace "golang.org/x/net/trace"
|
||||
)
|
||||
|
||||
// A Trace represents an active request.
|
||||
type Trace struct {
|
||||
family string
|
||||
title string
|
||||
t nettrace.Trace
|
||||
}
|
||||
|
||||
// New trace.
|
||||
func New(family, title string) *Trace {
|
||||
t := &Trace{family, title, nettrace.New(family, title)}
|
||||
|
||||
@@ -26,6 +28,7 @@ func New(family, title string) *Trace {
|
||||
return t
|
||||
}
|
||||
|
||||
// Printf adds this message to the trace's log.
|
||||
func (t *Trace) Printf(format string, a ...interface{}) {
|
||||
t.t.LazyPrintf(format, a...)
|
||||
|
||||
@@ -33,6 +36,7 @@ func (t *Trace) Printf(format string, a ...interface{}) {
|
||||
quote(fmt.Sprintf(format, a...)))
|
||||
}
|
||||
|
||||
// Debugf adds this message to the trace's log, with a debugging level.
|
||||
func (t *Trace) Debugf(format string, a ...interface{}) {
|
||||
t.t.LazyPrintf(format, a...)
|
||||
|
||||
@@ -40,6 +44,7 @@ func (t *Trace) Debugf(format string, a ...interface{}) {
|
||||
t.family, t.title, quote(fmt.Sprintf(format, a...)))
|
||||
}
|
||||
|
||||
// Errorf adds this message to the trace's log, with an error level.
|
||||
func (t *Trace) Errorf(format string, a ...interface{}) error {
|
||||
// Note we can't just call t.Error here, as it breaks caller logging.
|
||||
err := fmt.Errorf(format, a...)
|
||||
@@ -51,6 +56,8 @@ func (t *Trace) Errorf(format string, a ...interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Error marks the trace as having seen an error, and also logs it to the
|
||||
// trace's log.
|
||||
func (t *Trace) Error(err error) error {
|
||||
t.t.SetError()
|
||||
t.t.LazyPrintf("error: %v", err)
|
||||
@@ -61,20 +68,24 @@ func (t *Trace) Error(err error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Finish the trace. It should not be changed after this is called.
|
||||
func (t *Trace) Finish() {
|
||||
t.t.Finish()
|
||||
}
|
||||
|
||||
// EventLog is used for tracing long-lived objects.
|
||||
type EventLog struct {
|
||||
family string
|
||||
title string
|
||||
e nettrace.EventLog
|
||||
}
|
||||
|
||||
// NewEventLog returns a new EventLog.
|
||||
func NewEventLog(family, title string) *EventLog {
|
||||
return &EventLog{family, title, nettrace.NewEventLog(family, title)}
|
||||
}
|
||||
|
||||
// Printf adds the message to the EventLog.
|
||||
func (e *EventLog) Printf(format string, a ...interface{}) {
|
||||
e.e.Printf(format, a...)
|
||||
|
||||
@@ -82,6 +93,7 @@ func (e *EventLog) Printf(format string, a ...interface{}) {
|
||||
quote(fmt.Sprintf(format, a...)))
|
||||
}
|
||||
|
||||
// Debugf adds the message to the EventLog, with a debugging level.
|
||||
func (e *EventLog) Debugf(format string, a ...interface{}) {
|
||||
e.e.Printf(format, a...)
|
||||
|
||||
@@ -89,6 +101,7 @@ func (e *EventLog) Debugf(format string, a ...interface{}) {
|
||||
quote(fmt.Sprintf(format, a...)))
|
||||
}
|
||||
|
||||
// Errorf adds the message to the EventLog, with an error level.
|
||||
func (e *EventLog) Errorf(format string, a ...interface{}) error {
|
||||
err := fmt.Errorf(format, a...)
|
||||
e.e.Errorf("error: %v", err)
|
||||
|
||||
@@ -45,6 +45,7 @@ import (
|
||||
"blitiri.com.ar/go/chasquid/internal/protoio"
|
||||
)
|
||||
|
||||
// DB represents a single user database.
|
||||
type DB struct {
|
||||
fname string
|
||||
db *ProtoDB
|
||||
@@ -53,10 +54,7 @@ type DB struct {
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidUsername = errors.New("invalid username")
|
||||
)
|
||||
|
||||
// New returns a new user database, on the given file name.
|
||||
func New(fname string) *DB {
|
||||
return &DB{
|
||||
fname: fname,
|
||||
@@ -106,7 +104,8 @@ func (db *DB) Write() error {
|
||||
return protoio.WriteTextMessage(db.fname, db.db, 0660)
|
||||
}
|
||||
|
||||
// Is this password valid for the user?
|
||||
// Authenticate returns true if the password is valid for the user, false
|
||||
// otherwise.
|
||||
func (db *DB) Authenticate(name, plainPassword string) bool {
|
||||
db.mu.RLock()
|
||||
passwd, ok := db.db.Users[name]
|
||||
@@ -119,6 +118,7 @@ func (db *DB) Authenticate(name, plainPassword string) bool {
|
||||
return passwd.PasswordMatches(plainPassword)
|
||||
}
|
||||
|
||||
// PasswordMatches returns true if the given password is a match.
|
||||
func (p *Password) PasswordMatches(plain string) bool {
|
||||
switch s := p.Scheme.(type) {
|
||||
case nil:
|
||||
@@ -132,11 +132,11 @@ func (p *Password) PasswordMatches(plain string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Add a user to the database. If the user is already present, override it.
|
||||
// AddUser to the database. If the user is already present, override it.
|
||||
// Note we enforce that the name has been normalized previously.
|
||||
func (db *DB) AddUser(name, plainPassword string) error {
|
||||
if norm, err := normalize.User(name); err != nil || name != norm {
|
||||
return ErrInvalidUsername
|
||||
return errors.New("invalid username")
|
||||
}
|
||||
|
||||
s := &Scrypt{
|
||||
@@ -178,7 +178,7 @@ func (db *DB) RemoveUser(name string) bool {
|
||||
return present
|
||||
}
|
||||
|
||||
// Exists returns true if the user is present, False otherwise.
|
||||
// Exists returns true if the user is present, false otherwise.
|
||||
func (db *DB) Exists(name string) bool {
|
||||
db.mu.Lock()
|
||||
_, present := db.db.Users[name]
|
||||
@@ -190,7 +190,8 @@ func (db *DB) Exists(name string) bool {
|
||||
// Encryption schemes
|
||||
//
|
||||
|
||||
// Plain text scheme. Useful mostly for testing and debugging.
|
||||
// PasswordMatches implementation for the plain text scheme.
|
||||
// Useful mostly for testing and debugging.
|
||||
// TODO: Do we really need this? Removing it would make accidents less likely
|
||||
// to happen. Consider doing so when we add another scheme, so we a least have
|
||||
// two and multi-scheme support does not bit-rot.
|
||||
@@ -198,7 +199,8 @@ func (p *Plain) PasswordMatches(plain string) bool {
|
||||
return plain == string(p.Password)
|
||||
}
|
||||
|
||||
// scrypt scheme, which we use by default.
|
||||
// PasswordMatches implementation for the scrypt scheme, which we use by
|
||||
// default.
|
||||
func (s *Scrypt) PasswordMatches(plain string) bool {
|
||||
dk, err := scrypt.Key([]byte(plain), s.Salt,
|
||||
1<<s.LogN, int(s.R), int(s.P), int(s.KeyLen))
|
||||
|
||||
Reference in New Issue
Block a user