logo

drewdevault.com

[mirror] blog and personal website of Drew DeVault git clone https://hacktivis.me/git/mirror/drewdevault.com.git

finger-client.md (3218B)


  1. ---
  2. title: A finger client
  3. date: 2021-06-24
  4. outputs: [html, gemtext]
  5. ---
  6. This is a short follow-up to the [io_uring finger server][0] article posted
  7. about a month ago. In the time since, we have expanded our language with a more
  8. complete networking stack, most importantly by adding a DNS resolver. I have
  9. used these improvements to write a small client implementation of the [finger
  10. protocol][finger].
  11. [0]: https://drewdevault.com/2021/05/24/io_uring-finger-server.html
  12. [finger]: https://datatracker.ietf.org/doc/html/rfc1288
  13. ```hare
  14. use fmt;
  15. use io;
  16. use net::dial;
  17. use os;
  18. use strings;
  19. @init fn registersvc() void = dial::registersvc("tcp", "finger", [], 79);
  20. @noreturn fn usage() void = fmt::fatal("Usage: {} <user>[@<host>]", os::args[0]);
  21. export fn main() void = {
  22. if (len(os::args) != 2) usage();
  23. const items = strings::split(os::args[1], "@");
  24. defer free(items);
  25. if (len(items) == 0) usage();
  26. const user = items[0];
  27. const host = if (len(items) == 1) "localhost"
  28. else if (len(items) == 2) items[1]
  29. else usage();
  30. match (execute(user, host)) {
  31. err: dial::error => fmt::fatal(dial::strerror(err)),
  32. err: io::error => fmt::fatal(io::strerror(err)),
  33. void => void,
  34. };
  35. };
  36. fn execute(user: str, host: str) (void | dial::error | io::error) = {
  37. const conn = dial::dial("tcp", host, "finger")?;
  38. defer io::close(conn);
  39. fmt::fprintf(conn, "{}\r\n", user)?;
  40. io::copy(os::stdout, conn)?;
  41. };
  42. ```
  43. Technically, we could do more, but I chose to just address the most common
  44. use-case for finger servers in active use today: querying a specific user.
  45. Expanding this with full support for all finger requests would probably only
  46. grow this code by 2 or 3 times.
  47. Our language now provides a net::dial module, inspired by [Go's net.Dial][1] and
  48. the [Plan 9 dial function][2] Go is derived from. Our dial actually comes a bit
  49. closer to Plan 9 by re-introducing the service parameter &mdash; Plan 9's
  50. "tcp!example.org!http" becomes net::dial("tcp", "example.org", "http") in our
  51. language &mdash; which we use to find the port (unless you specify it in the
  52. address). The service parameter is tested against a small internal list of known
  53. services, and against /etc/services. We also automatically perform an SRV lookup
  54. for "_finger._tcp.example.org", so most programs written in our language will
  55. support SRV records with no additional effort.
  56. [1]: https://golang.org/pkg/net/#Dial
  57. [2]: http://man.9front.org/2/dial
  58. In our client code, we can see that the @init function adds "finger" to the list
  59. of known internal services. @init functions run on start-up, and this one just
  60. lets dial know about our protocol. Our network stack is open to extension in
  61. other respects, too &mdash; unlike Go, third-party libraries can define new
  62. protocol handlers for dial as well, perhaps opening it up in the future to
  63. networks like AF_BLUETOOTH, AF_AX25, and so on, complete with support for
  64. network-appropriate addresses and resolver functionality.
  65. The rest is pretty straightforward! We just parse the command line, dial the
  66. server, write the username to it, and splice the connection into stdout. Much
  67. simpler than the server. Future improvements might rewrite the CRLF to LF, but
  68. that's not particularly important.