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

unrenamed / chatd / 9973933170

17 Jul 2024 12:10PM UTC coverage: 32.813% (+16.6%) from 16.215%
9973933170

push

github

unrenamed
test: input validator

820 of 2499 relevant lines covered (32.81%)

0.8 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.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.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)
×
399
                            .user
×
400
                            .ignored
×
401
                            .insert(target_id);
×
402
                        let message = message::System::new(
403
                            user.into(),
×
404
                            format!("Ignoring: {}", target_username),
×
405
                        );
406
                        room.send_message(message.into()).await?;
×
407
                    }
408
                }
409
            }
410
            Command::Unignore(target_username) => 'label: {
×
411
                let member = room.find_member(username);
×
412
                let user = member.user.clone();
×
413

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

450
                if target.is_none() {
×
451
                    let focused_usernames: Vec<String> = user
×
452
                        .focused
×
453
                        .iter()
454
                        .filter_map(|id| room.try_get_name(id))
×
455
                        .map(|name| user.config.theme().style_username(name).to_string())
×
456
                        .collect();
457

458
                    let message_text = match focused_usernames.is_empty() {
×
459
                        true => "Focusing no users".to_string(),
×
460
                        false => format!(
×
461
                            "Focusing on {} users: {}",
462
                            focused_usernames.len(),
×
463
                            focused_usernames.join(", ")
×
464
                        ),
465
                    };
466

467
                    let message = message::System::new(user.into(), message_text);
×
468
                    room.send_message(message.into()).await?;
×
469
                    break 'label;
×
470
                }
471

472
                let target = target.as_deref().unwrap();
×
473
                if target == "$" {
×
474
                    room.find_member_mut(username).user.focused.clear();
×
475
                    let message = message::System::new(
476
                        user.into(),
×
477
                        "Removed focus from all users".to_string(),
×
478
                    );
479
                    room.send_message(message.into()).await?;
×
480
                    break 'label;
×
481
                }
482

483
                let mut focused = vec![];
×
484
                for target_username in target.split(",") {
×
485
                    let target_username = UserName::from(target_username);
×
486
                    match room
×
487
                        .try_find_member(&target_username)
×
488
                        .map(|a| a.user.id.clone())
×
489
                    {
490
                        None => continue,
×
491
                        Some(target_id) if target_id == user.id => continue,
×
492
                        Some(target_id) if user.focused.contains(&target_id) => continue,
×
493
                        Some(target_id) => {
×
494
                            room.find_member_mut(username)
×
495
                                .user
×
496
                                .focused
×
497
                                .insert(target_id);
×
498

499
                            focused.push(target_username);
×
500
                        }
501
                    }
502
                }
503

504
                let focused_usernames: Vec<String> = focused
×
505
                    .iter()
506
                    .map(|name| user.config.theme().style_username(name).to_string())
×
507
                    .collect();
508

509
                let message_text = match focused_usernames.is_empty() {
×
510
                    true => "No online users found to focus".to_string(),
×
511
                    false => format!(
×
512
                        "Focusing on {} users: {}",
513
                        focused_usernames.len(),
×
514
                        focused_usernames.join(", ")
×
515
                    ),
516
                };
517

518
                let message = message::System::new(user.into(), message_text);
×
519
                room.send_message(message.into()).await?;
×
520
            }
521
            Command::Version => {
×
522
                let message =
×
523
                    message::System::new(user.into(), format!("{}", env!("CARGO_PKG_VERSION")));
×
524
                room.send_message(message.into()).await?;
×
525
            }
526
            Command::Uptime => {
×
527
                let message = message::System::new(user.into(), room.uptime());
×
528
                room.send_message(message.into()).await?;
×
529
            }
530
            Command::Mute(target_username) => 'label: {
×
531
                if !auth.is_op(&user.public_key.clone().into()) {
×
532
                    let message =
×
533
                        message::Error::new(user.into(), "must be an operator".to_string());
×
534
                    room.send_message(message.into()).await?;
×
535
                    break 'label;
×
536
                }
537

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

581
                if !auth.is_op(&user.public_key.clone().into()) {
×
582
                    let message = message::Error::new(
583
                        user.into(),
×
584
                        "must be an operator to modify the MOTD".to_string(),
×
585
                    );
586
                    room.send_message(message.into()).await?;
×
587
                    break 'label;
×
588
                }
589

590
                room.set_motd(new_motd.as_deref().unwrap().to_string());
×
591

592
                let message = message::Announce::new(
593
                    user.into(),
×
594
                    format!(
×
595
                        "set new message of the day: {}-> {}",
×
596
                        utils::NEWLINE,
×
597
                        room.motd()
×
598
                    ),
599
                );
600
                room.send_message(message.into()).await?;
×
601
            }
602
            Command::Kick(target_username) => 'label: {
×
603
                if !auth.is_op(&user.public_key.clone().into()) {
×
604
                    let message =
×
605
                        message::Error::new(user.into(), "must be an operator".to_string());
×
606
                    room.send_message(message.into()).await?;
×
607
                    break 'label;
×
608
                }
609

610
                let target_username = UserName::from(target_username);
×
611
                match room.try_find_member_mut(&target_username) {
×
612
                    None => {
×
613
                        let message =
×
614
                            message::Error::new(user.into(), "user not found".to_string());
×
615
                        room.send_message(message.into()).await?;
×
616
                        break 'label;
×
617
                    }
618
                    Some(member) => {
×
619
                        let message = message::Announce::new(
620
                            user.into(),
×
621
                            format!("kicked {} from the server", target_username),
×
622
                        );
623
                        member.exit()?;
×
624
                        room.send_message(message.into()).await?;
×
625
                    }
626
                }
627
            }
628
            Command::Ban(query) => 'label: {
×
629
                if !auth.is_op(&user.public_key.clone().into()) {
×
630
                    let message =
×
631
                        message::Error::new(user.into(), "must be an operator".to_string());
×
632
                    room.send_message(message.into()).await?;
×
633
                    break 'label;
×
634
                }
635

636
                let query = query.parse::<BanQuery>();
×
637
                if let Err(err) = query {
×
638
                    let message = message::Error::new(user.into(), err.to_string());
×
639
                    room.send_message(message.into()).await?;
×
640
                    break 'label;
×
641
                }
642

643
                let mut messages: Vec<Message> = vec![];
×
644

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

676
                                    for (_, member) in room.members_iter_mut() {
×
677
                                        if member.user.username.eq(&username) {
×
678
                                            let message = message::Announce::new(
679
                                                user.clone().into(),
×
680
                                                format!("banned {} from the server", username),
×
681
                                            );
682
                                            messages.push(message.into());
×
683
                                            member.exit()?;
×
684
                                        }
685
                                    }
686
                                }
687
                                BanAttribute::Fingerprint(fingerprint) => {
×
688
                                    auth.ban_fingerprint(&fingerprint, item.duration);
×
689

690
                                    for (_, member) in room.members_iter_mut() {
×
691
                                        if member.user.public_key.fingerprint().eq(&fingerprint) {
×
692
                                            let message = message::Announce::new(
693
                                                user.clone().into(),
×
694
                                                format!(
×
695
                                                    "banned {} from the server",
×
696
                                                    member.user.username
×
697
                                                ),
698
                                            );
699
                                            messages.push(message.into());
×
700
                                            member.exit()?;
×
701
                                        }
702
                                    }
703
                                }
704
                                BanAttribute::Ip(_) => todo!(),
705
                            }
706
                        }
707
                    }
708
                }
709

710
                let message = message::System::new(
711
                    user.into(),
×
712
                    "Banning is complete. Offline users were silently banned.".to_string(),
×
713
                );
714
                messages.push(message.into());
×
715

716
                for message in messages {
×
717
                    room.send_message(message).await?;
×
718
                }
719
            }
720
            Command::Banned => 'label: {
×
721
                use std::fmt::Write;
722

723
                if !auth.is_op(&user.public_key.clone().into()) {
×
724
                    let message =
×
725
                        message::Error::new(user.into(), "must be an operator".to_string());
×
726
                    room.send_message(message.into()).await?;
×
727
                    break 'label;
×
728
                }
729

730
                let (names, fingerprints) = auth.banned();
×
731
                let mut banned = String::new();
×
732
                write!(banned, "Banned:").expect("Failed to write banned members to string");
×
733

734
                for name in names {
×
735
                    write!(banned, "{} \"name={}\"", utils::NEWLINE, name)
×
736
                        .expect("Failed to write banned members to string");
737
                }
738

739
                for fingerprint in fingerprints {
×
740
                    write!(banned, "{} \"fingerprint={}\"", utils::NEWLINE, fingerprint)
×
741
                        .expect("Failed to write banned members to string");
742
                }
743

744
                let message = message::System::new(user.into(), banned);
×
745
                room.send_message(message.into()).await?;
×
746
            }
747
            Command::Whitelist(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_whitelist_command(command, &user, room, auth).await?;
×
756
            }
757
            Command::Oplist(command) => 'label: {
×
758
                if !auth.is_op(&user.public_key.clone().into()) {
×
759
                    let message =
×
760
                        message::Error::new(user.into(), "must be an operator".to_string());
×
761
                    room.send_message(message.into()).await?;
×
762
                    break 'label;
×
763
                }
764

765
                exec_oplist_command(command, &user, room, auth).await?;
×
766
            }
767
        }
768

769
        Ok(())
×
770
    }
771

772
    fn next(&mut self) -> &mut Option<Box<dyn WorkflowHandler<H>>> {
×
773
        &mut self.next
×
774
    }
775
}
776

777
async fn exec_whitelist_command(
×
778
    command: &WhitelistCommand,
779
    user: &User,
780
    room: &mut ChatRoom,
781
    auth: &mut Auth,
782
) -> anyhow::Result<()> {
783
    match command {
×
784
        WhitelistCommand::On => {
785
            auth.enable_whitelist_mode();
×
786
            let message = message::System::new(
787
                user.into(),
×
788
                "Server whitelisting is now enabled".to_string(),
×
789
            );
790
            room.send_message(message.into()).await?;
×
791
        }
792
        WhitelistCommand::Off => {
793
            auth.disable_whitelist_mode();
×
794
            let message = message::System::new(
795
                user.into(),
×
796
                "Server whitelisting is now disabled".to_string(),
×
797
            );
798
            room.send_message(message.into()).await?;
×
799
        }
800
        WhitelistCommand::Add(users_or_keys) => {
×
801
            let mut invalid_keys = vec![];
×
802
            let mut invalid_users = vec![];
×
803

804
            let mut is_key = false;
×
805
            for user_or_key in users_or_keys.split_whitespace() {
×
806
                if user_or_key.starts_with("ssh-") {
×
807
                    is_key = true;
×
808
                    continue;
809
                }
810

811
                if is_key {
×
812
                    let key = user_or_key;
×
813
                    match russh_keys::parse_public_key_base64(&key) {
×
814
                        Ok(pk) => auth.add_trusted_key(pk.into()),
×
815
                        Err(_) => invalid_keys.push(key.to_string()),
×
816
                    }
817
                    is_key = false;
×
818
                } else {
819
                    let user = user_or_key;
820
                    let username = UserName::from(user);
×
821
                    match room.try_find_member(&username).map(|m| &m.user) {
×
822
                        Some(user) => auth.add_trusted_key(user.public_key.clone().into()),
×
823
                        None => invalid_users.push(user.to_string()),
×
824
                    }
825
                }
826
            }
827

828
            let mut messages = vec![];
×
829
            if !invalid_keys.is_empty() {
×
830
                messages.push(format!("Invalid keys: {}", invalid_keys.join(", ")));
×
831
            }
832
            if !invalid_users.is_empty() {
×
833
                messages.push(format!("Invalid users: {}", invalid_users.join(", ")));
×
834
            }
835
            if messages.is_empty() {
×
836
                messages.push(format!("Server whitelist is updated!"));
×
837
            }
838

839
            let message = message::System::new(user.into(), messages.join(utils::NEWLINE));
×
840
            room.send_message(message.into()).await?;
×
841
        }
842
        WhitelistCommand::Remove(users_or_keys) => {
×
843
            let mut invalid_keys = vec![];
×
844
            let mut invalid_users = vec![];
×
845

846
            let mut is_key = false;
×
847
            for user_or_key in users_or_keys.split_whitespace() {
×
848
                if user_or_key.starts_with("ssh-") {
×
849
                    is_key = true;
×
850
                    continue;
851
                }
852

853
                if is_key {
×
854
                    let key = user_or_key;
×
855
                    match russh_keys::parse_public_key_base64(&key) {
×
856
                        Ok(pk) => auth.remove_trusted_key(pk.into()),
×
857
                        Err(_) => invalid_keys.push(key.to_string()),
×
858
                    }
859
                    is_key = false;
×
860
                } else {
861
                    let user = user_or_key;
862
                    let username = UserName::from(user);
×
863
                    match room.try_find_member(&username).map(|m| &m.user) {
×
864
                        Some(user) => auth.remove_trusted_key(user.public_key.clone()),
×
865
                        None => invalid_users.push(user.to_string()),
×
866
                    }
867
                }
868
            }
869

870
            let mut messages = vec![];
×
871
            if !invalid_keys.is_empty() {
×
872
                messages.push(format!("Invalid keys: {}", invalid_keys.join(", ")));
×
873
            }
874
            if !invalid_users.is_empty() {
×
875
                messages.push(format!("Invalid users: {}", invalid_users.join(", ")));
×
876
            }
877
            if messages.is_empty() {
×
878
                messages.push(format!("Server whitelist is updated!"));
×
879
            }
880

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

923
            let auth = auth;
924
            let mut kicked = vec![];
×
925
            for (_, member) in room.members_iter() {
×
926
                if !auth.is_trusted(&member.user.public_key) {
×
927
                    kicked.push(member.user.clone());
×
928
                    member.exit()?;
×
929
                }
930
            }
931

932
            for user in kicked {
×
933
                let message = message::Announce::new(
934
                    user.into(),
×
935
                    "was kicked during pubkey reverification".to_string(),
×
936
                );
937
                room.send_message(message.into()).await?;
×
938
            }
939
        }
940
        WhitelistCommand::Status => {
941
            let auth = auth;
942
            let mut messages: Vec<String> = vec![];
×
943

944
            messages.push(
×
945
                String::from("Server whitelisting is ")
×
946
                    + match auth.is_whitelist_enabled() {
×
947
                        true => "enabled",
×
948
                        false => "disabled",
×
949
                    },
950
            );
951

952
            let mut trusted_online_users: Vec<String> = vec![];
×
953
            let mut trusted_keys = vec![];
×
954

955
            for key in auth.trusted_keys() {
×
956
                if let Some(user) = room
×
957
                    .members_iter()
958
                    .map(|(_, m)| &m.user)
×
959
                    .find(|u| u.public_key == *key)
×
960
                {
961
                    trusted_online_users.push(user.username.clone().into());
×
962
                } else {
963
                    trusted_keys.push(key.fingerprint());
×
964
                }
965
            }
966

967
            if !trusted_online_users.is_empty() {
×
968
                messages.push(format!(
×
969
                    "Trusted online users: {}",
970
                    trusted_online_users.join(", ")
×
971
                ));
972
            }
973

974
            if !trusted_keys.is_empty() {
×
975
                messages.push(format!("Trusted offline keys: {}", trusted_keys.join(", ")));
×
976
            }
977

978
            let message = message::System::new(user.into(), messages.join(utils::NEWLINE));
×
979
            room.send_message(message.into()).await?;
×
980
        }
981
        WhitelistCommand::Help => {
982
            let mut help = format!("Available commands: {}", utils::NEWLINE);
×
983
            help.push_str(&format_commands(&VISIBLE_WHITELIST_COMMANDS));
×
984

985
            let message = message::System::new(user.into(), help);
×
986
            room.send_message(message.into()).await?;
×
987
        }
988
    }
989

990
    Ok(())
×
991
}
992

993
async fn exec_oplist_command(
×
994
    command: &OplistCommand,
995
    user: &User,
996
    room: &mut ChatRoom,
997
    auth: &mut Auth,
998
) -> anyhow::Result<()> {
999
    match command {
×
1000
        OplistCommand::Add(users_or_keys) => {
×
1001
            let mut invalid_keys = vec![];
×
1002
            let mut invalid_users = vec![];
×
1003

1004
            let mut is_key = false;
×
1005
            for user_or_key in users_or_keys.split_whitespace() {
×
1006
                if user_or_key.starts_with("ssh-") {
×
1007
                    is_key = true;
×
1008
                    continue;
1009
                }
1010

1011
                if is_key {
×
1012
                    let key = user_or_key;
×
1013
                    match russh_keys::parse_public_key_base64(&key) {
×
1014
                        Ok(pk) => auth.add_operator(pk.into()),
×
1015
                        Err(_) => invalid_keys.push(key.to_string()),
×
1016
                    }
1017
                    is_key = false;
×
1018
                } else {
1019
                    let user = user_or_key;
1020
                    let username = UserName::from(user);
×
1021
                    match room.try_find_member(&username).map(|m| &m.user) {
×
1022
                        Some(user) => auth.add_operator(user.public_key.clone()),
×
1023
                        None => invalid_users.push(user.to_string()),
×
1024
                    }
1025
                }
1026
            }
1027

1028
            let mut messages = vec![];
×
1029
            if !invalid_keys.is_empty() {
×
1030
                messages.push(format!("Invalid keys: {}", invalid_keys.join(", ")));
×
1031
            }
1032
            if !invalid_users.is_empty() {
×
1033
                messages.push(format!("Invalid users: {}", invalid_users.join(", ")));
×
1034
            }
1035

1036
            if messages.is_empty() {
×
1037
                messages.push(format!("Server operators list is updated!"));
×
1038
            }
1039

1040
            let message = message::System::new(user.into(), messages.join(utils::NEWLINE));
×
1041
            room.send_message(message.into()).await?;
×
1042
        }
1043
        OplistCommand::Remove(users_or_keys) => {
×
1044
            let mut invalid_keys = vec![];
×
1045
            let mut invalid_users = vec![];
×
1046

1047
            let mut is_key = false;
×
1048
            for user_or_key in users_or_keys.split_whitespace() {
×
1049
                if user_or_key.starts_with("ssh-") {
×
1050
                    is_key = true;
×
1051
                    continue;
1052
                }
1053

1054
                if is_key {
×
1055
                    let key = user_or_key;
×
1056
                    match russh_keys::parse_public_key_base64(&key) {
×
1057
                        Ok(pk) => auth.remove_operator(pk.into()),
×
1058
                        Err(_) => invalid_keys.push(key.to_string()),
×
1059
                    }
1060
                    is_key = false;
×
1061
                } else {
1062
                    let user = user_or_key;
1063
                    let username = UserName::from(user);
×
1064
                    match room.try_find_member(&username).map(|m| &m.user) {
×
1065
                        Some(user) => auth.remove_operator(user.public_key.clone()),
×
1066
                        None => invalid_users.push(user.to_string()),
×
1067
                    }
1068
                }
1069
            }
1070

1071
            let mut messages = vec![];
×
1072
            if !invalid_keys.is_empty() {
×
1073
                messages.push(format!("Invalid keys: {}", invalid_keys.join(", ")));
×
1074
            }
1075
            if !invalid_users.is_empty() {
×
1076
                messages.push(format!("Invalid users: {}", invalid_users.join(", ")));
×
1077
            }
1078
            if messages.is_empty() {
×
1079
                messages.push(format!("Server operators list is updated!"));
×
1080
            }
1081

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

1120
            for key in auth.operators() {
×
1121
                if let Some(user) = room
×
1122
                    .members_iter()
1123
                    .map(|(_, m)| &m.user)
×
1124
                    .find(|u| u.public_key == *key)
×
1125
                {
1126
                    online_operators.push(user.username.clone().into());
×
1127
                } else {
1128
                    offline_keys.push(key.fingerprint());
×
1129
                }
1130
            }
1131

1132
            if !online_operators.is_empty() {
×
1133
                messages.push(format!("Online operators: {}", online_operators.join(", ")));
×
1134
            }
1135

1136
            if !offline_keys.is_empty() {
×
1137
                messages.push(format!(
×
1138
                    "Operators offline keys: {}",
1139
                    offline_keys.join(", ")
×
1140
                ));
1141
            }
1142

1143
            let message = message::System::new(user.into(), messages.join(utils::NEWLINE));
×
1144
            room.send_message(message.into()).await?;
×
1145
        }
1146
        OplistCommand::Help => {
1147
            let mut help = format!("Available commands: {}", utils::NEWLINE);
×
1148
            help.push_str(&format_commands(&VISIBLE_OPLIST_COMMANDS));
×
1149

1150
            let message = message::System::new(user.into(), help);
×
1151
            room.send_message(message.into()).await?;
×
1152
        }
1153
    }
1154

1155
    Ok(())
×
1156
}
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