logo

drewdevault.com

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

finger-client.md (3193B)


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