Bez getch
spoléhat na zastaralý getpass
a vyhýbat se mu , doporučený přístup je deaktivovat terminál ECHO přes termios
použití. Po několika hledáních, abych našel předpřipravenou flexibilní rutinu hesel, mě překvapilo, že jen velmi málo pro samostatné použití s C. Spíše než pouhé překódování getch
s termios c_lflag
trochu zobecněný přístup vyžaduje jen několik dodatků. Kromě nahrazení getch
každá rutina by měla vynutit zadanou maximální délku, aby se zabránilo přetečení, zkrátit, pokud se uživatel pokusí zadat více než maximum, a varovat, pokud ke zkrácení nějakým způsobem dojde.
Níže uvedené doplňky umožní čtení z libovolného FILE *
vstupní proud, omezující délku na zadanou délku, poskytují minimální možnosti úprav (backspace) při zadávání, umožňují zcela specifikovat nebo deaktivovat masku znaků a nakonec vrátit délku zadaného hesla. Bylo přidáno varování, když bylo zadané heslo zkráceno na maximální nebo zadanou délku.
Doufejme, že to bude užitečné pro ostatní s touto otázkou, kteří hledají podobné řešení:
#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 */
}
Jednoduchý program ukazující použití by byl následující. Pokud k uchování hesla používáte statické pole znaků, zajistěte, aby byl funkci předán ukazatel.
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;
}
Ukázkový výstup
$ ./bin/getpasswd2
Enter password: ******
you entered : 123456 (6 chars)
Enter password:
you entered : abcdef (6 chars)
V linuxovém světě se maskování obvykle neprovádí hvězdičkami, normálně se prostě vypne echo a na terminálu se zobrazí mezery, např. pokud používáte su
nebo se přihlaste do virtuálního terminálu atd.
K dispozici je funkce knihovny pro zpracování hesel, která nebude maskovat heslo hvězdičkami, ale zakáže opakování hesla do terminálu. Vytáhl jsem to z linuxové knihy, kterou mám. Věřím, že je to součást standardu posix
#include <unistd.h> char *getpass(const char *prompt); /*Returns pointer to statically allocated input password string on success, or NULL on error*/
Funkce getpass() nejprve zakáže echo a veškeré zpracování speciálních znaků terminálu (jako je znak přerušení, normálně Control-C).
Poté vytiskne řetězec, na který ukazuje výzva, a přečte řádek vstupu, přičemž jako výsledek funkce vrátí vstupní řetězec ukončený nulou s odstraněným koncovým novým řádkem.
Vyhledávání Google pro getpass() má odkaz na implementaci GNU (měla by být ve většině linuxových distribucí) a nějaký ukázkový kód pro implementaci vašeho vlastního, pokud je to nutné
http://www.gnu.org/s/hello/manual/libc/getpass.html
Jejich příklad pro válcování vlastního:
#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;
}
V případě potřeby jej můžete použít jako základ a upravit tak, aby zobrazoval hvězdičky.