2009-03-28 10:47:22 +00:00
;;; A basic XMPP library which should conform to RFCs 3920 and 3921
;;;
2009-04-01 10:15:16 +00:00
;;; Copyright (C) 2009 FoAM vzw.
;;;
;;; This package is free software: you can redistribute it and/or
;;; modify it under the terms of the GNU Lesser General Public
;;; License as published by the Free Software Foundation, either
;;; version 3 of the License, or (at your option) any later version.
;;;
;;; This program is distributed in the hope that it will be useful,
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;;; Lesser General Public License for more details.
;;;
;;; You can find a copy of the GNU Lesser General Public License at
;;; http://www.gnu.org/licenses/lgpl-3.0.html.
2009-03-28 10:47:22 +00:00
;;;
;;; Authors
;;;
;;; nik gaffney <nik@fo.am>
;;;
;;; Requirements
;;;
2009-04-01 10:15:16 +00:00
;;; PLT for now. TLS requires a version of PLT > 4.1.5.3
2009-03-28 10:47:22 +00:00
;;;
;;; Commentary
;;;
;;; Still a long way from implementing even a minimal subset of XMPP
;;;
;;; features implemented
2009-04-16 13:38:52 +00:00
;;; - plaintext sessions on port 5222
;;; - "old sytle" ssl sessions on port 5223 (default)
2009-03-28 10:47:22 +00:00
;;; - authenticate using an existing account
;;; - send messages (rfc 3921 sec.4)
;;; - send presence (rfc 3921 sec.5)
;;; - parse (some) xml reponses from server
;;; - handlers for responses
;;;
;;; features to implement
;;; - account creation
2009-04-01 10:15:16 +00:00
;;; - managing subscriptions (rfc 3921 sec.6)
2009-03-28 10:47:22 +00:00
;;; - rosters (rfc 3921 sec.7)
2009-04-01 10:15:16 +00:00
;;; - plaintext/tls/sasl negotiation (rfc 3920 sec.5 & 6)
2009-03-28 10:47:22 +00:00
;;; - encrypted connections using tls on port 5222
;;; - correct namespaces in sxml
;;; - message types
;;; - maintain session ids
;;; - maintain threads
;;; - error handling
;;; - events
;;; - [...]
;;; - rfc 3920
;;; - rfc 3921
;;;
2009-04-01 10:15:16 +00:00
;;; bugs and/or improvements
2009-04-16 13:38:52 +00:00
;;; - PLaneT installable
;;; - 'send' using call/cc vs 'parameter' i/o ports
;;; - coroutines for sasl negotiation
2009-03-28 10:47:22 +00:00
;;; - read-async & repsonse-handler
;;; - ssax:xml->sxml or lazy:xml->sxml
;;; - default handlers
2009-04-16 13:38:52 +00:00
;;; - syntax for defining sxpath based handlers
;;; - improve parsing
2009-03-28 10:47:22 +00:00
;;; - chatbot exmples
2009-04-16 13:38:52 +00:00
;;;
2009-03-28 10:47:22 +00:00
( module xmpp scheme
2009-04-01 10:15:16 +00:00
2009-03-28 10:47:22 +00:00
( provide ( all-defined-out )
;open-connection
;open-ssl-connection
;with-xmpp-session
)
2009-04-16 13:38:52 +00:00
( require ( planet lizorkin/sxml:2:1/sxml ) ) ;; encoding xml
( require ( planet lizorkin/ssax:2:0/ssax ) ) ;; decoding xml
( require mzlib/os ) ;; hostname
( require mzlib/defmacro ) ;; with-xmpp-session macro
( require scheme/tcp ) ;; networking
( require openssl ) ;; ssl/tls
( require srfi/13 ) ;; jid decoding
( require net/base64 ) ;; sasl
2009-03-28 10:47:22 +00:00
;;;;;;;;;;; ; ;;;; ; ;;; ; ; ;; ;
;;
;; networking
;;
;;;;;; ;; ;; ; ; ; ;
( define port 5222 )
( define ssl-port 5223 )
( define ( open-connection machine port handler )
( let-values ( ( ( in out )
( tcp-connect machine port ) ) )
( handler in out )
( close-output-port out )
( close-input-port in ) ) )
( define ( open-ssl-connection machine port handler )
( let-values ( ( ( in out )
( ssl-connect machine port 'tls ) ) )
( handler in out )
( close-output-port out )
( close-input-port in ) ) )
( define ( read-async in )
2009-04-01 10:15:16 +00:00
( bytes->string/utf-8 ( list->bytes ( read-async-bytes in ) ) ) )
( define ( read-async-bytes in )
( let ( ( bstr ' ( ) ) )
( when ( sync/timeout 0 in )
( set! bstr ( cons ( read-byte in ) ( read-async-bytes in ) ) ) ) bstr ) )
2009-03-28 10:47:22 +00:00
( define ssxml srl:sxml->xml-noindent )
;;;;;; ; ; ; ; ;; ;;;;;; ;
;;
;; XMPP stanzas
;;
;;;;;;;;;; ;;; ; ;; ; ;
;; intialization
( define ( xmpp-stream host )
2009-04-16 13:38:52 +00:00
( string-append "<?xml version='1.0'?><stream:stream xmlns:stream='http://etherx.jabber.org/streams' to='" host "' xmlns='jabber:client' >" ) ) ;; version='1.0' is a MUST for SASL on 5222 but NOT for ssl on 5223
2009-04-01 10:15:16 +00:00
2009-03-28 10:47:22 +00:00
;; authentication
( define ( xmpp-auth username password resource )
( ssxml ` ( iq ( @ ( type "set" ) ( id "auth" ) )
( query ( @ ( xmlns "jabber:iq:auth" ) )
( username , username )
( password , password )
( resource , resource ) ) ) ) )
2009-04-01 10:15:16 +00:00
2009-03-28 10:47:22 +00:00
( define ( xmpp-session host )
( ssxml ` ( iq ( @ ( to , host ) ( type "set" ) ( id "session" ) )
( session ( @ ( xmlns "urn:ietf:params:xml:ns:xmpp-session" ) ) ) ) ) )
2009-04-01 10:15:16 +00:00
;; messages
2009-03-28 10:47:22 +00:00
( define ( message to body )
( ssxml ` ( message ( @ ( to , to ) ) ( body , body ) ) ) )
2009-04-01 10:15:16 +00:00
2009-03-28 10:47:22 +00:00
;; presence
2009-04-16 13:38:52 +00:00
( define ( presence # :from ( from "" )
2009-03-28 10:47:22 +00:00
# :to ( to "" )
# :type ( type "" )
# :show ( show "" )
# :status ( status "" ) )
( cond ( ( not ( string=? status "" ) )
( ssxml ` ( presence ( @ ( type "probe" ) ) ( status , status ) ) ) )
( ( string=? type "" ) "<presence/>" )
( else ( ssxml ` ( presence ( @ ( type , type ) ) ) ) ) ) )
2009-04-16 13:38:52 +00:00
;; queries
( define ( iq body
# :from ( from "" )
# :to ( to "" )
# :type ( type "" )
# :id ( id "" ) )
( ssxml ` ( iq ( @ ( to , to ) ( type , type ) , body ) ) ) )
2009-03-28 10:47:22 +00:00
;; curried stanza disection (sxml stanza -> string)
( define ( ( sxpath-element xpath ) stanza )
( let ( ( node ( ( sxpath xpath ) stanza ) ) )
( if ( empty? node ) "" ( car node ) ) ) )
;; message
( define message-from ( sxpath-element "message/@from/text()" ) )
( define message-to ( sxpath-element "message/@to/text()" ) )
( define message-id ( sxpath-element "message/@id/text()" ) )
( define message-type ( sxpath-element "message/@type/text()" ) )
( define message-body ( sxpath-element "message/body/text()" ) )
( define message-subject ( sxpath-element "message/subject/text()" ) )
2009-04-01 10:15:16 +00:00
2009-03-28 10:47:22 +00:00
;; info/query
( define iq-type ( sxpath-element "iq/@type/text()" ) )
( define iq-id ( sxpath-element "iq/@id/text()" ) )
( define iq-error-type ( sxpath-element "iq/error/@type/text()" ) )
( define iq-error-text ( sxpath-element "iq/error/text()" ) )
( define iq-error ( sxpath-element "iq/error" ) )
2009-04-01 10:15:16 +00:00
2009-03-28 10:47:22 +00:00
;; presence
( define presence-show ( sxpath-element "presence/show/text()" ) )
( define presence-from ( sxpath-element "presence/@from/text()" ) )
( define presence-status ( sxpath-element "presence/status/text()" ) )
2009-04-01 10:15:16 +00:00
2009-04-16 13:38:52 +00:00
;;;; ;; ; ;;; ;
;;
;; tls & sasl
;; - http://xmpp.org/rfcs/rfc3920.html#tls
;; - http://xmpp.org/rfcs/rfc3920.html#sasl
;;
;;;; ;;
( define session->tls? #f ) ;; changes state when a tls proceed is recived
;; moved to xmpp-sasl until it 'works'
2009-03-28 10:47:22 +00:00
;;;;;;;;; ; ;; ; ; ;; ;; ; ;
;;
;; parsing & message/iq/error handlers
;; - minimal parsing
;; - handlers match on a tag (eg. 'message)
;; - handlers are called with a single relevant xmpp stanza
;;
;;;;;; ;; ; ; ;; ;
( define xmpp-handlers ( make-hash ) ) ;; a hash of tags and functions (possibly extend to using sxpaths)
2009-04-01 10:15:16 +00:00
2009-03-28 10:47:22 +00:00
( define ( set-xmpp-handler type fcn )
( dict-set! xmpp-handlers type fcn ) )
2009-04-16 13:38:52 +00:00
( define ( remove-xmpp-handler type fcn )
( dict-remove! xmpp-handlers type fcn ) )
2009-03-28 10:47:22 +00:00
( define ( run-xmpp-handler type sz )
( let ( ( fcn ( dict-ref xmpp-handlers type #f ) ) )
( when fcn ( begin
( display ( format "attempting to run handler ~a.~%" fcn ) )
( fcn sz ) ) ) ) )
2009-04-01 10:15:16 +00:00
2009-03-28 10:47:22 +00:00
;; no real parsing yet. dispatches any received xml stanzas as sxml
( define ( parse-xmpp-response str )
( when ( > ( string-length str ) 0 )
( let ( ( sz ( ssax:xml->sxml ( open-input-string ( clean str ) ) ' ( ) ) ) )
;;(let ((sz (lazy:xml->sxml (open-input-string str) '())))
( cond
( ( equal? ' ( null ) ( cadr sz ) )
( newline ) )
( ( equal? 'message ( caadr sz ) )
( run-xmpp-handler 'message sz ) )
( ( equal? 'iq ( caadr sz ) )
( run-xmpp-handler 'iq sz ) )
( ( equal? 'presence ( caadr sz ) )
( run-xmpp-handler 'presence sz ) )
2009-04-16 13:38:52 +00:00
( else ( run-xmpp-handler 'other sz ) ) ) ) ) )
2009-03-28 10:47:22 +00:00
;; example handlers to print stanzas or their contents
( define ( print-message sz )
( display ( format "a ~a message from ~a which says '~a.'~%" ( message-type sz ) ( message-from sz ) ( message-body sz ) ) ) )
2009-04-01 10:15:16 +00:00
2009-03-28 10:47:22 +00:00
( define ( print-iq sz )
2009-04-01 10:15:16 +00:00
( display ( format "an iq response of type '~a' with id '~a.'~%" ( iq-type sz ) ( iq-id sz ) ) ) )
2009-03-28 10:47:22 +00:00
( define ( print-presence sz )
( display ( format " p-r-e-s-e-n-e-c--> ~a is ~a" ( presence-from sz ) ( presence-status ) ) ) )
( define ( print-stanza sz )
( display ( format "? ?? -> ~%~a~%" sz ) ) )
;; QND hack to filter out anything not a message, iq or presence
( define ( clean str )
( let ( ( test ( substring str 0 3 ) ) )
2009-04-01 10:15:16 +00:00
( cond ( ( string-ci=? test "<me" ) str )
( ( string-ci=? test "<iq" ) str )
( ( string-ci=? test "<pr" ) str )
2009-04-16 13:38:52 +00:00
( ( string-ci=? test "<ur" ) str )
( else
( display ( format "~%recieved: ~a ~%parsed as <null/>~%~%" str ) )
"<null/>" ) ) ) )
2009-04-01 10:15:16 +00:00
;; response handler
2009-03-28 10:47:22 +00:00
( define ( xmpp-response-handler in )
( thread ( lambda ( )
( let loop ( )
2009-04-01 10:15:16 +00:00
( parse-xmpp-response ( read-async in ) )
( sleep 0.1 ) ;; slight delay to avoid a tight loop
2009-03-28 10:47:22 +00:00
( loop ) ) ) ) )
;; jid splicing (assuming the jid is in the format user@host/resource)
( define ( jid-user jid )
( string-take jid ( string-index jid #\@ ) ) )
2009-04-01 10:15:16 +00:00
2009-03-28 10:47:22 +00:00
( define ( jid-host jid )
( let* ( ( s ( string-take-right jid ( - ( string-length jid ) ( string-index jid #\@ ) 1 ) ) )
( v ( string-index s #\/ ) ) )
( if v ( string-take s v ) s ) ) )
2009-04-01 10:15:16 +00:00
2009-03-28 10:47:22 +00:00
( define ( jid-resource jid )
( let ( ( r ( jid-resource-0 jid ) ) )
( if ( void? r ) ( gethostname ) r ) ) )
2009-04-01 10:15:16 +00:00
2009-03-28 10:47:22 +00:00
( define ( jid-resource-0 jid )
( let ( ( v ( string-index jid #\/ ) ) )
( when v ( string-take-right jid ( - ( string-length jid ) v 1 ) ) ) ) )
2009-04-16 13:38:52 +00:00
2009-03-28 10:47:22 +00:00
;;;; ;; ; ; ;; ;; ;;;; ;
;;
;; interfaces
;;
;;;;; ;; ;;;; ; ;; ;
2009-04-01 10:15:16 +00:00
2009-04-16 13:38:52 +00:00
( define xmpp-in-port ( make-parameter ( current-input-port ) ) )
( define xmpp-out-port ( make-parameter ( current-output-port ) ) )
( define ( send str )
( printf "sending iO: ~a ~%~%" str )
( fprintf ( xmpp-out-port ) "~A~%" str ) ( flush-output ( xmpp-out-port ) ) )
2009-03-28 10:47:22 +00:00
( defmacro with-xmpp-session ( jid pass . body )
` ( let ( ( host ( jid-host , jid ) )
( user ( jid-user , jid ) )
( resource ( jid-resource , jid ) ) )
( let-values ( ( ( in out )
2009-04-01 10:15:16 +00:00
( ssl-connect host ssl-port 'tls ) ) )
;;(tcp-connect host port)))
2009-04-16 13:38:52 +00:00
( parameterize ( ( xmpp-in-port in )
( xmpp-out-port out ) )
( file-stream-buffer-mode out 'line )
( xmpp-response-handler in )
( send ( xmpp-stream host ) )
( send ( xmpp-session host ) )
;(starttls in out)
( send ( xmpp-auth user , pass resource ) )
( send ( presence ) )
( send ( presence # :status "Available" ) )
,@ body
( close-output-port out )
( close-input-port in ) ) ) ) )
2009-04-01 10:15:16 +00:00
) ;; end module