Implement getpass()

Also make fields in `termios` public; required for modifying them.

There's an new type of test: `EXPECT_INPUT_BINS`. These require a `.exp`
file to be present along with the `.c` file. The `.exp` file takes the
produced binary as an argument and sends input to the program. This is
useful for testing functions like `getpass()`.
This commit is contained in:
Agoston Szepessy
2024-07-21 14:15:18 +02:00
parent 798d17c5b3
commit a83d4cbced
6 changed files with 138 additions and 20 deletions
+1
View File
@@ -2,3 +2,4 @@
pub const PATH_MAX: usize = 4096;
pub const NGROUPS_MAX: usize = 65536;
pub const PASS_MAX: usize = 128;
+15 -15
View File
@@ -37,28 +37,28 @@ pub const TCSAFLUSH: c_int = 2;
#[cfg(target_os = "linux")]
#[repr(C)]
#[derive(Default)]
#[derive(Default, Clone)]
pub struct termios {
c_iflag: tcflag_t,
c_oflag: tcflag_t,
c_cflag: tcflag_t,
c_lflag: tcflag_t,
c_line: cc_t,
c_cc: [cc_t; NCCS],
__c_ispeed: speed_t,
__c_ospeed: speed_t,
pub c_iflag: tcflag_t,
pub c_oflag: tcflag_t,
pub c_cflag: tcflag_t,
pub c_lflag: tcflag_t,
pub c_line: cc_t,
pub c_cc: [cc_t; NCCS],
pub __c_ispeed: speed_t,
pub __c_ospeed: speed_t,
}
// Must match structure in redox_termios
#[cfg(target_os = "redox")]
#[repr(C)]
#[derive(Default)]
#[derive(Default, Clone)]
pub struct termios {
c_iflag: tcflag_t,
c_oflag: tcflag_t,
c_cflag: tcflag_t,
c_lflag: tcflag_t,
c_cc: [cc_t; NCCS],
pub c_iflag: tcflag_t,
pub c_oflag: tcflag_t,
pub c_cflag: tcflag_t,
pub c_lflag: tcflag_t,
pub c_cc: [cc_t; NCCS],
}
#[no_mangle]
+48 -4
View File
@@ -11,7 +11,9 @@ use crate::{
c_str::CStr,
header::{
crypt::{crypt_data, crypt_r},
errno, fcntl, limits,
errno, fcntl,
limits::{self, PASS_MAX},
stdio,
stdlib::getenv,
sys_ioctl, sys_resource, sys_time, sys_utsname, termios,
time::timespec,
@@ -450,9 +452,51 @@ pub extern "C" fn getpagesize() -> c_int {
.expect("page size not representable as type `int`")
}
// #[no_mangle]
pub extern "C" fn getpass(prompt: *const c_char) -> *mut c_char {
unimplemented!();
#[no_mangle]
pub unsafe extern "C" fn getpass(prompt: *const c_char) -> *mut c_char {
let tty = stdio::fopen(c_str!("/dev/tty").as_ptr(), c_str!("w+e").as_ptr());
if tty.is_null() {
return ptr::null_mut();
}
let fd = stdio::fileno(tty);
let mut term = termios::termios::default();
termios::tcgetattr(fd, &mut term as *mut termios::termios);
let old_temr = term.clone();
term.c_iflag &= !(termios::IGNCR | termios::INLCR) as u32;
term.c_iflag |= termios::ICRNL as u32;
term.c_lflag &= !(termios::ECHO | termios::ISIG) as u32;
term.c_lflag |= termios::ICANON as u32;
termios::tcsetattr(fd, termios::TCSAFLUSH, &term as *const termios::termios);
stdio::fputs(prompt, tty);
stdio::fflush(tty);
static mut PASSBUFF: [c_char; PASS_MAX] = [0; PASS_MAX];
let len = read(fd, PASSBUFF.as_mut_ptr() as *const c_void, PASSBUFF.len());
if len >= 0 {
let mut l = len as usize;
if PASSBUFF[l - 1] == b'\n' as c_char || PASSBUFF.len() == l {
l -= 1;
}
PASSBUFF[l] = 0;
}
termios::tcsetattr(fd, termios::TCSAFLUSH, &old_temr as *const termios::termios);
stdio::fputs(c_str!("\n").as_ptr(), tty);
stdio::fclose(tty);
if len < 0 {
return ptr::null_mut();
}
PASSBUFF.as_mut_ptr()
}
#[no_mangle]
+17 -1
View File
@@ -196,6 +196,11 @@ NAMES=\
# resource/getrusage
# time/times
# Tests run with `expect` (require a .c file and an .exp file
# that takes the produced binary as the first argument)
EXPECT_INPUT_NAMES=\
unistd/getpass
#TODO: dynamic tests currently broken
BINS=$(patsubst %,$(BUILD)/bins_static/%,$(NAMES))
#BINS+=$(patsubst %,bins_dynamic/%,$(NAMES))
@@ -203,6 +208,7 @@ BINS=$(patsubst %,$(BUILD)/bins_static/%,$(NAMES))
EXPECT_BINS=$(patsubst %,$(BUILD)/bins_static/%,$(EXPECT_NAMES))
#EXPECT_BINS+=$(patsubst %,bins_dynamic/%,$(EXPECT_NAMES))
#EXPECT_BINS+=$(patsubst %,bins_dynamic/%,$(DYNAMIC_ONLY_NAMES))
EXPECT_INPUT_BINS=$(patsubst %,$(BUILD)/bins_expect_input/%,$(EXPECT_INPUT_NAMES))
CARGO_TEST?=cargo
TEST_RUNNER?=
@@ -214,12 +220,17 @@ all: $(BINS)
clean:
rm -rf bins_* gen *.out
run: | $(BINS)
run: | $(BINS) $(EXPECT_INPUT_BINS)
for bin in $(BINS); \
do \
echo "# $${bin} #"; \
${TEST_RUNNER} "$${bin}" test args || exit $$?; \
done
for exp in $(EXPECT_INPUT_BINS); \
do \
echo "# expect $$(readlink -e $${exp}.exp) $$(readlink -e $${exp}) #"; \
expect "$$(readlink -e $${exp}.exp)" "$$(readlink -e $${exp})" test args || exit $$?; \
done
expected: | $(EXPECT_BINS)
rm -rf expected
@@ -289,6 +300,11 @@ $(BUILD)/bins_static/%: %.c $(DEPS)
mkdir -p "$$(dirname "$@")"
$(CC) "$<" -o "$@" $(FLAGS) $(STATIC_FLAGS)
$(BUILD)/bins_expect_input/%: %.c %.exp $(DEPS)
mkdir -p "$$(dirname "$@")"
$(CC) "$<" -o "$@" $(FLAGS) $(STATIC_FLAGS)
cp $(word 2, "$^") $(addsuffix .exp,"$@")
$(BUILD)/bins_dynamic/%.so: %.c $(DEPS)
mkdir -p "$$(dirname "$@")"
$(CC) "$<" -o "$@" -shared -fpic $(FLAGS) $(DYNAMIC_FLAGS)
+39
View File
@@ -0,0 +1,39 @@
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// #include "test_helpers.h"
int main(void)
{
const char *pass = "pass";
const char *prompt = "Enter password: ";
char *result = getpass(prompt);
if(strcmp(pass, result)) {
printf("incorrect password\n");
exit(EXIT_FAILURE);
}
const char *pass_127_chars = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
result = getpass(prompt);
if(strcmp(pass_127_chars, result)) {
printf("incorrect password\n");
exit(EXIT_FAILURE);
}
const char *pass_empty = "";
result = getpass(prompt);
if(strcmp(pass_empty, result)) {
printf("incorrect password\n");
exit(EXIT_FAILURE);
}
printf("matching passwords\n", result);
return 0;
}
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/expect
set testgetpass [lindex $argv 0];
spawn $testgetpass
expect "Enter password: "
send -- "pass\r"
expect "Enter password: "
send -- "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r"
expect "Enter password: "
send -- "\r"
expect {
"incorrect password" { exit 123 }
eof
}