diff --git a/src/cache.rs b/src/cache.rs
index 066081dc39952afc8c40172329c1db3768f66c7c..e9126f4bc8b80af282c7bb30ac27a7bdc6945371 100644
--- a/src/cache.rs
+++ b/src/cache.rs
@@ -1,4 +1,5 @@
 /* Copyright 2021 Dominik George <dominik.george@teckids.org>
+ * Copyright 2021 mirabilos <thorsten.glaser@teckids.org>
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,10 +16,11 @@
 
 use crate::BASE_NAME;
 
+use crate::unix::getpwnam_safe;
+
 use std::collections::HashMap;
 
-use libc::{geteuid, seteuid, getpwnam, uid_t};
-use std::ffi::{CStr, CString};
+use libc::{geteuid, seteuid, uid_t};
 
 use oauth2::basic::BasicTokenResponse;
 
@@ -26,7 +28,7 @@ use std::env;
 use std::fs;
 use std::io;
 use std::path::PathBuf;
-use xdg::{BaseDirectories, BaseDirectoriesError};
+use xdg::BaseDirectories;
 
 use serde::Serialize;
 use serde::de::DeserializeOwned;
@@ -53,48 +55,40 @@ impl Cache {
         }
     }
 
-    fn drop_privileges(&self, username: &String) -> Result<uid_t, &str> {
+    fn drop_privileges(&self, username: &String) -> Result<uid_t, io::Error> {
         let current_euid;
         unsafe {
             current_euid = geteuid();
         };
 
-        let nam = match CString::new(username.as_str()) {
-            Ok(nam) => nam,
-            Err(_) => return Err("Invalid username in lookup")
-        };
-        let pw;
-        unsafe {
-            pw = getpwnam(nam.as_ptr());
-        };
-        if pw.is_null() {
-            error!("Failed to lookup user {}", username);
-            return Err("Failed to lookup user");
-        }
-        let target_euid;
-        unsafe {
-            target_euid = (*pw).pw_uid;
+        let pw = match getpwnam_safe(username.to_string()) {
+            Ok(p) => p,
+            Err(e) => {
+                error!("Failed to lookup user {}: {}", username, e);
+                return Err(e);
+            }
         };
+        debug!("Lookup for user {} returned UID {}, GID {}", username, pw.pw_uid, pw.pw_gid);
+        debug!("name {} passwd {} gecos {} dir {} shell {}", pw.pw_name, pw.pw_passwd, pw.pw_gecos, pw.pw_dir, pw.pw_shell);
+        let target_euid = pw.pw_uid;
 
         if target_euid == current_euid {
             debug!("No need to drop privileges, already running as {}", username);
-            return Ok(self.original_euid);
-        } else if self.original_euid == 0 {
-            let res;
-            unsafe {
-                res = seteuid(target_euid);
-            };
-            if res == 0 {
-                debug!("Successfully dropped privileges to {}", username);
-                return Ok(target_euid);
-            } else {
-                error!("Could not drop privileges to {}", username);
-                return Err("Failed to drop privileges");
-            }
+            return Ok(current_euid);
         }
 
-        error!("Not running as root or target user, cannot drop privileges");
-        return Err("Dropping privileges not supported");
+        let res;
+        unsafe {
+            res = seteuid(target_euid);
+        };
+        if res == 0 {
+            debug!("Successfully dropped privileges to {}", username);
+            return Ok(target_euid);
+        } else {
+            let e = io::Error::last_os_error();
+            error!("Could not drop privileges to {}", username);
+            return Err(e);
+        }
     }
 
     fn restore_privileges(&self) {
@@ -105,30 +99,39 @@ impl Cache {
 
         if current_euid != self.original_euid {
             debug!("Restoring privileges");
+            let res;
             unsafe {
-                seteuid(self.original_euid);
+                res = seteuid(self.original_euid);
             };
+            if res != 0 {
+                panic!("Could not restore privileges to {}", self.original_euid);
+            }
         } else {
             debug!("No need to restore privileges, already running as original user");
         }
     }
 
-    fn get_user_xdg_base_directories(&self, username: &String) -> Result<BaseDirectories, BaseDirectoriesError> {
+    fn get_user_xdg_base_directories(&self, username: &String) -> Result<BaseDirectories, io::Error> {
         let saved_home = env::var_os("HOME");
 
-        let nam = match CString::new(username.as_str()) {
-            Ok(nam) => nam,
-            Err(_) => CString::new("nobody").ok().unwrap()
-        };
-        let user_home;
-        unsafe {
-           user_home = CStr::from_ptr((*getpwnam(nam.as_ptr())).pw_dir);
+        let pw = match getpwnam_safe(username.to_string()) {
+            Ok(p) => p,
+            Err(e) => {
+                error!("Failed to lookup user {}: {}", username, e);
+                return Err(e);
+            }
         };
+        debug!("Lookup for user {} returned UID {}, GID {}", username, pw.pw_uid, pw.pw_gid);
+        debug!("name {} passwd {} gecos {} dir {} shell {}", pw.pw_name, pw.pw_passwd, pw.pw_gecos, pw.pw_dir, pw.pw_shell);
+        let user_home = pw.pw_dir;
 
-        env::set_var("HOME", user_home.to_str().unwrap());
-        debug!("Home directory for {} is {}", username, user_home.to_str().unwrap());
+        env::set_var("HOME", &user_home);
+        debug!("Home directory for {} is {}", username, user_home);
 
-        let base_dirs = BaseDirectories::with_prefix(BASE_NAME)?;
+        let base_dirs = match BaseDirectories::with_prefix(BASE_NAME) {
+            Ok(b) => b,
+            Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e))
+        };
         if saved_home != None {
             env::set_var("HOME", saved_home.unwrap());
         } else {
diff --git a/src/lib.rs b/src/lib.rs
index 9f87bf0c87ec5905105291ae470cd3aca74ddc70..109b9bbb7e39e41536cc52ee063efffe9969aa1c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,6 +2,7 @@ const BASE_NAME: &str  = "nss_pam_oidc";
 
 // Modules and macro imports for our own code
 #[macro_use] extern crate log;
+mod unix;
 mod cache;
 mod logging;
 mod config;
diff --git a/src/nss.rs b/src/nss.rs
index 1c062cbe87e93f23da8b5b674bf476940e0adc55..598c8b698c6e82ac32e3aa0bd443d312445b32f4 100644
--- a/src/nss.rs
+++ b/src/nss.rs
@@ -26,8 +26,9 @@ use crate::logging::setup_log;
 use crate::oauth::{get_access_token_client, get_data_jq};
 use serde::{Serialize, Deserialize};
 
-use libc::{getpwuid, geteuid};
 use std::ffi::CStr;
+use libc::geteuid;
+use crate::unix::getpwuid_safe;
 
 use libnss::interop::Response;
 use libnss::passwd::{PasswdHooks, Passwd};
@@ -61,21 +62,17 @@ fn nss_hook_prepare() -> (Cache, Config) {
 
 fn get_current_user() -> String {
     let euid;
-    let pw;
     unsafe {
         euid = geteuid();
-        pw = getpwuid(euid);
     };
-    if pw.is_null() {
-        error!("Failed to look up user name for UID {}", euid);
-        return "nobody".to_string();
+    match getpwuid_safe(euid) {
+        Ok(p) => p.pw_name,
+        Err(e) => {
+            error!("Failed to lookup username for UID {}: {}", euid, e);
+	    /* FIXME: return something (empty string?) to make the caller bail out early */
+            "nobody".to_string()
+        }
     }
-
-    let euser;
-    unsafe {
-        euser = CStr::from_ptr((*pw).pw_name);
-    };
-    euser.to_str().ok().unwrap().to_string()
 }
 
 struct OidcPasswd;
diff --git a/src/unix.rs b/src/unix.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d74e0e7076661ddabd647bee51b6263dcc46ec47
--- /dev/null
+++ b/src/unix.rs
@@ -0,0 +1,114 @@
+/* Copyright 2021 Dominik George <dominik.george@teckids.org>
+ * Copyright 2021 mirabilos <thorsten.glaser@teckids.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use libc::{ERANGE, getpwnam_r, getpwuid_r, passwd, c_void, uid_t, gid_t, size_t, malloc, free};
+use std::ffi::{CStr, CString};
+use std::io;
+use std::mem::uninitialized;
+use std::ptr::null_mut;
+
+pub struct Passwd {
+    pub pw_name: String,
+    pub pw_passwd: String,
+    pub pw_uid: uid_t,
+    pub pw_gid: gid_t,
+    pub pw_gecos: String,
+    pub pw_dir: String,
+    pub pw_shell: String
+}
+
+const MAX_BUFLEN: size_t = 1024 * 1024;
+
+fn getpwxx_fillpw(c_passwd: passwd) -> Passwd {
+    unsafe {
+        Passwd {
+            pw_name: CStr::from_ptr(c_passwd.pw_name).to_string_lossy().into_owned(),
+            pw_passwd: CStr::from_ptr(c_passwd.pw_passwd).to_string_lossy().into_owned(),
+            pw_uid: c_passwd.pw_uid,
+            pw_gid: c_passwd.pw_gid,
+            pw_gecos: CStr::from_ptr(c_passwd.pw_gecos).to_string_lossy().into_owned(),
+            pw_dir: CStr::from_ptr(c_passwd.pw_dir).to_string_lossy().into_owned(),
+            pw_shell: CStr::from_ptr(c_passwd.pw_shell).to_string_lossy().into_owned(),
+        }
+    }
+}
+
+pub fn getpwnam_safe(name: String) -> Result<Passwd, io::Error> {
+    let res: Passwd;
+
+    unsafe {
+        let mut c_passwd: passwd = uninitialized();
+        let mut c_passwd_ptr: *mut passwd = null_mut();
+        let mut buf: *mut i8 = uninitialized();
+        let mut buflen: size_t = 1024;
+
+        let nam = match CString::new(name.as_str()) {
+            Ok(nam) => nam,
+            Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e))
+        };
+
+        while c_passwd_ptr.is_null() {
+            buf = malloc(buflen) as *mut i8;
+            if buf.is_null() {
+                return Err(io::Error::last_os_error());
+            }
+
+            let error = getpwnam_r(nam.as_ptr(), &mut c_passwd, buf, buflen, &mut c_passwd_ptr);
+            if c_passwd_ptr.is_null() && (error != ERANGE || buflen >= MAX_BUFLEN) {
+                free(buf as *mut c_void);
+                return Err(io::Error::from_raw_os_error(error));
+            }
+
+            buflen += 1024;
+        }
+
+        res = getpwxx_fillpw(c_passwd);
+        free(buf as *mut c_void);
+    }
+
+    return Ok(res);
+}
+
+pub fn getpwuid_safe(uid: uid_t) -> Result<Passwd, io::Error> {
+    let res: Passwd;
+
+    unsafe {
+        let mut c_passwd: passwd = uninitialized();
+        let mut c_passwd_ptr: *mut passwd = null_mut();
+        let mut buf: *mut i8 = uninitialized();
+        let mut buflen: size_t = 1024;
+
+        while c_passwd_ptr.is_null() {
+            buf = malloc(buflen) as *mut i8;
+            if buf.is_null() {
+                return Err(io::Error::last_os_error());
+            }
+
+            let error = getpwuid_r(uid, &mut c_passwd, buf, buflen, &mut c_passwd_ptr);
+            if c_passwd_ptr.is_null() && (error != ERANGE || buflen >= MAX_BUFLEN) {
+                free(buf as *mut c_void);
+                return Err(io::Error::from_raw_os_error(error));
+            }
+
+            buflen += 1024;
+        }
+
+        res = getpwxx_fillpw(c_passwd);
+        free(buf as *mut c_void);
+    }
+
+    return Ok(res);
+}