outgoing-cred-cmd: delay execution until an email needs to be sent
This can be useful in cases when: 1. outgoing-cred-cmd requires a user action or confirmation (e.g. when using pass with a Yubikey or similar smart card that requires a user to enter a pin or touch the device when decrypting the password) 2. A user starts aerc frequently, but not all the sessions end up with sending emails 3. So the user only wants to execute outgoing-cred-cmd when the password is really used, so the user doesn't have to enter pin or touch their Yubikey each time aerc starts Signed-off-by: Stas Rudakou <stas@garage22.net> Acked-by: Robin Jarry <robin@jarry.cc>
This commit is contained in:
parent
e73e5065b0
commit
ca90343850
|
@ -51,7 +51,11 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
|
|||
tabName := tab.Name
|
||||
config := composer.Config()
|
||||
|
||||
if config.Outgoing == "" {
|
||||
outgoing, err := config.Outgoing.ConnectionString()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "ReadCredentials(outgoing)")
|
||||
}
|
||||
if outgoing == "" {
|
||||
return errors.New(
|
||||
"No outgoing mail transport configured for this account")
|
||||
}
|
||||
|
@ -74,7 +78,7 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
|
|||
return errors.Wrap(err, "ParseAddress(config.From)")
|
||||
}
|
||||
|
||||
uri, err := url.Parse(config.Outgoing)
|
||||
uri, err := url.Parse(outgoing)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "url.Parse(outgoing)")
|
||||
}
|
||||
|
|
|
@ -95,6 +95,50 @@ const (
|
|||
FILTER_HEADER
|
||||
)
|
||||
|
||||
type RemoteConfig struct {
|
||||
Value string
|
||||
PasswordCmd string
|
||||
}
|
||||
|
||||
func (c RemoteConfig) parseValue() (*url.URL, error) {
|
||||
return url.Parse(c.Value)
|
||||
}
|
||||
|
||||
func (c RemoteConfig) ConnectionString() (string, error) {
|
||||
if c.Value == "" || c.PasswordCmd == "" {
|
||||
return c.Value, nil
|
||||
}
|
||||
|
||||
u, err := c.parseValue()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// ignore the command if a password is specified
|
||||
if _, exists := u.User.Password(); exists {
|
||||
return c.Value, nil
|
||||
}
|
||||
|
||||
// don't attempt to parse the command if the url is a path (ie /usr/bin/sendmail)
|
||||
if !u.IsAbs() {
|
||||
return c.Value, nil
|
||||
}
|
||||
|
||||
cmd := exec.Command("sh", "-c", c.PasswordCmd)
|
||||
cmd.Stdin = os.Stdin
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read password: %s", err)
|
||||
}
|
||||
|
||||
pw := strings.TrimSpace(string(output))
|
||||
u.User = url.UserPassword(u.User.Username(), pw)
|
||||
c.Value = u.String()
|
||||
c.PasswordCmd = ""
|
||||
|
||||
return c.Value, nil
|
||||
}
|
||||
|
||||
type AccountConfig struct {
|
||||
Archive string
|
||||
CopyTo string
|
||||
|
@ -104,12 +148,10 @@ type AccountConfig struct {
|
|||
Aliases string
|
||||
Name string
|
||||
Source string
|
||||
SourceCredCmd string
|
||||
Folders []string
|
||||
FoldersExclude []string
|
||||
Params map[string]string
|
||||
Outgoing string
|
||||
OutgoingCredCmd string
|
||||
Outgoing RemoteConfig
|
||||
SignatureFile string
|
||||
SignatureCmd string
|
||||
EnableFoldersSort bool `ini:"enable-folders-sort"`
|
||||
|
@ -239,6 +281,7 @@ func loadAccountConfig(path string) ([]AccountConfig, error) {
|
|||
continue
|
||||
}
|
||||
sec := file.Section(_sec)
|
||||
sourceRemoteConfig := RemoteConfig{}
|
||||
account := AccountConfig{
|
||||
Archive: "Archive",
|
||||
Default: "INBOX",
|
||||
|
@ -260,12 +303,14 @@ func loadAccountConfig(path string) ([]AccountConfig, error) {
|
|||
folders := strings.Split(val, ",")
|
||||
sort.Strings(folders)
|
||||
account.FoldersExclude = folders
|
||||
} else if key == "source" {
|
||||
sourceRemoteConfig.Value = val
|
||||
} else if key == "source-cred-cmd" {
|
||||
account.SourceCredCmd = val
|
||||
sourceRemoteConfig.PasswordCmd = val
|
||||
} else if key == "outgoing" {
|
||||
account.Outgoing = val
|
||||
account.Outgoing.Value = val
|
||||
} else if key == "outgoing-cred-cmd" {
|
||||
account.OutgoingCredCmd = val
|
||||
account.Outgoing.PasswordCmd = val
|
||||
} else if key == "from" {
|
||||
account.From = val
|
||||
} else if key == "aliases" {
|
||||
|
@ -295,56 +340,22 @@ func loadAccountConfig(path string) ([]AccountConfig, error) {
|
|||
return nil, fmt.Errorf("Expected from for account %s", _sec)
|
||||
}
|
||||
|
||||
source, err := parseCredential(account.Source, account.SourceCredCmd)
|
||||
source, err := sourceRemoteConfig.ConnectionString()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid source credentials for %s: %s", _sec, err)
|
||||
}
|
||||
account.Source = source
|
||||
|
||||
outgoing, err := parseCredential(account.Outgoing, account.OutgoingCredCmd)
|
||||
_, err = account.Outgoing.parseValue()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Invalid outgoing credentials for %s: %s", _sec, err)
|
||||
}
|
||||
account.Outgoing = outgoing
|
||||
|
||||
accounts = append(accounts, account)
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func parseCredential(cred, command string) (string, error) {
|
||||
if cred == "" || command == "" {
|
||||
return cred, nil
|
||||
}
|
||||
|
||||
u, err := url.Parse(cred)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// ignore the command if a password is specified
|
||||
if _, exists := u.User.Password(); exists {
|
||||
return cred, nil
|
||||
}
|
||||
|
||||
// don't attempt to parse the command if the url is a path (ie /usr/bin/sendmail)
|
||||
if !u.IsAbs() {
|
||||
return cred, nil
|
||||
}
|
||||
|
||||
cmd := exec.Command("sh", "-c", command)
|
||||
cmd.Stdin = os.Stdin
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read password: %s", err)
|
||||
}
|
||||
|
||||
pw := strings.TrimSpace(string(output))
|
||||
u.User = url.UserPassword(u.User.Username(), pw)
|
||||
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
// Set at build time
|
||||
var shareDir string
|
||||
|
||||
|
|
|
@ -522,7 +522,7 @@ func (wizard *AccountWizard) finish(tutorial bool) {
|
|||
Default: "INBOX",
|
||||
From: sec.Key("from").String(),
|
||||
Source: sec.Key("source").String(),
|
||||
Outgoing: sec.Key("outgoing").String(),
|
||||
Outgoing: config.RemoteConfig{Value: sec.Key("outgoing").String()},
|
||||
}
|
||||
if wizard.smtpMode == SMTP_STARTTLS {
|
||||
account.Params = map[string]string{
|
||||
|
|
Loading…
Reference in New Issue