diff --git a/src/header/limits/mod.rs b/src/header/limits/mod.rs
index 0c08687e8b09f16a0ba9f7243abf8f987aaafcab..80cfa1904bf5d11a34f65ca8cddb9be3cb72ada0 100644
--- a/src/header/limits/mod.rs
+++ b/src/header/limits/mod.rs
@@ -2,3 +2,4 @@
 
 pub const PATH_MAX: usize = 4096;
 pub const NGROUPS_MAX: usize = 65536;
+pub const PASS_MAX: usize = 128;
diff --git a/src/header/termios/mod.rs b/src/header/termios/mod.rs
index e1dc03feb1676cafc820350f700a0dc31b043d9e..7d2ad46ff7ac1a1cac568ce5fec051df6ad310ba 100644
--- a/src/header/termios/mod.rs
+++ b/src/header/termios/mod.rs
@@ -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]
diff --git a/src/header/unistd/mod.rs b/src/header/unistd/mod.rs
index 6989ee525c6c37814f73731f1411cf18df13431f..1bc169d9518cb780b184f098e9f713ddc397b96e 100644
--- a/src/header/unistd/mod.rs
+++ b/src/header/unistd/mod.rs
@@ -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]
diff --git a/tests/Makefile b/tests/Makefile
index 37abe58dbbe6d57944f1246081c30fab4f525ecf..636df83cc759f37430fb6311975a49435996d105 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -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)
diff --git a/tests/unistd/getpass.c b/tests/unistd/getpass.c
new file mode 100644
index 0000000000000000000000000000000000000000..07c8e5c9a5e9a449364674133c04e1bc722b57d2
--- /dev/null
+++ b/tests/unistd/getpass.c
@@ -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;
+}
\ No newline at end of file
diff --git a/tests/unistd/getpass.exp b/tests/unistd/getpass.exp
new file mode 100644
index 0000000000000000000000000000000000000000..a707a04655eb359f69bbf3ca364176f11a5cc22f
--- /dev/null
+++ b/tests/unistd/getpass.exp
@@ -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
+}
\ No newline at end of file