Subject: Re: bug#30190: 27.0.50; term run in line mode shows user passwords

From: Tino Calancha <[email-masked]>
Date: Thu, 15 Feb 2018 09:09:50 +0900

Tino Calancha <[email-masked]> writes:

> On Sat, 3 Feb 2018, Eli Zaretskii wrote:
>
>> My feedback is that such a radical solution with so many lines of code
>> is a no-no for the release branch.  Please look for a simpler
>> solution, perhaps don't create a new file?
> A suitable patch for the next release for discussion below:

My patch is not satisfactory.  It uses functions that haven't been
tested enough: `term-send-invisible' and `term-read-noecho' are not
used in the Emacs source tree:  after playing a bit with the patch
I found some problems (I even crashed Emacs several times, not so fun).

I) `term-read-noecho' doesn't expect the user might hit <up>, <down>,
`C-p' or `C-n'; it will try to convert these things to strings and return
them.

II) `term-send-invisible' doesn't cancel if the user hit `C-g'; instead
it pass nil as the string for the process.  The reason is how
`term-read-noecho'  handles `C-g'.

My Emacs session crashes if I do (using the patch in this thread):
M-x term RET
C-c C-j
sudo ls RET
C-g C-g ;; Emacs crash!

Note that I) and II) don't suppose any problem in a *shell* buffer if
calling `send-invisible', which uses the more robust `read-passwd'.

So I want to avoid using the not well tested `term-read-noecho'.  Instead,
I propose to use `read-passwd' as elsewhere within Emacs.

This is the updated patch:
--8<-----------------------------cut here---------------start------------->8---
commit 6254b0aca3c91ebd6d41f865c1cdcc13166c066d
Author: tino calancha <[email-masked]>
Date:   Thu Feb 15 08:58:45 2018 +0900

    Prevent term run in line mode from showing user passwords
    
    For buffers whose mode derive from comint-mode, the user
    password is read from the minibuffer and it's hidden.
    A buffer in term-mode and line submode, instead shows
    the passwords.
    This commit forces buffers in line term-mode to hide
    passwords (Bug#30190).
    
    * lisp/term.el (term-send-invisible):
    Prefer the more robust `read-passwd' instead of `term-read-noecho'.
    
    (term-password-prompt-regexp): New user option.
    (term-watch-for-password-prompt): New function.
    
    (term-send-input, term-emulate-terminal): Call it.
    (term-output-filter-hook): New hook.  Add term-watch-for-password-prompt
    to it.
    
    (term-send-input, term-emulate-terminal): Call the new hook each time
    we receive output.

diff --git a/lisp/term.el b/lisp/term.el
index 3970e93cf1..484a26cd7a 100644
--- a/lisp/term.el
+++ b/lisp/term.el
@@ -558,6 +558,27 @@ term-suppress-hard-newline
 ;; indications of the current pc.
 (defvar term-pending-frame nil)
 
+;; Stolen from comint.el
+(defcustom term-password-prompt-regexp
+  (concat
+   "\\(^ *\\|"
+   (regexp-opt
+    '("Enter" "enter" "Enter same" "enter same" "Enter the" "enter the"
+      "Old" "old" "New" "new" "'s" "login"
+      "Kerberos" "CVS" "UNIX" " SMB" "LDAP" "PEM" "SUDO"
+      "[sudo]" "Repeat" "Bad" "Retype")
+    t)
+   " +\\)"
+   "\\(?:" (regexp-opt password-word-equivalents) "\\|Response\\)"
+   "\\(?:\\(?:, try\\)? *again\\| (empty for no passphrase)\\| (again)\\)?"
+   ;; "[[:alpha:]]" used to be "for", which fails to match non-English.
+   "\\(?: [[:alpha:]]+ .+\\)?[\\s  ]*[:：៖][\\s  ]*\\'")
+  "Regexp matching prompts for passwords in the inferior process.
+This is used by `term-watch-for-password-prompt'."
+  :version "26.1"
+  :type 'regexp
+  :group 'comint)
+
 ;;; Here are the per-interpreter hooks.
 (defvar term-get-old-input (function term-get-old-input-default)
   "Function that submits old text in term mode.
@@ -586,6 +607,17 @@ term-input-filter-functions
 
 This variable is buffer-local.")
 
+;;; Stolen from comint.el
+;;;###autoload
+(defvar term-output-filter-hook '(term-watch-for-password-prompt)
+  "Functions to call after output is inserted into the buffer.
+One possible function is `term-watch-for-password-prompt'.
+These functions get one argument, a string containing the text as originally
+inserted.
+
+You can use `add-hook' to add functions to this list
+either globally or locally.")
+
 (defvar term-input-sender (function term-simple-send)
   "Function to actually send to PROCESS the STRING submitted by user.
 Usually this is just `term-simple-send', but if your mode needs to
@@ -2134,7 +2166,8 @@ term-send-input
 	  (set-marker term-pending-delete-marker pmark-val)
 	  (set-marker (process-mark proc) (point)))
 	(goto-char pmark)
-	(funcall term-input-sender proc input)))))
+	(funcall term-input-sender proc input)
+        (run-hook-with-args 'term-output-filter-hook "")))))
 
 (defun term-get-old-input-default ()
   "Default for `term-get-old-input'.
@@ -2255,7 +2288,8 @@ term-send-invisible
 \\[view-lossage]."
   (interactive "P") ; Defeat snooping via C-x esc
   (when (not (stringp str))
-    (setq str (term-read-noecho "Non-echoed text: " t)))
+    (let ((read-hide-char ?*))
+      (setq str (read-passwd "Non-echoed text: "))))
   (when (not proc)
     (setq proc (get-buffer-process (current-buffer))))
   (if (not proc) (error "Current buffer has no process")
@@ -2264,6 +2298,21 @@ term-send-invisible
     (term-send-string proc str)
     (term-send-string proc "\n")))
 
+;;; Stolen from comint.el
+;; TODO: This file share plenty of code with comint.el; it might be worth
+;; to extract the common functionality into a new file.
+(defun term-watch-for-password-prompt (string)
+  "Prompt in the minibuffer for password and send without echoing.
+This function uses `term-send-invisible' to read and send a password to the buffer's
+process if STRING contains a password prompt defined by
+`term-password-prompt-regexp'.
+
+This function could be in the list `term-emulate-terminal'."
+  (when (term-in-line-mode)
+    (when (let ((case-fold-search t))
+	    (string-match term-password-prompt-regexp string))
+      (term-send-invisible nil))))
+
 
 ;;; Low-level process communication
 
@@ -3121,6 +3170,8 @@ term-emulate-terminal
 	  (term-handle-deferred-scroll))
 
 	(set-marker (process-mark proc) (point))
+        ;; Run these hooks with point where the user had it.
+        (run-hook-with-args 'term-output-filter-hook str)
 	(when save-point
 	  (goto-char save-point)
 	  (set-marker save-point nil))
--8<-----------------------------cut here---------------end--------------->8---
In GNU Emacs 26.0.91 (build 15, x86_64-pc-linux-gnu, GTK+ Version 3.22.11)
 of 2018-02-15 built on calancha-pc
Repository revision: 874c0edf30308392bdba870e92247d7e4b0e66f4
Windowing system distributor 'The X.Org Foundation', version 11.0.11902000
System Description:	Debian GNU/Linux 9.3 (stretch)