2021-01-09 17:06:13 +01:00
|
|
|
package config
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"os/exec"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/BurntSushi/toml"
|
|
|
|
)
|
|
|
|
|
|
|
|
// maybeInt is an integer that may be nil
|
|
|
|
type maybeInt struct {
|
|
|
|
int
|
|
|
|
bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m maybeInt) empty() bool {
|
|
|
|
return !m.bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m maybeInt) value() int {
|
|
|
|
return m.int
|
|
|
|
}
|
|
|
|
|
|
|
|
func initMaybeInt(value int) maybeInt {
|
|
|
|
return maybeInt{value, true}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *maybeInt) UnmarshalText(text []byte) error {
|
|
|
|
var err error
|
|
|
|
var i int64
|
|
|
|
if text != nil {
|
|
|
|
str := string(text)
|
|
|
|
i, err = strconv.ParseInt(str, 10, 64)
|
|
|
|
m.int = int(i)
|
|
|
|
m.bool = true
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
type fileConfig struct {
|
2021-01-31 01:14:00 +01:00
|
|
|
Default string `toml:"default_account"`
|
2021-01-09 17:06:13 +01:00
|
|
|
MailboxPath string `toml:"mailbox_path"`
|
|
|
|
Accounts []account `toml:"account"`
|
|
|
|
DefaultMessages maybeInt `toml:"default_messages"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type account struct {
|
|
|
|
Name string `toml:"name"`
|
|
|
|
MailboxPath string `toml:"mailbox_path"`
|
|
|
|
ConnectionInfo *connectionInfo `toml:"connection"`
|
|
|
|
ExcludedFolders []string `toml:"excluded_folders"`
|
|
|
|
Messages maybeInt `toml:"messages"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type connectionInfo struct {
|
|
|
|
Host string `toml:"hostname"`
|
|
|
|
Port int `toml:"port"`
|
|
|
|
Username string `toml:"username"`
|
|
|
|
PasswordExec *ShellScript `toml:"password_exec"`
|
|
|
|
PasswordFile string `toml:"password_file"`
|
|
|
|
Password string `toml:"password"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *connectionInfo) String() string {
|
|
|
|
var res string
|
|
|
|
res += "{"
|
|
|
|
res += fmt.Sprintf("Host: %s, ", c.Host)
|
|
|
|
res += fmt.Sprintf("Port: %d, ", c.Port)
|
|
|
|
res += fmt.Sprintf("Username: %s, ", c.Username)
|
|
|
|
res += fmt.Sprintf("Password: **********, ")
|
|
|
|
res += fmt.Sprintf("PasswordFile: %s, ", c.PasswordFile)
|
|
|
|
res += fmt.Sprintf("PasswordExec: %s}", c.PasswordExec)
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
// ShellScript might be either:
|
|
|
|
// - the path to an executable
|
|
|
|
// - an executable in path
|
|
|
|
// - a shell command with its argument
|
|
|
|
// all these must return on stdout the password after successful execution.
|
|
|
|
type ShellScript struct {
|
|
|
|
Executable string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ShellScript) UnmarshalText(text []byte) error {
|
|
|
|
s.Executable = string(text)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ShellScript) String() string {
|
|
|
|
return s.Executable
|
|
|
|
}
|
|
|
|
|
|
|
|
// Present indicates if the value has been filled in into the config
|
|
|
|
func (s *ShellScript) Present() bool {
|
|
|
|
if s != nil {
|
|
|
|
return s.Executable != ""
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run tries to execute the command, first looking for a corresponding executable and
|
|
|
|
// invoking it, then invoking the string as a command followed by its parameters.
|
|
|
|
func (s *ShellScript) Run() (string, error) {
|
|
|
|
if path, err := exec.LookPath(s.Executable); err == nil {
|
|
|
|
return doExec(path)
|
|
|
|
}
|
|
|
|
parts := strings.Split(s.Executable, " ")
|
|
|
|
return doExec(parts[0], parts[1:]...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func doExec(path string, args ...string) (string, error) {
|
|
|
|
cmd := exec.Command(path, args...)
|
|
|
|
out, err := cmd.Output()
|
|
|
|
if err != nil {
|
|
|
|
log.Println("Error running:", cmd)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return string(out), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseFile reads the given config file and, if succeeding, returns a *FileConfig.
|
|
|
|
func parseFile(path string) (*fileConfig, error) {
|
|
|
|
newConfig := fileConfig{}
|
|
|
|
_, err := toml.DecodeFile(path, &newConfig)
|
|
|
|
return &newConfig, err
|
|
|
|
}
|