From 4f6f3dcd4b8c71f5caa52864092dbde22665a645 Mon Sep 17 00:00:00 2001 From: tjpcc Date: Mon, 30 Jan 2023 11:34:13 -0700 Subject: finger protocol --- finger/request.go | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 finger/request.go (limited to 'finger/request.go') diff --git a/finger/request.go b/finger/request.go new file mode 100644 index 0000000..833072d --- /dev/null +++ b/finger/request.go @@ -0,0 +1,76 @@ +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 +} -- cgit v1.2.3