Added input_hidden() to ask for passwords in the CLI without echoing the chars. (0.37.37)
This commit is contained in:
parent
4997ad1e4b
commit
b32f7e839b
7 changed files with 172 additions and 4 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(fun VERSION 0.37.36 LANGUAGES C)
|
project(fun VERSION 0.37.37 LANGUAGES C)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
|
||||||
29
examples/input_hidden_example.fun
Executable file
29
examples/input_hidden_example.fun
Executable file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#!/usr/bin/env fun
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* Example: Demonstrate hidden input (no echo) for passwords.
|
||||||
|
*
|
||||||
|
* Added: 2026-01-02
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <io/console.fun>
|
||||||
|
|
||||||
|
print("=== input_hidden() example ===")
|
||||||
|
|
||||||
|
c = Console()
|
||||||
|
|
||||||
|
username = c.ask("Username")
|
||||||
|
password = c.ask_hidden("Password")
|
||||||
|
|
||||||
|
// Do not print the password; just show the username
|
||||||
|
print(join(["Hello, ", username, "!"], ""))
|
||||||
|
|
||||||
|
/* Expected output:
|
||||||
|
Username: hanez
|
||||||
|
Password:
|
||||||
|
=== input_hidden() example ===
|
||||||
|
Hello, hanez!
|
||||||
|
*/
|
||||||
45
examples/input_hidden_pam_auth.fun
Executable file
45
examples/input_hidden_pam_auth.fun
Executable file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#!/usr/bin/env fun
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Fun programming language.
|
||||||
|
* https://fun-lang.xyz/
|
||||||
|
*
|
||||||
|
* Example: Demonstrate hidden input (no echo) for passwords using
|
||||||
|
* /usr/bin/chkpwd from https://git.xw3.org/hanez/chkusr for PAM authetication.
|
||||||
|
*
|
||||||
|
* Added: 2026-01-02
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <io/console.fun>
|
||||||
|
|
||||||
|
print("=== input_hidden() example with PAM authentication ===")
|
||||||
|
|
||||||
|
c = Console()
|
||||||
|
|
||||||
|
username = c.ask("Username")
|
||||||
|
password = c.ask_hidden("Password")
|
||||||
|
|
||||||
|
// Do not print the password; just show the username
|
||||||
|
print(join(["Hello, ", username, "!"], ""))
|
||||||
|
|
||||||
|
res = proc_run("chkpwd -u " + username + " -p " + password)
|
||||||
|
content = res["out"]
|
||||||
|
if (res["code"] == 0)
|
||||||
|
print("Login success!")
|
||||||
|
else
|
||||||
|
print("Login failed!")
|
||||||
|
|
||||||
|
/* Possible output:
|
||||||
|
Username: hanez
|
||||||
|
Password:
|
||||||
|
=== input_hidden() example with PAM authentication ===
|
||||||
|
Hello, hanez!
|
||||||
|
Login success!
|
||||||
|
|
||||||
|
Or:
|
||||||
|
Username: hanez
|
||||||
|
Password:
|
||||||
|
=== input_hidden() example with PAM authentication ===
|
||||||
|
Hello, hanez!
|
||||||
|
Login failed!
|
||||||
|
*/
|
||||||
|
|
@ -17,6 +17,15 @@ class Console()
|
||||||
else
|
else
|
||||||
return input(join([q, ": "], ""))
|
return input(join([q, ": "], ""))
|
||||||
|
|
||||||
|
// Ask for a secret string (e.g., password) without echoing input
|
||||||
|
// Returns the entered string. A trailing newline is not echoed back.
|
||||||
|
fun ask_hidden(this, question)
|
||||||
|
q = to_string(question)
|
||||||
|
if (len(q) == 0)
|
||||||
|
return input_hidden("")
|
||||||
|
else
|
||||||
|
return input_hidden(join([q, ": "], ""))
|
||||||
|
|
||||||
// Ask a yes/no question; returns 1 for yes, 0 for no
|
// Ask a yes/no question; returns 1 for yes, 0 for no
|
||||||
// Accepts: y, yes, n, no (case-insensitive). Keeps asking until valid.
|
// Accepts: y, yes, n, no (case-insensitive). Keeps asking until valid.
|
||||||
fun ask_yes_no(this, question)
|
fun ask_yes_no(this, question)
|
||||||
|
|
|
||||||
14
src/parser.c
14
src/parser.c
|
|
@ -709,6 +709,20 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
||||||
free(name);
|
free(name);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
if (strcmp(name, "input_hidden") == 0) {
|
||||||
|
(*pos)++; /* '(' */
|
||||||
|
int hasPrompt = 0;
|
||||||
|
skip_spaces(src, len, pos);
|
||||||
|
if (*pos < len && src[*pos] != ')') {
|
||||||
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "input_hidden expects 0 or 1 argument"); free(name); return 0; }
|
||||||
|
hasPrompt = 1;
|
||||||
|
}
|
||||||
|
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after input_hidden arg(s)"); free(name); return 0; }
|
||||||
|
/* operand bit0 = hasPrompt, bit1 = hidden */
|
||||||
|
bytecode_add_instruction(bc, OP_INPUT_LINE, (hasPrompt ? 1 : 0) | 2);
|
||||||
|
free(name);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
if (strcmp(name, "proc_run") == 0) {
|
if (strcmp(name, "proc_run") == 0) {
|
||||||
(*pos)++; /* '(' */
|
(*pos)++; /* '(' */
|
||||||
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "proc_run expects 1 argument (command string)"); free(name); return 0; }
|
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "proc_run expects 1 argument (command string)"); free(name); return 0; }
|
||||||
|
|
|
||||||
6
src/vm.c
6
src/vm.c
|
|
@ -29,9 +29,15 @@
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
/* For hidden input (password) handling in OP_INPUT_LINE */
|
||||||
|
#include <termios.h>
|
||||||
//#include <arpa/inet.h>
|
//#include <arpa/inet.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Bring in split-out built-ins without changing the build system yet */
|
/* Bring in split-out built-ins without changing the build system yet */
|
||||||
#include "iter.c"
|
#include "iter.c"
|
||||||
#include "map.c"
|
#include "map.c"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
case OP_INPUT_LINE: {
|
case OP_INPUT_LINE: {
|
||||||
/* operand: 0 = no prompt; 1 = has prompt (string or any value convertible to string) */
|
/* operand bit flags:
|
||||||
int has_prompt = inst.operand ? 1 : 0;
|
* bit0 (1): has prompt (string or any value convertible to string) — top of stack holds prompt when set
|
||||||
|
* bit1 (2): hidden input (do not echo typed characters)
|
||||||
|
*/
|
||||||
|
int has_prompt = (inst.operand & 1) ? 1 : 0;
|
||||||
|
int hidden = (inst.operand & 2) ? 1 : 0;
|
||||||
if (has_prompt) {
|
if (has_prompt) {
|
||||||
/* pop prompt value and print without newline */
|
/* pop prompt value and print without newline */
|
||||||
Value pv = pop_value(vm);
|
Value pv = pop_value(vm);
|
||||||
|
|
@ -13,6 +17,35 @@ case OP_INPUT_LINE: {
|
||||||
free_value(pv);
|
free_value(pv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* For hidden input, temporarily disable terminal echo if possible */
|
||||||
|
int echo_disabled = 0;
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (hidden) {
|
||||||
|
HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
|
||||||
|
if (hStdin != INVALID_HANDLE_VALUE) {
|
||||||
|
DWORD mode;
|
||||||
|
if (GetConsoleMode(hStdin, &mode)) {
|
||||||
|
DWORD newMode = mode & ~(ENABLE_ECHO_INPUT);
|
||||||
|
if (SetConsoleMode(hStdin, newMode)) {
|
||||||
|
echo_disabled = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (hidden) {
|
||||||
|
/* POSIX termios */
|
||||||
|
struct termios oldt;
|
||||||
|
if (tcgetattr(STDIN_FILENO, &oldt) == 0) {
|
||||||
|
struct termios newt = oldt;
|
||||||
|
newt.c_lflag &= ~(ECHO);
|
||||||
|
if (tcsetattr(STDIN_FILENO, TCSANOW, &newt) == 0) {
|
||||||
|
echo_disabled = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* read a line from stdin, dynamically grow buffer */
|
/* read a line from stdin, dynamically grow buffer */
|
||||||
size_t cap = 128;
|
size_t cap = 128;
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
|
|
@ -20,7 +53,8 @@ case OP_INPUT_LINE: {
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
fprintf(stderr, "Runtime error: out of memory reading input");
|
fprintf(stderr, "Runtime error: out of memory reading input");
|
||||||
push_value(vm, make_string(""));
|
push_value(vm, make_string(""));
|
||||||
break;
|
/* On early exit, try to restore echo if we turned it off */
|
||||||
|
goto restore_echo_and_break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ch;
|
int ch;
|
||||||
|
|
@ -68,5 +102,36 @@ case OP_INPUT_LINE: {
|
||||||
free(buf);
|
free(buf);
|
||||||
|
|
||||||
push_done:
|
push_done:
|
||||||
|
/* If we disabled echo, restore terminal settings and print a newline for UX */
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (echo_disabled) {
|
||||||
|
HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
|
||||||
|
if (hStdin != INVALID_HANDLE_VALUE) {
|
||||||
|
DWORD mode;
|
||||||
|
if (GetConsoleMode(hStdin, &mode)) {
|
||||||
|
/* Re-enable ECHO flag */
|
||||||
|
mode |= ENABLE_ECHO_INPUT;
|
||||||
|
SetConsoleMode(hStdin, mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (has_prompt) {
|
||||||
|
fputc('\n', stdout);
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (echo_disabled) {
|
||||||
|
struct termios t;
|
||||||
|
if (tcgetattr(STDIN_FILENO, &t) == 0) {
|
||||||
|
t.c_lflag |= ECHO;
|
||||||
|
tcsetattr(STDIN_FILENO, TCSANOW, &t);
|
||||||
|
}
|
||||||
|
if (has_prompt) {
|
||||||
|
fputc('\n', stdout);
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
restore_echo_and_break:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue