Subject: lib/28505: getpass does not restore terminal after SIGCONT
To: None <lib-bug-people@netbsd.org, gnats-admin@netbsd.org,>
From: None <Peter.Bex@student.kun.nl>
List: netbsd-bugs
Date: 12/01/2004 23:12:00
>Number:         28505
>Category:       lib
>Synopsis:       Sometimes getpass(3) echoes password chars after SIGSTOP + SIGCONT
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    lib-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Wed Dec 01 23:12:00 +0000 2004
>Originator:     Peter Bex
>Release:        NetBSD 2.0_RC1
>Organization:
	
>Environment:
	
	
System: NetBSD frohike.nvie.com 2.0_RC1 NetBSD 2.0_RC1 (FROHIKE) #3: Wed Sep 29 19:34:49 CEST 2004 root@:/usr/src/sys/arch/i386/compile/FROHIKE i386
Architecture: i386
Machine: i386
>Description:
	When running su, suspending that by sending SIGSTOP and then resuming
	it again, in some shells (those that don't save/restore the child
	process' terminal state correctly) the password which is typed is
	echoed back to the terminal.

	This appears to be a bug in how getpass(3) handles signals.
>How-To-Repeat:
	$ /usr/pkg/bin/zsh
	% su
	Password:

	<switch to other terminal>

	$ pkill -SIGSTOP su

	<switch back to su terminal>

	Password:zsh: suspended (signal)  su
	% fg
	[1]  + continued  su
	<Password is now echoed to the terminal>

	I was only able to reproduce this problem under the zsh.
	(pkgsrc/shells/zsh, pkg version 4.2.1)
	/bin/csh and /bin/ksh don't have this problem (presumably because
	these shells save and restore child process terminal states)

	On a related note, /bin/sh does not do anything about the terminal,
	so when you start typing after sending the STOP signal to su,
	the characters aren't echoed.
>Fix:

Handle SIGSTOP and SIGCONT and set terminal state there.

--- getpass.c.orig	2003-08-07 18:42:50.000000000 +0200
+++ getpass.c	2004-12-01 22:34:21.000000000 +0100
@@ -52,6 +52,28 @@
 __weak_alias(getpass,_getpass)
 #endif
 
+/* Reset terminal on suspend/resume of the job, so pwd will not be echoed */
+static void sighan(int sig)
+{
+	struct termios term;
+	FILE *fp;
+
+	if ((fp = fopen(_PATH_TTY, "w+")) == NULL)
+		fp = stdin;
+
+	(void)tcgetattr(fileno(fp), &term);
+
+	if (sig == SIGCONT)
+		term.c_lflag &= ~ECHO;
+	else
+		term.c_lflag |= ECHO;
+
+	(void)tcsetattr(fileno(fp), TCSAFLUSH|TCSASOFT, &term);
+
+	if (fp != stdin)
+		(void)fclose(fp);
+}
+
 char *
 getpass(prompt)
 	const char *prompt;
@@ -63,6 +85,7 @@
 	int echo;
 	static char buf[_PASSWORD_LEN + 1];
 	sigset_t oset, nset;
+	struct sigaction act, ostop_act, ocont_act;
 
 	_DIAGASSERT(prompt != NULL);
 
@@ -84,6 +107,12 @@
 	sigaddset(&nset, SIGTSTP);
 	(void)sigprocmask(SIG_BLOCK, &nset, &oset);
 
+	sigemptyset(&act.sa_mask);
+	act.sa_handler = sighan;
+	act.sa_flags = 0;
+	sigaction(SIGSTOP, &act, &ostop_act);
+	sigaction(SIGCONT, &act, &ocont_act);
+
 	(void)tcgetattr(fileno(fp), &term);
 	if ((echo = (term.c_lflag & ECHO)) != 0) {
 		term.c_lflag &= ~ECHO;
@@ -101,6 +130,8 @@
 		term.c_lflag |= ECHO;
 		(void)tcsetattr(fileno(fp), TCSAFLUSH|TCSASOFT, &term);
 	}
+	sigaction(SIGSTOP, &ostop_act, NULL);
+	sigaction(SIGCONT, &ocont_act, NULL);
 	(void)sigprocmask(SIG_SETMASK, &oset, NULL);
 	if (fp != stdin)
 		(void)fclose(fp);

>Unformatted: