1
use anyhow::Context;
2
use clap::{CommandFactory, Parser};
3
use clap_complete::{Shell, generate};
4
use roowho2_lib::server::varlink_api::VarlinkFingerClientProxy;
5

            
6
/// User information lookup program
7
///
8
/// The `finger` utility displays information about the system users.
9
///
10
///
11
/// If no options are specified, finger defaults to the -l style output if operands are provided, otherwise to the -s style.
12
/// Note that some fields may be missing, in either format, if information is not available for them.
13
///
14
/// If no arguments are specified, finger will print an entry for each user currently logged into the system.
15
///
16
/// Finger may be used to look up users on a remote machine.
17
/// The format is to specify a user as “user@host”, or “@host”,
18
/// where the default output format for the former is the -l style,
19
/// and the default output format for the latter is the -s style.
20
/// The -l option is the only option that may be passed to a remote machine.
21
///
22
/// If standard output is a socket, finger will emit a carriage return (^M) before every linefeed (^J).
23
/// This is for processing remote finger requests when invoked by the daemon.
24
#[derive(Debug, Parser)]
25
#[command(
26
    author = "Programvareverkstedet <projects@pvv.ntnu.no>",
27
    about,
28
    version
29
)]
30
pub struct Args {
31
    /// Forces finger to use IPv4 addresses only.
32
    #[arg(long, short = '4', conflicts_with = "ipv6")]
33
    ipv4: bool,
34

            
35
    /// Forces finger to use IPv6 addresses only.
36
    #[arg(long, short = '6', conflicts_with = "ipv4")]
37
    ipv6: bool,
38

            
39
    /// Display the user's login name, real name, terminal name and write status
40
    /// (as a ``*'' before the terminal name if write permission is denied),
41
    /// idle time, login time, and either office location and office phone number,
42
    /// or the remote host. If -o is given, the office location and office phone number
43
    /// is printed (the default). If -h is given, the remote host is printed instead.
44
    ///
45
    /// Idle time is in minutes if it is a single integer, hours and minutes if a ``:''
46
    /// is present, or days if a ``d'' is present. If it is an "*", the login time indicates
47
    /// the time of last login. Login time is displayed as the day name if less than 6 days,
48
    /// else month, day; hours and minutes, unless more than six months ago, in which case the year
49
    /// is displayed rather than the hours and minutes.
50
    ///
51
    /// Unknown devices as well as nonexistent idle and login times are displayed as single asterisks.
52
    #[arg(long, short, conflicts_with = "long")]
53
    short: bool,
54

            
55
    /// When used in conjunction with the -s option, the name of the remote host
56
    /// is displayed instead of the office location and office phone.
57
    #[arg(long, short = 'H', requires = "short", conflicts_with = "office")]
58
    host: bool,
59

            
60
    /// When used in conjunction with the -s option, the office location and
61
    /// office phone information is displayed instead of the name of the remote host.
62
    #[arg(long, short, requires = "short", conflicts_with = "host")]
63
    office: bool,
64

            
65
    /// This option restricts the gecos output to only the users' real name.
66
    /// It also has the side-effect of restricting the output of the remote host
67
    /// when used in conjunction with the -h option.
68
    #[arg(long, short, requires = "short")]
69
    gecos: bool,
70

            
71
    /// Disable all use of the user accounting database.
72
    #[arg(short = 'k')]
73
    no_acct: bool,
74

            
75
    /// Produce a multi-line format displaying all of the information
76
    /// described for the -s option as well as the user's home directory,
77
    /// home phone number, login shell, mail status, and the contents of
78
    /// the files .forward, .plan, .project and .pubkey from the user's home directory.
79
    ///
80
    /// If idle time is at least a minute and less than a day, it is presented in the form ``hh:mm''.
81
    /// Idle times greater than a day are presented as ``d day[s]hh:mm''
82
    ///
83
    /// Phone numbers specified as eleven digits are printed as ``+N-NNN-NNN-NNNN''.
84
    /// Numbers specified as ten or seven digits are printed as the appropriate subset of that string.
85
    /// Numbers specified as five digits are printed as ``xN-NNNN''.
86
    /// Numbers specified as four digits are printed as ``xNNNN''.
87
    ///
88
    /// If write permission is denied to the device, the phrase ``(messages off)''
89
    /// is appended to the line containing the device name. One entry per user is displayed with the -l option;
90
    /// if a user is logged on multiple times, terminal information is repeated once per login.
91
    ///
92
    /// Mail status is shown as ``No Mail.'' if there is no mail at all,
93
    /// ``Mail last read DDD MMM ## HH:MM YYYY (TZ)'' if the person has looked at their mailbox since new mail arriving,
94
    /// or ``New mail received ...'', ``Unread since ...'' if they have new mail.
95
    #[arg(long, short, conflicts_with = "short")]
96
    long: bool,
97

            
98
    /// Prevent the -l option of finger from displaying the contents of
99
    /// the .forward, .plan, .project and .pubkey files.
100
    #[arg(long, short, requires = "long")]
101
    prevent_files: bool,
102

            
103
    /// Prevent matching of user names. User is usually a login name;
104
    /// however, matching will also be done on the users' real names,
105
    /// unless the -m option is supplied. All name matching performed by finger is case insensitive.
106
    #[arg(long, short = 'm')]
107
    no_name_match: bool,
108

            
109
    /// Output in JSON format
110
    #[arg(long, short)]
111
    json: bool,
112

            
113
    /// Generate shell completion scripts for the specified shell
114
    /// and print them to stdout.
115
    #[arg(long, value_enum, hide = true)]
116
    completions: Option<Shell>,
117

            
118
    users: Vec<String>,
119
}
120

            
121
#[tokio::main]
122
async fn main() -> anyhow::Result<()> {
123
    let args = Args::parse();
124

            
125
    if let Some(shell) = args.completions {
126
        generate(shell, &mut Args::command(), "rwho", &mut std::io::stdout());
127
        return Ok(());
128
    }
129

            
130
    let mut conn = zlink::unix::connect("/run/roowho2/roowho2.varlink")
131
        .await
132
        .expect("Failed to connect to fingerd server");
133

            
134
    let reply = conn
135
        .finger(args.users)
136
        .await
137
        .context("Failed to send finger request")?
138
        .map_err(|e| anyhow::anyhow!("Server returned an error for finger request: {:?}", e))?;
139

            
140
    if args.json {
141
        println!("{}", serde_json::to_string_pretty(&reply).unwrap());
142
    } else {
143
        for user in reply {
144
            println!("{:#?}", user.unwrap());
145
        }
146
    }
147

            
148
    Ok(())
149
}