#![allow(non_camel_case_types)]
#![allow(dead_code)]
use clap::{Arg, ArgAction, Command};
use std::ffi::CStr;
use std::io::{self, Write};
use uucore::display::Quotable;
use uucore::entries::{self, Group, Locate, Passwd};
use uucore::error::UResult;
use uucore::error::{USimpleError, set_exit_code};
pub use uucore::libc;
use uucore::libc::{getlogin, uid_t};
use uucore::line_ending::LineEnding;
use uucore::translate;
use uucore::process::{getegid, geteuid, getgid, getuid};
use uucore::{format_usage, show_error};
macro_rules! cstr2cow {
($v:expr) => {
unsafe {
let ptr = $v;
if ptr.is_null() {
None
} else {
Some({ CStr::from_ptr(ptr) }.to_string_lossy())
}
}
};
}
fn get_context_help_text() -> String {
#[cfg(not(any(feature = "selinux", feature = "smack")))]
return translate!("id-context-help-disabled");
#[cfg(any(feature = "selinux", feature = "smack"))]
return translate!("id-context-help-enabled");
}
mod options {
pub const OPT_IGNORE: &str = "ignore";
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, #[cfg(feature = "selinux")]
selinux_supported: bool,
#[cfg(feature = "smack")]
smack_supported: bool,
ids: Option<Ids>,
user_specified: bool,
}
#[uucore::main(no_signals)]
#[allow(clippy::cognitive_complexity)]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
let mut lock = io::stdout().lock();
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),
#[cfg(feature = "selinux")]
selinux_supported: uucore::selinux::is_selinux_enabled(),
#[cfg(feature = "smack")]
smack_supported: uucore::smack::is_smack_enabled(),
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,
translate!("id-error-names-real-ids-require-flags"),
));
}
if state.zflag && default_format && !state.cflag {
return Err(USimpleError::new(
1,
translate!("id-error-zero-not-permitted-default"),
));
}
if state.user_specified && state.cflag {
return Err(USimpleError::new(
1,
translate!("id-error-cannot-print-context-with-user"),
));
}
let delimiter = if state.zflag { "\0" } else { " " };
let line_ending = LineEnding::from_zero_flag(state.zflag);
if state.cflag {
#[cfg(feature = "selinux")]
if state.selinux_supported {
if let Ok(context) = selinux::SecurityContext::current(false) {
let bytes = context.as_bytes();
write!(lock, "{}{line_ending}", String::from_utf8_lossy(bytes))?;
return Ok(());
}
return Err(USimpleError::new(
1,
translate!("id-error-cannot-get-context"),
));
}
#[cfg(feature = "smack")]
if state.smack_supported {
match uucore::smack::get_smack_label_for_self() {
Ok(label) => {
write!(lock, "{label}{line_ending}")?;
return Ok(());
}
Err(_) => {
return Err(USimpleError::new(
1,
translate!("id-error-cannot-get-context"),
));
}
}
}
return Err(USimpleError::new(
1,
translate!("id-error-context-security-only"),
));
}
for i in 0..=users.len() {
let possible_pw = if state.user_specified {
if let Ok(p) = Passwd::locate(users[i].as_str()) {
Some(p)
} else {
show_error!(
"{}",
translate!("id-error-no-such-user", "user" => users[i].quote())
);
set_exit_code(1);
if i + 1 >= users.len() {
break;
}
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_or(
{
let use_effective = !state.rflag && (state.uflag || state.gflag || state.gsflag);
if use_effective {
(geteuid(), getegid())
} else {
(getuid(), getgid())
}
},
|p| (p.uid, p.gid),
);
state.ids = Some(Ids {
uid,
gid,
euid: geteuid(),
egid: getegid(),
});
if state.gflag {
write!(
lock,
"{}",
if state.nflag {
entries::gid2grp(gid).unwrap_or_else(|_| {
show_error!(
"{}",
translate!("id-error-cannot-find-group-name", "gid" => gid)
);
set_exit_code(1);
gid.to_string()
})
} else {
gid.to_string()
}
)?;
}
if state.uflag {
write!(
lock,
"{}",
if state.nflag {
entries::uid2usr(uid).unwrap_or_else(|_| {
show_error!(
"{}",
translate!("id-error-cannot-find-user-name", "uid" => uid)
);
set_exit_code(1);
uid.to_string()
})
} else {
uid.to_string()
}
)?;
}
let groups = entries::get_groups_gnu(Some(gid))?;
let groups = if state.user_specified {
possible_pw.as_ref().map(Passwd::belongs_to).unwrap()
} else {
groups.clone()
};
if state.gsflag {
write!(
lock,
"{}{}",
groups
.iter()
.map(|&id| {
if state.nflag {
entries::gid2grp(id).unwrap_or_else(|_| {
show_error!(
"{}",
translate!("id-error-cannot-find-group-name", "gid" => 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)?;
}
write!(lock, "{line_ending}")?;
if i + 1 >= users.len() {
break;
}
}
Ok(())
}
pub fn uu_app() -> Command {
Command::new("id")
.version(uucore::crate_version!())
.help_template(uucore::localized_help_template("id"))
.about(translate!("id-about"))
.override_usage(format_usage(&translate!("id-usage")))
.infer_long_args(true)
.args_override_self(true)
.after_help(translate!("id-after-help"))
.arg(
Arg::new(options::OPT_IGNORE)
.short('a')
.long(options::OPT_IGNORE)
.help(translate!("id-help-ignore"))
.action(ArgAction::SetTrue),
)
.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(translate!("id-help-audit"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::OPT_EFFECTIVE_USER)
.short('u')
.long(options::OPT_EFFECTIVE_USER)
.conflicts_with(options::OPT_GROUP)
.help(translate!("id-help-user"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::OPT_GROUP)
.short('g')
.long(options::OPT_GROUP)
.conflicts_with(options::OPT_EFFECTIVE_USER)
.help(translate!("id-help-group"))
.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(translate!("id-help-groups"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::OPT_HUMAN_READABLE)
.short('p')
.help(translate!("id-help-human-readable"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::OPT_NAME)
.short('n')
.long(options::OPT_NAME)
.help(translate!("id-help-name"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::OPT_PASSWORD)
.short('P')
.help(translate!("id-help-password"))
.conflicts_with(options::OPT_HUMAN_READABLE)
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::OPT_REAL_ID)
.short('r')
.long(options::OPT_REAL_ID)
.help(translate!("id-help-real"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::OPT_ZERO)
.short('z')
.long(options::OPT_ZERO)
.help(translate!("id-help-zero"))
.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(get_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>) -> io::Result<()> {
let mut lock = io::stdout().lock();
if let Some(p) = possible_pw {
writeln!(
lock,
"{}\t{}\n{}\t",
translate!("id-output-uid"),
p.name,
translate!("id-output-groups")
)?;
writeln!(
lock,
"{}",
p.belongs_to()
.iter()
.map(|&gr| entries::gid2grp(gr).unwrap_or_else(|_| gr.to_string()))
.collect::<Vec<_>>()
.join(" ")
)?;
} else {
let login = cstr2cow!(getlogin().cast_const());
let uid = getuid();
if let Ok(p) = Passwd::locate(uid) {
if let Some(user_name) = login {
writeln!(lock, "{}\t{user_name}", translate!("id-output-login"))?;
}
writeln!(lock, "{}\t{}", translate!("id-output-uid"), p.name)?;
} else {
writeln!(lock, "{}\t{uid}", translate!("id-output-uid"))?;
}
let euid = geteuid();
if euid != uid {
if let Ok(p) = Passwd::locate(euid) {
writeln!(lock, "{}\t{}", translate!("id-output-euid"), p.name)?;
} else {
writeln!(lock, "{}\t{euid}", translate!("id-output-euid"))?;
}
}
let rgid = getgid();
let egid = getegid();
if egid != rgid {
if let Ok(g) = Group::locate(rgid) {
writeln!(lock, "{}\t{}", translate!("id-output-rgid"), g.name)?;
} else {
writeln!(lock, "{}\t{rgid}", translate!("id-output-rgid"))?;
}
}
writeln!(
lock,
"{}\t{}",
translate!("id-output-groups"),
entries::get_groups_gnu(None)?
.iter()
.map(|&gr| entries::gid2grp(gr).unwrap_or_else(|_| gr.to_string()))
.collect::<Vec<_>>()
.join(" ")
)?;
}
Ok(())
}
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
fn pline(possible_uid: Option<uid_t>) -> io::Result<()> {
let uid = possible_uid.unwrap_or_else(getuid);
let pw = Passwd::locate(uid)?;
writeln!(
io::stdout().lock(),
"{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
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",
target_os = "cygwin",
target_os = "netbsd"
))]
fn pline(possible_uid: Option<uid_t>) -> io::Result<()> {
let uid = possible_uid.unwrap_or_else(getuid);
let pw = Passwd::locate(uid)?;
writeln!(
io::stdout().lock(),
"{}:{}:{}:{}:{}:{}:{}",
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()
)?;
Ok(())
}
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "openbsd",
target_os = "cygwin",
target_os = "netbsd"
))]
#[allow(clippy::unnecessary_wraps)]
fn auditid() -> io::Result<()> {
Ok(())
}
#[cfg(not(any(
target_os = "linux",
target_os = "android",
target_os = "openbsd",
target_os = "cygwin",
target_os = "netbsd"
)))]
fn auditid() -> io::Result<()> {
use std::mem::MaybeUninit;
let mut lock = io::stdout().lock();
let mut auditinfo: MaybeUninit<audit::c_auditinfo_addr_t> = MaybeUninit::uninit();
let address = auditinfo.as_mut_ptr();
if unsafe { audit::getaudit(address) } < 0 {
writeln!(lock, "{}", translate!("id-error-audit-retrieve"))?;
return Ok(());
}
let auditinfo = unsafe { auditinfo.assume_init() };
writeln!(lock, "auid={}", auditinfo.ai_auid)?;
writeln!(lock, "mask.success=0x{:x}", auditinfo.ai_mask.am_success)?;
writeln!(lock, "mask.failure=0x{:x}", auditinfo.ai_mask.am_failure)?;
writeln!(lock, "termid.port=0x{:x}", auditinfo.ai_termid.port)?;
writeln!(lock, "asid={}", auditinfo.ai_asid)?;
Ok(())
}
fn id_print(state: &State, groups: &[u32]) -> io::Result<()> {
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;
let mut lock = io::stdout().lock();
write!(
lock,
"uid={uid}({})",
entries::uid2usr(uid).unwrap_or_else(|_| {
show_error!(
"{}",
translate!("id-error-cannot-find-user-name", "uid" => uid)
);
set_exit_code(1);
uid.to_string()
})
)?;
write!(
lock,
" gid={gid}({})",
entries::gid2grp(gid).unwrap_or_else(|_| {
show_error!(
"{}",
translate!("id-error-cannot-find-group-name", "gid" => gid)
);
set_exit_code(1);
gid.to_string()
})
)?;
if !state.user_specified && (euid != uid) {
write!(
lock,
" euid={euid}({})",
entries::uid2usr(euid).unwrap_or_else(|_| {
show_error!(
"{}",
translate!("id-error-cannot-find-user-name", "uid" => euid)
);
set_exit_code(1);
euid.to_string()
})
)?;
}
if !state.user_specified && (egid != gid) {
write!(
lock,
" egid={egid}({})",
entries::gid2grp(egid).unwrap_or_else(|_| {
show_error!(
"{}",
translate!("id-error-cannot-find-group-name", "gid" => egid)
);
set_exit_code(1);
egid.to_string()
})
)?;
}
write!(
lock,
" groups={}",
groups
.iter()
.map(|&gr| format!(
"{gr}({})",
entries::gid2grp(gr).unwrap_or_else(|_| {
show_error!(
"{}",
translate!("id-error-cannot-find-group-name", "gid" => gr)
);
set_exit_code(1);
gr.to_string()
})
))
.collect::<Vec<_>>()
.join(",")
)?;
#[cfg(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();
write!(lock, " context={}", String::from_utf8_lossy(bytes))?;
}
}
#[cfg(feature = "smack")]
if state.smack_supported
&& !state.user_specified
&& std::env::var_os("POSIXLY_CORRECT").is_none()
{
if let Ok(label) = uucore::smack::get_smack_label_for_self() {
write!(lock, " context={label}")?;
}
}
Ok(())
}
#[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)]
#[expect(clippy::struct_field_names)]
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;
unsafe extern "C" {
pub fn getaudit(auditinfo_addr: *mut c_auditinfo_addr_t) -> c_int;
}
}