author | Alberto Bertogli
<albertito@blitiri.com.ar> 2018-03-04 15:54:34 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2018-03-04 16:00:06 UTC |
parent | 40ae9b5f693ef67800ddeeb9fb27eb26f4f9d31e |
cmd/chasquid-util/chasquid-util.go | +13 | -12 |
internal/aliases/aliases.go | +13 | -1 |
internal/auth/auth.go | +9 | -4 |
internal/config/config.go | +1 | -0 |
internal/courier/procmail.go | +2 | -0 |
internal/courier/smtp.go | +2 | -0 |
internal/domaininfo/domaininfo.go | +3 | -0 |
internal/dovecot/dovecot.go | +10 | -6 |
internal/dovecot/dovecot_test.go | +2 | -2 |
internal/envelope/envelope.go | +4 | -0 |
internal/maillog/maillog.go | +20 | -2 |
internal/normalize/normalize.go | +3 | -3 |
internal/protoio/protoio.go | +3 | -0 |
internal/queue/queue.go | +5 | -1 |
internal/safeio/safeio.go | +1 | -1 |
internal/set/set.go | +4 | -0 |
internal/smtp/smtp.go | +2 | -1 |
internal/smtpsrv/conn.go | +17 | -2 |
internal/smtpsrv/server.go | +15 | -1 |
internal/trace/trace.go | +13 | -0 |
internal/userdb/userdb.go | +12 | -10 |
diff --git a/cmd/chasquid-util/chasquid-util.go b/cmd/chasquid-util/chasquid-util.go index a617e0e..5a9b145 100644 --- a/cmd/chasquid-util/chasquid-util.go +++ b/cmd/chasquid-util/chasquid-util.go @@ -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") diff --git a/internal/aliases/aliases.go b/internal/aliases/aliases.go index 31ef18b..6635e72 100644 --- a/internal/aliases/aliases.go +++ b/internal/aliases/aliases.go @@ -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{} diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 6ab74c2..c9b543f 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -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) diff --git a/internal/config/config.go b/internal/config/config.go index 5bf4abd..ef35308 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -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) diff --git a/internal/courier/procmail.go b/internal/courier/procmail.go index 0a0aa6e..9f2dc7e 100644 --- a/internal/courier/procmail.go +++ b/internal/courier/procmail.go @@ -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() diff --git a/internal/courier/smtp.go b/internal/courier/smtp.go index 7f1ce25..3df9a5c 100644 --- a/internal/courier/smtp.go +++ b/internal/courier/smtp.go @@ -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, diff --git a/internal/domaininfo/domaininfo.go b/internal/domaininfo/domaininfo.go index 841f459..2d7ff42 100644 --- a/internal/domaininfo/domaininfo.go +++ b/internal/domaininfo/domaininfo.go @@ -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 { diff --git a/internal/dovecot/dovecot.go b/internal/dovecot/dovecot.go index 855cfee..f6d2c13 100644 --- a/internal/dovecot/dovecot.go +++ b/internal/dovecot/dovecot.go @@ -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 } diff --git a/internal/dovecot/dovecot_test.go b/internal/dovecot/dovecot_test.go index 4fcbfc7..f2fd887 100644 --- a/internal/dovecot/dovecot_test.go +++ b/internal/dovecot/dovecot_test.go @@ -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) } } diff --git a/internal/envelope/envelope.go b/internal/envelope/envelope.go index c504ad0..ec04bf9 100644 --- a/internal/envelope/envelope.go +++ b/internal/envelope/envelope.go @@ -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. diff --git a/internal/maillog/maillog.go b/internal/maillog/maillog.go index 568bc6f..322d8b3 100644 --- a/internal/maillog/maillog.go +++ b/internal/maillog/maillog.go @@ -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) } diff --git a/internal/normalize/normalize.go b/internal/normalize/normalize.go index 569dc76..df68226 100644 --- a/internal/normalize/normalize.go +++ b/internal/normalize/normalize.go @@ -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 == "<>" { diff --git a/internal/protoio/protoio.go b/internal/protoio/protoio.go index 4f5ac10..f066955 100644 --- a/internal/protoio/protoio.go +++ b/internal/protoio/protoio.go @@ -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{} diff --git a/internal/queue/queue.go b/internal/queue/queue.go index 7b3d4e3..cd92e58 100644 --- a/internal/queue/queue.go +++ b/internal/queue/queue.go @@ -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() diff --git a/internal/safeio/safeio.go b/internal/safeio/safeio.go index a78302d..dc742f7 100644 --- a/internal/safeio/safeio.go +++ b/internal/safeio/safeio.go @@ -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. diff --git a/internal/set/set.go b/internal/set/set.go index e53308a..dfaf9c9 100644 --- a/internal/set/set.go +++ b/internal/set/set.go @@ -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. diff --git a/internal/smtp/smtp.go b/internal/smtp/smtp.go index aa67696..bb91b85 100644 --- a/internal/smtp/smtp.go +++ b/internal/smtp/smtp.go @@ -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) diff --git a/internal/smtpsrv/conn.go b/internal/smtpsrv/conn.go index 42a8cd7..14aee1c 100644 --- a/internal/smtpsrv/conn.go +++ b/internal/smtpsrv/conn.go @@ -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" diff --git a/internal/smtpsrv/server.go b/internal/smtpsrv/server.go index 640a17e..f3cb0e6 100644 --- a/internal/smtpsrv/server.go +++ b/internal/smtpsrv/server.go @@ -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 diff --git a/internal/trace/trace.go b/internal/trace/trace.go index e2b004c..84880c9 100644 --- a/internal/trace/trace.go +++ b/internal/trace/trace.go @@ -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) diff --git a/internal/userdb/userdb.go b/internal/userdb/userdb.go index 0359fbb..81b04bc 100644 --- a/internal/userdb/userdb.go +++ b/internal/userdb/userdb.go @@ -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))