[comp.unix.questions] How to turn off echo, and get a character at a time

ron@eatdust.uswest.com (Ron Schweikert) (06/07/91)

Sorry to post this here, but it is in response to multiple questions recently
in this group (and I believe in comp.lang.c, although I didn't post there).

I tried mailing to the people who requested it, but the mailer bounced it
back, so here goes...

This program kills two birds with one stone.  For the people who asked for, but
apparently didn't have getpass(), this duplicates it.  For those who wanted
to get a keystroke immediately without putting in a CR, look inside 
my_getpass().  On some systems, I think there's a simple CBREAK entry you can
use.  I also include my getpass() version that I use when running under
curses, because the prompt is left after calling erase().  Again, sorry to
take up bandwidth, but perhaps others will find some useful snippets as well.

Ron


This is my curses version of getpass().  I created my own because erase()
didn't clear the prompt.  It's used the same as the unix version.

Following this is the unix version.

-------- cut here -----

char *
my_getpass(prompt)
char *prompt;
{
	/* I'm using my own version of getpass() because the original must
	   write to stderr, erase() doesn't clear it!
	*/

	int i, x;
	static char tmp[9];
	
	printw ("%s", prompt);
	refresh();

	for (i = 0; i < 8; i++)
	{
		x = getch ();
		if (x == RETURN)
			break;
		tmp[i] = (char)x;
	}
	tmp[i] = '\0';
	return (tmp);
}

-------- cut here -----

This one below is a standalone version of my_getpass().  It doesn't need curses.

-------- cut here -----

/*
	Sample program for duplicating getpass() by using ioctl to turn off echo.

	Ron Schweikert
*/

#include <stdio.h>
#include <fcntl.h>
#include <sys/termios.h>

#define RETURN '\n'

extern int errno;

main()
{
	char input[10], *my_getpass();

	printf ("Here's how we implement getpass() if you don't have it:\n\n");

	(void)strcpy (input, my_getpass ("Password: "));

	printf ("\nThe password entered was [%s]\n", input);

	/* Of course you may want to call crypt() to encrypt the password! to
	   be more fancy!
	*/
}

char *
my_getpass (prompt)
char *prompt;
{
	int fd, i;
	char input[9];
	struct termios old, new;

	if ((fd = open ("/dev/tty", O_RDWR)) == -1)
	{
		printf ("Could not get port id, errno = %d\n", errno);
		exit (-1);
	}

	save_settings (fd, &old);		/* So we can reset the terminal */
	save_settings (fd, &new);

	new.c_cc[VMIN]  = 1;			/* Read will be satisfied by 1 char., */
	new.c_cc[VTIME] = 0;			/* timing is not an issue			  */

	new.c_lflag &= ~(ICANON | ECHO);

	/* Get into non-canonical mode so that the read() below will be satisfied
	   after each character.  Also turn off echo.
	*/

	modify_settings (fd, &new);

	fprintf (stdout, "%s", prompt); fflush(stdout);

	for (i = 0; i < sizeof(input); i++) 
	{
		read (fd, &input[i], 1);
	
		if (input[i] == RETURN)
			break;
	}

	input[i] = '\0';

	restore_settings (fd, &old);

	return (input);
}

save_settings (fd, term)
int fd;
struct termios *term;
{
	if (ioctl (fd, TCGETS, term) == -1)
	{
		printf ("save_settings ioctl failure, errno = %d\n", errno);
		exit (-1);
	}
}

restore_settings (fd, old)
int fd;
struct termios *old;
{
	if (ioctl (fd, TCSETS, old) == -1)
	{
		printf ("restore_settings ioctl failure, errno = %d\n", errno);
		exit (-1);
	}
}

modify_settings (fd, new)
int fd;
struct termios *new;
{
	if (ioctl (fd, TCSETS, new) == -1)
	{
		printf ("Ioctl failure, errno = %d\n", errno);
		exit (-1);
	}
}