quinoa/src/user_info.rs

129 lines
3.8 KiB
Rust

use std::process::Stdio;
use anyhow::{Context, Result};
use nix::unistd::{Gid, Uid};
#[derive(Debug, Clone)]
pub struct User {
pub name: String,
pub id: Uid,
}
#[derive(Debug, Clone)]
pub struct Group {
pub name: String,
pub id: Gid,
}
#[derive(Debug, Clone)]
pub struct UserInfo {
pub user: User,
pub group: Group,
pub groups: Vec<Group>,
}
fn parse_pair(s: &str) -> Result<(u32, &str, &str)> {
let (id, s) = s.split_once('(').with_context(|| {
format!(
"Invalid output because it is missing the opening parenthesis: {:?}",
s
)
})?;
let (name, s) = s.split_once(')').with_context(|| {
format!(
"Invalid output because it is missing the closing parenthesis: {:?}",
s
)
})?;
let id = id
.parse()
.with_context(|| format!("Invalid output because the id is invalid: {:?}", id))?;
Ok((id, name, s))
}
pub async fn get_user_info(username: &str) -> Result<UserInfo> {
let output = tokio::process::Command::new("id")
.arg("--")
.arg(username)
.stdin(Stdio::null())
.stderr(Stdio::null())
.output()
.await?;
let mut stdout = String::from_utf8(output.stdout)?;
if stdout.ends_with('\n') {
stdout.pop();
}
let mut user = None;
let mut group = None;
let mut groups = Vec::new();
for item in stdout.split(' ') {
let (k, v) = item
.split_once('=')
.context("Invalid output because the pair is missing the key-value delimiter")?;
match k {
"uid" if user.is_none() => {
let (id, name, s) = parse_pair(v)?;
if !s.is_empty() {
return Err(anyhow!(
"Invalid output because there are unexpected trailing bytes after pair"
));
}
user = Some(User {
id: Uid::from_raw(id),
name: name.to_owned(),
});
}
"gid" if group.is_none() => {
let (id, name, s) = parse_pair(v)?;
if !s.is_empty() {
return Err(anyhow!(
"Invalid output because there are unexpected trailing bytes after pair"
));
}
group = Some(Group {
id: Gid::from_raw(id),
name: name.to_owned(),
});
}
"groups" if groups.is_empty() => {
let mut v = v;
if v.is_empty() {
continue;
}
loop {
let (id, name, s) = parse_pair(v)?;
v = s;
groups.push(Group {
id: Gid::from_raw(id),
name: name.to_owned(),
});
if v.is_empty() {
break;
} else if v.as_bytes()[0] != b',' {
return Err(anyhow!(
"Invalid output because there is a missing comma between pairs: {:?}",
v
));
} else {
v = &v[1..];
}
}
}
_ => {
return Err(anyhow!(
"Invalid output because the key was either unrecognized or duplicated: {:?}",
k
))
}
}
}
Ok(UserInfo {
user: user.context("Invalid output because the user entry was missing")?,
group: group.context("Invalid output because the group entry was missing")?,
groups,
})
}