pwdin/pwdin.c
2025-05-03 00:05:11 +02:00

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;
}