use anyhow::Context; use clap::{CommandFactory, Parser, builder::ArgPredicate}; use clap_complete::{Shell, generate}; use roowho2_lib::server::{ fingerd::{FingerRequestInfo, FingerRequestNetworking}, varlink_api::VarlinkFingerClientProxy, }; /// User information lookup program /// /// The `finger` utility displays information about the system users. /// /// /// If no options are specified, finger defaults to the -l style output if operands are provided, otherwise to the -s style. /// Note that some fields may be missing, in either format, if information is not available for them. /// /// If no arguments are specified, finger will print an entry for each user currently logged into the system. /// /// Finger may be used to look up users on a remote machine. /// The format is to specify a user as “user@host”, or “@host”, /// where the default output format for the former is the -l style, /// and the default output format for the latter is the -s style. /// The -l option is the only option that may be passed to a remote machine. /// /// If standard output is a socket, finger will emit a carriage return (^M) before every linefeed (^J). /// This is for processing remote finger requests when invoked by the daemon. #[derive(Debug, Parser)] #[command(author = "Programvareverkstedet ", version)] pub struct Args { /// Forces finger to use IPv4 addresses only. #[arg(long, short = '4', conflicts_with = "ipv6")] ipv4: bool, /// Forces finger to use IPv6 addresses only. #[arg(long, short = '6', conflicts_with = "ipv4")] ipv6: bool, /// Display the user's login name, real name, terminal name and write status /// (as a ``*'' before the terminal name if write permission is denied), /// idle time, login time, and either office location and office phone number, /// or the remote host. If -o is given, the office location and office phone number /// is printed (the default). If -h is given, the remote host is printed instead. /// /// Idle time is in minutes if it is a single integer, hours and minutes if a ``:'' /// is present, or days if a ``d'' is present. If it is an "*", the login time indicates /// the time of last login. Login time is displayed as the day name if less than 6 days, /// else month, day; hours and minutes, unless more than six months ago, in which case the year /// is displayed rather than the hours and minutes. /// /// Unknown devices as well as nonexistent idle and login times are displayed as single asterisks. #[arg(long, short, conflicts_with = "long")] short: bool, /// When used in conjunction with the -s option, the name of the remote host /// is displayed instead of the office location and office phone. #[arg(long, short = 'H', requires = "short", conflicts_with = "office")] host: bool, /// When used in conjunction with the -s option, the office location and /// office phone information is displayed instead of the name of the remote host. #[arg(long, short, requires = "short", conflicts_with = "host")] office: bool, /// This option restricts the gecos output to only the users' real name. /// It also has the side-effect of restricting the output of the remote host /// when used in conjunction with the -h option. #[arg(long, short, requires = "short")] gecos: bool, /// Disable all use of the user accounting database. #[arg(short = 'k')] no_acct: bool, /// Produce a multi-line format displaying all of the information /// described for the -s option as well as the user's home directory, /// home phone number, login shell, mail status, and the contents of /// the files .forward, .plan, .project and .pubkey from the user's home directory. /// /// If idle time is at least a minute and less than a day, it is presented in the form ``hh:mm''. /// Idle times greater than a day are presented as ``d day[s]hh:mm'' /// /// Phone numbers specified as eleven digits are printed as ``+N-NNN-NNN-NNNN''. /// Numbers specified as ten or seven digits are printed as the appropriate subset of that string. /// Numbers specified as five digits are printed as ``xN-NNNN''. /// Numbers specified as four digits are printed as ``xNNNN''. /// /// If write permission is denied to the device, the phrase ``(messages off)'' /// is appended to the line containing the device name. One entry per user is displayed with the -l option; /// if a user is logged on multiple times, terminal information is repeated once per login. /// /// Mail status is shown as ``No Mail.'' if there is no mail at all, /// ``Mail last read DDD MMM ## HH:MM YYYY (TZ)'' if the person has looked at their mailbox since new mail arriving, /// or ``New mail received ...'', ``Unread since ...'' if they have new mail. #[arg( long, short, conflicts_with = "short", default_value_ifs([ ("short", ArgPredicate::IsPresent, "false"), ("users", ArgPredicate::IsPresent, "true"), ]), )] long: bool, /// Prevent the -l option of finger from displaying the contents of /// the .forward, .plan, .project and .pubkey files. #[arg(long, short, requires = "long")] prevent_files: bool, /// Prevent matching of user names. User is usually a login name; /// however, matching will also be done on the users' real names, /// unless the -m option is supplied. All name matching performed by finger is case insensitive. #[arg(long, short = 'm')] no_name_match: bool, /// Output in JSON format #[arg(long, short)] json: bool, /// Generate shell completion scripts for the specified shell /// and print them to stdout. #[arg(long, value_enum, hide = true)] completions: Option, users: Option>, } fn determine_request_info(args: &Args) -> FingerRequestInfo { if args.long { FingerRequestInfo::Long { prevent_files: args.prevent_files, } } else { debug_assert!( args.short, "Either short or long output format must be selected" ); debug_assert!( !args.host || !args.office, "Host and office options cannot both be enabled for short output format" ); if args.host { FingerRequestInfo::ShortHost { restrict_gecos: args.gecos, } } else { FingerRequestInfo::ShortOffice { restrict_gecos: args.gecos, } } } } #[tokio::main] async fn main() -> anyhow::Result<()> { let args = Args::parse(); debug_assert_ne!( args.short, args.long, "Short and long output formats cannot both be enabled or both be disabled at the same time" ); if let Some(shell) = args.completions { generate(shell, &mut Args::command(), "rwho", &mut std::io::stdout()); return Ok(()); } let mut conn = zlink::unix::connect("/run/roowho2/roowho2.varlink") .await .expect("Failed to connect to fingerd server"); let request_info = determine_request_info(&args); let request_networking = match (args.ipv4, args.ipv6) { (true, false) => FingerRequestNetworking::IPv4Only, (false, true) => FingerRequestNetworking::IPv6Only, _ => FingerRequestNetworking::Any, }; let reply = conn .finger( args.users, !args.no_name_match, request_info, request_networking, args.no_acct, ) .await .context("Failed to send finger request")? .map_err(|e| anyhow::anyhow!("Server returned an error for finger request: {:?}", e))?; if args.json { println!("{}", serde_json::to_string_pretty(&reply).unwrap()); } else { for user in reply { println!("{:#?}", user); } } Ok(()) }