182 lines
4.9 KiB
C
182 lines
4.9 KiB
C
#include "pwdin.h"
|
|
|
|
#include <stdio.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
|
|
// Input modes
|
|
// Hidden: The char set as mask will be printed on input.
|
|
// Revealed: The char itself will be printed on input.
|
|
// None: Set implicitly when mask = 0. Nothing will be printed.
|
|
#define PWDIN_INM_HIDDEN 0
|
|
#define PWDIN_INM_REVEALED 1
|
|
#define PWDIN_INM_NONE 3
|
|
|
|
// ASCII codes of interesting keys.
|
|
#define PWDIN_KEY_BACK 10
|
|
#define PWDIN_KEY_TAB 9
|
|
#define PWDIN_KEY_DEL 127
|
|
#define PWDIN_KEY_ESC 27
|
|
|
|
void pwdin_setup_term(struct termios* _newt, struct termios* _oldt)
|
|
{
|
|
tcgetattr(STDIN_FILENO, _oldt);
|
|
*_newt = *_oldt;
|
|
_newt->c_lflag &= ~(ECHO); // Disable printing of characters
|
|
_newt->c_lflag &= ~(ICANON); // Make typed chars available imidiatly and don't wait for <ENTER>
|
|
|
|
tcsetattr(STDIN_FILENO, TCSANOW, _newt);
|
|
}
|
|
|
|
void pwdin_teardown_term(struct termios* _oldt) { tcsetattr(STDIN_FILENO, TCSANOW, _oldt); }
|
|
|
|
void pwdin_print_prompt(const char* prompt) { printf("%s", prompt); }
|
|
|
|
// Prints a notice to the user.
|
|
// A notice is a feedback to the terminal once the user input a character.
|
|
// The notice can be in form of a hidden character (mask), the character itself (typed)
|
|
// or nothing (mode == 3).
|
|
//
|
|
// @param in Character input by user.
|
|
// @param mask Mask printed instead of actual character (e.g. "*")
|
|
// @param mode Current input mode.
|
|
void pwdin_print_notice(char in, char mask, int mode)
|
|
{
|
|
switch (mode) {
|
|
case PWDIN_INM_HIDDEN:
|
|
putc(mask, stdout);
|
|
break;
|
|
case PWDIN_INM_REVEALED:
|
|
putc(in, stdout);
|
|
}
|
|
}
|
|
|
|
void pwdin_reveal_input(int default_mode, char* _buffer, size_t len)
|
|
{
|
|
if (default_mode == PWDIN_INM_HIDDEN && len > 0) {
|
|
printf("\e[%luD", len);
|
|
fflush(stdout);
|
|
}
|
|
write(STDOUT_FILENO, _buffer, len);
|
|
}
|
|
|
|
void pwdin_hide_input(int default_mode, char mask, size_t len)
|
|
{
|
|
if (len > 0) {
|
|
printf("\e[%luD", len); // Move cursor to the beginning of the input.
|
|
fflush(stdout);
|
|
}
|
|
if (default_mode == PWDIN_INM_NONE) {
|
|
printf("\e[0K"); // Clear line to end.
|
|
} else {
|
|
for (; len > 0; len--)
|
|
putc(mask, stdout);
|
|
}
|
|
}
|
|
|
|
// Looks out for the mode changing character (reveal_key). If found it will toggle the current input
|
|
// mode between reveal and defalt (hidden/none). It will also cause the already buffered input to be
|
|
// printed / hidden.
|
|
//
|
|
// @param current_input_mode Id of the current mode (hidden/revealed/none).
|
|
// @param default_mode Default hidden mode (hidden/none).
|
|
// @param in Last typed character. Used to determine wether mode change was triggered.
|
|
// @param reveal_key ASCII of key which triggers reveal toggle
|
|
// @param mask Character printed in hidden mode.
|
|
// @param _buffer Required of rendering new output upon toggle.
|
|
// @param input_length Size of the written part of the buffer.
|
|
//
|
|
// @returns int New mode.
|
|
int pwdin_check_special_keys(int* current_input_mode, int default_mode, char in, char reveal_key,
|
|
char mask, char* _buffer, size_t* input_length)
|
|
{
|
|
#ifdef DEBUG
|
|
printf("(%lu)", *input_length);
|
|
#endif /* ifdef DEBUG */
|
|
switch (in) {
|
|
case PWDIN_KEY_DEL:
|
|
if (*input_length == 0) {
|
|
return 1;
|
|
}
|
|
*input_length = (*input_length) - 1;
|
|
write(STDOUT_FILENO, "\b \b", 3);
|
|
return 1;
|
|
break;
|
|
|
|
case PWDIN_KEY_ESC:
|
|
// Ignore the following special character sequence.
|
|
(void)getc(stdin);
|
|
(void)getc(stdin);
|
|
return 1;
|
|
}
|
|
|
|
if (in != reveal_key) {
|
|
// Return 1 when encountering a control symbol.
|
|
return (in < 32 || in > 126);
|
|
}
|
|
|
|
else if (*current_input_mode == PWDIN_INM_REVEALED) {
|
|
pwdin_hide_input(default_mode, mask, *input_length);
|
|
*current_input_mode = default_mode;
|
|
return 1;
|
|
} else {
|
|
pwdin_reveal_input(default_mode, _buffer, *input_length);
|
|
*current_input_mode = PWDIN_INM_REVEALED;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
size_t pwdin_await_input(char mask, char reveal_key, char* _buffer, size_t _buffer_size)
|
|
{
|
|
// Keeps track of the amount of characters that were put in by the user.
|
|
size_t len = 0;
|
|
|
|
// Current input mode (hidden/revealed/none). Default is hidden. If mask == 0
|
|
// then NONE is by default.
|
|
int default_mode = (mask == 0) ? PWDIN_INM_NONE : PWDIN_INM_HIDDEN;
|
|
int input_mode = default_mode;
|
|
|
|
// Latest character typed.
|
|
char c = 0;
|
|
for (; c != 10;) {
|
|
c = getc(stdin);
|
|
// Print notice only if no mode change occured.
|
|
if (!pwdin_check_special_keys(&input_mode, default_mode, c, reveal_key, mask, _buffer, &len)
|
|
&& len < _buffer_size) {
|
|
pwdin_print_notice(c, mask, input_mode);
|
|
_buffer[len] = c;
|
|
len++;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
printf("(%d)", c);
|
|
#endif /* ifdef DEBUG */
|
|
}
|
|
|
|
putc(10, stdout); // Enter new line after password end.
|
|
|
|
_buffer[len] = '\0';
|
|
|
|
return len;
|
|
}
|
|
|
|
size_t pwdin_getpass(const char* prompt, char mask, char reveal_key, int write_zero, char* _buffer,
|
|
size_t _buffer_size)
|
|
{
|
|
struct termios oldt, newt;
|
|
pwdin_setup_term(&newt, &oldt);
|
|
|
|
pwdin_print_prompt(prompt);
|
|
|
|
size_t len = pwdin_await_input(mask, reveal_key, _buffer, _buffer_size);
|
|
|
|
pwdin_teardown_term(&oldt);
|
|
|
|
if (write_zero) {
|
|
for (size_t k = len + 1; k < _buffer_size; k++) {
|
|
_buffer[k] = '\0';
|
|
}
|
|
}
|
|
return len;
|
|
}
|