Я хочу замаскировать свой пароль, записывая его с помощью «*». Для этого кода я использую Linux GCC. Я знаю одно решение – использовать функцию getch() следующим образом:
#include <conio.h>
int main() {
char c,password[10];
int i;
while( (c=getch())!= '\n');{
password[i] = c;
printf("*");
i++;
}
return 1;
}
Но проблема в том, что GCC не включает файл conio.h, поэтому getch() бесполезна для меня. Есть ли у кого-нибудь решение?
Ответ 1
В мире Linux маскировка звездочками обычно не делается, чаще всего эхо просто отключается, и в терминале отображаются пробелы, например, если вы используете «su» или входите в виртуальный терминал и т.д. Существует библиотечная функция для обработки получения паролей, она не маскирует пароль звездочками, но отключает эхо пароля в терминале. Я взял это из книги по linux, которая у меня есть. Я полагаю, что это часть стандарта posix:
#include <unistd.h>
char *getpass(const char *prompt);
/*Возвращает указатель на статически выделенную строку входного пароля
при успехе, или NULL при ошибке*/
Функция getpass() сначала отключает эхо и всю обработку специальных символов терминала (таких, как символ прерывания, обычно Control-C). Затем она печатает строку, на которую указывает prompt, и считывает строку ввода, возвращая в качестве результата работы нуль-терминированную строку ввода с обрезкой новой строки. Поиск в Google по getpass() дает ссылку на реализацию GNU (должна быть в большинстве дистрибутивов linux) и пример кода ее реализации, если это необходимо:
#include <termios.h>
#include <stdio.h>
ssize_t my_getpass (char **lineptr, size_t *n, FILE *stream) {
struct termios old, new;
int nread;
if (tcgetattr (fileno (stream), &old) != 0)
return -1;
new = old;
new.c_lflag &= ~ECHO;
if (tcsetattr (fileno (stream), TCSAFLUSH, &new) != 0)
return -1;
/* чтение пароля. */
nread = getline (lineptr, n, stream);
/* восстановление терминала. */
(void) tcsetattr (fileno (stream), TCSAFLUSH, &old);
return nread;
}
При необходимости вы можете использовать этот код в качестве основы и модифицировать для его для себя.
Ответ 2
Не имея getch и избегая устаревшего getpass, нужно использовать рекомендуемый подход, который заключается в отключении терминального ECHO через использование termios. После нескольких попыток найти готовые гибкие процедуры ввода паролей, я был удивлен, что их очень мало для автономного использования в C. Вместо того, чтобы просто перекодировать getch с опциями termios c_lflag, более обобщенный подход требует всего нескольких дополнений. Помимо замены getch, любая процедура должна обеспечивать заданную максимальную длину для предотвращения переполнения, если пользователь пытается ввести строку больше максимальной длины, и предупреждать, если усечение происходит каким-либо иным образом.
Ниже приведены дополнения, позволяющие читать из любого входного потока FILE*, ограничивать длину до заданной, обеспечивать минимальную возможность редактирования (backspace) при вводе, задавать или полностью отключать маску символов, и, наконец, возвращать длину введенного пароля. Было добавлено предупреждение, если введенный пароль был усечен до максимальной или заданной длины. Надеюсь, это окажется полезным для других пользователей, ищущих подобное решение:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#define MAXPW 32
/* считывание строки из fp в pw, маскируя нажатие клавиши символом маски.
getpasswd считывает до sz-1 символов в pw, завершая нулем
полученную строку. В случае успеха возвращается количество символов в pw, в противном случае -1.
*/
ssize_t getpasswd (char **pw, size_t sz, int mask, FILE *fp) {
if (!pw || !sz || !fp) return -1; /* валидация ввода */
#ifdef MAXPW
if (sz > MAXPW) sz = MAXPW;
#endif
if (*pw == NULL) { /* перераспределение при отсутствии адреса */
void *tmp = realloc (*pw, sz * sizeof **pw);
if (!tmp)
return -1;
memset (tmp, 0, sz); /* инициализация памяти нулями */
*pw = (char*) tmp;
}
size_t idx = 0; /* индекс, количество символов при чтении */
int c = 0;
struct termios old_kbd_mode; /* параметры клавиатуры */
struct termios new_kbd_mode;
if (tcgetattr (0, &old_kbd_mode)) { /* сохранение оригинальных настроек */
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;
}
/* считывание символов из fp - это маска, если указан правильный символ */
while (((c = fgetc (fp)) != '\n' && c != EOF && idx < sz - 1) || (idx == sz - 1 && c == 127)) {
if (c != 127) {
if (31 < mask && mask < 127) /* валидация символа */
fputc (mask, stdout);
(*pw)[idx++] = c;
}
else if (idx > 0) {
if (31 < mask && mask < 127) {
fputc (0x8, stdout);
fputc (' ', stdout);
fputc (0x8, stdout);
}
(*pw)[--idx] = 0;
}
}
(*pw)[idx] = 0; /* завершение строки */
/* сброс клавиатуры */
if (tcsetattr (0, TCSANOW, &old_kbd_mode)) {
fprintf (stderr, "%s() error: tcsetattr failed.\n", __func__);
return -1;
}
if (idx == sz - 1 && c != '\n')
fprintf (stderr, " (%s() warning: truncated at %zu chars.)\n",
__func__, sz - 1);
return idx; /* количество символов в пароле */
}
Простая программа, демонстрирующая использование данного кода, будет выглядеть следующим образом. Если для хранения пароля используется статический массив символов, просто убедитесь, что в функцию передается указатель.
int main (void ) {
char pw[MAXPW] = {0};
char *p = pw;
FILE *fp = stdin;
ssize_t nchr = 0;
printf ( "\n Введите пароль: ");
nchr = getpasswd (&p, MAXPW, '*', fp);
printf ("\n было введено: %s (%zu символов)\n", p, nchr);
printf ( "\n Введите пароль: ");
nchr = getpasswd (&p, MAXPW, 0, fp);
printf ("\n было введено: %s (%zu символов)\n\n", p, nchr);
return 0;
}
Вывод:
$ ./bin/getpasswd2
Введите пароль: ******
было введено: 123456 (6 символов)
Введите пароль:
было введено: abcdef (6 символов)
Ответ 3
Функциональность getch (которая является нестандартной функцией Windows) может быть эмулирована с помощью этого кода:
#include <termios.h>
#include <unistd.h>
int getch() {
struct termios oldt, newt;
int ch;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
return ch;
}
Обратите внимание, что ваш подход не идеален – лучше использовать что-то вроде ncurses или другую терминальную библиотеку для обработки подобных ситуаций.
Ответ 4
Вы можете создать свою собственную функцию getch() в Linux таким образом:
int getch() {
struct termios oldtc, newtc;
int ch;
tcgetattr(STDIN_FILENO, &oldtc);
newtc = oldtc;
newtc.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newtc);
ch=getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &oldtc);
return ch;
}
Тест:
int main(int argc, char **argv) {
int ch;
printf("Press x to exit.\n\n");
for (;;) {
ch = getch();
printf("ch = %c (%d)\n", ch, ch);
if(ch == 'x')
break;
}
return 0;
}
Linux