package finger import ( "bufio" "errors" "io" "net/url" "strings" "tildegit.org/tjp/gus" ) // ForwardingDenied is returned in response to requests for forwarding service. var ForwardingDenied = errors.New("Finger forwarding service denied.") // InvalidFingerQuery is sent when a client doesn't properly format the query. var InvalidFingerQuery = errors.New("Invalid finger query .") // ParseRequest builds a gus.Request by reading a finger protocol request. // // At the time of writing, there is no firm standard on how to represent finger // queries as URLs (the finger protocol itself predates URLs entirely), but there // are a few helpful resources to go from. // - The lynx browser supports finger URLs and documents the forms they may take: // https://lynx.invisible-island.net/lynx_help/lynx_url_support.html#finger_url // - There is an IETF draft: // https://datatracker.ietf.org/doc/html/draft-ietf-uri-url-finger // // As this function builds a *gus.Request (which is mostly a wrapper around a URL) // from nothing but an io.Reader, it doesn't have the context of the hostname which // the receiving server was hosting. So it only has the host component if it // arrived in the body of the query in the form username@hostname. Bear in mind that // in gus handlers, request objects will also carry a reference to the server so // that hostname is always available as request.Server.Hostname(). // // The primary deviation from the IETF draft is that a query-specified host becomes // the Host section of the URL, rather than remaining in the Path. Where the IETF draft // would consider a query of "tjp@ctrl-c.club\r\n" to be "finger:/tjp@ctrl-c.club", this // function will parse it into "finger://ctrl-c.club/tjp". This decision to separate the // query-specified host from the username is intended to make it easier to avoid // inadvertently acting as a jump host for example with: // `exec.Command("/usr/bin/finger", request.Path[1:])`. // // Consistent with the IETF draft, the /W whois switch is dropped and not represented // in the URL at all. // // In accordance with the recommendation of RFC 1288 section 3.2.1 // (https://datatracker.ietf.org/doc/html/rfc1288#section-3.2.1), any queries which // include a jump-host (user@host1@host2) are rejected with the ForwardingDenied error. func ParseRequest(rdr io.Reader) (*gus.Request, error) { line, err := bufio.NewReader(rdr).ReadString('\n') if err != nil { return nil, err } if line[len(line)-2] != '\r' { return nil, InvalidFingerQuery } line = strings.TrimSuffix(line, "\r\n") line = strings.TrimPrefix(line, "/W") line = strings.TrimLeft(line, " ") username, hostname, _ := strings.Cut(line, "@") if strings.Contains(hostname, "@") { return nil, ForwardingDenied } return &gus.Request{URL: &url.URL{ Scheme: "finger", Host: hostname, Path: "/" + username, OmitHost: true, //nolint:typecheck // (for some reason typecheck on drone-ci doesn't realize OmitHost is a field in url.URL) }}, nil }