Connecting to a Drupal XML-RPC server

The Drupal services module allows you to set up an XML RPC service (or other types, if you install extra modules) through which you can obtain information about nodes, users etc.

The following snippet connects to a drupal server and loads the node typed in by the user. It uses key-based authentication, so you will have to go to /admin/build/services/keys and generate a key under the domain 'testdomain', and replace the key in the code below by the generated key. You will also need to enable key authentication ("use keys") in /admin/build/services/settings, otherwise the whole hmac stuff is not neccessary. sessid should be disabled when you want to allow anonymous users to connect. If you enable sessid, all function calls change (...) and accept one extra argument before all the others: a session ID.

This session ID can be obtained by calling the "system.connect" function but without(!) going through the hmac generation. (the system.connect function has '#auth' => FALSE, '#key' => FALSE in its declaration)

(use xml-rpc-client sha2 extras srfi-4 srfi-4-utils)

;; Quick and dirty HMAC implementation. This should probably be added to the message-digest and sha2 eggs
(define (sha256-hmac key)
  (when (> (string-length key) 64) (set! key (sha256-binary-digest key)))
  (set! key (string-pad-right key 64 (integer->char 0)))
  (set! key (blob->u8vector (string->blob key)))
  (let ((ipad (blob->string (u8vector->blob (u8vector-map (lambda (v) (bitwise-xor v #x36)) key))))
        (opad (blob->string (u8vector->blob (u8vector-map (lambda (v) (bitwise-xor v #x5c)) key)))))
    (lambda (message)
      (sha256-digest (string-append opad (sha256-binary-digest (string-append ipad message)))))))
  
(define-record drupal-xml server domain api-key)

(define (drupal-connect url domain api-key)
  (make-drupal-xml (xml-rpc:server url) domain api-key))

;; Define a procedure to define Drupal procedures, with automatic key management
(define (drupal-proc conn procname)
  (let ((proc ((drupal-xml-server conn) procname))
	(hmac (sha256-hmac (drupal-xml-api-key conn))))
    (lambda args
      (let* ((timestamp (number->string (inexact->exact (current-seconds))))
	     ;; We use a random number between 0 and 999 as 'nonce'. Real implementations
	     ;; should ensure that the same nonce is not re-used within the time frame
	     ;; selected in the Drupal admin interface.
	     (rnd (number->string (random 1000)))
	     (hash (hmac (string-join (list timestamp (drupal-xml-domain conn) rnd procname) ";"))))
	(apply proc (string-downcase hash) (drupal-xml-domain conn) timestamp rnd args)))))

;; Connect to the server
(define my-connection (drupal-connect "http://localhost/services/xmlrpc" "testdomain" "c18d3861121dfa93bf9eca356cd1ca24"))

;; Define remote procedures
(define get-user (drupal-proc my-connection "user.get"))

;; Ask for a user number and display the result
(print "Please type a user number")
(pp (get-user (read)))

Note This code was tested with Drupal 6.12 and Services 6.x-0.13. Unfortunately, this version of the services module is a bit screwy and several things will not work correctly. For example, I was unable to make node.get work, and couldn't figure out how to make user.login work. Please update this wiki page if you find a better way to do it.