#![allow(non_camel_case_types)]
#![allow(dead_code)]
use clap::{crate_version, Arg, ArgAction, Command};
use std::ffi::CStr;
use uucore::display::Quotable;
use uucore::entries::{self, Group, Locate, Passwd};
use uucore::error::UResult;
use uucore::error::{set_exit_code, USimpleError};
pub use uucore::libc;
use uucore::libc::{getlogin, uid_t};
use uucore::line_ending::LineEnding;
use uucore::process::{getegid, geteuid, getgid, getuid};
use uucore::{format_usage, help_about, help_section, help_usage, show_error};
macro_rules! cstr2cow {
($v:expr) => {
unsafe { CStr::from_ptr($v).to_string_lossy() }
};
}
const ABOUT: &str = help_about!("id.md");
const USAGE: &str = help_usage!("id.md");
const AFTER_HELP: &str = help_section!("after help", "id.md");
#[cfg(not(feature = "selinux"))]
static CONTEXT_HELP_TEXT: &str = "print only the security context of the process (not enabled)";
#[cfg(feature = "selinux")]
static CONTEXT_HELP_TEXT: &str = "print only the security context of the process";
mod options {
pub const OPT_AUDIT: &str = "audit"; pub const OPT_CONTEXT: &str = "context";
pub const OPT_EFFECTIVE_USER: &str = "user";
pub const OPT_GROUP: &str = "group";
pub const OPT_GROUPS: &str = "groups";
pub const OPT_HUMAN_READABLE: &str = "human-readable"; pub const OPT_NAME: &str = "name";
pub const OPT_PASSWORD: &str = "password"; pub const OPT_REAL_ID: &str = "real";
pub const OPT_ZERO: &str = "zero"; pub const ARG_USERS: &str = "USER";
}
struct Ids {
uid: u32, gid: u32, euid: u32, egid: u32, }
struct State {
nflag: bool, uflag: bool, gflag: bool, gsflag: bool, rflag: bool, zflag: bool, cflag: bool, selinux_supported: bool,
ids: Option<Ids>,
user_specified: bool,
}
#[uucore::main]
#[allow(clippy::cognitive_complexity)]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?;
let users: Vec<String> = matches
.get_many::<String>(options::ARG_USERS)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
let mut state = State {
nflag: matches.get_flag(options::OPT_NAME),
uflag: matches.get_flag(options::OPT_EFFECTIVE_USER),
gflag: matches.get_flag(options::OPT_GROUP),
gsflag: matches.get_flag(options::OPT_GROUPS),
rflag: matches.get_flag(options::OPT_REAL_ID),
zflag: matches.get_flag(options::OPT_ZERO),
cflag: matches.get_flag(options::OPT_CONTEXT),
selinux_supported: {
#[cfg(feature = "selinux")]
{
selinux::kernel_support() != selinux::KernelSupport::Unsupported
}
#[cfg(not(feature = "selinux"))]
{
false
}
},
user_specified: !users.is_empty(),
ids: None,
};
let default_format = {
!(state.uflag || state.gflag || state.gsflag)
};
if (state.nflag || state.rflag) && default_format && !state.cflag {
return Err(USimpleError::new(
1,
"cannot print only names or real IDs in default format",
));
}
if state.zflag && default_format && !state.cflag {
return Err(USimpleError::new(
1,
"option --zero not permitted in default format",
));
}
if state.user_specified && state.cflag {
return Err(USimpleError::new(
1,
"cannot print security context when user specified",
));
}
let delimiter = {
if state.zflag {
"\0".to_string()
} else {
" ".to_string()
}
};
let line_ending = LineEnding::from_zero_flag(state.zflag);
if state.cflag {
if state.selinux_supported {
#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "selinux"))]
if let Ok(context) = selinux::SecurityContext::current(false) {
let bytes = context.as_bytes();
print!("{}{}", String::from_utf8_lossy(bytes), line_ending);
} else {
return Err(USimpleError::new(1, "can't get process context"));
}
return Ok(());
} else {
return Err(USimpleError::new(
1,
"--context (-Z) works only on an SELinux-enabled kernel",
));
}
}
for i in 0..=users.len() {
let possible_pw = if state.user_specified {
match Passwd::locate(users[i].as_str()) {
Ok(p) => Some(p),
Err(_) => {
show_error!("{}: no such user", users[i].quote());
set_exit_code(1);
if i + 1 >= users.len() {
break;
} else {
continue;
}
}
}
} else {
None
};
if matches.get_flag(options::OPT_PASSWORD) {
pline(possible_pw.as_ref().map(|v| v.uid));
return Ok(());
};
if matches.get_flag(options::OPT_HUMAN_READABLE) {
pretty(possible_pw);
return Ok(());
}
if matches.get_flag(options::OPT_AUDIT) {
auditid();
return Ok(());
}
let (uid, gid) = possible_pw.as_ref().map(|p| (p.uid, p.gid)).unwrap_or((
if state.rflag { getuid() } else { geteuid() },
if state.rflag { getgid() } else { getegid() },
));
state.ids = Some(Ids {
uid,
gid,
euid: geteuid(),
egid: getegid(),
});
if state.gflag {
print!(
"{}",
if state.nflag {
entries::gid2grp(gid).unwrap_or_else(|_| {
show_error!("cannot find name for group ID {}", gid);
set_exit_code(1);
gid.to_string()
})
} else {
gid.to_string()
}
);
}
if state.uflag {
print!(
"{}",
if state.nflag {
entries::uid2usr(uid).unwrap_or_else(|_| {
show_error!("cannot find name for user ID {}", uid);
set_exit_code(1);
uid.to_string()
})
} else {
uid.to_string()
}
);
}
let groups = entries::get_groups_gnu(Some(gid)).unwrap();
let groups = if state.user_specified {
possible_pw.as_ref().map(|p| p.belongs_to()).unwrap()
} else {
groups.clone()
};
if state.gsflag {
print!(
"{}{}",
groups
.iter()
.map(|&id| {
if state.nflag {
entries::gid2grp(id).unwrap_or_else(|_| {
show_error!("cannot find name for group ID {}", id);
set_exit_code(1);
id.to_string()
})
} else {
id.to_string()
}
})
.collect::<Vec<_>>()
.join(&delimiter),
if state.zflag && state.user_specified && users.len() > 1 {
"\0"
} else {
""
}
);
}
if default_format {
id_print(&state, &groups);
}
print!("{line_ending}");
if i + 1 >= users.len() {
break;
}
}
Ok(())
}
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.infer_long_args(true)
.arg(
Arg::new(options::OPT_AUDIT)
.short('A')
.conflicts_with_all([
options::OPT_GROUP,
options::OPT_EFFECTIVE_USER,
options::OPT_HUMAN_READABLE,
options::OPT_PASSWORD,
options::OPT_GROUPS,
options::OPT_ZERO,
])
.help(
"Display the process audit user ID and other process audit properties,\n\
which requires privilege (not available on Linux).",
)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::OPT_EFFECTIVE_USER)
.short('u')
.long(options::OPT_EFFECTIVE_USER)
.conflicts_with(options::OPT_GROUP)
.help("Display only the effective user ID as a number.")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::OPT_GROUP)
.short('g')
.long(options::OPT_GROUP)
.conflicts_with(options::OPT_EFFECTIVE_USER)
.help("Display only the effective group ID as a number")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::OPT_GROUPS)
.short('G')
.long(options::OPT_GROUPS)
.conflicts_with_all([
options::OPT_GROUP,
options::OPT_EFFECTIVE_USER,
options::OPT_CONTEXT,
options::OPT_HUMAN_READABLE,
options::OPT_PASSWORD,
options::OPT_AUDIT,
])
.help(
"Display only the different group IDs as white-space separated numbers, \
in no particular order.",
)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::OPT_HUMAN_READABLE)
.short('p')
.help("Make the output human-readable. Each display is on a separate line.")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::OPT_NAME)
.short('n')
.long(options::OPT_NAME)
.help(
"Display the name of the user or group ID for the -G, -g and -u options \
instead of the number.\nIf any of the ID numbers cannot be mapped into \
names, the number will be displayed as usual.",
)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::OPT_PASSWORD)
.short('P')
.help("Display the id as a password file entry.")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::OPT_REAL_ID)
.short('r')
.long(options::OPT_REAL_ID)
.help(
"Display the real ID for the -G, -g and -u options instead of \
the effective ID.",
)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::OPT_ZERO)
.short('z')
.long(options::OPT_ZERO)
.help(
"delimit entries with NUL characters, not whitespace;\n\
not permitted in default format",
)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::OPT_CONTEXT)
.short('Z')
.long(options::OPT_CONTEXT)
.conflicts_with_all([options::OPT_GROUP, options::OPT_EFFECTIVE_USER])
.help(CONTEXT_HELP_TEXT)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::ARG_USERS)
.action(ArgAction::Append)
.value_name(options::ARG_USERS)
.value_hint(clap::ValueHint::Username),
)
}
fn pretty(possible_pw: Option<Passwd>) {
if let Some(p) = possible_pw {
print!("uid\t{}\ngroups\t", p.name);
println!(
"{}",
p.belongs_to()
.iter()
.map(|&gr| entries::gid2grp(gr).unwrap())
.collect::<Vec<_>>()
.join(" ")
);
} else {
let login = cstr2cow!(getlogin() as *const _);
let rid = getuid();
if let Ok(p) = Passwd::locate(rid) {
if login == p.name {
println!("login\t{login}");
}
println!("uid\t{}", p.name);
} else {
println!("uid\t{rid}");
}
let eid = getegid();
if eid == rid {
if let Ok(p) = Passwd::locate(eid) {
println!("euid\t{}", p.name);
} else {
println!("euid\t{eid}");
}
}
let rid = getgid();
if rid != eid {
if let Ok(g) = Group::locate(rid) {
println!("euid\t{}", g.name);
} else {
println!("euid\t{rid}");
}
}
println!(
"groups\t{}",
entries::get_groups_gnu(None)
.unwrap()
.iter()
.map(|&gr| entries::gid2grp(gr).unwrap())
.collect::<Vec<_>>()
.join(" ")
);
}
}
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
fn pline(possible_uid: Option<uid_t>) {
let uid = possible_uid.unwrap_or_else(getuid);
let pw = Passwd::locate(uid).unwrap();
println!(
"{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
pw.name,
pw.user_passwd.unwrap_or_default(),
pw.uid,
pw.gid,
pw.user_access_class.unwrap_or_default(),
pw.passwd_change_time,
pw.expiration,
pw.user_info.unwrap_or_default(),
pw.user_dir.unwrap_or_default(),
pw.user_shell.unwrap_or_default()
);
}
#[cfg(any(target_os = "linux", target_os = "android", target_os = "openbsd"))]
fn pline(possible_uid: Option<uid_t>) {
let uid = possible_uid.unwrap_or_else(getuid);
let pw = Passwd::locate(uid).unwrap();
println!(
"{}:{}:{}:{}:{}:{}:{}",
pw.name,
pw.user_passwd.unwrap_or_default(),
pw.uid,
pw.gid,
pw.user_info.unwrap_or_default(),
pw.user_dir.unwrap_or_default(),
pw.user_shell.unwrap_or_default()
);
}
#[cfg(any(target_os = "linux", target_os = "android", target_os = "openbsd"))]
fn auditid() {}
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "openbsd")))]
fn auditid() {
use std::mem::MaybeUninit;
let mut auditinfo: MaybeUninit<audit::c_auditinfo_addr_t> = MaybeUninit::uninit();
let address = auditinfo.as_mut_ptr();
if unsafe { audit::getaudit(address) } < 0 {
println!("couldn't retrieve information");
return;
}
let auditinfo = unsafe { auditinfo.assume_init() };
println!("auid={}", auditinfo.ai_auid);
println!("mask.success=0x{:x}", auditinfo.ai_mask.am_success);
println!("mask.failure=0x{:x}", auditinfo.ai_mask.am_failure);
println!("termid.port=0x{:x}", auditinfo.ai_termid.port);
println!("asid={}", auditinfo.ai_asid);
}
fn id_print(state: &State, groups: &[u32]) {
let uid = state.ids.as_ref().unwrap().uid;
let gid = state.ids.as_ref().unwrap().gid;
let euid = state.ids.as_ref().unwrap().euid;
let egid = state.ids.as_ref().unwrap().egid;
print!(
"uid={}({})",
uid,
entries::uid2usr(uid).unwrap_or_else(|_| {
show_error!("cannot find name for user ID {}", uid);
set_exit_code(1);
uid.to_string()
})
);
print!(
" gid={}({})",
gid,
entries::gid2grp(gid).unwrap_or_else(|_| {
show_error!("cannot find name for group ID {}", gid);
set_exit_code(1);
gid.to_string()
})
);
if !state.user_specified && (euid != uid) {
print!(
" euid={}({})",
euid,
entries::uid2usr(euid).unwrap_or_else(|_| {
show_error!("cannot find name for user ID {}", euid);
set_exit_code(1);
euid.to_string()
})
);
}
if !state.user_specified && (egid != gid) {
print!(
" egid={}({})",
euid,
entries::gid2grp(egid).unwrap_or_else(|_| {
show_error!("cannot find name for group ID {}", egid);
set_exit_code(1);
egid.to_string()
})
);
}
print!(
" groups={}",
groups
.iter()
.map(|&gr| format!(
"{}({})",
gr,
entries::gid2grp(gr).unwrap_or_else(|_| {
show_error!("cannot find name for group ID {}", gr);
set_exit_code(1);
gr.to_string()
})
))
.collect::<Vec<_>>()
.join(",")
);
#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "selinux"))]
if state.selinux_supported
&& !state.user_specified
&& std::env::var_os("POSIXLY_CORRECT").is_none()
{
if let Ok(context) = selinux::SecurityContext::current(false) {
let bytes = context.as_bytes();
print!(" context={}", String::from_utf8_lossy(bytes));
}
}
}
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "openbsd")))]
mod audit {
use super::libc::{c_int, c_uint, dev_t, pid_t, uid_t};
pub type au_id_t = uid_t;
pub type au_asid_t = pid_t;
pub type au_event_t = c_uint;
pub type au_emod_t = c_uint;
pub type au_class_t = c_int;
pub type au_flag_t = u64;
#[repr(C)]
pub struct au_mask {
pub am_success: c_uint,
pub am_failure: c_uint,
}
pub type au_mask_t = au_mask;
#[repr(C)]
pub struct au_tid_addr {
pub port: dev_t,
}
pub type au_tid_addr_t = au_tid_addr;
#[repr(C)]
pub struct c_auditinfo_addr {
pub ai_auid: au_id_t, pub ai_mask: au_mask_t, pub ai_termid: au_tid_addr_t, pub ai_asid: au_asid_t, pub ai_flags: au_flag_t, }
pub type c_auditinfo_addr_t = c_auditinfo_addr;
extern "C" {
pub fn getaudit(auditinfo_addr: *mut c_auditinfo_addr_t) -> c_int;
}
}