Sans getch
s'appuyer et éviter l'obsolète getpass
, l'approche recommandée consiste à désactiver le terminal ECHO via termios
utilisation. Après quelques recherches pour trouver une routine de mot de passe flexible prédéfinie, j'ai été surpris qu'il y en ait très peu pour une utilisation autonome avec C. Plutôt que de simplement recoder getch
avec les termes c_lflag
options, une approche légèrement plus généralisée ne nécessite que quelques ajouts. Au-delà du remplacement de getch
toute routine doit appliquer une longueur maximale spécifiée pour éviter le débordement, tronquer si l'utilisateur tente d'entrer au-delà du maximum et avertir si la troncation se produit d'une manière ou d'une autre.
Ci-dessous, les ajouts permettront de lire à partir de n'importe quel FILE *
flux d'entrée, limitant la longueur à une longueur spécifiée, fournir une capacité d'édition minimale (retour arrière) lors de la prise d'entrée, permettre au masque de caractères d'être spécifié ou désactivé complètement, et enfin renvoyer la longueur du mot de passe saisi. Un avertissement a été ajouté lorsque le mot de passe saisi a été tronqué à la longueur maximale ou spécifiée.
J'espère que cela s'avérera utile à d'autres avec cette question à la recherche d'une solution similaire :
#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 programme simple montrant l'utilisation serait le suivant. Si vous utilisez un tableau statique de caractères pour contenir le mot de passe, assurez-vous simplement qu'un pointeur est passé à la fonction.
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;
}
Exemple de sortie
$ ./bin/getpasswd2
Enter password: ******
you entered : 123456 (6 chars)
Enter password:
you entered : abcdef (6 chars)
Dans le monde Linux, le masquage n'est généralement pas fait avec des astérisques, normalement l'écho est simplement désactivé et le terminal affiche des blancs. si vous utilisez su
ou connectez-vous à un terminal virtuel, etc.
Il existe une fonction de bibliothèque pour gérer l'obtention des mots de passe, elle ne masquera pas le mot de passe avec des astérisques mais désactivera l'écho du mot de passe au terminal. J'ai tiré cela d'un livre Linux que j'ai. Je crois que cela fait partie de la norme posix
#include <unistd.h> char *getpass(const char *prompt); /*Returns pointer to statically allocated input password string on success, or NULL on error*/
La fonction getpass() désactive d'abord l'écho et tout traitement des caractères spéciaux du terminal (tels que le caractère d'interruption, normalement Control-C).
Il imprime ensuite la chaîne pointée par l'invite et lit une ligne d'entrée, renvoyant la chaîne d'entrée terminée par un caractère nul avec la nouvelle ligne de fin supprimée, comme résultat de sa fonction.
Une recherche Google pour getpass() contient une référence à l'implémentation GNU (devrait être dans la plupart des distributions Linux) et un exemple de code pour implémenter le vôtre si nécessaire
http://www.gnu.org/s/hello/manual/libc/getpass.html
Leur exemple pour rouler le vôtre :
#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 besoin est, vous pouvez l'utiliser comme base en le modifiant pour afficher des astérisques.