Lightweight sudo-like program for Linux written in Rust
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

326 lines
9.1 KiB

  1. use std::env::Args;
  2. use std::ffi::CStr;
  3. use std::ffi::CString;
  4. use std::fs::File;
  5. use std::io::BufRead;
  6. use std::io::BufReader;
  7. use std::io::{Error, ErrorKind};
  8. use std::fs;
  9. extern crate libc;
  10. use libc::passwd;
  11. struct Entry {
  12. users: Vec<String>,
  13. dest_user: String,
  14. cmd: String,
  15. args: String,
  16. argv0: String,
  17. inherit_envs: Vec<String>,
  18. no_new_privs: bool,
  19. arbitrary_args: bool,
  20. }
  21. impl Entry {
  22. fn new() -> Entry {
  23. return Entry {
  24. users: Vec::new(),
  25. dest_user: String::new(),
  26. cmd: String::new(),
  27. args: String::new(),
  28. argv0: String::new(),
  29. inherit_envs: Vec::new(),
  30. no_new_privs: true,
  31. arbitrary_args: false,
  32. };
  33. }
  34. }
  35. struct Passwd {
  36. pw_name: String,
  37. pw_passwd: String,
  38. pw_uid: libc::uid_t,
  39. pw_gid: libc::gid_t,
  40. pw_gecos: String,
  41. pw_dir: String,
  42. pw_shell: String,
  43. }
  44. fn initgroups(user: &str, group: libc::gid_t) -> std::io::Result<()> {
  45. let userarg = CString::new(user);
  46. return errnowrapper(unsafe {
  47. libc::initgroups(userarg.unwrap().as_ptr(), group)
  48. });
  49. }
  50. fn errnowrapper(ret: libc::c_int) -> std::io::Result<()> {
  51. if ret != 0 {
  52. return Err(Error::last_os_error());
  53. }
  54. return Ok(());
  55. }
  56. fn setuid(id: libc::uid_t) -> std::io::Result<()> {
  57. return errnowrapper(unsafe { libc::setuid(id) });
  58. }
  59. fn setgid(gid: libc::gid_t) -> std::io::Result<()> {
  60. return errnowrapper(unsafe { libc::setgid(gid) });
  61. }
  62. fn geteuid() -> u32 {
  63. unsafe {
  64. return libc::geteuid();
  65. }
  66. }
  67. fn getpwnam(username: &str) -> std::io::Result<Passwd> {
  68. fn getstr(str: *mut libc::c_char) -> String {
  69. unsafe { CStr::from_ptr(str).to_string_lossy().into_owned() }
  70. }
  71. let username_c = CString::new(username).unwrap();
  72. let username_ptr = username_c.as_ptr();
  73. let pwnamresult: *mut libc::passwd = unsafe { libc::getpwnam(username_ptr) };
  74. if pwnamresult.is_null() {
  75. if Error::last_os_error().raw_os_error().unwrap() == 0 {
  76. return Err(Error::new(
  77. ErrorKind::NotFound,
  78. format!("The username '{}' was not found", username),
  79. ));
  80. }
  81. return Err(Error::new(
  82. Error::last_os_error().kind(),
  83. format!(
  84. "Lookup of user {} failed: {}",
  85. username,
  86. &Error::last_os_error().to_string()
  87. ),
  88. ));
  89. }
  90. unsafe {
  91. Ok(Passwd {
  92. pw_name: getstr((*pwnamresult).pw_name),
  93. pw_passwd: getstr((*pwnamresult).pw_passwd),
  94. pw_uid: (*pwnamresult).pw_uid,
  95. pw_gid: (*pwnamresult).pw_gid,
  96. pw_gecos: getstr((*pwnamresult).pw_gecos),
  97. pw_dir: getstr((*pwnamresult).pw_dir),
  98. pw_shell: getstr((*pwnamresult).pw_shell),
  99. })
  100. }
  101. }
  102. fn ensure_allowed(userid: libc::uid_t, entry: &Entry) -> std::io::Result<()> {
  103. if userid == 0 {
  104. return Ok(());
  105. }
  106. for user in &entry.users {
  107. let passwd: Passwd = getpwnam(&user)?;
  108. if passwd.pw_uid == userid {
  109. return Ok(());
  110. }
  111. }
  112. let passwd: Passwd = getpwnam(&entry.dest_user)?;
  113. if passwd.pw_uid == userid {
  114. return Ok(());
  115. }
  116. return Err(Error::new(
  117. ErrorKind::PermissionDenied,
  118. "Not allowed to become target user",
  119. ));
  120. }
  121. fn usage() {
  122. println!("Usage: raou ENRTYNAME");
  123. }
  124. fn add_multi(vec: &mut Vec<String>, val: String) {
  125. if val.contains(',') {
  126. let splitted = val.split(',');
  127. for part in splitted {
  128. vec.push(part.to_owned());
  129. }
  130. } else {
  131. vec.push(val);
  132. }
  133. }
  134. fn assign(entry: &mut Entry, key: &str, value: &str) {
  135. let val = value.to_owned();
  136. match key {
  137. "path" => entry.cmd = val,
  138. "user" => add_multi(&mut entry.users, val),
  139. "env_vars" => add_multi(&mut entry.inherit_envs, val),
  140. "argv0" => entry.argv0 = val,
  141. "target_user" => entry.dest_user = val,
  142. "allow_args" => entry.arbitrary_args = val == "1" || val == "true",
  143. "args" => entry.args = val,
  144. "no_new_privs" => entry.no_new_privs = val == "1" || val == "true",
  145. _ => {
  146. eprintln!("Ignoring invalid key {}", key);
  147. }
  148. }
  149. }
  150. fn assign_from_line(entry: &mut Entry, line: &str) {
  151. let mut splitted = line.splitn(2, ' ');
  152. let key = splitted.next();
  153. let value = splitted.next();
  154. if !key.is_some() || !value.is_some() {
  155. return;
  156. }
  157. assign(entry, key.unwrap(), value.unwrap())
  158. }
  159. fn create_entry_from_file(filepath: &str) -> std::io::Result<Entry> {
  160. let mut entry: Entry = Entry::new();
  161. let f = File::open(filepath)?;
  162. let bf = BufReader::new(f);
  163. for line in bf.lines() {
  164. assign_from_line(&mut entry, &line.unwrap());
  165. }
  166. Ok(entry)
  167. }
  168. //TODO: clearenv does not set errno?
  169. fn clearenv() -> std::io::Result<()> {
  170. return errnowrapper(unsafe { libc::clearenv() });
  171. }
  172. //TODO: AsRef for envs?
  173. fn setup_environment(passwd: &Passwd, envs: &[String]) -> std::io::Result<()> {
  174. let saved_envs: Vec<String> = envs.iter()
  175. .map(|s| std::env::var(s).expect("No such var"))
  176. .collect();
  177. clearenv()?;
  178. //TODO: set_var does not have a return val?
  179. std::env::set_var("HOME", &passwd.pw_dir);
  180. std::env::set_var("USER", &passwd.pw_name);
  181. std::env::set_var("LOGNAME", &passwd.pw_name);
  182. std::env::set_var("SHELL", &passwd.pw_shell);
  183. for (i, item) in saved_envs.iter().enumerate() {
  184. std::env::set_var(&envs[i], item);
  185. }
  186. Ok(())
  187. }
  188. fn become_user(passwd: &Passwd) -> std::io::Result<()> {
  189. initgroups(&(passwd.pw_name), passwd.pw_gid)?;
  190. setgid(passwd.pw_gid)?;
  191. setuid(passwd.pw_uid)?;
  192. std::env::set_current_dir(&passwd.pw_dir)?;
  193. Ok(())
  194. }
  195. fn drop_privs(entry: &Entry) -> std::io::Result<()> {
  196. if entry.no_new_privs {
  197. errnowrapper(unsafe { libc::prctl(libc::PR_SET_DUMPABLE, 0) })?;
  198. errnowrapper(unsafe {
  199. libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)
  200. })?;
  201. }
  202. Ok(())
  203. }
  204. #[inline(always)]
  205. fn to_cstring<T: AsRef<str>>(s: T) -> *const libc::c_char {
  206. return CString::new(s.as_ref()).unwrap().into_raw();
  207. }
  208. fn create_execv_args(entry: &Entry, cmdargs: &Vec<String>) -> Vec<*const libc::c_char> {
  209. let mut args: Vec<*const libc::c_char>;
  210. if entry.arbitrary_args && cmdargs.len() > 2 {
  211. args = cmdargs.iter().skip(2).map(to_cstring).collect();
  212. } else {
  213. args = entry
  214. .args
  215. .as_str()
  216. .split_whitespace()
  217. .map(to_cstring)
  218. .collect();
  219. }
  220. if !&entry.argv0.is_empty() {
  221. args.insert(0, to_cstring(&entry.argv0));
  222. } else {
  223. let cmdbegin = &entry.cmd.rfind("/").unwrap() + 1;
  224. args.insert(0, to_cstring(&entry.cmd.split_at(cmdbegin).1));
  225. }
  226. args.push(std::ptr::null());
  227. return args;
  228. }
  229. fn exec(entryname: &str, cmdargs: &Vec<String>) -> std::io::Result<()> {
  230. let basedir: String = String::from("/etc/raou.d/");
  231. let filepath: String = basedir.to_string() + entryname;
  232. let realpath = fs::canonicalize(&filepath)?;
  233. if !realpath.starts_with(basedir) {
  234. return Err(std::io::Error::new(
  235. ErrorKind::InvalidInput,
  236. "Specified entry is outside base directory",
  237. ));
  238. }
  239. if !std::path::Path::new(&filepath).exists() {
  240. return Err(std::io::Error::new(
  241. ErrorKind::NotFound,
  242. format!("The entry {} does not exist", filepath),
  243. ));
  244. }
  245. let entry: Entry = create_entry_from_file(&filepath)?;
  246. let destuserpasswd: Passwd = getpwnam(&entry.dest_user)?;
  247. let currentuser: u32 = geteuid();
  248. let args = create_execv_args(&entry, &cmdargs);
  249. ensure_allowed(currentuser, &entry)?;
  250. become_user(&destuserpasswd).or_else(|e| {
  251. return Err(Error::new(
  252. ErrorKind::PermissionDenied,
  253. format!("Failed to switch user: {}", &e.to_string()),
  254. ));
  255. })?;
  256. setup_environment(&destuserpasswd, &entry.inherit_envs)
  257. .or_else(|e| {
  258. return Err(Error::new(
  259. ErrorKind::Other,
  260. format!("Environment setup failure: {}", &e.to_string()),
  261. ));
  262. })?;
  263. drop_privs(&entry).or_else(|e| {
  264. return Err(Error::new(
  265. ErrorKind::Other,
  266. format!("Failed to drop priviliges: {}", &e.to_string()),
  267. ));
  268. })?;
  269. unsafe {
  270. errnowrapper(libc::execv(to_cstring(entry.cmd), args.as_ptr()))
  271. .or_else(|e| {
  272. return Err(Error::new(
  273. ErrorKind::Other,
  274. format!("execv failed: {}", &e.to_string()),
  275. ));
  276. })?;
  277. }
  278. std::process::exit(0);
  279. }
  280. fn main() -> Result<(), std::io::Error> {
  281. let argv: Args = std::env::args();
  282. let cmdargs: Vec<String> = argv.collect();
  283. let entryname = cmdargs.get(1);
  284. if entryname.is_some() {
  285. match exec(&entryname.unwrap(), &cmdargs) {
  286. Err(e) => {
  287. eprintln!("The following error ocurred:");
  288. eprintln!("{}", e);
  289. std::process::exit(1);
  290. }
  291. _ => {}
  292. };
  293. }
  294. usage();
  295. std::process::exit(1);
  296. }