1
use std::{os::fd::OwnedFd, time::Duration};
2

            
3
use anyhow::Context;
4
use itertools::Itertools;
5
use serde::{Deserialize, Serialize};
6
use tokio::time::timeout;
7
use zlink::{ReplyError, service::MethodReply};
8

            
9
use crate::{
10
    proto::{WhodStatusUpdate, WhodUserEntry, finger_protocol::FingerResponseUserEntry},
11
    server::{
12
        fingerd::{self, FingerRequestInfo, FingerRequestNetworking, finger_utmp_users},
13
        ignore_list::IgnoreList,
14
        rwhod::RwhodStatusStore,
15
    },
16
};
17

            
18
// Types for 'no.ntnu.pvv.roowho2.rwhod'
19

            
20
#[zlink::proxy("no.ntnu.pvv.roowho2.rwhod")]
21
pub trait VarlinkRwhodClientProxy {
22
    async fn rwho(
23
        &mut self,
24
        all: bool,
25
    ) -> zlink::Result<Result<VarlinkRwhoResponse, VarlinkRwhodClientError>>;
26

            
27
    async fn ruptime(
28
        &mut self,
29
        all: bool,
30
    ) -> zlink::Result<Result<VarlinkRuptimeResponse, VarlinkRwhodClientError>>;
31
}
32

            
33
#[derive(Debug, Deserialize)]
34
#[serde(tag = "method", content = "parameters")]
35
pub enum VarlinkRwhodClientRequest {
36
    #[serde(rename = "no.ntnu.pvv.roowho2.rwhod.Rwho")]
37
    Rwho {
38
        /// Retrieve all users, even those that have been idle for a long time.
39
        all: bool,
40
    },
41

            
42
    #[serde(rename = "no.ntnu.pvv.roowho2.rwhod.Ruptime")]
43
    Ruptime {
44
        /// Count all users, even those that have been idle for a long time.
45
        all: bool,
46
    },
47
}
48

            
49
#[derive(Debug, Clone, PartialEq, Serialize)]
50
#[serde(untagged)]
51
pub enum VarlinkRwhodClientResponse {
52
    Rwho(VarlinkRwhoResponse),
53
    Ruptime(VarlinkRuptimeResponse),
54
}
55

            
56
pub type VarlinkRwhoResponse = Vec<(String, WhodUserEntry)>;
57
pub type VarlinkRuptimeResponse = Vec<WhodStatusUpdate>;
58

            
59
#[derive(Debug, Clone, PartialEq, ReplyError)]
60
#[zlink(interface = "no.ntnu.pvv.roowho2.rwhod")]
61
pub enum VarlinkRwhodClientError {
62
    InvalidRequest,
63
    TimedOut,
64
    Disabled,
65
}
66

            
67
// Types for 'no.ntnu.pvv.roowho2.finger'
68

            
69
#[zlink::proxy("no.ntnu.pvv.roowho2.finger")]
70
pub trait VarlinkFingerClientProxy {
71
    async fn finger(
72
        &mut self,
73
        user_queries: Option<Vec<String>>,
74
        match_fullnames: bool,
75
        request_info: FingerRequestInfo,
76
        request_networking: FingerRequestNetworking,
77
        disable_user_account_db: bool,
78
        raw_remote_output: bool,
79
    ) -> zlink::Result<Result<VarlinkFingerResponse, VarlinkFingerClientError>>;
80
}
81

            
82
#[derive(Debug, Deserialize)]
83
#[serde(tag = "method", content = "parameters")]
84
pub enum VarlinkFingerClientRequest {
85
    #[serde(rename = "no.ntnu.pvv.roowho2.finger.Finger")]
86
    Finger {
87
        user_queries: Option<Vec<String>>,
88
        match_fullnames: bool,
89
        request_info: FingerRequestInfo,
90
        request_networking: FingerRequestNetworking,
91
        disable_user_account_db: bool,
92
        raw_remote_output: bool,
93
    },
94
}
95

            
96
#[derive(Debug, Serialize)]
97
#[serde(untagged)]
98
pub enum VarlinkFingerClientResponse {
99
    Finger(VarlinkFingerResponse),
100
}
101

            
102
pub type VarlinkFingerResponse = Vec<FingerResponseUserEntry>;
103

            
104
#[derive(Debug, Clone, PartialEq, ReplyError)]
105
#[zlink(interface = "no.ntnu.pvv.roowho2.finger")]
106
pub enum VarlinkFingerClientError {
107
    InvalidRequest,
108
    TimedOut,
109
    Disabled,
110
}
111

            
112
// --------------------
113

            
114
#[derive(Debug, Deserialize)]
115
#[serde(untagged)]
116
#[allow(unused)]
117
pub enum VarlinkMethod {
118
    Rwhod(VarlinkRwhodClientRequest),
119
    Finger(VarlinkFingerClientRequest),
120
}
121

            
122
#[derive(Debug, Serialize)]
123
#[serde(untagged)]
124
#[allow(unused)]
125
pub enum VarlinkReply {
126
    Rwhod(VarlinkRwhodClientResponse),
127
    Finger(VarlinkFingerClientResponse),
128
}
129

            
130
#[derive(Debug, Clone, PartialEq, Serialize)]
131
#[serde(untagged)]
132
#[allow(unused)]
133
pub enum VarlinkReplyError {
134
    Rwhod(VarlinkRwhodClientError),
135
    Finger(VarlinkFingerClientError),
136
}
137

            
138
#[derive(Debug, Clone)]
139
pub struct VarlinkRoowhoo2ClientServer {
140
    whod_status_store: RwhodStatusStore,
141
    rwhod_enabled: bool,
142
    fingerd_enabled: bool,
143
    finger_ignore_list: Option<IgnoreList>,
144
}
145

            
146
impl VarlinkRoowhoo2ClientServer {
147
    pub fn new(
148
        whod_status_store: RwhodStatusStore,
149
        rwhod_enabled: bool,
150
        fingerd_enabled: bool,
151
        finger_ignore_list: Option<IgnoreList>,
152
    ) -> Self {
153
        Self {
154
            whod_status_store,
155
            rwhod_enabled,
156
            fingerd_enabled,
157
            finger_ignore_list,
158
        }
159
    }
160
}
161

            
162
impl VarlinkRoowhoo2ClientServer {
163
    async fn handle_rwho_request(&self, all: bool) -> VarlinkRwhoResponse {
164
        tracing::debug!(all, "Handling Rwho request");
165
        let store = self.whod_status_store.read().await;
166

            
167
        let mut all_user_entries = Vec::with_capacity(store.len());
168
        for status_update in store.values() {
169
            all_user_entries.extend(
170
                status_update
171
                    .users
172
                    .iter()
173
                    .filter(|user| all || user.idle_time < chrono::Duration::hours(1))
174
                    .cloned()
175
                    .map(|user| (status_update.hostname.clone(), user)),
176
            );
177
        }
178

            
179
        all_user_entries
180
    }
181

            
182
    async fn handle_ruptime_request(&self, all: bool) -> VarlinkRuptimeResponse {
183
        tracing::debug!(all, "Handling Ruptime request");
184
        let store = self.whod_status_store.read().await;
185

            
186
        store
187
            .values()
188
            .cloned()
189
            .map(|mut status_update| {
190
                if !all {
191
                    status_update
192
                        .users
193
                        .retain(|user| user.idle_time < chrono::Duration::hours(1));
194
                }
195
                status_update
196
            })
197
            .collect()
198
    }
199

            
200
    async fn handle_finger_request(
201
        &self,
202
        user_queries: Option<Vec<String>>,
203
        match_fullnames: bool,
204
        request_info: FingerRequestInfo,
205
        _request_networking: FingerRequestNetworking,
206
        _disable_user_account_db: bool,
207
        _raw_remote_output: bool,
208
    ) -> VarlinkFingerResponse {
209
        tracing::debug!(
210
          user_queries = ?user_queries,
211
          match_fullnames = match_fullnames,
212
          request_info = ?request_info,
213
          "Handling Finger request",
214
        );
215
        match user_queries {
216
            Some(usernames) => usernames
217
                .into_iter()
218
                .flat_map::<Vec<_>, _>(|username| {
219
                    fingerd::search_for_user(
220
                        &username,
221
                        match_fullnames,
222
                        &request_info,
223
                        self.finger_ignore_list.as_ref(),
224
                    )
225
                    .into_iter()
226
                    .map(|res| (username.clone(), res))
227
                    .collect()
228
                })
229
                .dedup_by(|a, b| match (&a.1, &b.1) {
230
                    (Ok(user_a), Ok(user_b)) => user_a.username == user_b.username,
231
                    _ => false,
232
                })
233
                .filter_map(|(username, user)| match user {
234
                    Ok(user_info) => Some(user_info),
235
                    Err(err) => {
236
                        tracing::error!(
237
                            "Error retrieving local user information for '{}': {}",
238
                            username,
239
                            err
240
                        );
241
                        None
242
                    }
243
                })
244
                .map(Box::new)
245
                .map(FingerResponseUserEntry::Structured)
246
                .collect(),
247
            None => finger_utmp_users(&request_info, self.finger_ignore_list.as_ref())
248
                .into_iter()
249
                .filter_map(|res| match res {
250
                    Ok(user_info) => Some(user_info),
251
                    Err(err) => {
252
                        tracing::error!("Error retrieving local user information: {}", err);
253
                        None
254
                    }
255
                })
256
                .map(Box::new)
257
                .map(FingerResponseUserEntry::Structured)
258
                .collect(),
259
        }
260
    }
261
}
262

            
263
impl zlink::Service<zlink::unix::Stream> for VarlinkRoowhoo2ClientServer {
264
    type MethodCall<'de> = VarlinkMethod;
265
    type ReplyParams<'se> = VarlinkReply;
266
    type ReplyStreamParams = ();
267
    type ReplyStream = futures_util::stream::Empty<(zlink::Reply<()>, Vec<OwnedFd>)>;
268
    type ReplyError<'se> = VarlinkReplyError;
269

            
270
    async fn handle<'service>(
271
        &'service mut self,
272
        call: &'service zlink::Call<Self::MethodCall<'_>>,
273
        _conn: &mut zlink::Connection<zlink::unix::Stream>,
274
        _fds: Vec<std::os::fd::OwnedFd>,
275
    ) -> zlink::service::HandleResult<
276
        Self::ReplyParams<'service>,
277
        Self::ReplyStream,
278
        Self::ReplyError<'service>,
279
    > {
280
        match call.method() {
281
            VarlinkMethod::Rwhod(VarlinkRwhodClientRequest::Rwho { all }) => {
282
                if !self.rwhod_enabled {
283
                    return (
284
                        MethodReply::Error(VarlinkReplyError::Rwhod(
285
                            VarlinkRwhodClientError::Disabled,
286
                        )),
287
                        Default::default(),
288
                    );
289
                }
290

            
291
                let result =
292
                    match timeout(Duration::from_secs(2), self.handle_rwho_request(*all)).await {
293
                        Ok(response) => response,
294
                        Err(_) => {
295
                            tracing::error!("Rwho request timed out after 2 seconds");
296
                            return (
297
                                MethodReply::Error(VarlinkReplyError::Rwhod(
298
                                    VarlinkRwhodClientError::TimedOut,
299
                                )),
300
                                Default::default(),
301
                            );
302
                        }
303
                    };
304

            
305
                (
306
                    MethodReply::Single(Some(VarlinkReply::Rwhod(
307
                        VarlinkRwhodClientResponse::Rwho(result),
308
                    ))),
309
                    Default::default(),
310
                )
311
            }
312
            VarlinkMethod::Rwhod(VarlinkRwhodClientRequest::Ruptime { all }) => {
313
                if !self.rwhod_enabled {
314
                    return (
315
                        MethodReply::Error(VarlinkReplyError::Rwhod(
316
                            VarlinkRwhodClientError::Disabled,
317
                        )),
318
                        Default::default(),
319
                    );
320
                }
321

            
322
                let result = match timeout(
323
                    Duration::from_secs(2),
324
                    self.handle_ruptime_request(*all),
325
                )
326
                .await
327
                {
328
                    Ok(response) => response,
329
                    Err(_) => {
330
                        tracing::error!("Ruptime request timed out after 2 seconds");
331
                        return (
332
                            MethodReply::Error(VarlinkReplyError::Rwhod(
333
                                VarlinkRwhodClientError::TimedOut,
334
                            )),
335
                            Default::default(),
336
                        );
337
                    }
338
                };
339

            
340
                (
341
                    MethodReply::Single(Some(VarlinkReply::Rwhod(
342
                        VarlinkRwhodClientResponse::Ruptime(result),
343
                    ))),
344
                    Default::default(),
345
                )
346
            }
347
            VarlinkMethod::Finger(VarlinkFingerClientRequest::Finger {
348
                user_queries,
349
                match_fullnames,
350
                request_info,
351
                request_networking,
352
                disable_user_account_db,
353
                raw_remote_output,
354
            }) => {
355
                if !self.fingerd_enabled {
356
                    return (
357
                        MethodReply::Error(VarlinkReplyError::Finger(
358
                            VarlinkFingerClientError::Disabled,
359
                        )),
360
                        Default::default(),
361
                    );
362
                }
363

            
364
                let result = match timeout(
365
                    Duration::from_secs(2),
366
                    self.handle_finger_request(
367
                        user_queries.clone(),
368
                        *match_fullnames,
369
                        request_info.clone(),
370
                        request_networking.clone(),
371
                        *disable_user_account_db,
372
                        *raw_remote_output,
373
                    ),
374
                )
375
                .await
376
                {
377
                    Ok(response) => response,
378
                    Err(_) => {
379
                        tracing::error!("Finger request timed out after 2 seconds");
380
                        return (
381
                            MethodReply::Error(VarlinkReplyError::Finger(
382
                                VarlinkFingerClientError::TimedOut,
383
                            )),
384
                            Default::default(),
385
                        );
386
                    }
387
                };
388

            
389
                (
390
                    MethodReply::Single(Some(VarlinkReply::Finger(
391
                        VarlinkFingerClientResponse::Finger(result),
392
                    ))),
393
                    Default::default(),
394
                )
395
            }
396
        }
397
    }
398
}
399

            
400
pub async fn varlink_client_server_task(
401
    socket: zlink::unix::Listener,
402
    whod_status_store: RwhodStatusStore,
403
    rwhod_enabled: bool,
404
    fingerd_enabled: bool,
405
    finger_ignore_list: Option<IgnoreList>,
406
) -> anyhow::Result<()> {
407
    let service = VarlinkRoowhoo2ClientServer::new(
408
        whod_status_store,
409
        rwhod_enabled,
410
        fingerd_enabled,
411
        finger_ignore_list,
412
    );
413

            
414
    let server = zlink::Server::new(socket, service);
415

            
416
    tracing::info!("Starting Rwhod client API server");
417

            
418
    server
419
        .run()
420
        .await
421
        .context("Rwhod client API server failed")?;
422

            
423
    Ok(())
424
}