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