129 lines
3.8 KiB
Rust
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,
|
|
})
|
|
}
|