Sin getch
confiar y evitar el obsoleto getpass
, el enfoque recomendado es deshabilitar el terminal ECHO a través de termios
usar. Después de algunas búsquedas para encontrar una rutina de contraseña flexible enlatada, me sorprendió que muy pocas fueran para uso independiente con C. En lugar de simplemente recodificar getch
con termios c_lflag
opciones, un enfoque un poco más generalizado requiere solo algunas adiciones. Más allá de reemplazar getch
cualquier rutina debe imponer una longitud máxima específica para evitar el desbordamiento, truncar si el usuario intenta ingresar más allá del máximo y advertir si se produce el truncamiento de alguna manera.
A continuación, las adiciones permitirán leer desde cualquier FILE *
el flujo de entrada, que limita la longitud a una longitud específica, proporciona una capacidad de edición mínima (retroceso) al ingresar, permite que la máscara de caracteres se especifique o deshabilite por completo y, finalmente, devuelve la longitud de la contraseña ingresada. Se agregó una advertencia cuando la contraseña ingresada se truncó a la longitud máxima o especificada.
Con suerte, será útil para otros con esta pregunta que buscan una solución similar:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#define MAXPW 32
/* read a string from fp into pw masking keypress with mask char.
getpasswd will read upto sz - 1 chars into pw, null-terminating
the resulting string. On success, the number of characters in
pw are returned, -1 otherwise.
*/
ssize_t getpasswd (char **pw, size_t sz, int mask, FILE *fp)
{
if (!pw || !sz || !fp) return -1; /* validate input */
#ifdef MAXPW
if (sz > MAXPW) sz = MAXPW;
#endif
if (*pw == NULL) { /* reallocate if no address */
void *tmp = realloc (*pw, sz * sizeof **pw);
if (!tmp)
return -1;
memset (tmp, 0, sz); /* initialize memory to 0 */
*pw = (char*) tmp;
}
size_t idx = 0; /* index, number of chars in read */
int c = 0;
struct termios old_kbd_mode; /* orig keyboard settings */
struct termios new_kbd_mode;
if (tcgetattr (0, &old_kbd_mode)) { /* save orig settings */
fprintf (stderr, "%s() error: tcgetattr failed.\n", __func__);
return -1;
} /* copy old to new */
memcpy (&new_kbd_mode, &old_kbd_mode, sizeof(struct termios));
new_kbd_mode.c_lflag &= ~(ICANON | ECHO); /* new kbd flags */
new_kbd_mode.c_cc[VTIME] = 0;
new_kbd_mode.c_cc[VMIN] = 1;
if (tcsetattr (0, TCSANOW, &new_kbd_mode)) {
fprintf (stderr, "%s() error: tcsetattr failed.\n", __func__);
return -1;
}
/* read chars from fp, mask if valid char specified */
while (((c = fgetc (fp)) != '\n' && c != EOF && idx < sz - 1) ||
(idx == sz - 1 && c == 127))
{
if (c != 127) {
if (31 < mask && mask < 127) /* valid ascii char */
fputc (mask, stdout);
(*pw)[idx++] = c;
}
else if (idx > 0) { /* handle backspace (del) */
if (31 < mask && mask < 127) {
fputc (0x8, stdout);
fputc (' ', stdout);
fputc (0x8, stdout);
}
(*pw)[--idx] = 0;
}
}
(*pw)[idx] = 0; /* null-terminate */
/* reset original keyboard */
if (tcsetattr (0, TCSANOW, &old_kbd_mode)) {
fprintf (stderr, "%s() error: tcsetattr failed.\n", __func__);
return -1;
}
if (idx == sz - 1 && c != '\n') /* warn if pw truncated */
fprintf (stderr, " (%s() warning: truncated at %zu chars.)\n",
__func__, sz - 1);
return idx; /* number of chars in passwd */
}
Un programa simple que muestre el uso sería el siguiente. Si usa una matriz estática de caracteres para guardar la contraseña, solo asegúrese de pasar un puntero a la función.
int main (void ) {
char pw[MAXPW] = {0};
char *p = pw;
FILE *fp = stdin;
ssize_t nchr = 0;
printf ( "\n Enter password: ");
nchr = getpasswd (&p, MAXPW, '*', fp);
printf ("\n you entered : %s (%zu chars)\n", p, nchr);
printf ( "\n Enter password: ");
nchr = getpasswd (&p, MAXPW, 0, fp);
printf ("\n you entered : %s (%zu chars)\n\n", p, nchr);
return 0;
}
Ejemplo de salida
$ ./bin/getpasswd2
Enter password: ******
you entered : 123456 (6 chars)
Enter password:
you entered : abcdef (6 chars)
En el mundo de Linux, el enmascaramiento generalmente no se realiza con asteriscos, normalmente el eco simplemente se desactiva y la terminal muestra espacios en blanco, p. si usas su
o inicie sesión en una terminal virtual, etc.
Hay una función de biblioteca para manejar la obtención de contraseñas, no enmascarará la contraseña con asteriscos pero deshabilitará el eco de la contraseña en la terminal. Saqué esto de un libro de Linux que tengo. Creo que es parte del estándar posix
#include <unistd.h> char *getpass(const char *prompt); /*Returns pointer to statically allocated input password string on success, or NULL on error*/
La función getpass() primero deshabilita el eco y todo el procesamiento de caracteres especiales terminales (como el carácter de interrupción, normalmente Control-C).
Luego imprime la cadena a la que apunta el indicador y lee una línea de entrada, devolviendo la cadena de entrada terminada en nulo con la nueva línea final eliminada, como resultado de su función.
Una búsqueda en Google de getpass() tiene una referencia a la implementación de GNU (debería estar en la mayoría de las distribuciones de Linux) y un código de muestra para implementar el suyo propio si es necesario
http://www.gnu.org/s/hello/manual/libc/getpass.html
Su ejemplo para rodar el tuyo:
#include <termios.h>
#include <stdio.h>
ssize_t
my_getpass (char **lineptr, size_t *n, FILE *stream)
{
struct termios old, new;
int nread;
/* Turn echoing off and fail if we can't. */
if (tcgetattr (fileno (stream), &old) != 0)
return -1;
new = old;
new.c_lflag &= ~ECHO;
if (tcsetattr (fileno (stream), TCSAFLUSH, &new) != 0)
return -1;
/* Read the password. */
nread = getline (lineptr, n, stream);
/* Restore terminal. */
(void) tcsetattr (fileno (stream), TCSAFLUSH, &old);
return nread;
}
Si es necesario, puede usar esto como base y modificarlo para mostrar asteriscos.