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