1#![allow(non_camel_case_types)]
34#![allow(dead_code)]
35
36use clap::{crate_version, Arg, ArgAction, Command};
37use std::ffi::CStr;
38use uucore::display::Quotable;
39use uucore::entries::{self, Group, Locate, Passwd};
40use uucore::error::UResult;
41use uucore::error::{set_exit_code, USimpleError};
42pub use uucore::libc;
43use uucore::libc::{getlogin, uid_t};
44use uucore::line_ending::LineEnding;
45use uucore::process::{getegid, geteuid, getgid, getuid};
46use uucore::{format_usage, help_about, help_section, help_usage, show_error};
47
48macro_rules! cstr2cow {
49 ($v:expr) => {
50 unsafe { CStr::from_ptr($v).to_string_lossy() }
51 };
52}
53
54const ABOUT: &str = help_about!("id.md");
55const USAGE: &str = help_usage!("id.md");
56const AFTER_HELP: &str = help_section!("after help", "id.md");
57
58#[cfg(not(feature = "selinux"))]
59static CONTEXT_HELP_TEXT: &str = "print only the security context of the process (not enabled)";
60#[cfg(feature = "selinux")]
61static CONTEXT_HELP_TEXT: &str = "print only the security context of the process";
62
63mod options {
64 pub const OPT_AUDIT: &str = "audit"; pub const OPT_CONTEXT: &str = "context";
66 pub const OPT_EFFECTIVE_USER: &str = "user";
67 pub const OPT_GROUP: &str = "group";
68 pub const OPT_GROUPS: &str = "groups";
69 pub const OPT_HUMAN_READABLE: &str = "human-readable"; pub const OPT_NAME: &str = "name";
71 pub const OPT_PASSWORD: &str = "password"; pub const OPT_REAL_ID: &str = "real";
73 pub const OPT_ZERO: &str = "zero"; pub const ARG_USERS: &str = "USER";
75}
76
77struct Ids {
78 uid: u32, gid: u32, euid: u32, egid: u32, }
83
84struct State {
85 nflag: bool, uflag: bool, gflag: bool, gsflag: bool, rflag: bool, zflag: bool, cflag: bool, selinux_supported: bool,
93 ids: Option<Ids>,
94 user_specified: bool,
109}
110
111#[uucore::main]
112#[allow(clippy::cognitive_complexity)]
113pub fn uumain(args: impl uucore::Args) -> UResult<()> {
114 let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?;
115
116 let users: Vec<String> = matches
117 .get_many::<String>(options::ARG_USERS)
118 .map(|v| v.map(ToString::to_string).collect())
119 .unwrap_or_default();
120
121 let mut state = State {
122 nflag: matches.get_flag(options::OPT_NAME),
123 uflag: matches.get_flag(options::OPT_EFFECTIVE_USER),
124 gflag: matches.get_flag(options::OPT_GROUP),
125 gsflag: matches.get_flag(options::OPT_GROUPS),
126 rflag: matches.get_flag(options::OPT_REAL_ID),
127 zflag: matches.get_flag(options::OPT_ZERO),
128 cflag: matches.get_flag(options::OPT_CONTEXT),
129
130 selinux_supported: {
131 #[cfg(feature = "selinux")]
132 {
133 selinux::kernel_support() != selinux::KernelSupport::Unsupported
134 }
135 #[cfg(not(feature = "selinux"))]
136 {
137 false
138 }
139 },
140 user_specified: !users.is_empty(),
141 ids: None,
142 };
143
144 let default_format = {
145 !(state.uflag || state.gflag || state.gsflag)
147 };
148
149 if (state.nflag || state.rflag) && default_format && !state.cflag {
150 return Err(USimpleError::new(
151 1,
152 "cannot print only names or real IDs in default format",
153 ));
154 }
155 if state.zflag && default_format && !state.cflag {
156 return Err(USimpleError::new(
158 1,
159 "option --zero not permitted in default format",
160 ));
161 }
162 if state.user_specified && state.cflag {
163 return Err(USimpleError::new(
164 1,
165 "cannot print security context when user specified",
166 ));
167 }
168
169 let delimiter = {
170 if state.zflag {
171 "\0".to_string()
172 } else {
173 " ".to_string()
174 }
175 };
176 let line_ending = LineEnding::from_zero_flag(state.zflag);
177
178 if state.cflag {
179 if state.selinux_supported {
180 #[cfg(all(any(target_os = "linux", target_os = "android"), feature = "selinux"))]
182 if let Ok(context) = selinux::SecurityContext::current(false) {
183 let bytes = context.as_bytes();
184 print!("{}{}", String::from_utf8_lossy(bytes), line_ending);
185 } else {
186 return Err(USimpleError::new(1, "can't get process context"));
188 }
189 return Ok(());
190 } else {
191 return Err(USimpleError::new(
192 1,
193 "--context (-Z) works only on an SELinux-enabled kernel",
194 ));
195 }
196 }
197
198 for i in 0..=users.len() {
199 let possible_pw = if state.user_specified {
200 match Passwd::locate(users[i].as_str()) {
201 Ok(p) => Some(p),
202 Err(_) => {
203 show_error!("{}: no such user", users[i].quote());
204 set_exit_code(1);
205 if i + 1 >= users.len() {
206 break;
207 } else {
208 continue;
209 }
210 }
211 }
212 } else {
213 None
214 };
215
216 if matches.get_flag(options::OPT_PASSWORD) {
218 pline(possible_pw.as_ref().map(|v| v.uid));
220 return Ok(());
221 };
222 if matches.get_flag(options::OPT_HUMAN_READABLE) {
223 pretty(possible_pw);
225 return Ok(());
226 }
227 if matches.get_flag(options::OPT_AUDIT) {
228 auditid();
230 return Ok(());
231 }
232
233 let (uid, gid) = possible_pw.as_ref().map(|p| (p.uid, p.gid)).unwrap_or((
234 if state.rflag { getuid() } else { geteuid() },
235 if state.rflag { getgid() } else { getegid() },
236 ));
237 state.ids = Some(Ids {
238 uid,
239 gid,
240 euid: geteuid(),
241 egid: getegid(),
242 });
243
244 if state.gflag {
245 print!(
246 "{}",
247 if state.nflag {
248 entries::gid2grp(gid).unwrap_or_else(|_| {
249 show_error!("cannot find name for group ID {}", gid);
250 set_exit_code(1);
251 gid.to_string()
252 })
253 } else {
254 gid.to_string()
255 }
256 );
257 }
258
259 if state.uflag {
260 print!(
261 "{}",
262 if state.nflag {
263 entries::uid2usr(uid).unwrap_or_else(|_| {
264 show_error!("cannot find name for user ID {}", uid);
265 set_exit_code(1);
266 uid.to_string()
267 })
268 } else {
269 uid.to_string()
270 }
271 );
272 }
273
274 let groups = entries::get_groups_gnu(Some(gid)).unwrap();
275 let groups = if state.user_specified {
276 possible_pw.as_ref().map(|p| p.belongs_to()).unwrap()
277 } else {
278 groups.clone()
279 };
280
281 if state.gsflag {
282 print!(
283 "{}{}",
284 groups
285 .iter()
286 .map(|&id| {
287 if state.nflag {
288 entries::gid2grp(id).unwrap_or_else(|_| {
289 show_error!("cannot find name for group ID {}", id);
290 set_exit_code(1);
291 id.to_string()
292 })
293 } else {
294 id.to_string()
295 }
296 })
297 .collect::<Vec<_>>()
298 .join(&delimiter),
299 if state.zflag && state.user_specified && users.len() > 1 {
301 "\0"
302 } else {
303 ""
304 }
305 );
306 }
307
308 if default_format {
309 id_print(&state, &groups);
310 }
311 print!("{line_ending}");
312
313 if i + 1 >= users.len() {
314 break;
315 }
316 }
317
318 Ok(())
319}
320
321pub fn uu_app() -> Command {
322 Command::new(uucore::util_name())
323 .version(crate_version!())
324 .about(ABOUT)
325 .override_usage(format_usage(USAGE))
326 .infer_long_args(true)
327 .args_override_self(true)
328 .arg(
329 Arg::new(options::OPT_AUDIT)
330 .short('A')
331 .conflicts_with_all([
332 options::OPT_GROUP,
333 options::OPT_EFFECTIVE_USER,
334 options::OPT_HUMAN_READABLE,
335 options::OPT_PASSWORD,
336 options::OPT_GROUPS,
337 options::OPT_ZERO,
338 ])
339 .help(
340 "Display the process audit user ID and other process audit properties,\n\
341 which requires privilege (not available on Linux).",
342 )
343 .action(ArgAction::SetTrue),
344 )
345 .arg(
346 Arg::new(options::OPT_EFFECTIVE_USER)
347 .short('u')
348 .long(options::OPT_EFFECTIVE_USER)
349 .conflicts_with(options::OPT_GROUP)
350 .help("Display only the effective user ID as a number.")
351 .action(ArgAction::SetTrue),
352 )
353 .arg(
354 Arg::new(options::OPT_GROUP)
355 .short('g')
356 .long(options::OPT_GROUP)
357 .conflicts_with(options::OPT_EFFECTIVE_USER)
358 .help("Display only the effective group ID as a number")
359 .action(ArgAction::SetTrue),
360 )
361 .arg(
362 Arg::new(options::OPT_GROUPS)
363 .short('G')
364 .long(options::OPT_GROUPS)
365 .conflicts_with_all([
366 options::OPT_GROUP,
367 options::OPT_EFFECTIVE_USER,
368 options::OPT_CONTEXT,
369 options::OPT_HUMAN_READABLE,
370 options::OPT_PASSWORD,
371 options::OPT_AUDIT,
372 ])
373 .help(
374 "Display only the different group IDs as white-space separated numbers, \
375 in no particular order.",
376 )
377 .action(ArgAction::SetTrue),
378 )
379 .arg(
380 Arg::new(options::OPT_HUMAN_READABLE)
381 .short('p')
382 .help("Make the output human-readable. Each display is on a separate line.")
383 .action(ArgAction::SetTrue),
384 )
385 .arg(
386 Arg::new(options::OPT_NAME)
387 .short('n')
388 .long(options::OPT_NAME)
389 .help(
390 "Display the name of the user or group ID for the -G, -g and -u options \
391 instead of the number.\nIf any of the ID numbers cannot be mapped into \
392 names, the number will be displayed as usual.",
393 )
394 .action(ArgAction::SetTrue),
395 )
396 .arg(
397 Arg::new(options::OPT_PASSWORD)
398 .short('P')
399 .help("Display the id as a password file entry.")
400 .conflicts_with(options::OPT_HUMAN_READABLE)
401 .action(ArgAction::SetTrue),
402 )
403 .arg(
404 Arg::new(options::OPT_REAL_ID)
405 .short('r')
406 .long(options::OPT_REAL_ID)
407 .help(
408 "Display the real ID for the -G, -g and -u options instead of \
409 the effective ID.",
410 )
411 .action(ArgAction::SetTrue),
412 )
413 .arg(
414 Arg::new(options::OPT_ZERO)
415 .short('z')
416 .long(options::OPT_ZERO)
417 .help(
418 "delimit entries with NUL characters, not whitespace;\n\
419 not permitted in default format",
420 )
421 .action(ArgAction::SetTrue),
422 )
423 .arg(
424 Arg::new(options::OPT_CONTEXT)
425 .short('Z')
426 .long(options::OPT_CONTEXT)
427 .conflicts_with_all([options::OPT_GROUP, options::OPT_EFFECTIVE_USER])
428 .help(CONTEXT_HELP_TEXT)
429 .action(ArgAction::SetTrue),
430 )
431 .arg(
432 Arg::new(options::ARG_USERS)
433 .action(ArgAction::Append)
434 .value_name(options::ARG_USERS)
435 .value_hint(clap::ValueHint::Username),
436 )
437}
438
439fn pretty(possible_pw: Option<Passwd>) {
440 if let Some(p) = possible_pw {
441 print!("uid\t{}\ngroups\t", p.name);
442 println!(
443 "{}",
444 p.belongs_to()
445 .iter()
446 .map(|&gr| entries::gid2grp(gr).unwrap())
447 .collect::<Vec<_>>()
448 .join(" ")
449 );
450 } else {
451 let login = cstr2cow!(getlogin() as *const _);
452 let rid = getuid();
453 if let Ok(p) = Passwd::locate(rid) {
454 if login == p.name {
455 println!("login\t{login}");
456 }
457 println!("uid\t{}", p.name);
458 } else {
459 println!("uid\t{rid}");
460 }
461
462 let eid = getegid();
463 if eid == rid {
464 if let Ok(p) = Passwd::locate(eid) {
465 println!("euid\t{}", p.name);
466 } else {
467 println!("euid\t{eid}");
468 }
469 }
470
471 let rid = getgid();
472 if rid != eid {
473 if let Ok(g) = Group::locate(rid) {
474 println!("euid\t{}", g.name);
475 } else {
476 println!("euid\t{rid}");
477 }
478 }
479
480 println!(
481 "groups\t{}",
482 entries::get_groups_gnu(None)
483 .unwrap()
484 .iter()
485 .map(|&gr| entries::gid2grp(gr).unwrap())
486 .collect::<Vec<_>>()
487 .join(" ")
488 );
489 }
490}
491
492#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
493fn pline(possible_uid: Option<uid_t>) {
494 let uid = possible_uid.unwrap_or_else(getuid);
495 let pw = Passwd::locate(uid).unwrap();
496
497 println!(
498 "{}:{}:{}:{}:{}:{}:{}:{}:{}:{}",
499 pw.name,
500 pw.user_passwd.unwrap_or_default(),
501 pw.uid,
502 pw.gid,
503 pw.user_access_class.unwrap_or_default(),
504 pw.passwd_change_time,
505 pw.expiration,
506 pw.user_info.unwrap_or_default(),
507 pw.user_dir.unwrap_or_default(),
508 pw.user_shell.unwrap_or_default()
509 );
510}
511
512#[cfg(any(target_os = "linux", target_os = "android", target_os = "openbsd"))]
513fn pline(possible_uid: Option<uid_t>) {
514 let uid = possible_uid.unwrap_or_else(getuid);
515 let pw = Passwd::locate(uid).unwrap();
516
517 println!(
518 "{}:{}:{}:{}:{}:{}:{}",
519 pw.name,
520 pw.user_passwd.unwrap_or_default(),
521 pw.uid,
522 pw.gid,
523 pw.user_info.unwrap_or_default(),
524 pw.user_dir.unwrap_or_default(),
525 pw.user_shell.unwrap_or_default()
526 );
527}
528
529#[cfg(any(target_os = "linux", target_os = "android", target_os = "openbsd"))]
530fn auditid() {}
531
532#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "openbsd")))]
533fn auditid() {
534 use std::mem::MaybeUninit;
535
536 let mut auditinfo: MaybeUninit<audit::c_auditinfo_addr_t> = MaybeUninit::uninit();
537 let address = auditinfo.as_mut_ptr();
538 if unsafe { audit::getaudit(address) } < 0 {
539 println!("couldn't retrieve information");
540 return;
541 }
542
543 let auditinfo = unsafe { auditinfo.assume_init() };
545
546 println!("auid={}", auditinfo.ai_auid);
547 println!("mask.success=0x{:x}", auditinfo.ai_mask.am_success);
548 println!("mask.failure=0x{:x}", auditinfo.ai_mask.am_failure);
549 println!("termid.port=0x{:x}", auditinfo.ai_termid.port);
550 println!("asid={}", auditinfo.ai_asid);
551}
552
553fn id_print(state: &State, groups: &[u32]) {
554 let uid = state.ids.as_ref().unwrap().uid;
555 let gid = state.ids.as_ref().unwrap().gid;
556 let euid = state.ids.as_ref().unwrap().euid;
557 let egid = state.ids.as_ref().unwrap().egid;
558
559 print!(
560 "uid={}({})",
561 uid,
562 entries::uid2usr(uid).unwrap_or_else(|_| {
563 show_error!("cannot find name for user ID {}", uid);
564 set_exit_code(1);
565 uid.to_string()
566 })
567 );
568 print!(
569 " gid={}({})",
570 gid,
571 entries::gid2grp(gid).unwrap_or_else(|_| {
572 show_error!("cannot find name for group ID {}", gid);
573 set_exit_code(1);
574 gid.to_string()
575 })
576 );
577 if !state.user_specified && (euid != uid) {
578 print!(
579 " euid={}({})",
580 euid,
581 entries::uid2usr(euid).unwrap_or_else(|_| {
582 show_error!("cannot find name for user ID {}", euid);
583 set_exit_code(1);
584 euid.to_string()
585 })
586 );
587 }
588 if !state.user_specified && (egid != gid) {
589 print!(
590 " egid={}({})",
591 euid,
592 entries::gid2grp(egid).unwrap_or_else(|_| {
593 show_error!("cannot find name for group ID {}", egid);
594 set_exit_code(1);
595 egid.to_string()
596 })
597 );
598 }
599 print!(
600 " groups={}",
601 groups
602 .iter()
603 .map(|&gr| format!(
604 "{}({})",
605 gr,
606 entries::gid2grp(gr).unwrap_or_else(|_| {
607 show_error!("cannot find name for group ID {}", gr);
608 set_exit_code(1);
609 gr.to_string()
610 })
611 ))
612 .collect::<Vec<_>>()
613 .join(",")
614 );
615
616 #[cfg(all(any(target_os = "linux", target_os = "android"), feature = "selinux"))]
617 if state.selinux_supported
618 && !state.user_specified
619 && std::env::var_os("POSIXLY_CORRECT").is_none()
620 {
621 if let Ok(context) = selinux::SecurityContext::current(false) {
623 let bytes = context.as_bytes();
624 print!(" context={}", String::from_utf8_lossy(bytes));
625 }
626 }
627}
628
629#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "openbsd")))]
630mod audit {
631 use super::libc::{c_int, c_uint, dev_t, pid_t, uid_t};
632
633 pub type au_id_t = uid_t;
634 pub type au_asid_t = pid_t;
635 pub type au_event_t = c_uint;
636 pub type au_emod_t = c_uint;
637 pub type au_class_t = c_int;
638 pub type au_flag_t = u64;
639
640 #[repr(C)]
641 pub struct au_mask {
642 pub am_success: c_uint,
643 pub am_failure: c_uint,
644 }
645 pub type au_mask_t = au_mask;
646
647 #[repr(C)]
648 pub struct au_tid_addr {
649 pub port: dev_t,
650 }
651 pub type au_tid_addr_t = au_tid_addr;
652
653 #[repr(C)]
654 pub struct c_auditinfo_addr {
655 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, }
661 pub type c_auditinfo_addr_t = c_auditinfo_addr;
662
663 extern "C" {
664 pub fn getaudit(auditinfo_addr: *mut c_auditinfo_addr_t) -> c_int;
665 }
666}