mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-17 17:47:03 +00:00
- Rename BUILD_DATE to BUILDDATE in goxc - Update travis config - Follow linter recommendations for inbucket.go - Follow linter recommendations for config package - Follow linter recommendations for log package - Follow linter recommendations for pop3d package - Follow linter recommendations for smtpd package - Follow linter recommendations for web package - Fix Id -> ID in templates - Add shebang to REST demo scripts - Add or refine many comments
400 lines
11 KiB
Go
400 lines
11 KiB
Go
package config
|
|
|
|
import (
|
|
"container/list"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/robfig/config"
|
|
)
|
|
|
|
// SMTPConfig contains the SMTP server configuration - not using pointers
|
|
// so that we can pass around copies of the object safely.
|
|
type SMTPConfig struct {
|
|
IP4address net.IP
|
|
IP4port int
|
|
Domain string
|
|
DomainNoStore string
|
|
MaxRecipients int
|
|
MaxIdleSeconds int
|
|
MaxMessageBytes int
|
|
StoreMessages bool
|
|
}
|
|
|
|
// POP3Config contains the POP3 server configuration
|
|
type POP3Config struct {
|
|
IP4address net.IP
|
|
IP4port int
|
|
Domain string
|
|
MaxIdleSeconds int
|
|
}
|
|
|
|
// WebConfig contains the HTTP server configuration
|
|
type WebConfig struct {
|
|
IP4address net.IP
|
|
IP4port int
|
|
TemplateDir string
|
|
TemplateCache bool
|
|
PublicDir string
|
|
GreetingFile string
|
|
}
|
|
|
|
// DataStoreConfig contains the mail store configuration
|
|
type DataStoreConfig struct {
|
|
Path string
|
|
RetentionMinutes int
|
|
RetentionSleep int
|
|
MailboxMsgCap int
|
|
}
|
|
|
|
var (
|
|
// Version of this build, set by main
|
|
Version = ""
|
|
|
|
// BuildDate for this build, set by main
|
|
BuildDate = ""
|
|
|
|
// Config is our global robfig/config object
|
|
Config *config.Config
|
|
|
|
// Parsed specific configs
|
|
smtpConfig *SMTPConfig
|
|
pop3Config *POP3Config
|
|
webConfig *WebConfig
|
|
dataStoreConfig *DataStoreConfig
|
|
)
|
|
|
|
// GetSMTPConfig returns a copy of the SmtpConfig object
|
|
func GetSMTPConfig() SMTPConfig {
|
|
return *smtpConfig
|
|
}
|
|
|
|
// GetPOP3Config returns a copy of the Pop3Config object
|
|
func GetPOP3Config() POP3Config {
|
|
return *pop3Config
|
|
}
|
|
|
|
// GetWebConfig returns a copy of the WebConfig object
|
|
func GetWebConfig() WebConfig {
|
|
return *webConfig
|
|
}
|
|
|
|
// GetDataStoreConfig returns a copy of the DataStoreConfig object
|
|
func GetDataStoreConfig() DataStoreConfig {
|
|
return *dataStoreConfig
|
|
}
|
|
|
|
// LoadConfig loads the specified configuration file into inbucket.Config
|
|
// and performs validations on it.
|
|
func LoadConfig(filename string) error {
|
|
var err error
|
|
Config, err = config.ReadDefault(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
messages := list.New()
|
|
|
|
// Validate sections
|
|
requireSection(messages, "logging")
|
|
requireSection(messages, "smtp")
|
|
requireSection(messages, "pop3")
|
|
requireSection(messages, "web")
|
|
requireSection(messages, "datastore")
|
|
if messages.Len() > 0 {
|
|
fmt.Fprintln(os.Stderr, "Error(s) validating configuration:")
|
|
for e := messages.Front(); e != nil; e = e.Next() {
|
|
fmt.Fprintln(os.Stderr, " -", e.Value.(string))
|
|
}
|
|
return fmt.Errorf("Failed to validate configuration")
|
|
}
|
|
|
|
// Validate options
|
|
requireOption(messages, "logging", "level")
|
|
requireOption(messages, "smtp", "ip4.address")
|
|
requireOption(messages, "smtp", "ip4.port")
|
|
requireOption(messages, "smtp", "domain")
|
|
requireOption(messages, "smtp", "max.recipients")
|
|
requireOption(messages, "smtp", "max.idle.seconds")
|
|
requireOption(messages, "smtp", "max.message.bytes")
|
|
requireOption(messages, "smtp", "store.messages")
|
|
requireOption(messages, "pop3", "ip4.address")
|
|
requireOption(messages, "pop3", "ip4.port")
|
|
requireOption(messages, "pop3", "domain")
|
|
requireOption(messages, "pop3", "max.idle.seconds")
|
|
requireOption(messages, "web", "ip4.address")
|
|
requireOption(messages, "web", "ip4.port")
|
|
requireOption(messages, "web", "template.dir")
|
|
requireOption(messages, "web", "template.cache")
|
|
requireOption(messages, "web", "public.dir")
|
|
requireOption(messages, "datastore", "path")
|
|
requireOption(messages, "datastore", "retention.minutes")
|
|
requireOption(messages, "datastore", "retention.sleep.millis")
|
|
requireOption(messages, "datastore", "mailbox.message.cap")
|
|
|
|
// Return error if validations failed
|
|
if messages.Len() > 0 {
|
|
fmt.Fprintln(os.Stderr, "Error(s) validating configuration:")
|
|
for e := messages.Front(); e != nil; e = e.Next() {
|
|
fmt.Fprintln(os.Stderr, " -", e.Value.(string))
|
|
}
|
|
return fmt.Errorf("Failed to validate configuration")
|
|
}
|
|
|
|
if err = parseSMTPConfig(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = parsePOP3Config(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = parseWebConfig(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = parseDataStoreConfig(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseLoggingConfig trying to catch config errors early
|
|
func parseLoggingConfig() error {
|
|
section := "logging"
|
|
|
|
option := "level"
|
|
str, err := Config.String(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
switch strings.ToUpper(str) {
|
|
case "TRACE", "INFO", "WARN", "ERROR":
|
|
default:
|
|
return fmt.Errorf("Invalid value provided for [%v]%v: '%v'", section, option, str)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// parseSMTPConfig trying to catch config errors early
|
|
func parseSMTPConfig() error {
|
|
smtpConfig = new(SMTPConfig)
|
|
section := "smtp"
|
|
|
|
// Parse IP4 address only, error on IP6.
|
|
option := "ip4.address"
|
|
str, err := Config.String(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
addr := net.ParseIP(str)
|
|
if addr == nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
addr = addr.To4()
|
|
if addr == nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v' not IPv4!", section, option, err)
|
|
}
|
|
smtpConfig.IP4address = addr
|
|
|
|
option = "ip4.port"
|
|
smtpConfig.IP4port, err = Config.Int(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
|
|
option = "domain"
|
|
str, err = Config.String(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
smtpConfig.Domain = str
|
|
|
|
option = "domain.nostore"
|
|
if Config.HasOption(section, option) {
|
|
str, err = Config.String(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
smtpConfig.DomainNoStore = str
|
|
}
|
|
|
|
option = "max.recipients"
|
|
smtpConfig.MaxRecipients, err = Config.Int(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
|
|
option = "max.idle.seconds"
|
|
smtpConfig.MaxIdleSeconds, err = Config.Int(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
|
|
option = "max.message.bytes"
|
|
smtpConfig.MaxMessageBytes, err = Config.Int(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
|
|
option = "store.messages"
|
|
flag, err := Config.Bool(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
smtpConfig.StoreMessages = flag
|
|
|
|
return nil
|
|
}
|
|
|
|
// parsePOP3Config trying to catch config errors early
|
|
func parsePOP3Config() error {
|
|
pop3Config = new(POP3Config)
|
|
section := "pop3"
|
|
|
|
// Parse IP4 address only, error on IP6.
|
|
option := "ip4.address"
|
|
str, err := Config.String(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
addr := net.ParseIP(str)
|
|
if addr == nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
addr = addr.To4()
|
|
if addr == nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v' not IPv4!", section, option, err)
|
|
}
|
|
pop3Config.IP4address = addr
|
|
|
|
option = "ip4.port"
|
|
pop3Config.IP4port, err = Config.Int(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
|
|
option = "domain"
|
|
str, err = Config.String(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
pop3Config.Domain = str
|
|
|
|
option = "max.idle.seconds"
|
|
pop3Config.MaxIdleSeconds, err = Config.Int(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseWebConfig trying to catch config errors early
|
|
func parseWebConfig() error {
|
|
webConfig = new(WebConfig)
|
|
section := "web"
|
|
|
|
// Parse IP4 address only, error on IP6.
|
|
option := "ip4.address"
|
|
str, err := Config.String(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
addr := net.ParseIP(str)
|
|
if addr == nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
addr = addr.To4()
|
|
if addr == nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v' not IPv4!", section, option, err)
|
|
}
|
|
webConfig.IP4address = addr
|
|
|
|
option = "ip4.port"
|
|
webConfig.IP4port, err = Config.Int(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
|
|
option = "template.dir"
|
|
str, err = Config.String(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
webConfig.TemplateDir = str
|
|
|
|
option = "template.cache"
|
|
flag, err := Config.Bool(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
webConfig.TemplateCache = flag
|
|
|
|
option = "public.dir"
|
|
str, err = Config.String(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
webConfig.PublicDir = str
|
|
|
|
option = "greeting.file"
|
|
str, err = Config.String(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
webConfig.GreetingFile = str
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseDataStoreConfig trying to catch config errors early
|
|
func parseDataStoreConfig() error {
|
|
dataStoreConfig = new(DataStoreConfig)
|
|
section := "datastore"
|
|
|
|
option := "path"
|
|
str, err := Config.String(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
dataStoreConfig.Path = str
|
|
|
|
option = "retention.minutes"
|
|
dataStoreConfig.RetentionMinutes, err = Config.Int(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
option = "retention.sleep.millis"
|
|
dataStoreConfig.RetentionSleep, err = Config.Int(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
option = "mailbox.message.cap"
|
|
dataStoreConfig.MailboxMsgCap, err = Config.Int(section, option)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to parse [%v]%v: '%v'", section, option, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// requireSection checks that a [section] is defined in the configuration file,
|
|
// appending a message if not.
|
|
func requireSection(messages *list.List, section string) {
|
|
if !Config.HasSection(section) {
|
|
messages.PushBack(fmt.Sprintf("Config section [%v] is required", section))
|
|
}
|
|
}
|
|
|
|
// requireOption checks that 'option' is defined in [section] of the config file,
|
|
// appending a message if not.
|
|
func requireOption(messages *list.List, section string, option string) {
|
|
if !Config.HasOption(section, option) {
|
|
messages.PushBack(fmt.Sprintf("Config option '%v' is required in section [%v]", option, section))
|
|
}
|
|
}
|