aboutsummaryrefslogtreecommitdiff
path: root/finger.rs
blob: 13ed402c3d3a3f97860ffa1de26aa4f4e6ac71dd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// A simple finger client written in Rust implementing rfc1288. Zero dependencies outside std. Compiles with rustc:
// rustc finger.rs -o finger
// Usage:
// finger request@host
// e.g.

// finger aw@happynetbox.com

// You could also just use netcat:

// printf 'aw\r\n' | nc happnetbox.com 79

// The finger protocol is featured heavily in Elif Batuman's Pulitizer Prize nominated novel The Idiot.
// FINGER > FACEBOOK

use std::io::prelude::*;
use std::net::TcpStream;
use std::process;
use std::{env, io, str};

// supports standard finger queries and URIs
// From the drat spec:
// https://datatracker.ietf.org/doc/html/draft-ietf-uri-url-finger-02
// returns host, request
fn get_params(query: &str) -> (&str, &str) {
    // is URI
    if query.starts_with("finger://") {
        // kinda ugly
        let i = query[9..].find("/").unwrap_or(query.len() - 9);
        return (&query[9..9 + i], &query[9 + i..].trim_matches('/'));
    } else {
        let user: &str;
        let host: &str;
        let params: Vec<&str> = query.split("@").collect();
        if params.len() == 1 {
            host = params[0];
            user = "";
        } else {
            user = params[0];
            if params.len() > 2 || !user.chars().all(|c| c.is_ascii()) {
                println!("Username must be ascii and not include @");
                process::exit(1);
            }
            host = params[1];
        }
        return (host, user);
    }
}

fn main() -> std::io::Result<()> {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        println!("Usage: finger [request]@host or finger://host/request");
        process::exit(1);
    }

    let (host, user) = get_params(&args[1]);

    let dest: String;
    if !host.contains(":") {
        dest = format!("{}:79", host);
    } else {
        dest = host.to_owned();
    }
    let mut stream = TcpStream::connect(dest).expect("Couldn't connect to the server.");

    // Only supports queries of type Q1
    // https://datatracker.ietf.org/doc/html/rfc1288#section-2.3
    // Doesn't support long mode (/W) -- most servers online don't
    stream.write(user.as_bytes())?;
    stream.write(&[13, 10])?; // CLRF
    let mut line: Vec<u8> = vec![];
    // TODO -- filter out nonprintable
    stream.read_to_end(&mut line)?;

    // Assume UTF-8 input, against the spec, which predates UTF8
    println!("{}", str::from_utf8(&line).unwrap());
    Ok(())
}