Two Guys Arguing

asdf-install innards

Posted in common lisp by youngnh on 08.07.09

asdf-install’s README is good stuff.

For example, I did not know that sbcl has a standalone program for downloading lisp libraries:

sbcl-asdf-install drakma

will do the same thing as the more traditional method of starting sbcl, requiring asdf-install and then installing drakma.

The REAMDE also notes that the arguments to asdf-install may be:

– The name of a cliki page. asdf-install visits that page and finds
the download location from the `:(package)’ tag – usually rendered
as “Download ASDF package from …”

– A URL, which is downloaded directly

– A local tar.gz file, which is installed

If you look at the wiki source of http://www.cliki.net/Drakma, you’ll see a :(package) tag just as described:

:(package "http://weitz.de/files/drakma.tar.gz")

Poking around in the source of asdf-install, you’ll see this:

  
(let ((url
         (if (= (mismatch package-name-or-url "http://") 7)
             package-name-or-url
             (format nil "http://www.cliki.net/~A?download"
                     package-name-or-url))))

Which means that cliki generates a url for a page that ends in “?download”. Here’s the headers returned when requesting that url:

GET /Drakma?download HTTP/1.1
Host: www.cliki.net
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.1) Gecko/20090718 Shiretoko/3.5.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
If-Modified-Since: Fri, 07 Aug 2009 13:50:21 GMT

HTTP/1.x 302 Redirected
Date: Fri, 07 Aug 2009 13:58:26 GMT
Server: Araneida/0.84
Connection: close
Content-Type: text/html
Last-Modified: Fri, 07 Aug 2009 13:58:26 GMT
Location: http://weitz.de/files/drakma.tar.gz
Pragma: no-cache
Expires: Fri, 30 Oct 1998 14:19:41 GMT

Redirected to the file named in the :(package) tag.

The pages on cliki are freely editable. This means that it is trivially easy to make a piece of software, add a cliki page for it and anyone with asdf-install could install it, which is a fantastic and democratic way to distribute software.

However, by the same token, this is exactly why you should pay attention to the signatures of packages and download any that don’t match or aren’t trusted to see for yourself what you’re getting before loading and executing it.

See the “Making your package downloadable…” section of this cliki page for more details on how to publish your lisp code for all the world to use:
http://www.cliki.net/asdf-install

Tagged with: , ,

asdf-install and GPG signatures

Posted in common lisp by youngnh on 08.05.09

asdf-install is a very useful package manager for lisp code. like rubygems.

The idea is that you load up your lisp REPL, and then punch in (asdf-install:install 'some-package) to install whatever lisp library you might want. It’s then fetched from the internet, compiled and loaded into your running lisp. peachy.

I’m not sure about the specifics, but I think asdf-install looks for the software you’re trying to find on cliki, which is a public lisp wiki to which anyone can edit, or upload files.

asdf-install is smart about this sort of thing though, it expects any software you download from the internet to have an accompanying signature file. This means the author of the software package needs to sign it with his or her private key and put the signature in the same place as the package. asdf-install will then check that:

a) you have that author’s public key
b) you trust the key comes from who it says it does
c) you have given asdf-install permission to install software from that author

This is all well and good. Except it’s a bit of a headache in practice, and if the user so chooses, asdf-install will allow you to bypass these security checks.

My goal for this post was to run through an install without ever using the dubious 0: [SKIP-GPG-CHECK] Don't check GPG signature for this package restart.

Before I get in too deep, these following 3 links were a tremendous help in figuring what the hell was going on. I love Common Lisp, but the learning curve is very, very steep. I’ve been struggling off and on with these issues for 3 years and these pages go into a bit more depth than I do here:
http://common-lisp.net/project/asdf-install/tutorial/install.html
http://www.cliki.net/ASDF-Install%20and%20GPG
http://www.pps.jussieu.fr/~jch/software/pgp-validating.html


First thing I do is create my own public/private keypair:

gpg --gen-key

and download and import the cliki common-lisp.net developer keyring, which has the public keys of all developers publishing software on cliki:

wget http://common-lisp.net/keyring.asc
gpg --import keyring.asc

Next, start up a fresh sbcl 1.0.30 image:

$ sbcl
* (require 'asdf-install)
* (asdf-install:install 'drakma)

and…we run into our first problem, GPG warns that the key id 0x595FF045057958C6 (Dr. Edmund Weitz ) is not fully trusted. Simple enough to fix:

gpg --edit edi@weitz.de
> lsign
> save

and…it blows up again, saying Dr. Edmund Weitz (key id 595FF045057958C6) is not on your package supplier list. This time, the restart is fine, as asdf-install doesn’t recommend editing the package suppliers list by hand, so choose the 0: [ADD-KEY ] Add to package supplier list restart.

Next up, cl+ssl, a dependency of drakma, doesn’t have a signature file to be found. You can sneak a peak at the project’s releases here: http://common-lisp.net/project/cl-plus-ssl/download/ and see that although some of the older ones have signature files, there is, indeed, no signature for the latest release that asdf-install is trying to install. So download to your local machine, crack it open and take a quick look at the source, to be sure that it’s not malicious software. Then:

(asdf-install:install "cl+ssl.tar.gz")

Except even though you are now installing from a local file and asdf-install won’t check for a signature, the installation blows up again, because it depends on the trivial-gray-streams package, which it tries to fetch from the internet and is also missing a signature file. In fact, trivial-gray streams can be downloaded from the same directory as cl+ssl, and you can see for yourself that trivial-gray-streams doesn’t have a signature file either. So we download, peek inside and repeat the local asdf-install for trivial-gray-streams, then cl+ssl:

(asdf-install:install "trivial-gray-streams.tar.gz")
(asdf-install:install "cl+ssl.tar.gz")

This should result in stopping to alert you that the cffi and rt library authors are not trusted. Sign them with gpg, then hit the restarts to add them to the package suppliers list.

And things finally finish.


So that’s one kind of problem. Going through the process with cl-ppcre presents a somewhat different problem.

(asdf-install:install 'cl-ppcre)

When checking the signature of cl-unicode, you get the error No key found for key id 0x595FF045057958C6.

This is a misleading error message. We know it’s not true, since that’s Edi Weitz’s key and we’ve successfully installed software signed by him before. So, quit out of sbcl and let’s check that signature by hand:

wget http://weitz.de/files/cl-unicode.tar.gz
wget http://weitz.de/files/cl-unicode.tar.gz.asc
gpg cl-unicode.tar.gz.asc

and we see:

gpg: Signature made Wed 23 Jul 2008 05:28:42 PM CDT using DSA key ID 057958C6
gpg: BAD signature from "Dr. Edmund Weitz <edi@weitz.de>"

Dun dun duunnn. Here’s an example of exactly why asdf-install does these security checks. The signature provided couldn’t have come from the file we were about to download, nevermind the fact that asdf-install does a terrible job of letting us know this.

However, I have trouble believing this is malicious code since it did come from Edi Weitz’s server (which could have been hacked). Since we’ve downloaded the tarball to check it’s signature anyway, we can take a look inside and see that it’s not terribly obviously malicious. So the source was probably modified and just wasn’t re-signed, so we go through the procedure to install from a local file.

So there you have a rather long-winded tour of just about everything that could go wrong when installing lisp code from wild west internet wikis. asdf-install needs some polish for sure and the packaging practices of the software on common-lisp.net is lacking. I hear clbuild is a good alternative, but I don’t know much about it. I’d like to look into it and try this same excercise with it and post back.

Tagged with: , , , ,

One Infinite Loop

Posted in common lisp by youngnh on 08.03.09

I wanted to write a small bot to scrape my netflix queue from their website, but making a simple request to their homepage redirected in a loop forever.

I’ve used Edi Weitz’s Drakma library for this sort of thing before and I really like it. Here was the code that I wrote:

(let ((cookies (make-instance 'cookie-jar)))
  (http-request "https://www.netflix.com/"
		     :method :get
		     :cookie-jar cookies))

Requesting http://www.netflix.com would result in being redirected to http://www.netflix.com/Default?tcw=1&cqs=, which would result in being redirected back to netflix.com and so on forever. Drakma actually handled the situation very gracefully. After 6 redirect attempts, it threw an exception saying it had exceeded its redirection limit. I just couldn’t figure out why.

I tried messing with my user agent. Writing scrapers has always made me a little paranoid that the developers on the other end know what I’m up to, are locked in a struggle-to-the-death to prevent my unauthorized uses and of course use the most sophisticated and obvious option available to them: my User-Agent header. Drakma has a remarkably convenient built-in facility for spoofing major browser versions. It’s literally a 20 character change to the function call. I tried that. No dice. Apparently the Netflix developers are as overworked as everyone else and bots just aren’t a big enough problem to justify snuffing them out by filtering User-Agent headers.

The redirection eventually terminated in Firefox. Otherwise I and millions of other users wouldn’t be able to see the page at all. Maybe Firefox has a built in limit to redirection, at which point it just says “fuck it, I’m just gonna go with this page”. But that doesn’t make any sense either, becuase 302’s don’t come with any HTML to display just in case the browser decides it’s tired of bouncing from page to page.

Firebug’s net panel showed that with cookies on, I didn’t redirect at all, netflix.com returned a 200 OK. With cookies off, I redirected once to /Default, then back to the homepage and that time, a 200 came back with gloriously renderable HTML.

I sent Drakma’s HTTP headers to *standard-output* with a handy little one-liner:

(setq *header-stream* *standard-output*)

Here’s what I saw:

GET / HTTP/1.1
Host: www.netflix.com
User-Agent: Drakma/1.0.0 (SBCL 1.0.30; Linux; 2.6.30-ARCH; http://weitz.de/drakma/)
Accept: */*
Connection: close

HTTP/1.1 302 Moved Temporarily
Date: Mon, 03 Aug 2009 12:47:31 GMT
Server: Apache-Coyote/1.1
P3P: CP="CAO DSP COR DEVa TAIa OUR BUS UNI STA"
Location: http://www.netflix.com/Default?tcw=1&amp;cqs=
Content-Length: 0
Set-Cookie: VisitorId=XXX; Domain=.netflix.com; Path=/
Set-Cookie: country=X; Domain=.netflix.com; Expires=Tue, 03-Aug-2010 12:47:32 GMT; Path=/
Set-Cookie: nflxsid=XXX; Domain=.netflix.com; Path=/
Set-Cookie: NetflixSession=XXX; Domain=.netflix.com; Path=/
Set-Cookie: NetflixCookies=XXX; Domain=.netflix.com; Expires=Wed, 02-Sep-2009 12:47:32 GMT; Path=/
Set-Cookie: asearch=XXX; Domain=.netflix.com; Expires=Tue, 03-Aug-2010 12:47:32 GMT; Path=/
Vary: Accept-Encoding
Cache-Control: private
Keep-Alive: timeout=15, max=93
Connection: Keep-Alive
Content-Type: text/plain
Set-Cookie: NSC_xxx.ofugmjy.dpn=XXX;path=/

GET /Default?tcw=1&amp;cqs= HTTP/1.1
Host: www.netflix.com
User-Agent: Drakma/1.0.0 (SBCL 1.0.30; Linux; 2.6.30-ARCH; http://weitz.de/drakma/)
Accept: */*
Cookie: NSC_xxx.ofugmjy.dpn=XXX; asearch=XXX; NetflixCookies=XXX; NetflixSession=XXX; nflxsid=XXX; country=XXX; VisitorId=XXX
Connection: close

HTTP/1.1 302 Moved Temporarily
Date: Mon, 03 Aug 2009 12:47:32 GMT
Server: Apache-Coyote/1.1
P3P: CP="CAO DSP COR DEVa TAIa OUR BUS UNI STA"
Location: http://www.netflix.com/
Content-Length: 0
Set-Cookie: lastHitTime=XXX; Domain=.netflix.com; Path=/
Set-Cookie: VisitorId=XXX; Domain=.netflix.com; Path=/
Set-Cookie: country=XXX; Domain=.netflix.com; Expires=Tue, 03-Aug-2010 12:47:32 GMT; Path=/
Set-Cookie: nflxsid=XXX; Domain=.netflix.com; Path=/
Set-Cookie: NetflixCookies=XXX; Domain=.netflix.com; Expires=Wed, 02-Sep-2009 12:47:32 GMT; Path=/
Vary: Accept-Encoding
Cache-Control: private
Keep-Alive: timeout=15, max=84
Connection: Keep-Alive
Content-Type: text/plain

GET / HTTP/1.1
Host: www.netflix.com
User-Agent: Drakma/1.0.0 (SBCL 1.0.30; Linux; 2.6.30-ARCH; http://weitz.de/drakma/)
Accept: */*
Connection: close

HTTP/1.1 302 Moved Temporarily
Date: Mon, 03 Aug 2009 12:47:32 GMT
Server: Apache-Coyote/1.1
P3P: CP="CAO DSP COR DEVa TAIa OUR BUS UNI STA"
Location: http://www.netflix.com/Default?tcw=1&amp;cqs=
Content-Length: 0
Set-Cookie: VisitorId=XXX; Domain=.netflix.com; Path=/
Set-Cookie: country=XXX; Domain=.netflix.com; Expires=Tue, 03-Aug-2010 12:47:32 GMT; Path=/
Set-Cookie: nflxsid=XXX; Domain=.netflix.com; Path=/
Set-Cookie: NetflixSession=XXX; Domain=.netflix.com; Path=/
Set-Cookie: NetflixCookies=XXX; Domain=.netflix.com; Expires=Wed, 02-Sep-2009 12:47:32 GMT; Path=/
Set-Cookie: asearch=XXX; Domain=.netflix.com; Expires=Tue, 03-Aug-2010 12:47:32 GMT; Path=/
Vary: Accept-Encoding
Cache-Control: private
Keep-Alive: timeout=15, max=69
Connection: Keep-Alive
Content-Type: text/plain
Set-Cookie: NSC_xxx.ofugmjy.dpn=XXX;path=/

GET /Default?tcw=1&amp;cqs= HTTP/1.1
Host: www.netflix.com
User-Agent: Drakma/1.0.0 (SBCL 1.0.30; Linux; 2.6.30-ARCH; http://weitz.de/drakma/)
Accept: */*
Cookie: lastHitTime=XXX; NSC_xxx.ofugmjy.dpn=XXX; asearch=XXX; NetflixCookies=XXX; NetflixSession=XXX; nflxsid=XXX; country=XXX; VisitorId=XXX
Connection: close

HTTP/1.1 302 Moved Temporarily
Date: Mon, 03 Aug 2009 12:47:32 GMT
Server: Apache-Coyote/1.1
P3P: CP="CAO DSP COR DEVa TAIa OUR BUS UNI STA"
Location: http://www.netflix.com/
Content-Length: 0
Set-Cookie: lastHitTime=XXX; Domain=.netflix.com; Path=/
Set-Cookie: VisitorId=XXX; Domain=.netflix.com; Path=/
Set-Cookie: country=XXX; Domain=.netflix.com; Expires=Tue, 03-Aug-2010 12:47:33 GMT; Path=/
Set-Cookie: nflxsid=XXX; Domain=.netflix.com; Path=/
Set-Cookie: NetflixCookies=XXX; Domain=.netflix.com; Expires=Wed, 02-Sep-2009 12:47:33 GMT; Path=/
Vary: Accept-Encoding
Cache-Control: private
Keep-Alive: timeout=15, max=92
Connection: Keep-Alive
Content-Type: text/plain

GET / HTTP/1.1
Host: www.netflix.com
User-Agent: Drakma/1.0.0 (SBCL 1.0.30; Linux; 2.6.30-ARCH; http://weitz.de/drakma/)
Accept: */*
Connection: close

HTTP/1.1 302 Moved Temporarily
Date: Mon, 03 Aug 2009 12:47:32 GMT
Server: Apache-Coyote/1.1
P3P: CP="CAO DSP COR DEVa TAIa OUR BUS UNI STA"
Location: http://www.netflix.com/Default?tcw=1&amp;cqs=
Content-Length: 0
Set-Cookie: VisitorId=XXX; Domain=.netflix.com; Path=/
Set-Cookie: country=XXX; Domain=.netflix.com; Expires=Tue, 03-Aug-2010 12:47:33 GMT; Path=/
Set-Cookie: nflxsid=XXX; Domain=.netflix.com; Path=/
Set-Cookie: NetflixSession=XXX; Domain=.netflix.com; Path=/
Set-Cookie: NetflixCookies=XXX; Domain=.netflix.com; Expires=Wed, 02-Sep-2009 12:47:33 GMT; Path=/
Set-Cookie: asearch=XXX; Domain=.netflix.com; Expires=Tue, 03-Aug-2010 12:47:33 GMT; Path=/
Vary: Accept-Encoding
Cache-Control: private
Keep-Alive: timeout=15, max=93
Connection: Keep-Alive
Content-Type: text/plain
Set-Cookie: NSC_xxx.ofugmjy.dpn=XXX;path=/

GET /Default?tcw=1&amp;cqs= HTTP/1.1
Host: www.netflix.com
User-Agent: Drakma/1.0.0 (SBCL 1.0.30; Linux; 2.6.30-ARCH; http://weitz.de/drakma/)
Accept: */*
Cookie: lastHitTime=XXX; NSC_xxx.ofugmjy.dpn=XXX; asearch=XXX; NetflixCookies=XXX; NetflixSession=XXX; nflxsid=XXX; country=XXX; VisitorId=XXX
Connection: close

HTTP/1.1 302 Moved Temporarily
Date: Mon, 03 Aug 2009 12:47:32 GMT
Server: Apache-Coyote/1.1
P3P: CP="CAO DSP COR DEVa TAIa OUR BUS UNI STA"
Location: http://www.netflix.com/
Content-Length: 0
Set-Cookie: lastHitTime=XXX; Domain=.netflix.com; Path=/
Set-Cookie: VisitorId=XXX; Domain=.netflix.com; Path=/
Set-Cookie: country=XXX; Domain=.netflix.com; Expires=Tue, 03-Aug-2010 12:47:33 GMT; Path=/
Set-Cookie: nflxsid=XXX; Domain=.netflix.com; Path=/
Set-Cookie: NetflixCookies=XXX; Domain=.netflix.com; Expires=Wed, 02-Sep-2009 12:47:33 GMT; Path=/
Vary: Accept-Encoding
Cache-Control: private
Keep-Alive: timeout=15, max=15
Connection: Keep-Alive
Content-Type: text/plain

Huh, the issue was in Drakma. I would request their homepage with no cookies, and Netflix would respond with a redirect to a different URL and a bunch of Set-Cookie headers. Drakma would follow, dutifully passing a Cookie header, and be redirected back to the original URL, also with Set-Cookie headers. Drakma would again follow, but this time not include cookies in the request.

Netflix is doing the redirects — I think — to ‘prime the pump’. If you don’t send cookies to the second URL, you get redirected to a you need to turn cookies on page. If you do, Netflix figures you’ll end up back at their homepage with a bunch of initialized cookies. It’s weird behavior on the part of Drakma that’s breaking the system.

I wonder who to talk to about this? Edi Weitz? #lisp?

Tagged with: , , , , ,
Follow

Get every new post delivered to your Inbox.