• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

unrenamed / chatd / 10022794800

20 Jul 2024 08:09PM UTC coverage: 43.41% (+6.4%) from 36.987%
10022794800

push

github

unrenamed
test: Message struct

1077 of 2481 relevant lines covered (43.41%)

1.01 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/server/session_workflow/command_exec.rs
1
use async_trait::async_trait;
2
use std::io::Write;
3

4
use crate::auth::{Auth, BanAttribute, BanQuery};
5
use crate::chat::message::Message;
6
use crate::chat::{
7
    format_commands, message, ChatRoom, Command, OplistCommand, OplistLoadMode, Theme,
8
    TimestampMode, User, UserName, UserStatus, WhitelistCommand, WhitelistLoadMode,
9
    VISIBLE_NOOP_CHAT_COMMANDS, VISIBLE_OPLIST_COMMANDS, VISIBLE_OP_CHAT_COMMANDS,
10
    VISIBLE_WHITELIST_COMMANDS,
11
};
12
use crate::terminal::{CloseHandle, Terminal};
13
use crate::utils::{self, sanitize};
14

15
use super::handler::WorkflowHandler;
16
use super::WorkflowContext;
17

18
pub struct CommandExecutor<H>
19
where
20
    H: Clone + Write + CloseHandle + Send,
21
{
22
    next: Option<Box<dyn WorkflowHandler<H>>>,
23
}
24

25
impl<H> CommandExecutor<H>
26
where
27
    H: Clone + Write + CloseHandle + Send,
28
{
29
    pub fn new() -> Self {
×
30
        Self { next: None }
31
    }
32
}
33

34
#[async_trait]
35
impl<H> WorkflowHandler<H> for CommandExecutor<H>
36
where
37
    H: Clone + Write + CloseHandle + Send,
38
{
39
    async fn handle(
40
        &mut self,
41
        context: &mut WorkflowContext,
42
        terminal: &mut Terminal<H>,
43
        room: &mut ChatRoom,
44
        auth: &mut Auth,
45
    ) -> anyhow::Result<()> {
46
        let command = match &context.command {
×
47
            Some(command) => command,
×
48
            None => return Ok(()),
×
49
        };
50
        let user = context.user.clone();
×
51
        let username = &user.username();
×
52

53
        match command {
×
54
            Command::Exit => {
×
55
                let member = room.find_member(username);
×
56
                member.exit()?;
×
57
            }
58
            Command::Away(reason) => {
×
59
                let member = room.find_member_mut(username);
×
60
                member.user.go_away(reason.to_string());
×
61

62
                let message = message::Emote::new(
63
                    member.user.clone().into(),
×
64
                    format!("has gone away: \"{}\"", reason),
×
65
                );
66
                room.send_message(message.into()).await?;
×
67
            }
68
            Command::Back => {
×
69
                let member = room.find_member_mut(username);
×
70
                if let UserStatus::Away {
×
71
                    reason: _,
×
72
                    since: _,
×
73
                } = &member.user.status()
×
74
                {
75
                    member.user.return_active();
×
76
                    let message =
×
77
                        message::Emote::new(member.user.clone().into(), "is back".to_string());
×
78
                    room.send_message(message.into()).await?;
×
79
                }
80
            }
81
            Command::Name(new_name) => 'label: {
×
82
                let member = room.find_member_mut(username);
×
83
                let user = member.user.clone();
×
84
                let new_name = sanitize::name(&new_name);
×
85
                let new_username = UserName::from(&new_name);
×
86

87
                if user.username() == &new_username {
×
88
                    let message = message::Error::new(
89
                        user.into(),
×
90
                        "new name is the same as the original".to_string(),
×
91
                    );
92
                    room.send_message(message.into()).await?;
×
93
                    break 'label;
×
94
                }
95

96
                if let Some(_) = room.try_find_member(&new_username) {
×
97
                    let message = message::Error::new(
98
                        user.into(),
×
99
                        format!("\"{}\" name is already taken", new_username),
×
100
                    );
101
                    room.send_message(message.into()).await?;
×
102
                    break 'label;
×
103
                }
104

105
                let message = message::Announce::new(
106
                    user.clone().into(),
×
107
                    format!("user is now known as {}.", new_username),
×
108
                );
109
                room.send_message(message.into()).await?;
×
110

111
                let old_name = user.username();
×
112
                let user_id = user.id();
×
113

114
                let member = room.find_member_mut(username);
×
115
                member.user.set_username(new_username.clone());
×
116
                terminal.set_prompt(&member.user.config().display_name());
×
117

118
                let member = member.clone();
×
119
                room.add_member(new_username.clone(), member);
×
120
                room.remove_member(&old_name);
×
121
                room.add_name(user_id, new_username);
×
122
            }
123
            Command::Msg(to_username, msg) => 'label: {
×
124
                let from = room.find_member(username).user.clone();
×
125
                let to_username = UserName::from(to_username);
×
126

127
                match room.try_find_member_mut(&to_username).map(|a| &mut a.user) {
×
128
                    None => {
×
129
                        let message =
×
130
                            message::Error::new(from.into(), format!("user is not found"));
×
131
                        room.send_message(message.into()).await?;
×
132
                        break 'label;
×
133
                    }
134
                    Some(to) if from.id().eq(&to.id()) => {
×
135
                        let message =
×
136
                            message::Error::new(from.into(), format!("you can't message yourself"));
×
137
                        room.send_message(message.into()).await?;
×
138
                        break 'label;
×
139
                    }
140
                    Some(to) => {
×
141
                        let status = to.status().clone();
×
142
                        let name = to.username().clone();
×
143

144
                        to.set_reply_to(from.id());
×
145

146
                        let message = message::Private::new(
147
                            from.clone().into(),
×
148
                            to.clone().into(),
×
149
                            msg.to_string(),
×
150
                        );
151
                        room.send_message(message.into()).await?;
×
152

153
                        match status {
×
154
                            UserStatus::Away { reason, since: _ } => {
×
155
                                let message = message::System::new(
156
                                    from.into(),
×
157
                                    format!(
×
158
                                        "Sent PM to {}, but they're away now: {}",
×
159
                                        name, reason
×
160
                                    ),
161
                                );
162
                                room.send_message(message.into()).await?;
×
163
                            }
164
                            UserStatus::Active => {}
×
165
                        }
166
                    }
167
                }
168
            }
169
            Command::Reply(message_body) => 'label: {
×
170
                let member = room.find_member(username);
×
171
                let from = member.user.clone();
×
172

173
                if from.reply_to().is_none() {
×
174
                    let message =
×
175
                        message::Error::new(from.into(), "no message to reply to".to_string());
×
176
                    room.send_message(message.into()).await?;
×
177
                    break 'label;
×
178
                }
179

180
                let target_id = &from.reply_to().unwrap();
×
181
                let target_name = room.try_get_name(&target_id);
×
182
                if target_name.is_none() {
×
183
                    let message =
×
184
                        message::Error::new(from.into(), "user already left the room".to_string());
×
185
                    room.send_message(message.into()).await?;
×
186
                    break 'label;
×
187
                }
188

189
                let member = room.find_member(target_name.unwrap());
×
190
                let to = member.user.clone();
×
191
                let message =
×
192
                    message::Private::new(from.into(), to.into(), (*message_body).to_string());
×
193
                room.send_message(message.into()).await?;
×
194
            }
195
            Command::Users => {
×
196
                let member = room.find_member(username);
×
197
                let user = member.user.clone();
×
198

199
                let mut usernames = room.names().values().collect::<Vec<&UserName>>();
×
200
                usernames.sort_by_key(|a| a.to_lowercase());
×
201

202
                let colorized_names = usernames
×
203
                    .iter()
204
                    .map(|u| user.config().theme().style_username(u).to_string())
×
205
                    .collect::<Vec<String>>();
206

207
                let body = format!(
×
208
                    "{} connected: {}",
209
                    room.names().len(),
×
210
                    colorized_names.join(", ")
×
211
                );
212

213
                let message = message::System::new(user.into(), body);
×
214
                room.send_message(message.into()).await?;
×
215
            }
216
            Command::Whois(target_username) => {
×
217
                let member = room.find_member(username);
×
218
                let user = member.user.clone();
×
219
                let target_username = UserName::from(target_username);
×
220
                let message = match room
×
221
                    .try_find_member(&target_username)
×
222
                    .map(|member| &member.user)
×
223
                {
224
                    Some(target) => message::System::new(user.into(), target.to_string()).into(),
×
225
                    None => message::Error::new(user.into(), "user not found".to_string()).into(),
×
226
                };
227
                room.send_message(message).await?;
×
228
            }
229
            Command::Slap(target_username) => 'label: {
×
230
                let member = room.find_member(username);
×
231
                let user = member.user.clone();
×
232

233
                if target_username.is_none() {
×
234
                    let message = message::Emote::new(
235
                        user.into(),
×
236
                        "hits himself with a squishy banana.".to_string(),
×
237
                    );
238
                    room.send_message(message.into()).await?;
×
239
                    break 'label;
×
240
                }
241

242
                let target_username = UserName::from(target_username.as_deref().unwrap());
×
243
                let target = room
×
244
                    .try_find_member_mut(&target_username)
×
245
                    .map(|member| &member.user);
×
246

247
                let message = if let Some(t) = target {
×
248
                    message::Emote::new(
249
                        user.into(),
×
250
                        format!("hits {} with a squishy banana.", t.username()),
×
251
                    )
252
                    .into()
253
                } else {
254
                    message::Error::new(
255
                        user.into(),
×
256
                        "that slippin' monkey not in the room".to_string(),
×
257
                    )
258
                    .into()
259
                };
260
                room.send_message(message).await?;
×
261
            }
262
            Command::Shrug => {
×
263
                let member = room.find_member(username);
×
264
                let user = member.user.clone();
×
265
                let message = message::Emote::new(user.into(), "¯\\_(◕‿◕)_/¯".to_string());
×
266
                room.send_message(message.into()).await?;
×
267
            }
268
            Command::Me(action) => {
×
269
                let member = room.find_member(username);
×
270
                let user = member.user.clone();
×
271
                let message = message::Emote::new(
272
                    user.into(),
×
273
                    match action {
×
274
                        Some(s) => format!("{}", s),
×
275
                        None => format!("is at a loss for words."),
×
276
                    },
277
                );
278
                room.send_message(message.into()).await?;
×
279
            }
280
            Command::Help => {
×
281
                let member = room.find_member(username);
×
282
                let user = member.user.clone();
×
283

284
                let mut help = format!("Available commands: {}", utils::NEWLINE);
×
285
                help.push_str(&format_commands(&VISIBLE_NOOP_CHAT_COMMANDS));
×
286

287
                if auth.is_op(&user.public_key().clone().into()) {
×
288
                    help.push_str(&format!(
×
289
                        "{}{}Operator commands: {}{}",
×
290
                        utils::NEWLINE,
×
291
                        utils::NEWLINE,
×
292
                        utils::NEWLINE,
×
293
                        &format_commands(&VISIBLE_OP_CHAT_COMMANDS)
×
294
                    ));
295
                }
296

297
                let message = message::System::new(user.into(), help);
×
298
                room.send_message(message.into()).await?;
×
299
            }
300
            Command::Quiet => {
×
301
                let member = room.find_member_mut(username);
×
302
                member.user.config_mut().switch_quiet_mode();
×
303
                let message = message::System::new(
304
                    member.user.clone().into(),
×
305
                    match member.user.config().quiet() {
×
306
                        true => "Quiet mode is toggled ON",
×
307
                        false => "Quiet mode is toggled OFF",
×
308
                    }
309
                    .to_string(),
×
310
                );
311
                room.send_message(message.into()).await?;
×
312
            }
313
            Command::Timestamp(mode) => {
×
314
                let member = room.find_member_mut(username);
×
315
                member.user.config_mut().set_timestamp_mode(*mode);
×
316
                let message = message::System::new(
317
                    member.user.clone().into(),
×
318
                    match member.user.config().timestamp_mode() {
×
319
                        TimestampMode::Time | TimestampMode::DateTime => {
×
320
                            "Timestamp is toggled ON, timezone is UTC"
×
321
                        }
322
                        TimestampMode::Off => "Timestamp is toggled OFF",
×
323
                    }
324
                    .to_string(),
×
325
                );
326
                room.send_message(message.into()).await?;
×
327
            }
328
            Command::Theme(theme) => {
×
329
                let member = room.find_member_mut(username);
×
330
                let message = message::System::new(user.into(), format!("Set theme: {}", theme));
×
331
                member.user.set_theme((*theme).into());
×
332
                terminal.set_prompt(&member.user.config().display_name());
×
333
                room.send_message(message.into()).await?;
×
334
            }
335
            Command::Themes => {
×
336
                let member = room.find_member(username);
×
337
                let user = member.user.clone();
×
338
                let message = message::System::new(
339
                    user.into(),
×
340
                    format!("Supported themes: {}", Theme::values().join(", ")),
×
341
                );
342
                room.send_message(message.into()).await?;
×
343
            }
344
            Command::Ignore(target) => 'label: {
×
345
                let member = room.find_member(username);
×
346
                let user = member.user.clone();
×
347

348
                if target.is_none() {
×
349
                    let ignored_usernames: Vec<String> = user
×
350
                        .ignored()
351
                        .iter()
352
                        .filter_map(|id| room.try_get_name(id))
×
353
                        .map(|name| user.config().theme().style_username(name).to_string())
×
354
                        .collect();
355

356
                    let message_text = match ignored_usernames.is_empty() {
×
357
                        true => "0 users ignored".to_string(),
×
358
                        false => format!(
×
359
                            "{} users ignored: {}",
360
                            ignored_usernames.len(),
×
361
                            ignored_usernames.join(", ")
×
362
                        ),
363
                    };
364

365
                    let message = message::System::new(user.into(), message_text);
×
366
                    room.send_message(message.into()).await?;
×
367
                    break 'label;
×
368
                }
369

370
                let target_username = UserName::from(target.as_deref().unwrap());
×
371
                match room
×
372
                    .try_find_member(&target_username)
×
373
                    .map(|a| a.user.id().clone())
×
374
                {
375
                    Some(target_id) if target_id == user.id() => {
×
376
                        let message = message::Error::new(
377
                            user.into(),
×
378
                            "you can't ignore yourself".to_string(),
×
379
                        );
380
                        room.send_message(message.into()).await?;
×
381
                        break 'label;
×
382
                    }
383
                    Some(target_id) if user.ignored().contains(&target_id) => {
×
384
                        let message = message::System::new(
385
                            user.into(),
×
386
                            format!("user already in the ignored list"),
×
387
                        );
388
                        room.send_message(message.into()).await?;
×
389
                        break 'label;
×
390
                    }
391
                    None => {
×
392
                        let message =
×
393
                            message::Error::new(user.into(), "user not found".to_string());
×
394
                        room.send_message(message.into()).await?;
×
395
                        break 'label;
×
396
                    }
397
                    Some(target_id) => {
×
398
                        room.find_member_mut(username).user.ignore(target_id);
×
399
                        let message = message::System::new(
400
                            user.into(),
×
401
                            format!("Ignoring: {}", target_username),
×
402
                        );
403
                        room.send_message(message.into()).await?;
×
404
                    }
405
                }
406
            }
407
            Command::Unignore(target_username) => 'label: {
×
408
                let member = room.find_member(username);
×
409
                let user = member.user.clone();
×
410

411
                let target_username = UserName::from(target_username);
×
412
                match room
×
413
                    .try_find_member(&target_username)
×
414
                    .map(|a| a.user.id().clone())
×
415
                {
416
                    None => {
×
417
                        let message =
×
418
                            message::Error::new(user.into(), "user not found".to_string());
×
419
                        room.send_message(message.into()).await?;
×
420
                        break 'label;
×
421
                    }
422
                    Some(target_id) if !user.ignored().contains(&target_id) => {
×
423
                        let message = message::Error::new(
424
                            user.into(),
×
425
                            "user not in the ignored list yet".to_string(),
×
426
                        );
427
                        room.send_message(message.into()).await?;
×
428
                        break 'label;
×
429
                    }
430
                    Some(target_id) => {
×
431
                        room.find_member_mut(username).user.unignore(&target_id);
×
432
                        let message = message::System::new(
433
                            user.into(),
×
434
                            format!("No longer ignoring: {}", target_username),
×
435
                        );
436
                        room.send_message(message.into()).await?;
×
437
                    }
438
                }
439
            }
440
            Command::Focus(target) => 'label: {
×
441
                let member = room.find_member(username);
×
442
                let user = member.user.clone();
×
443

444
                if target.is_none() {
×
445
                    let focused_usernames: Vec<String> = user
×
446
                        .focused()
447
                        .iter()
448
                        .filter_map(|id| room.try_get_name(id))
×
449
                        .map(|name| user.config().theme().style_username(name).to_string())
×
450
                        .collect();
451

452
                    let message_text = match focused_usernames.is_empty() {
×
453
                        true => "Focusing no users".to_string(),
×
454
                        false => format!(
×
455
                            "Focusing on {} users: {}",
456
                            focused_usernames.len(),
×
457
                            focused_usernames.join(", ")
×
458
                        ),
459
                    };
460

461
                    let message = message::System::new(user.into(), message_text);
×
462
                    room.send_message(message.into()).await?;
×
463
                    break 'label;
×
464
                }
465

466
                let target = target.as_deref().unwrap();
×
467
                if target == "$" {
×
468
                    room.find_member_mut(username).user.unfocus_all();
×
469
                    let message = message::System::new(
470
                        user.into(),
×
471
                        "Removed focus from all users".to_string(),
×
472
                    );
473
                    room.send_message(message.into()).await?;
×
474
                    break 'label;
×
475
                }
476

477
                let mut focused = vec![];
×
478
                for target_username in target.split(",") {
×
479
                    let target_username = UserName::from(target_username);
×
480
                    match room
×
481
                        .try_find_member(&target_username)
×
482
                        .map(|a| a.user.id().clone())
×
483
                    {
484
                        None => continue,
×
485
                        Some(target_id) if target_id == user.id() => continue,
×
486
                        Some(target_id) if user.focused().contains(&target_id) => continue,
×
487
                        Some(target_id) => {
×
488
                            room.find_member_mut(username).user.focus(target_id);
×
489
                            focused.push(target_username);
×
490
                        }
491
                    }
492
                }
493

494
                let focused_usernames: Vec<String> = focused
×
495
                    .iter()
496
                    .map(|name| user.config().theme().style_username(name).to_string())
×
497
                    .collect();
498

499
                let message_text = match focused_usernames.is_empty() {
×
500
                    true => "No online users found to focus".to_string(),
×
501
                    false => format!(
×
502
                        "Focusing on {} users: {}",
503
                        focused_usernames.len(),
×
504
                        focused_usernames.join(", ")
×
505
                    ),
506
                };
507

508
                let message = message::System::new(user.into(), message_text);
×
509
                room.send_message(message.into()).await?;
×
510
            }
511
            Command::Version => {
×
512
                let message =
×
513
                    message::System::new(user.into(), format!("{}", env!("CARGO_PKG_VERSION")));
×
514
                room.send_message(message.into()).await?;
×
515
            }
516
            Command::Uptime => {
×
517
                let message = message::System::new(user.into(), room.uptime());
×
518
                room.send_message(message.into()).await?;
×
519
            }
520
            Command::Mute(target_username) => 'label: {
×
521
                if !auth.is_op(&user.public_key().clone().into()) {
×
522
                    let message =
×
523
                        message::Error::new(user.into(), "must be an operator".to_string());
×
524
                    room.send_message(message.into()).await?;
×
525
                    break 'label;
×
526
                }
527

528
                let target_username = UserName::from(target_username);
×
529
                match room
×
530
                    .try_find_member_mut(&target_username)
×
531
                    .map(|a| &mut a.user)
×
532
                {
533
                    None => {
×
534
                        let message =
×
535
                            message::Error::new(user.into(), "user not found".to_string());
×
536
                        room.send_message(message.into()).await?;
×
537
                        break 'label;
×
538
                    }
539
                    Some(target) if target.id() == user.id() => {
×
540
                        let message =
×
541
                            message::Error::new(user.into(), "you can't mute yourself".to_string());
×
542
                        room.send_message(message.into()).await?;
×
543
                        break 'label;
×
544
                    }
545
                    Some(target) => {
×
546
                        target.switch_mute_mode();
×
547
                        let target = target.clone();
×
548
                        let message = message::System::new(
549
                            user.into(),
×
550
                            format!(
×
551
                                "{}: {}, id = {}",
×
552
                                match target.is_muted() {
×
553
                                    true => "Muted",
×
554
                                    false => "Unmuted",
×
555
                                },
556
                                target.username(),
×
557
                                target.id()
×
558
                            ),
559
                        );
560
                        room.send_message(message.into()).await?;
×
561
                    }
562
                }
563
            }
564
            Command::Motd(new_motd) => 'label: {
×
565
                if new_motd.is_none() {
×
566
                    let message = message::System::new(user.into(), room.motd().clone());
×
567
                    room.send_message(message.into()).await?;
×
568
                    break 'label;
×
569
                }
570

571
                if !auth.is_op(&user.public_key().clone().into()) {
×
572
                    let message = message::Error::new(
573
                        user.into(),
×
574
                        "must be an operator to modify the MOTD".to_string(),
×
575
                    );
576
                    room.send_message(message.into()).await?;
×
577
                    break 'label;
×
578
                }
579

580
                room.set_motd(new_motd.as_deref().unwrap().to_string());
×
581

582
                let message = message::Announce::new(
583
                    user.into(),
×
584
                    format!(
×
585
                        "set new message of the day: {}-> {}",
×
586
                        utils::NEWLINE,
×
587
                        room.motd()
×
588
                    ),
589
                );
590
                room.send_message(message.into()).await?;
×
591
            }
592
            Command::Kick(target_username) => 'label: {
×
593
                if !auth.is_op(&user.public_key().clone().into()) {
×
594
                    let message =
×
595
                        message::Error::new(user.into(), "must be an operator".to_string());
×
596
                    room.send_message(message.into()).await?;
×
597
                    break 'label;
×
598
                }
599

600
                let target_username = UserName::from(target_username);
×
601
                match room.try_find_member_mut(&target_username) {
×
602
                    None => {
×
603
                        let message =
×
604
                            message::Error::new(user.into(), "user not found".to_string());
×
605
                        room.send_message(message.into()).await?;
×
606
                        break 'label;
×
607
                    }
608
                    Some(member) => {
×
609
                        let message = message::Announce::new(
610
                            user.into(),
×
611
                            format!("kicked {} from the server", target_username),
×
612
                        );
613
                        member.exit()?;
×
614
                        room.send_message(message.into()).await?;
×
615
                    }
616
                }
617
            }
618
            Command::Ban(query) => 'label: {
×
619
                if !auth.is_op(&user.public_key().clone().into()) {
×
620
                    let message =
×
621
                        message::Error::new(user.into(), "must be an operator".to_string());
×
622
                    room.send_message(message.into()).await?;
×
623
                    break 'label;
×
624
                }
625

626
                let query = query.parse::<BanQuery>();
×
627
                if let Err(err) = query {
×
628
                    let message = message::Error::new(user.into(), err.to_string());
×
629
                    room.send_message(message.into()).await?;
×
630
                    break 'label;
×
631
                }
632

633
                let mut messages: Vec<Message> = vec![];
×
634

635
                match query.unwrap() {
×
636
                    BanQuery::Single { name, duration } => {
×
637
                        let target_username = UserName::from(&name);
×
638
                        match room.try_find_member(&target_username) {
×
639
                            Some(member) => {
×
640
                                auth.ban_fingerprint(
×
641
                                    &member.user.public_key().fingerprint(),
×
642
                                    duration,
×
643
                                );
644
                                let message = message::Announce::new(
645
                                    user.clone().into(),
×
646
                                    format!("banned {} from the server", member.user.username()),
×
647
                                );
648
                                member.exit()?;
×
649
                                messages.push(message.into());
×
650
                            }
651
                            None => {
×
652
                                let message =
×
653
                                    message::Error::new(user.into(), "user not found".to_string());
×
654
                                room.send_message(message.into()).await?;
×
655
                                break 'label;
×
656
                            }
657
                        }
658
                    }
659
                    BanQuery::Multiple(items) => {
×
660
                        for item in items {
×
661
                            match item.attribute {
×
662
                                BanAttribute::Name(name) => {
×
663
                                    let username = UserName::from(&name);
×
664
                                    auth.ban_username(&username, item.duration);
×
665

666
                                    for (_, member) in room.members_iter_mut() {
×
667
                                        if member.user.username().eq(&username) {
×
668
                                            let message = message::Announce::new(
669
                                                user.clone().into(),
×
670
                                                format!("banned {} from the server", username),
×
671
                                            );
672
                                            messages.push(message.into());
×
673
                                            member.exit()?;
×
674
                                        }
675
                                    }
676
                                }
677
                                BanAttribute::Fingerprint(fingerprint) => {
×
678
                                    auth.ban_fingerprint(&fingerprint, item.duration);
×
679

680
                                    for (_, member) in room.members_iter_mut() {
×
681
                                        if member.user.public_key().fingerprint().eq(&fingerprint) {
×
682
                                            let message = message::Announce::new(
683
                                                user.clone().into(),
×
684
                                                format!(
×
685
                                                    "banned {} from the server",
×
686
                                                    member.user.username()
×
687
                                                ),
688
                                            );
689
                                            messages.push(message.into());
×
690
                                            member.exit()?;
×
691
                                        }
692
                                    }
693
                                }
694
                                BanAttribute::Ip(_) => todo!(),
695
                            }
696
                        }
697
                    }
698
                }
699

700
                let message = message::System::new(
701
                    user.into(),
×
702
                    "Banning is complete. Offline users were silently banned.".to_string(),
×
703
                );
704
                messages.push(message.into());
×
705

706
                for message in messages {
×
707
                    room.send_message(message).await?;
×
708
                }
709
            }
710
            Command::Banned => 'label: {
×
711
                use std::fmt::Write;
712

713
                if !auth.is_op(&user.public_key().clone().into()) {
×
714
                    let message =
×
715
                        message::Error::new(user.into(), "must be an operator".to_string());
×
716
                    room.send_message(message.into()).await?;
×
717
                    break 'label;
×
718
                }
719

720
                let (names, fingerprints) = auth.banned();
×
721
                let mut banned = String::new();
×
722
                write!(banned, "Banned:").expect("Failed to write banned members to string");
×
723

724
                for name in names {
×
725
                    write!(banned, "{} \"name={}\"", utils::NEWLINE, name)
×
726
                        .expect("Failed to write banned members to string");
727
                }
728

729
                for fingerprint in fingerprints {
×
730
                    write!(banned, "{} \"fingerprint={}\"", utils::NEWLINE, fingerprint)
×
731
                        .expect("Failed to write banned members to string");
732
                }
733

734
                let message = message::System::new(user.into(), banned);
×
735
                room.send_message(message.into()).await?;
×
736
            }
737
            Command::Whitelist(command) => 'label: {
×
738
                if !auth.is_op(&user.public_key().clone().into()) {
×
739
                    let message =
×
740
                        message::Error::new(user.into(), "must be an operator".to_string());
×
741
                    room.send_message(message.into()).await?;
×
742
                    break 'label;
×
743
                }
744

745
                exec_whitelist_command(command, &user, room, auth).await?;
×
746
            }
747
            Command::Oplist(command) => 'label: {
×
748
                if !auth.is_op(&user.public_key().clone().into()) {
×
749
                    let message =
×
750
                        message::Error::new(user.into(), "must be an operator".to_string());
×
751
                    room.send_message(message.into()).await?;
×
752
                    break 'label;
×
753
                }
754

755
                exec_oplist_command(command, &user, room, auth).await?;
×
756
            }
757
        }
758

759
        Ok(())
×
760
    }
761

762
    fn next(&mut self) -> &mut Option<Box<dyn WorkflowHandler<H>>> {
×
763
        &mut self.next
×
764
    }
765
}
766

767
async fn exec_whitelist_command(
×
768
    command: &WhitelistCommand,
769
    user: &User,
770
    room: &mut ChatRoom,
771
    auth: &mut Auth,
772
) -> anyhow::Result<()> {
773
    match command {
×
774
        WhitelistCommand::On => {
775
            auth.enable_whitelist_mode();
×
776
            let message = message::System::new(
777
                user.into(),
×
778
                "Server whitelisting is now enabled".to_string(),
×
779
            );
780
            room.send_message(message.into()).await?;
×
781
        }
782
        WhitelistCommand::Off => {
783
            auth.disable_whitelist_mode();
×
784
            let message = message::System::new(
785
                user.into(),
×
786
                "Server whitelisting is now disabled".to_string(),
×
787
            );
788
            room.send_message(message.into()).await?;
×
789
        }
790
        WhitelistCommand::Add(users_or_keys) => {
×
791
            let mut invalid_keys = vec![];
×
792
            let mut invalid_users = vec![];
×
793

794
            let mut is_key = false;
×
795
            for user_or_key in users_or_keys.split_whitespace() {
×
796
                if user_or_key.starts_with("ssh-") {
×
797
                    is_key = true;
×
798
                    continue;
799
                }
800

801
                if is_key {
×
802
                    let key = user_or_key;
×
803
                    match russh_keys::parse_public_key_base64(&key) {
×
804
                        Ok(pk) => auth.add_trusted_key(pk.into()),
×
805
                        Err(_) => invalid_keys.push(key.to_string()),
×
806
                    }
807
                    is_key = false;
×
808
                } else {
809
                    let user = user_or_key;
810
                    let username = UserName::from(user);
×
811
                    match room.try_find_member(&username).map(|m| &m.user) {
×
812
                        Some(user) => auth.add_trusted_key(user.public_key().clone().into()),
×
813
                        None => invalid_users.push(user.to_string()),
×
814
                    }
815
                }
816
            }
817

818
            let mut messages = vec![];
×
819
            if !invalid_keys.is_empty() {
×
820
                messages.push(format!("Invalid keys: {}", invalid_keys.join(", ")));
×
821
            }
822
            if !invalid_users.is_empty() {
×
823
                messages.push(format!("Invalid users: {}", invalid_users.join(", ")));
×
824
            }
825
            if messages.is_empty() {
×
826
                messages.push(format!("Server whitelist is updated!"));
×
827
            }
828

829
            let message = message::System::new(user.into(), messages.join(utils::NEWLINE));
×
830
            room.send_message(message.into()).await?;
×
831
        }
832
        WhitelistCommand::Remove(users_or_keys) => {
×
833
            let mut invalid_keys = vec![];
×
834
            let mut invalid_users = vec![];
×
835

836
            let mut is_key = false;
×
837
            for user_or_key in users_or_keys.split_whitespace() {
×
838
                if user_or_key.starts_with("ssh-") {
×
839
                    is_key = true;
×
840
                    continue;
841
                }
842

843
                if is_key {
×
844
                    let key = user_or_key;
×
845
                    match russh_keys::parse_public_key_base64(&key) {
×
846
                        Ok(pk) => auth.remove_trusted_key(pk.into()),
×
847
                        Err(_) => invalid_keys.push(key.to_string()),
×
848
                    }
849
                    is_key = false;
×
850
                } else {
851
                    let user = user_or_key;
852
                    let username = UserName::from(user);
×
853
                    match room.try_find_member(&username).map(|m| &m.user) {
×
854
                        Some(user) => auth.remove_trusted_key(user.public_key().clone()),
×
855
                        None => invalid_users.push(user.to_string()),
×
856
                    }
857
                }
858
            }
859

860
            let mut messages = vec![];
×
861
            if !invalid_keys.is_empty() {
×
862
                messages.push(format!("Invalid keys: {}", invalid_keys.join(", ")));
×
863
            }
864
            if !invalid_users.is_empty() {
×
865
                messages.push(format!("Invalid users: {}", invalid_users.join(", ")));
×
866
            }
867
            if messages.is_empty() {
×
868
                messages.push(format!("Server whitelist is updated!"));
×
869
            }
870

871
            let message = message::System::new(user.into(), messages.join(utils::NEWLINE));
×
872
            room.send_message(message.into()).await?;
×
873
        }
874
        WhitelistCommand::Load(mode) => {
×
875
            if *mode == WhitelistLoadMode::Replace {
×
876
                auth.clear_trusted_keys();
×
877
            }
878
            let message: Message = match auth.load_trusted_keys() {
×
879
                Ok(_) => {
880
                    let body = "Trusted keys are up-to-date with the whitelist file".to_string();
×
881
                    message::System::new(user.into(), body).into()
×
882
                }
883
                Err(err) => {
×
884
                    let body = err.to_string();
×
885
                    message::Error::new(user.into(), body).into()
×
886
                }
887
            };
888
            room.send_message(message).await?;
×
889
        }
890
        WhitelistCommand::Save => {
891
            let message: Message = match auth.save_trusted_keys() {
×
892
                Ok(_) => {
893
                    let body = "Whitelist file is up-to-date with the trusted keys".to_string();
×
894
                    message::System::new(user.into(), body).into()
×
895
                }
896
                Err(err) => {
×
897
                    let body = err.to_string();
×
898
                    message::Error::new(user.into(), body).into()
×
899
                }
900
            };
901
            room.send_message(message).await?;
×
902
        }
903
        WhitelistCommand::Reverify => 'label: {
×
904
            if !auth.is_whitelist_enabled() {
×
905
                let message = message::System::new(
906
                    user.into(),
×
907
                    "Whitelist is disabled, so nobody will be kicked".to_string(),
×
908
                );
909
                room.send_message(message.into()).await?;
×
910
                break 'label;
911
            }
912

913
            let auth = auth;
914
            let mut kicked = vec![];
×
915
            for (_, member) in room.members_iter() {
×
916
                if !auth.is_trusted(&member.user.public_key()) {
×
917
                    kicked.push(member.user.clone());
×
918
                    member.exit()?;
×
919
                }
920
            }
921

922
            for user in kicked {
×
923
                let message = message::Announce::new(
924
                    user.into(),
×
925
                    "was kicked during pubkey reverification".to_string(),
×
926
                );
927
                room.send_message(message.into()).await?;
×
928
            }
929
        }
930
        WhitelistCommand::Status => {
931
            let auth = auth;
932
            let mut messages: Vec<String> = vec![];
×
933

934
            messages.push(
×
935
                String::from("Server whitelisting is ")
×
936
                    + match auth.is_whitelist_enabled() {
×
937
                        true => "enabled",
×
938
                        false => "disabled",
×
939
                    },
940
            );
941

942
            let mut trusted_online_users: Vec<String> = vec![];
×
943
            let mut trusted_keys = vec![];
×
944

945
            for key in auth.trusted_keys() {
×
946
                if let Some(user) = room
×
947
                    .members_iter()
948
                    .map(|(_, m)| &m.user)
×
949
                    .find(|u| u.public_key() == key)
×
950
                {
951
                    trusted_online_users.push(user.username().clone().into());
×
952
                } else {
953
                    trusted_keys.push(key.fingerprint());
×
954
                }
955
            }
956

957
            if !trusted_online_users.is_empty() {
×
958
                messages.push(format!(
×
959
                    "Trusted online users: {}",
960
                    trusted_online_users.join(", ")
×
961
                ));
962
            }
963

964
            if !trusted_keys.is_empty() {
×
965
                messages.push(format!("Trusted offline keys: {}", trusted_keys.join(", ")));
×
966
            }
967

968
            let message = message::System::new(user.into(), messages.join(utils::NEWLINE));
×
969
            room.send_message(message.into()).await?;
×
970
        }
971
        WhitelistCommand::Help => {
972
            let mut help = format!("Available commands: {}", utils::NEWLINE);
×
973
            help.push_str(&format_commands(&VISIBLE_WHITELIST_COMMANDS));
×
974

975
            let message = message::System::new(user.into(), help);
×
976
            room.send_message(message.into()).await?;
×
977
        }
978
    }
979

980
    Ok(())
×
981
}
982

983
async fn exec_oplist_command(
×
984
    command: &OplistCommand,
985
    user: &User,
986
    room: &mut ChatRoom,
987
    auth: &mut Auth,
988
) -> anyhow::Result<()> {
989
    match command {
×
990
        OplistCommand::Add(users_or_keys) => {
×
991
            let mut invalid_keys = vec![];
×
992
            let mut invalid_users = vec![];
×
993

994
            let mut is_key = false;
×
995
            for user_or_key in users_or_keys.split_whitespace() {
×
996
                if user_or_key.starts_with("ssh-") {
×
997
                    is_key = true;
×
998
                    continue;
999
                }
1000

1001
                if is_key {
×
1002
                    let key = user_or_key;
×
1003
                    match russh_keys::parse_public_key_base64(&key) {
×
1004
                        Ok(pk) => auth.add_operator(pk.into()),
×
1005
                        Err(_) => invalid_keys.push(key.to_string()),
×
1006
                    }
1007
                    is_key = false;
×
1008
                } else {
1009
                    let user = user_or_key;
1010
                    let username = UserName::from(user);
×
1011
                    match room.try_find_member(&username).map(|m| &m.user) {
×
1012
                        Some(user) => auth.add_operator(user.public_key().clone()),
×
1013
                        None => invalid_users.push(user.to_string()),
×
1014
                    }
1015
                }
1016
            }
1017

1018
            let mut messages = vec![];
×
1019
            if !invalid_keys.is_empty() {
×
1020
                messages.push(format!("Invalid keys: {}", invalid_keys.join(", ")));
×
1021
            }
1022
            if !invalid_users.is_empty() {
×
1023
                messages.push(format!("Invalid users: {}", invalid_users.join(", ")));
×
1024
            }
1025

1026
            if messages.is_empty() {
×
1027
                messages.push(format!("Server operators list is updated!"));
×
1028
            }
1029

1030
            let message = message::System::new(user.into(), messages.join(utils::NEWLINE));
×
1031
            room.send_message(message.into()).await?;
×
1032
        }
1033
        OplistCommand::Remove(users_or_keys) => {
×
1034
            let mut invalid_keys = vec![];
×
1035
            let mut invalid_users = vec![];
×
1036

1037
            let mut is_key = false;
×
1038
            for user_or_key in users_or_keys.split_whitespace() {
×
1039
                if user_or_key.starts_with("ssh-") {
×
1040
                    is_key = true;
×
1041
                    continue;
1042
                }
1043

1044
                if is_key {
×
1045
                    let key = user_or_key;
×
1046
                    match russh_keys::parse_public_key_base64(&key) {
×
1047
                        Ok(pk) => auth.remove_operator(pk.into()),
×
1048
                        Err(_) => invalid_keys.push(key.to_string()),
×
1049
                    }
1050
                    is_key = false;
×
1051
                } else {
1052
                    let user = user_or_key;
1053
                    let username = UserName::from(user);
×
1054
                    match room.try_find_member(&username).map(|m| &m.user) {
×
1055
                        Some(user) => auth.remove_operator(user.public_key().clone()),
×
1056
                        None => invalid_users.push(user.to_string()),
×
1057
                    }
1058
                }
1059
            }
1060

1061
            let mut messages = vec![];
×
1062
            if !invalid_keys.is_empty() {
×
1063
                messages.push(format!("Invalid keys: {}", invalid_keys.join(", ")));
×
1064
            }
1065
            if !invalid_users.is_empty() {
×
1066
                messages.push(format!("Invalid users: {}", invalid_users.join(", ")));
×
1067
            }
1068
            if messages.is_empty() {
×
1069
                messages.push(format!("Server operators list is updated!"));
×
1070
            }
1071

1072
            let message = message::System::new(user.into(), messages.join(utils::NEWLINE));
×
1073
            room.send_message(message.into()).await?;
×
1074
        }
1075
        OplistCommand::Load(mode) => {
×
1076
            if *mode == OplistLoadMode::Replace {
×
1077
                auth.clear_operators();
×
1078
            }
1079
            let message: Message = match auth.load_operators() {
×
1080
                Ok(_) => {
1081
                    let body = "Operators keys are up-to-date with the oplist file".to_string();
×
1082
                    message::System::new(user.into(), body).into()
×
1083
                }
1084
                Err(err) => {
×
1085
                    let body = err.to_string();
×
1086
                    message::Error::new(user.into(), body).into()
×
1087
                }
1088
            };
1089
            room.send_message(message).await?;
×
1090
        }
1091
        OplistCommand::Save => {
1092
            let message: Message = match auth.save_operators() {
×
1093
                Ok(_) => {
1094
                    let body = "Oplist file is up-to-date with the operators".to_string();
×
1095
                    message::System::new(user.into(), body).into()
×
1096
                }
1097
                Err(err) => {
×
1098
                    let body = err.to_string();
×
1099
                    message::Error::new(user.into(), body).into()
×
1100
                }
1101
            };
1102
            room.send_message(message).await?;
×
1103
        }
1104
        OplistCommand::Status => {
1105
            let auth = auth;
1106
            let mut messages: Vec<String> = vec![];
×
1107
            let mut online_operators: Vec<String> = vec![];
×
1108
            let mut offline_keys = vec![];
×
1109

1110
            for key in auth.operators() {
×
1111
                if let Some(user) = room
×
1112
                    .members_iter()
1113
                    .map(|(_, m)| &m.user)
×
1114
                    .find(|u| u.public_key() == key)
×
1115
                {
1116
                    online_operators.push(user.username().clone().into());
×
1117
                } else {
1118
                    offline_keys.push(key.fingerprint());
×
1119
                }
1120
            }
1121

1122
            if !online_operators.is_empty() {
×
1123
                messages.push(format!("Online operators: {}", online_operators.join(", ")));
×
1124
            }
1125

1126
            if !offline_keys.is_empty() {
×
1127
                messages.push(format!(
×
1128
                    "Operators offline keys: {}",
1129
                    offline_keys.join(", ")
×
1130
                ));
1131
            }
1132

1133
            let message = message::System::new(user.into(), messages.join(utils::NEWLINE));
×
1134
            room.send_message(message.into()).await?;
×
1135
        }
1136
        OplistCommand::Help => {
1137
            let mut help = format!("Available commands: {}", utils::NEWLINE);
×
1138
            help.push_str(&format_commands(&VISIBLE_OPLIST_COMMANDS));
×
1139

1140
            let message = message::System::new(user.into(), help);
×
1141
            room.send_message(message.into()).await?;
×
1142
        }
1143
    }
1144

1145
    Ok(())
×
1146
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc