Inanity 2: From RAGS to Riches ------------------------------ This is the second post in my planned epic three part tale. The following is poorly written, contains loads of spelling and gramatical errors, and is likely of interest to almost nobody. Proceed at your own risk!! * * * After being asked several times by various people whether I had my own gemlog, I finally spent some time last weekend doing something about this. Of course, in the spirit of this The Lambda Lab this meant implementing a new server in Scheme. :-) The goal was to have something like Scratchy [1] (my gopher server) but for gemini. Honestly I wasn't expecting this to be very hard at all. The only thing I was unsure of was the TLS aspect. I've never had anything to do with TLS, besides the experience getting elpher to initiate TLS connections to supporting gopher servers and then gemini servers. Some searching revealed that there already was an egg (that's what Chicken calls its packages :-) ) providing openssl bindings. Sadly, although it is nicely documented, the documentation lacks any examples. While this isn't usually a problem with scheme libraries, it was a show-stopper for me as - honestly - I didn't know what half of the options to the procedures used to listen for incomming connections actually meant. Eventually I went hunting through the source code of the one package that seemed to use this library: the HTTP library, Spiffy. Therein I was able to find the necessary examples, which honestly seemed too simple - most of the options to the listener-creation procedures being apparently unnecessary. I was able to use this to cobble together a simple test that would accept TLS connections on a port and send a "Hello, World!\n" when a client connected. After managing to get this working, replaced the hello world with a gemini header string and successfully managed to get av98 to connect to the port and process the result. At this point I figured I was home free. I spent an enjoyable hour or two writing the document delivery code. This was a wonderful experience which again drove home how much nicer it is to deal with gemini, both on the server and the client side, than gopher. The resulting server is at least as capable as Scratchy, but both shorter and less complex. (The ability of servers to indicate the mime-type of the content they produce makes so many things easier.) Now I must digress for a moment to gush about how lisps make the creation of extensible programs just incredibly trivial. Here's the definition of the procedure which reads a a scheme function from a file, then applies that function to the URI requested by the client: (define (serve-script config uri) (let* ((path (document-path config uri)) (proc (eval (with-input-from-file path read)))) (with-current-working-directory (pathname-directory (document-path config uri)) (lambda () (proc uri))))) If you're not used to lispy languages, this might just look like a mess of parentheses, but just look at the let binding up the top. The expression (eval (with-input-from-file path read)) is responsible for opening the file and parsing the contents as lisp expression, then evaluating the result. That file can have comments, the lisp expression can be arbitrarily long: it can have it's own imports, procedure definitions, etc etc. Talk about concise! All due to the power of `read'. There's a document [2] on Richard Stallman's web page where he discusses his various opinions on programming languages, among other things, and where he states that "Most other [non-lisp] languages have nothing comparable to `read', nothing comparable to `eval', and nothing comparable to `print'. What gaping deficiencies!" Even after a few short years hacking on elisp and scheme, I tend to agree. (Of course, the above procedure comes with a _bunch_ of security concerns, but (a) it's just a hobby server, and (b) as long as I carefully vet the extension scripts I run and take steps to ensure nothing I haven't vetted is ever run, I think I'm pretty safe.) Okay, so once the document serving code was complete, I quickly hooked it up to the TLS front-end, fired up a local instance and verified that av98 could connect and retrieve documents. Too easy! I got excited and, in a bout of prescient madness, gave the server its name: RAGS, the Right-Awful Gemini Server. All that remained was to cross-compile a static binary for deployment on my VPS... $ csc_linux -static rags.scm /usr/bin/ld: /usr/local/lib/chicken/11/openssl.o: in function `stub1057': openssl.static.c:(.text+0xaf3b): undefined reference to `SSL_get_peer_certificate' /usr/bin/ld: openssl.static.c:(.text+0xaf52): undefined reference to `X509_get_issuer_name' /usr/bin/ld: openssl.static.c:(.text+0xaf5f): undefined reference to `X509_NAME_oneline' /usr/bin/ld: openssl.static.c:(.text+0xaf6a): undefined reference to `X509_free' ... Okay, you get the idea. Now, I've had trouble statically compiling my chicken projects before. A couple of times I've even had to ask for help on the #chicken freenode channel. There's always been a solution, and the Chicken community is one of the most notoriously helpful and friendly around, so it's always been a pleasant experience. However in this instance I suspected that the problem was more severe than normal due to the involvement of openssl, and the fact that my server code relied on an egg which provided bindings to this library but didn't include the library itself. I thus shelved the whole idea of producing a static binary. You might be asking - why am I bothering with static binaries? Well, running a Chicken binary with shared library dependencies requires, of course, the Chicken libraries to be installed. This on its own isn't a problem: my VPS runs Debian and there's, of course, a chicken-bin package containing the required files. However my VPS is (was) running Debian 8, for which the most recent Chicken version available was 4.something. There's are some pretty big difference between Chicken 4 and 5, and all of my code is written assuming Chicken 5. Then why not just compile Chicken 5 on the VPS? Well, I tried that when I was just getting set up a little over a year ago, but here's the thing - my VPS only has 128MB of RAM. Which is enough for an amazing number of things but, apparently, not enough to compile Chicken 5 with gcc. Thus, for the whole of the last year, I've been hacking together gopher, mail, corewar servers, I've been using Chicken's -static option to produce hulking 5MB ELFs that run wherever. (In combination with a Docker container allowing me to build such executables on the distinctly non-GNU/Linux machine I've been suffering with, for my sins.) So... what now? Just for kicks, I decided to do what I should have tried ages ago - I tried to see how far I could get by compiling a dynamic executable and then simply copying .so files over to the VPS. It turns out that there really weren't many that needed to be copied: just libchicken.so and those corresponding to the individual eggs. And it worked! I shouldn't have been surprised, but honestly you doubt everything when you get to my age. I quickly replaced all of my old static executables for dynamic versions and saved a bunch of memory across the board. And, once I installed the required openssl libs on the server, I was able to get RAGS running too. Fantastic! Now I just needed to check that av98 could connect to thelambdalab.xyz... .. but no. No it doesn't. It refuses to connect. The server reports that it and av98 failed to agree on a cypher to use for the connection. AAArrrrgh!!! Okay, deep breaths. The first thing I tried was commenting out the line of the av98 python code that restricts the choice of cyphers. It was then able to connect without trouble, meaning that it really was an issue with the required cyphers not being made available by my server. But why was this only a problem on my VPS? After all, I was using the same openssl libraries there as in my local environment, wasn't I? To try to understand this, I used a script to spam the server with TLS connections restricted to particular cyphers so that I could build up a list of which cyphers were being made available. Repeating this on the server when running on my localhost confirmed that the cyphers supported by the server running on the VPS formed a much smaller subset of the ones made available by the server running on my home machine. At this point I began to suspect the problem. Debian 8 ships with libssl 1.0.1, while on my local machine I'm using libssl 1.1.0. Happily, my VPS provider sent me an email sometime over the last few months indicating that I could now upgrade to Debian 10 with a click of a button. After confirming that Debian 10 would indeed allow me to easily install later versions of this library (but sadly not Chicken..) I figured that today was the day to press this button. After an amazingly short time (seriously, maybe 30 seconds? a minute?) I was able to ssh into the freshly re-imaged server and begin the painful process of putting pieces of my Rube Goldberg configuration back together. It took nearly half a day, in the end, and this is despite having anticipated this nightmare from the beginning and fastidiously backing up every one of the small number of configuration files that I've modified over the last year. Oh how I wish I were running Guix or Nix. Anyway, _finally_ I was able to test RAGS on the new configuration and confirm that it was able to meet the cypher requirements of av98. I quickly put together a Gemini landing page, together with a dynamically generated (using those wonderful scheme extension scripts!) list of my recent phlog posts and an atom feed generator. After checking that everything ran smoothly and rendered nicely in elpher, bombadillo and av98, I went to bed. Which, if you made it through all of that sorry tale, I'm sure you'll want to do too! 0.o If you're keen to see what the state of things is now, head over to gemini://thelambdalab.xyz! And if you want to glance over the RAGS source, comment on it or submit patches, visit the project's gopher page at gopher://thelambdalab.xyz/1/projects/rags. Until next time... --- [1]: gopher://thelambdalab.xyz/1/projects/scratchy/ [2]: https://stallman.org/stallman-computing.html