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
3 changed files with 60 additions and 45 deletions
|
@ -51,7 +51,11 @@ func (Send) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
tabName := tab.Name
|
tabName := tab.Name
|
||||||
config := composer.Config()
|
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(
|
return errors.New(
|
||||||
"No outgoing mail transport configured for this account")
|
"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)")
|
return errors.Wrap(err, "ParseAddress(config.From)")
|
||||||
}
|
}
|
||||||
|
|
||||||
uri, err := url.Parse(config.Outgoing)
|
uri, err := url.Parse(outgoing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "url.Parse(outgoing)")
|
return errors.Wrap(err, "url.Parse(outgoing)")
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,50 @@ const (
|
||||||
FILTER_HEADER
|
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 {
|
type AccountConfig struct {
|
||||||
Archive string
|
Archive string
|
||||||
CopyTo string
|
CopyTo string
|
||||||
|
@ -104,12 +148,10 @@ type AccountConfig struct {
|
||||||
Aliases string
|
Aliases string
|
||||||
Name string
|
Name string
|
||||||
Source string
|
Source string
|
||||||
SourceCredCmd string
|
|
||||||
Folders []string
|
Folders []string
|
||||||
FoldersExclude []string
|
FoldersExclude []string
|
||||||
Params map[string]string
|
Params map[string]string
|
||||||
Outgoing string
|
Outgoing RemoteConfig
|
||||||
OutgoingCredCmd string
|
|
||||||
SignatureFile string
|
SignatureFile string
|
||||||
SignatureCmd string
|
SignatureCmd string
|
||||||
EnableFoldersSort bool `ini:"enable-folders-sort"`
|
EnableFoldersSort bool `ini:"enable-folders-sort"`
|
||||||
|
@ -239,6 +281,7 @@ func loadAccountConfig(path string) ([]AccountConfig, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sec := file.Section(_sec)
|
sec := file.Section(_sec)
|
||||||
|
sourceRemoteConfig := RemoteConfig{}
|
||||||
account := AccountConfig{
|
account := AccountConfig{
|
||||||
Archive: "Archive",
|
Archive: "Archive",
|
||||||
Default: "INBOX",
|
Default: "INBOX",
|
||||||
|
@ -260,12 +303,14 @@ func loadAccountConfig(path string) ([]AccountConfig, error) {
|
||||||
folders := strings.Split(val, ",")
|
folders := strings.Split(val, ",")
|
||||||
sort.Strings(folders)
|
sort.Strings(folders)
|
||||||
account.FoldersExclude = folders
|
account.FoldersExclude = folders
|
||||||
|
} else if key == "source" {
|
||||||
|
sourceRemoteConfig.Value = val
|
||||||
} else if key == "source-cred-cmd" {
|
} else if key == "source-cred-cmd" {
|
||||||
account.SourceCredCmd = val
|
sourceRemoteConfig.PasswordCmd = val
|
||||||
} else if key == "outgoing" {
|
} else if key == "outgoing" {
|
||||||
account.Outgoing = val
|
account.Outgoing.Value = val
|
||||||
} else if key == "outgoing-cred-cmd" {
|
} else if key == "outgoing-cred-cmd" {
|
||||||
account.OutgoingCredCmd = val
|
account.Outgoing.PasswordCmd = val
|
||||||
} else if key == "from" {
|
} else if key == "from" {
|
||||||
account.From = val
|
account.From = val
|
||||||
} else if key == "aliases" {
|
} else if key == "aliases" {
|
||||||
|
@ -295,56 +340,22 @@ func loadAccountConfig(path string) ([]AccountConfig, error) {
|
||||||
return nil, fmt.Errorf("Expected from for account %s", _sec)
|
return nil, fmt.Errorf("Expected from for account %s", _sec)
|
||||||
}
|
}
|
||||||
|
|
||||||
source, err := parseCredential(account.Source, account.SourceCredCmd)
|
source, err := sourceRemoteConfig.ConnectionString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Invalid source credentials for %s: %s", _sec, err)
|
return nil, fmt.Errorf("Invalid source credentials for %s: %s", _sec, err)
|
||||||
}
|
}
|
||||||
account.Source = source
|
account.Source = source
|
||||||
|
|
||||||
outgoing, err := parseCredential(account.Outgoing, account.OutgoingCredCmd)
|
_, err = account.Outgoing.parseValue()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Invalid outgoing credentials for %s: %s", _sec, err)
|
return nil, fmt.Errorf("Invalid outgoing credentials for %s: %s", _sec, err)
|
||||||
}
|
}
|
||||||
account.Outgoing = outgoing
|
|
||||||
|
|
||||||
accounts = append(accounts, account)
|
accounts = append(accounts, account)
|
||||||
}
|
}
|
||||||
return accounts, nil
|
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
|
// Set at build time
|
||||||
var shareDir string
|
var shareDir string
|
||||||
|
|
||||||
|
|
|
@ -522,7 +522,7 @@ func (wizard *AccountWizard) finish(tutorial bool) {
|
||||||
Default: "INBOX",
|
Default: "INBOX",
|
||||||
From: sec.Key("from").String(),
|
From: sec.Key("from").String(),
|
||||||
Source: sec.Key("source").String(),
|
Source: sec.Key("source").String(),
|
||||||
Outgoing: sec.Key("outgoing").String(),
|
Outgoing: config.RemoteConfig{Value: sec.Key("outgoing").String()},
|
||||||
}
|
}
|
||||||
if wizard.smtpMode == SMTP_STARTTLS {
|
if wizard.smtpMode == SMTP_STARTTLS {
|
||||||
account.Params = map[string]string{
|
account.Params = map[string]string{
|
||||||
|
|
Loading…
Reference in a new issue