From 163ea3ec7d2af3bac1afe6489071a8a286f282b8 Mon Sep 17 00:00:00 2001 From: Koni Marti Date: Tue, 6 Aug 2024 22:37:28 +0200 Subject: aerc: support terminal-based pinentry programs Support terminal-based pinentry programs. Suspend vaxis before running the command that can trigger a pinentry call. Provide the proper tty in the GPG_TTY environment variable (and set a TERM variable if not provided; this is necessary for pinentry-curses). Finally, resume vaxis. To enable terminal-based pinentry support, you have to set [general] use-terminal-pinentry = true in your aerc.conf. Any GUI-based pinentry programs will work the same as before if this option is not set to true. To test pinentry-tty, add the following to your ~/.gnupg/gpg-agent.conf: pinentry-program /usr/bin/pinentry-tty and kill all running gpg-agents: $ killall gpg-agent Fixes: https://todo.sr.ht/~rjarry/aerc/202 Changelog-fixed: Terminal-based pinentry programs (e.g. `pinentry-curses`) now work properly. Signed-off-by: Koni Marti Acked-by: Robin Jarry --- config/general.go | 1 + doc/aerc-config.5.scd | 17 ++++++++++++ lib/pinentry/pinentry.go | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/pinentry/ttyname.go | 45 ++++++++++++++++++++++++++++++ lib/ui/ui.go | 13 +++++++++ 5 files changed, 147 insertions(+) create mode 100644 lib/pinentry/pinentry.go create mode 100644 lib/pinentry/ttyname.go diff --git a/config/general.go b/config/general.go index edb1b3ad..7d86b0fb 100644 --- a/config/general.go +++ b/config/general.go @@ -24,6 +24,7 @@ type GeneralConfig struct { Term string `ini:"term" default:"xterm-256color"` DefaultMenuCmd string `ini:"default-menu-cmd"` QuakeMode bool `ini:"enable-quake-mode" default:"false"` + UsePinentry bool `ini:"use-terminal-pinentry" default:"false"` } var General = new(GeneralConfig) diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd index d4c1ad19..80922fa1 100644 --- a/doc/aerc-config.5.scd +++ b/doc/aerc-config.5.scd @@ -41,6 +41,23 @@ These options are configured in the *[general]* section of _aerc.conf_. Default: _auto_ +*use-terminal-pinentry* = _true_|_false_ + For terminal-based pinentry programs (such as _pinentry-tty_, + _pinentry-curses_ or _pinentry-vaxis_) to work properly with *aerc*(1), + set this to _true_. + + In some setups *aerc*(1) will not be able to determine the correct tty. + In those cases, you have to manually set _GPG_TTY_ to the output of *tty*(1) + before running *aerc*(1) as recommended by GnuPG for invoking a GPG-agent. Add + the following to your shell initialization scripts: + + ``` + GPG_TTY=$(tty) + export GPG_TTY + ``` + + Default: _false_ + *unsafe-accounts-conf* = _true_|_false_ By default, the file permissions of _accounts.conf_ must be restrictive and only allow reading by the file owner (_0600_). Set this option to diff --git a/lib/pinentry/pinentry.go b/lib/pinentry/pinentry.go new file mode 100644 index 00000000..51b54920 --- /dev/null +++ b/lib/pinentry/pinentry.go @@ -0,0 +1,71 @@ +package pinentry + +import ( + "fmt" + "os" + "os/exec" + "strings" + "sync/atomic" + + "git.sr.ht/~rjarry/aerc/config" + "git.sr.ht/~rjarry/aerc/lib/log" + "git.sr.ht/~rjarry/aerc/lib/ui" +) + +var pinentryMode int32 = 0 + +func Enable() { + if !config.General.UsePinentry { + return + } + if atomic.SwapInt32(&pinentryMode, 1) == 1 { + // cannot enter pinentry mode twice + return + } + ui.SuspendScreen() +} + +func Disable() { + if atomic.SwapInt32(&pinentryMode, 0) == 0 { + // not in pinentry mode + return + } + ui.ResumeScreen() +} + +func SetCmdEnv(cmd *exec.Cmd) { + if cmd == nil || atomic.LoadInt32(&pinentryMode) == 0 { + return + } + + env := cmd.Env + if env == nil { + env = os.Environ() + } + + hasTerm := false + hasGPGTTY := false + for _, e := range env { + switch { + case strings.HasPrefix(strings.ToUpper(e), "TERM="): + log.Debugf("pinentry: use %v", e) + hasTerm = true + case strings.HasPrefix(strings.ToUpper(e), "GPG_TTY="): + log.Debugf("pinentry: use %v", e) + hasGPGTTY = true + } + } + + if !hasTerm { + env = append(env, "TERM=xterm-256color") + log.Debugf("pinentry: set TERM=xterm-256color") + } + + if !hasGPGTTY { + tty := ttyname() + env = append(env, fmt.Sprintf("GPG_TTY=%s", tty)) + log.Debugf("pinentry: set GPG_TTY=%s", tty) + } + + cmd.Env = env +} diff --git a/lib/pinentry/ttyname.go b/lib/pinentry/ttyname.go new file mode 100644 index 00000000..053cff74 --- /dev/null +++ b/lib/pinentry/ttyname.go @@ -0,0 +1,45 @@ +package pinentry + +import ( + "fmt" + "os" + "strings" + + "git.sr.ht/~rjarry/aerc/lib/log" +) + +var missingGPGTTYmsg = ` +You need to set GPG_TTY manually before starting aerc. Add the following to your +.bashrc or whatever initialization file is used for shell invocations: + + GPG_TTY=$(tty) + export GPG_TTY + +Further information can be found here: +https://www.gnupg.org/documentation/manuals/gnupg/Invoking-GPG_002dAGENT.html +` + +// ttyname returns current name of the pty. This is necessary in order to tell +// pinentry where to ask for the passphrase. +// +// If there is a GPG_TTY environment variable set, use this one. Otherwise, try +// readline() on /proc//fd/0. +// +// If both approaches fail, the user's only option is to set GPG_TTY manually. +// +// If tty name could not be determined, an empty string is returned. +func ttyname() string { + if s := os.Getenv("GPG_TTY"); s != "" { + return s + } + + // try readlink or else show missing GPG_TTY warning msg + tty, err := os.Readlink(fmt.Sprintf("/proc/%d/fd/0", os.Getpid())) + if err != nil { + log.Debugf("readlink: '%s' with err: %v", tty, err) + log.Warnf(missingGPGTTYmsg) + return "" + } + + return strings.TrimSpace(tty) +} diff --git a/lib/ui/ui.go b/lib/ui/ui.go index c20eac37..3680fb1a 100644 --- a/lib/ui/ui.go +++ b/lib/ui/ui.go @@ -99,6 +99,19 @@ func QueueSuspend() { } } +// SuspendScreen should be called from the main thread. +func SuspendScreen() { + _ = state.vx.Suspend() +} + +func ResumeScreen() { + err := state.vx.Resume() + if err != nil { + log.Errorf("ui: cannot resume after suspend: %v", err) + } + Invalidate() +} + func Suspend() error { var err error if atomic.SwapUint32(&state.suspending, 0) != 0 { -- cgit